mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 02:38:02 +00:00
Refactor out swing-related code from forge-gui into new forge-gui-desktop module
This commit is contained in:
BIN
forge-gui-desktop/src/main/config/Forge.icns
Normal file
BIN
forge-gui-desktop/src/main/config/Forge.icns
Normal file
Binary file not shown.
BIN
forge-gui-desktop/src/main/config/appbundler-1.0-custom.jar
Normal file
BIN
forge-gui-desktop/src/main/config/appbundler-1.0-custom.jar
Normal file
Binary file not shown.
BIN
forge-gui-desktop/src/main/config/backgroundImage.jpg
Normal file
BIN
forge-gui-desktop/src/main/config/backgroundImage.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 390 KiB |
26
forge-gui-desktop/src/main/config/builder/create-dmg.builder
Normal file
26
forge-gui-desktop/src/main/config/builder/create-dmg.builder
Normal file
@@ -0,0 +1,26 @@
|
||||
SET app_name create-dmg
|
||||
|
||||
VERSION create-dmg.cur create-dmg heads/master
|
||||
|
||||
NEWDIR build.dir temp %-build -
|
||||
|
||||
NEWFILE create-dmg.zip featured %.zip %
|
||||
|
||||
|
||||
COPYTO [build.dir]
|
||||
INTO create-dmg [create-dmg.cur]/create-dmg
|
||||
INTO sample [create-dmg.cur]/sample
|
||||
INTO support [create-dmg.cur]/support
|
||||
|
||||
SUBSTVARS [build.dir<alter>]/create-dmg [[]]
|
||||
|
||||
|
||||
ZIP [create-dmg.zip]
|
||||
INTO [build-files-prefix] [build.dir]
|
||||
|
||||
|
||||
PUT megabox-builds create-dmg.zip
|
||||
PUT megabox-builds build.log
|
||||
|
||||
PUT s3-builds create-dmg.zip
|
||||
PUT s3-builds build.log
|
||||
210
forge-gui-desktop/src/main/config/create-dmg
Normal file
210
forge-gui-desktop/src/main/config/create-dmg
Normal file
@@ -0,0 +1,210 @@
|
||||
#! /bin/bash
|
||||
|
||||
# Create a read-only disk image of the contents of a folder
|
||||
|
||||
set -e;
|
||||
|
||||
function pure_version() {
|
||||
echo '1.0.0.2'
|
||||
}
|
||||
|
||||
function version() {
|
||||
echo "create-dmg $(pure_version)"
|
||||
}
|
||||
|
||||
function usage() {
|
||||
version
|
||||
echo "Creates a fancy DMG file."
|
||||
echo "Usage: $(basename $0) options... image.dmg source_folder"
|
||||
echo "All contents of source_folder will be copied into the disk image."
|
||||
echo "Options:"
|
||||
echo " --volname name"
|
||||
echo " set volume name (displayed in the Finder sidebar and window title)"
|
||||
echo " --volicon icon.icns"
|
||||
echo " set volume icon"
|
||||
echo " --background pic.png"
|
||||
echo " set folder background image (provide png, gif, jpg)"
|
||||
echo " --window-pos x y"
|
||||
echo " set position the folder window"
|
||||
echo " --window-size width height"
|
||||
echo " set size of the folder window"
|
||||
echo " --icon-size icon_size"
|
||||
echo " set window icons size (up to 128)"
|
||||
echo " --icon file_name x y"
|
||||
echo " set position of the file's icon"
|
||||
echo " --hide-extension file_name"
|
||||
echo " hide the extension of file"
|
||||
echo " --custom-icon file_name custom_icon_or_sample_file x y"
|
||||
echo " set position and custom icon"
|
||||
echo " --app-drop-link x y"
|
||||
echo " make a drop link to Applications, at location x,y"
|
||||
echo " --eula eula_file"
|
||||
echo " attach a license file to the dmg"
|
||||
echo " --version show tool version number"
|
||||
echo " -h, --help display this help"
|
||||
exit 0
|
||||
}
|
||||
|
||||
WINX=10
|
||||
WINY=60
|
||||
WINW=500
|
||||
WINH=350
|
||||
ICON_SIZE=128
|
||||
|
||||
while test "${1:0:1}" = "-"; do
|
||||
case $1 in
|
||||
--volname)
|
||||
VOLUME_NAME="$2"
|
||||
shift; shift;;
|
||||
--volicon)
|
||||
VOLUME_ICON_FILE="$2"
|
||||
shift; shift;;
|
||||
--background)
|
||||
BACKGROUND_FILE="$2"
|
||||
BACKGROUND_FILE_NAME="$(basename $BACKGROUND_FILE)"
|
||||
BACKGROUND_CLAUSE="set background picture of opts to file \".background:$BACKGROUND_FILE_NAME\""
|
||||
shift; shift;;
|
||||
--icon-size)
|
||||
ICON_SIZE="$2"
|
||||
shift; shift;;
|
||||
--window-pos)
|
||||
WINX=$2; WINY=$3
|
||||
shift; shift; shift;;
|
||||
--window-size)
|
||||
WINW=$2; WINH=$3
|
||||
shift; shift; shift;;
|
||||
--icon)
|
||||
POSITION_CLAUSE="${POSITION_CLAUSE}set position of item \"$2\" to {$3, $4}
|
||||
"
|
||||
shift; shift; shift; shift;;
|
||||
--hide-extension)
|
||||
HIDING_CLAUSE="${HIDING_CLAUSE}set the extension hidden of item \"$2\" to true"
|
||||
shift; shift;;
|
||||
--custom-icon)
|
||||
shift; shift; shift; shift; shift;;
|
||||
-h | --help)
|
||||
usage;;
|
||||
--version)
|
||||
version; exit 0;;
|
||||
--pure-version)
|
||||
pure_version; exit 0;;
|
||||
--app-drop-link)
|
||||
APPLICATION_LINK=$2
|
||||
APPLICATION_CLAUSE="set position of item \"Applications\" to {$2, $3}
|
||||
"
|
||||
shift; shift; shift;;
|
||||
--eula)
|
||||
EULA_RSRC=$2
|
||||
shift; shift;;
|
||||
-*)
|
||||
echo "Unknown option $1. Run with --help for help."
|
||||
exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
test -z "$2" && {
|
||||
echo "Not enough arguments. Invoke with --help for help."
|
||||
exit 1
|
||||
}
|
||||
|
||||
DMG_PATH="$1"
|
||||
DMG_DIRNAME="$(dirname "$DMG_PATH")"
|
||||
DMG_DIR="$(cd $DMG_DIRNAME > /dev/null; pwd)"
|
||||
DMG_NAME="$(basename "$DMG_PATH")"
|
||||
DMG_TEMP_NAME="$DMG_DIR/rw.${DMG_NAME}"
|
||||
SRC_FOLDER="$(cd "$2" > /dev/null; pwd)"
|
||||
test -z "$VOLUME_NAME" && VOLUME_NAME="$(basename "$DMG_PATH" .dmg)"
|
||||
|
||||
AUX_PATH="$(dirname $0)/support"
|
||||
|
||||
test -d "$AUX_PATH" || {
|
||||
echo "Cannot find support directory: $AUX_PATH"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ -f "$SRC_FOLDER/.DS_Store" ]; then
|
||||
echo "Deleting any .DS_Store in source folder"
|
||||
rm "$SRC_FOLDER/.DS_Store"
|
||||
fi
|
||||
|
||||
# Create the image
|
||||
echo "Creating disk image..."
|
||||
test -f "${DMG_TEMP_NAME}" && rm -f "${DMG_TEMP_NAME}"
|
||||
ACTUAL_SIZE=`du -sm "$SRC_FOLDER" | sed -e 's/ .*//g'`
|
||||
DISK_IMAGE_SIZE=$(expr $ACTUAL_SIZE + 20)
|
||||
hdiutil create -srcfolder "$SRC_FOLDER" -volname "${VOLUME_NAME}" -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${DISK_IMAGE_SIZE}m "${DMG_TEMP_NAME}"
|
||||
|
||||
# mount it
|
||||
echo "Mounting disk image..."
|
||||
MOUNT_DIR="/Volumes/${VOLUME_NAME}"
|
||||
|
||||
# try unmount dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it)
|
||||
echo "Unmounting disk image..."
|
||||
DEV_NAME=$(hdiutil info | egrep '^/dev/' | sed 1q | awk '{print $1}')
|
||||
test -d "${MOUNT_DIR}" && hdiutil detach "${DEV_NAME}"
|
||||
|
||||
echo "Mount directory: $MOUNT_DIR"
|
||||
DEV_NAME=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | egrep '^/dev/' | sed 1q | awk '{print $1}')
|
||||
echo "Device name: $DEV_NAME"
|
||||
|
||||
if ! test -z "$BACKGROUND_FILE"; then
|
||||
echo "Copying background file..."
|
||||
test -d "$MOUNT_DIR/.background" || mkdir "$MOUNT_DIR/.background"
|
||||
cp "$BACKGROUND_FILE" "$MOUNT_DIR/.background/$BACKGROUND_FILE_NAME"
|
||||
fi
|
||||
|
||||
if ! test -z "$APPLICATION_LINK"; then
|
||||
echo "making link to Applications dir"
|
||||
echo $MOUNT_DIR
|
||||
ln -s /Applications "$MOUNT_DIR/Applications"
|
||||
fi
|
||||
|
||||
if ! test -z "$VOLUME_ICON_FILE"; then
|
||||
echo "Copying volume icon file '$VOLUME_ICON_FILE'..."
|
||||
cp "$VOLUME_ICON_FILE" "$MOUNT_DIR/.VolumeIcon.icns"
|
||||
SetFile -c icnC "$MOUNT_DIR/.VolumeIcon.icns"
|
||||
fi
|
||||
|
||||
# run applescript
|
||||
APPLESCRIPT=$(mktemp -t createdmg)
|
||||
cat "$AUX_PATH/template.applescript" | sed -e "s/WINX/$WINX/g" -e "s/WINY/$WINY/g" -e "s/WINW/$WINW/g" -e "s/WINH/$WINH/g" -e "s/BACKGROUND_CLAUSE/$BACKGROUND_CLAUSE/g" -e "s/ICON_SIZE/$ICON_SIZE/g" | perl -pe "s/POSITION_CLAUSE/$POSITION_CLAUSE/g" | perl -pe "s/APPLICATION_CLAUSE/$APPLICATION_CLAUSE/g" | perl -pe "s/HIDING_CLAUSE/$HIDING_CLAUSE/" >"$APPLESCRIPT"
|
||||
|
||||
echo "Running Applescript: /usr/bin/osascript \"${APPLESCRIPT}\" \"${VOLUME_NAME}\""
|
||||
"/usr/bin/osascript" "${APPLESCRIPT}" "${VOLUME_NAME}" || true
|
||||
echo "Done running the applescript..."
|
||||
sleep 4
|
||||
|
||||
rm "$APPLESCRIPT"
|
||||
|
||||
# make sure it's not world writeable
|
||||
echo "Fixing permissions..."
|
||||
chmod -Rf go-w "${MOUNT_DIR}" &> /dev/null || true
|
||||
echo "Done fixing permissions."
|
||||
|
||||
# make the top window open itself on mount:
|
||||
echo "Blessing started"
|
||||
bless --folder "${MOUNT_DIR}" --openfolder "${MOUNT_DIR}"
|
||||
echo "Blessing finished"
|
||||
|
||||
if ! test -z "$VOLUME_ICON_FILE"; then
|
||||
# tell the volume that it has a special file attribute
|
||||
SetFile -a C "$MOUNT_DIR"
|
||||
fi
|
||||
|
||||
# unmount
|
||||
echo "Unmounting disk image..."
|
||||
hdiutil detach "${DEV_NAME}"
|
||||
|
||||
# compress image
|
||||
echo "Compressing disk image..."
|
||||
hdiutil convert "${DMG_TEMP_NAME}" -format UDZO -imagekey zlib-level=9 -o "${DMG_DIR}/${DMG_NAME}"
|
||||
rm -f "${DMG_TEMP_NAME}"
|
||||
|
||||
# adding EULA resources
|
||||
if [ ! -z "${EULA_RSRC}" -a "${EULA_RSRC}" != "-null-" ]; then
|
||||
echo "adding EULA resources"
|
||||
"${AUX_PATH}/dmg-license.py" "${DMG_DIR}/${DMG_NAME}" "${EULA_RSRC}"
|
||||
fi
|
||||
|
||||
echo "Disk image done"
|
||||
exit 0
|
||||
3
forge-gui-desktop/src/main/config/forge.command
Normal file
3
forge-gui-desktop/src/main/config/forge.command
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd "`dirname \"$0\"`"
|
||||
java -Xmx1024m -jar $project.build.finalName$
|
||||
BIN
forge-gui-desktop/src/main/config/forge.ico
Normal file
BIN
forge-gui-desktop/src/main/config/forge.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
3
forge-gui-desktop/src/main/config/forge.sh
Normal file
3
forge-gui-desktop/src/main/config/forge.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd "`dirname \"$0\"`"
|
||||
java -Xmx1024m -jar $project.build.finalName$
|
||||
215
forge-gui-desktop/src/main/config/forge_checks.xml
Normal file
215
forge-gui-desktop/src/main/config/forge_checks.xml
Normal file
@@ -0,0 +1,215 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
|
||||
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
|
||||
|
||||
<!--
|
||||
|
||||
Checkstyle configuration that checks the sun coding conventions from:
|
||||
|
||||
- the Java Language Specification at
|
||||
http://java.sun.com/docs/books/jls/second_edition/html/index.html
|
||||
|
||||
- the Sun Code Conventions at http://java.sun.com/docs/codeconv/
|
||||
|
||||
- the Javadoc guidelines at
|
||||
http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
|
||||
|
||||
- the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
|
||||
|
||||
- some best practices
|
||||
|
||||
Checkstyle is very configurable. Be sure to read the documentation at
|
||||
http://checkstyle.sf.net (or in your downloaded distribution).
|
||||
|
||||
Most Checks are configurable, be sure to consult the documentation.
|
||||
|
||||
To completely disable a check, just comment it out or delete it from the file.
|
||||
|
||||
Finally, it is worth reading the documentation.
|
||||
|
||||
-->
|
||||
|
||||
<module name="Checker">
|
||||
<!--
|
||||
If you set the basedir property below, then all reported file
|
||||
names will be relative to the specified directory. See
|
||||
http://checkstyle.sourceforge.net/5.x/config.html#Checker
|
||||
|
||||
<property name="basedir" value="${basedir}"/>
|
||||
-->
|
||||
|
||||
<!-- Checks that a package-info.java file exists for each package. -->
|
||||
<!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage -->
|
||||
<!-- <module name="JavadocPackage"/> -->
|
||||
|
||||
<!-- Checks whether files end with a new line. -->
|
||||
<!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
|
||||
<module name="NewlineAtEndOfFile"><property name="lineSeparator" value="lf"/></module>
|
||||
|
||||
<!-- Checks that property files contain the same keys. -->
|
||||
<!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
|
||||
<module name="Translation"/>
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See http://checkstyle.sf.net/config_sizes.html -->
|
||||
<!-- <module name="FileLength"/> -->
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
|
||||
<module name="FileTabCharacter"/>
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See http://checkstyle.sf.net/config_misc.html -->
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="(^|[^\*])\s+$"/>
|
||||
<property name="minimum" value="0"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="message" value="Line has trailing spaces."/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<property name="cacheFile" value="${cacheFile}"/>
|
||||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
|
||||
<module name="JavadocMethod">
|
||||
<property name="scope" value="package"/>
|
||||
</module>
|
||||
<module name="JavadocType">
|
||||
<property name="scope" value="package"/>
|
||||
</module>
|
||||
<module name="JavadocVariable">
|
||||
<property name="scope" value="package"/>
|
||||
</module>
|
||||
<module name="JavadocStyle"/>
|
||||
|
||||
|
||||
<!-- Checks for Naming Conventions. -->
|
||||
<!-- See http://checkstyle.sf.net/config_naming.html -->
|
||||
<module name="ConstantName"/>
|
||||
<module name="LocalFinalVariableName"/>
|
||||
<module name="LocalVariableName"/>
|
||||
<module name="MemberName"/>
|
||||
|
||||
<!-- Allow test_ methods to have underscores in them. -->
|
||||
<module name="MethodName">
|
||||
<property name="format" value="^[a-z][a-zA-Z0-9]*$|^test_[_a-zA-Z0-9]*$"/>
|
||||
</module>
|
||||
|
||||
<module name="PackageName"/>
|
||||
<module name="ParameterName"/>
|
||||
<module name="StaticVariableName"/>
|
||||
<module name="TypeName"/>
|
||||
|
||||
|
||||
<!-- Checks for Headers -->
|
||||
<!-- See http://checkstyle.sf.net/config_header.html -->
|
||||
<!-- <module name="Header"> -->
|
||||
<!-- The follow property value demonstrates the ability -->
|
||||
<!-- to have access to ANT properties. In this case it uses -->
|
||||
<!-- the ${basedir} property to allow Checkstyle to be run -->
|
||||
<!-- from any directory within a project. See property -->
|
||||
<!-- expansion, -->
|
||||
<!-- http://checkstyle.sf.net/config.html#properties -->
|
||||
<!-- <property -->
|
||||
<!-- name="headerFile" -->
|
||||
<!-- value="${basedir}/java.header"/> -->
|
||||
<!-- </module> -->
|
||||
|
||||
<!-- Following interprets the header file as regular expressions. -->
|
||||
<!-- <module name="RegexpHeader"/> -->
|
||||
|
||||
|
||||
<!-- Checks for imports -->
|
||||
<!-- See http://checkstyle.sf.net/config_import.html -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="IllegalImport"/>
|
||||
<!-- defaults to sun.* packages -->
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See http://checkstyle.sf.net/config_sizes.html -->
|
||||
<!-- <module name="LineLength">
|
||||
<property name="max" value="180"/>
|
||||
</module>
|
||||
<module name="MethodLength"/>
|
||||
<module name="ParameterNumber"/> -->
|
||||
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
|
||||
<module name="EmptyForIteratorPad"/>
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoWhitespaceAfter">
|
||||
<property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS, UNARY_PLUS"/>
|
||||
</module>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="OperatorWrap"/>
|
||||
<module name="ParenPad"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround"/>
|
||||
|
||||
|
||||
<!-- Modifier Checks -->
|
||||
<!-- See http://checkstyle.sf.net/config_modifiers.html -->
|
||||
<module name="ModifierOrder"/>
|
||||
<module name="RedundantModifier"/>
|
||||
|
||||
|
||||
<!-- Checks for blocks. You know, those {}'s -->
|
||||
<!-- See http://checkstyle.sf.net/config_blocks.html -->
|
||||
<module name="AvoidNestedBlocks"/>
|
||||
|
||||
<!-- Allow empty blocks. <module name="EmptyBlock"/> -->
|
||||
|
||||
<module name="LeftCurly">
|
||||
<!-- Place left curly at EOL for one-line clauses,
|
||||
and on next line for multi-line clauses.
|
||||
-->
|
||||
<!-- <property name="option" value="nlow" /> -->
|
||||
</module>
|
||||
|
||||
<module name="NeedBraces"/>
|
||||
|
||||
<!-- Allow for right curly to appear on same or next line. <module name="RightCurly"/> -->
|
||||
|
||||
|
||||
<!-- Checks for common coding problems -->
|
||||
<!-- See http://checkstyle.sf.net/config_coding.html -->
|
||||
<!-- <module name="AvoidInlineConditionals"/> -->
|
||||
<module name="DoubleCheckedLocking"/>
|
||||
<!-- MY FAVOURITE -->
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="EqualsHashCode"/>
|
||||
<!-- <module name="HiddenField"/> -->
|
||||
<module name="IllegalInstantiation"/>
|
||||
<module name="InnerAssignment"/>
|
||||
<!-- <module name="MagicNumber"/> -->
|
||||
<module name="MissingSwitchDefault"/>
|
||||
<module name="RedundantThrows"/>
|
||||
<module name="SimplifyBooleanExpression"/>
|
||||
<module name="SimplifyBooleanReturn"/>
|
||||
|
||||
<!-- Checks for class design -->
|
||||
<!-- See http://checkstyle.sf.net/config_design.html -->
|
||||
<!-- <module name="DesignForExtension"/> -->
|
||||
<module name="FinalClass"/>
|
||||
<!-- <module name="HideUtilityClassConstructor"/> -->
|
||||
<module name="InterfaceIsType"/>
|
||||
<module name="VisibilityModifier"/>
|
||||
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See http://checkstyle.sf.net/config_misc.html -->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<!-- <module name="FinalParameters"/> -->
|
||||
<!-- <module name="TodoComment"/> -->
|
||||
<module name="UpperEll"/>
|
||||
|
||||
</module>
|
||||
|
||||
</module>
|
||||
141
forge-gui-desktop/src/main/config/support/dmg-license.py
Normal file
141
forge-gui-desktop/src/main/config/support/dmg-license.py
Normal file
@@ -0,0 +1,141 @@
|
||||
#! /usr/bin/env python
|
||||
"""
|
||||
This script adds a license file to a DMG. Requires Xcode and a plain ascii text
|
||||
license file.
|
||||
Obviously only runs on a Mac.
|
||||
|
||||
Copyright (C) 2011 Jared Hobbs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import optparse
|
||||
|
||||
|
||||
class Path(str):
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
os.unlink(self)
|
||||
|
||||
|
||||
def mktemp(dir=None, suffix=''):
|
||||
(fd, filename) = tempfile.mkstemp(dir=dir, suffix=suffix)
|
||||
os.close(fd)
|
||||
return Path(filename)
|
||||
|
||||
|
||||
def main(options, args):
|
||||
dmgFile, license = args
|
||||
with mktemp('.') as tmpFile:
|
||||
with open(tmpFile, 'w') as f:
|
||||
f.write("""data 'LPic' (5000) {
|
||||
$"0002 0011 0003 0001 0000 0000 0002 0000"
|
||||
$"0000 000E 0006 0001 0005 0007 0000 0007"
|
||||
$"0008 0000 0047 0009 0000 0034 000A 0001"
|
||||
$"0035 000B 0001 0020 000C 0000 0011 000D"
|
||||
$"0000 005B 0004 0000 0033 000F 0001 000C"
|
||||
$"0010 0000 000B 000E 0000"
|
||||
};\n\n""")
|
||||
with open(license, 'r') as l:
|
||||
f.write('data \'TEXT\' (5002, "English") {\n')
|
||||
for line in l:
|
||||
if len(line) < 1000:
|
||||
f.write(' "' + line.strip().replace('"', '\\"') +
|
||||
'\\n"\n')
|
||||
else:
|
||||
for liner in line.split('.'):
|
||||
f.write(' "' +
|
||||
liner.strip().replace('"', '\\"') +
|
||||
'. \\n"\n')
|
||||
f.write('};\n\n')
|
||||
f.write("""resource 'STR#' (5002, "English") {
|
||||
{
|
||||
"English",
|
||||
"Agree",
|
||||
"Disagree",
|
||||
"Print",
|
||||
"Save...",
|
||||
"IMPORTANT - By clicking on the \\"Agree\\" button, you agree "
|
||||
"to be bound by the terms of the License Agreement.",
|
||||
"Software License Agreement",
|
||||
"This text cannot be saved. This disk may be full or locked, or the "
|
||||
"file may be locked.",
|
||||
"Unable to print. Make sure you have selected a printer."
|
||||
}
|
||||
};""")
|
||||
os.system('/usr/bin/hdiutil unflatten -quiet "%s"' % dmgFile)
|
||||
os.system('%s "%s/"*.r %s -a -o "%s"' %
|
||||
(options.rez, options.flat_carbon, tmpFile, dmgFile))
|
||||
|
||||
os.system('/usr/bin/hdiutil flatten -quiet "%s"' % dmgFile)
|
||||
if options.compression is not None:
|
||||
os.system('cp %s %s.temp.dmg' % (dmgFile, dmgFile))
|
||||
os.remove(dmgFile)
|
||||
if options.compression == "bz2":
|
||||
os.system('hdiutil convert %s.temp.dmg -format UDBZ -o %s' %
|
||||
(dmgFile, dmgFile))
|
||||
elif options.compression == "gz":
|
||||
os.system('hdiutil convert %s.temp.dmg -format ' % dmgFile +
|
||||
'UDZO -imagekey zlib-devel=9 -o %s' % dmgFile)
|
||||
os.remove('%s.temp.dmg' % dmgFile)
|
||||
print "Successfully added license to '%s'" % dmgFile
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = optparse.OptionParser()
|
||||
parser.set_usage("""%prog <dmgFile> <licenseFile> [OPTIONS]
|
||||
This program adds a software license agreement to a DMG file.
|
||||
It requires Xcode and a plain ascii text <licenseFile>.
|
||||
|
||||
See --help for more details.""")
|
||||
parser.add_option(
|
||||
'--rez',
|
||||
'-r',
|
||||
action='store',
|
||||
default='/Applications/Xcode.app/Contents/Developer/Tools/Rez',
|
||||
help='The path to the Rez tool. Defaults to %default'
|
||||
)
|
||||
parser.add_option(
|
||||
'--flat-carbon',
|
||||
'-f',
|
||||
action='store',
|
||||
default='/Applications/Xcode.app/Contents/Developer/Platforms'
|
||||
'/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk'
|
||||
'/Developer/Headers/FlatCarbon',
|
||||
help='The path to the FlatCarbon headers. Defaults to %default'
|
||||
)
|
||||
parser.add_option(
|
||||
'--compression',
|
||||
'-c',
|
||||
action='store',
|
||||
choices=['bz2', 'gz'],
|
||||
default=None,
|
||||
help='Optionally compress dmg using specified compression type. '
|
||||
'Choices are bz2 and gz.'
|
||||
)
|
||||
options, args = parser.parse_args()
|
||||
cond = len(args) != 2 or not os.path.exists(options.rez) \
|
||||
or not os.path.exists(options.flat_carbon)
|
||||
if cond:
|
||||
parser.print_usage()
|
||||
sys.exit(1)
|
||||
main(options, args)
|
||||
@@ -0,0 +1,77 @@
|
||||
on run (volumeName)
|
||||
tell application "Finder"
|
||||
tell disk (volumeName as string)
|
||||
open
|
||||
|
||||
set theXOrigin to WINX
|
||||
set theYOrigin to WINY
|
||||
set theWidth to WINW
|
||||
set theHeight to WINH
|
||||
|
||||
set theBottomRightX to (theXOrigin + theWidth)
|
||||
set theBottomRightY to (theYOrigin + theHeight)
|
||||
set dsStore to "\"" & "/Volumes/" & volumeName & "/" & ".DS_STORE\""
|
||||
|
||||
tell container window
|
||||
set current view to icon view
|
||||
set toolbar visible to false
|
||||
set statusbar visible to false
|
||||
set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}
|
||||
set statusbar visible to false
|
||||
end tell
|
||||
|
||||
set opts to the icon view options of container window
|
||||
tell opts
|
||||
set icon size to ICON_SIZE
|
||||
set arrangement to not arranged
|
||||
end tell
|
||||
BACKGROUND_CLAUSE
|
||||
|
||||
-- Positioning
|
||||
POSITION_CLAUSE
|
||||
|
||||
-- Hiding
|
||||
HIDING_CLAUSE
|
||||
|
||||
-- Application Link Clause
|
||||
APPLICATION_CLAUSE
|
||||
close
|
||||
open
|
||||
|
||||
update without registering applications
|
||||
-- Force saving of the size
|
||||
delay 1
|
||||
|
||||
tell container window
|
||||
set statusbar visible to false
|
||||
set the bounds to {theXOrigin, theYOrigin, theBottomRightX - 10, theBottomRightY - 10}
|
||||
end tell
|
||||
|
||||
update without registering applications
|
||||
end tell
|
||||
|
||||
delay 1
|
||||
|
||||
tell disk (volumeName as string)
|
||||
tell container window
|
||||
set statusbar visible to false
|
||||
set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY}
|
||||
end tell
|
||||
|
||||
update without registering applications
|
||||
end tell
|
||||
|
||||
--give the finder some time to write the .DS_Store file
|
||||
delay 3
|
||||
|
||||
set waitTime to 0
|
||||
set ejectMe to false
|
||||
repeat while ejectMe is false
|
||||
delay 1
|
||||
set waitTime to waitTime + 1
|
||||
|
||||
if (do shell script "[ -f " & dsStore & " ]; echo $?") = "0" then set ejectMe to true
|
||||
end repeat
|
||||
log "waited " & waitTime & " seconds for .DS_STORE to be created."
|
||||
end tell
|
||||
end run
|
||||
100
forge-gui-desktop/src/main/html/connectionTest.html
Normal file
100
forge-gui-desktop/src/main/html/connectionTest.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Tail-based by Web Sockets</title>
|
||||
|
||||
|
||||
<link href="css/core.css" media="all" rel="stylesheet" type="text/css" />
|
||||
<script type="text/javascript" src="js/jquery/jquery-1.9.1.min.js"></script>
|
||||
<script type="text/javascript" src="js/observable.js"></script>
|
||||
<script type="text/javascript" src="js/socket.js"></script>
|
||||
<script type='text/javascript' src="js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="title">
|
||||
<span>Your server: <b>ws://</b></span>
|
||||
<input id="ws_uri" type="text" value="localhost:81/" name="uri"/>
|
||||
<button id="connect" name="connect" class="cn">Connect</button>
|
||||
<button id="disconn" name="disconn" class="dc" disabled="disabled">Disconnect</button>
|
||||
</div>
|
||||
<ul class="messages" id='messages'></ul>
|
||||
<div class="packets" id='input'>
|
||||
<span>Raw text to send:</span>
|
||||
<input type="text" name="packet" value="" />
|
||||
<button id="send" class="send">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
if (!window.WebSocket)
|
||||
alert("WebSocket not supported by this browser");
|
||||
|
||||
var server = Socket();
|
||||
var listener = {
|
||||
onOpen : function() {
|
||||
$('#input').slideDown();
|
||||
},
|
||||
|
||||
onMessage : function(m) {
|
||||
if (m.data) {
|
||||
addLi("incoming", m.data);
|
||||
}
|
||||
},
|
||||
|
||||
onClose : function(m) {
|
||||
addLi("error", "Connection was closed (" + m.code + "): " + m.reason);
|
||||
onDisconnectClicked();
|
||||
$('#input').fadeOut();
|
||||
}
|
||||
};
|
||||
server.addObserver(listener);
|
||||
|
||||
function addLi(className, text) {
|
||||
var spanText = document.createElement('li');
|
||||
spanText.className = className;
|
||||
spanText.innerHTML = text;
|
||||
var messageBox = $('#messages')[0];
|
||||
messageBox.appendChild(spanText);
|
||||
messageBox.scrollTop = messageBox.scrollHeight - messageBox.clientHeight;
|
||||
}
|
||||
|
||||
function onConnectClicked() {
|
||||
var uri = $("#ws_uri").val()
|
||||
addLi("connecting", "Connecting to ws://" + uri + " ..." )
|
||||
server.connect(uri);
|
||||
$('#connect').attr("disabled", "disabled")
|
||||
$('#disconn').removeAttr("disabled")
|
||||
|
||||
}
|
||||
|
||||
function onDisconnectClicked() {
|
||||
server.close();
|
||||
$('#disconn').attr("disabled", "disabled")
|
||||
$('#connect').removeAttr("disabled")
|
||||
|
||||
}
|
||||
|
||||
function onSendClicked() {
|
||||
var toSend = $("#input input").val();
|
||||
$("#input input").val("");
|
||||
addLi("outcoming", toSend);
|
||||
server.send(toSend)
|
||||
}
|
||||
|
||||
function onInputKey(event) {
|
||||
if( event.keyCode == 13 )
|
||||
onSendClicked();
|
||||
}
|
||||
|
||||
function onReady() {
|
||||
$('#connect').on("click", onConnectClicked);
|
||||
$('#disconn').on("click", onDisconnectClicked);
|
||||
$('#send').on("click", onSendClicked);
|
||||
$("#input input").on("keypress", onInputKey);
|
||||
}
|
||||
|
||||
$(onReady)
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
23
forge-gui-desktop/src/main/html/css/core.css
Normal file
23
forge-gui-desktop/src/main/html/css/core.css
Normal file
@@ -0,0 +1,23 @@
|
||||
ul, ol { margin: 0; }
|
||||
|
||||
div { border: 0px solid black; }
|
||||
div.wrap { width: 640px; margin: 100px auto;}
|
||||
|
||||
.title { height: 24px; background-color: #ddd; padding: 4px; border: 1px solid black; border-bottom: 0px }
|
||||
.title input {width: 300px; }
|
||||
.title span { display: inline-block; width: 120px; text-align: right; }
|
||||
|
||||
.messages { height: 30ex; overflow: auto; background-color: #fff; padding: 4px; border: 1px solid black; list-style: none; }
|
||||
.messages .incoming { color: #006; }
|
||||
.messages .incoming:before { content: "<< "}
|
||||
.messages .outcoming { color: #060; }
|
||||
.messages .outcoming:before { content: ">> "}
|
||||
.messages .error { color: #600; }
|
||||
.messages li:nth-child(2n) { background-color: #f7f7f7; }
|
||||
|
||||
.packets { padding: 4px; background-color: #ddd; border: 1px solid black; border-top: 0px; display: none; }
|
||||
.packets span { display: inline-block; width: 120px; text-align: right; }
|
||||
.packets input {width: 400px; }
|
||||
.packets button { width: 100px; }
|
||||
|
||||
span.alert { font-style: italic; }
|
||||
9597
forge-gui-desktop/src/main/html/js/jquery/jquery-1.9.1.js
vendored
Normal file
9597
forge-gui-desktop/src/main/html/js/jquery/jquery-1.9.1.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5
forge-gui-desktop/src/main/html/js/jquery/jquery-1.9.1.min.js
vendored
Normal file
5
forge-gui-desktop/src/main/html/js/jquery/jquery-1.9.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
23
forge-gui-desktop/src/main/html/js/observable.js
Normal file
23
forge-gui-desktop/src/main/html/js/observable.js
Normal file
@@ -0,0 +1,23 @@
|
||||
var Observable = function(eventNames) {
|
||||
var _t = {};
|
||||
var observers = {};
|
||||
|
||||
_t.addObserver = function(obj) {
|
||||
for(var i = 0; i < eventNames.length; i++) {
|
||||
var evName = eventNames[i]
|
||||
var method = obj["on" + evName];
|
||||
if( typeof(method) === 'function') {
|
||||
var handlers = observers[evName]
|
||||
if( 'undefined' === typeof(handler))
|
||||
handlers = observers[evName] = [];
|
||||
handlers.push(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_t.fireEvent = function() { // usually invoked as .apply(EventName, args)
|
||||
var q = observers[this]
|
||||
if ( q ) for( var i = 0; i < q.length; i++ ) q[i]['on'+ this].apply(q[i], arguments);
|
||||
}
|
||||
return _t;
|
||||
}
|
||||
27
forge-gui-desktop/src/main/html/js/socket.js
Normal file
27
forge-gui-desktop/src/main/html/js/socket.js
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
// There should be some kind of fallback to Flash-powered sockets (IE 9-, Opera with sockets switched off)
|
||||
var Socket = function() {
|
||||
var _t = Observable(["Open", "Message", "Close", "Error"]);
|
||||
|
||||
function onOpen() { _t.fireEvent.apply("Open", arguments); }
|
||||
function onClose() { _t.fireEvent.apply("Close", arguments); }
|
||||
function onError() { _t.fireEvent.apply("Error", arguments); }
|
||||
function onMessage() { _t.fireEvent.apply("Message", arguments); }
|
||||
|
||||
var ws;
|
||||
_t.connect = function(location) {
|
||||
ws = new WebSocket("ws://" + location);
|
||||
ws.onopen = onOpen;
|
||||
ws.onmessage = onMessage;
|
||||
ws.onclose = onClose;
|
||||
ws.onerror = onError;
|
||||
}
|
||||
|
||||
// _t.getWs = function() { return ws; }
|
||||
_t.isOpen = function() { return ws && ws.readyState == ws.OPEN; }
|
||||
_t.close = function() { ws && ws.close(); }
|
||||
_t.send = function(text) { text != null && text.length > 0 && ws && ws.send(text); };
|
||||
|
||||
return _t;
|
||||
};
|
||||
|
||||
369
forge-gui-desktop/src/main/java/forge/GuiDesktop.java
Normal file
369
forge-gui-desktop/src/main/java/forge/GuiDesktop.java
Normal file
@@ -0,0 +1,369 @@
|
||||
package forge;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.MenuElement;
|
||||
import javax.swing.MenuSelectionManager;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.assets.ISkinImage;
|
||||
import forge.control.FControl;
|
||||
import forge.error.BugReporter;
|
||||
import forge.events.UiEvent;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.GameType;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.combat.Combat;
|
||||
import forge.game.event.GameEventTurnBegan;
|
||||
import forge.game.phase.PhaseHandler;
|
||||
import forge.game.phase.PhaseType;
|
||||
import forge.game.player.LobbyPlayer;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.RegisteredPlayer;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.gui.CardListViewer;
|
||||
import forge.gui.FNetOverlay;
|
||||
import forge.gui.GuiChoose;
|
||||
import forge.gui.GuiUtils;
|
||||
import forge.gui.SOverlayUtils;
|
||||
import forge.gui.framework.SDisplayUtil;
|
||||
import forge.gui.framework.SLayoutIO;
|
||||
import forge.interfaces.IButton;
|
||||
import forge.interfaces.IGuiBase;
|
||||
import forge.item.PaperCard;
|
||||
import forge.match.input.InputQueue;
|
||||
import forge.net.FServer;
|
||||
import forge.screens.match.CMatchUI;
|
||||
import forge.screens.match.VMatchUI;
|
||||
import forge.screens.match.ViewWinLose;
|
||||
import forge.screens.match.controllers.CPrompt;
|
||||
import forge.screens.match.controllers.CStack;
|
||||
import forge.screens.match.views.VField;
|
||||
import forge.screens.match.views.VHand;
|
||||
import forge.screens.match.views.VPrompt;
|
||||
import forge.toolbox.FButton;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinImage;
|
||||
import forge.toolbox.special.PhaseLabel;
|
||||
import forge.util.BuildInfo;
|
||||
|
||||
public class GuiDesktop implements IGuiBase {
|
||||
public void invokeInEdtLater(Runnable runnable) {
|
||||
SwingUtilities.invokeLater(runnable);
|
||||
}
|
||||
|
||||
public void invokeInEdtAndWait(final Runnable proc) {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
// Just run in the current thread.
|
||||
proc.run();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(proc);
|
||||
}
|
||||
catch (final InterruptedException exn) {
|
||||
throw new RuntimeException(exn);
|
||||
}
|
||||
catch (final InvocationTargetException exn) {
|
||||
throw new RuntimeException(exn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isGuiThread() {
|
||||
return SwingUtilities.isEventDispatchThread();
|
||||
}
|
||||
|
||||
public String getAssetsRoot() {
|
||||
return StringUtils.containsIgnoreCase(BuildInfo.getVersionString(), "svn") ?
|
||||
"../forge-gui/res/" : "res/";
|
||||
}
|
||||
|
||||
public boolean mayShowCard(Card card) {
|
||||
return Singletons.getControl().mayShowCard(card);
|
||||
}
|
||||
|
||||
public void reportBug(String details) {
|
||||
BugReporter.reportBug(details);
|
||||
}
|
||||
public void reportException(Throwable ex) {
|
||||
BugReporter.reportException(ex);
|
||||
}
|
||||
public void reportException(Throwable ex, String message) {
|
||||
BugReporter.reportException(ex, message);
|
||||
}
|
||||
|
||||
public boolean showConfirmDialog(String message) {
|
||||
return FOptionPane.showConfirmDialog(message);
|
||||
}
|
||||
|
||||
public ISkinImage getUnskinnedIcon(String path) {
|
||||
return new FSkin.UnskinnedIcon(path);
|
||||
}
|
||||
|
||||
public int showOptionDialog(String message, String title, ISkinImage icon, String[] options, int defaultOption) {
|
||||
return FOptionPane.showOptionDialog(message, title, (SkinImage)icon, options, defaultOption);
|
||||
}
|
||||
|
||||
public <T> T showInputDialog(String message, String title, ISkinImage icon, T initialInput, T[] inputOptions) {
|
||||
return FOptionPane.showInputDialog(message, title, (SkinImage)icon, initialInput, inputOptions);
|
||||
}
|
||||
|
||||
public <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) {
|
||||
return GuiChoose.getChoices(message, min, max, choices, selected, display);
|
||||
}
|
||||
|
||||
public <T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax,
|
||||
final List<T> sourceChoices, final List<T> destChoices, final Card referenceCard, final boolean sideboardingMode) {
|
||||
return GuiChoose.order(title, top, sourceChoices, referenceCard);
|
||||
}
|
||||
|
||||
public void showCardList(final String title, final String message, final List<PaperCard> list) {
|
||||
final CardListViewer cardView = new CardListViewer(title, message, list);
|
||||
cardView.setVisible(true);
|
||||
cardView.dispose();
|
||||
}
|
||||
|
||||
public IButton getBtnOK() {
|
||||
return VMatchUI.SINGLETON_INSTANCE.getBtnOK();
|
||||
}
|
||||
|
||||
public IButton getBtnCancel() {
|
||||
return VMatchUI.SINGLETON_INSTANCE.getBtnCancel();
|
||||
}
|
||||
|
||||
public void focusButton(final IButton button) {
|
||||
// ensure we don't steal focus from an overlay
|
||||
if (!SOverlayUtils.overlayHasFocus()) {
|
||||
FThreads.invokeInEdtLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
((FButton)button).requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void flashIncorrectAction() {
|
||||
SDisplayUtil.remind(VPrompt.SINGLETON_INSTANCE);
|
||||
}
|
||||
|
||||
public void updatePhase() {
|
||||
PhaseHandler pH = Singletons.getControl().getObservedGame().getPhaseHandler();
|
||||
Player p = pH.getPlayerTurn();
|
||||
PhaseType ph = pH.getPhase();
|
||||
|
||||
final CMatchUI matchUi = CMatchUI.SINGLETON_INSTANCE;
|
||||
PhaseLabel lbl = matchUi.getFieldViewFor(p).getPhaseIndicator().getLabelFor(ph);
|
||||
|
||||
matchUi.resetAllPhaseButtons();
|
||||
if (lbl != null) lbl.setActive(true);
|
||||
}
|
||||
|
||||
public void updateTurn(final GameEventTurnBegan event, final Game game) {
|
||||
VField nextField = CMatchUI.SINGLETON_INSTANCE.getFieldViewFor(event.turnOwner);
|
||||
SDisplayUtil.showTab(nextField);
|
||||
CPrompt.SINGLETON_INSTANCE.updateText(game);
|
||||
}
|
||||
|
||||
public void updatePlayerControl() {
|
||||
CMatchUI.SINGLETON_INSTANCE.initHandViews(FServer.getLobby().getGuiPlayer());
|
||||
SLayoutIO.loadLayout(null);
|
||||
VMatchUI.SINGLETON_INSTANCE.populate();
|
||||
for (VHand h : VMatchUI.SINGLETON_INSTANCE.getHands()) {
|
||||
h.getLayoutControl().updateHand();
|
||||
}
|
||||
}
|
||||
|
||||
public void finishGame() {
|
||||
new ViewWinLose(Singletons.getControl().getObservedGame());
|
||||
SOverlayUtils.showOverlay();
|
||||
}
|
||||
|
||||
public void updateStack() {
|
||||
CStack.SINGLETON_INSTANCE.update();
|
||||
}
|
||||
|
||||
public void startMatch(GameType gameType, List<RegisteredPlayer> players) {
|
||||
FControl.instance.startMatch(gameType, players);
|
||||
}
|
||||
|
||||
public void setPanelSelection(Card c) {
|
||||
GuiUtils.setPanelSelection(c);
|
||||
}
|
||||
|
||||
public SpellAbility getAbilityToPlay(List<SpellAbility> abilities, Object triggerEvent) {
|
||||
if (triggerEvent == null) {
|
||||
if (abilities.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (abilities.size() == 1) {
|
||||
return abilities.get(0);
|
||||
}
|
||||
return GuiChoose.oneOrNone("Choose ability to play", abilities);
|
||||
}
|
||||
|
||||
if (abilities.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (abilities.size() == 1 && !abilities.get(0).promptIfOnlyPossibleAbility()) {
|
||||
if (abilities.get(0).canPlay()) {
|
||||
return abilities.get(0); //only return ability if it's playable, otherwise return null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//show menu if mouse was trigger for ability
|
||||
final JPopupMenu menu = new JPopupMenu("Abilities");
|
||||
|
||||
boolean enabled;
|
||||
boolean hasEnabled = false;
|
||||
int shortcut = KeyEvent.VK_1; //use number keys as shortcuts for abilities 1-9
|
||||
for (final SpellAbility ab : abilities) {
|
||||
enabled = ab.canPlay();
|
||||
if (enabled) {
|
||||
hasEnabled = true;
|
||||
}
|
||||
GuiUtils.addMenuItem(menu, FSkin.encodeSymbols(ab.toString(), true),
|
||||
shortcut > 0 ? KeyStroke.getKeyStroke(shortcut, 0) : null,
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CPrompt.SINGLETON_INSTANCE.getInputControl().selectAbility(ab);
|
||||
}
|
||||
}, enabled);
|
||||
if (shortcut > 0) {
|
||||
shortcut++;
|
||||
if (shortcut > KeyEvent.VK_9) {
|
||||
shortcut = 0; //stop adding shortcuts after 9
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasEnabled) { //only show menu if at least one ability can be played
|
||||
SwingUtilities.invokeLater(new Runnable() { //use invoke later to ensure first ability selected by default
|
||||
public void run() {
|
||||
MenuSelectionManager.defaultManager().setSelectedPath(new MenuElement[]{menu, menu.getSubElements()[0]});
|
||||
}
|
||||
});
|
||||
MouseEvent mouseEvent = (MouseEvent) triggerEvent;
|
||||
menu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
|
||||
}
|
||||
|
||||
return null; //delay ability until choice made
|
||||
}
|
||||
|
||||
public void hear(LobbyPlayer player, String message) {
|
||||
FNetOverlay.SINGLETON_INSTANCE.addMessage(player.getName(), message);
|
||||
}
|
||||
|
||||
public int getAvatarCount() {
|
||||
if (FSkin.isLoaded()) {
|
||||
FSkin.getAvatars().size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int showOptionDialog(String message, String title, FSkinProp icon, String[] options, int defaultOption) {
|
||||
return FOptionPane.showOptionDialog(message, title, FSkin.getImage(icon), options, defaultOption);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T showInputDialog(String message, String title, FSkinProp icon, T initialInput, T[] inputOptions) {
|
||||
return FOptionPane.showInputDialog(message, title, FSkin.getImage(icon), initialInput, inputOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fireEvent(UiEvent e) {
|
||||
CMatchUI.SINGLETON_INSTANCE.fireEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCard(Card card) {
|
||||
CMatchUI.SINGLETON_INSTANCE.setCard(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showCombat(Combat combat) {
|
||||
CMatchUI.SINGLETON_INSTANCE.showCombat(combat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsedToPay(Card card, boolean b) {
|
||||
CMatchUI.SINGLETON_INSTANCE.setUsedToPay(card, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHighlighted(Player player, boolean b) {
|
||||
CMatchUI.SINGLETON_INSTANCE.setHighlighted(player, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showPromptMessage(String message) {
|
||||
CMatchUI.SINGLETON_INSTANCE.showMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stopAtPhase(Player playerTurn, PhaseType phase) {
|
||||
return CMatchUI.SINGLETON_INSTANCE.stopAtPhase(playerTurn, phase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputQueue getInputQueue() {
|
||||
return FControl.instance.getInputQueue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Game getGame() {
|
||||
return FControl.instance.getObservedGame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateZones(List<Pair<Player, ZoneType>> zonesToUpdate) {
|
||||
CMatchUI.SINGLETON_INSTANCE.updateZones(zonesToUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCards(Set<Card> cardsToUpdate) {
|
||||
CMatchUI.SINGLETON_INSTANCE.updateCards(cardsToUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateManaPool(List<Player> manaPoolUpdate) {
|
||||
CMatchUI.SINGLETON_INSTANCE.updateManaPool(manaPoolUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLives(List<Player> livesUpdate) {
|
||||
CMatchUI.SINGLETON_INSTANCE.updateLives(livesUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endCurrentGame() {
|
||||
FControl.instance.endCurrentGame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Card, Integer> getDamageToAssign(Card attacker, List<Card> blockers,
|
||||
int damageDealt, GameEntity defender, boolean overrideOrder) {
|
||||
return CMatchUI.SINGLETON_INSTANCE.getDamageToAssign(attacker, blockers,
|
||||
damageDealt, defender, overrideOrder);
|
||||
}
|
||||
}
|
||||
218
forge-gui-desktop/src/main/java/forge/ImageCache.java
Normal file
218
forge-gui-desktop/src/main/java/forge/ImageCache.java
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.mortennobel.imagescaling.ResampleOp;
|
||||
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.assets.ImageUtil;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.IHasIcon;
|
||||
import forge.item.InventoryItem;
|
||||
import forge.properties.ForgeConstants;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinIcon;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* 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 25093 2014-03-08 05:36:37Z drdev $
|
||||
*/
|
||||
public class ImageCache {
|
||||
// short prefixes to save memory
|
||||
|
||||
private static final Set<String> _missingIconKeys = new HashSet<String>();
|
||||
private static final LoadingCache<String, BufferedImage> _CACHE = CacheBuilder.newBuilder().softValues().build(new ImageLoader());
|
||||
private static final BufferedImage _defaultImage;
|
||||
static {
|
||||
BufferedImage defImage = null;
|
||||
try {
|
||||
defImage = ImageIO.read(new File(ForgeConstants.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;
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
_CACHE.invalidateAll();
|
||||
_missingIconKeys.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve an image from the cache. returns null if the image is not found in the cache
|
||||
* and cannot be loaded from disk. pass -1 for width and/or height to avoid resizing in that dimension.
|
||||
*/
|
||||
public static BufferedImage getImage(Card card, int width, int height) {
|
||||
final String key;
|
||||
if (!Singletons.getControl().mayShowCard(card) || card.isFaceDown()) {
|
||||
key = ImageKeys.TOKEN_PREFIX + ImageKeys.MORPH_IMAGE;
|
||||
} else {
|
||||
key = card.getImageKey();
|
||||
}
|
||||
return scaleImage(key, width, height, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve an image from the cache. returns null if the image is not found in the cache
|
||||
* and cannot be loaded from disk. pass -1 for width and/or height to avoid resizing in that dimension.
|
||||
*/
|
||||
public static BufferedImage getImage(InventoryItem ii, int width, int height) {
|
||||
return scaleImage(ImageKeys.getImageKey(ii, false), width, height, 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 SkinIcon getIcon(IHasIcon ihi) {
|
||||
String imageKey = ihi.getIconImageKey();
|
||||
final BufferedImage i;
|
||||
if (_missingIconKeys.contains(imageKey) ||
|
||||
null == (i = scaleImage(ihi.getIconImageKey(), -1, -1, false))) {
|
||||
_missingIconKeys.add(imageKey);
|
||||
return FSkin.getIcon(FSkinProp.ICO_UNKNOWN);
|
||||
}
|
||||
return new FSkin.UnskinnedIcon(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 BufferedImage getOriginalImage(String imageKey, boolean useDefaultIfNotFound) {
|
||||
if (null == 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 = ImageUtil.getImageKey(ImageUtil.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.
|
||||
BufferedImage original = getImage(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 (original == null) {
|
||||
if (useDefaultIfNotFound) {
|
||||
original = _defaultImage;
|
||||
_CACHE.put(imageKey, _defaultImage);
|
||||
} else {
|
||||
original = null;
|
||||
}
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
private static BufferedImage scaleImage(String key, final int width, final int height, boolean useDefaultImage) {
|
||||
if (StringUtils.isEmpty(key) || (3 > width && -1 != width) || (3 > height && -1 != height)) {
|
||||
// picture too small or key not defined; return a blank
|
||||
return null;
|
||||
}
|
||||
|
||||
String resizedKey = String.format("%s#%dx%d", key, width, height);
|
||||
|
||||
final BufferedImage cached = _CACHE.getIfPresent(resizedKey);
|
||||
if (null != cached) {
|
||||
//System.out.println("found cached image: " + resizedKey);
|
||||
return cached;
|
||||
}
|
||||
|
||||
BufferedImage original = getOriginalImage(key, useDefaultImage);
|
||||
if (original == null) { return null; }
|
||||
|
||||
// Calculate the scale required to best fit the image into the requested
|
||||
// (width x height) dimensions whilst retaining aspect ratio.
|
||||
double scaleX = (-1 == width ? 1 : (double)width / original.getWidth());
|
||||
double scaleY = (-1 == height? 1 : (double)height / original.getHeight());
|
||||
double bestFitScale = Math.min(scaleX, scaleY);
|
||||
if ((bestFitScale > 1) && !ImageUtil.mayEnlarge()) {
|
||||
bestFitScale = 1;
|
||||
}
|
||||
|
||||
BufferedImage result;
|
||||
if (1 == bestFitScale) {
|
||||
result = original;
|
||||
} else {
|
||||
|
||||
int destWidth = (int)(original.getWidth() * bestFitScale);
|
||||
int destHeight = (int)(original.getHeight() * bestFitScale);
|
||||
|
||||
ResampleOp resampler = new ResampleOp(destWidth, destHeight);
|
||||
result = resampler.filter(original, null);
|
||||
}
|
||||
|
||||
//System.out.println("caching image: " + resizedKey);
|
||||
_CACHE.put(resizedKey, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Image corresponding to the key.
|
||||
*/
|
||||
private static BufferedImage getImage(final String key) {
|
||||
FThreads.assertExecutedByEdt(true);
|
||||
try {
|
||||
return ImageCache._CACHE.get(key);
|
||||
} catch (final ExecutionException ex) {
|
||||
if (ex.getCause() instanceof NullPointerException) {
|
||||
return null;
|
||||
}
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
} catch (final InvalidCacheLoadException ex) {
|
||||
// should be when a card legitimately has no image
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
forge-gui-desktop/src/main/java/forge/ImageLoader.java
Normal file
99
forge-gui-desktop/src/main/java/forge/ImageLoader.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package forge;
|
||||
|
||||
import com.google.common.cache.CacheLoader;
|
||||
|
||||
import forge.assets.ImageUtil;
|
||||
import forge.error.BugReporter;
|
||||
import forge.properties.ForgeConstants;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
final class ImageLoader extends CacheLoader<String, BufferedImage> {
|
||||
// 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 BufferedImage 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 = ForgeConstants.CACHE_TOKEN_PICS_DIR;
|
||||
} else if (key.startsWith(ImageKeys.ICON_PREFIX)) {
|
||||
filename = key.substring(ImageKeys.ICON_PREFIX.length());
|
||||
path = ForgeConstants.CACHE_ICON_PICS_DIR;
|
||||
} else if (key.startsWith(ImageKeys.BOOSTER_PREFIX)) {
|
||||
filename = key.substring(ImageKeys.BOOSTER_PREFIX.length());
|
||||
path = ForgeConstants.CACHE_BOOSTER_PICS_DIR;
|
||||
} else if (key.startsWith(ImageKeys.FATPACK_PREFIX)) {
|
||||
filename = key.substring(ImageKeys.FATPACK_PREFIX.length());
|
||||
path = ForgeConstants.CACHE_FATPACK_PICS_DIR;
|
||||
} else if (key.startsWith(ImageKeys.PRECON_PREFIX)) {
|
||||
filename = key.substring(ImageKeys.PRECON_PREFIX.length());
|
||||
path = ForgeConstants.CACHE_PRECON_PICS_DIR;
|
||||
} else if (key.startsWith(ImageKeys.TOURNAMENTPACK_PREFIX)) {
|
||||
filename = key.substring(ImageKeys.TOURNAMENTPACK_PREFIX.length());
|
||||
path = ForgeConstants.CACHE_TOURNAMENTPACK_PICS_DIR;
|
||||
} else {
|
||||
filename = key;
|
||||
path = ForgeConstants.CACHE_CARD_PICS_DIR;
|
||||
}
|
||||
|
||||
BufferedImage ret = _findFile(key, path, filename);
|
||||
|
||||
// some S00 cards are really part of 6ED
|
||||
if (null == ret ) {
|
||||
String s2kAlias = ImageUtil.getSetFolder("S00");
|
||||
if ( filename.startsWith(s2kAlias) ) {
|
||||
ret = _findFile(key, path, filename.replace(s2kAlias, ImageUtil.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 BufferedImage _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 ImageIO.read(file);
|
||||
} catch (IOException ex) {
|
||||
BugReporter.reportException(ex, "Could not read image file " + file.getAbsolutePath() + " ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
62
forge-gui-desktop/src/main/java/forge/Singletons.java
Normal file
62
forge-gui-desktop/src/main/java/forge/Singletons.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import forge.control.FControl;
|
||||
import forge.model.FModel;
|
||||
import forge.view.FView;
|
||||
|
||||
/**
|
||||
* Provides global/static access to singleton instances.
|
||||
*/
|
||||
public final class Singletons {
|
||||
private static boolean initialized = false;
|
||||
private static FView view = null;
|
||||
private static FControl control = null;
|
||||
|
||||
/**
|
||||
* IMPORTANT - does not return view frame! Must call
|
||||
* getFrame() from FView for that.
|
||||
*/
|
||||
public static FView getView() { return view; }
|
||||
public static FControl getControl() { return control; }
|
||||
|
||||
public static void initializeOnce(boolean withUi) {
|
||||
FThreads.assertExecutedByEdt(false);
|
||||
|
||||
synchronized (Singletons.class) {
|
||||
if (initialized) {
|
||||
throw new IllegalStateException("Singletons.initializeOnce really cannot be called again");
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
if (withUi) {
|
||||
view = FView.SINGLETON_INSTANCE;
|
||||
}
|
||||
|
||||
FModel.initialize(view == null ? null : view.getSplash().getProgressBar());
|
||||
|
||||
if (withUi) {
|
||||
control = FControl.instance;
|
||||
}
|
||||
}
|
||||
|
||||
// disallow instantiation
|
||||
private Singletons() { }
|
||||
}
|
||||
673
forge-gui-desktop/src/main/java/forge/control/FControl.java
Normal file
673
forge-gui-desktop/src/main/java/forge/control/FControl.java
Normal file
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
* 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.control;
|
||||
|
||||
import forge.FThreads;
|
||||
import forge.ImageCache;
|
||||
import forge.Singletons;
|
||||
import forge.ai.LobbyPlayerAi;
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.control.KeyboardShortcuts.Shortcut;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameRules;
|
||||
import forge.game.GameType;
|
||||
import forge.game.Match;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.player.LobbyPlayer;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.player.RegisteredPlayer;
|
||||
import forge.gui.GuiDialog;
|
||||
import forge.gui.SOverlayUtils;
|
||||
import forge.gui.framework.*;
|
||||
import forge.player.GamePlayerUtil;
|
||||
import forge.player.LobbyPlayerHuman;
|
||||
import forge.match.input.InputQueue;
|
||||
import forge.menus.ForgeMenu;
|
||||
import forge.model.FModel;
|
||||
import forge.net.FServer;
|
||||
import forge.properties.ForgePreferences;
|
||||
import forge.properties.ForgePreferences.FPref;
|
||||
import forge.properties.ForgeConstants;
|
||||
import forge.quest.QuestController;
|
||||
import forge.quest.data.QuestPreferences.QPref;
|
||||
import forge.quest.io.QuestDataIO;
|
||||
import forge.screens.deckeditor.CDeckEditorUI;
|
||||
import forge.screens.match.CMatchUI;
|
||||
import forge.screens.match.VMatchUI;
|
||||
import forge.screens.match.controllers.CDock;
|
||||
import forge.screens.match.controllers.CLog;
|
||||
import forge.screens.match.controllers.CPrompt;
|
||||
import forge.screens.match.controllers.CStack;
|
||||
import forge.screens.match.views.VAntes;
|
||||
import forge.screens.match.views.VDev;
|
||||
import forge.screens.match.views.VField;
|
||||
import forge.sound.SoundSystem;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.special.PhaseIndicator;
|
||||
import forge.view.FFrame;
|
||||
import forge.view.FView;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* FControl.
|
||||
* </p>
|
||||
* Controls all Forge UI functionality inside one JFrame. This class switches
|
||||
* between various display screens in that JFrame. Controllers are instantiated
|
||||
* separately by each screen's top level view class.
|
||||
*/
|
||||
public enum FControl implements KeyEventDispatcher {
|
||||
instance;
|
||||
|
||||
private ForgeMenu forgeMenu;
|
||||
private List<Shortcut> shortcuts;
|
||||
private JLayeredPane display;
|
||||
private FScreen currentScreen;
|
||||
private boolean altKeyLastDown;
|
||||
private CloseAction closeAction;
|
||||
|
||||
public static enum CloseAction {
|
||||
NONE,
|
||||
CLOSE_SCREEN,
|
||||
EXIT_FORGE
|
||||
}
|
||||
|
||||
private final SoundSystem soundSystem = new SoundSystem();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* FControl.
|
||||
* </p>
|
||||
* Controls all Forge UI functionality inside one JFrame. This class
|
||||
* switches between various display screens in that JFrame. Controllers are
|
||||
* instantiated separately by each screen's top level view class.
|
||||
*/
|
||||
private FControl() {
|
||||
Singletons.getView().getFrame().addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(final WindowEvent e) {
|
||||
switch (closeAction) {
|
||||
case NONE: //prompt user for close action if not previously specified
|
||||
String[] options = {"Close Screen", "Exit Forge", "Cancel"};
|
||||
int reply = FOptionPane.showOptionDialog(
|
||||
"Forge now supports navigation tabs which allow closing and switching between different screens with ease. "
|
||||
+ "As a result, you no longer need to use the X button in the upper right to close the current screen and go back."
|
||||
+ "\n\n"
|
||||
+ "Please select what you want to happen when clicking the X button in the upper right. This choice will be used "
|
||||
+ "going forward and you will not see this message again. You can change this behavior at any time in Preferences.",
|
||||
"Select Your Close Action",
|
||||
FOptionPane.INFORMATION_ICON,
|
||||
options,
|
||||
2);
|
||||
switch (reply) {
|
||||
case 0: //Close Screen
|
||||
setCloseAction(CloseAction.CLOSE_SCREEN);
|
||||
windowClosing(e); //call again to apply chosen close action
|
||||
return;
|
||||
case 1: //Exit Forge
|
||||
setCloseAction(CloseAction.EXIT_FORGE);
|
||||
windowClosing(e); //call again to apply chosen close action
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CLOSE_SCREEN:
|
||||
Singletons.getView().getNavigationBar().closeSelectedTab();
|
||||
break;
|
||||
case EXIT_FORGE:
|
||||
if (exitForge()) { return; }
|
||||
break;
|
||||
}
|
||||
//prevent closing Forge if we reached this point
|
||||
Singletons.getView().getFrame().setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CloseAction getCloseAction() {
|
||||
return this.closeAction;
|
||||
}
|
||||
|
||||
public void setCloseAction(CloseAction closeAction0) {
|
||||
if (this.closeAction == closeAction0) { return; }
|
||||
this.closeAction = closeAction0;
|
||||
Singletons.getView().getNavigationBar().updateBtnCloseTooltip();
|
||||
|
||||
final ForgePreferences prefs = FModel.getPreferences();
|
||||
prefs.setPref(FPref.UI_CLOSE_ACTION, closeAction0.toString());
|
||||
prefs.save();
|
||||
}
|
||||
|
||||
public boolean canExitForge(boolean forRestart) {
|
||||
String action = (forRestart ? "Restart" : "Exit");
|
||||
String userPrompt = "Are you sure you wish to " + (forRestart ? "restart" : "exit") + " Forge?";
|
||||
if (this.game != null) {
|
||||
userPrompt = "A game is currently active. " + userPrompt;
|
||||
}
|
||||
if (!FOptionPane.showConfirmDialog(userPrompt, action + " Forge", action, "Cancel", this.game == null)) { //default Yes if no game active
|
||||
return false;
|
||||
}
|
||||
if (!CDeckEditorUI.SINGLETON_INSTANCE.canSwitchAway(true)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean exitForge() {
|
||||
if (!canExitForge(false)) {
|
||||
return false;
|
||||
}
|
||||
Singletons.getView().getFrame().setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
System.exit(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** After view and model have been initialized, control can start.
|
||||
* @param isHeadlessMode */
|
||||
public void initialize() {
|
||||
// Preloads skin components (using progress bar).
|
||||
FSkin.loadFull(true);
|
||||
|
||||
this.shortcuts = KeyboardShortcuts.attachKeyboardShortcuts();
|
||||
this.display = FView.SINGLETON_INSTANCE.getLpnDocument();
|
||||
|
||||
final ForgePreferences prefs = FModel.getPreferences();
|
||||
|
||||
this.closeAction = CloseAction.valueOf(prefs.getPref(FPref.UI_CLOSE_ACTION));
|
||||
|
||||
FView.SINGLETON_INSTANCE.setSplashProgessBarMessage("About to load current quest.");
|
||||
// Preload quest data if present
|
||||
final File dirQuests = new File(ForgeConstants.QUEST_SAVE_DIR);
|
||||
final String questname = FModel.getQuestPreferences().getPref(QPref.CURRENT_QUEST);
|
||||
final File data = new File(dirQuests.getPath(), questname);
|
||||
if (data.exists()) {
|
||||
FModel.getQuest().load(QuestDataIO.loadData(data));
|
||||
}
|
||||
|
||||
// Handles resizing in null layouts of layers in JLayeredPane as well as saving window layout
|
||||
final FFrame window = Singletons.getView().getFrame();
|
||||
window.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(final ComponentEvent e) {
|
||||
sizeChildren();
|
||||
window.updateNormalBounds();
|
||||
SLayoutIO.saveWindowLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentMoved(final ComponentEvent e) {
|
||||
window.updateNormalBounds();
|
||||
SLayoutIO.saveWindowLayout();
|
||||
}
|
||||
});
|
||||
|
||||
FView.SINGLETON_INSTANCE.getLpnDocument().addMouseListener(SOverflowUtil.getHideOverflowListener());
|
||||
FView.SINGLETON_INSTANCE.getLpnDocument().addComponentListener(SResizingUtil.getWindowResizeListener());
|
||||
|
||||
setGlobalKeyboardHandler();
|
||||
|
||||
FView.SINGLETON_INSTANCE.setSplashProgessBarMessage("Opening main window...");
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Singletons.getView().initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setGlobalKeyboardHandler() {
|
||||
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
|
||||
manager.addKeyEventDispatcher(this);
|
||||
}
|
||||
|
||||
public ForgeMenu getForgeMenu() {
|
||||
if (this.forgeMenu == null) {
|
||||
this.forgeMenu = new ForgeMenu();
|
||||
}
|
||||
return this.forgeMenu;
|
||||
}
|
||||
|
||||
public FScreen getCurrentScreen() {
|
||||
return this.currentScreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches between display screens in top level JFrame.
|
||||
*/
|
||||
public boolean setCurrentScreen(FScreen screen) {
|
||||
return setCurrentScreen(screen, false);
|
||||
}
|
||||
public boolean setCurrentScreen(FScreen screen, boolean previousScreenClosed) {
|
||||
//TODO: Uncomment the line below if this function stops being used to refresh
|
||||
//the current screen in some places (such as Continue and Restart in the match screen)
|
||||
//if (this.currentScreen == screen) { return; }
|
||||
|
||||
//give previous screen a chance to perform special switch handling and/or cancel switching away from screen
|
||||
if (this.currentScreen != screen && !Singletons.getView().getNavigationBar().canSwitch(screen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.currentScreen == FScreen.MATCH_SCREEN) { //hide targeting overlay and reset image if was on match screen
|
||||
SOverlayUtils.hideTargetingOverlay();
|
||||
if (isMatchBackgroundImageVisible()) {
|
||||
FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(new ImageIcon());
|
||||
}
|
||||
}
|
||||
|
||||
clearChildren(JLayeredPane.DEFAULT_LAYER);
|
||||
SOverlayUtils.hideOverlay();
|
||||
ImageCache.clear(); //reduce memory usage by clearing image cache when switching screens
|
||||
|
||||
this.currentScreen = screen;
|
||||
|
||||
//load layout for new current screen
|
||||
try {
|
||||
SLayoutIO.loadLayout(null);
|
||||
} catch (InvalidLayoutFileException ex) {
|
||||
GuiDialog.message("Your " + screen.getTabCaption() + " layout file could not be read. It will be deleted after you press OK.\nThe game will proceed with default layout.");
|
||||
if (screen.deleteLayoutFile()) {
|
||||
SLayoutIO.loadLayout(null); //try again
|
||||
}
|
||||
}
|
||||
|
||||
screen.getView().populate();
|
||||
screen.getController().initialize();
|
||||
|
||||
if (screen == FScreen.MATCH_SCREEN) {
|
||||
if (isMatchBackgroundImageVisible()) {
|
||||
FView.SINGLETON_INSTANCE.getPnlInsets().setForegroundImage(FSkin.getIcon(FSkinProp.BG_MATCH));
|
||||
}
|
||||
SOverlayUtils.showTargetingOverlay();
|
||||
}
|
||||
|
||||
Singletons.getView().getNavigationBar().updateSelectedTab();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isMatchBackgroundImageVisible() {
|
||||
return FModel.getPreferences().getPrefBoolean(FPref.UI_MATCH_IMAGE_VISIBLE);
|
||||
}
|
||||
|
||||
public boolean ensureScreenActive(FScreen screen) {
|
||||
if (this.currentScreen == screen) { return true; }
|
||||
|
||||
return setCurrentScreen(screen);
|
||||
}
|
||||
|
||||
/** @return List<Shortcut> A list of attached keyboard shortcut descriptions and properties. */
|
||||
public List<Shortcut> getShortcuts() {
|
||||
return this.shortcuts;
|
||||
}
|
||||
|
||||
/** Remove all children from a specified layer. */
|
||||
private void clearChildren(final int layer0) {
|
||||
final Component[] children = FView.SINGLETON_INSTANCE.getLpnDocument().getComponentsInLayer(layer0);
|
||||
|
||||
for (final Component c : children) {
|
||||
display.remove(c);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sizes children of JLayeredPane to fully fit their layers. */
|
||||
private void sizeChildren() {
|
||||
Component[] children = display.getComponentsInLayer(JLayeredPane.DEFAULT_LAYER);
|
||||
if (children.length != 0) { children[0].setSize(display.getSize()); }
|
||||
|
||||
children = display.getComponentsInLayer(FView.TARGETING_LAYER);
|
||||
if (children.length != 0) { children[0].setSize(display.getSize()); }
|
||||
|
||||
children = display.getComponentsInLayer(JLayeredPane.MODAL_LAYER);
|
||||
if (children.length != 0) { children[0].setSize(display.getSize()); }
|
||||
}
|
||||
|
||||
public Player getCurrentPlayer() {
|
||||
// try current priority
|
||||
Player currentPriority = game.getPhaseHandler().getPriorityPlayer();
|
||||
if (null != currentPriority && currentPriority.getLobbyPlayer() == FServer.getLobby().getGuiPlayer()) {
|
||||
return currentPriority;
|
||||
}
|
||||
|
||||
// otherwise find just any player, belonging to this lobbyplayer
|
||||
for (Player p : game.getPlayers()) {
|
||||
if (p.getLobbyPlayer() == FServer.getLobby().getGuiPlayer()) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean mayShowCard(Card c) {
|
||||
return game == null || !gameHasHumanPlayer || c.canBeShownTo(getCurrentPlayer());
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @return
|
||||
*/
|
||||
public SoundSystem getSoundSystem() {
|
||||
return soundSystem;
|
||||
}
|
||||
|
||||
private Game game;
|
||||
private boolean gameHasHumanPlayer;
|
||||
|
||||
public Game getObservedGame() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public final void stopGame() {
|
||||
List<Player> pp = new ArrayList<Player>();
|
||||
for (Player p : game.getPlayers()) {
|
||||
if (p.getOriginalLobbyPlayer() == FServer.getLobby().getGuiPlayer()) {
|
||||
pp.add(p);
|
||||
}
|
||||
}
|
||||
boolean hasHuman = !pp.isEmpty();
|
||||
|
||||
if (pp.isEmpty()) {
|
||||
pp.addAll(game.getPlayers()); // no human? then all players surrender!
|
||||
}
|
||||
|
||||
for (Player p: pp) {
|
||||
p.concede();
|
||||
}
|
||||
|
||||
Player priorityPlayer = game.getPhaseHandler().getPriorityPlayer();
|
||||
boolean humanHasPriority = priorityPlayer == null || priorityPlayer.getLobbyPlayer() == FServer.getLobby().getGuiPlayer();
|
||||
|
||||
if (hasHuman && humanHasPriority) {
|
||||
game.getAction().checkGameOverCondition();
|
||||
}
|
||||
else {
|
||||
game.isGameOver(); // this is synchronized method - it's used to make Game-0 thread see changes made here
|
||||
inputQueue.onGameOver(false); //release any waiting input, effectively passing priority
|
||||
}
|
||||
|
||||
playbackControl.onGameStopRequested();
|
||||
}
|
||||
|
||||
private InputQueue inputQueue;
|
||||
public InputQueue getInputQueue() {
|
||||
return inputQueue;
|
||||
}
|
||||
|
||||
public final void startGameWithUi(final Match match) {
|
||||
if (this.game != null) {
|
||||
this.setCurrentScreen(FScreen.MATCH_SCREEN);
|
||||
SOverlayUtils.hideOverlay();
|
||||
FOptionPane.showMessageDialog("Cannot start a new game while another game is already in progress.");
|
||||
return; //TODO: See if it's possible to run multiple games at once without crashing
|
||||
}
|
||||
setPlayerName(match.getPlayers());
|
||||
final Game newGame = match.createGame();
|
||||
attachToGame(newGame);
|
||||
|
||||
// It's important to run match in a different thread to allow GUI inputs to be invoked from inside game.
|
||||
// Game is set on pause while gui player takes decisions
|
||||
game.getAction().invoke(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
match.startGame(newGame);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public final void endCurrentGame() {
|
||||
if (this.game == null) { return; }
|
||||
|
||||
Singletons.getView().getNavigationBar().closeTab(FScreen.MATCH_SCREEN);
|
||||
this.game = null;
|
||||
}
|
||||
|
||||
private final FControlGameEventHandler fcVisitor = new FControlGameEventHandler();
|
||||
private final FControlGamePlayback playbackControl = new FControlGamePlayback();
|
||||
private void attachToGame(Game game0) {
|
||||
if (game0.getRules().getGameType() == GameType.Quest) {
|
||||
QuestController qc = FModel.getQuest();
|
||||
// Reset new list when the Match round starts, not when each game starts
|
||||
if (game0.getMatch().getPlayedGames().isEmpty()) {
|
||||
qc.getCards().resetNewList();
|
||||
}
|
||||
game0.subscribeToEvents(qc); // this one listens to player's mulligans ATM
|
||||
}
|
||||
|
||||
inputQueue = new InputQueue();
|
||||
|
||||
this.game = game0;
|
||||
game.subscribeToEvents(Singletons.getControl().getSoundSystem());
|
||||
|
||||
LobbyPlayer humanLobbyPlayer = FServer.getLobby().getGuiPlayer();
|
||||
// The UI controls should use these game data as models
|
||||
CMatchUI.SINGLETON_INSTANCE.initMatch(game.getRegisteredPlayers(), humanLobbyPlayer);
|
||||
CDock.SINGLETON_INSTANCE.setModel(game, humanLobbyPlayer);
|
||||
CStack.SINGLETON_INSTANCE.setModel(game.getStack(), humanLobbyPlayer);
|
||||
CLog.SINGLETON_INSTANCE.setModel(game.getGameLog());
|
||||
|
||||
actuateMatchPreferences();
|
||||
VAntes.SINGLETON_INSTANCE.setModel(game.getRegisteredPlayers());
|
||||
|
||||
setCurrentScreen(FScreen.MATCH_SCREEN);
|
||||
SDisplayUtil.showTab(EDocID.REPORT_LOG.getDoc());
|
||||
|
||||
CPrompt.SINGLETON_INSTANCE.getInputControl().setGame(game);
|
||||
|
||||
// Listen to DuelOutcome event to show ViewWinLose
|
||||
game.subscribeToEvents(fcVisitor);
|
||||
|
||||
// Add playback controls to match if needed
|
||||
gameHasHumanPlayer = false;
|
||||
for (Player p : game.getPlayers()) {
|
||||
if (p.getController().getLobbyPlayer() == FServer.getLobby().getGuiPlayer())
|
||||
gameHasHumanPlayer = true;
|
||||
}
|
||||
|
||||
if (!gameHasHumanPlayer) {
|
||||
game.subscribeToEvents(playbackControl);
|
||||
}
|
||||
|
||||
for (final VField field : VMatchUI.SINGLETON_INSTANCE.getFieldViews()) {
|
||||
field.getDetailsPanel().getLblLibrary().setHoverable(ForgePreferences.DEV_MODE);
|
||||
}
|
||||
|
||||
// per player observers were set in CMatchUI.SINGLETON_INSTANCE.initMatch
|
||||
//Set Field shown to current player.
|
||||
VField nextField = CMatchUI.SINGLETON_INSTANCE.getFieldViewFor(game.getPlayers().get(0));
|
||||
SDisplayUtil.showTab(nextField);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.KeyEventDispatcher#dispatchKeyEvent(java.awt.event.KeyEvent)
|
||||
*/
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent e) {
|
||||
// Show Forge menu if Alt key pressed without modifiers and released without pressing any other keys in between
|
||||
if (e.getKeyCode() == KeyEvent.VK_ALT) {
|
||||
if (e.getID() == KeyEvent.KEY_RELEASED) {
|
||||
if (altKeyLastDown) {
|
||||
forgeMenu.show(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (e.getID() == KeyEvent.KEY_PRESSED && e.getModifiers() == KeyEvent.ALT_MASK) {
|
||||
altKeyLastDown = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
altKeyLastDown = false;
|
||||
if (e.getID() == KeyEvent.KEY_PRESSED) {
|
||||
if (forgeMenu.handleKeyEvent(e)) { //give Forge menu the chance to handle the key event
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (e.getID() == KeyEvent.KEY_RELEASED) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_CONTEXT_MENU) {
|
||||
forgeMenu.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
//Allow the event to be redispatched
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts user for a name that will be used instead of "Human" during gameplay.
|
||||
* <p>
|
||||
* This is a one time only event that is triggered when starting a game and the
|
||||
* PLAYER_NAME setting is blank. Does not apply to a hotseat game.
|
||||
*/
|
||||
private void setPlayerName(List<RegisteredPlayer> players) {
|
||||
final ForgePreferences prefs = FModel.getPreferences();
|
||||
if (StringUtils.isBlank(prefs.getPref(FPref.PLAYER_NAME))) {
|
||||
boolean isPlayerOneHuman = players.get(0).getPlayer() instanceof LobbyPlayerHuman;
|
||||
boolean isPlayerTwoComputer = players.get(1).getPlayer() instanceof LobbyPlayerAi;
|
||||
if (isPlayerOneHuman && isPlayerTwoComputer) {
|
||||
GamePlayerUtil.setPlayerName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startMatch(GameType gameType, List<RegisteredPlayer> players) {
|
||||
startMatch(gameType, null, players);
|
||||
}
|
||||
|
||||
public void startMatch(GameType gameType, List<GameType> appliedVariants, List<RegisteredPlayer> players) {
|
||||
boolean useRandomFoil = FModel.getPreferences().getPrefBoolean(FPref.UI_RANDOM_FOIL);
|
||||
for(RegisteredPlayer rp : players) {
|
||||
rp.setRandomFoil(useRandomFoil);
|
||||
}
|
||||
|
||||
GameRules rules = new GameRules(gameType);
|
||||
if (null != appliedVariants && !appliedVariants.isEmpty())
|
||||
rules.setAppliedVariants(appliedVariants);
|
||||
rules.setPlayForAnte(FModel.getPreferences().getPrefBoolean(FPref.UI_ANTE));
|
||||
rules.setManaBurn(FModel.getPreferences().getPrefBoolean(FPref.UI_MANABURN));
|
||||
rules.canCloneUseTargetsImage = FModel.getPreferences().getPrefBoolean(FPref.UI_CLONE_MODE_SOURCE);
|
||||
|
||||
final Match mc = new Match(rules, players);
|
||||
SOverlayUtils.startGameOverlay();
|
||||
SOverlayUtils.showOverlay();
|
||||
FThreads.invokeInEdtLater(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
startGameWithUi(mc);
|
||||
SOverlayUtils.hideOverlay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Needs to be reworked for efficiency with rest of prefs saves in
|
||||
* codebase.
|
||||
*/
|
||||
public void writeMatchPreferences() {
|
||||
final ForgePreferences prefs = FModel.getPreferences();
|
||||
final List<VField> fieldViews = VMatchUI.SINGLETON_INSTANCE.getFieldViews();
|
||||
|
||||
// AI field is at index [1]
|
||||
PhaseIndicator fvAi = fieldViews.get(1).getPhaseIndicator();
|
||||
prefs.setPref(FPref.PHASE_AI_UPKEEP, String.valueOf(fvAi.getLblUpkeep().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_DRAW, String.valueOf(fvAi.getLblDraw().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_MAIN1, String.valueOf(fvAi.getLblMain1().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_BEGINCOMBAT, String.valueOf(fvAi.getLblBeginCombat().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_DECLAREATTACKERS, String.valueOf(fvAi.getLblDeclareAttackers().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_DECLAREBLOCKERS, String.valueOf(fvAi.getLblDeclareBlockers().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_FIRSTSTRIKE, String.valueOf(fvAi.getLblFirstStrike().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_COMBATDAMAGE, String.valueOf(fvAi.getLblCombatDamage().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_ENDCOMBAT, String.valueOf(fvAi.getLblEndCombat().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_MAIN2, String.valueOf(fvAi.getLblMain2().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_EOT, String.valueOf(fvAi.getLblEndTurn().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_AI_CLEANUP, String.valueOf(fvAi.getLblCleanup().getEnabled()));
|
||||
|
||||
// Human field is at index [0]
|
||||
PhaseIndicator fvHuman = fieldViews.get(0).getPhaseIndicator();
|
||||
prefs.setPref(FPref.PHASE_HUMAN_UPKEEP, String.valueOf(fvHuman.getLblUpkeep().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_DRAW, String.valueOf(fvHuman.getLblDraw().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_MAIN1, String.valueOf(fvHuman.getLblMain1().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_BEGINCOMBAT, String.valueOf(fvHuman.getLblBeginCombat().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_DECLAREATTACKERS, String.valueOf(fvHuman.getLblDeclareAttackers().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_DECLAREBLOCKERS, String.valueOf(fvHuman.getLblDeclareBlockers().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_FIRSTSTRIKE, String.valueOf(fvHuman.getLblFirstStrike().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_COMBATDAMAGE, String.valueOf(fvHuman.getLblCombatDamage().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_ENDCOMBAT, String.valueOf(fvHuman.getLblEndCombat().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_MAIN2, String.valueOf(fvHuman.getLblMain2().getEnabled()));
|
||||
prefs.setPref(FPref.PHASE_HUMAN_EOT, fvHuman.getLblEndTurn().getEnabled());
|
||||
prefs.setPref(FPref.PHASE_HUMAN_CLEANUP, fvHuman.getLblCleanup().getEnabled());
|
||||
|
||||
final VDev v = VDev.SINGLETON_INSTANCE;
|
||||
|
||||
// prefs.setPref(FPref.DEV_MILLING_LOSS, v.getLblMilling().getEnabled());
|
||||
prefs.setPref(FPref.DEV_UNLIMITED_LAND, v.getLblUnlimitedLands().getEnabled());
|
||||
|
||||
prefs.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Needs to be reworked for efficiency with rest of prefs saves in
|
||||
* codebase.
|
||||
*/
|
||||
public void actuateMatchPreferences() {
|
||||
final ForgePreferences prefs = FModel.getPreferences();
|
||||
final List<VField> fieldViews = VMatchUI.SINGLETON_INSTANCE.getFieldViews();
|
||||
|
||||
ForgePreferences.DEV_MODE = prefs.getPrefBoolean(FPref.DEV_MODE_ENABLED);
|
||||
ForgePreferences.UPLOAD_DRAFT = ForgePreferences.NET_CONN; // && prefs.getPrefBoolean(FPref.UI_UPLOAD_DRAFT);
|
||||
|
||||
// AI field is at index [0]
|
||||
PhaseIndicator fvAi = fieldViews.get(1).getPhaseIndicator();
|
||||
fvAi.getLblUpkeep().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_UPKEEP));
|
||||
fvAi.getLblDraw().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_DRAW));
|
||||
fvAi.getLblMain1().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_MAIN1));
|
||||
fvAi.getLblBeginCombat().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_BEGINCOMBAT));
|
||||
fvAi.getLblDeclareAttackers().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_DECLAREATTACKERS));
|
||||
fvAi.getLblDeclareBlockers().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_DECLAREBLOCKERS));
|
||||
fvAi.getLblFirstStrike().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_FIRSTSTRIKE));
|
||||
fvAi.getLblCombatDamage().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_COMBATDAMAGE));
|
||||
fvAi.getLblEndCombat().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_ENDCOMBAT));
|
||||
fvAi.getLblMain2().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_MAIN2));
|
||||
fvAi.getLblEndTurn().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_EOT));
|
||||
fvAi.getLblCleanup().setEnabled(prefs.getPrefBoolean(FPref.PHASE_AI_CLEANUP));
|
||||
|
||||
// Human field is at index [1]
|
||||
PhaseIndicator fvHuman = fieldViews.get(0).getPhaseIndicator();
|
||||
fvHuman.getLblUpkeep().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_UPKEEP));
|
||||
fvHuman.getLblDraw().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_DRAW));
|
||||
fvHuman.getLblMain1().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_MAIN1));
|
||||
fvHuman.getLblBeginCombat().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_BEGINCOMBAT));
|
||||
fvHuman.getLblDeclareAttackers().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_DECLAREATTACKERS));
|
||||
fvHuman.getLblDeclareBlockers().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_DECLAREBLOCKERS));
|
||||
fvHuman.getLblFirstStrike().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_FIRSTSTRIKE));
|
||||
fvHuman.getLblCombatDamage().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_COMBATDAMAGE));
|
||||
fvHuman.getLblEndCombat().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_ENDCOMBAT));
|
||||
fvHuman.getLblMain2().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_MAIN2));
|
||||
fvHuman.getLblEndTurn().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_EOT));
|
||||
fvHuman.getLblCleanup().setEnabled(prefs.getPrefBoolean(FPref.PHASE_HUMAN_CLEANUP));
|
||||
|
||||
//Singletons.getView().getViewMatch().setLayoutParams(prefs.getPref(FPref.UI_LAYOUT_PARAMS));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
package forge.control;
|
||||
|
||||
import forge.Singletons;
|
||||
import forge.gui.framework.EDocID;
|
||||
import forge.gui.framework.FScreen;
|
||||
import forge.gui.framework.SDisplayUtil;
|
||||
import forge.model.FModel;
|
||||
import forge.properties.ForgePreferences.FPref;
|
||||
import forge.screens.home.settings.VSubmenuPreferences.KeyboardShortcutField;
|
||||
import forge.screens.match.CMatchUI;
|
||||
import forge.screens.match.controllers.CDock;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Consolidates keyboard shortcut assembly into one location
|
||||
* for all shortcuts in the project.
|
||||
*
|
||||
* Just map a new Shortcut object here, set a default in preferences,
|
||||
* and you're done.
|
||||
*/
|
||||
public class KeyboardShortcuts {
|
||||
/**
|
||||
* Attaches all keyboard shortcuts for match UI,
|
||||
* and returns a list of shortcuts with necessary properties for later access.
|
||||
*
|
||||
* @return List<Shortcut> Shortcut objects
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public static List<Shortcut> attachKeyboardShortcuts() {
|
||||
final JComponent c = Singletons.getView().getFrame().getLayeredPane();
|
||||
final InputMap im = c.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
||||
final ActionMap am = c.getActionMap();
|
||||
|
||||
List<Shortcut> list = new ArrayList<Shortcut>();
|
||||
|
||||
//========== Match Shortcuts
|
||||
/** Show stack. */
|
||||
final Action actShowStack = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (Singletons.getControl().getCurrentScreen() != FScreen.MATCH_SCREEN) { return; }
|
||||
SDisplayUtil.showTab(EDocID.REPORT_STACK.getDoc());
|
||||
}
|
||||
};
|
||||
|
||||
/** Show combat. */
|
||||
final Action actShowCombat = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (Singletons.getControl().getCurrentScreen() != FScreen.MATCH_SCREEN) { return; }
|
||||
SDisplayUtil.showTab(EDocID.REPORT_COMBAT.getDoc());
|
||||
}
|
||||
};
|
||||
|
||||
/** Show console. */
|
||||
final Action actShowConsole = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (Singletons.getControl().getCurrentScreen() != FScreen.MATCH_SCREEN) { return; }
|
||||
SDisplayUtil.showTab(EDocID.REPORT_LOG.getDoc());
|
||||
}
|
||||
};
|
||||
|
||||
/** Show players panel. */
|
||||
final Action actShowPlayers = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (Singletons.getControl().getCurrentScreen() != FScreen.MATCH_SCREEN) { return; }
|
||||
SDisplayUtil.showTab(EDocID.REPORT_PLAYERS.getDoc());
|
||||
}
|
||||
};
|
||||
|
||||
/** Show dev panel. */
|
||||
final Action actShowDev = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (Singletons.getControl().getCurrentScreen() != FScreen.MATCH_SCREEN) { return; }
|
||||
if (FModel.getPreferences().getPrefBoolean(FPref.DEV_MODE_ENABLED)) {
|
||||
SDisplayUtil.showTab(EDocID.DEV_MODE.getDoc());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Concede game. */
|
||||
final Action actConcede = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (Singletons.getControl().getCurrentScreen() != FScreen.MATCH_SCREEN) { return; }
|
||||
CMatchUI.SINGLETON_INSTANCE.concede();
|
||||
}
|
||||
};
|
||||
|
||||
/** End turn. */
|
||||
final Action actEndTurn = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (Singletons.getControl().getCurrentScreen() != FScreen.MATCH_SCREEN) { return; }
|
||||
CDock.SINGLETON_INSTANCE.endTurn();
|
||||
}
|
||||
};
|
||||
|
||||
/** Alpha Strike. */
|
||||
final Action actAllAttack = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (Singletons.getControl().getCurrentScreen() != FScreen.MATCH_SCREEN) { return; }
|
||||
CDock.SINGLETON_INSTANCE.alphaStrike();
|
||||
}
|
||||
};
|
||||
|
||||
/** Targeting visualization overlay. */
|
||||
final Action actTgtOverlay = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (Singletons.getControl().getCurrentScreen() != FScreen.MATCH_SCREEN) { return; }
|
||||
CDock.SINGLETON_INSTANCE.toggleTargeting();
|
||||
}
|
||||
};
|
||||
|
||||
//========== Instantiate shortcut objects and add to list.
|
||||
list.add(new Shortcut(FPref.SHORTCUT_SHOWSTACK, "Match: show stack panel", actShowStack, am, im));
|
||||
list.add(new Shortcut(FPref.SHORTCUT_SHOWCOMBAT, "Match: show combat panel", actShowCombat, am, im));
|
||||
list.add(new Shortcut(FPref.SHORTCUT_SHOWCONSOLE, "Match: show console panel", actShowConsole, am, im));
|
||||
list.add(new Shortcut(FPref.SHORTCUT_SHOWPLAYERS, "Match: show players panel", actShowPlayers, am, im));
|
||||
list.add(new Shortcut(FPref.SHORTCUT_SHOWDEV, "Match: show dev panel", actShowDev, am, im));
|
||||
list.add(new Shortcut(FPref.SHORTCUT_CONCEDE, "Match: concede game", actConcede, am, im));
|
||||
list.add(new Shortcut(FPref.SHORTCUT_ENDTURN, "Match: pass priority until EOT or next stack event", actEndTurn, am, im));
|
||||
list.add(new Shortcut(FPref.SHORTCUT_ALPHASTRIKE, "Match: Alpha Strike (attack with all available)", actAllAttack, am, im));
|
||||
list.add(new Shortcut(FPref.SHORTCUT_SHOWTARGETING, "Match: toggle targeting visual overlay", actTgtOverlay, am, im));
|
||||
return list;
|
||||
} // End initMatchShortcuts()
|
||||
|
||||
/**
|
||||
*
|
||||
* Instantiates a shortcut key instance with various properties for use
|
||||
* throughout the project.
|
||||
*
|
||||
*/
|
||||
public static class Shortcut {
|
||||
/** */
|
||||
private final FPref prefkeys;
|
||||
/** */
|
||||
private final String description;
|
||||
/** */
|
||||
private final Action handler;
|
||||
/** */
|
||||
private final ActionMap actionMap;
|
||||
/** */
|
||||
private final InputMap inputMap;
|
||||
/** */
|
||||
private KeyStroke key;
|
||||
/** */
|
||||
private String str;
|
||||
|
||||
/**
|
||||
*
|
||||
* Instantiates a shortcut key instance with various properties for use
|
||||
* throughout the project.
|
||||
*
|
||||
* @param prefkey0 String, ident key in forge.preferences
|
||||
* @param description0 String, description of shortcut function
|
||||
* @param handler0 Action, action on execution of shortcut
|
||||
* @param am0 ActionMap, of container targeted by shortcut
|
||||
* @param im0 InputMap, of container targeted by shortcut
|
||||
*/
|
||||
public Shortcut(final FPref prefkey0, final String description0,
|
||||
final Action handler0, final ActionMap am0, final InputMap im0) {
|
||||
|
||||
prefkeys = prefkey0;
|
||||
description = description0;
|
||||
handler = handler0;
|
||||
actionMap = am0;
|
||||
inputMap = im0;
|
||||
attach();
|
||||
}
|
||||
|
||||
/** @return {@link java.lang.String} */
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* String ident key in forge.preferences.
|
||||
* @return {@link java.lang.String}
|
||||
*/
|
||||
public FPref getPrefKey() {
|
||||
return prefkeys;
|
||||
}
|
||||
|
||||
/** */
|
||||
public void attach() {
|
||||
detach();
|
||||
str = FModel.getPreferences().getPref(prefkeys);
|
||||
if (!str.isEmpty()) {
|
||||
key = assembleKeystrokes(str.split(" "));
|
||||
|
||||
// Attach key stroke to input map...
|
||||
inputMap.put(key, str);
|
||||
|
||||
// ...then attach actionListener to action map
|
||||
actionMap.put(str, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/** */
|
||||
public void detach() {
|
||||
inputMap.remove(key);
|
||||
actionMap.remove(str);
|
||||
}
|
||||
} // End class Shortcut
|
||||
|
||||
private static KeyStroke assembleKeystrokes(final String[] keys0) {
|
||||
int[] inputEvents = new int[2];
|
||||
int modifier = 0;
|
||||
int keyEvent = 0;
|
||||
|
||||
inputEvents[0] = 0;
|
||||
inputEvents[1] = 0;
|
||||
|
||||
// If CTRL or SHIFT is pressed, it must be passed as a modifier,
|
||||
// in the form of an input event object. So, first test if these were pressed.
|
||||
// ALT shortcuts will be ignored.
|
||||
for (final String s : keys0) {
|
||||
if (s.equals("16")) { inputEvents[0] = 16; }
|
||||
else if (s.equals("17")) { inputEvents[1] = 17; }
|
||||
else {
|
||||
keyEvent = Integer.valueOf(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Then, convert to InputEvent.
|
||||
if (inputEvents[0] == 16 && inputEvents[1] != 17) {
|
||||
modifier = InputEvent.SHIFT_DOWN_MASK;
|
||||
}
|
||||
else if (inputEvents[0] != 16 && inputEvents[1] == 17) {
|
||||
modifier = InputEvent.CTRL_DOWN_MASK;
|
||||
}
|
||||
else if (inputEvents[0] != 0 && inputEvents[1] != 0) {
|
||||
modifier = InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK;
|
||||
}
|
||||
|
||||
return KeyStroke.getKeyStroke(keyEvent, modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* - Adds keycode to list stored in name of a text field.
|
||||
* - Code is not added if already in list.
|
||||
* - Backspace removes last code in list.
|
||||
* - Sets text of text field with character equivalent of keycodes.
|
||||
*
|
||||
* @param e   KeyEvent
|
||||
*/
|
||||
public static void addKeyCode(final KeyEvent e) {
|
||||
final KeyboardShortcutField ksf = (KeyboardShortcutField) e.getSource();
|
||||
final String newCode = Integer.toString(e.getKeyCode());
|
||||
final String codestring = ksf.getCodeString();
|
||||
List<String> existingCodes;
|
||||
|
||||
if (codestring != null) {
|
||||
existingCodes = new ArrayList<String>(Arrays.asList(codestring.split(" ")));
|
||||
} else {
|
||||
existingCodes = new ArrayList<String>();
|
||||
}
|
||||
|
||||
// Backspace (8) will remove last code from list.
|
||||
if (e.getKeyCode() == 8) {
|
||||
existingCodes.remove(existingCodes.size() - 1);
|
||||
} else if (!existingCodes.contains(newCode)) {
|
||||
existingCodes.add(newCode);
|
||||
}
|
||||
|
||||
ksf.setCodeString(StringUtils.join(existingCodes, ' '));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package forge.control;
|
||||
|
||||
import forge.Singletons;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Restarts a java app.
|
||||
* Credit: http://leolewis.website.org/wordpress/2011/07/06/programmatically-restart-a-java-application/
|
||||
*/
|
||||
public class RestartUtil {
|
||||
/**
|
||||
* Sun property pointing the main class and its arguments.
|
||||
* Might not be defined on non Hotspot VM implementations.
|
||||
*/
|
||||
public static final String SUN_JAVA_COMMAND = "sun.java.command";
|
||||
|
||||
/**
|
||||
* Restart the current Java application.
|
||||
* @param runBeforeRestart some custom code to be run before restarting
|
||||
*/
|
||||
public static void restartApplication(final Runnable runBeforeRestart) {
|
||||
if (!Singletons.getControl().canExitForge(true)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// java binary
|
||||
final String java = System.getProperty("java.home")
|
||||
+ File.separator + "bin" + File.separator + "java";
|
||||
|
||||
// vm arguments
|
||||
final List<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
|
||||
final StringBuffer vmArgsOneLine = new StringBuffer();
|
||||
for (final String arg : vmArguments) {
|
||||
// if it's the agent argument : we ignore it otherwise the
|
||||
// address of the old application and the new one will be in conflict
|
||||
if (!arg.contains("-agentlib")) {
|
||||
vmArgsOneLine.append(arg);
|
||||
vmArgsOneLine.append(" ");
|
||||
}
|
||||
}
|
||||
// init the command to execute, add the vm args
|
||||
final StringBuffer cmd = new StringBuffer("\"" + java + "\" " + vmArgsOneLine);
|
||||
|
||||
// program main and program arguments
|
||||
final String[] mainCommand = System.getProperty(SUN_JAVA_COMMAND).split(" ");
|
||||
// program main is a jar
|
||||
if (mainCommand[0].endsWith(".jar")) {
|
||||
// if it's a jar, add -jar mainJar
|
||||
cmd.append("-jar " + new File(mainCommand[0]).getPath());
|
||||
} else {
|
||||
// else it's a .class, add the classpath and mainClass
|
||||
cmd.append("-cp \"" + System.getProperty("java.class.path") + "\" " + mainCommand[0]);
|
||||
}
|
||||
// finally add program arguments
|
||||
for (int i = 1; i < mainCommand.length; i++) {
|
||||
cmd.append(" ");
|
||||
cmd.append(mainCommand[i]);
|
||||
}
|
||||
// execute the command in a shutdown hook, to be sure that all the
|
||||
// resources have been disposed before restarting the application
|
||||
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Runtime.getRuntime().exec(cmd.toString());
|
||||
} catch (final IOException e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
// execute some custom code before restarting
|
||||
if (runBeforeRestart != null) {
|
||||
runBeforeRestart.run();
|
||||
}
|
||||
// exit
|
||||
System.exit(0);
|
||||
} catch (final Exception ex) {
|
||||
//ErrorViewer.showError(ex, "Restart \"%s\" exception", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
/** Controller (as in model-view-controller) for Forge. */
|
||||
package forge.control;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package forge.deckchooser;
|
||||
|
||||
import forge.deck.DeckType;
|
||||
import forge.gui.MouseUtil;
|
||||
import forge.toolbox.FComboBoxWrapper;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FComboBox.TextAlignment;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.Cursor;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DecksComboBox extends FComboBoxWrapper<DeckType> {
|
||||
private List<IDecksComboBoxListener> _listeners = new ArrayList<>();
|
||||
private DeckType selectedDeckType = null;
|
||||
|
||||
public DecksComboBox() {
|
||||
setSkinFont(FSkin.getBoldFont(14));
|
||||
setTextAlignment(TextAlignment.CENTER);
|
||||
addActionListener(getDeckTypeComboListener());
|
||||
}
|
||||
|
||||
public void refresh(DeckType deckType) {
|
||||
setModel(new DefaultComboBoxModel<DeckType>(DeckType.values()));
|
||||
setSelectedItem(deckType);
|
||||
}
|
||||
|
||||
private ActionListener getDeckTypeComboListener() {
|
||||
return new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
MouseUtil.setCursor(Cursor.WAIT_CURSOR);
|
||||
DeckType newDeckType = (DeckType)getSelectedItem();
|
||||
if (newDeckType != selectedDeckType) {
|
||||
notifyDeckTypeSelected(newDeckType);
|
||||
selectedDeckType = newDeckType;
|
||||
}
|
||||
MouseUtil.resetCursor();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public synchronized void addListener(IDecksComboBoxListener obj) {
|
||||
_listeners.add(obj);
|
||||
}
|
||||
|
||||
public synchronized void removeListener(IDecksComboBoxListener obj) {
|
||||
_listeners.remove(obj);
|
||||
}
|
||||
|
||||
private synchronized void notifyDeckTypeSelected(DeckType deckType) {
|
||||
if (deckType != null) {
|
||||
for (IDecksComboBoxListener listener : _listeners) {
|
||||
listener.deckTypeSelected(new DecksComboBoxEvent(this, deckType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DeckType getDeckType() {
|
||||
return selectedDeckType;
|
||||
}
|
||||
|
||||
public void setDeckType(DeckType valueOf) {
|
||||
selectedDeckType = valueOf;
|
||||
setSelectedItem(selectedDeckType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package forge.deckchooser;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
import forge.deck.DeckType;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class DecksComboBoxEvent extends EventObject {
|
||||
private final DeckType deckType;
|
||||
|
||||
public DecksComboBoxEvent(Object source, DeckType deckType0) {
|
||||
super(source);
|
||||
deckType = deckType0;
|
||||
}
|
||||
|
||||
public DeckType getDeckType() {
|
||||
return deckType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
package forge.deckchooser;
|
||||
|
||||
import forge.UiCommand;
|
||||
import forge.deck.Deck;
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.deck.DeckType;
|
||||
import forge.deck.DeckgenUtil;
|
||||
import forge.game.GameType;
|
||||
import forge.game.player.RegisteredPlayer;
|
||||
import forge.itemmanager.DeckManager;
|
||||
import forge.itemmanager.ItemManagerConfig;
|
||||
import forge.itemmanager.ItemManagerContainer;
|
||||
import forge.model.FModel;
|
||||
import forge.properties.ForgePreferences;
|
||||
import forge.properties.ForgePreferences.FPref;
|
||||
import forge.quest.QuestController;
|
||||
import forge.quest.QuestEvent;
|
||||
import forge.quest.QuestEventChallenge;
|
||||
import forge.quest.QuestUtil;
|
||||
import forge.toolbox.FLabel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class FDeckChooser extends JPanel implements IDecksComboBoxListener {
|
||||
private DecksComboBox decksComboBox;
|
||||
private DeckType selectedDeckType;
|
||||
private ItemManagerContainer lstDecksContainer;
|
||||
|
||||
private final DeckManager lstDecks = new DeckManager(GameType.Constructed);
|
||||
private final FLabel btnViewDeck = new FLabel.ButtonBuilder().text("View Deck").fontSize(14).build();
|
||||
private final FLabel btnRandom = new FLabel.ButtonBuilder().fontSize(14).build();
|
||||
|
||||
private boolean isAi;
|
||||
|
||||
private final ForgePreferences prefs = FModel.getPreferences();
|
||||
private FPref stateSetting = null;
|
||||
|
||||
public FDeckChooser(boolean forAi) {
|
||||
setOpaque(false);
|
||||
isAi = forAi;
|
||||
UiCommand cmdViewDeck = new UiCommand() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (selectedDeckType != DeckType.COLOR_DECK && selectedDeckType != DeckType.THEME_DECK) {
|
||||
FDeckViewer.show(getDeck());
|
||||
}
|
||||
}
|
||||
};
|
||||
lstDecks.setItemActivateCommand(cmdViewDeck);
|
||||
btnViewDeck.setCommand(cmdViewDeck);
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
initialize(DeckType.COLOR_DECK);
|
||||
}
|
||||
public void initialize(DeckType defaultDeckType) {
|
||||
initialize(null, defaultDeckType);
|
||||
}
|
||||
public void initialize(FPref savedStateSetting, DeckType defaultDeckType) {
|
||||
stateSetting = savedStateSetting;
|
||||
selectedDeckType = defaultDeckType;
|
||||
}
|
||||
|
||||
public DeckType getSelectedDeckType() { return selectedDeckType; }
|
||||
public void setSelectedDeckType(DeckType selectedDeckType0) {
|
||||
refreshDecksList(selectedDeckType0, false, null);
|
||||
}
|
||||
|
||||
public DeckManager getLstDecks() { return lstDecks; }
|
||||
|
||||
private void updateCustom() {
|
||||
lstDecks.setAllowMultipleSelections(false);
|
||||
|
||||
lstDecks.setPool(DeckProxy.getAllConstructedDecks(FModel.getDecks().getConstructed()));
|
||||
lstDecks.setup(ItemManagerConfig.CONSTRUCTED_DECKS);
|
||||
|
||||
btnRandom.setText("Random Deck");
|
||||
btnRandom.setCommand(new UiCommand() {
|
||||
@Override
|
||||
public void run() {
|
||||
DeckgenUtil.randomSelect(lstDecks);
|
||||
}
|
||||
});
|
||||
|
||||
lstDecks.setSelectedIndex(0);
|
||||
}
|
||||
|
||||
private class ColorDeckGenerator extends DeckProxy implements Comparable<ColorDeckGenerator> {
|
||||
private String name;
|
||||
private int index;
|
||||
|
||||
public ColorDeckGenerator(String name0, int index0) {
|
||||
super();
|
||||
name = name0;
|
||||
this.index = index0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int compareTo(final ColorDeckGenerator d) {
|
||||
return d instanceof ColorDeckGenerator ? Integer.compare(this.index, ((ColorDeckGenerator)d).index) : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Deck getDeck() {
|
||||
List<String> selection = new ArrayList<String>();
|
||||
for (DeckProxy deck : lstDecks.getSelectedItems()) {
|
||||
selection.add(deck.getName());
|
||||
}
|
||||
if (DeckgenUtil.colorCheck(selection)) {
|
||||
return DeckgenUtil.buildColorDeck(selection, isAi);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeneratedDeck() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateColors() {
|
||||
lstDecks.setAllowMultipleSelections(true);
|
||||
|
||||
String[] colors = new String[] { "Random 1", "Random 2", "Random 3",
|
||||
"White", "Blue", "Black", "Red", "Green" };
|
||||
ArrayList<DeckProxy> decks = new ArrayList<DeckProxy>();
|
||||
for (int i = 0; i < colors.length; i++) {
|
||||
decks.add(new ColorDeckGenerator(colors[i], i));
|
||||
}
|
||||
|
||||
lstDecks.setPool(decks);
|
||||
lstDecks.setup(ItemManagerConfig.STRING_ONLY);
|
||||
|
||||
btnRandom.setText("Random Colors");
|
||||
btnRandom.setCommand(new UiCommand() {
|
||||
@Override
|
||||
public void run() {
|
||||
DeckgenUtil.randomSelectColors(lstDecks);
|
||||
}
|
||||
});
|
||||
|
||||
// default selection = basic two color deck
|
||||
lstDecks.setSelectedIndices(new Integer[]{0, 1});
|
||||
}
|
||||
|
||||
private void updateThemes() {
|
||||
lstDecks.setAllowMultipleSelections(false);
|
||||
|
||||
lstDecks.setPool(DeckProxy.getAllThemeDecks());
|
||||
lstDecks.setup(ItemManagerConfig.STRING_ONLY);
|
||||
|
||||
btnRandom.setText("Random Deck");
|
||||
btnRandom.setCommand(new UiCommand() {
|
||||
@Override
|
||||
public void run() {
|
||||
DeckgenUtil.randomSelect(lstDecks);
|
||||
}
|
||||
});
|
||||
|
||||
lstDecks.setSelectedIndex(0);
|
||||
}
|
||||
|
||||
private void updatePrecons() {
|
||||
lstDecks.setAllowMultipleSelections(false);
|
||||
|
||||
lstDecks.setPool(DeckProxy.getAllPreconstructedDecks(QuestController.getPrecons()));
|
||||
lstDecks.setup(ItemManagerConfig.PRECON_DECKS);
|
||||
|
||||
btnRandom.setText("Random Deck");
|
||||
btnRandom.setCommand(new UiCommand() {
|
||||
@Override
|
||||
public void run() {
|
||||
DeckgenUtil.randomSelect(lstDecks);
|
||||
}
|
||||
});
|
||||
|
||||
lstDecks.setSelectedIndex(0);
|
||||
}
|
||||
|
||||
private void updateQuestEvents() {
|
||||
lstDecks.setAllowMultipleSelections(false);
|
||||
|
||||
lstDecks.setPool(DeckProxy.getAllQuestEventAndChallenges());
|
||||
lstDecks.setup(ItemManagerConfig.QUEST_EVENT_DECKS);
|
||||
|
||||
btnRandom.setText("Random Deck");
|
||||
btnRandom.setCommand(new UiCommand() {
|
||||
@Override
|
||||
public void run() {
|
||||
DeckgenUtil.randomSelect(lstDecks);
|
||||
}
|
||||
});
|
||||
|
||||
lstDecks.setSelectedIndex(0);
|
||||
}
|
||||
|
||||
public Deck getDeck() {
|
||||
DeckProxy proxy = lstDecks.getSelectedItem();
|
||||
return proxy.getDeck();
|
||||
}
|
||||
|
||||
/** Generates deck from current list selection(s). */
|
||||
public RegisteredPlayer getPlayer() {
|
||||
if (lstDecks.getSelectedIndex() < 0) { return null; }
|
||||
|
||||
// Special branch for quest events
|
||||
if (selectedDeckType == DeckType.QUEST_OPPONENT_DECK) {
|
||||
QuestEvent event = DeckgenUtil.getQuestEvent(lstDecks.getSelectedItem().getName());
|
||||
RegisteredPlayer result = new RegisteredPlayer(event.getEventDeck());
|
||||
if (event instanceof QuestEventChallenge) {
|
||||
result.setStartingLife(((QuestEventChallenge) event).getAiLife());
|
||||
}
|
||||
result.setCardsOnBattlefield(QuestUtil.getComputerStartingCards(event));
|
||||
return result;
|
||||
}
|
||||
|
||||
return new RegisteredPlayer(getDeck());
|
||||
}
|
||||
|
||||
public void populate() {
|
||||
if (decksComboBox == null) { //initialize components with delayed initialization the first time this is populated
|
||||
decksComboBox = new DecksComboBox();
|
||||
lstDecksContainer = new ItemManagerContainer(lstDecks);
|
||||
restoreSavedState();
|
||||
decksComboBox.addListener(this);
|
||||
}
|
||||
else {
|
||||
removeAll();
|
||||
restoreSavedState(); //ensure decks refreshed and state restored in case any deleted or added since last loaded
|
||||
}
|
||||
this.setLayout(new MigLayout("insets 0, gap 0"));
|
||||
decksComboBox.addTo(this, "w 100%, h 30px!, gapbottom 5px, spanx 2, wrap");
|
||||
this.add(lstDecksContainer, "w 100%, growy, pushy, spanx 2, wrap");
|
||||
this.add(btnViewDeck, "w 50%-3px, h 30px!, gaptop 5px, gapright 6px");
|
||||
this.add(btnRandom, "w 50%-3px, h 30px!, gaptop 5px");
|
||||
if (isShowing()) {
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean isAi() {
|
||||
return isAi;
|
||||
}
|
||||
|
||||
public void setIsAi(boolean isAiDeck) {
|
||||
this.isAi = isAiDeck;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.deckchooser.IDecksComboBoxListener#deckTypeSelected(forge.gui.deckchooser.DecksComboBoxEvent)
|
||||
*/
|
||||
@Override
|
||||
public void deckTypeSelected(DecksComboBoxEvent ev) {
|
||||
refreshDecksList(ev.getDeckType(), false, ev);
|
||||
}
|
||||
|
||||
private void refreshDecksList(DeckType deckType, boolean forceRefresh, DecksComboBoxEvent ev) {
|
||||
if (selectedDeckType == deckType && !forceRefresh) { return; }
|
||||
selectedDeckType = deckType;
|
||||
|
||||
if (ev == null) {
|
||||
decksComboBox.refresh(deckType);
|
||||
}
|
||||
lstDecks.setCaption(deckType.toString());
|
||||
|
||||
switch (deckType) {
|
||||
case CUSTOM_DECK:
|
||||
updateCustom();
|
||||
break;
|
||||
case COLOR_DECK:
|
||||
updateColors();
|
||||
break;
|
||||
case THEME_DECK:
|
||||
updateThemes();
|
||||
break;
|
||||
case QUEST_OPPONENT_DECK:
|
||||
updateQuestEvents();
|
||||
break;
|
||||
case PRECONSTRUCTED_DECK:
|
||||
updatePrecons();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private final String SELECTED_DECK_DELIMITER = "::";
|
||||
|
||||
public void saveState() {
|
||||
if (stateSetting == null) {
|
||||
throw new NullPointerException("State setting missing. Specify first using the initialize() method.");
|
||||
}
|
||||
prefs.setPref(stateSetting, getState());
|
||||
prefs.save();
|
||||
}
|
||||
|
||||
private String getState() {
|
||||
String deckType = decksComboBox.getDeckType().name();
|
||||
StringBuilder state = new StringBuilder(deckType);
|
||||
state.append(";");
|
||||
joinSelectedDecks(state, SELECTED_DECK_DELIMITER);
|
||||
return state.toString();
|
||||
}
|
||||
|
||||
private void joinSelectedDecks(StringBuilder state, String delimiter) {
|
||||
Iterable<DeckProxy> selectedDecks = lstDecks.getSelectedItems();
|
||||
boolean isFirst = true;
|
||||
if (selectedDecks != null) {
|
||||
for (DeckProxy deck : selectedDecks) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
}
|
||||
else {
|
||||
state.append(delimiter);
|
||||
}
|
||||
state.append(deck.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a clean name from the state that can be used for labels. */
|
||||
public final String getStateForLabel() {
|
||||
String deckType = decksComboBox.getDeckType().toString();
|
||||
StringBuilder state = new StringBuilder(deckType);
|
||||
state.append(": ");
|
||||
joinSelectedDecks(state, ", ");
|
||||
return state.toString();
|
||||
}
|
||||
|
||||
private void restoreSavedState() {
|
||||
if (stateSetting == null) {
|
||||
//if can't restore saved state, just refresh deck list
|
||||
refreshDecksList(selectedDeckType, true, null);
|
||||
return;
|
||||
}
|
||||
|
||||
String savedState = prefs.getPref(stateSetting);
|
||||
refreshDecksList(getDeckTypeFromSavedState(savedState), true, null);
|
||||
lstDecks.setSelectedStrings(getSelectedDecksFromSavedState(savedState));
|
||||
}
|
||||
|
||||
private DeckType getDeckTypeFromSavedState(String savedState) {
|
||||
try {
|
||||
if (StringUtils.isBlank(savedState)) {
|
||||
return selectedDeckType;
|
||||
}
|
||||
else {
|
||||
return DeckType.valueOf(savedState.split(";")[0]);
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
System.err.println(ex.getMessage() + ". Using default : " + selectedDeckType);
|
||||
return selectedDeckType;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getSelectedDecksFromSavedState(String savedState) {
|
||||
try {
|
||||
if (StringUtils.isBlank(savedState)) {
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
else {
|
||||
return Arrays.asList(savedState.split(";")[1].split(SELECTED_DECK_DELIMITER));
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
System.err.println(ex + " [savedState=" + savedState + "]");
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
}
|
||||
|
||||
public DecksComboBox getDecksComboBox() {
|
||||
return decksComboBox;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
package forge.deckchooser;
|
||||
|
||||
import forge.deck.CardPool;
|
||||
import forge.deck.Deck;
|
||||
import forge.deck.DeckSection;
|
||||
import forge.game.card.Card;
|
||||
import forge.gui.CardDetailPanel;
|
||||
import forge.gui.CardPicturePanel;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.CardManager;
|
||||
import forge.itemmanager.ItemManagerConfig;
|
||||
import forge.itemmanager.ItemManagerContainer;
|
||||
import forge.itemmanager.ItemManagerModel;
|
||||
import forge.itemmanager.views.*;
|
||||
import forge.toolbox.FButton;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.view.FDialog;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class FDeckViewer extends FDialog {
|
||||
private final Deck deck;
|
||||
private final List<DeckSection> sections = new ArrayList<DeckSection>();
|
||||
private final CardManager cardManager;
|
||||
private DeckSection currentSection;
|
||||
|
||||
private final CardDetailPanel cardDetail = new CardDetailPanel(null);
|
||||
private final CardPicturePanel cardPicture = new CardPicturePanel();
|
||||
private final FButton btnCopyToClipboard = new FButton("Copy to Clipboard");
|
||||
private final FButton btnChangeSection = new FButton("Change Section");
|
||||
private final FButton btnClose = new FButton("Close");
|
||||
|
||||
public static void show(final Deck deck) {
|
||||
if (deck == null) { return; }
|
||||
|
||||
FDeckViewer deckViewer = new FDeckViewer(deck);
|
||||
deckViewer.setVisible(true);
|
||||
deckViewer.dispose();
|
||||
}
|
||||
|
||||
private FDeckViewer(Deck deck0) {
|
||||
this.deck = deck0;
|
||||
this.setTitle(deck.getName());
|
||||
this.cardManager = new CardManager(false) {
|
||||
@Override //show hovered card in Image View in dialog instead of main Detail/Picture panes
|
||||
protected ImageView<PaperCard> createImageView(final ItemManagerModel<PaperCard> model0) {
|
||||
return new ImageView<PaperCard>(this, model0) {
|
||||
@Override
|
||||
protected void showHoveredItem(PaperCard item) {
|
||||
Card card = Card.getCardForUi(item);
|
||||
if (card == null) { return; }
|
||||
|
||||
cardDetail.setCard(card);
|
||||
cardPicture.setCard(card, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
this.cardManager.setPool(deck.getMain());
|
||||
this.cardManager.addSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
PaperCard paperCard = cardManager.getSelectedItem();
|
||||
if (paperCard == null) { return; }
|
||||
|
||||
Card card = Card.getCardForUi(paperCard);
|
||||
if (card == null) { return; }
|
||||
|
||||
cardDetail.setCard(card);
|
||||
cardPicture.setCard(card, true);
|
||||
}
|
||||
});
|
||||
|
||||
for (Entry<DeckSection, CardPool> entry : deck) {
|
||||
this.sections.add(entry.getKey());
|
||||
}
|
||||
this.currentSection = DeckSection.Main;
|
||||
updateCaption();
|
||||
|
||||
this.btnCopyToClipboard.setFocusable(false);
|
||||
this.btnCopyToClipboard.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
FDeckViewer.this.copyToClipboard();
|
||||
}
|
||||
});
|
||||
this.btnChangeSection.setFocusable(false);
|
||||
if (this.sections.size() > 1) {
|
||||
this.btnChangeSection.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
FDeckViewer.this.changeSection();
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.btnChangeSection.setEnabled(false);
|
||||
}
|
||||
this.btnClose.setFocusable(false);
|
||||
this.btnClose.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
FDeckViewer.this.setVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
final int width = 800;
|
||||
final int height = 600;
|
||||
this.setPreferredSize(new Dimension(width, height));
|
||||
this.setSize(width, height);
|
||||
|
||||
this.cardPicture.setOpaque(false);
|
||||
|
||||
JPanel cardPanel = new JPanel(new MigLayout("insets 0, gap 0, wrap"));
|
||||
cardPanel.setOpaque(false);
|
||||
cardPanel.add(this.cardDetail, "w 225px, h 240px, gapbottom 10px");
|
||||
cardPanel.add(this.cardPicture, "w 225px, h 350px, gapbottom 10px");
|
||||
|
||||
JPanel buttonPanel = new JPanel(new MigLayout("insets 0, gap 0"));
|
||||
buttonPanel.setOpaque(false);
|
||||
buttonPanel.add(this.btnCopyToClipboard, "w 200px!, h 26px!, gapright 5px");
|
||||
buttonPanel.add(this.btnChangeSection, "w 200px!, h 26px!");
|
||||
|
||||
this.add(new ItemManagerContainer(this.cardManager), "push, grow, gapright 10px, gapbottom 10px");
|
||||
this.add(cardPanel, "wrap");
|
||||
this.add(buttonPanel);
|
||||
this.add(this.btnClose, "w 120px!, h 26px!, ax right");
|
||||
|
||||
this.cardManager.setup(ItemManagerConfig.DECK_VIEWER);
|
||||
this.setDefaultFocus(this.cardManager.getCurrentView().getComponent());
|
||||
}
|
||||
|
||||
private void changeSection() {
|
||||
int index = sections.indexOf(currentSection);
|
||||
index = (index + 1) % sections.size();
|
||||
currentSection = sections.get(index);
|
||||
this.cardManager.setPool(this.deck.get(currentSection));
|
||||
updateCaption();
|
||||
}
|
||||
|
||||
private void updateCaption() {
|
||||
this.cardManager.setCaption(deck.getName() + " - " + currentSection.name());
|
||||
}
|
||||
|
||||
private void copyToClipboard() {
|
||||
final String nl = System.getProperty("line.separator");
|
||||
final StringBuilder deckList = new StringBuilder();
|
||||
final String dName = deck.getName();
|
||||
deckList.append(dName == null ? "" : dName + nl + nl);
|
||||
|
||||
for (DeckSection s : DeckSection.values()){
|
||||
CardPool cp = deck.get(s);
|
||||
if (cp == null || cp.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
deckList.append(s.toString()).append(": ");
|
||||
if (s.isSingleCard()) {
|
||||
deckList.append(cp.get(0).getName()).append(nl);
|
||||
}
|
||||
else {
|
||||
deckList.append(nl);
|
||||
for (final Entry<PaperCard, Integer> ev : cp) {
|
||||
deckList.append(ev.getValue()).append(" ").append(ev.getKey()).append(nl);
|
||||
}
|
||||
}
|
||||
deckList.append(nl);
|
||||
}
|
||||
|
||||
final StringSelection ss = new StringSelection(deckList.toString());
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
|
||||
FOptionPane.showMessageDialog("Deck list for '" + deck.getName() + "' copied to clipboard.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package forge.deckchooser;
|
||||
|
||||
public interface IDecksComboBoxListener {
|
||||
public void deckTypeSelected(DecksComboBoxEvent ev);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import forge.assets.ImageUtil;
|
||||
import forge.card.CardRules;
|
||||
import forge.item.PaperCard;
|
||||
import forge.model.FModel;
|
||||
import forge.properties.ForgeConstants;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class GuiDownloadPicturesLQ extends GuiDownloader {
|
||||
public GuiDownloadPicturesLQ() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Map<String, String> getNeededImages() {
|
||||
Map<String, String> downloads = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
for (PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) {
|
||||
addDLObject(c, downloads, false);
|
||||
if (ImageUtil.hasBackFacePicture(c)) {
|
||||
addDLObject(c, downloads, true);
|
||||
}
|
||||
}
|
||||
|
||||
for (PaperCard c : FModel.getMagicDb().getVariantCards().getAllCards()) {
|
||||
addDLObject(c, downloads, false);
|
||||
}
|
||||
|
||||
// Add missing tokens to the list of things to download.
|
||||
addMissingItems(downloads, ForgeConstants.IMAGE_LIST_TOKENS_FILE, ForgeConstants.CACHE_TOKEN_PICS_DIR);
|
||||
|
||||
return downloads;
|
||||
}
|
||||
|
||||
private void addDLObject(PaperCard c, Map<String, String> downloads, boolean backFace) {
|
||||
CardRules cardRules = c.getRules();
|
||||
String urls = cardRules.getPictureUrl(backFace);
|
||||
if (StringUtils.isEmpty(urls)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String filename = ImageUtil.getImageKey(c, backFace, false);
|
||||
File destFile = new File(ForgeConstants.CACHE_CARD_PICS_DIR, filename + ".jpg");
|
||||
if (destFile.exists())
|
||||
return;
|
||||
|
||||
filename = destFile.getAbsolutePath();
|
||||
|
||||
if (downloads.containsKey(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String urlToDownload;
|
||||
int urlIndex = 0;
|
||||
int allUrlsLen = 1;
|
||||
if (urls.indexOf("\\") < 0)
|
||||
urlToDownload = urls;
|
||||
else {
|
||||
String[] allUrls = urls.split("\\\\");
|
||||
allUrlsLen = allUrls.length;
|
||||
urlIndex = (c.getArtIndex()-1) % allUrlsLen;
|
||||
urlToDownload = allUrls[urlIndex];
|
||||
}
|
||||
|
||||
//System.out.println(c.getName() + "|" + c.getEdition() + " - " + c.getArtIndex() + " -> " + urlIndex + "/" + allUrlsLen + " === " + filename + " <<< " + urlToDownload);
|
||||
downloads.put(destFile.getAbsolutePath(), urlToDownload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import forge.properties.ForgeConstants;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class GuiDownloadPrices extends GuiDownloader {
|
||||
public GuiDownloadPrices() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, String> getNeededImages() {
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
result.put(ForgeConstants.QUEST_CARD_PRICE_FILE, ForgeConstants.URL_PRICE_DOWNLOAD);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import forge.properties.ForgeConstants;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/** */
|
||||
@SuppressWarnings("serial")
|
||||
public class GuiDownloadQuestImages extends GuiDownloader {
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for GuiDownloadQuestImages.
|
||||
* </p>
|
||||
*/
|
||||
public GuiDownloadQuestImages() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getNeededCards.
|
||||
* </p>
|
||||
*
|
||||
* @return an array of {@link forge.download.GuiDownloadSetPicturesLQ} objects.
|
||||
*/
|
||||
@Override
|
||||
protected final Map<String, String> getNeededImages() {
|
||||
// read all card names and urls
|
||||
final Map<String, String> urls = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
addMissingItems(urls, ForgeConstants.IMAGE_LIST_QUEST_OPPONENT_ICONS_FILE, ForgeConstants.CACHE_ICON_PICS_DIR);
|
||||
addMissingItems(urls, ForgeConstants.IMAGE_LIST_QUEST_PET_SHOP_ICONS_FILE, ForgeConstants.CACHE_ICON_PICS_DIR);
|
||||
addMissingItems(urls, ForgeConstants.IMAGE_LIST_QUEST_BOOSTERS_FILE, ForgeConstants.CACHE_BOOSTER_PICS_DIR);
|
||||
addMissingItems(urls, ForgeConstants.IMAGE_LIST_QUEST_FATPACKS_FILE, ForgeConstants.CACHE_FATPACK_PICS_DIR);
|
||||
addMissingItems(urls, ForgeConstants.IMAGE_LIST_QUEST_PRECONS_FILE, ForgeConstants.CACHE_PRECON_PICS_DIR);
|
||||
addMissingItems(urls, ForgeConstants.IMAGE_LIST_QUEST_TOURNAMENTPACKS_FILE, ForgeConstants.CACHE_TOURNAMENTPACK_PICS_DIR);
|
||||
addMissingItems(urls, ForgeConstants.IMAGE_LIST_QUEST_TOKENS_FILE, ForgeConstants.CACHE_TOKEN_PICS_DIR);
|
||||
|
||||
return urls;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.assets.ImageUtil;
|
||||
import forge.card.CardEdition;
|
||||
import forge.item.PaperCard;
|
||||
import forge.model.FModel;
|
||||
import forge.properties.ForgeConstants;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class GuiDownloadSetPicturesLQ extends GuiDownloader {
|
||||
public GuiDownloadSetPicturesLQ() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Map<String, String> getNeededImages() {
|
||||
Map<String, String> downloads = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
for (final PaperCard c : Iterables.concat(FModel.getMagicDb().getCommonCards().getAllCards(), FModel.getMagicDb().getVariantCards().getAllCards())) {
|
||||
final String setCode3 = c.getEdition();
|
||||
if (StringUtils.isBlank(setCode3) || CardEdition.UNKNOWN.getCode().equals(setCode3)) {
|
||||
// we don't want cards from unknown sets
|
||||
continue;
|
||||
}
|
||||
addDLObject(ImageUtil.getDownloadUrl(c, false), ImageUtil.getImageKey(c, false, true), downloads);
|
||||
|
||||
if (ImageUtil.hasBackFacePicture(c)) {
|
||||
addDLObject(ImageUtil.getDownloadUrl(c, true), ImageUtil.getImageKey(c, true, true), downloads);
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing tokens to the list of things to download.
|
||||
addMissingItems(downloads, ForgeConstants.IMAGE_LIST_TOKENS_FILE, ForgeConstants.CACHE_TOKEN_PICS_DIR);
|
||||
|
||||
return downloads;
|
||||
}
|
||||
|
||||
private void addDLObject(String urlPath, String filename, Map<String, String> downloads) {
|
||||
File destFile = new File(ForgeConstants.CACHE_CARD_PICS_DIR, filename + ".jpg");
|
||||
// System.out.println(filename);
|
||||
if (!destFile.exists()) {
|
||||
downloads.put(destFile.getAbsolutePath(), ForgeConstants.URL_PIC_DOWNLOAD + urlPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import com.esotericsoftware.minlog.Log;
|
||||
|
||||
import forge.UiCommand;
|
||||
import forge.ImageCache;
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.error.BugReporter;
|
||||
import forge.gui.SOverlayUtils;
|
||||
import forge.toolbox.*;
|
||||
import forge.util.FileUtil;
|
||||
import forge.util.MyRandom;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Random;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class GuiDownloader extends DefaultBoundedRangeModel implements Runnable {
|
||||
public static final Proxy.Type[] TYPES = Proxy.Type.values();
|
||||
|
||||
// Actions and commands
|
||||
private final ActionListener actStartDownload = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
// invalidate image cache so newly downloaded images will be loaded
|
||||
ImageCache.clear();
|
||||
new Thread(GuiDownloader.this).start();
|
||||
btnStart.setEnabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
private final ActionListener actOK = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
private final UiCommand cmdClose = new UiCommand() { @Override
|
||||
public void run() { close(); } };
|
||||
|
||||
// Swing components
|
||||
private final FPanel pnlDialog = new FPanel(new MigLayout("insets 0, gap 0, wrap, ax center, ay center"));
|
||||
private final FProgressBar barProgress = new FProgressBar();
|
||||
private final FButton btnStart = new FButton("Start");
|
||||
private final JTextField txfAddr = new JTextField("Proxy Address");
|
||||
private final JTextField txfPort = new JTextField("Proxy Port");
|
||||
|
||||
private final FLabel btnClose = new FLabel.Builder().text("X")
|
||||
.hoverable(true).fontAlign(SwingConstants.CENTER).cmdClick(cmdClose).build();
|
||||
|
||||
private final JRadioButton radProxyNone = new FRadioButton("No Proxy");
|
||||
private final JRadioButton radProxySocks = new FRadioButton("SOCKS Proxy");
|
||||
private final JRadioButton radProxyHTTP = new FRadioButton("HTTP Proxy");
|
||||
|
||||
// Proxy info
|
||||
private int type;
|
||||
|
||||
// Progress variables
|
||||
private Map<String, String> cards; // local path -> url
|
||||
private boolean cancel;
|
||||
private final long[] times = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
private int tptr = 0;
|
||||
private int skipped = 0;
|
||||
private long lTime = System.currentTimeMillis();
|
||||
|
||||
protected GuiDownloader() {
|
||||
String radConstraints = "w 100%!, h 30px!, gap 2% 0 0 10px";
|
||||
JXButtonPanel grpPanel = new JXButtonPanel();
|
||||
grpPanel.add(radProxyNone, radConstraints);
|
||||
grpPanel.add(radProxyHTTP, radConstraints);
|
||||
grpPanel.add(radProxySocks, radConstraints);
|
||||
|
||||
radProxyNone.addChangeListener(new ProxyHandler(0));
|
||||
radProxyHTTP.addChangeListener(new ProxyHandler(1));
|
||||
radProxySocks.addChangeListener(new ProxyHandler(2));
|
||||
radProxyNone.setSelected(true);
|
||||
|
||||
btnClose.setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_TEXT)));
|
||||
btnStart.setFont(FSkin.getFont(18));
|
||||
btnStart.setVisible(false);
|
||||
|
||||
barProgress.reset();
|
||||
barProgress.setString("Scanning for existing items...");
|
||||
pnlDialog.setBackgroundTexture(FSkin.getIcon(FSkinProp.BG_TEXTURE));
|
||||
|
||||
// Layout
|
||||
pnlDialog.add(grpPanel, "w 50%!");
|
||||
pnlDialog.add(txfAddr, "w 95%!, h 30px!, gap 2% 0 0 10px");
|
||||
pnlDialog.add(txfPort, "w 95%!, h 30px!, gap 2% 0 0 10px");
|
||||
pnlDialog.add(barProgress, "w 95%!, h 40px!, gap 2% 0 20px 0");
|
||||
pnlDialog.add(btnStart, "w 200px!, h 40px!, gap 0 0 20px 0, ax center");
|
||||
pnlDialog.add(btnClose, "w 20px!, h 20px!, pos 370px 10px");
|
||||
|
||||
final JPanel pnl = FOverlay.SINGLETON_INSTANCE.getPanel();
|
||||
pnl.removeAll();
|
||||
pnl.setLayout(new MigLayout("insets 0, gap 0, wrap, ax center, ay center"));
|
||||
pnl.add(pnlDialog, "w 400px!, h 350px!, ax center, ay center");
|
||||
SOverlayUtils.showOverlay();
|
||||
|
||||
// Free up the EDT by assembling card list in the background
|
||||
SwingWorker<Void, Void> thrGetImages = new SwingWorker<Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
try {
|
||||
GuiDownloader.this.cards = GuiDownloader.this.getNeededImages();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
GuiDownloader.this.readyToStart();
|
||||
}
|
||||
};
|
||||
|
||||
thrGetImages.execute();
|
||||
}
|
||||
|
||||
private void readyToStart() {
|
||||
if (this.cards.isEmpty()) {
|
||||
barProgress.setString("All items have been downloaded.");
|
||||
btnStart.setText("OK");
|
||||
btnStart.addActionListener(actOK);
|
||||
} else {
|
||||
barProgress.setMaximum(this.cards.size());
|
||||
barProgress.setString(this.cards.size() == 1 ? "1 item found." : this.cards.size() + " items found.");
|
||||
//for(Entry<String, String> kv : cards.entrySet()) System.out.printf("Will get %s from %s%n", kv.getKey(), kv.getValue());
|
||||
btnStart.addActionListener(actStartDownload);
|
||||
}
|
||||
btnStart.setVisible(true);
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
btnStart.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setCancel(final boolean cancel) {
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
private void close() {
|
||||
setCancel(true);
|
||||
|
||||
// Kill overlay
|
||||
SOverlayUtils.hideOverlay();
|
||||
}
|
||||
|
||||
protected final int getAverageTimePerObject() {
|
||||
int numNonzero = 10;
|
||||
|
||||
if (this.tptr > 9) {
|
||||
this.tptr = 0;
|
||||
}
|
||||
|
||||
this.times[this.tptr] = System.currentTimeMillis() - this.lTime;
|
||||
this.lTime = System.currentTimeMillis();
|
||||
|
||||
int tTime = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
tTime += this.times[i];
|
||||
if (this.times[i] == 0) {
|
||||
numNonzero--;
|
||||
}
|
||||
}
|
||||
|
||||
this.tptr++;
|
||||
return tTime / Math.max(1, numNonzero);
|
||||
}
|
||||
|
||||
private void update(final int card, final File dest) {
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GuiDownloader.this.fireStateChanged();
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final int a = GuiDownloader.this.getAverageTimePerObject();
|
||||
|
||||
if (card != GuiDownloader.this.cards.size()) {
|
||||
sb.append(card + "/" + GuiDownloader.this.cards.size() + " - ");
|
||||
|
||||
long t2Go = (GuiDownloader.this.cards.size() - card) * a;
|
||||
|
||||
if (t2Go > 3600000) {
|
||||
sb.append(String.format("%02d:", t2Go / 3600000));
|
||||
t2Go = t2Go % 3600000;
|
||||
}
|
||||
if (t2Go > 60000) {
|
||||
sb.append(String.format("%02d:", t2Go / 60000));
|
||||
t2Go = t2Go % 60000;
|
||||
} else {
|
||||
sb.append("00:");
|
||||
}
|
||||
|
||||
sb.append(String.format("%02d remaining.", t2Go / 1000));
|
||||
} else {
|
||||
sb.append(String.format("%d of %d items finished! Skipped " + skipped + " items. Please close!",
|
||||
card, GuiDownloader.this.cards.size()));
|
||||
btnStart.setText("OK");
|
||||
btnStart.addActionListener(actOK);
|
||||
btnStart.setEnabled(true);
|
||||
btnStart.requestFocusInWindow();
|
||||
}
|
||||
|
||||
GuiDownloader.this.barProgress.setString(sb.toString());
|
||||
System.out.println(card + "/" + GuiDownloader.this.cards.size() + " - " + dest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
final Random r = MyRandom.getRandom();
|
||||
|
||||
Proxy p = null;
|
||||
if (this.type == 0) {
|
||||
p = Proxy.NO_PROXY;
|
||||
} else {
|
||||
try {
|
||||
p = new Proxy(GuiDownloader.TYPES[this.type], new InetSocketAddress(this.txfAddr.getText(),
|
||||
Integer.parseInt(this.txfPort.getText())));
|
||||
} catch (final Exception ex) {
|
||||
BugReporter.reportException(ex,
|
||||
"Proxy connection could not be established!\nProxy address: %s\nProxy port: %s",
|
||||
this.txfAddr.getText(), this.txfPort.getText());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int iCard = 0;
|
||||
for(Entry<String, String> kv : cards.entrySet()) {
|
||||
if( cancel )
|
||||
break;
|
||||
|
||||
String url = kv.getValue();
|
||||
final File fileDest = new File(kv.getKey());
|
||||
final File base = fileDest.getParentFile();
|
||||
|
||||
ReadableByteChannel rbc = null;
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
// test for folder existence
|
||||
if (!base.exists() && !base.mkdir()) { // create folder if not found
|
||||
System.out.println("Can't create folder" + base.getAbsolutePath());
|
||||
}
|
||||
|
||||
URL imageUrl = new URL(url);
|
||||
HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection(p);
|
||||
// don't allow redirections here -- they indicate 'file not found' on the server
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
conn.connect();
|
||||
|
||||
if (conn.getResponseCode() != 200) {
|
||||
conn.disconnect();
|
||||
System.out.println("Skipped Download for: " + fileDest.getPath());
|
||||
update(++iCard, fileDest);
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
rbc = Channels.newChannel(conn.getInputStream());
|
||||
fos = new FileOutputStream(fileDest);
|
||||
fos.getChannel().transferFrom(rbc, 0, 1 << 24);
|
||||
} catch (final ConnectException ce) {
|
||||
System.out.println("Connection refused for url: " + url);
|
||||
} catch (final MalformedURLException mURLe) {
|
||||
System.out.println("Error - possibly missing URL for: " + fileDest.getName());
|
||||
} catch (final FileNotFoundException fnfe) {
|
||||
String formatStr = "Error - the LQ picture %s could not be found on the server. [%s] - %s";
|
||||
System.out.println(String.format(formatStr, fileDest.getName(), url, fnfe.getMessage()));
|
||||
} catch (final Exception ex) {
|
||||
Log.error("LQ Pictures", "Error downloading pictures", ex);
|
||||
} finally {
|
||||
if (null != rbc) {
|
||||
try { rbc.close(); } catch (IOException e) { System.out.println("error closing input stream"); }
|
||||
}
|
||||
if (null != fos) {
|
||||
try { fos.close(); } catch (IOException e) { System.out.println("error closing output stream"); }
|
||||
}
|
||||
}
|
||||
|
||||
update(++iCard, fileDest);
|
||||
|
||||
// throttle to reduce load on the server
|
||||
try {
|
||||
Thread.sleep(r.nextInt(50) + 50);
|
||||
} catch (final InterruptedException e) {
|
||||
Log.error("GuiDownloader", "Sleep Error", e);
|
||||
}
|
||||
} // for
|
||||
}
|
||||
|
||||
protected abstract Map<String, String> getNeededImages();
|
||||
|
||||
protected static void addMissingItems(Map<String, String> list, String nameUrlFile, String dir) {
|
||||
for (Pair<String, String> nameUrlPair : FileUtil.readNameUrlFile(nameUrlFile)) {
|
||||
File f = new File(dir, nameUrlPair.getLeft());
|
||||
//System.out.println(f.getAbsolutePath());
|
||||
if (!f.exists()) {
|
||||
list.put(f.getAbsolutePath(), nameUrlPair.getRight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class ProxyHandler implements ChangeListener {
|
||||
private final int type;
|
||||
|
||||
public ProxyHandler(final int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void stateChanged(final ChangeEvent e) {
|
||||
if (((AbstractButton) e.getSource()).isSelected()) {
|
||||
GuiDownloader.this.type = this.type;
|
||||
GuiDownloader.this.txfAddr.setEnabled(this.type != 0);
|
||||
GuiDownloader.this.txfPort.setEnabled(this.type != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
/** Forge Card Game. */
|
||||
package forge.download;
|
||||
|
||||
324
forge-gui-desktop/src/main/java/forge/error/BugReporter.java
Normal file
324
forge-gui-desktop/src/main/java/forge/error/BugReporter.java
Normal file
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* 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.FThreads;
|
||||
import forge.gui.WrapLayout;
|
||||
import forge.toolbox.FHyperlink;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.util.BuildInfo;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* The class ErrorViewer. Enables showing and saving error messages that
|
||||
* occurred in forge.
|
||||
*
|
||||
* @author Clemens Koza
|
||||
* @version V1.0 02.08.2009
|
||||
*/
|
||||
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;
|
||||
}
|
||||
if (message != null) {
|
||||
System.err.printf("%s > %s%n", FThreads.debugGetCurrThreadId(), message);
|
||||
}
|
||||
System.err.print( FThreads.debugGetCurrThreadId() + " > " );
|
||||
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(FThreads.debugGetCurrThreadId()).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() { }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
/** Forge Card Game. */
|
||||
package forge.error;
|
||||
|
||||
50
forge-gui-desktop/src/main/java/forge/gui/CardContainer.java
Normal file
50
forge-gui-desktop/src/main/java/forge/gui/CardContainer.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import forge.game.card.Card;
|
||||
|
||||
/**
|
||||
* The class CardContainer. A card container is an object that references a
|
||||
* card.
|
||||
*
|
||||
* @author Clemens Koza
|
||||
* @version V0.0 17.02.2010
|
||||
*/
|
||||
public interface CardContainer {
|
||||
/**
|
||||
* <p>
|
||||
* setCard.
|
||||
* </p>
|
||||
*
|
||||
* @param card
|
||||
* a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
void setCard(Card card);
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getCard.
|
||||
* </p>
|
||||
*
|
||||
* @return a {@link forge.game.card.Card} object.
|
||||
*/
|
||||
Card getCard();
|
||||
|
||||
}
|
||||
700
forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java
Normal file
700
forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java
Normal file
@@ -0,0 +1,700 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import forge.Singletons;
|
||||
import forge.card.CardCharacteristicName;
|
||||
import forge.card.CardEdition;
|
||||
import forge.card.CardType;
|
||||
import forge.card.ColorSet;
|
||||
import forge.game.GameEntity;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.card.CardUtil;
|
||||
import forge.game.card.CounterType;
|
||||
import forge.game.player.Player;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.item.IPaperCard;
|
||||
import forge.item.InventoryItemFromSet;
|
||||
import forge.item.PreconDeck;
|
||||
import forge.item.SealedProduct;
|
||||
import forge.model.FModel;
|
||||
import forge.toolbox.*;
|
||||
import forge.toolbox.FSkin.SkinnedPanel;
|
||||
import forge.util.Lang;
|
||||
import forge.view.FDialog;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* The class CardDetailPanel. Shows the details of a card.
|
||||
*
|
||||
* @author Clemens Koza
|
||||
* @version V0.0 17.02.2010
|
||||
*/
|
||||
public class CardDetailPanel extends SkinnedPanel {
|
||||
/** Constant <code>serialVersionUID=-8461473263764812323L</code>. */
|
||||
private static final long serialVersionUID = -8461473263764812323L;
|
||||
|
||||
private static Color purple = new Color(14381203);
|
||||
|
||||
private final FLabel nameCostLabel;
|
||||
private final FLabel typeLabel;
|
||||
private final FLabel powerToughnessLabel;
|
||||
private final FLabel idLabel;
|
||||
private final JLabel setInfoLabel;
|
||||
private final FHtmlViewer cdArea;
|
||||
private final FScrollPane scrArea;
|
||||
|
||||
public CardDetailPanel(final Card card) {
|
||||
super();
|
||||
this.setLayout(null);
|
||||
this.setOpaque(false);
|
||||
|
||||
this.nameCostLabel = new FLabel.Builder().fontAlign(SwingConstants.CENTER).build();
|
||||
this.typeLabel = new FLabel.Builder().fontAlign(SwingConstants.CENTER).build();
|
||||
this.idLabel = new FLabel.Builder().fontAlign(SwingConstants.LEFT).tooltip("Card ID").build();
|
||||
this.powerToughnessLabel = new FLabel.Builder().fontAlign(SwingConstants.CENTER).build();
|
||||
this.setInfoLabel = new JLabel();
|
||||
this.setInfoLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
Font font = new Font("Dialog", 0, 14);
|
||||
this.nameCostLabel.setFont(font);
|
||||
this.typeLabel.setFont(font);
|
||||
this.idLabel.setFont(font);
|
||||
this.powerToughnessLabel.setFont(font);
|
||||
|
||||
this.cdArea = new FHtmlViewer();
|
||||
this.cdArea.setBorder(new EmptyBorder(2, 6, 2, 6));
|
||||
this.cdArea.setOpaque(false);
|
||||
this.scrArea = new FScrollPane(this.cdArea, false);
|
||||
|
||||
this.add(this.nameCostLabel);
|
||||
this.add(this.typeLabel);
|
||||
this.add(this.idLabel);
|
||||
this.add(this.powerToughnessLabel);
|
||||
this.add(this.setInfoLabel);
|
||||
this.add(this.scrArea);
|
||||
|
||||
this.setCard(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout() {
|
||||
int insets = 3;
|
||||
int setInfoWidth = 40;
|
||||
int x = insets;
|
||||
int y = insets;
|
||||
int lineWidth = getWidth() - 2 * insets;
|
||||
int lineHeight = this.nameCostLabel.getPreferredSize().height;
|
||||
int dy = lineHeight + 1;
|
||||
|
||||
this.nameCostLabel.setBounds(x, y, lineWidth, lineHeight);
|
||||
y += dy;
|
||||
|
||||
this.typeLabel.setBounds(x, y, lineWidth, lineHeight);
|
||||
y += dy;
|
||||
|
||||
this.idLabel.setBounds(x, y, this.idLabel.getAutoSizeWidth(), lineHeight);
|
||||
this.powerToughnessLabel.setBounds(x, y, lineWidth, lineHeight);
|
||||
|
||||
//+1 to x,y so set info label right up against border and the baseline matches ID and P/T
|
||||
this.setInfoLabel.setBounds(x + lineWidth - setInfoWidth + 1, y + 1, setInfoWidth, lineHeight);
|
||||
y += dy;
|
||||
|
||||
this.scrArea.setBounds(0, y, getWidth(), getHeight() - y);
|
||||
}
|
||||
|
||||
public String getItemDescription(InventoryItemFromSet i) {
|
||||
if( i instanceof SealedProduct )
|
||||
return ((SealedProduct)i).getDescription();
|
||||
if( i instanceof PreconDeck)
|
||||
return ((PreconDeck) i).getDescription();
|
||||
return i.getName();
|
||||
}
|
||||
|
||||
public final void setItem(InventoryItemFromSet item) {
|
||||
nameCostLabel.setText(item.getName());
|
||||
typeLabel.setVisible(false);
|
||||
powerToughnessLabel.setVisible(false);
|
||||
idLabel.setText("");
|
||||
cdArea.setText(getItemDescription(item));
|
||||
this.updateBorder(item instanceof IPaperCard ? ((IPaperCard)item).getRules().getColor() : null, false);
|
||||
|
||||
String set = item.getEdition();
|
||||
setInfoLabel.setText(set);
|
||||
setInfoLabel.setToolTipText("");
|
||||
if (StringUtils.isEmpty(set)) {
|
||||
setInfoLabel.setOpaque(false);
|
||||
setInfoLabel.setBorder(null);
|
||||
} else {
|
||||
CardEdition edition = FModel.getMagicDb().getEditions().get(set);
|
||||
if (null != edition) {
|
||||
setInfoLabel.setToolTipText(edition.getName());
|
||||
}
|
||||
|
||||
this.setInfoLabel.setOpaque(true);
|
||||
this.setInfoLabel.setBackground(Color.BLACK);
|
||||
this.setInfoLabel.setForeground(Color.WHITE);
|
||||
this.setInfoLabel.setBorder(BorderFactory.createLineBorder(Color.WHITE));
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scrArea.getVerticalScrollBar().setValue(scrArea.getVerticalScrollBar().getMinimum());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public final void setCard(final Card card) {
|
||||
this.nameCostLabel.setText("");
|
||||
this.typeLabel.setVisible(true);
|
||||
this.typeLabel.setText("");
|
||||
this.powerToughnessLabel.setVisible(true);
|
||||
this.powerToughnessLabel.setText("");
|
||||
this.idLabel.setText("");
|
||||
this.setInfoLabel.setText("");
|
||||
this.setInfoLabel.setToolTipText("");
|
||||
this.setInfoLabel.setOpaque(false);
|
||||
this.setInfoLabel.setBorder(null);
|
||||
this.cdArea.setText("");
|
||||
if( card == null ) {
|
||||
this.updateBorder(null, false);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean canShowThis = false;
|
||||
|
||||
if (card.isFaceDown()) {
|
||||
if (card.isInZone(ZoneType.Battlefield)) {
|
||||
this.nameCostLabel.setText("Morph");
|
||||
this.typeLabel.setText("Creature");
|
||||
}
|
||||
}
|
||||
else if (Singletons.getControl().mayShowCard(card) || FDialog.isModalOpen()) { //allow showing cards while modal open to account for revealing, picking, and ordering cards
|
||||
canShowThis = true;
|
||||
|
||||
if (card.getManaCost().isNoCost()) {
|
||||
this.nameCostLabel.setText(card.getName());
|
||||
}
|
||||
else {
|
||||
String manaCost = card.getManaCost().toString();
|
||||
if ( card.isSplitCard() && card.getCurState() == CardCharacteristicName.Original) {
|
||||
manaCost = card.getRules().getMainPart().getManaCost().toString() + " // " + card.getRules().getOtherPart().getManaCost().toString();
|
||||
}
|
||||
this.nameCostLabel.setText(FSkin.encodeSymbols(card.getName() + " - " + manaCost, true));
|
||||
}
|
||||
this.typeLabel.setText(formatCardType(card));
|
||||
|
||||
String set = card.getCurSetCode();
|
||||
this.setInfoLabel.setText(set);
|
||||
if (null != set && !set.isEmpty()) {
|
||||
CardEdition edition = FModel.getMagicDb().getEditions().get(set);
|
||||
if (null == edition) {
|
||||
setInfoLabel.setToolTipText(card.getRarity().name());
|
||||
}
|
||||
else {
|
||||
setInfoLabel.setToolTipText(String.format("%s (%s)", edition.getName(), card.getRarity().name()));
|
||||
}
|
||||
|
||||
this.setInfoLabel.setOpaque(true);
|
||||
switch(card.getRarity()) {
|
||||
case Uncommon:
|
||||
this.setInfoLabel.setBackground(Color.LIGHT_GRAY);
|
||||
this.setInfoLabel.setForeground(Color.BLACK);
|
||||
this.setInfoLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
|
||||
break;
|
||||
|
||||
case Rare:
|
||||
this.setInfoLabel.setBackground(Color.YELLOW);
|
||||
this.setInfoLabel.setForeground(Color.BLACK);
|
||||
this.setInfoLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
|
||||
break;
|
||||
|
||||
case MythicRare:
|
||||
this.setInfoLabel.setBackground(Color.RED);
|
||||
this.setInfoLabel.setForeground(Color.BLACK);
|
||||
this.setInfoLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
|
||||
break;
|
||||
|
||||
case Special:
|
||||
// "Timeshifted" or other Special Rarity Cards
|
||||
this.setInfoLabel.setBackground(CardDetailPanel.purple);
|
||||
this.setInfoLabel.setForeground(Color.BLACK);
|
||||
this.setInfoLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
|
||||
break;
|
||||
|
||||
default: //case BasicLand: + case Common:
|
||||
this.setInfoLabel.setBackground(Color.BLACK);
|
||||
this.setInfoLabel.setForeground(Color.WHITE);
|
||||
this.setInfoLabel.setBorder(BorderFactory.createLineBorder(Color.WHITE));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateBorder(CardUtil.getColors(card), canShowThis);
|
||||
|
||||
StringBuilder ptText = new StringBuilder();
|
||||
if (card.isCreature()) {
|
||||
ptText.append(card.getNetAttack()).append(" / ").append(card.getNetDefense());
|
||||
}
|
||||
|
||||
if (card.isPlaneswalker()) {
|
||||
if (ptText.length() > 0) {
|
||||
ptText.insert(0, "P/T: ");
|
||||
ptText.append(" - ").append("Loy: ");
|
||||
} else {
|
||||
ptText.append("Loyalty: ");
|
||||
}
|
||||
|
||||
int loyalty = card.getCounters(CounterType.LOYALTY);
|
||||
if (loyalty == 0) {
|
||||
loyalty = card.getBaseLoyalty();
|
||||
}
|
||||
ptText.append(loyalty);
|
||||
}
|
||||
|
||||
this.powerToughnessLabel.setText(ptText.toString());
|
||||
|
||||
this.idLabel.setText(card.getUniqueNumber() > 0 ? "[" + card.getUniqueNumber() + "]" : "");
|
||||
|
||||
// fill the card text
|
||||
this.cdArea.setText(composeCardText(card, canShowThis));
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scrArea.getVerticalScrollBar().setValue(scrArea.getVerticalScrollBar().getMinimum());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String composeCardText(final Card card, final boolean canShow) {
|
||||
final StringBuilder area = new StringBuilder();
|
||||
|
||||
// Token
|
||||
if (card.isToken()) {
|
||||
area.append("Token");
|
||||
}
|
||||
|
||||
if (canShow) {
|
||||
// card text
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
String text = card.getText();
|
||||
// LEVEL [0-9]+-[0-9]+
|
||||
// LEVEL [0-9]+\+
|
||||
|
||||
String regex = "LEVEL [0-9]+-[0-9]+ ";
|
||||
text = text.replaceAll(regex, "$0\r\n");
|
||||
|
||||
regex = "LEVEL [0-9]+\\+ ";
|
||||
text = text.replaceAll(regex, "\r\n$0\r\n");
|
||||
|
||||
// displays keywords that have dots in them a little better:
|
||||
regex = "\\., ";
|
||||
text = text.replaceAll(regex, ".\r\n");
|
||||
|
||||
area.append(text);
|
||||
}
|
||||
|
||||
if (card.isPhasedOut()) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Phased Out");
|
||||
}
|
||||
|
||||
// counter text
|
||||
final CounterType[] counters = CounterType.values();
|
||||
for (final CounterType counter : counters) {
|
||||
if (card.getCounters(counter) != 0) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append(counter.getName() + " counters: ");
|
||||
area.append(card.getCounters(counter));
|
||||
}
|
||||
}
|
||||
|
||||
if (card.isCreature()) {
|
||||
int damage = card.getDamage();
|
||||
if (damage > 0) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Damage: " + damage);
|
||||
}
|
||||
int assigned = card.getTotalAssignedDamage();
|
||||
if (assigned > 0) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Assigned Damage: " + assigned);
|
||||
}
|
||||
}
|
||||
if (card.isPlaneswalker()) {
|
||||
int assigned = card.getTotalAssignedDamage();
|
||||
if (assigned > 0) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Assigned Damage: " + assigned);
|
||||
}
|
||||
}
|
||||
|
||||
// Regeneration Shields
|
||||
final int regenShields = card.getShield().size();
|
||||
if (regenShields > 0) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Regeneration Shield(s): ").append(regenShields);
|
||||
}
|
||||
|
||||
// Damage Prevention
|
||||
final int preventNextDamage = card.getPreventNextDamageTotalShields();
|
||||
if (preventNextDamage > 0) {
|
||||
area.append("\n");
|
||||
area.append("Prevent the next ").append(preventNextDamage).append(" damage that would be dealt to ");
|
||||
area.append(card.getName()).append(" this turn.");
|
||||
}
|
||||
|
||||
// top revealed
|
||||
if ((card.hasKeyword("Play with the top card of your library revealed.") || card
|
||||
.hasKeyword("Players play with the top card of their libraries revealed."))
|
||||
&& card.getController() != null
|
||||
&& (card.isInZone(ZoneType.Battlefield) || (card.isInZone(ZoneType.Command) && !card.isCommander()))
|
||||
&& !card.getController().getZone(ZoneType.Library).isEmpty()) {
|
||||
area.append("\r\nTop card of your library: ");
|
||||
area.append(card.getController().getCardsIn(ZoneType.Library, 1));
|
||||
if (card.hasKeyword("Players play with the top card of their libraries revealed.")) {
|
||||
for (final Player p : card.getController().getAllOtherPlayers()) {
|
||||
if (p.getZone(ZoneType.Library).isEmpty()) {
|
||||
area.append(p.getName());
|
||||
area.append("'s library is empty.");
|
||||
} else {
|
||||
area.append("\r\nTop card of ");
|
||||
area.append(p.getName());
|
||||
area.append("'s library: ");
|
||||
area.append(p.getCardsIn(ZoneType.Library, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// chosen type
|
||||
if (!card.getChosenType().equals("")) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("(chosen type: ");
|
||||
area.append(card.getChosenType());
|
||||
area.append(")");
|
||||
}
|
||||
|
||||
// chosen color
|
||||
if (!card.getChosenColor().isEmpty()) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("(chosen colors: ");
|
||||
area.append(card.getChosenColor());
|
||||
area.append(")");
|
||||
}
|
||||
|
||||
// chosen player
|
||||
if (card.getChosenPlayer() != null) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("(chosen player: " + card.getChosenPlayer() + ")");
|
||||
}
|
||||
|
||||
// named card
|
||||
if (!card.getNamedCard().equals("")) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("(named card: ");
|
||||
area.append(card.getNamedCard());
|
||||
area.append(")");
|
||||
}
|
||||
|
||||
// equipping
|
||||
if (!card.getEquipping().isEmpty()) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("=Equipping ");
|
||||
area.append(card.getEquipping().get(0));
|
||||
area.append("=");
|
||||
}
|
||||
|
||||
// equipped by
|
||||
if (!card.getEquippedBy().isEmpty()) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("=Equipped by ");
|
||||
for (final Iterator<Card> it = card.getEquippedBy().iterator(); it.hasNext();) {
|
||||
area.append(it.next());
|
||||
if (it.hasNext()) {
|
||||
area.append(", ");
|
||||
}
|
||||
}
|
||||
area.append("=");
|
||||
}
|
||||
|
||||
// enchanting
|
||||
final GameEntity entity = card.getEnchanting();
|
||||
if (entity != null) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("*Enchanting ");
|
||||
|
||||
if (entity instanceof Card) {
|
||||
final Card c = (Card) entity;
|
||||
if (!Singletons.getControl().mayShowCard(c)) {
|
||||
area.append("Morph (");
|
||||
area.append(card.getUniqueNumber());
|
||||
area.append(")");
|
||||
} else {
|
||||
area.append(entity);
|
||||
}
|
||||
} else {
|
||||
area.append(entity);
|
||||
}
|
||||
area.append("*");
|
||||
}
|
||||
|
||||
// enchanted by
|
||||
if (!card.getEnchantedBy().isEmpty()) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("*Enchanted by ");
|
||||
for (final Iterator<Card> it = card.getEnchantedBy().iterator(); it.hasNext();) {
|
||||
area.append(it.next());
|
||||
if (it.hasNext()) {
|
||||
area.append(", ");
|
||||
}
|
||||
}
|
||||
area.append("*");
|
||||
}
|
||||
|
||||
// controlling
|
||||
if (card.getGainControlTargets().size() > 0) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("+Controlling: ");
|
||||
for (final Iterator<Card> it = card.getGainControlTargets().iterator(); it.hasNext();) {
|
||||
area.append(it.next());
|
||||
if (it.hasNext()) {
|
||||
area.append(", ");
|
||||
}
|
||||
}
|
||||
area.append("+");
|
||||
}
|
||||
|
||||
// cloned via
|
||||
if (card.getCloneOrigin() != null) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("^Cloned via: ");
|
||||
area.append(card.getCloneOrigin().getName());
|
||||
area.append("^");
|
||||
}
|
||||
|
||||
// Imprint
|
||||
if (!card.getImprinted().isEmpty()) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Imprinting: ");
|
||||
for (final Iterator<Card> it = card.getImprinted().iterator(); it.hasNext();) {
|
||||
area.append(it.next());
|
||||
if (it.hasNext()) {
|
||||
area.append(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Haunt
|
||||
if (!card.getHauntedBy().isEmpty()) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Haunted by: ");
|
||||
for (final Iterator<Card> it = card.getHauntedBy().iterator(); it.hasNext();) {
|
||||
area.append(it.next());
|
||||
if (it.hasNext()) {
|
||||
area.append(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (card.getHaunting() != null) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
area.append("Haunting " + card.getHaunting());
|
||||
}
|
||||
|
||||
// must block
|
||||
if (card.getMustBlockCards() != null) {
|
||||
if (area.length() != 0) {
|
||||
area.append("\n");
|
||||
}
|
||||
String mustBlockThese = Lang.joinHomogenous(card.getMustBlockCards());
|
||||
area.append("Must block " + mustBlockThese);
|
||||
}
|
||||
return FSkin.encodeSymbols(area.toString(), true);
|
||||
}
|
||||
|
||||
/** @return FLabel */
|
||||
public FLabel getNameCostLabel() {
|
||||
return this.nameCostLabel;
|
||||
}
|
||||
|
||||
/** @return FLabel */
|
||||
public FLabel getTypeLabel() {
|
||||
return this.typeLabel;
|
||||
}
|
||||
|
||||
/** @return FLabel */
|
||||
public FLabel getPowerToughnessLabel() {
|
||||
return this.powerToughnessLabel;
|
||||
}
|
||||
|
||||
/** @return JLabel */
|
||||
public JLabel getSetInfoLabel() {
|
||||
return this.setInfoLabel;
|
||||
}
|
||||
|
||||
/** @return FHtmlViewer */
|
||||
public FHtmlViewer getCDArea() {
|
||||
return this.cdArea;
|
||||
}
|
||||
|
||||
public static String formatCardType(final Card card) {
|
||||
final ArrayList<String> list = card.getType();
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final ArrayList<String> superTypes = new ArrayList<String>();
|
||||
final ArrayList<String> cardTypes = new ArrayList<String>();
|
||||
final ArrayList<String> subTypes = new ArrayList<String>();
|
||||
final boolean allCreatureTypes = list.contains("AllCreatureTypes");
|
||||
|
||||
for (final String t : list) {
|
||||
if (allCreatureTypes && t.equals("AllCreatureTypes")) {
|
||||
continue;
|
||||
}
|
||||
if (CardType.isASuperType(t) && !superTypes.contains(t)) {
|
||||
superTypes.add(t);
|
||||
}
|
||||
if (CardType.isACardType(t) && !cardTypes.contains(t)) {
|
||||
cardTypes.add(t);
|
||||
}
|
||||
if (CardType.isASubType(t) && !subTypes.contains(t) && (!allCreatureTypes || !CardType.isACreatureType(t))) {
|
||||
subTypes.add(t);
|
||||
}
|
||||
}
|
||||
|
||||
for (final String type : superTypes) {
|
||||
sb.append(type).append(" ");
|
||||
}
|
||||
for (final String type : cardTypes) {
|
||||
sb.append(type).append(" ");
|
||||
}
|
||||
if (!subTypes.isEmpty() || allCreatureTypes) {
|
||||
sb.append("- ");
|
||||
}
|
||||
if (allCreatureTypes) {
|
||||
sb.append("All creature types ");
|
||||
}
|
||||
for (final String type : subTypes) {
|
||||
sb.append(type).append(" ");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void updateBorder(ColorSet list, final boolean canShow) {
|
||||
// color info
|
||||
if (list == null) {
|
||||
this.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
|
||||
scrArea.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
Color color;
|
||||
if (!canShow) {
|
||||
color = Color.gray;
|
||||
} else if (list.isMulticolor()) {
|
||||
color = Color.orange;
|
||||
} else if (list.hasBlack()) {
|
||||
color = Color.black;
|
||||
} else if (list.hasGreen()) {
|
||||
color = new Color(0, 220, 39);
|
||||
} else if (list.hasWhite()) {
|
||||
color = Color.white;
|
||||
} else if (list.hasRed()) {
|
||||
color = Color.red;
|
||||
} else if (list.hasBlue()) {
|
||||
color = Color.blue;
|
||||
} else if (list.isColorless()) {
|
||||
color = Color.gray;
|
||||
} else {
|
||||
color = new Color(200, 0, 230); // If your card has a violet border, something is wrong
|
||||
}
|
||||
|
||||
if (color != Color.gray) {
|
||||
int r = color.getRed();
|
||||
int g = color.getGreen();
|
||||
int b = color.getBlue();
|
||||
|
||||
final int shade = 10;
|
||||
|
||||
r -= shade;
|
||||
g -= shade;
|
||||
b -= shade;
|
||||
|
||||
r = Math.max(0, r);
|
||||
g = Math.max(0, g);
|
||||
b = Math.max(0, b);
|
||||
|
||||
color = new Color(r, g, b);
|
||||
}
|
||||
this.setBorder(BorderFactory.createLineBorder(color, 2));
|
||||
scrArea.setBorder(BorderFactory.createMatteBorder(2, 0, 0, 0, color));
|
||||
}
|
||||
}
|
||||
162
forge-gui-desktop/src/main/java/forge/gui/CardListViewer.java
Normal file
162
forge-gui-desktop/src/main/java/forge/gui/CardListViewer.java
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import forge.game.card.Card;
|
||||
import forge.item.PaperCard;
|
||||
import forge.toolbox.FButton;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FScrollPane;
|
||||
import forge.view.FDialog;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowFocusListener;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple class that shows a list of cards in a dialog with preview in its
|
||||
* right part.
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: ListChooser.java 9708 2011-08-09 19:34:12Z jendave $
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class CardListViewer extends FDialog {
|
||||
|
||||
// Data and number of choices for the list
|
||||
private final List<PaperCard> list;
|
||||
|
||||
// initialized before; listeners may be added to it
|
||||
private final JList<PaperCard> jList;
|
||||
private final CardDetailPanel detail;
|
||||
private final CardPicturePanel picture;
|
||||
|
||||
/**
|
||||
* Instantiates a new card list viewer.
|
||||
*
|
||||
* @param title
|
||||
* the title
|
||||
* @param list
|
||||
* the list
|
||||
*/
|
||||
public CardListViewer(final String title, final List<PaperCard> list) {
|
||||
this(title, "", list, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new card list viewer.
|
||||
*
|
||||
* @param title
|
||||
* the title
|
||||
* @param message
|
||||
* the message
|
||||
* @param list
|
||||
* the list
|
||||
*/
|
||||
public CardListViewer(final String title, final String message, final List<PaperCard> list) {
|
||||
this(title, message, list, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new card list viewer.
|
||||
*
|
||||
* @param title
|
||||
* the title
|
||||
* @param message
|
||||
* the message
|
||||
* @param list
|
||||
* the list
|
||||
* @param dialogIcon
|
||||
* the dialog icon
|
||||
*/
|
||||
public CardListViewer(final String title, final String message, final List<PaperCard> list, final Icon dialogIcon) {
|
||||
this.list = Collections.unmodifiableList(list);
|
||||
this.jList = new JList<PaperCard>(new ChooserListModel());
|
||||
this.detail = new CardDetailPanel(null);
|
||||
this.picture = new CardPicturePanel();
|
||||
this.picture.setOpaque(false);
|
||||
|
||||
this.setTitle(title);
|
||||
this.setSize(720, 360);
|
||||
this.addWindowFocusListener(new CardListFocuser());
|
||||
|
||||
FButton btnOK = new FButton("OK");
|
||||
btnOK.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
CardListViewer.this.processWindowEvent(new WindowEvent(CardListViewer.this, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
});
|
||||
|
||||
this.add(new FLabel.Builder().text(message).build(), "cell 0 0, spanx 3, gapbottom 4");
|
||||
this.add(new FScrollPane(this.jList, true), "cell 0 1, w 225, growy, pushy, ax c");
|
||||
this.add(this.picture, "cell 1 1, w 225, growy, pushy, ax c");
|
||||
this.add(this.detail, "cell 2 1, w 225, growy, pushy, ax c");
|
||||
this.add(btnOK, "cell 1 2, w 150, h 26, ax c, gaptop 6");
|
||||
|
||||
// selection is here
|
||||
this.jList.getSelectionModel().addListSelectionListener(new SelListener());
|
||||
this.jList.setSelectedIndex(0);
|
||||
}
|
||||
|
||||
private class ChooserListModel extends AbstractListModel<PaperCard> {
|
||||
private static final long serialVersionUID = 3871965346333840556L;
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return CardListViewer.this.list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaperCard getElementAt(final int index) {
|
||||
return CardListViewer.this.list.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
private class CardListFocuser implements WindowFocusListener {
|
||||
@Override
|
||||
public void windowGainedFocus(final WindowEvent e) {
|
||||
CardListViewer.this.jList.grabFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowLostFocus(final WindowEvent e) {
|
||||
}
|
||||
}
|
||||
|
||||
private class SelListener implements ListSelectionListener {
|
||||
@Override
|
||||
public void valueChanged(final ListSelectionEvent e) {
|
||||
final int row = CardListViewer.this.jList.getSelectedIndex();
|
||||
// (String) jList.getSelectedValue();
|
||||
if ((row >= 0) && (row < CardListViewer.this.list.size())) {
|
||||
final PaperCard cp = CardListViewer.this.list.get(row);
|
||||
CardListViewer.this.detail.setCard(Card.getCardForUi(cp));
|
||||
CardListViewer.this.picture.setCard(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
forge-gui-desktop/src/main/java/forge/gui/CardPicturePanel.java
Normal file
111
forge-gui-desktop/src/main/java/forge/gui/CardPicturePanel.java
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import forge.ImageCache;
|
||||
import forge.ImageKeys;
|
||||
import forge.card.CardCharacteristicName;
|
||||
import forge.game.card.Card;
|
||||
import forge.item.InventoryItem;
|
||||
import forge.model.FModel;
|
||||
import forge.properties.ForgePreferences.FPref;
|
||||
import forge.toolbox.imaging.FImagePanel;
|
||||
import forge.toolbox.imaging.FImageUtil;
|
||||
import forge.toolbox.imaging.FImagePanel.AutoSizeImageMode;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* Displays image associated with a card or inventory item.
|
||||
*
|
||||
* @version $Id: CardPicturePanel.java 25265 2014-03-27 02:18:47Z drdev $
|
||||
*
|
||||
*/
|
||||
public final class CardPicturePanel extends JPanel {
|
||||
/** Constant <code>serialVersionUID=-3160874016387273383L</code>. */
|
||||
private static final long serialVersionUID = -3160874016387273383L;
|
||||
|
||||
private Object displayed;
|
||||
|
||||
private final FImagePanel panel;
|
||||
private BufferedImage currentImage;
|
||||
private boolean mayShowCard;
|
||||
|
||||
public CardPicturePanel() {
|
||||
super(new BorderLayout());
|
||||
|
||||
this.panel = new FImagePanel();
|
||||
this.add(this.panel);
|
||||
}
|
||||
|
||||
public void setCard(final InventoryItem cp) {
|
||||
this.displayed = cp;
|
||||
this.mayShowCard = true;
|
||||
this.setImage();
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void setCard(final Card c, boolean mayShowCard) {
|
||||
this.displayed = c;
|
||||
this.mayShowCard = mayShowCard;
|
||||
this.setImage();
|
||||
}
|
||||
|
||||
public void setCardImage(CardCharacteristicName flipState) {
|
||||
BufferedImage image = FImageUtil.getImage((Card)displayed, flipState);
|
||||
if (image != null && image != this.currentImage) {
|
||||
this.currentImage = image;
|
||||
this.panel.setImage(image, getAutoSizeImageMode());
|
||||
}
|
||||
}
|
||||
|
||||
public void setImage() {
|
||||
BufferedImage image = getImage();
|
||||
if (image != null && image != this.currentImage) {
|
||||
this.currentImage = image;
|
||||
this.panel.setImage(image, getAutoSizeImageMode());
|
||||
}
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
if (displayed instanceof InventoryItem) {
|
||||
InventoryItem item = (InventoryItem) displayed;
|
||||
return ImageCache.getOriginalImage(ImageKeys.getImageKey(item, false), true);
|
||||
}
|
||||
else if (displayed instanceof Card) {
|
||||
if (mayShowCard) {
|
||||
return FImageUtil.getImage((Card)displayed);
|
||||
}
|
||||
return ImageCache.getOriginalImage(ImageKeys.TOKEN_PREFIX + ImageKeys.MORPH_IMAGE, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private AutoSizeImageMode getAutoSizeImageMode() {
|
||||
return (isUIScaleLarger() ? AutoSizeImageMode.PANEL : AutoSizeImageMode.SOURCE);
|
||||
}
|
||||
|
||||
private boolean isUIScaleLarger() {
|
||||
return FModel.getPreferences().getPrefBoolean(FPref.UI_SCALE_LARGER);
|
||||
}
|
||||
|
||||
}
|
||||
451
forge-gui-desktop/src/main/java/forge/gui/DualListBox.java
Normal file
451
forge-gui-desktop/src/main/java/forge/gui/DualListBox.java
Normal file
@@ -0,0 +1,451 @@
|
||||
package forge.gui;
|
||||
|
||||
import forge.game.card.Card;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.item.IPaperCard;
|
||||
import forge.item.PaperCard;
|
||||
import forge.screens.match.CMatchUI;
|
||||
import forge.toolbox.*;
|
||||
import forge.view.FDialog;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListDataEvent;
|
||||
import javax.swing.event.ListDataListener;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// An input box for handling the order of choices.
|
||||
// Left box has the original choices
|
||||
// Right box has the final order
|
||||
// Top string will be like Top of the Stack or Top of the Library
|
||||
// Bottom string will be like Bottom of the Stack or Bottom of the Library
|
||||
// Single Arrows in between left box and right box for ordering
|
||||
// Multi Arrows for moving everything in order
|
||||
// Up/down arrows on the right of the right box for swapping
|
||||
// Single ok button, disabled until left box has specified number of items remaining
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class DualListBox<T> extends FDialog {
|
||||
private final FList<T> sourceList;
|
||||
private final UnsortedListModel<T> sourceListModel;
|
||||
|
||||
private final FList<T> destList;
|
||||
private final UnsortedListModel<T> destListModel;
|
||||
|
||||
private final FButton addButton;
|
||||
private final FButton addAllButton;
|
||||
private final FButton removeButton;
|
||||
private final FButton removeAllButton;
|
||||
private final FButton okButton;
|
||||
private final FButton autoButton;
|
||||
|
||||
private final FLabel orderedLabel;
|
||||
private final FLabel selectOrder;
|
||||
|
||||
private final int targetRemainingSourcesMin;
|
||||
private final int targetRemainingSourcesMax;
|
||||
|
||||
private boolean sideboardingMode = false;
|
||||
private boolean showCard = true;
|
||||
|
||||
public DualListBox(int remainingSources, List<T> sourceElements, List<T> destElements) {
|
||||
this(remainingSources, remainingSources, sourceElements, destElements);
|
||||
}
|
||||
|
||||
public DualListBox(int remainingSourcesMin, int remainingSourcesMax, List<T> sourceElements, List<T> destElements) {
|
||||
targetRemainingSourcesMin = remainingSourcesMin;
|
||||
targetRemainingSourcesMax = remainingSourcesMax;
|
||||
sourceListModel = new UnsortedListModel<T>();
|
||||
sourceList = new FList<T>(sourceListModel);
|
||||
destListModel = new UnsortedListModel<T>();
|
||||
destList = new FList<T>(destListModel);
|
||||
|
||||
final Runnable onAdd = new Runnable() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void run() {
|
||||
if (!addButton.isEnabled()) { return; }
|
||||
|
||||
List<T> selected = new ArrayList<T>();
|
||||
for (Object item : sourceList.getSelectedValuesList()) {
|
||||
selected.add((T)item);
|
||||
}
|
||||
addDestinationElements(selected);
|
||||
clearSourceSelected();
|
||||
sourceList.validate();
|
||||
_setButtonState();
|
||||
}
|
||||
};
|
||||
|
||||
final Runnable onRemove = new Runnable() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void run() {
|
||||
if (!removeButton.isEnabled()) { return; }
|
||||
|
||||
List<T> selected = new ArrayList<T>();
|
||||
for (Object item : destList.getSelectedValuesList()) {
|
||||
selected.add((T)item);
|
||||
}
|
||||
clearDestinationSelected();
|
||||
addSourceElements(selected);
|
||||
_setButtonState();
|
||||
}
|
||||
};
|
||||
|
||||
sourceList.addKeyListener(new KeyAdapter() {
|
||||
@Override public void keyPressed(final KeyEvent e) {
|
||||
_handleListKey(e, onAdd, destList);
|
||||
}
|
||||
});
|
||||
sourceList.addMouseListener(new MouseAdapter() {
|
||||
@Override public void mouseClicked(MouseEvent e) {
|
||||
if (MouseEvent.BUTTON1 == e.getButton() && 2 == e.getClickCount()) { onAdd.run(); }
|
||||
}
|
||||
});
|
||||
|
||||
destList.addKeyListener(new KeyAdapter() {
|
||||
@Override public void keyPressed(final KeyEvent e) {
|
||||
_handleListKey(e, onRemove, sourceList);
|
||||
}
|
||||
});
|
||||
destList.addMouseListener(new MouseAdapter() {
|
||||
@Override public void mouseClicked(MouseEvent e) {
|
||||
if (MouseEvent.BUTTON1 == e.getButton() && 2 == e.getClickCount()) { onRemove.run(); }
|
||||
}
|
||||
});
|
||||
|
||||
// Dual List control buttons
|
||||
addButton = new FButton(">");
|
||||
addButton.addActionListener(new ActionListener() {@Override public void actionPerformed(ActionEvent e) { onAdd.run(); } });
|
||||
addAllButton = new FButton(">>");
|
||||
addAllButton.addActionListener(new ActionListener() {@Override public void actionPerformed(ActionEvent e) { _addAll(); } });
|
||||
removeButton = new FButton("<");
|
||||
removeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { onRemove.run(); } });
|
||||
removeAllButton = new FButton("<<");
|
||||
removeAllButton.addActionListener(new ActionListener() {@Override public void actionPerformed(ActionEvent e) { _removeAll(); } });
|
||||
|
||||
// Dual List Complete Buttons
|
||||
okButton = new FButton("OK");
|
||||
okButton.addActionListener(new ActionListener() {@Override public void actionPerformed(ActionEvent e) { _finish(); } });
|
||||
autoButton = new FButton("Auto");
|
||||
autoButton.addActionListener(new ActionListener() {@Override public void actionPerformed(ActionEvent e) { _addAll(); _finish(); } });
|
||||
|
||||
FPanel leftPanel = new FPanel(new BorderLayout());
|
||||
selectOrder = new FLabel.Builder().text("Select Order:").build();
|
||||
leftPanel.add(selectOrder, BorderLayout.NORTH);
|
||||
leftPanel.add(new FScrollPane(sourceList, true), BorderLayout.CENTER);
|
||||
leftPanel.add(okButton, BorderLayout.SOUTH);
|
||||
|
||||
FPanel centerPanel = new FPanel(new GridLayout(6, 1));
|
||||
centerPanel.setBorderToggle(false);
|
||||
JPanel emptyPanel = new JPanel();
|
||||
emptyPanel.setOpaque(false);
|
||||
centerPanel.add(emptyPanel); // empty panel to take up the first slot
|
||||
centerPanel.add(addButton);
|
||||
centerPanel.add(addAllButton);
|
||||
centerPanel.add(removeButton);
|
||||
centerPanel.add(removeAllButton);
|
||||
|
||||
orderedLabel = new FLabel.Builder().build();
|
||||
|
||||
FPanel rightPanel = new FPanel(new BorderLayout());
|
||||
rightPanel.add(orderedLabel, BorderLayout.NORTH);
|
||||
rightPanel.add(new FScrollPane(destList, true), BorderLayout.CENTER);
|
||||
rightPanel.add(autoButton, BorderLayout.SOUTH);
|
||||
|
||||
add(leftPanel, "w 250, h 300");
|
||||
add(centerPanel, "w 100, h 300");
|
||||
add(rightPanel, "w 250, h 300");
|
||||
|
||||
_addListListeners(sourceList);
|
||||
_addListListeners(destList);
|
||||
|
||||
if (destElements != null && !destElements.isEmpty()) {
|
||||
addDestinationElements(destElements);
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
destList.setSelectedIndex(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sourceElements != null && !sourceElements.isEmpty()) {
|
||||
addSourceElements(sourceElements);
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sourceList.setSelectedIndex(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_setButtonState();
|
||||
|
||||
if (remainingSourcesMin <= sourceElements.size() && remainingSourcesMax >= sourceElements.size()) {
|
||||
//ensure OK button gets initial focus if remainingSources matches source list count
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
okButton.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void setSecondColumnLabelText(String label) {
|
||||
orderedLabel.setText(label);
|
||||
}
|
||||
|
||||
public void setSideboardMode(boolean isSideboardMode) {
|
||||
sideboardingMode = isSideboardMode;
|
||||
if (sideboardingMode) {
|
||||
addAllButton.setVisible(false);
|
||||
removeAllButton.setVisible(false);
|
||||
autoButton.setEnabled(false);
|
||||
selectOrder.setText(String.format("Sideboard (%d):", sourceListModel.getSize()));
|
||||
orderedLabel.setText(String.format("Main Deck (%d):", destListModel.getSize()));
|
||||
}
|
||||
}
|
||||
|
||||
private void _handleListKey(KeyEvent e, Runnable onSpace, FList<T> arrowFocusTarget) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_SPACE:
|
||||
onSpace.run();
|
||||
break;
|
||||
|
||||
case KeyEvent.VK_LEFT:
|
||||
case KeyEvent.VK_RIGHT:
|
||||
arrowFocusTarget.requestFocusInWindow();
|
||||
break;
|
||||
|
||||
case KeyEvent.VK_ENTER:
|
||||
if (okButton.isEnabled()) {
|
||||
okButton.doClick();
|
||||
}
|
||||
else if (autoButton.isEnabled()) {
|
||||
autoButton.doClick();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearSourceListModel() {
|
||||
sourceListModel.clear();
|
||||
}
|
||||
|
||||
public void clearDestinationListModel() {
|
||||
destListModel.clear();
|
||||
}
|
||||
|
||||
public void addSourceElements(ListModel<T> newValue) {
|
||||
fillListModel(sourceListModel, newValue);
|
||||
}
|
||||
|
||||
public void setSourceElements(ListModel<T> newValue) {
|
||||
clearSourceListModel();
|
||||
addSourceElements(newValue);
|
||||
}
|
||||
|
||||
public void addDestinationElements(List<T> newValue) {
|
||||
fillListModel(destListModel, newValue);
|
||||
}
|
||||
|
||||
public void addDestinationElements(ListModel<T> newValue) {
|
||||
fillListModel(destListModel, newValue);
|
||||
}
|
||||
|
||||
private void fillListModel(UnsortedListModel<T> model, ListModel<T> newValues) {
|
||||
model.addAll(newValues);
|
||||
}
|
||||
|
||||
public void addSourceElements(List<T> newValue) {
|
||||
fillListModel(sourceListModel, newValue);
|
||||
}
|
||||
|
||||
public void setSourceElements(List<T> newValue) {
|
||||
clearSourceListModel();
|
||||
addSourceElements(newValue);
|
||||
}
|
||||
|
||||
private void fillListModel(UnsortedListModel<T> model, List<T> newValues) {
|
||||
model.addAll(newValues);
|
||||
}
|
||||
|
||||
private void clearSourceSelected() {
|
||||
int[] selected = sourceList.getSelectedIndices();
|
||||
for (int i = selected.length - 1; i >= 0; --i) {
|
||||
sourceListModel.removeElement(selected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDestinationSelected() {
|
||||
int[] selected = destList.getSelectedIndices();
|
||||
for (int i = selected.length - 1; i >= 0; --i) {
|
||||
destListModel.removeElement(selected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> getOrderedList() {
|
||||
this.setVisible(false);
|
||||
return destListModel.model;
|
||||
}
|
||||
|
||||
public List<T> getRemainingSourceList() {
|
||||
return sourceListModel.model;
|
||||
}
|
||||
|
||||
private void showSelectedCard(Object obj) {
|
||||
if (!showCard || null == obj) {
|
||||
return;
|
||||
}
|
||||
Card card = null;
|
||||
if (obj instanceof Card) {
|
||||
card = (Card) obj;
|
||||
} else if (obj instanceof SpellAbility) {
|
||||
card = ((SpellAbility) obj).getHostCard();
|
||||
} else if (obj instanceof PaperCard) {
|
||||
card = Card.getCardForUi((IPaperCard) obj);
|
||||
}
|
||||
|
||||
GuiUtils.clearPanelSelections();
|
||||
if (card != null) {
|
||||
CMatchUI.SINGLETON_INSTANCE.setCard(card);
|
||||
GuiUtils.setPanelSelection(card);
|
||||
}
|
||||
}
|
||||
|
||||
private void _addListListeners(final FList<T> list) {
|
||||
list.getModel().addListDataListener(new ListDataListener() {
|
||||
int callCount = 0;
|
||||
@Override
|
||||
public void intervalRemoved(final ListDataEvent e) {
|
||||
final int callNum = ++callCount;
|
||||
// invoke this later since the list is out of sync with the model
|
||||
// at this moment.
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (callNum != callCount) {
|
||||
// don't run stale callbacks
|
||||
return;
|
||||
}
|
||||
|
||||
ListModel<T> model = list.getModel();
|
||||
if (0 == model.getSize()) {
|
||||
// nothing left to show
|
||||
return;
|
||||
}
|
||||
|
||||
int cardIdx = e.getIndex0();
|
||||
if (model.getSize() <= cardIdx) {
|
||||
// the last element got removed, get the one above it
|
||||
cardIdx = model.getSize() - 1;
|
||||
}
|
||||
showCard = false;
|
||||
list.setSelectedIndex(cardIdx);
|
||||
showCard = true;
|
||||
showSelectedCard(model.getElementAt(cardIdx));
|
||||
if (!okButton.isEnabled()) {
|
||||
list.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void intervalAdded(final ListDataEvent e) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// select just-added items so user can undo the add with a single click
|
||||
int startIdx = Math.min(e.getIndex0(), e.getIndex1());
|
||||
int endIdx = Math.max(e.getIndex0(), e.getIndex1());
|
||||
int[] addedIndices = new int[endIdx - startIdx + 1];
|
||||
for (int idx = startIdx; idx <= endIdx; ++idx) {
|
||||
addedIndices[idx - startIdx] = idx;
|
||||
}
|
||||
// attempt to scroll to just-added item (setSelectedIndices does not scroll)
|
||||
// this will scroll to the wrong item if there are other identical items previously in the list
|
||||
showCard = false;
|
||||
list.setSelectedValue(list.getModel().getElementAt(
|
||||
Math.min(endIdx, startIdx + list.getVisibleRowCount())), true);
|
||||
list.setSelectedIndices(addedIndices);
|
||||
showCard = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contentsChanged(ListDataEvent e) {
|
||||
}
|
||||
});
|
||||
|
||||
list.addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent ev) {
|
||||
showSelectedCard(list.getSelectedValue());
|
||||
}
|
||||
});
|
||||
|
||||
list.addFocusListener(new FocusAdapter() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
showSelectedCard(list.getSelectedValue());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void _addAll() {
|
||||
addDestinationElements(sourceListModel);
|
||||
clearSourceListModel();
|
||||
_setButtonState();
|
||||
}
|
||||
|
||||
private void _removeAll() {
|
||||
addSourceElements(destListModel);
|
||||
clearDestinationListModel();
|
||||
_setButtonState();
|
||||
}
|
||||
|
||||
private void _setButtonState() {
|
||||
if (sideboardingMode) {
|
||||
removeAllButton.setVisible(false);
|
||||
addAllButton.setVisible(false);
|
||||
selectOrder.setText(String.format("Sideboard (%d):", sourceListModel.getSize()));
|
||||
orderedLabel.setText(String.format("Main Deck (%d):", destListModel.getSize()));
|
||||
}
|
||||
|
||||
boolean anySize = targetRemainingSourcesMax < 0;
|
||||
boolean canAdd = sourceListModel.getSize() != 0 && (anySize || targetRemainingSourcesMin <= sourceListModel.getSize());
|
||||
boolean canRemove = destListModel.getSize() != 0;
|
||||
boolean targetReached = anySize || targetRemainingSourcesMin <= sourceListModel.getSize() && targetRemainingSourcesMax >= sourceListModel.getSize();
|
||||
|
||||
autoButton.setEnabled(targetRemainingSourcesMax == 0 && !targetReached && !sideboardingMode);
|
||||
|
||||
addButton.setEnabled(canAdd);
|
||||
addAllButton.setEnabled(canAdd);
|
||||
removeButton.setEnabled(canRemove);
|
||||
removeAllButton.setEnabled(canRemove);
|
||||
|
||||
okButton.setEnabled(targetReached);
|
||||
if (targetReached) {
|
||||
okButton.requestFocusInWindow(); //focus OK button if specific target reached
|
||||
}
|
||||
}
|
||||
|
||||
private void _finish() {
|
||||
this.setVisible(false);
|
||||
}
|
||||
}
|
||||
130
forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java
Normal file
130
forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package forge.gui;
|
||||
|
||||
import forge.control.ChatArea;
|
||||
import forge.net.FServer;
|
||||
import forge.net.Lobby;
|
||||
import forge.toolbox.*;
|
||||
import forge.toolbox.FSkin.SkinnedPanel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public enum FNetOverlay {
|
||||
SINGLETON_INSTANCE;
|
||||
|
||||
private final OverlayPanel pnl = new OverlayPanel();
|
||||
/** @return {@link javax.swing.JPanel} */
|
||||
public SkinnedPanel getPanel() {
|
||||
return this.pnl;
|
||||
}
|
||||
|
||||
private final FTextArea txtLog = new FTextArea();
|
||||
private final FTextField txtInput = new FTextField.Builder().maxLength(60).build();
|
||||
private final FLabel cmdSend = new FLabel.ButtonBuilder().text("Send").build();
|
||||
|
||||
|
||||
//private boolean minimized = false;
|
||||
private int height = 120;
|
||||
private int width = 400;
|
||||
|
||||
private final ActionListener onSend = new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String message = txtInput.getText();
|
||||
txtInput.setText("");
|
||||
if ( StringUtils.isBlank(message) )
|
||||
return;
|
||||
|
||||
Lobby lobby = FServer.getLobby();
|
||||
lobby.speak(ChatArea.Room, lobby.getGuiPlayer(), message);
|
||||
}
|
||||
};
|
||||
|
||||
//private final int minimizedHeight = 30;
|
||||
|
||||
/**
|
||||
* Semi-transparent overlay panel. Should be used with layered panes.
|
||||
*/
|
||||
private FNetOverlay() {
|
||||
pnl.setOpaque(false);
|
||||
pnl.setVisible(false);
|
||||
pnl.setBackground(FSkin.getColor(FSkin.Colors.CLR_ZEBRA));
|
||||
pnl.setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_BORDERS)));
|
||||
|
||||
pnl.setLayout(new MigLayout("insets 0, gap 0, ax center, wrap 2"));
|
||||
// pnl.add(new FLabel.Builder().text("Loading new game...").fontSize(22).build(), "h 40px!, align center");
|
||||
|
||||
// Block all input events below the overlay
|
||||
|
||||
txtLog.setOpaque(true);
|
||||
txtLog.setFocusable(true);
|
||||
txtLog.setBackground(FSkin.getColor(FSkin.Colors.CLR_ZEBRA));
|
||||
|
||||
FScrollPane _operationLogScroller = new FScrollPane(txtLog, false);
|
||||
_operationLogScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
new SmartScroller(_operationLogScroller);
|
||||
pnl.add(_operationLogScroller, "pushx, hmin 24, pushy, growy, growx, gap 2px 2px 2px 0, sx 2");
|
||||
|
||||
txtInput.setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_BORDERS)));
|
||||
pnl.add(txtInput, "pushx, growx, h 26px!, gap 2px 2px 2px 0");
|
||||
pnl.add(cmdSend, "w 60px!, h 28px!, gap 0 0 2px 0");
|
||||
|
||||
txtInput.addActionListener(onSend);
|
||||
cmdSend.setCommand(new Runnable() { @Override public void run() { onSend.actionPerformed(null); } });
|
||||
}
|
||||
|
||||
public void showUp(String message) {
|
||||
txtLog.setText(message);
|
||||
pnl.setVisible(true);
|
||||
}
|
||||
|
||||
private class OverlayPanel extends SkinnedPanel {
|
||||
private static final long serialVersionUID = -5056220798272120558L;
|
||||
|
||||
/**
|
||||
* For some reason, the alpha channel background doesn't work properly on
|
||||
* Windows 7, so the paintComponent override is required for a
|
||||
* semi-transparent overlay.
|
||||
*
|
||||
* @param g
|
||||
*   Graphics object
|
||||
*/
|
||||
@Override
|
||||
public void paintComponent(final Graphics g) {
|
||||
super.paintComponent(g);
|
||||
g.setColor(this.getBackground());
|
||||
g.fillRect(0, 0, this.getWidth(), this.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @param mainBounds
|
||||
*/
|
||||
public void containerResized(Rectangle mainBounds) {
|
||||
int w = Math.max(width, (int)(mainBounds.width * 0.25f));
|
||||
int x = mainBounds.width - w;
|
||||
int y = mainBounds.height - height;
|
||||
getPanel().setBounds(x, y, w, height);
|
||||
getPanel().validate();
|
||||
}
|
||||
|
||||
SimpleDateFormat inFormat = new SimpleDateFormat("HH:mm:ss");
|
||||
public void addMessage(String origin, String message) {
|
||||
String toAdd = String.format("%n[%s] %s: %s", inFormat.format(new Date()), origin, message);
|
||||
txtLog.append(toAdd);
|
||||
}
|
||||
}
|
||||
37
forge-gui-desktop/src/main/java/forge/gui/ForgeAction.java
Normal file
37
forge-gui-desktop/src/main/java/forge/gui/ForgeAction.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import forge.match.MatchConstants;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class ForgeAction extends AbstractAction {
|
||||
public ForgeAction(MatchConstants property) {
|
||||
super(property.button);
|
||||
this.putValue("buttonText", property.button);
|
||||
this.putValue("menuText", property.menu);
|
||||
}
|
||||
|
||||
public <T extends AbstractButton> T setupButton(final T button) {
|
||||
button.setAction(this);
|
||||
button.setText((String) this.getValue(button instanceof JMenuItem ? "menuText" : "buttonText"));
|
||||
return button;
|
||||
}
|
||||
}
|
||||
358
forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java
Normal file
358
forge-gui-desktop/src/main/java/forge/gui/GuiChoose.java
Normal file
@@ -0,0 +1,358 @@
|
||||
package forge.gui;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.FThreads;
|
||||
import forge.Singletons;
|
||||
import forge.game.card.Card;
|
||||
import forge.item.InventoryItem;
|
||||
import forge.screens.match.CMatchUI;
|
||||
import forge.toolbox.FOptionPane;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class GuiChoose {
|
||||
|
||||
/**
|
||||
* Convenience for getChoices(message, 0, 1, choices).
|
||||
*
|
||||
* @param <T>
|
||||
* is automatically inferred.
|
||||
* @param message
|
||||
* a {@link java.lang.String} object.
|
||||
* @param choices
|
||||
* a T object.
|
||||
* @return null if choices is missing, empty, or if the users' choices are
|
||||
* empty; otherwise, returns the first item in the List returned by
|
||||
* getChoices.
|
||||
* @see #getChoices(String, int, int, Object...)
|
||||
*/
|
||||
public static <T> T oneOrNone(final String message, final T[] choices) {
|
||||
if ((choices == null) || (choices.length == 0)) {
|
||||
return null;
|
||||
}
|
||||
final List<T> choice = GuiChoose.getChoices(message, 0, 1, choices);
|
||||
return choice.isEmpty() ? null : choice.get(0);
|
||||
} // getChoiceOptional(String,T...)
|
||||
|
||||
public static <T> T oneOrNone(final String message, final Collection<T> choices) {
|
||||
if ((choices == null) || choices.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
final List<T> choice = GuiChoose.getChoices(message, 0, 1, choices);
|
||||
return choice.isEmpty() ? null : choice.get(0);
|
||||
} // getChoiceOptional(String,T...)
|
||||
|
||||
// returned Object will never be null
|
||||
/**
|
||||
* <p>
|
||||
* getChoice.
|
||||
* </p>
|
||||
*
|
||||
* @param <T>
|
||||
* a T object.
|
||||
* @param message
|
||||
* a {@link java.lang.String} object.
|
||||
* @param choices
|
||||
* a T object.
|
||||
* @return a T object.
|
||||
*/
|
||||
public static <T> T one(final String message, final T[] choices) {
|
||||
final List<T> choice = GuiChoose.getChoices(message, 1, 1, choices);
|
||||
assert choice.size() == 1;
|
||||
return choice.get(0);
|
||||
}
|
||||
|
||||
public static <T> T one(final String message, final Collection<T> choices) {
|
||||
if (choices == null || choices.isEmpty())
|
||||
return null;
|
||||
if( choices.size() == 1)
|
||||
return Iterables.getFirst(choices, null);
|
||||
|
||||
final List<T> choice = GuiChoose.getChoices(message, 1, 1, choices);
|
||||
assert choice.size() == 1;
|
||||
return choice.get(0);
|
||||
}
|
||||
|
||||
public static <T> List<T> noneOrMany(final String message, final Collection<T> choices) {
|
||||
return GuiChoose.getChoices(message, 0, choices.size(), choices, null, null);
|
||||
}
|
||||
|
||||
// Nothing to choose here. Code uses this to just reveal one or more items
|
||||
public static <T> void reveal(final String message, final T item) {
|
||||
List<T> items = new ArrayList<T>();
|
||||
items.add(item);
|
||||
reveal(message, items);
|
||||
}
|
||||
public static <T> void reveal(final String message, final T[] items) {
|
||||
GuiChoose.getChoices(message, -1, -1, items);
|
||||
}
|
||||
public static <T> void reveal(final String message, final Collection<T> items) {
|
||||
GuiChoose.getChoices(message, -1, -1, items);
|
||||
}
|
||||
|
||||
// Get Integer in range
|
||||
public static Integer getInteger(final String message) {
|
||||
return getInteger(message, 0, Integer.MAX_VALUE);
|
||||
}
|
||||
public static Integer getInteger(final String message, int min) {
|
||||
return getInteger(message, min, Integer.MAX_VALUE);
|
||||
}
|
||||
public static Integer getInteger(final String message, int min, int max) {
|
||||
if (max <= min) { return min; } //just return min if max <= min
|
||||
|
||||
//force cutting off after 100 numbers at most
|
||||
if (max == Integer.MAX_VALUE) {
|
||||
return getInteger(message, min, max, min + 99);
|
||||
}
|
||||
int count = max - min + 1;
|
||||
if (count > 100) {
|
||||
return getInteger(message, min, max, min + 99);
|
||||
}
|
||||
|
||||
final Integer[] choices = new Integer[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
choices[i] = Integer.valueOf(i + min);
|
||||
}
|
||||
return GuiChoose.oneOrNone(message, choices);
|
||||
}
|
||||
public static Integer getInteger(final String message, int min, int max, int cutoff) {
|
||||
if (max <= min || cutoff < min) { return min; } //just return min if max <= min or cutoff < min
|
||||
|
||||
if (cutoff >= max) { //fallback to regular integer prompt if cutoff at or after max
|
||||
return getInteger(message, min, max);
|
||||
}
|
||||
|
||||
List<Object> choices = new ArrayList<Object>();
|
||||
for (int i = min; i <= cutoff; i++) {
|
||||
choices.add(Integer.valueOf(i));
|
||||
}
|
||||
choices.add("Other...");
|
||||
|
||||
Object choice = GuiChoose.oneOrNone(message, choices);
|
||||
if (choice instanceof Integer || choice == null) {
|
||||
return (Integer)choice;
|
||||
}
|
||||
|
||||
//if Other option picked, prompt for number input
|
||||
String prompt = "Enter a number";
|
||||
if (min != Integer.MIN_VALUE) {
|
||||
if (max != Integer.MAX_VALUE) {
|
||||
prompt += " between " + min + " and " + max;
|
||||
}
|
||||
else {
|
||||
prompt += " greater than or equal to " + min;
|
||||
}
|
||||
}
|
||||
else if (max != Integer.MAX_VALUE) {
|
||||
prompt += " less than or equal to " + max;
|
||||
}
|
||||
prompt += ":";
|
||||
|
||||
while (true) {
|
||||
String str = FOptionPane.showInputDialog(prompt, message);
|
||||
if (str == null) { return null; } // that is 'cancel'
|
||||
|
||||
if (StringUtils.isNumeric(str)) {
|
||||
Integer val = Integer.valueOf(str);
|
||||
if (val >= min && val <= max) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returned Object will never be null
|
||||
public static <T> List<T> getChoices(final String message, final int min, final int max, final T[] choices) {
|
||||
return getChoices(message, min, max, Arrays.asList(choices), null, null);
|
||||
}
|
||||
|
||||
public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices) {
|
||||
return getChoices(message, min, max, choices, null, null);
|
||||
}
|
||||
|
||||
public static <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) {
|
||||
if (choices == null || choices.isEmpty()) {
|
||||
if (min == 0) {
|
||||
return new ArrayList<T>();
|
||||
}
|
||||
throw new RuntimeException("choice required from empty list");
|
||||
}
|
||||
|
||||
Callable<List<T>> showChoice = new Callable<List<T>>() {
|
||||
@Override
|
||||
public List<T> call() {
|
||||
ListChooser<T> c = new ListChooser<T>(message, min, max, choices, display);
|
||||
final JList<T> list = c.getLstChoices();
|
||||
list.addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(final ListSelectionEvent ev) {
|
||||
if (list.getSelectedValue() instanceof Card) {
|
||||
Card card = (Card) list.getSelectedValue();
|
||||
if (card.isFaceDown() && Singletons.getControl().mayShowCard(card)) {
|
||||
CMatchUI.SINGLETON_INSTANCE.setCard(card, true);
|
||||
}
|
||||
else {
|
||||
CMatchUI.SINGLETON_INSTANCE.setCard(card);
|
||||
}
|
||||
|
||||
GuiUtils.clearPanelSelections();
|
||||
GuiUtils.setPanelSelection(card);
|
||||
}
|
||||
if (list.getSelectedValue() instanceof InventoryItem) {
|
||||
CMatchUI.SINGLETON_INSTANCE.setCard((InventoryItem) list.getSelectedValue());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (selected != null) {
|
||||
c.show(selected);
|
||||
}
|
||||
else {
|
||||
c.show();
|
||||
}
|
||||
|
||||
GuiUtils.clearPanelSelections();
|
||||
return c.getSelectedValues();
|
||||
}
|
||||
};
|
||||
|
||||
FutureTask<List<T>> future = new FutureTask<List<T>>(showChoice);
|
||||
FThreads.invokeInEdtAndWait(future);
|
||||
try {
|
||||
return future.get();
|
||||
} catch (Exception e) { // should be no exception here
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> List<T> many(final String title, final String topCaption, int cnt, final List<T> sourceChoices, Card referenceCard) {
|
||||
return order(title, topCaption, cnt, cnt, sourceChoices, null, referenceCard, false);
|
||||
}
|
||||
|
||||
public static <T> List<T> many(final String title, final String topCaption, int min, int max, final List<T> sourceChoices, Card referenceCard) {
|
||||
int m2 = min >= 0 ? sourceChoices.size() - min : -1;
|
||||
int m1 = max >= 0 ? sourceChoices.size() - max : -1;
|
||||
return order(title, topCaption, m1, m2, sourceChoices, null, referenceCard, false);
|
||||
}
|
||||
|
||||
public static <T> List<T> order(final String title, final String top, final List<T> sourceChoices, Card referenceCard) {
|
||||
return order(title, top, 0, 0, sourceChoices, null, referenceCard, false);
|
||||
}
|
||||
|
||||
public static <T extends Comparable<? super T>> List<T> sideboard(List<T> sideboard, List<T> deck) {
|
||||
Collections.sort(deck);
|
||||
Collections.sort(sideboard);
|
||||
return order("Sideboard", "Main Deck", -1, -1, sideboard, deck, null, true);
|
||||
}
|
||||
|
||||
private static <T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax,
|
||||
final List<T> sourceChoices, final List<T> destChoices, final Card referenceCard, final boolean sideboardingMode) {
|
||||
// An input box for handling the order of choices.
|
||||
|
||||
Callable<List<T>> callable = new Callable<List<T>>() {
|
||||
@Override
|
||||
public List<T> call() throws Exception {
|
||||
DualListBox<T> dual = new DualListBox<T>(remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices);
|
||||
dual.setSecondColumnLabelText(top);
|
||||
|
||||
dual.setSideboardMode(sideboardingMode);
|
||||
|
||||
dual.setTitle(title);
|
||||
dual.pack();
|
||||
dual.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
if (referenceCard != null) {
|
||||
CMatchUI.SINGLETON_INSTANCE.setCard(referenceCard);
|
||||
// MARKED FOR UPDATE
|
||||
}
|
||||
dual.setVisible(true);
|
||||
|
||||
List<T> objects = dual.getOrderedList();
|
||||
|
||||
dual.dispose();
|
||||
GuiUtils.clearPanelSelections();
|
||||
return objects;
|
||||
}
|
||||
};
|
||||
|
||||
FutureTask<List<T>> ft = new FutureTask<List<T>>(callable);
|
||||
FThreads.invokeInEdtAndWait(ft);
|
||||
try {
|
||||
return ft.get();
|
||||
} catch (Exception e) { // we have waited enough
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine
|
||||
public static <T> T sortedOneOrNone(final String message, final T[] choices, Comparator<T> comparer) {
|
||||
if ((choices == null) || (choices.length == 0)) {
|
||||
return null;
|
||||
}
|
||||
final List<T> choice = GuiChoose.sortedGetChoices(message, 0, 1, choices, comparer);
|
||||
return choice.isEmpty() ? null : choice.get(0);
|
||||
} // getChoiceOptional(String,T...)
|
||||
|
||||
// If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine
|
||||
public static <T> T sortedOneOrNone(final String message, final List<T> choices, Comparator<T> comparer) {
|
||||
if ((choices == null) || choices.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
final List<T> choice = GuiChoose.sortedGetChoices(message, 0, 1, choices, comparer);
|
||||
return choice.isEmpty() ? null : choice.get(0);
|
||||
} // getChoiceOptional(String,T...)
|
||||
|
||||
|
||||
// If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine
|
||||
public static <T> T sortedOne(final String message, final T[] choices, Comparator<T> comparer) {
|
||||
final List<T> choice = GuiChoose.sortedGetChoices(message, 1, 1, choices, comparer);
|
||||
assert choice.size() == 1;
|
||||
return choice.get(0);
|
||||
} // getChoice()
|
||||
|
||||
// If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine
|
||||
public static <T> T sortedOne(final String message, final List<T> choices, Comparator<T> comparer) {
|
||||
if ((choices == null) || (choices.size() == 0)) {
|
||||
return null;
|
||||
}
|
||||
final List<T> choice = GuiChoose.sortedGetChoices(message, 1, 1, choices, comparer);
|
||||
assert choice.size() == 1;
|
||||
return choice.get(0);
|
||||
}
|
||||
|
||||
// If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine
|
||||
public static <T> List<T> sortedNoneOrMany(final String message, final List<T> choices, Comparator<T> comparer) {
|
||||
return GuiChoose.sortedGetChoices(message, 0, choices.size(), choices, comparer);
|
||||
}
|
||||
|
||||
// If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine
|
||||
public static <T> List<T> sortedGetChoices(final String message, final int min, final int max, final T[] choices, Comparator<T> comparer) {
|
||||
// You may create a copy of source array if callers expect the collection to be unchanged
|
||||
Arrays.sort(choices, comparer);
|
||||
return getChoices(message, min, max, choices);
|
||||
}
|
||||
|
||||
// If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine
|
||||
public static <T> List<T> sortedGetChoices(final String message, final int min, final int max, final List<T> choices, Comparator<T> comparer) {
|
||||
// You may create a copy of source list if callers expect the collection to be unchanged
|
||||
Collections.sort(choices, comparer);
|
||||
return getChoices(message, min, max, choices);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
77
forge-gui-desktop/src/main/java/forge/gui/GuiDialog.java
Normal file
77
forge-gui-desktop/src/main/java/forge/gui/GuiDialog.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package forge.gui;
|
||||
|
||||
import forge.FThreads;
|
||||
import forge.game.card.Card;
|
||||
import forge.screens.match.CMatchUI;
|
||||
import forge.toolbox.FOptionPane;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
/**
|
||||
* Holds player interactions using standard windows
|
||||
*
|
||||
*/
|
||||
public class GuiDialog {
|
||||
private static final String[] defaultConfirmOptions = { "Yes", "No" };
|
||||
|
||||
public static boolean confirm(final Card c, final String question) {
|
||||
return GuiDialog.confirm(c, question, true, null);
|
||||
}
|
||||
public static boolean confirm(final Card c, final String question, final boolean defaultChoice) {
|
||||
return GuiDialog.confirm(c, question, defaultChoice, null);
|
||||
}
|
||||
public static boolean confirm(final Card c, final String question, String[] options) {
|
||||
return GuiDialog.confirm(c, question, true, options);
|
||||
}
|
||||
|
||||
public static boolean confirm(final Card c, final String question, final boolean defaultIsYes, final String[] options) {
|
||||
Callable<Boolean> confirmTask = new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
if ( null != c )
|
||||
CMatchUI.SINGLETON_INSTANCE.setCard(c);
|
||||
|
||||
final String title = c == null ? "Question" : c.getName() + " - Ability";
|
||||
String questionToUse = StringUtils.isBlank(question) ? "Activate card's ability?" : question;
|
||||
String[] opts = options == null ? defaultConfirmOptions : options;
|
||||
int answer = FOptionPane.showOptionDialog(questionToUse, title, FOptionPane.QUESTION_ICON, opts, defaultIsYes ? 0 : 1);
|
||||
return answer == 0;
|
||||
}};
|
||||
|
||||
FutureTask<Boolean> future = new FutureTask<Boolean>(confirmTask);
|
||||
FThreads.invokeInEdtAndWait(future);
|
||||
try {
|
||||
return future.get().booleanValue();
|
||||
}
|
||||
catch (Exception e) { // should be no exception here
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* showInfoDialg.
|
||||
* </p>
|
||||
*
|
||||
* @param message
|
||||
* a {@link java.lang.String} object.
|
||||
*/
|
||||
public static void message(final String message) {
|
||||
message(message, UIManager.getString("OptionPane.messageDialogTitle"));
|
||||
}
|
||||
|
||||
public static void message(final String message, final String title) {
|
||||
FThreads.invokeInEdtAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FOptionPane.showMessageDialog(message, title, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import forge.view.FDialog;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gui_ProgressBarWindow class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: GuiProgressBarWindow.java 24769 2014-02-09 13:56:04Z Hellfish $
|
||||
* @since 1.0.15
|
||||
*/
|
||||
public class GuiProgressBarWindow extends FDialog {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 5832740611050396643L;
|
||||
private final JPanel contentPanel = new JPanel();
|
||||
private final JProgressBar progressBar = new JProgressBar();
|
||||
|
||||
/**
|
||||
* Create the dialog.
|
||||
*/
|
||||
public GuiProgressBarWindow() {
|
||||
this.setResizable(false);
|
||||
this.setTitle("Some Progress");
|
||||
final Dimension screen = this.getToolkit().getScreenSize();
|
||||
this.setBounds(screen.width / 3, 100, // position
|
||||
450, 84); // size
|
||||
this.getContentPane().setLayout(null);
|
||||
this.contentPanel.setBounds(0, 0, 442, 58);
|
||||
this.contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
this.getContentPane().add(this.contentPanel);
|
||||
this.contentPanel.setLayout(null);
|
||||
this.progressBar.setValue(50);
|
||||
// progressBar.setBackground(Color.GRAY);
|
||||
// progressBar.setForeground(Color.BLUE);
|
||||
this.progressBar.setBounds(12, 12, 418, 32);
|
||||
this.contentPanel.add(this.progressBar);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* setProgressRange.
|
||||
* </p>
|
||||
*
|
||||
* @param min
|
||||
* a int.
|
||||
* @param max
|
||||
* a int.
|
||||
*/
|
||||
public final void setProgressRange(final int min, final int max) {
|
||||
this.progressBar.setMinimum(min);
|
||||
this.progressBar.setMaximum(max);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* increment.
|
||||
* </p>
|
||||
*/
|
||||
public final void increment() {
|
||||
this.progressBar.setValue(this.progressBar.getValue() + 1);
|
||||
|
||||
if ((this.progressBar.getValue() % 10) == 0) {
|
||||
this.contentPanel.paintImmediately(this.progressBar.getBounds());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the progressBar for fine tuning (e.g., adding text).
|
||||
*
|
||||
* @return the progressBar
|
||||
*/
|
||||
public final JProgressBar getProgressBar() {
|
||||
return this.progressBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the progress back to zero units completed.
|
||||
*
|
||||
* It is not certain whether this method actually works the way it was
|
||||
* intended.
|
||||
*/
|
||||
public final void reset() {
|
||||
this.getProgressBar().setValue(0);
|
||||
this.contentPanel.paintImmediately(this.getProgressBar().getBounds());
|
||||
}
|
||||
}
|
||||
169
forge-gui-desktop/src/main/java/forge/gui/GuiUtils.java
Normal file
169
forge-gui-desktop/src/main/java/forge/gui/GuiUtils.java
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import forge.game.card.Card;
|
||||
import forge.screens.match.VMatchUI;
|
||||
import forge.screens.match.views.VField;
|
||||
import forge.view.arcane.CardPanel;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* GuiUtils class.
|
||||
* </p>
|
||||
*
|
||||
* @author Forge
|
||||
* @version $Id: GuiUtils.java 24769 2014-02-09 13:56:04Z Hellfish $
|
||||
*/
|
||||
public final class GuiUtils {
|
||||
private GuiUtils() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to create a font from a filename. Concise error reported if
|
||||
* exceptions found.
|
||||
*
|
||||
* @param filename
|
||||
* String
|
||||
* @return Font
|
||||
*/
|
||||
public static Font newFont(final String filename) {
|
||||
final File file = new File(filename);
|
||||
Font ttf = null;
|
||||
|
||||
try {
|
||||
ttf = Font.createFont(Font.TRUETYPE_FONT, file);
|
||||
} catch (final FontFormatException e) {
|
||||
System.err.println("GuiUtils > newFont: bad font format \"" + filename + "\"");
|
||||
} catch (final IOException e) {
|
||||
System.err.println("GuiUtils > newFont: can't find \"" + filename + "\"");
|
||||
}
|
||||
return ttf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all visually highlighted card panels on the battlefield.
|
||||
*/
|
||||
public static void clearPanelSelections() {
|
||||
List<VField> view = VMatchUI.SINGLETON_INSTANCE.getFieldViews();
|
||||
for (VField v : view) {
|
||||
for (CardPanel p : v.getTabletop().getCardPanels()) {
|
||||
p.setSelected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight a card on the playfield.
|
||||
*
|
||||
* @param c
|
||||
* a card to be highlighted
|
||||
*/
|
||||
public static void setPanelSelection(final Card c) {
|
||||
mainLoop:
|
||||
for (VField v : VMatchUI.SINGLETON_INSTANCE.getFieldViews()) {
|
||||
List<CardPanel> panels = v.getTabletop().getCardPanels();
|
||||
for (CardPanel p : panels) {
|
||||
if (p.getCard().equals(c)) {
|
||||
p.setSelected(true);
|
||||
break mainLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int minItemWidth = 100;
|
||||
private static final int itemHeight = 25;
|
||||
|
||||
public static void setMenuItemSize(JMenuItem item) {
|
||||
item.setPreferredSize(new Dimension(Math.max(item.getPreferredSize().width, minItemWidth), itemHeight));
|
||||
}
|
||||
|
||||
public static JMenu createMenu(String label) {
|
||||
if (label.startsWith("<html>")) { //adjust label if HTML
|
||||
label = "<html>" + "<div style='height: " + itemHeight + "px; margin-top: 6px;'>" + label.substring(6, label.length() - 7) + "</div></html>";
|
||||
}
|
||||
JMenu menu = new JMenu(label);
|
||||
setMenuItemSize(menu);
|
||||
return menu;
|
||||
}
|
||||
|
||||
public static JMenuItem createMenuItem(String label, KeyStroke accelerator, final Runnable onClick, boolean enabled, boolean bold) {
|
||||
if (label.startsWith("<html>")) { //adjust label if HTML
|
||||
label = "<html>" + "<div style='height: " + itemHeight + "px; margin-top: 6px;'>" + label.substring(6, label.length() - 7) + "</div></html>";
|
||||
}
|
||||
JMenuItem item = new JMenuItem(label);
|
||||
item.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
if (null != onClick) {
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
item.setEnabled(enabled);
|
||||
item.setAccelerator(accelerator);
|
||||
if (bold) {
|
||||
item.setFont(item.getFont().deriveFont(Font.BOLD));
|
||||
}
|
||||
setMenuItemSize(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
public static void addMenuItem(JPopupMenu parent, String label, KeyStroke accelerator, Runnable onClick) {
|
||||
parent.add(createMenuItem(label, accelerator, onClick, true, false));
|
||||
}
|
||||
|
||||
public static void addMenuItem(JMenuItem parent, String label, KeyStroke accelerator, Runnable onClick) {
|
||||
parent.add(createMenuItem(label, accelerator, onClick, true, false));
|
||||
}
|
||||
|
||||
public static void addMenuItem(JPopupMenu parent, String label, KeyStroke accelerator, Runnable onClick, boolean enabled) {
|
||||
parent.add(createMenuItem(label, accelerator, onClick, enabled, false));
|
||||
}
|
||||
|
||||
public static void addMenuItem(JMenuItem parent, String label, KeyStroke accelerator, Runnable onClick, boolean enabled) {
|
||||
parent.add(createMenuItem(label, accelerator, onClick, enabled, false));
|
||||
}
|
||||
|
||||
public static void addMenuItem(JPopupMenu parent, String label, KeyStroke accelerator, Runnable onClick, boolean enabled, boolean bold) {
|
||||
parent.add(createMenuItem(label, accelerator, onClick, enabled, bold));
|
||||
}
|
||||
|
||||
public static void addMenuItem(JMenuItem parent, String label, KeyStroke accelerator, Runnable onClick, boolean enabled, boolean bold) {
|
||||
parent.add(createMenuItem(label, accelerator, onClick, enabled, bold));
|
||||
}
|
||||
|
||||
public static void addSeparator(JPopupMenu parent) {
|
||||
parent.add(new JSeparator());
|
||||
}
|
||||
|
||||
public static void addSeparator(JMenuItem parent) {
|
||||
parent.add(new JSeparator());
|
||||
}
|
||||
}
|
||||
988
forge-gui-desktop/src/main/java/forge/gui/ImportDialog.java
Normal file
988
forge-gui-desktop/src/main/java/forge/gui/ImportDialog.java
Normal file
@@ -0,0 +1,988 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (c) 2013 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.gui;
|
||||
|
||||
import forge.UiCommand;
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.error.BugReporter;
|
||||
import forge.gui.ImportSourceAnalyzer.OpType;
|
||||
import forge.properties.ForgeConstants;
|
||||
import forge.toolbox.*;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
|
||||
/**
|
||||
* This class implements an overlay-based dialog that imports data from a user-selected directory
|
||||
* into the correct locations in the user and cache directories. There is a lot of I/O and data
|
||||
* processing done in this class, so most operations are asynchronous.
|
||||
*/
|
||||
public class ImportDialog {
|
||||
private final FButton _btnStart;
|
||||
private final FButton _btnCancel;
|
||||
private final FLabel _btnChooseDir;
|
||||
private final FPanel _topPanel;
|
||||
private final JPanel _selectionPanel;
|
||||
|
||||
// volatile since it is checked from multiple threads
|
||||
private volatile boolean _cancel;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public ImportDialog(String forcedSrcDir, final Runnable onDialogClose) {
|
||||
_topPanel = new FPanel(new MigLayout("insets dialog, gap 0, center, wrap, fill"));
|
||||
_topPanel.setOpaque(false);
|
||||
_topPanel.setBackgroundTexture(FSkin.getIcon(FSkinProp.BG_TEXTURE));
|
||||
|
||||
final boolean isMigration = !StringUtils.isEmpty(forcedSrcDir);
|
||||
|
||||
// header
|
||||
_topPanel.add(new FLabel.Builder().text((isMigration ? "Migrate" : "Import") + " profile data").fontSize(15).build(), "center");
|
||||
|
||||
// add some help text if this is for the initial data migration
|
||||
if (isMigration) {
|
||||
FPanel blurbPanel = new FPanel(new MigLayout("insets panel, gap 10, fill"));
|
||||
blurbPanel.setOpaque(false);
|
||||
JPanel blurbPanelInterior = new JPanel(new MigLayout("insets dialog, gap 10, center, wrap, fill"));
|
||||
blurbPanelInterior.setOpaque(false);
|
||||
blurbPanelInterior.add(new FLabel.Builder().text("<html><b>What's this?</b></html>").build(), "growx, w 50:50:");
|
||||
blurbPanelInterior.add(new FLabel.Builder().text(
|
||||
"<html>Over the last several years, people have had to jump through a lot of hoops to" +
|
||||
" update to the most recent version. We hope to reduce this workload to a point where a new" +
|
||||
" user will find that it is fairly painless to update. In order to make this happen, Forge" +
|
||||
" has changed where it stores your data so that it is outside of the program installation directory." +
|
||||
" This way, when you upgrade, you will no longer need to import your data every time to get things" +
|
||||
" working. There are other benefits to having user data separate from program data, too, and it" +
|
||||
" lays the groundwork for some cool new features.</html>").build(), "growx, w 50:50:");
|
||||
blurbPanelInterior.add(new FLabel.Builder().text("<html><b>So where's my data going?</b></html>").build(), "growx, w 50:50:");
|
||||
blurbPanelInterior.add(new FLabel.Builder().text(
|
||||
"<html>Forge will now store your data in the same place as other applications on your system." +
|
||||
" Specifically, your personal data, like decks, quest progress, and program preferences will be" +
|
||||
" stored in <b>" + ForgeConstants.USER_DIR + "</b> and all downloaded content, such as card pictures," +
|
||||
" skins, and quest world prices will be under <b>" + ForgeConstants.CACHE_DIR + "</b>. If, for whatever" +
|
||||
" reason, you need to set different paths, cancel out of this dialog, exit Forge, and find the <b>" +
|
||||
ForgeConstants.PROFILE_TEMPLATE_FILE + "</b> file in the program installation directory. Copy or rename" +
|
||||
" it to <b>" + ForgeConstants.PROFILE_FILE + "</b> and edit the paths inside it. Then restart Forge and use" +
|
||||
" this dialog to move your data to the paths that you set. Keep in mind that if you install a future" +
|
||||
" version of Forge into a different directory, you'll need to copy this file over so Forge will know" +
|
||||
" where to find your data.</html>").build(), "growx, w 50:50:");
|
||||
blurbPanelInterior.add(new FLabel.Builder().text(
|
||||
"<html><b>Remember, your data won't be available until you complete this step!</b></html>").build(), "growx, w 50:50:");
|
||||
|
||||
FScrollPane blurbScroller = new FScrollPane(blurbPanelInterior, true,
|
||||
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
blurbPanel.add(blurbScroller, "hmin 150, growy, growx, center, gap 0 0 5 5");
|
||||
_topPanel.add(blurbPanel, "gap 10 10 20 0, growy, growx, w 50:50:");
|
||||
}
|
||||
|
||||
// import source widgets
|
||||
JPanel importSourcePanel = new JPanel(new MigLayout("insets 0, gap 10"));
|
||||
importSourcePanel.setOpaque(false);
|
||||
importSourcePanel.add(new FLabel.Builder().text("Import from:").build());
|
||||
final FTextField txfSrc = new FTextField.Builder().readonly().build();
|
||||
importSourcePanel.add(txfSrc, "pushx, growx");
|
||||
_btnChooseDir = new FLabel.ButtonBuilder().text("Choose directory...").enabled(!isMigration).build();
|
||||
final JFileChooser _fileChooser = new JFileChooser();
|
||||
_fileChooser.setMultiSelectionEnabled(false);
|
||||
_fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
_btnChooseDir.setCommand(new UiCommand() {
|
||||
@Override public void run() {
|
||||
// bring up a file open dialog and, if the OK button is selected, apply the filename
|
||||
// to the import source text field
|
||||
if (JFileChooser.APPROVE_OPTION == _fileChooser.showOpenDialog(JOptionPane.getRootFrame())) {
|
||||
File f = _fileChooser.getSelectedFile();
|
||||
if (!f.canRead()) {
|
||||
FOptionPane.showErrorDialog("Cannot access selected directory (Permission denied).");
|
||||
}
|
||||
else {
|
||||
txfSrc.setText(f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
importSourcePanel.add(_btnChooseDir, "h pref+8!, w pref+12!");
|
||||
|
||||
// add change handler to the import source text field that starts up a
|
||||
// new analyzer. it also interacts with the current active analyzer,
|
||||
// if any, to make sure it cancels out before the new one is initiated
|
||||
txfSrc.getDocument().addDocumentListener(new DocumentListener() {
|
||||
boolean _analyzerActive; // access synchronized on _onAnalyzerDone
|
||||
String prevText;
|
||||
|
||||
private final Runnable _onAnalyzerDone = new Runnable() {
|
||||
public synchronized void run() {
|
||||
_analyzerActive = false;
|
||||
notify();
|
||||
}
|
||||
};
|
||||
|
||||
@Override public void removeUpdate(DocumentEvent e) { }
|
||||
@Override public void changedUpdate(DocumentEvent e) { }
|
||||
@Override public void insertUpdate(DocumentEvent e) {
|
||||
// text field is read-only, so the only time this will get updated
|
||||
// is when _btnChooseDir does it
|
||||
final String text = txfSrc.getText();
|
||||
if (text.equals(prevText)) {
|
||||
// only restart the analyzer if the directory has changed
|
||||
return;
|
||||
}
|
||||
prevText = text;
|
||||
|
||||
// cancel any active analyzer
|
||||
_cancel = true;
|
||||
|
||||
if (!text.isEmpty()) {
|
||||
// ensure we don't get two instances of this function running at the same time
|
||||
_btnChooseDir.setEnabled(false);
|
||||
|
||||
// re-disable the start button. it will be enabled if the previous analyzer has
|
||||
// already successfully finished
|
||||
_btnStart.setEnabled(false);
|
||||
|
||||
// we have to wait in a background thread since we can't block in the GUI thread
|
||||
SwingWorker<Void, Void> analyzerStarter = new SwingWorker<Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
// wait for active analyzer (if any) to quit
|
||||
synchronized (_onAnalyzerDone) {
|
||||
while (_analyzerActive) {
|
||||
_onAnalyzerDone.wait();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// executes in gui event loop thread
|
||||
@Override
|
||||
protected void done() {
|
||||
_cancel = false;
|
||||
synchronized (_onAnalyzerDone) {
|
||||
// this will populate the panel with data selection widgets
|
||||
_AnalyzerUpdater analyzer = new _AnalyzerUpdater(text, _onAnalyzerDone, isMigration);
|
||||
analyzer.run();
|
||||
_analyzerActive = true;
|
||||
}
|
||||
if (!isMigration) {
|
||||
// only enable the directory choosing button if this is not a migration dialog
|
||||
// since in that case we're permanently locked to the starting directory
|
||||
_btnChooseDir.setEnabled(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
analyzerStarter.execute();
|
||||
}
|
||||
}
|
||||
});
|
||||
_topPanel.add(importSourcePanel, "gaptop 20, pushx, growx");
|
||||
|
||||
// prepare import selection panel (will be cleared and filled in later by an analyzer)
|
||||
_selectionPanel = new JPanel();
|
||||
_selectionPanel.setOpaque(false);
|
||||
_topPanel.add(_selectionPanel, "growx, growy, gaptop 10");
|
||||
|
||||
// action button widgets
|
||||
final Runnable cleanup = new Runnable() {
|
||||
@Override public void run() { SOverlayUtils.hideOverlay(); }
|
||||
};
|
||||
_btnStart = new FButton("Start import");
|
||||
_btnStart.setEnabled(false);
|
||||
_btnCancel = new FButton("Cancel");
|
||||
_btnCancel.addActionListener(new ActionListener() {
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
_cancel = true;
|
||||
cleanup.run();
|
||||
if (null != onDialogClose) {
|
||||
onDialogClose.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JPanel southPanel = new JPanel(new MigLayout("ax center"));
|
||||
southPanel.setOpaque(false);
|
||||
southPanel.add(_btnStart, "center, w pref+144!, h pref+12!");
|
||||
southPanel.add(_btnCancel, "center, w pref+144!, h pref+12!, gap 72");
|
||||
_topPanel.add(southPanel, "growx");
|
||||
|
||||
JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel();
|
||||
overlay.setLayout(new MigLayout("insets 0, gap 0, wrap, ax center, ay center"));
|
||||
overlay.add(_topPanel, "w 500::90%, h 100::90%");
|
||||
SOverlayUtils.showOverlay();
|
||||
|
||||
// focus cancel button after the dialog is shown
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override public void run() { _btnCancel.requestFocusInWindow(); }
|
||||
});
|
||||
|
||||
// if our source dir is provided, set the text, which will fire off an analyzer
|
||||
if (isMigration) {
|
||||
File srcDirFile = new File(forcedSrcDir);
|
||||
txfSrc.setText(srcDirFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
// encapsulates the choices in the combobox for choosing the destination paths for
|
||||
// decks of unknown type
|
||||
private class _UnknownDeckChoice {
|
||||
public final String name;
|
||||
public final String path;
|
||||
|
||||
public _UnknownDeckChoice(String name0, String path0) {
|
||||
name = name0;
|
||||
path = path0;
|
||||
}
|
||||
|
||||
@Override public String toString() { return name; }
|
||||
}
|
||||
|
||||
// this class owns the import selection widgets and bridges them with the running
|
||||
// MigrationSourceAnalyzer instance
|
||||
private class _AnalyzerUpdater extends SwingWorker<Void, Void> {
|
||||
// associates a file operation type with its enablement checkbox and the set
|
||||
// of file move/copy operations that enabling it would entail
|
||||
private final Map<OpType, Pair<FCheckBox, ? extends Map<File, File>>> _selections =
|
||||
new HashMap<OpType, Pair<FCheckBox, ? extends Map<File, File>>>();
|
||||
|
||||
// attached to all changeable widgets to keep the UI in sync
|
||||
private final ChangeListener _stateChangedListener = new ChangeListener() {
|
||||
@Override public void stateChanged(ChangeEvent arg0) { _updateUI(); }
|
||||
};
|
||||
|
||||
private final String _srcDir;
|
||||
private final Runnable _onAnalyzerDone;
|
||||
private final boolean _isMigration;
|
||||
private final FLabel _unknownDeckLabel;
|
||||
private final FComboBoxWrapper<_UnknownDeckChoice> _unknownDeckCombo;
|
||||
private final FCheckBox _moveCheckbox;
|
||||
private final FCheckBox _overwriteCheckbox;
|
||||
private final JTextArea _operationLog;
|
||||
private final JScrollPane _operationLogScroller;
|
||||
private final JProgressBar _progressBar;
|
||||
|
||||
// updates the _operationLog widget asynchronously to keep the UI responsive
|
||||
private final _OperationLogAsyncUpdater _operationLogUpdater;
|
||||
|
||||
public _AnalyzerUpdater(String srcDir, Runnable onAnalyzerDone, boolean isMigration) {
|
||||
_srcDir = srcDir;
|
||||
_onAnalyzerDone = onAnalyzerDone;
|
||||
_isMigration = isMigration;
|
||||
|
||||
_selectionPanel.removeAll();
|
||||
_selectionPanel.setLayout(new MigLayout("insets 0, gap 5, wrap, fill"));
|
||||
|
||||
JPanel cbPanel = new JPanel(new MigLayout("insets 0, gap 5"));
|
||||
cbPanel.setOpaque(false);
|
||||
|
||||
// add deck selections
|
||||
JPanel knownDeckPanel = new JPanel(new MigLayout("insets 0, gap 5, wrap 2"));
|
||||
knownDeckPanel.setOpaque(false);
|
||||
knownDeckPanel.add(new FLabel.Builder().text("Decks").build(), "wrap");
|
||||
_addSelectionWidget(knownDeckPanel, OpType.CONSTRUCTED_DECK, "Constructed decks");
|
||||
_addSelectionWidget(knownDeckPanel, OpType.DRAFT_DECK, "Draft decks");
|
||||
_addSelectionWidget(knownDeckPanel, OpType.PLANAR_DECK, "Planar decks");
|
||||
_addSelectionWidget(knownDeckPanel, OpType.SCHEME_DECK, "Scheme decks");
|
||||
_addSelectionWidget(knownDeckPanel, OpType.SEALED_DECK, "Sealed decks");
|
||||
_addSelectionWidget(knownDeckPanel, OpType.UNKNOWN_DECK, "Unknown decks");
|
||||
JPanel unknownDeckPanel = new JPanel(new MigLayout("insets 0, gap 5"));
|
||||
unknownDeckPanel.setOpaque(false);
|
||||
_unknownDeckCombo = new FComboBoxWrapper<_UnknownDeckChoice>();
|
||||
_unknownDeckCombo.addItem(new _UnknownDeckChoice("Constructed", ForgeConstants.DECK_CONSTRUCTED_DIR));
|
||||
_unknownDeckCombo.addItem(new _UnknownDeckChoice("Draft", ForgeConstants.DECK_DRAFT_DIR));
|
||||
_unknownDeckCombo.addItem(new _UnknownDeckChoice("Planar", ForgeConstants.DECK_PLANE_DIR));
|
||||
_unknownDeckCombo.addItem(new _UnknownDeckChoice("Scheme", ForgeConstants.DECK_SCHEME_DIR));
|
||||
_unknownDeckCombo.addItem(new _UnknownDeckChoice("Sealed", ForgeConstants.DECK_SEALED_DIR));
|
||||
_unknownDeckCombo.addActionListener(new ActionListener() {
|
||||
@Override public void actionPerformed(ActionEvent arg0) { _updateUI(); }
|
||||
});
|
||||
_unknownDeckLabel = new FLabel.Builder().text("Treat unknown decks as:").build();
|
||||
unknownDeckPanel.add(_unknownDeckLabel);
|
||||
_unknownDeckCombo.addTo(unknownDeckPanel);
|
||||
knownDeckPanel.add(unknownDeckPanel, "span");
|
||||
cbPanel.add(knownDeckPanel, "aligny top");
|
||||
|
||||
// add other userDir data elements
|
||||
JPanel dataPanel = new JPanel(new MigLayout("insets 0, gap 5, wrap"));
|
||||
dataPanel.setOpaque(false);
|
||||
dataPanel.add(new FLabel.Builder().text("Other data").build());
|
||||
_addSelectionWidget(dataPanel, OpType.GAUNTLET_DATA, "Gauntlet data");
|
||||
_addSelectionWidget(dataPanel, OpType.QUEST_DATA, "Quest saves");
|
||||
_addSelectionWidget(dataPanel, OpType.PREFERENCE_FILE, "Preference files");
|
||||
cbPanel.add(dataPanel, "aligny top");
|
||||
|
||||
// add cacheDir data elements
|
||||
JPanel cachePanel = new JPanel(new MigLayout("insets 0, gap 5, wrap 2"));
|
||||
cachePanel.setOpaque(false);
|
||||
cachePanel.add(new FLabel.Builder().text("Cached data").build(), "wrap");
|
||||
_addSelectionWidget(cachePanel, OpType.DEFAULT_CARD_PIC, "Default card pics");
|
||||
_addSelectionWidget(cachePanel, OpType.SET_CARD_PIC, "Set-specific card pics");
|
||||
_addSelectionWidget(cachePanel, OpType.TOKEN_PIC, "Card token pics");
|
||||
_addSelectionWidget(cachePanel, OpType.QUEST_PIC, "Quest-related pics");
|
||||
_addSelectionWidget(cachePanel, OpType.DB_FILE, "Database files", true, null, "wrap");
|
||||
|
||||
_addSelectionWidget(cachePanel, OpType.POSSIBLE_SET_CARD_PIC,
|
||||
"Import possible set pics from as-yet unsupported cards", false,
|
||||
"<html>Picture files that are not recognized as belonging to any known card.<br>" +
|
||||
"It could be that these pictures belong to cards that are not yet supported<br>" +
|
||||
"by Forge. If you know this to be the case and want the pictures imported for<br>" +
|
||||
"future use, select this option.<html>", "span");
|
||||
cbPanel.add(cachePanel, "aligny top");
|
||||
_selectionPanel.add(cbPanel, "center");
|
||||
|
||||
// add move/copy and overwrite checkboxes
|
||||
JPanel ioOptionPanel = new JPanel(new MigLayout("insets 0, gap 10"));
|
||||
ioOptionPanel.setOpaque(false);
|
||||
_moveCheckbox = new FCheckBox("Remove source files after copy");
|
||||
_moveCheckbox.setToolTipText("Move files into the data directories instead of just copying them");
|
||||
_moveCheckbox.setSelected(isMigration);
|
||||
_moveCheckbox.addChangeListener(_stateChangedListener);
|
||||
ioOptionPanel.add(_moveCheckbox);
|
||||
_overwriteCheckbox = new FCheckBox("Overwrite files in destination");
|
||||
_overwriteCheckbox.setToolTipText("Overwrite existing data with the imported data");
|
||||
_overwriteCheckbox.addChangeListener(_stateChangedListener);
|
||||
ioOptionPanel.add(_overwriteCheckbox);
|
||||
_selectionPanel.add(ioOptionPanel);
|
||||
|
||||
// add operation summary textfield
|
||||
_operationLog = new JTextArea();
|
||||
_operationLog.setFont(new Font("Monospaced", Font.PLAIN, 10));
|
||||
_operationLog.setOpaque(false);
|
||||
_operationLog.setWrapStyleWord(true);
|
||||
_operationLog.setLineWrap(true);
|
||||
_operationLog.setEditable(false);
|
||||
// autoscroll when we set/add text unless the user has intentionally scrolled somewhere else
|
||||
_operationLogScroller = new JScrollPane(_operationLog);
|
||||
_operationLogScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
new SmartScroller(_operationLogScroller);
|
||||
_selectionPanel.add(_operationLogScroller, "w 400:400:, hmin 60, growy, growx");
|
||||
|
||||
// add progress bar
|
||||
_progressBar = new JProgressBar();
|
||||
_progressBar.setString("Preparing to analyze source directory...");
|
||||
_progressBar.setStringPainted(true);
|
||||
_selectionPanel.add(_progressBar, "w 100%!");
|
||||
|
||||
// start the op log updater
|
||||
_operationLogUpdater = new _OperationLogAsyncUpdater(_selections, _operationLog);
|
||||
_operationLogUpdater.start();
|
||||
|
||||
// set initial checkbox labels
|
||||
_updateUI();
|
||||
|
||||
// resize the panel properly now that the _selectionPanel is filled in
|
||||
_selectionPanel.getParent().validate();
|
||||
_selectionPanel.getParent().invalidate();
|
||||
}
|
||||
|
||||
private void _addSelectionWidget(JPanel parent, OpType type, String name) {
|
||||
_addSelectionWidget(parent, type, name, true, null, null);
|
||||
}
|
||||
|
||||
private void _addSelectionWidget(JPanel parent, OpType type, String name, boolean selected,
|
||||
String tooltip, String constraints) {
|
||||
FCheckBox cb = new FCheckBox();
|
||||
cb.setName(name);
|
||||
cb.setSelected(selected);
|
||||
cb.setToolTipText(tooltip);
|
||||
cb.addChangeListener(_stateChangedListener);
|
||||
|
||||
// use a skip list map instead of a regular hashmap so that the files are sorted
|
||||
// alphabetically in the logs. note that this is a concurrent data structure
|
||||
// since it will be modified and read simultaneously by different threads
|
||||
_selections.put(type, Pair.of(cb, new ConcurrentSkipListMap<File, File>()));
|
||||
parent.add(cb, constraints);
|
||||
}
|
||||
|
||||
// must be called from GUI event loop thread
|
||||
private void _updateUI() {
|
||||
// update checkbox text labels with current totals
|
||||
Set<OpType> selectedOptions = new HashSet<OpType>();
|
||||
for (Map.Entry<OpType, Pair<FCheckBox, ? extends Map<File, File>>> entry : _selections.entrySet()) {
|
||||
Pair<FCheckBox, ? extends Map<File, File>> selection = entry.getValue();
|
||||
FCheckBox cb = selection.getLeft();
|
||||
|
||||
if (cb.isSelected()) {
|
||||
selectedOptions.add(entry.getKey());
|
||||
}
|
||||
|
||||
cb.setText(String.format("%s (%d)", cb.getName(), selection.getRight().size()));
|
||||
}
|
||||
|
||||
// asynchronously update the text in the op log, which may be many tens of thousands of lines long
|
||||
// if this were done synchronously the UI would slow to a crawl
|
||||
_operationLogUpdater.requestUpdate(selectedOptions, (_UnknownDeckChoice)_unknownDeckCombo.getSelectedItem(),
|
||||
_moveCheckbox.isSelected(), _overwriteCheckbox.isSelected());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
Timer timer = null;
|
||||
|
||||
try {
|
||||
Map<OpType, Map<File, File>> selections = new HashMap<OpType, Map<File, File>>();
|
||||
for (Map.Entry<OpType, Pair<FCheckBox, ? extends Map<File, File>>> entry : _selections.entrySet()) {
|
||||
selections.put(entry.getKey(), entry.getValue().getRight());
|
||||
}
|
||||
|
||||
ImportSourceAnalyzer.AnalysisCallback cb = new ImportSourceAnalyzer.AnalysisCallback() {
|
||||
@Override
|
||||
public boolean checkCancel() { return _cancel; }
|
||||
|
||||
@Override
|
||||
public void addOp(OpType type, File src, File dest) {
|
||||
// add to concurrent map
|
||||
_selections.get(type).getRight().put(src, dest);
|
||||
}
|
||||
};
|
||||
|
||||
final ImportSourceAnalyzer msa = new ImportSourceAnalyzer(_srcDir, cb);
|
||||
final int numFilesToAnalyze = msa.getNumFilesToAnalyze();
|
||||
|
||||
// update only once every half-second so we're not flooding the UI with updates
|
||||
timer = new Timer(500, null);
|
||||
timer.setInitialDelay(100);
|
||||
final Timer finalTimer = timer;
|
||||
timer.addActionListener(new ActionListener() {
|
||||
@Override public void actionPerformed(ActionEvent arg0) {
|
||||
if (_cancel) {
|
||||
finalTimer.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// timers run in the gui event loop, so it's ok to interact with widgets
|
||||
_progressBar.setValue(msa.getNumFilesAnalyzed());
|
||||
_updateUI();
|
||||
|
||||
// allow the the panel to resize to accommodate additional text
|
||||
_selectionPanel.getParent().validate();
|
||||
_selectionPanel.getParent().invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
// update the progress bar widget from the GUI event loop
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override public void run() {
|
||||
if (_cancel) { return; }
|
||||
_progressBar.setString("Analyzing...");
|
||||
_progressBar.setMaximum(numFilesToAnalyze);
|
||||
_progressBar.setValue(0);
|
||||
_progressBar.setIndeterminate(false);
|
||||
|
||||
// start update timer
|
||||
finalTimer.start();
|
||||
}
|
||||
});
|
||||
|
||||
// does not return until analysis is complete or has been canceled
|
||||
msa.doAnalysis();
|
||||
} catch (final Exception e) {
|
||||
_cancel = true;
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override public void run() {
|
||||
_progressBar.setString("Error");
|
||||
BugReporter.reportException(e);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
// ensure the UI update timer is stopped after analysis is complete
|
||||
if (null != timer) {
|
||||
timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// executes in gui event loop thread
|
||||
@Override
|
||||
protected void done() {
|
||||
if (!_cancel) {
|
||||
_progressBar.setValue(_progressBar.getMaximum());
|
||||
_updateUI();
|
||||
_progressBar.setString("Analysis complete");
|
||||
|
||||
// clear any previously-set action listeners on the start button
|
||||
// in case we've previously completed an analysis but changed the directory
|
||||
// instead of starting the import
|
||||
for (ActionListener a : _btnStart.getActionListeners()) {
|
||||
_btnStart.removeActionListener(a);
|
||||
}
|
||||
|
||||
// deselect and disable all options that have 0 operations associated with
|
||||
// them to highlight the important options
|
||||
for (Pair<FCheckBox, ? extends Map<File, File>> p : _selections.values()) {
|
||||
FCheckBox cb = p.getLeft();
|
||||
if (0 == p.getRight().size()) {
|
||||
cb.removeChangeListener(_stateChangedListener);
|
||||
cb.setSelected(false);
|
||||
cb.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 == _selections.get(OpType.UNKNOWN_DECK).getRight().size()) {
|
||||
_unknownDeckLabel.setEnabled(false);
|
||||
_unknownDeckCombo.setEnabled(false);
|
||||
}
|
||||
|
||||
// set up the start button to start the prepared import on click
|
||||
_btnStart.addActionListener(new ActionListener() {
|
||||
@Override public void actionPerformed(ActionEvent arg0) {
|
||||
// if this is a migration, warn if active settings will not complete a migration and give the
|
||||
// user an option to fix
|
||||
if (_isMigration) {
|
||||
// assemble a list of selections that need to be selected to complete a full migration
|
||||
List<String> unselectedButShouldBe = new ArrayList<String>();
|
||||
for (Map.Entry<OpType, Pair<FCheckBox, ? extends Map<File, File>>> entry : _selections.entrySet()) {
|
||||
if (OpType.POSSIBLE_SET_CARD_PIC == entry.getKey()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add name to list if checkbox is unselected, but contains operations
|
||||
Pair<FCheckBox, ? extends Map<File, File>> p = entry.getValue();
|
||||
FCheckBox cb = p.getLeft();
|
||||
if (!cb.isSelected() && 0 < p.getRight().size()) {
|
||||
unselectedButShouldBe.add(cb.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (!unselectedButShouldBe.isEmpty() || !_moveCheckbox.isSelected()) {
|
||||
StringBuilder sb = new StringBuilder("<html>");
|
||||
if (!unselectedButShouldBe.isEmpty()) {
|
||||
sb.append("It looks like the following options are not selected, which will result in an incomplete migration:");
|
||||
sb.append("<ul>");
|
||||
for (String cbName : unselectedButShouldBe) {
|
||||
sb.append("<li><b>").append(cbName).append("</b></li>");
|
||||
}
|
||||
sb.append("</ul>");
|
||||
}
|
||||
|
||||
if (!_moveCheckbox.isSelected()) {
|
||||
sb.append(unselectedButShouldBe.isEmpty() ? "It " : "It also ").append("looks like the <b>");
|
||||
sb.append(_moveCheckbox.getText()).append("</b> option is not selected.<br><br>");
|
||||
}
|
||||
|
||||
sb.append("You can continue anyway, but the migration will be incomplete, and the data migration prompt<br>");
|
||||
sb.append("will come up again the next time you start Forge in order to migrate the remaining files<br>");
|
||||
sb.append("unless you move or delete them manually.</html>");
|
||||
|
||||
String[] options = { "Whoops, let me fix that!", "Continue with the import, I know what I'm doing." };
|
||||
int chosen = FOptionPane.showOptionDialog(sb.toString(), "Migration warning", FOptionPane.WARNING_ICON, options);
|
||||
|
||||
if (chosen != 1) {
|
||||
// i.e. option 0 was chosen or the dialog was otherwise closed
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure no other actions (except for cancel) can be taken while the import is in progress
|
||||
_btnStart.setEnabled(false);
|
||||
_btnChooseDir.setEnabled(false);
|
||||
|
||||
for (Pair<FCheckBox, ? extends Map<File, File>> selection : _selections.values()) {
|
||||
selection.getLeft().setEnabled(false);
|
||||
}
|
||||
_unknownDeckCombo.setEnabled(false);
|
||||
_moveCheckbox.setEnabled(false);
|
||||
_overwriteCheckbox.setEnabled(false);
|
||||
|
||||
// stop updating the operation log -- the importer needs it now
|
||||
_operationLogUpdater.requestStop();
|
||||
|
||||
// jump to the bottom of the log text area so it starts autoscrolling again
|
||||
// note that since it is controlled by a SmartScroller, just setting the caret position will not work
|
||||
JScrollBar scrollBar = _operationLogScroller.getVerticalScrollBar();
|
||||
scrollBar.setValue(scrollBar.getMaximum());
|
||||
|
||||
// start importing!
|
||||
_Importer importer = new _Importer(
|
||||
_srcDir, _selections, _unknownDeckCombo, _operationLog, _progressBar,
|
||||
_moveCheckbox.isSelected(), _overwriteCheckbox.isSelected());
|
||||
importer.run();
|
||||
|
||||
_btnCancel.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// import ready to proceed: enable the start button
|
||||
_btnStart.setEnabled(true);
|
||||
}
|
||||
|
||||
// report to the Choose Directory button that this analysis run has stopped
|
||||
_onAnalyzerDone.run();
|
||||
}
|
||||
}
|
||||
|
||||
// asynchronously iterates through the given concurrent maps and populates the operation log with
|
||||
// the proposed operations
|
||||
private class _OperationLogAsyncUpdater extends Thread {
|
||||
final Map<OpType, Map<File, File>> _selections;
|
||||
final JTextArea _operationLog; // safe to set text from another thread
|
||||
|
||||
// synchronized-access data
|
||||
private int _updateCallCnt = 0;
|
||||
private Set<OpType> _selectedOptions;
|
||||
private _UnknownDeckChoice _unknownDeckChoice;
|
||||
private boolean _isMove;
|
||||
private boolean _isOverwrite;
|
||||
private boolean _stop;
|
||||
|
||||
// only accessed from the event loop thread
|
||||
int _maxLogLength = 0;
|
||||
|
||||
public _OperationLogAsyncUpdater(Map<OpType, Pair<FCheckBox, ? extends Map<File, File>>> selections, JTextArea operationLog) {
|
||||
super("OperationLogUpdater");
|
||||
setDaemon(true);
|
||||
|
||||
_selections = new HashMap<OpType, Map<File, File>>();
|
||||
_operationLog = operationLog;
|
||||
|
||||
// remove references to FCheckBox when populating map -- we can't safely access it from a thread
|
||||
// anyway and it's better to keep our data structure clean to prevent mistakes
|
||||
for (Map.Entry<OpType, Pair<FCheckBox, ? extends Map<File, File>>> entry : selections.entrySet()) {
|
||||
_selections.put(entry.getKey(), entry.getValue().getRight());
|
||||
}
|
||||
}
|
||||
|
||||
// updates the synchronized data with values for the next iteration in _run
|
||||
public synchronized void requestUpdate(
|
||||
Set<OpType> selectedOptions, _UnknownDeckChoice unknownDeckChoice, boolean isMove, boolean isOverwrite) {
|
||||
++_updateCallCnt;
|
||||
_selectedOptions = selectedOptions;
|
||||
_unknownDeckChoice = unknownDeckChoice;
|
||||
_isMove = isMove;
|
||||
_isOverwrite = isOverwrite;
|
||||
|
||||
// notify waiter
|
||||
notify();
|
||||
}
|
||||
|
||||
public synchronized void requestStop() {
|
||||
_stop = true;
|
||||
|
||||
// notify waiter
|
||||
notify();
|
||||
}
|
||||
|
||||
private void _run() throws InterruptedException {
|
||||
int lastUpdateCallCnt = _updateCallCnt;
|
||||
Set<OpType> selectedOptions;
|
||||
_UnknownDeckChoice unknownDeckChoice;
|
||||
boolean isMove;
|
||||
boolean isOverwrite;
|
||||
|
||||
while (true) {
|
||||
synchronized (this) {
|
||||
// can't check _stop in the while condition since we have to do it in a synchronized block
|
||||
if (_stop) { break; }
|
||||
|
||||
// if we're stopped while looping here, run through the update one last time
|
||||
// before returning
|
||||
while (lastUpdateCallCnt == _updateCallCnt && !_stop) {
|
||||
wait();
|
||||
}
|
||||
|
||||
// safely copy synchronized data to local values that we will use for this runthrough
|
||||
lastUpdateCallCnt = _updateCallCnt;
|
||||
selectedOptions = _selectedOptions;
|
||||
unknownDeckChoice = _unknownDeckChoice;
|
||||
isMove = _isMove;
|
||||
isOverwrite = _isOverwrite;
|
||||
}
|
||||
|
||||
// build operation log
|
||||
final StringBuilder log = new StringBuilder();
|
||||
int totalOps = 0;
|
||||
for (OpType opType : selectedOptions) {
|
||||
Map<File, File> ops = _selections.get(opType);
|
||||
totalOps += ops.size();
|
||||
|
||||
for (Map.Entry<File, File> op : ops.entrySet()) {
|
||||
File dest = op.getValue();
|
||||
if (OpType.UNKNOWN_DECK == opType) {
|
||||
dest = new File(unknownDeckChoice.path, dest.getName());
|
||||
}
|
||||
log.append(op.getKey().getAbsolutePath()).append(" -> ");
|
||||
log.append(dest.getAbsolutePath()).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// append summary
|
||||
if (0 < totalOps) {
|
||||
log.append("\n");
|
||||
}
|
||||
log.append("Prepared to ").append(isMove ? "move" : "copy");
|
||||
log.append(" ").append(totalOps).append(" files\n");
|
||||
log.append(isOverwrite ? "O" : "Not o").append("verwriting existing files");
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String logText = log.toString();
|
||||
|
||||
// setText is thread-safe, but the resizing is not, so might as well do this in the swing event loop thread
|
||||
_operationLog.setText(log.toString());
|
||||
|
||||
if (_maxLogLength < logText.length()) {
|
||||
_maxLogLength = logText.length();
|
||||
|
||||
// resize the panel properly for the new log contents
|
||||
_selectionPanel.getParent().validate();
|
||||
_selectionPanel.getParent().invalidate();
|
||||
_topPanel.getParent().validate();
|
||||
_topPanel.getParent().invalidate();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try { _run(); } catch (final InterruptedException e) {
|
||||
_cancel = true;
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override public void run() {
|
||||
// we never interrupt the thread, so this is not expected to happen
|
||||
BugReporter.reportException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// asynchronously completes the specified I/O operations and updates the progress bar and operation log
|
||||
private class _Importer extends SwingWorker<Void, Void> {
|
||||
private final String _srcDir;
|
||||
private final Map<File, File> _operations;
|
||||
private final JTextArea _operationLog;
|
||||
private final JProgressBar _progressBar;
|
||||
private final boolean _move;
|
||||
private final boolean _overwrite;
|
||||
|
||||
public _Importer(String srcDir, Map<OpType, Pair<FCheckBox, ? extends Map<File, File>>> selections, FComboBoxWrapper<_UnknownDeckChoice> unknownDeckCombo,
|
||||
JTextArea operationLog, JProgressBar progressBar, boolean move, boolean overwrite) {
|
||||
_srcDir = srcDir;
|
||||
_operationLog = operationLog;
|
||||
_progressBar = progressBar;
|
||||
_move = move;
|
||||
_overwrite = overwrite;
|
||||
|
||||
// build local operations map that only includes data that we can access from the background thread
|
||||
// use a tree map to maintain alphabetical order
|
||||
_operations = new TreeMap<File, File>();
|
||||
for (Map.Entry<OpType, Pair<FCheckBox, ? extends Map<File, File>>> entry : selections.entrySet()) {
|
||||
Pair<FCheckBox, ? extends Map<File, File>> selection = entry.getValue();
|
||||
if (selection.getLeft().isSelected()) {
|
||||
if (OpType.UNKNOWN_DECK != entry.getKey()) {
|
||||
_operations.putAll(selection.getRight());
|
||||
} else {
|
||||
// map unknown decks to selected directory
|
||||
for (Map.Entry<File, File> op : selection.getRight().entrySet()) {
|
||||
_UnknownDeckChoice choice = (_UnknownDeckChoice)unknownDeckCombo.getSelectedItem();
|
||||
_operations.put(op.getKey(), new File(choice.path, op.getValue().getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set progress bar bounds
|
||||
_progressBar.setString(_move ? "Moving files..." : "Copying files...");
|
||||
_progressBar.setMinimum(0);
|
||||
_progressBar.setMaximum(_operations.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
try {
|
||||
// working with textbox text is thread safe
|
||||
_operationLog.setText("");
|
||||
|
||||
// only update the text box once very half second, but make the first
|
||||
// update after only 100ms
|
||||
final long updateIntervalMs = 500;
|
||||
long lastUpdateTimestampMs = System.currentTimeMillis() - 400;
|
||||
StringBuffer opLogBuf = new StringBuffer();
|
||||
|
||||
// only update the progress bar when we expect the visual value to change
|
||||
final long progressInterval = Math.max(1, _operations.size() / _progressBar.getWidth());
|
||||
|
||||
// the length of the prefix to remove from source paths
|
||||
final int srcPathPrefixLen;
|
||||
if (_srcDir.endsWith("/") || _srcDir.endsWith(File.separator)) {
|
||||
srcPathPrefixLen = _srcDir.length();
|
||||
} else
|
||||
{
|
||||
srcPathPrefixLen = _srcDir.length() + 1;
|
||||
}
|
||||
|
||||
// stats maintained during import sequence
|
||||
int numOps = 0;
|
||||
int numExisting = 0;
|
||||
int numSucceeded = 0;
|
||||
int numFailed = 0;
|
||||
for (Map.Entry<File, File> op : _operations.entrySet()) {
|
||||
if (_cancel) { break; }
|
||||
|
||||
final int curOpNum = ++numOps;
|
||||
if (0 == curOpNum % progressInterval) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override public void run() {
|
||||
if (_cancel) { return; }
|
||||
_progressBar.setValue(curOpNum);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
long curTimeMs = System.currentTimeMillis();
|
||||
if (updateIntervalMs <= curTimeMs - lastUpdateTimestampMs) {
|
||||
lastUpdateTimestampMs = curTimeMs;
|
||||
|
||||
// working with textbox text is thread safe
|
||||
_operationLog.append(opLogBuf.toString());
|
||||
opLogBuf.setLength(0);
|
||||
}
|
||||
|
||||
File srcFile = op.getKey();
|
||||
File destFile = op.getValue();
|
||||
|
||||
try {
|
||||
// simplify logged source path and log next attempted operation
|
||||
String srcPath = srcFile.getAbsolutePath();
|
||||
// I doubt that the srcPath will start with anything other than _srcDir, even with symlinks,
|
||||
// hardlinks, or Windows junctioned nodes, but it's better to be safe than to have malformed output
|
||||
if (srcPath.startsWith(_srcDir)) {
|
||||
srcPath = srcPath.substring(srcPathPrefixLen);
|
||||
}
|
||||
opLogBuf.append(_move ? "Moving " : "Copying ").append(srcPath).append(" -> ");
|
||||
opLogBuf.append(destFile.getAbsolutePath()).append("\n");
|
||||
|
||||
if (!destFile.exists()) {
|
||||
_copyFile(srcFile, destFile, _move);
|
||||
} else {
|
||||
if (_overwrite) {
|
||||
opLogBuf.append(" Destination file exists; overwriting\n");
|
||||
_copyFile(srcFile, destFile, _move);
|
||||
} else {
|
||||
opLogBuf.append(" Destination file exists; skipping copy\n");
|
||||
}
|
||||
++numExisting;
|
||||
}
|
||||
|
||||
if (_move) {
|
||||
// source file may have been deleted already if _copyFile was called
|
||||
srcFile.delete();
|
||||
opLogBuf.append(" Removed source file after successful copy\n");
|
||||
}
|
||||
|
||||
++numSucceeded;
|
||||
} catch (IOException e) {
|
||||
opLogBuf.append(" Operation failed: ").append(e.getMessage()).append("\n");
|
||||
++numFailed;
|
||||
}
|
||||
}
|
||||
|
||||
// append summary footer
|
||||
opLogBuf.append("\nImport complete: ");
|
||||
opLogBuf.append(numSucceeded).append(" operation").append(1 == numSucceeded ? "" : "s").append(" succeeded, ");
|
||||
opLogBuf.append(numFailed).append(" error").append(1 == numFailed ? "" : "s");
|
||||
if (0 < numExisting) {
|
||||
opLogBuf.append(", ").append(numExisting);
|
||||
if (_overwrite) {
|
||||
opLogBuf.append(" existing destination files overwritten");
|
||||
} else {
|
||||
opLogBuf.append(" copy operations skipped due to existing destination files");
|
||||
}
|
||||
}
|
||||
_operationLog.append(opLogBuf.toString());
|
||||
} catch (final Exception e) {
|
||||
_cancel = true;
|
||||
|
||||
// report any exceptions in a standard dialog
|
||||
// note that regular I/O errors don't throw, they'll just be mentioned in the log
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override public void run() {
|
||||
_progressBar.setString("Error");
|
||||
BugReporter.reportException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
_btnCancel.requestFocusInWindow();
|
||||
if (_cancel) { return; }
|
||||
|
||||
_progressBar.setValue(_progressBar.getMaximum());
|
||||
_progressBar.setString("Import complete");
|
||||
_btnCancel.setText("Done");
|
||||
}
|
||||
}
|
||||
|
||||
// when copying is required, uses java nio classes for ultra-fast I/O
|
||||
private static void _copyFile(File srcFile, File destFile, boolean deleteSrcAfter) throws IOException {
|
||||
destFile.getParentFile().mkdirs();
|
||||
|
||||
// if this is a move, try a simple rename first
|
||||
if (deleteSrcAfter) {
|
||||
if (srcFile.renameTo(destFile)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!destFile.exists()) {
|
||||
destFile.createNewFile();
|
||||
}
|
||||
|
||||
FileChannel src = null;
|
||||
FileChannel dest = null;
|
||||
try {
|
||||
src = new FileInputStream(srcFile).getChannel();
|
||||
dest = new FileOutputStream(destFile).getChannel();
|
||||
dest.transferFrom(src, 0, src.size());
|
||||
} finally {
|
||||
if (src != null) { src.close(); }
|
||||
if (dest != null) { dest.close(); }
|
||||
}
|
||||
|
||||
if (deleteSrcAfter) {
|
||||
srcFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,660 @@
|
||||
/*
|
||||
* Forge: Play Magic: the Gathering.
|
||||
* Copyright (c) 2013 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.gui;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.assets.ImageUtil;
|
||||
import forge.card.CardEdition;
|
||||
import forge.card.CardRules;
|
||||
import forge.item.IPaperCard;
|
||||
import forge.item.PaperCard;
|
||||
import forge.model.FModel;
|
||||
import forge.properties.ForgeConstants;
|
||||
import forge.util.FileUtil;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class ImportSourceAnalyzer {
|
||||
public static enum OpType {
|
||||
CONSTRUCTED_DECK,
|
||||
DRAFT_DECK,
|
||||
PLANAR_DECK,
|
||||
SCHEME_DECK,
|
||||
SEALED_DECK,
|
||||
UNKNOWN_DECK,
|
||||
DEFAULT_CARD_PIC,
|
||||
SET_CARD_PIC,
|
||||
POSSIBLE_SET_CARD_PIC,
|
||||
TOKEN_PIC,
|
||||
QUEST_PIC,
|
||||
GAUNTLET_DATA,
|
||||
QUEST_DATA,
|
||||
PREFERENCE_FILE,
|
||||
DB_FILE
|
||||
}
|
||||
|
||||
public static interface AnalysisCallback {
|
||||
boolean checkCancel();
|
||||
void addOp(OpType type, File src, File dest);
|
||||
}
|
||||
|
||||
private final File _source;
|
||||
private final AnalysisCallback _cb;
|
||||
private final int _numFilesToAnalyze;
|
||||
|
||||
private int _numFilesAnalyzed;
|
||||
|
||||
public ImportSourceAnalyzer(String source, AnalysisCallback cb) {
|
||||
_source = new File(source);
|
||||
_cb = cb;
|
||||
|
||||
_numFilesToAnalyze = _countFiles(_source);
|
||||
}
|
||||
|
||||
public int getNumFilesToAnalyze() { return _numFilesToAnalyze; }
|
||||
public int getNumFilesAnalyzed() { return _numFilesAnalyzed; }
|
||||
|
||||
public void doAnalysis() {
|
||||
_identifyAndAnalyze(_source);
|
||||
}
|
||||
|
||||
private void _identifyAndAnalyze(File root) {
|
||||
// see if we can figure out the likely identity of the source folder and
|
||||
// dispatch to the best analysis subroutine to handle it
|
||||
String dirname = root.getName();
|
||||
|
||||
if ("res".equalsIgnoreCase(dirname)) { _analyzeOldResDir(root); }
|
||||
else if ("constructed".equalsIgnoreCase(dirname)) { _analyzeConstructedDeckDir(root); }
|
||||
else if ("draft".equalsIgnoreCase(dirname)) { _analyzeDraftDeckDir(root); }
|
||||
else if ("plane".equalsIgnoreCase(dirname) || "planar".equalsIgnoreCase(dirname)) { _analyzePlanarDeckDir(root); }
|
||||
else if ("scheme".equalsIgnoreCase(dirname)) { _analyzeSchemeDeckDir(root); }
|
||||
else if ("sealed".equalsIgnoreCase(dirname)) { _analyzeSealedDeckDir(root); }
|
||||
else if (StringUtils.containsIgnoreCase(dirname, "deck")) { _analyzeDecksDir(root); }
|
||||
else if ("gauntlet".equalsIgnoreCase(dirname)) { _analyzeGauntletDataDir(root); }
|
||||
else if ("layouts".equalsIgnoreCase(dirname)) { _analyzeLayoutsDir(root); }
|
||||
else if ("pics".equalsIgnoreCase(dirname)) { _analyzeCardPicsDir(root); }
|
||||
else if ("pics_product".equalsIgnoreCase(dirname)) { _analyzeProductPicsDir(root); }
|
||||
else if ("preferences".equalsIgnoreCase(dirname)) { _analyzePreferencesDir(root); }
|
||||
else if ("quest".equalsIgnoreCase(dirname)) { _analyzeQuestDir(root); }
|
||||
else if (null != FModel.getMagicDb().getEditions().get(dirname)) { _analyzeCardPicsSetDir(root); }
|
||||
else {
|
||||
// look at files in directory and make a semi-educated guess based on file extensions
|
||||
int numUnhandledFiles = 0;
|
||||
for (File file : root.listFiles()) {
|
||||
if (_cb.checkCancel()) { return; }
|
||||
|
||||
if (file.isFile()) {
|
||||
String filename = file.getName();
|
||||
if (StringUtils.endsWithIgnoreCase(filename, ".dck")) {
|
||||
_analyzeDecksDir(root);
|
||||
numUnhandledFiles = 0;
|
||||
break;
|
||||
} else if (StringUtils.endsWithIgnoreCase(filename, ".jpg")) {
|
||||
_analyzeCardPicsDir(root);
|
||||
numUnhandledFiles = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
++numUnhandledFiles;
|
||||
} else if (file.isDirectory()) {
|
||||
_identifyAndAnalyze(file);
|
||||
}
|
||||
}
|
||||
_numFilesAnalyzed += numUnhandledFiles;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// pre-profile res dir
|
||||
//
|
||||
|
||||
private void _analyzeOldResDir(File root) {
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override boolean onDir(File dir) {
|
||||
String dirname = dir.getName();
|
||||
if ("decks".equalsIgnoreCase(dirname)) {
|
||||
_analyzeDecksDir(dir);
|
||||
} else if ("gauntlet".equalsIgnoreCase(dirname)) {
|
||||
_analyzeGauntletDataDir(dir);
|
||||
} else if ("layouts".equalsIgnoreCase(dirname)) {
|
||||
_analyzeLayoutsDir(dir);
|
||||
} else if ("pics".equalsIgnoreCase(dirname)) {
|
||||
_analyzeCardPicsDir(dir);
|
||||
} else if ("pics_product".equalsIgnoreCase(dirname)) {
|
||||
_analyzeProductPicsDir(dir);
|
||||
} else if ("preferences".equalsIgnoreCase(dirname)) {
|
||||
_analyzePreferencesDir(dir);
|
||||
} else if ("quest".equalsIgnoreCase(dirname)) {
|
||||
_analyzeQuestDir(dir);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// decks
|
||||
//
|
||||
|
||||
private void _analyzeDecksDir(File root) {
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override void onFile(File file) {
|
||||
// we don't really expect any files in here, but if we find a .dck file, add it to the unknown list
|
||||
String filename = file.getName();
|
||||
if (StringUtils.endsWithIgnoreCase(filename, ".dck")) {
|
||||
File targetFile = new File(_lcaseExt(filename));
|
||||
_cb.addOp(OpType.UNKNOWN_DECK, file, targetFile);
|
||||
}
|
||||
}
|
||||
|
||||
@Override boolean onDir(File dir) {
|
||||
String dirname = dir.getName();
|
||||
if ("constructed".equalsIgnoreCase(dirname)) {
|
||||
_analyzeConstructedDeckDir(dir);
|
||||
} else if ("cube".equalsIgnoreCase(dirname)) {
|
||||
return false;
|
||||
} else if ("draft".equalsIgnoreCase(dirname)) {
|
||||
_analyzeDraftDeckDir(dir);
|
||||
} else if ("plane".equalsIgnoreCase(dirname) || "planar".equalsIgnoreCase(dirname)) {
|
||||
_analyzePlanarDeckDir(dir);
|
||||
} else if ("scheme".equalsIgnoreCase(dirname)) {
|
||||
_analyzeSchemeDeckDir(dir);
|
||||
} else if ("sealed".equalsIgnoreCase(dirname)) {
|
||||
_analyzeSealedDeckDir(dir);
|
||||
} else {
|
||||
_analyzeKnownDeckDir(dir, null, OpType.UNKNOWN_DECK);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void _analyzeConstructedDeckDir(File root) {
|
||||
_analyzeKnownDeckDir(root, ForgeConstants.DECK_CONSTRUCTED_DIR, OpType.CONSTRUCTED_DECK);
|
||||
}
|
||||
|
||||
private void _analyzeDraftDeckDir(File root) {
|
||||
_analyzeKnownDeckDir(root, ForgeConstants.DECK_DRAFT_DIR, OpType.DRAFT_DECK);
|
||||
}
|
||||
|
||||
private void _analyzePlanarDeckDir(File root) {
|
||||
_analyzeKnownDeckDir(root, ForgeConstants.DECK_PLANE_DIR, OpType.PLANAR_DECK);
|
||||
}
|
||||
|
||||
private void _analyzeSchemeDeckDir(File root) {
|
||||
_analyzeKnownDeckDir(root, ForgeConstants.DECK_SCHEME_DIR, OpType.SCHEME_DECK);
|
||||
}
|
||||
|
||||
private void _analyzeSealedDeckDir(File root) {
|
||||
_analyzeKnownDeckDir(root, ForgeConstants.DECK_SEALED_DIR, OpType.SEALED_DECK);
|
||||
}
|
||||
|
||||
private void _analyzeKnownDeckDir(File root, final String targetDir, final OpType opType) {
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override void onFile(File file) {
|
||||
String filename = file.getName();
|
||||
if (StringUtils.endsWithIgnoreCase(filename, ".dck")) {
|
||||
File targetFile = new File(targetDir, _lcaseExt(filename));
|
||||
if (!file.equals(targetFile)) {
|
||||
_cb.addOp(opType, file, targetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override boolean onDir(File dir) {
|
||||
// if there's a dir beneath a known directory, assume the same kind of decks are in there
|
||||
_analyzeKnownDeckDir(dir, targetDir, opType);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// gauntlet
|
||||
//
|
||||
|
||||
private void _analyzeGauntletDataDir(File root) {
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override void onFile(File file) {
|
||||
// find *.dat files, but exclude LOCKED_*
|
||||
String filename = file.getName();
|
||||
if (StringUtils.endsWithIgnoreCase(filename, ".dat") && !filename.startsWith("LOCKED_")) {
|
||||
File targetFile = new File(ForgeConstants.GAUNTLET_DIR.userPrefLoc, _lcaseExt(filename));
|
||||
if (!file.equals(targetFile)) {
|
||||
_cb.addOp(OpType.GAUNTLET_DATA, file, targetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// layouts
|
||||
//
|
||||
|
||||
private void _analyzeLayoutsDir(File root) {
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override void onFile(File file) {
|
||||
// find *_preferred.xml files
|
||||
String filename = file.getName();
|
||||
if (StringUtils.endsWithIgnoreCase(filename, "_preferred.xml")) {
|
||||
File targetFile = new File(ForgeConstants.USER_PREFS_DIR,
|
||||
file.getName().toLowerCase(Locale.ENGLISH).replace("_preferred", ""));
|
||||
_cb.addOp(OpType.PREFERENCE_FILE, file, targetFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// default card pics
|
||||
//
|
||||
|
||||
private static String _oldCleanString(String in) {
|
||||
final StringBuffer out = new StringBuffer();
|
||||
for (int i = 0; i < in.length(); i++) {
|
||||
char c = in.charAt(i);
|
||||
if ((c == ' ') || (c == '-')) {
|
||||
out.append('_');
|
||||
} else if (Character.isLetterOrDigit(c) || (c == '_')) {
|
||||
out.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
// usually we would want to pass Locale.ENGLISH to the toLowerCase() method to prevent unintentional
|
||||
// character mangling on some system locales, but we want to replicate the old code here exactly
|
||||
return out.toString().toLowerCase();
|
||||
}
|
||||
|
||||
private void _addDefaultPicNames(PaperCard c, boolean backFace) {
|
||||
CardRules card = c.getRules();
|
||||
String urls = card.getPictureUrl(backFace);
|
||||
if (StringUtils.isEmpty(urls)) { return; }
|
||||
|
||||
int numPics = 1 + StringUtils.countMatches(urls, "\\");
|
||||
if (c.getArtIndex() > numPics) {
|
||||
return;
|
||||
}
|
||||
|
||||
String filenameBase = ImageUtil.getImageKey(c, backFace, false);
|
||||
String filename = filenameBase + ".jpg";
|
||||
boolean alreadyHadIt = null != _defaultPicNames.put(filename, filename);
|
||||
if ( alreadyHadIt ) return;
|
||||
|
||||
// Do you shift artIndex by one here?
|
||||
String newLastSymbol = 0 == c.getArtIndex() ? "" : String.valueOf(c.getArtIndex() /* + 1 */);
|
||||
String oldFilename = _oldCleanString(filenameBase.replaceAll("[0-9]?(\\.full)?$", "")) + newLastSymbol + ".jpg";
|
||||
//if ( numPics > 1 )
|
||||
//System.out.printf("Will move %s -> %s%n", oldFilename, filename);
|
||||
_defaultPicOldNameToCurrentName.put(oldFilename, filename);
|
||||
}
|
||||
|
||||
|
||||
private Map<String, String> _defaultPicNames;
|
||||
private Map<String, String> _defaultPicOldNameToCurrentName;
|
||||
private void _analyzeCardPicsDir(File root) {
|
||||
if (null == _defaultPicNames) {
|
||||
_defaultPicNames = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
_defaultPicOldNameToCurrentName = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
for (PaperCard c : FModel.getMagicDb().getCommonCards().getAllCards()) {
|
||||
_addDefaultPicNames(c, false);
|
||||
if (ImageUtil.hasBackFacePicture(c)) {
|
||||
_addDefaultPicNames(c, true);
|
||||
}
|
||||
}
|
||||
|
||||
for (PaperCard c : FModel.getMagicDb().getVariantCards().getAllCards()) {
|
||||
_addDefaultPicNames(c, false);
|
||||
// variants never have backfaces
|
||||
}
|
||||
}
|
||||
|
||||
_analyzeListedDir(root, ForgeConstants.CACHE_CARD_PICS_DIR, new _ListedAnalyzer() {
|
||||
@Override public String map(String filename) {
|
||||
if (_defaultPicOldNameToCurrentName.containsKey(filename)) {
|
||||
return _defaultPicOldNameToCurrentName.get(filename);
|
||||
}
|
||||
return _defaultPicNames.get(filename);
|
||||
}
|
||||
|
||||
@Override public OpType getOpType(String filename) { return OpType.DEFAULT_CARD_PIC; }
|
||||
|
||||
@Override boolean onDir(File dir) {
|
||||
if ("icons".equalsIgnoreCase(dir.getName())) {
|
||||
_analyzeIconsPicsDir(dir);
|
||||
} else if ("tokens".equalsIgnoreCase(dir.getName())) {
|
||||
_analyzeTokenPicsDir(dir);
|
||||
} else {
|
||||
_analyzeCardPicsSetDir(dir);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// set card pics
|
||||
//
|
||||
|
||||
private static void _addSetCards(Map<String, String> cardFileNames, Iterable<PaperCard> library, Predicate<PaperCard> filter) {
|
||||
for (PaperCard c : Iterables.filter(library, filter)) {
|
||||
String filename = ImageUtil.getImageKey(c, false, true) + ".jpg";
|
||||
cardFileNames.put(filename, filename);
|
||||
if (ImageUtil.hasBackFacePicture(c)) {
|
||||
filename = ImageUtil.getImageKey(c, true, true) + ".jpg";
|
||||
cardFileNames.put(filename, filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Map<String, String>> _cardFileNamesBySet;
|
||||
Map<String, String> _nameUpdates;
|
||||
private void _analyzeCardPicsSetDir(File root) {
|
||||
if (null == _cardFileNamesBySet) {
|
||||
_cardFileNamesBySet = new TreeMap<String, Map<String, String>>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (CardEdition ce : FModel.getMagicDb().getEditions()) {
|
||||
Map<String, String> cardFileNames = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
Predicate<PaperCard> filter = IPaperCard.Predicates.printedInSet(ce.getCode());
|
||||
_addSetCards(cardFileNames, FModel.getMagicDb().getCommonCards().getAllCards(), filter);
|
||||
_addSetCards(cardFileNames, FModel.getMagicDb().getVariantCards().getAllCards(), filter);
|
||||
_cardFileNamesBySet.put(ce.getCode2(), cardFileNames);
|
||||
}
|
||||
|
||||
// planar cards now don't have the ".full" part in their filenames
|
||||
_nameUpdates = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
Predicate<PaperCard> predPlanes = new Predicate<PaperCard>() {
|
||||
@Override
|
||||
public boolean apply(PaperCard arg0) {
|
||||
return arg0.getRules().getType().isPlane() || arg0.getRules().getType().isPhenomenon();
|
||||
}
|
||||
};
|
||||
|
||||
for (PaperCard c : Iterables.filter(FModel.getMagicDb().getVariantCards().getAllCards(), predPlanes)) {
|
||||
String baseName = ImageUtil.getImageKey(c,false, true);
|
||||
_nameUpdates.put(baseName + ".full.jpg", baseName + ".jpg");
|
||||
if (ImageUtil.hasBackFacePicture(c)) {
|
||||
baseName = ImageUtil.getImageKey(c, true, true);
|
||||
_nameUpdates.put(baseName + ".full.jpg", baseName + ".jpg");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardEdition.Collection editions = FModel.getMagicDb().getEditions();
|
||||
String editionCode = root.getName();
|
||||
CardEdition edition = editions.get(editionCode);
|
||||
if (null == edition) {
|
||||
// not a valid set name, skip
|
||||
_numFilesAnalyzed += _countFiles(root);
|
||||
return;
|
||||
}
|
||||
|
||||
final String editionCode2 = edition.getCode2();
|
||||
final Map<String, String> validFilenames = _cardFileNamesBySet.get(editionCode2);
|
||||
_analyzeListedDir(root, ForgeConstants.CACHE_CARD_PICS_DIR, new _ListedAnalyzer() {
|
||||
@Override public String map(String filename) {
|
||||
filename = editionCode2 + "/" + filename;
|
||||
if (_nameUpdates.containsKey(filename)) {
|
||||
filename = _nameUpdates.get(filename);
|
||||
}
|
||||
if (validFilenames.containsKey(filename)) {
|
||||
return validFilenames.get(filename);
|
||||
} else if (StringUtils.endsWithIgnoreCase(filename, ".jpg")
|
||||
|| StringUtils.endsWithIgnoreCase(filename, ".png")) {
|
||||
return filename;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override public OpType getOpType(String filename) {
|
||||
return validFilenames.containsKey(filename) ? OpType.SET_CARD_PIC : OpType.POSSIBLE_SET_CARD_PIC;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// other image dirs
|
||||
//
|
||||
|
||||
Map<String, String> _iconFileNames;
|
||||
private void _analyzeIconsPicsDir(File root) {
|
||||
if (null == _iconFileNames) {
|
||||
_iconFileNames = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (Pair<String, String> nameurl : FileUtil.readNameUrlFile(ForgeConstants.IMAGE_LIST_QUEST_OPPONENT_ICONS_FILE)) {
|
||||
_iconFileNames.put(nameurl.getLeft(), nameurl.getLeft());
|
||||
}
|
||||
for (Pair<String, String> nameurl : FileUtil.readNameUrlFile(ForgeConstants.IMAGE_LIST_QUEST_PET_SHOP_ICONS_FILE)) {
|
||||
_iconFileNames.put(nameurl.getLeft(), nameurl.getLeft());
|
||||
}
|
||||
}
|
||||
|
||||
_analyzeListedDir(root, ForgeConstants.CACHE_ICON_PICS_DIR, new _ListedAnalyzer() {
|
||||
@Override public String map(String filename) { return _iconFileNames.containsKey(filename) ? _iconFileNames.get(filename) : null; }
|
||||
@Override public OpType getOpType(String filename) { return OpType.QUEST_PIC; }
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, String> _tokenFileNames;
|
||||
Map<String, String> _questTokenFileNames;
|
||||
private void _analyzeTokenPicsDir(File root) {
|
||||
if (null == _tokenFileNames) {
|
||||
_tokenFileNames = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
_questTokenFileNames = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (Pair<String, String> nameurl : FileUtil.readNameUrlFile(ForgeConstants.IMAGE_LIST_TOKENS_FILE)) {
|
||||
_tokenFileNames.put(nameurl.getLeft(), nameurl.getLeft());
|
||||
}
|
||||
for (Pair<String, String> nameurl : FileUtil.readNameUrlFile(ForgeConstants.IMAGE_LIST_QUEST_TOKENS_FILE)) {
|
||||
_questTokenFileNames.put(nameurl.getLeft(), nameurl.getLeft());
|
||||
}
|
||||
}
|
||||
|
||||
_analyzeListedDir(root, ForgeConstants.CACHE_TOKEN_PICS_DIR, new _ListedAnalyzer() {
|
||||
@Override public String map(String filename) {
|
||||
if (_questTokenFileNames.containsKey(filename)) { return _questTokenFileNames.get(filename); }
|
||||
if (_tokenFileNames.containsKey(filename)) { return _tokenFileNames.get(filename); }
|
||||
return null;
|
||||
}
|
||||
@Override public OpType getOpType(String filename) {
|
||||
return _questTokenFileNames.containsKey(filename) ? OpType.QUEST_PIC : OpType.TOKEN_PIC;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void _analyzeProductPicsDir(File root) {
|
||||
// we don't care about the files in the root dir -- the new booster files are .png, not the current .jpg ones
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override boolean onDir(File dir) {
|
||||
String dirName = dir.getName();
|
||||
if ("booster".equalsIgnoreCase(dirName)) {
|
||||
_analyzeSimpleListedDir(dir, ForgeConstants.IMAGE_LIST_QUEST_BOOSTERS_FILE, ForgeConstants.CACHE_BOOSTER_PICS_DIR, OpType.QUEST_PIC);
|
||||
} else if ("fatpacks".equalsIgnoreCase(dirName)) {
|
||||
_analyzeSimpleListedDir(dir, ForgeConstants.IMAGE_LIST_QUEST_FATPACKS_FILE, ForgeConstants.CACHE_FATPACK_PICS_DIR, OpType.QUEST_PIC);
|
||||
} else if ("precons".equalsIgnoreCase(dirName)) {
|
||||
_analyzeSimpleListedDir(dir, ForgeConstants.IMAGE_LIST_QUEST_PRECONS_FILE, ForgeConstants.CACHE_PRECON_PICS_DIR, OpType.QUEST_PIC);
|
||||
} else if ("tournamentpacks".equalsIgnoreCase(dirName)) {
|
||||
_analyzeSimpleListedDir(dir, ForgeConstants.IMAGE_LIST_QUEST_TOURNAMENTPACKS_FILE, ForgeConstants.CACHE_TOURNAMENTPACK_PICS_DIR, OpType.QUEST_PIC);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// preferences
|
||||
//
|
||||
|
||||
private void _analyzePreferencesDir(File root) {
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override void onFile(File file) {
|
||||
String filename = file.getName();
|
||||
if ("editor.preferences".equalsIgnoreCase(filename) || "forge.preferences".equalsIgnoreCase(filename)) {
|
||||
File targetFile = new File(ForgeConstants.USER_PREFS_DIR, filename.toLowerCase(Locale.ENGLISH));
|
||||
if (!file.equals(targetFile)) {
|
||||
_cb.addOp(OpType.PREFERENCE_FILE, file, targetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// quest data
|
||||
//
|
||||
|
||||
private void _analyzeQuestDir(File root) {
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override void onFile(File file) {
|
||||
String filename = file.getName();
|
||||
if ("all-prices.txt".equalsIgnoreCase(filename)) {
|
||||
File targetFile = new File(ForgeConstants.DB_DIR, filename.toLowerCase(Locale.ENGLISH));
|
||||
if (!file.equals(targetFile)) {
|
||||
_cb.addOp(OpType.DB_FILE, file, targetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override boolean onDir(File dir) {
|
||||
if ("data".equalsIgnoreCase(dir.getName())) {
|
||||
_analyzeQuestDataDir(dir);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void _analyzeQuestDataDir(File root) {
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override void onFile(File file) {
|
||||
String filename = file.getName();
|
||||
if (StringUtils.endsWithIgnoreCase(filename, ".dat")) {
|
||||
File targetFile = new File(ForgeConstants.QUEST_SAVE_DIR, _lcaseExt(filename));
|
||||
if (!file.equals(targetFile)) {
|
||||
_cb.addOp(OpType.QUEST_DATA, file, targetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// utility functions
|
||||
//
|
||||
|
||||
private class _Analyzer {
|
||||
void onFile(File file) { }
|
||||
|
||||
// returns whether the directory has been handled
|
||||
boolean onDir(File dir) { return false; }
|
||||
}
|
||||
|
||||
private void _analyzeDir(File root, _Analyzer analyzer) {
|
||||
for (File file : root.listFiles()) {
|
||||
if (_cb.checkCancel()) { return; }
|
||||
|
||||
if (file.isFile()) {
|
||||
++_numFilesAnalyzed;
|
||||
analyzer.onFile(file);
|
||||
} else if (file.isDirectory()) {
|
||||
if (!analyzer.onDir(file)) {
|
||||
_numFilesAnalyzed += _countFiles(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Map<String, String>> _fileNameDb = new HashMap<String, Map<String, String>>();
|
||||
private void _analyzeSimpleListedDir(File root, String listFile, String targetDir, final OpType opType) {
|
||||
if (!_fileNameDb.containsKey(listFile)) {
|
||||
Map<String, String> fileNames = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
|
||||
for (Pair<String, String> nameurl : FileUtil.readNameUrlFile(listFile)) {
|
||||
// we use a map instead of a set since we need to match case-insensitively but still map to the correct case
|
||||
fileNames.put(nameurl.getLeft(), nameurl.getLeft());
|
||||
}
|
||||
_fileNameDb.put(listFile, fileNames);
|
||||
}
|
||||
|
||||
final Map<String, String> fileDb = _fileNameDb.get(listFile);
|
||||
_analyzeListedDir(root, targetDir, new _ListedAnalyzer() {
|
||||
@Override public String map(String filename) { return fileDb.containsKey(filename) ? fileDb.get(filename) : null; }
|
||||
@Override public OpType getOpType(String filename) { return opType; }
|
||||
});
|
||||
}
|
||||
|
||||
private abstract class _ListedAnalyzer {
|
||||
abstract String map(String filename);
|
||||
abstract OpType getOpType(String filename);
|
||||
|
||||
// returns whether the directory has been handled
|
||||
boolean onDir(File dir) { return false; }
|
||||
}
|
||||
|
||||
private void _analyzeListedDir(File root, final String targetDir, final _ListedAnalyzer listedAnalyzer) {
|
||||
_analyzeDir(root, new _Analyzer() {
|
||||
@Override void onFile(File file) {
|
||||
String filename = listedAnalyzer.map(file.getName());
|
||||
if (null != filename) {
|
||||
File targetFile = new File(targetDir, filename);
|
||||
if (!file.equals(targetFile)) {
|
||||
_cb.addOp(listedAnalyzer.getOpType(filename), file, targetFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override boolean onDir(File dir) { return listedAnalyzer.onDir(dir); }
|
||||
});
|
||||
}
|
||||
|
||||
private int _countFiles(File root) {
|
||||
int count = 0;
|
||||
for (File file : root.listFiles()) {
|
||||
if (_cb.checkCancel()) { return 0; }
|
||||
|
||||
if (file.isFile()) {
|
||||
++count;
|
||||
} else if (file.isDirectory()) {
|
||||
count += _countFiles(file);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private String _lcaseExt(String filename) {
|
||||
int lastDotIdx = filename.lastIndexOf('.');
|
||||
if (0 > lastDotIdx) {
|
||||
return filename;
|
||||
}
|
||||
String basename = filename.substring(0, lastDotIdx);
|
||||
String ext = filename.substring(lastDotIdx).toLowerCase(Locale.ENGLISH);
|
||||
if (filename.endsWith(ext)) {
|
||||
return filename;
|
||||
}
|
||||
return basename + ext;
|
||||
}
|
||||
}
|
||||
310
forge-gui-desktop/src/main/java/forge/gui/ListChooser.java
Normal file
310
forge-gui-desktop/src/main/java/forge/gui/ListChooser.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.gui;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.FThreads;
|
||||
import forge.toolbox.FList;
|
||||
import forge.toolbox.FMouseAdapter;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.toolbox.FScrollPane;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple class that shows a list of choices in a dialog. Two properties
|
||||
* influence the behavior of a list chooser: minSelection and maxSelection.
|
||||
* These two give the allowed number of selected items for the dialog to be
|
||||
* closed. A negative value for minSelection suggests that the list is revealed
|
||||
* and the choice doesn't matter.
|
||||
* <ul>
|
||||
* <li>If minSelection is 0, there will be a Cancel button.</li>
|
||||
* <li>If minSelection is -1, 0 or 1, double-clicking a choice will also close the
|
||||
* dialog.</li>
|
||||
* <li>If the number of selections is out of bounds, the "OK" button is
|
||||
* disabled.</li>
|
||||
* <li>The dialog was "committed" if "OK" was clicked or a choice was double
|
||||
* clicked.</li>
|
||||
* <li>The dialog was "canceled" if "Cancel" or "X" was clicked.</li>
|
||||
* <li>If the dialog was canceled, the selection will be empty.</li>
|
||||
* <li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <T>
|
||||
* the generic type
|
||||
* @author Forge
|
||||
* @version $Id: ListChooser.java 25183 2014-03-14 23:09:45Z drdev $
|
||||
*/
|
||||
public class ListChooser<T> {
|
||||
// Data and number of choices for the list
|
||||
private List<T> list;
|
||||
private int minChoices, maxChoices;
|
||||
|
||||
// Flag: was the dialog already shown?
|
||||
private boolean called;
|
||||
|
||||
// initialized before; listeners may be added to it
|
||||
private FList<T> lstChoices;
|
||||
private FOptionPane optionPane;
|
||||
|
||||
public ListChooser(final String title, final int minChoices, final int maxChoices, final Collection<T> list, final Function<T, String> display) {
|
||||
FThreads.assertExecutedByEdt(true);
|
||||
this.minChoices = minChoices;
|
||||
this.maxChoices = maxChoices;
|
||||
this.list = list.getClass().isInstance(List.class) ? (List<T>)list : Lists.newArrayList(list);
|
||||
this.lstChoices = new FList<T>(new ChooserListModel());
|
||||
|
||||
String[] options;
|
||||
if (minChoices == 0) {
|
||||
options = new String[] {"OK","Cancel"};
|
||||
}
|
||||
else {
|
||||
options = new String[] {"OK"};
|
||||
}
|
||||
|
||||
if (maxChoices == 1 || minChoices == -1) {
|
||||
this.lstChoices.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
}
|
||||
|
||||
if (display != null) {
|
||||
this.lstChoices.setCellRenderer(new TransformedCellRenderer(display));
|
||||
}
|
||||
|
||||
FScrollPane listScroller = new FScrollPane(this.lstChoices, true);
|
||||
int minWidth = this.lstChoices.getAutoSizeWidth();
|
||||
if (this.lstChoices.getModel().getSize() > this.lstChoices.getVisibleRowCount()) {
|
||||
minWidth += listScroller.getVerticalScrollBar().getPreferredSize().width;
|
||||
}
|
||||
listScroller.setMinimumSize(new Dimension(minWidth, listScroller.getMinimumSize().height));
|
||||
|
||||
this.optionPane = new FOptionPane(null, title, null, listScroller, options, minChoices < 0 ? 0 : -1);
|
||||
this.optionPane.setButtonEnabled(0, minChoices <= 0);
|
||||
|
||||
if (minChoices != -1) {
|
||||
this.optionPane.setDefaultFocus(this.lstChoices);
|
||||
}
|
||||
|
||||
if (minChoices > 0) {
|
||||
this.optionPane.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
}
|
||||
|
||||
if (minChoices != -1) {
|
||||
this.lstChoices.getSelectionModel().addListSelectionListener(new SelListener());
|
||||
}
|
||||
|
||||
this.lstChoices.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
ListChooser.this.commit();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.lstChoices.addMouseListener(new FMouseAdapter() {
|
||||
@Override
|
||||
public void onLeftClick(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
ListChooser.this.commit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FList used in the list chooser. this is useful for
|
||||
* registering listeners before showing the dialog.
|
||||
*
|
||||
* @return a {@link javax.swing.JList} object.
|
||||
*/
|
||||
public FList<T> getLstChoices() {
|
||||
return this.lstChoices;
|
||||
}
|
||||
|
||||
/** @return boolean */
|
||||
public boolean show() {
|
||||
return show(list.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the dialog and returns after the dialog was closed.
|
||||
*
|
||||
* @param index0 index to select when shown
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean show(final T item) {
|
||||
if (this.called) {
|
||||
throw new IllegalStateException("Already shown");
|
||||
}
|
||||
int result;
|
||||
do {
|
||||
SwingUtilities.invokeLater(new Runnable() { //invoke later so selected item not set until dialog open
|
||||
@Override
|
||||
public void run() {
|
||||
if (list.contains(item)) {
|
||||
lstChoices.setSelectedValue(item, true);
|
||||
}
|
||||
else {
|
||||
lstChoices.setSelectedIndex(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.optionPane.setVisible(true);
|
||||
result = this.optionPane.getResult();
|
||||
if (result != 0) {
|
||||
this.lstChoices.clearSelection();
|
||||
}
|
||||
// can't stop closing by ESC, so repeat if cancelled
|
||||
} while (this.minChoices > 0 && result != 0);
|
||||
|
||||
this.optionPane.dispose();
|
||||
|
||||
// this assert checks if we really don't return on a cancel if input is mandatory
|
||||
assert (this.minChoices <= 0) || (result == 0);
|
||||
this.called = true;
|
||||
return (result == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the dialog was closed by pressing "OK" or double clicking an
|
||||
* option the last time.
|
||||
*
|
||||
* @return a boolean.
|
||||
*/
|
||||
public boolean isCommitted() {
|
||||
if (!this.called) {
|
||||
throw new IllegalStateException("not yet shown");
|
||||
}
|
||||
return (this.optionPane.getResult() == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected indices as a list of integers.
|
||||
*
|
||||
* @return a {@link java.util.List} object.
|
||||
*/
|
||||
public int[] getSelectedIndices() {
|
||||
if (!this.called) {
|
||||
throw new IllegalStateException("not yet shown");
|
||||
}
|
||||
return this.lstChoices.getSelectedIndices();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected values as a list of objects. no casts are necessary
|
||||
* when retrieving the objects.
|
||||
*
|
||||
* @return a {@link java.util.List} object.
|
||||
*/
|
||||
public List<T> getSelectedValues() {
|
||||
if (!this.called) {
|
||||
throw new IllegalStateException("not yet shown");
|
||||
}
|
||||
return this.lstChoices.getSelectedValuesList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the (minimum) selected index, or -1.
|
||||
*
|
||||
* @return a int.
|
||||
*/
|
||||
public int getSelectedIndex() {
|
||||
if (!this.called) {
|
||||
throw new IllegalStateException("not yet shown");
|
||||
}
|
||||
return this.lstChoices.getSelectedIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the (first) selected value, or null.
|
||||
*
|
||||
* @return a T object.
|
||||
*/
|
||||
public T getSelectedValue() {
|
||||
if (!this.called) {
|
||||
throw new IllegalStateException("not yet shown");
|
||||
}
|
||||
return (T) this.lstChoices.getSelectedValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* commit.
|
||||
* </p>
|
||||
*/
|
||||
private void commit() {
|
||||
if (this.optionPane.isButtonEnabled(0)) {
|
||||
optionPane.setResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
private class ChooserListModel extends AbstractListModel<T> {
|
||||
private static final long serialVersionUID = 3871965346333840556L;
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return ListChooser.this.list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getElementAt(final int index) {
|
||||
return ListChooser.this.list.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
private class SelListener implements ListSelectionListener {
|
||||
@Override
|
||||
public void valueChanged(final ListSelectionEvent e) {
|
||||
final int num = ListChooser.this.lstChoices.getSelectedIndices().length;
|
||||
ListChooser.this.optionPane.setButtonEnabled(0, (num >= ListChooser.this.minChoices) && (num <= ListChooser.this.maxChoices));
|
||||
}
|
||||
}
|
||||
|
||||
private class TransformedCellRenderer implements ListCellRenderer<T> {
|
||||
public final Function<T, String> transformer;
|
||||
public final DefaultListCellRenderer defRenderer;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for Constructor.
|
||||
*/
|
||||
public TransformedCellRenderer(final Function<T, String> t1) {
|
||||
transformer = t1;
|
||||
defRenderer = new DefaultListCellRenderer();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.swing.ListCellRenderer#getListCellRendererComponent(javax.swing.JList, java.lang.Object, int, boolean, boolean)
|
||||
*/
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends T> list, T value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
// TODO Auto-generated method stub
|
||||
return defRenderer.getListCellRendererComponent(list, transformer.apply(value), index, isSelected, cellHasFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
forge-gui-desktop/src/main/java/forge/gui/MouseUtil.java
Normal file
61
forge-gui-desktop/src/main/java/forge/gui/MouseUtil.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package forge.gui;
|
||||
|
||||
import forge.view.FView;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
public final class MouseUtil {
|
||||
private static Cursor cursor;
|
||||
private static int cursorLockCount;
|
||||
|
||||
/**
|
||||
* Lock cursor as it is currently displayed until unlockCursor called
|
||||
*/
|
||||
public static void lockCursor() {
|
||||
cursorLockCount++;
|
||||
}
|
||||
public static void unlockCursor() {
|
||||
if (cursorLockCount == 0) { return; }
|
||||
if (--cursorLockCount == 0) {
|
||||
//update displayed cursor after cursor unlocked
|
||||
FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The only reliable way to ensure the mouse cursor is set properly in Forge.
|
||||
*
|
||||
* @param mouseCursor one of the predefined {@code Cursor} types.
|
||||
*/
|
||||
public static void resetCursor() {
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
}
|
||||
public static void setCursor(int cursorType) {
|
||||
setCursor(Cursor.getPredefinedCursor(cursorType));
|
||||
}
|
||||
public static void setCursor(Cursor cursor0) {
|
||||
if (cursor == cursor0) { return; }
|
||||
cursor = cursor0;
|
||||
if (cursorLockCount > 0) { return; }
|
||||
FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(cursor);
|
||||
}
|
||||
|
||||
public static void setComponentCursor(final Component comp, final int cursorType) {
|
||||
setComponentCursor(comp, Cursor.getPredefinedCursor(cursorType));
|
||||
}
|
||||
public static void setComponentCursor(final Component comp, final Cursor cursor0) {
|
||||
comp.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
setCursor(cursor0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
resetCursor();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
126
forge-gui-desktop/src/main/java/forge/gui/MultiLineLabel.java
Normal file
126
forge-gui-desktop/src/main/java/forge/gui/MultiLineLabel.java
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* A {@link JLabel} with support for multi-line text that wraps when the line
|
||||
* doesn't fit in the available width. Multi-line text support is handled by the
|
||||
* {@link MultiLineLabelUI}, the default UI delegate of this component. The text
|
||||
* in the label can be horizontally and vertically aligned, relative to the
|
||||
* bounds of the component.
|
||||
*
|
||||
* @author Samuel Sjoberg, http://samuelsjoberg.com
|
||||
* @version 1.0.0
|
||||
*/
|
||||
public class MultiLineLabel extends JLabel {
|
||||
|
||||
/**
|
||||
* Default serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Horizontal text alignment.
|
||||
*/
|
||||
private int halign = SwingConstants.LEFT;
|
||||
|
||||
/**
|
||||
* Vertical text alignment.
|
||||
*/
|
||||
private int valign = SwingConstants.CENTER;
|
||||
|
||||
/**
|
||||
* Cache to save heap allocations.
|
||||
*/
|
||||
private Rectangle bounds;
|
||||
|
||||
/**
|
||||
* Creates a new empty label.
|
||||
*/
|
||||
public MultiLineLabel() {
|
||||
super();
|
||||
this.setUI(MultiLineLabelUI.getLabelUI());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new label with <code>text</code> value.
|
||||
*
|
||||
* @param text
|
||||
* the value of the label
|
||||
*/
|
||||
public MultiLineLabel(final String text) {
|
||||
this();
|
||||
this.setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return a {@link java.awt.Rectangle} object.
|
||||
*/
|
||||
@Override
|
||||
public Rectangle getBounds() {
|
||||
if (this.bounds == null) {
|
||||
this.bounds = new Rectangle();
|
||||
}
|
||||
return super.getBounds(this.bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the vertical text alignment.
|
||||
*
|
||||
* @param alignment
|
||||
* vertical alignment
|
||||
*/
|
||||
public void setVerticalTextAlignment(final int alignment) {
|
||||
this.firePropertyChange("verticalTextAlignment", this.valign, alignment);
|
||||
this.valign = alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the horizontal text alignment.
|
||||
*
|
||||
* @param alignment
|
||||
* horizontal alignment
|
||||
*/
|
||||
public void setHorizontalTextAlignment(final int alignment) {
|
||||
this.firePropertyChange("horizontalTextAlignment", this.halign, alignment);
|
||||
this.halign = alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the vertical text alignment.
|
||||
*
|
||||
* @return vertical text alignment
|
||||
*/
|
||||
public int getVerticalTextAlignment() {
|
||||
return this.valign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the horizontal text alignment.
|
||||
*
|
||||
* @return horizontal text alignment
|
||||
*/
|
||||
public int getHorizontalTextAlignment() {
|
||||
return this.halign;
|
||||
}
|
||||
}
|
||||
655
forge-gui-desktop/src/main/java/forge/gui/MultiLineLabelUI.java
Normal file
655
forge-gui-desktop/src/main/java/forge/gui/MultiLineLabelUI.java
Normal file
@@ -0,0 +1,655 @@
|
||||
/*
|
||||
* 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.gui;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.plaf.ComponentUI;
|
||||
import javax.swing.plaf.LabelUI;
|
||||
import javax.swing.plaf.basic.BasicLabelUI;
|
||||
import javax.swing.text.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Label UI delegate that supports multiple lines and line wrapping. Hard line
|
||||
* breaks (<code>\n</code>) are preserved. If the dimensions of the label is too
|
||||
* small to fit all content, the string will be clipped and "..." appended to
|
||||
* the end of the visible text (similar to the default behavior of
|
||||
* <code>JLabel</code>). If used in conjunction with a {@link MultiLineLabel},
|
||||
* text alignment (horizontal and vertical) is supported. The UI delegate can be
|
||||
* used on a regular <code>JLabel</code> if text alignment isn't required. The
|
||||
* default alignment, left and vertically centered, will then be used.
|
||||
* <p/>
|
||||
* Example of usage:
|
||||
* <p/>
|
||||
*
|
||||
* <pre>
|
||||
* JLabel myLabel = new JLabel();
|
||||
* myLabel.setUI(MultiLineLabelUI.labelUI);
|
||||
* myLabel.setText("A long label that will wrap automatically.");
|
||||
* </pre>
|
||||
* <p/>
|
||||
* <p/>
|
||||
* The line and wrapping support is implemented without using a
|
||||
* <code>View</code> to make it easy for subclasses to add custom text effects
|
||||
* by overriding {@link #paintEnabledText(JLabel, Graphics, String, int, int)}
|
||||
* and {@link #paintDisabledText(JLabel, Graphics, String, int, int)}. This
|
||||
* class is designed to be easily extended by subclasses.
|
||||
*
|
||||
* @author Samuel Sjoberg, http://samuelsjoberg.com
|
||||
* @version 1.3.0
|
||||
*/
|
||||
public class MultiLineLabelUI extends BasicLabelUI implements ComponentListener {
|
||||
|
||||
/**
|
||||
* Shared instance of the UI delegate.
|
||||
*/
|
||||
private static LabelUI labelUI = new MultiLineLabelUI();
|
||||
|
||||
/**
|
||||
* Client property key used to store the calculated wrapped lines on the
|
||||
* JLabel.
|
||||
*/
|
||||
public static final String PROPERTY_KEY = "WrappedText";
|
||||
|
||||
// Static references to avoid heap allocations.
|
||||
/** Constant <code>paintIconR</code>. */
|
||||
private static Rectangle paintIconR = new Rectangle();
|
||||
|
||||
/** Constant <code>paintTextR</code>. */
|
||||
private static Rectangle paintTextR = new Rectangle();
|
||||
|
||||
/** Constant <code>paintViewR</code>. */
|
||||
private static Rectangle paintViewR = new Rectangle();
|
||||
|
||||
/** Constant <code>paintViewInsets</code>. */
|
||||
private static Insets paintViewInsets = new Insets(0, 0, 0, 0);
|
||||
|
||||
/**
|
||||
* Font metrics of the JLabel being rendered.
|
||||
*/
|
||||
private FontMetrics metrics;
|
||||
|
||||
/**
|
||||
* Default size of the lines list.
|
||||
*/
|
||||
private static int defaultSize = 4;
|
||||
|
||||
/**
|
||||
* Get the shared UI instance.
|
||||
*
|
||||
* @param c
|
||||
* the c
|
||||
* @return a ComponentUI
|
||||
*/
|
||||
public static ComponentUI createUI(final JComponent c) {
|
||||
return MultiLineLabelUI.getLabelUI();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
protected void uninstallDefaults(final JLabel c) {
|
||||
super.uninstallDefaults(c);
|
||||
this.clearCache(c);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
protected void installListeners(final JLabel c) {
|
||||
super.installListeners(c);
|
||||
c.addComponentListener(this);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
protected void uninstallListeners(final JLabel c) {
|
||||
super.uninstallListeners(c);
|
||||
c.removeComponentListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the wrapped line cache.
|
||||
*
|
||||
* @param l
|
||||
* the label containing a cached value
|
||||
*/
|
||||
protected void clearCache(final JLabel l) {
|
||||
l.putClientProperty(MultiLineLabelUI.PROPERTY_KEY, null);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void propertyChange(final PropertyChangeEvent e) {
|
||||
super.propertyChange(e);
|
||||
final String name = e.getPropertyName();
|
||||
if (name.equals("text") || "font".equals(name)) {
|
||||
this.clearCache((JLabel) e.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the paint rectangles for the icon and text for the passed
|
||||
* label.
|
||||
*
|
||||
* @param l
|
||||
* a label
|
||||
* @param fm
|
||||
* the font metrics to use, or <code>null</code> to get the font
|
||||
* metrics from the label
|
||||
* @param width
|
||||
* label width
|
||||
* @param height
|
||||
* label height
|
||||
*/
|
||||
protected void updateLayout(final JLabel l, FontMetrics fm, final int width, final int height) {
|
||||
if (fm == null) {
|
||||
fm = l.getFontMetrics(l.getFont());
|
||||
}
|
||||
this.metrics = fm;
|
||||
|
||||
final String text = l.getText();
|
||||
final Icon icon = l.getIcon();
|
||||
final Insets insets = l.getInsets(MultiLineLabelUI.paintViewInsets);
|
||||
|
||||
MultiLineLabelUI.paintViewR.x = insets.left;
|
||||
MultiLineLabelUI.paintViewR.y = insets.top;
|
||||
MultiLineLabelUI.paintViewR.width = width - (insets.left + insets.right);
|
||||
MultiLineLabelUI.paintViewR.height = height - (insets.top + insets.bottom);
|
||||
|
||||
MultiLineLabelUI.paintIconR.x = 0;
|
||||
MultiLineLabelUI.paintIconR.y = 0;
|
||||
MultiLineLabelUI.paintIconR.width = 0;
|
||||
MultiLineLabelUI.paintIconR.height = 0;
|
||||
MultiLineLabelUI.paintTextR.x = 0;
|
||||
MultiLineLabelUI.paintTextR.y = 0;
|
||||
MultiLineLabelUI.paintTextR.width = 0;
|
||||
MultiLineLabelUI.paintTextR.height = 0;
|
||||
|
||||
this.layoutCL(l, fm, text, icon, MultiLineLabelUI.paintViewR, MultiLineLabelUI.paintIconR,
|
||||
MultiLineLabelUI.paintTextR);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* prepareGraphics.
|
||||
* </p>
|
||||
*
|
||||
* @param g
|
||||
* a {@link java.awt.Graphics} object.
|
||||
*/
|
||||
protected void prepareGraphics(final Graphics g) {
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void paint(final Graphics g, final JComponent c) {
|
||||
|
||||
// parent's update method fills the background
|
||||
this.prepareGraphics(g);
|
||||
|
||||
final JLabel label = (JLabel) c;
|
||||
final String text = label.getText();
|
||||
final Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon();
|
||||
|
||||
if ((icon == null) && (text == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final FontMetrics fm = g.getFontMetrics();
|
||||
|
||||
this.updateLayout(label, fm, c.getWidth(), c.getHeight());
|
||||
|
||||
if (icon != null) {
|
||||
icon.paintIcon(c, g, MultiLineLabelUI.paintIconR.x, MultiLineLabelUI.paintIconR.y);
|
||||
}
|
||||
|
||||
if (text != null) {
|
||||
final View v = (View) c.getClientProperty("html");
|
||||
if (v != null) {
|
||||
// HTML view disables multi-line painting.
|
||||
v.paint(g, MultiLineLabelUI.paintTextR);
|
||||
} else {
|
||||
// Paint the multi line text
|
||||
this.paintTextLines(g, label, fm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paint the wrapped text lines.
|
||||
*
|
||||
* @param g
|
||||
* graphics component to paint on
|
||||
* @param label
|
||||
* the label being painted
|
||||
* @param fm
|
||||
* font metrics for current font
|
||||
*/
|
||||
protected void paintTextLines(final Graphics g, final JLabel label, final FontMetrics fm) {
|
||||
final List<String> lines = this.getTextLines(label);
|
||||
|
||||
// Available component height to paint on.
|
||||
final int height = this.getAvailableHeight(label);
|
||||
|
||||
int textHeight = lines.size() * fm.getHeight();
|
||||
while (textHeight > height) {
|
||||
// Remove one line until no. of visible lines is found.
|
||||
textHeight -= fm.getHeight();
|
||||
}
|
||||
MultiLineLabelUI.paintTextR.height = Math.min(textHeight, height);
|
||||
MultiLineLabelUI.paintTextR.y = this.alignmentY(label, fm, MultiLineLabelUI.paintTextR);
|
||||
|
||||
final int textX = MultiLineLabelUI.paintTextR.x;
|
||||
int textY = MultiLineLabelUI.paintTextR.y;
|
||||
|
||||
for (final Iterator<String> it = lines.iterator(); it.hasNext()
|
||||
&& MultiLineLabelUI.paintTextR.contains(textX, textY + MultiLineLabelUI.getAscent(fm)); textY += fm
|
||||
.getHeight()) {
|
||||
|
||||
String text = it.next().trim();
|
||||
|
||||
if (it.hasNext()
|
||||
&& !MultiLineLabelUI.paintTextR.contains(textX,
|
||||
textY + fm.getHeight() + MultiLineLabelUI.getAscent(fm))) {
|
||||
// The last visible row, add a clip indication.
|
||||
text = this.clip(text, fm, MultiLineLabelUI.paintTextR);
|
||||
}
|
||||
|
||||
final int x = this.alignmentX(label, fm, text, MultiLineLabelUI.paintTextR);
|
||||
|
||||
if (label.isEnabled()) {
|
||||
this.paintEnabledText(label, g, text, x, textY);
|
||||
} else {
|
||||
this.paintDisabledText(label, g, text, x, textY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the available height to paint text on. This is the height of the
|
||||
* passed component with insets subtracted.
|
||||
*
|
||||
* @param l
|
||||
* a component
|
||||
* @return the available height
|
||||
*/
|
||||
protected int getAvailableHeight(final JLabel l) {
|
||||
l.getInsets(MultiLineLabelUI.paintViewInsets);
|
||||
return l.getHeight() - MultiLineLabelUI.paintViewInsets.top - MultiLineLabelUI.paintViewInsets.bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a clip indication to the string. It is important that the string
|
||||
* length does not exceed the length or the original string.
|
||||
*
|
||||
* @param text
|
||||
* the to be painted
|
||||
* @param fm
|
||||
* font metrics
|
||||
* @param bounds
|
||||
* the text bounds
|
||||
* @return the clipped string
|
||||
*/
|
||||
protected String clip(final String text, final FontMetrics fm, final Rectangle bounds) {
|
||||
// Fast and lazy way to insert a clip indication is to simply replace
|
||||
// the last characters in the string with the clip indication.
|
||||
// A better way would be to use metrics and calculate how many (if any)
|
||||
// characters that need to be replaced.
|
||||
if (text.length() < 3) {
|
||||
return "...";
|
||||
}
|
||||
return text.substring(0, text.length() - 3) + "...";
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish the vertical text alignment. The default alignment is to center
|
||||
* the text in the label.
|
||||
*
|
||||
* @param label
|
||||
* the label to paint
|
||||
* @param fm
|
||||
* font metrics
|
||||
* @param bounds
|
||||
* the text bounds rectangle
|
||||
* @return the vertical text alignment, defaults to CENTER.
|
||||
*/
|
||||
protected int alignmentY(final JLabel label, final FontMetrics fm, final Rectangle bounds) {
|
||||
final int height = this.getAvailableHeight(label);
|
||||
final int textHeight = bounds.height;
|
||||
|
||||
if (label instanceof MultiLineLabel) {
|
||||
final int align = ((MultiLineLabel) label).getVerticalTextAlignment();
|
||||
switch (align) {
|
||||
case SwingConstants.TOP:
|
||||
return MultiLineLabelUI.getAscent(fm) + MultiLineLabelUI.paintViewInsets.top;
|
||||
case SwingConstants.BOTTOM:
|
||||
return (((MultiLineLabelUI.getAscent(fm) + height) - MultiLineLabelUI.paintViewInsets.top) + MultiLineLabelUI.paintViewInsets.bottom)
|
||||
- textHeight;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Center alignment
|
||||
final int textY = MultiLineLabelUI.paintViewInsets.top + ((height - textHeight) / 2)
|
||||
+ MultiLineLabelUI.getAscent(fm);
|
||||
return Math.max(textY, MultiLineLabelUI.getAscent(fm) + MultiLineLabelUI.paintViewInsets.top);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* getAscent.
|
||||
* </p>
|
||||
*
|
||||
* @param fm
|
||||
* a {@link java.awt.FontMetrics} object.
|
||||
* @return a int.
|
||||
*/
|
||||
private static int getAscent(final FontMetrics fm) {
|
||||
return fm.getAscent() + fm.getLeading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish the horizontal text alignment. The default alignment is left
|
||||
* aligned text.
|
||||
*
|
||||
* @param label
|
||||
* the label to paint
|
||||
* @param fm
|
||||
* font metrics
|
||||
* @param s
|
||||
* the string to paint
|
||||
* @param bounds
|
||||
* the text bounds rectangle
|
||||
* @return the x-coordinate to use when painting for proper alignment
|
||||
*/
|
||||
protected int alignmentX(final JLabel label, final FontMetrics fm, final String s, final Rectangle bounds) {
|
||||
if (label instanceof MultiLineLabel) {
|
||||
final int align = ((MultiLineLabel) label).getHorizontalTextAlignment();
|
||||
switch (align) {
|
||||
case SwingConstants.RIGHT:
|
||||
return (bounds.x + MultiLineLabelUI.paintViewR.width) - fm.stringWidth(s);
|
||||
case SwingConstants.CENTER:
|
||||
return (bounds.x + (MultiLineLabelUI.paintViewR.width / 2)) - (fm.stringWidth(s) / 2);
|
||||
default:
|
||||
return bounds.x;
|
||||
}
|
||||
}
|
||||
return bounds.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the given string to see if it should be rendered as HTML. Code
|
||||
* based on implementation found in
|
||||
* <code>BasicHTML.isHTMLString(String)</code> in future JDKs.
|
||||
*
|
||||
* @param s
|
||||
* the string
|
||||
* @return <code>true</code> if string is HTML, otherwise <code>false</code>
|
||||
*/
|
||||
private static boolean isHTMLString(final String s) {
|
||||
if (s != null) {
|
||||
if ((s.length() >= 6) && (s.charAt(0) == '<') && (s.charAt(5) == '>')) {
|
||||
final String tag = s.substring(1, 5);
|
||||
return tag.equalsIgnoreCase("html");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Dimension getPreferredSize(final JComponent c) {
|
||||
final Dimension d = super.getPreferredSize(c);
|
||||
final JLabel label = (JLabel) c;
|
||||
|
||||
if (MultiLineLabelUI.isHTMLString(label.getText())) {
|
||||
return d; // HTML overrides everything and we don't need to process
|
||||
}
|
||||
|
||||
// Width calculated by super is OK. The preferred width is the width of
|
||||
// the unwrapped content as long as it does not exceed the width of the
|
||||
// parent container.
|
||||
|
||||
if (c.getParent() != null) {
|
||||
// Ensure that preferred width never exceeds the available width
|
||||
// (including its border insets) of the parent container.
|
||||
final Insets insets = c.getParent().getInsets();
|
||||
final Dimension size = c.getParent().getSize();
|
||||
if (size.width > 0) {
|
||||
// If width isn't set component shouldn't adjust.
|
||||
d.width = size.width - insets.left - insets.right;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateLayout(label, null, d.width, d.height);
|
||||
|
||||
// The preferred height is either the preferred height of the text
|
||||
// lines, or the height of the icon.
|
||||
d.height = Math.max(d.height, this.getPreferredHeight(label));
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* The preferred height of the label is the height of the lines with added
|
||||
* top and bottom insets.
|
||||
*
|
||||
* @param label
|
||||
* the label
|
||||
* @return the preferred height of the wrapped lines.
|
||||
*/
|
||||
protected int getPreferredHeight(final JLabel label) {
|
||||
final int numOfLines = this.getTextLines(label).size();
|
||||
final Insets insets = label.getInsets(MultiLineLabelUI.paintViewInsets);
|
||||
return (numOfLines * this.metrics.getHeight()) + insets.top + insets.bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the lines of text contained in the text label. The prepared lines is
|
||||
* cached as a client property, accessible via {@link #PROPERTY_KEY}.
|
||||
*
|
||||
* @param l
|
||||
* the label
|
||||
* @return the text lines of the label.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected List<String> getTextLines(final JLabel l) {
|
||||
List<String> lines = (List<String>) l.getClientProperty(MultiLineLabelUI.PROPERTY_KEY);
|
||||
if (lines == null) {
|
||||
lines = this.prepareLines(l);
|
||||
l.putClientProperty(MultiLineLabelUI.PROPERTY_KEY, lines);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void componentHidden(final ComponentEvent e) {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void componentMoved(final ComponentEvent e) {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void componentResized(final ComponentEvent e) {
|
||||
this.clearCache((JLabel) e.getSource());
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void componentShown(final ComponentEvent e) {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the text lines for rendering. The lines are wrapped to fit in the
|
||||
* current available space for text. Explicit line breaks are preserved.
|
||||
*
|
||||
* @param l
|
||||
* the label to render
|
||||
* @return a list of text lines to render
|
||||
*/
|
||||
protected List<String> prepareLines(final JLabel l) {
|
||||
final List<String> lines = new ArrayList<String>(MultiLineLabelUI.defaultSize);
|
||||
final String text = l.getText();
|
||||
if (text == null) {
|
||||
return null; // Null guard
|
||||
}
|
||||
final PlainDocument doc = new PlainDocument();
|
||||
try {
|
||||
doc.insertString(0, text, null);
|
||||
} catch (final BadLocationException e) {
|
||||
return null;
|
||||
}
|
||||
final Element root = doc.getDefaultRootElement();
|
||||
for (int i = 0, j = root.getElementCount(); i < j; i++) {
|
||||
this.wrap(lines, root.getElement(i));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* If necessary, wrap the text into multiple lines.
|
||||
*
|
||||
* @param lines
|
||||
* line array in which to store the wrapped lines
|
||||
* @param elem
|
||||
* the document element containing the text content
|
||||
*/
|
||||
protected void wrap(final List<String> lines, final Element elem) {
|
||||
final int p1 = elem.getEndOffset();
|
||||
final Document doc = elem.getDocument();
|
||||
for (int p0 = elem.getStartOffset(); p0 < p1;) {
|
||||
final int p = this.calculateBreakPosition(doc, p0, p1);
|
||||
try {
|
||||
lines.add(doc.getText(p0, p - p0));
|
||||
} catch (final BadLocationException e) {
|
||||
throw new Error("Can't get line text. p0=" + p0 + " p=" + p);
|
||||
}
|
||||
p0 = (p == p0) ? p1 : p;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the position on which to break (wrap) the line.
|
||||
*
|
||||
* @param doc
|
||||
* the document
|
||||
* @param p0
|
||||
* start position
|
||||
* @param p1
|
||||
* end position
|
||||
* @return the actual end position, will be <code>p1</code> if content does
|
||||
* not need to wrap, otherwise it will be less than <code>p1</code>.
|
||||
*/
|
||||
protected int calculateBreakPosition(final Document doc, final int p0, final int p1) {
|
||||
final Segment segment = SegmentCache.getSegment();
|
||||
try {
|
||||
doc.getText(p0, p1 - p0, segment);
|
||||
} catch (final BadLocationException e) {
|
||||
throw new Error("Can't get line text");
|
||||
}
|
||||
|
||||
final int width = MultiLineLabelUI.paintTextR.width;
|
||||
final int p = p0 + Utilities.getBreakLocation(segment, this.metrics, 0, width, null, p0);
|
||||
SegmentCache.releaseSegment(segment);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the label ui.
|
||||
*
|
||||
* @return the labelUI
|
||||
*/
|
||||
public static LabelUI getLabelUI() {
|
||||
return MultiLineLabelUI.labelUI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label ui.
|
||||
*
|
||||
* @param labelUI
|
||||
* the new label ui
|
||||
*/
|
||||
public static void setLabelUI(final LabelUI labelUI) {
|
||||
MultiLineLabelUI.labelUI = labelUI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static singleton {@link Segment} cache.
|
||||
*
|
||||
* @author Samuel Sjoberg
|
||||
* @see javax.swing.text.SegmentCache
|
||||
*/
|
||||
protected static final class SegmentCache {
|
||||
|
||||
/**
|
||||
* Reused segments.
|
||||
*/
|
||||
private final ArrayList<Segment> segments = new ArrayList<Segment>(2);
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
private static SegmentCache cache = new SegmentCache();
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private SegmentCache() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a <code>Segment</code>. When done, the <code>Segment</code>
|
||||
* should be recycled by invoking {@link #releaseSegment(Segment)}.
|
||||
*
|
||||
* @return a <code>Segment</code>.
|
||||
*/
|
||||
public static Segment getSegment() {
|
||||
final int size = SegmentCache.cache.segments.size();
|
||||
if (size > 0) {
|
||||
return SegmentCache.cache.segments.remove(size - 1);
|
||||
}
|
||||
return new Segment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a <code>Segment</code>. A segment should not be used after
|
||||
* it is released, and a segment should never be released more than
|
||||
* once.
|
||||
*
|
||||
* @param segment
|
||||
* the segment
|
||||
*/
|
||||
public static void releaseSegment(final Segment segment) {
|
||||
segment.array = null;
|
||||
segment.count = 0;
|
||||
SegmentCache.cache.segments.add(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
158
forge-gui-desktop/src/main/java/forge/gui/SOverlayUtils.java
Normal file
158
forge-gui-desktop/src/main/java/forge/gui/SOverlayUtils.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package forge.gui;
|
||||
|
||||
import forge.Singletons;
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.screens.match.TargetingOverlay;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FOverlay;
|
||||
import forge.toolbox.FPanel;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinnedButton;
|
||||
import forge.toolbox.FSkin.SkinnedLabel;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.FocusManager;
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
/**
|
||||
* All overlay interaction is handled here.
|
||||
*
|
||||
* <br><br><i>(S at beginning of class name denotes a static factory.)</i>
|
||||
*/
|
||||
public final class SOverlayUtils {
|
||||
private static int counter = 0;
|
||||
|
||||
/**
|
||||
* A standardized overlay for a game start condition.
|
||||
*/
|
||||
public static void startGameOverlay() {
|
||||
final JPanel overlay = SOverlayUtils.genericOverlay();
|
||||
final int w = overlay.getWidth();
|
||||
final int h = overlay.getHeight();
|
||||
final int pnlW = 400;
|
||||
final int pnlH = 300;
|
||||
|
||||
// Adds the "loading" panel to generic overlay container
|
||||
// (which is preset with null layout and close button)
|
||||
final FPanel pnl = new FPanel();
|
||||
pnl.setLayout(new MigLayout("insets 0, gap 0, ax center, wrap"));
|
||||
pnl.setBackground(FSkin.getColor(FSkin.Colors.CLR_ACTIVE));
|
||||
pnl.setBounds(new Rectangle(((w - pnlW) / 2), ((h - pnlH) / 2), pnlW, pnlH));
|
||||
|
||||
pnl.add(new FLabel.Builder().icon(FSkin.getIcon(FSkinProp.ICO_LOGO)).build(),
|
||||
"h 200px!, align center");
|
||||
pnl.add(new FLabel.Builder().text("Loading new game...")
|
||||
.fontSize(22).build(), "h 40px!, align center");
|
||||
|
||||
overlay.add(pnl);
|
||||
}
|
||||
|
||||
/**
|
||||
* A standardized overlay for a loading condition (note: thread issues, as of 1-Mar-12).
|
||||
* @param msg0   {@link java.lang.String}
|
||||
* @return {@link javax.swing.JPanel}
|
||||
*/
|
||||
// NOTE: This animation happens on the EDT; if the EDT is tied up doing something
|
||||
// else, the animation is effectively frozen. So, this needs some work.
|
||||
public static JPanel loadingOverlay(final String msg0) {
|
||||
final JPanel overlay = SOverlayUtils.genericOverlay();
|
||||
final FPanel pnlLoading = new FPanel();
|
||||
final int w = overlay.getWidth();
|
||||
final int h = overlay.getHeight();
|
||||
|
||||
final SkinnedLabel lblLoading = new SkinnedLabel("");
|
||||
lblLoading.setOpaque(true);
|
||||
lblLoading.setBackground(FSkin.getColor(FSkin.Colors.CLR_TEXT));
|
||||
lblLoading.setMinimumSize(new Dimension(0, 20));
|
||||
|
||||
pnlLoading.setBounds(((w - 170) / 2), ((h - 80) / 2), 170, 80);
|
||||
pnlLoading.setLayout(new MigLayout("wrap, align center"));
|
||||
pnlLoading.add(new FLabel.Builder().fontSize(18)
|
||||
.text(msg0).build(), "h 20px!, w 140px!, gap 0 0 5px 0");
|
||||
pnlLoading.add(lblLoading, "gap 0 0 0 10px");
|
||||
|
||||
overlay.add(pnlLoading);
|
||||
|
||||
SOverlayUtils.counter = 0;
|
||||
final Timer timer = new Timer(300, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
lblLoading.setMinimumSize(new Dimension(10 * (SOverlayUtils.counter++), 20));
|
||||
lblLoading.revalidate();
|
||||
if (SOverlayUtils.counter > 13) { SOverlayUtils.counter = 0; }
|
||||
}
|
||||
});
|
||||
timer.start();
|
||||
|
||||
return overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* A template overlay with close button, null layout, ready for anything.
|
||||
* @return {@link javax.swing.JPanel}
|
||||
*/
|
||||
public static JPanel genericOverlay() {
|
||||
final JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel();
|
||||
final int w = overlay.getWidth();
|
||||
|
||||
final SkinnedButton btnCloseTopRight = new SkinnedButton("X");
|
||||
btnCloseTopRight.setBounds(w - 25, 10, 15, 15);
|
||||
btnCloseTopRight.setForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT));
|
||||
btnCloseTopRight.setBorder(new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_TEXT)));
|
||||
btnCloseTopRight.setOpaque(false);
|
||||
btnCloseTopRight.setBackground(new Color(0, 0, 0));
|
||||
btnCloseTopRight.setFocusPainted(false);
|
||||
btnCloseTopRight.addActionListener(new ActionListener() { @Override
|
||||
public void actionPerformed(ActionEvent arg0) { SOverlayUtils.hideOverlay(); } });
|
||||
|
||||
overlay.removeAll();
|
||||
overlay.setLayout(null);
|
||||
overlay.add(btnCloseTopRight);
|
||||
|
||||
return overlay;
|
||||
}
|
||||
|
||||
private static boolean _overlayHasFocus;
|
||||
public static boolean overlayHasFocus() {
|
||||
return _overlayHasFocus;
|
||||
}
|
||||
|
||||
private static Component prevFocusOwner;
|
||||
public static void showOverlay() {
|
||||
Singletons.getView().getNavigationBar().setEnabled(false);
|
||||
prevFocusOwner = FocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
|
||||
FOverlay.SINGLETON_INSTANCE.getPanel().setVisible(true);
|
||||
// ensure no background element has focus
|
||||
FOverlay.SINGLETON_INSTANCE.getPanel().requestFocusInWindow();
|
||||
_overlayHasFocus = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes child components and closes overlay.
|
||||
*/
|
||||
public static void hideOverlay() {
|
||||
Singletons.getView().getNavigationBar().setEnabled(true);
|
||||
FOverlay.SINGLETON_INSTANCE.getPanel().removeAll();
|
||||
FOverlay.SINGLETON_INSTANCE.getPanel().setVisible(false);
|
||||
if (null != prevFocusOwner) {
|
||||
prevFocusOwner.requestFocusInWindow();
|
||||
prevFocusOwner = null;
|
||||
}
|
||||
_overlayHasFocus = false;
|
||||
}
|
||||
|
||||
public static void showTargetingOverlay() {
|
||||
TargetingOverlay.SINGLETON_INSTANCE.getPanel().setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes child components and closes overlay.
|
||||
*/
|
||||
public static void hideTargetingOverlay() {
|
||||
TargetingOverlay.SINGLETON_INSTANCE.getPanel().setVisible(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package forge.gui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class UnsortedListModel<T> extends AbstractListModel<T> {
|
||||
List<T> model;
|
||||
|
||||
public UnsortedListModel() {
|
||||
model = new ArrayList<T>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return model.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getElementAt(int index) {
|
||||
return model.get(index);
|
||||
}
|
||||
|
||||
public void add(T element) {
|
||||
model.add(element);
|
||||
fireIntervalAdded(this, getSize() - 1, getSize() - 1);
|
||||
fireContentsChanged(this, 0, getSize() - 1);
|
||||
}
|
||||
|
||||
public void addAll(T[] elements) {
|
||||
for (T e : elements) {
|
||||
model.add(e);
|
||||
}
|
||||
fireIntervalAdded(this, getSize() - elements.length, getSize() - 1);
|
||||
fireContentsChanged(this, 0, getSize() - 1);
|
||||
}
|
||||
|
||||
public void addAll(Collection<T> elements) {
|
||||
if (elements.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
model.addAll(elements);
|
||||
fireIntervalAdded(this, getSize() - elements.size(), getSize() - 1);
|
||||
fireContentsChanged(this, 0, getSize() - 1);
|
||||
}
|
||||
|
||||
public void addAll(ListModel<T> otherModel) {
|
||||
Collection<T> elements = new ArrayList<T>();
|
||||
int size = otherModel.getSize();
|
||||
for (int i = 0; size > i; ++i) {
|
||||
elements.add((T)otherModel.getElementAt(i));
|
||||
}
|
||||
addAll(elements);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
int prevSize = getSize();
|
||||
model.clear();
|
||||
fireIntervalRemoved(this, 0, prevSize - 1);
|
||||
fireContentsChanged(this, 0, prevSize - 1);
|
||||
}
|
||||
|
||||
public boolean contains(Object element) {
|
||||
return model.contains(element);
|
||||
}
|
||||
|
||||
public Iterator<T> iterator() {
|
||||
return model.iterator();
|
||||
}
|
||||
|
||||
public void removeElement(int idx) {
|
||||
model.remove(idx);
|
||||
fireIntervalRemoved(this, idx, idx);
|
||||
fireContentsChanged(this, 0, getSize());
|
||||
}
|
||||
}
|
||||
163
forge-gui-desktop/src/main/java/forge/gui/WrapLayout.java
Normal file
163
forge-gui-desktop/src/main/java/forge/gui/WrapLayout.java
Normal file
@@ -0,0 +1,163 @@
|
||||
package forge.gui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* FlowLayout subclass that fully supports wrapping of components.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class WrapLayout extends FlowLayout {
|
||||
/**
|
||||
* Constructs a new <code>WrapLayout</code> with a left
|
||||
* alignment and a default 5-unit horizontal and vertical gap.
|
||||
*/
|
||||
public WrapLayout() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <code>FlowLayout</code> with the specified
|
||||
* alignment and a default 5-unit horizontal and vertical gap.
|
||||
* The value of the alignment argument must be one of
|
||||
* <code>FlowLayout.LEFT</code>, <code>FlowLayout.CENTER</code>,
|
||||
* or <code>FlowLayout.RIGHT</code>.
|
||||
* @param align the alignment value
|
||||
*/
|
||||
public WrapLayout(int align) {
|
||||
super(align);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new flow layout manager with the indicated alignment
|
||||
* and the indicated horizontal and vertical gaps.
|
||||
* <p>
|
||||
* The value of the alignment argument must be one of
|
||||
* <code>FlowLayout.LEFT</code>, <code>FlowLayout.CENTER</code>,
|
||||
* or <code>FlowLayout.RIGHT</code>.
|
||||
* @param align the alignment value
|
||||
* @param hgap the horizontal gap between components
|
||||
* @param vgap the vertical gap between components
|
||||
*/
|
||||
public WrapLayout(int align, int hgap, int vgap) {
|
||||
super(align, hgap, vgap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preferred dimensions for this layout given the
|
||||
* <i>visible</i> components in the specified target container.
|
||||
* @param target the component which needs to be laid out
|
||||
* @return the preferred dimensions to lay out the
|
||||
* subcomponents of the specified container
|
||||
*/
|
||||
@Override
|
||||
public Dimension preferredLayoutSize(Container target) {
|
||||
return layoutSize(target, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum dimensions needed to layout the <i>visible</i>
|
||||
* components contained in the specified target container.
|
||||
* @param target the component which needs to be laid out
|
||||
* @return the minimum dimensions to lay out the
|
||||
* subcomponents of the specified container
|
||||
*/
|
||||
@Override
|
||||
public Dimension minimumLayoutSize(Container target) {
|
||||
Dimension minimum = layoutSize(target, false);
|
||||
minimum.width -= (getHgap() + 1);
|
||||
return minimum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum or preferred dimension needed to layout the target
|
||||
* container.
|
||||
*
|
||||
* @param target target to get layout size for
|
||||
* @param preferred should preferred size be calculated
|
||||
* @return the dimension to layout the target container
|
||||
*/
|
||||
private Dimension layoutSize(Container target, boolean preferred) {
|
||||
synchronized (target.getTreeLock()) {
|
||||
// Each row must fit with the width allocated to the container.
|
||||
// When the container width = 0, the preferred width of the container
|
||||
// has not yet been calculated so we use a width guaranteed to be less
|
||||
// than we need so that it gets recalculated later when the widget is
|
||||
// shown.
|
||||
|
||||
int hgap = getHgap();
|
||||
Insets insets = target.getInsets();
|
||||
int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
|
||||
int targetWidth = Math.max(horizontalInsetsAndGap, target.getSize().width);
|
||||
int maxWidth = targetWidth - horizontalInsetsAndGap;
|
||||
|
||||
// Fit components into the allowed width
|
||||
Dimension dim = new Dimension(0, 0);
|
||||
int rowWidth = 0;
|
||||
int rowHeight = 0;
|
||||
|
||||
final int nmembers = target.getComponentCount();
|
||||
for (int i = 0; i < nmembers; i++) {
|
||||
Component m = target.getComponent(i);
|
||||
|
||||
if (!m.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
|
||||
|
||||
// can't add the component to current row. Start a new row if
|
||||
// there's at least one component in this row.
|
||||
if (0 < rowWidth && rowWidth + d.width > maxWidth) {
|
||||
addRow(dim, rowWidth, rowHeight);
|
||||
rowWidth = 0;
|
||||
rowHeight = 0;
|
||||
}
|
||||
|
||||
// Add a horizontal gap for all components after the first
|
||||
if (rowWidth != 0) {
|
||||
rowWidth += hgap;
|
||||
}
|
||||
|
||||
rowWidth += d.width;
|
||||
rowHeight = Math.max(rowHeight, d.height);
|
||||
}
|
||||
|
||||
// add last row
|
||||
addRow(dim, rowWidth, rowHeight);
|
||||
|
||||
dim.width += horizontalInsetsAndGap;
|
||||
dim.height += insets.top + insets.bottom + getVgap() * 2;
|
||||
|
||||
// When using a scroll pane or the DecoratedLookAndFeel we need to
|
||||
// make sure the preferred size is less than the size of the
|
||||
// target container so shrinking the container size works
|
||||
// correctly. Removing the horizontal gap is an easy way to do this.
|
||||
|
||||
Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
|
||||
|
||||
if (scrollPane != null) {
|
||||
dim.width -= (hgap + 1);
|
||||
}
|
||||
|
||||
return dim;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A new row has been completed. Use the dimensions of this row
|
||||
* to update the preferred size for the container.
|
||||
*
|
||||
* @param dim update the width and height when appropriate
|
||||
* @param rowWidth the width of the row to add
|
||||
* @param rowHeight the height of the row to add
|
||||
*/
|
||||
private void addRow(Dimension dim, int rowWidth, int rowHeight) {
|
||||
dim.width = Math.max(dim.width, rowWidth);
|
||||
|
||||
if (dim.height > 0) {
|
||||
dim.height += getVgap();
|
||||
}
|
||||
dim.height += rowHeight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.UiCommand;
|
||||
|
||||
/**
|
||||
* An intentionally empty ICDoc to fill field slots unused
|
||||
* by the current layout of a match UI.
|
||||
*/
|
||||
public class CEmptyDoc implements ICDoc {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.framework.ICDoc#getCommandOnSelect()
|
||||
*/
|
||||
@Override
|
||||
public UiCommand getCommandOnSelect() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.framework.ICDoc#initialize()
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.framework.ICDoc#update()
|
||||
*/
|
||||
@Override
|
||||
public void update() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.model.FModel;
|
||||
import forge.properties.ForgePreferences;
|
||||
import forge.properties.ForgePreferences.FPref;
|
||||
import forge.toolbox.FPanel;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinImage;
|
||||
import forge.view.FView;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Top-level container in drag layout. A cell holds
|
||||
* tabs, a drag handle, and a tab overflow selector.
|
||||
* <br>A cell also has two borders, right and bottom,
|
||||
* for resizing.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public final class DragCell extends JPanel implements ILocalRepaint {
|
||||
// Layout creation worker vars
|
||||
private RectangleOfDouble roughSize;
|
||||
private int smoothX = 0;
|
||||
private int smoothY = 0;
|
||||
private int smoothW = 0;
|
||||
private int smoothH = 0;
|
||||
|
||||
// Core layout stuff
|
||||
private final JPanel pnlHead = new JPanel(new MigLayout("insets 0, gap 0, hidemode 3"));
|
||||
private final FPanel pnlBody = new FPanel();
|
||||
private final JPanel pnlBorderRight = new JPanel();
|
||||
private final JPanel pnlBorderBottom = new JPanel();
|
||||
private final int tabPaddingPx = 2;
|
||||
private final int margin = 2 * tabPaddingPx;
|
||||
|
||||
// Tab handling layout stuff
|
||||
private final List<IVDoc<? extends ICDoc>> allDocs = new ArrayList<IVDoc<? extends ICDoc>>();
|
||||
private final JLabel lblHandle = new DragHandle();
|
||||
private final JLabel lblOverflow = new JLabel();
|
||||
private IVDoc<? extends ICDoc> docSelected = null;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public DragCell() {
|
||||
super(new MigLayout("insets 0, gap 0, wrap 2"));
|
||||
|
||||
this.setOpaque(false);
|
||||
pnlHead.setOpaque(false);
|
||||
|
||||
pnlHead.setBackground(Color.DARK_GRAY);
|
||||
|
||||
lblOverflow.setForeground(Color.white);
|
||||
lblOverflow.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
lblOverflow.setHorizontalTextPosition(SwingConstants.CENTER);
|
||||
lblOverflow.setFont(new Font(Font.DIALOG, Font.PLAIN, 10));
|
||||
lblOverflow.setOpaque(true);
|
||||
lblOverflow.setBackground(Color.black);
|
||||
lblOverflow.setToolTipText("Other tabs");
|
||||
|
||||
pnlBorderRight.setOpaque(false);
|
||||
pnlBorderRight.addMouseListener(SResizingUtil.getResizeXListener());
|
||||
pnlBorderRight.addMouseMotionListener(SResizingUtil.getDragXListener());
|
||||
|
||||
pnlBorderBottom.setOpaque(false);
|
||||
pnlBorderBottom.addMouseListener(SResizingUtil.getResizeYListener());
|
||||
pnlBorderBottom.addMouseMotionListener(SResizingUtil.getDragYListener());
|
||||
|
||||
lblOverflow.addMouseListener(SOverflowUtil.getOverflowListener());
|
||||
|
||||
pnlHead.add(lblHandle, "pushx, growx, h 100%!, gap " + tabPaddingPx + "px " + tabPaddingPx + "px 0 0", -1);
|
||||
pnlHead.add(lblOverflow, "w 20px!, h 100%!, gap " + tabPaddingPx + "px " + tabPaddingPx + "px 0 0", -1);
|
||||
|
||||
pnlBody.setCornerDiameter(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the cell layout without affecting contents.
|
||||
* <p>
|
||||
* Primarily used to toggle visibility of tabs.
|
||||
*/
|
||||
public void doCellLayout(boolean showTabs) {
|
||||
this.removeAll();
|
||||
int borderT = SLayoutConstants.BORDER_T;
|
||||
int headH = ((showTabs || allDocs.size() > 1) ? SLayoutConstants.HEAD_H : 0);
|
||||
this.add(pnlHead,
|
||||
"w 100% - " + borderT + "px!" + ", " + "h " + headH + "px!");
|
||||
this.add(pnlBorderRight,
|
||||
"w " + borderT + "px!" + ", " + "h 100% - " + borderT + "px!, span 1 2");
|
||||
this.add(pnlBody,
|
||||
"w 100% - " + borderT + "px!" + ", " + "h 100% - " + (headH + borderT) + "px!");
|
||||
this.add(pnlBorderBottom,
|
||||
"w 100% - " + borderT + "px!" + ", " + "h " + borderT + "px!");
|
||||
if (this.isShowing()) {
|
||||
this.validate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines visibility of tabs on game screen.
|
||||
*/
|
||||
private boolean showGameTabs() {
|
||||
ForgePreferences prefs = FModel.getPreferences();
|
||||
return !prefs.getPrefBoolean(FPref.UI_HIDE_GAME_TABS);
|
||||
}
|
||||
|
||||
/** @return {@link javax.swing.JPanel} */
|
||||
public JPanel getHead() {
|
||||
return DragCell.this.pnlHead;
|
||||
}
|
||||
|
||||
/** @return {@link javax.swing.JPanel} */
|
||||
public JPanel getBody() {
|
||||
return DragCell.this.pnlBody;
|
||||
}
|
||||
|
||||
/** @return {@link javax.swing.JPanel} */
|
||||
public JPanel getBorderRight() {
|
||||
return DragCell.this.pnlBorderRight;
|
||||
}
|
||||
|
||||
/** @return {@link javax.swing.JPanel} */
|
||||
public JPanel getBorderBottom() {
|
||||
return DragCell.this.pnlBorderBottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a defensive copy list of all documents in this cell.
|
||||
* @return {@link java.util.List}<{@link forge.gui.framework.IVDoc}>
|
||||
*/
|
||||
public List<IVDoc<? extends ICDoc>> getDocs() {
|
||||
return Lists.newArrayList(allDocs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void repaintSelf() {
|
||||
final Dimension d = DragCell.this.getSize();
|
||||
repaint(0, 0, d.width, d.height);
|
||||
}
|
||||
|
||||
/** @return int */
|
||||
public int getW() {
|
||||
return this.getWidth();
|
||||
}
|
||||
|
||||
/** @return int */
|
||||
public int getH() {
|
||||
return this.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen location of left edge of cell.
|
||||
* @return int
|
||||
*/
|
||||
public int getAbsX() {
|
||||
int i = 0;
|
||||
|
||||
try { i = (int) this.getLocationOnScreen().getX(); }
|
||||
catch (final Exception e) { }
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen location of right edge of cell.
|
||||
* @return int
|
||||
*/
|
||||
public int getAbsX2() {
|
||||
int i = 0;
|
||||
|
||||
try { i = this.getAbsX() + this.getW(); }
|
||||
catch (final Exception e) { }
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen location of upper edge of cell.
|
||||
* @return int
|
||||
*/
|
||||
public int getAbsY() {
|
||||
int i = 0;
|
||||
|
||||
try { i = (int) this.getLocationOnScreen().getY(); }
|
||||
catch (final Exception e) { }
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen location of lower edge of cell.
|
||||
* @return int
|
||||
*/
|
||||
public int getAbsY2() {
|
||||
int i = 0;
|
||||
|
||||
try { i = this.getAbsY() + this.getH(); }
|
||||
catch (final Exception e) { }
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically calculates rough bounds of this cell.
|
||||
* @param rectangleOfDouble
|
||||
*/
|
||||
public void updateRoughBounds() {
|
||||
final double contentW = FView.SINGLETON_INSTANCE.getPnlContent().getWidth();
|
||||
final double contentH = FView.SINGLETON_INSTANCE.getPnlContent().getHeight();
|
||||
|
||||
this.roughSize = new RectangleOfDouble(this.getX() / contentW, this.getY() / contentH,
|
||||
this.getW() / contentW, this.getH() / contentH);
|
||||
}
|
||||
|
||||
/** Explicitly sets percent bounds of this cell. Will be smoothed
|
||||
* later to avoid pixel rounding errors.
|
||||
* @param x0   double
|
||||
* @param y0   double
|
||||
* @param w0   double
|
||||
* @param h0   double
|
||||
*/
|
||||
public void setRoughBounds(RectangleOfDouble rectangleOfDouble) {
|
||||
this.roughSize = rectangleOfDouble;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this method.
|
||||
* @return
|
||||
*/
|
||||
public RectangleOfDouble getRoughBounds() {
|
||||
return roughSize;
|
||||
}
|
||||
|
||||
/** Sets bounds in superclass using smoothed values from this class. */
|
||||
public void setSmoothBounds() {
|
||||
super.setBounds(smoothX, smoothY, smoothW, smoothH);
|
||||
}
|
||||
|
||||
/** @param x0   int */
|
||||
public void setSmoothX(final int x0) {
|
||||
this.smoothX = x0;
|
||||
}
|
||||
|
||||
/** @param y0   int */
|
||||
public void setSmoothY(final int y0) {
|
||||
this.smoothY = y0;
|
||||
}
|
||||
|
||||
/** @param w0   int */
|
||||
public void setSmoothW(final int w0) {
|
||||
this.smoothW = w0;
|
||||
}
|
||||
|
||||
/** @param h0   int */
|
||||
public void setSmoothH(final int h0) {
|
||||
this.smoothH = h0;
|
||||
}
|
||||
|
||||
/** Adds a document to the tabs.
|
||||
* @param doc0   {@link forge.gui.framework.IVDoc} */
|
||||
public void addDoc(final IVDoc<? extends ICDoc> doc0) {
|
||||
if (doc0 instanceof VEmptyDoc) { return; }
|
||||
allDocs.add(doc0);
|
||||
doc0.setParentCell(this);
|
||||
pnlHead.add(doc0.getTabLabel(), "h 100%!, gap " + tabPaddingPx + "px " + tabPaddingPx + "px 0 0", allDocs.size() - 1);
|
||||
|
||||
// Ensure that a tab is selected
|
||||
setSelected(getSelected());
|
||||
|
||||
doCellLayout(showGameTabs());
|
||||
}
|
||||
|
||||
/** Removes a document from the layout and tabs.
|
||||
* @param doc0   {@link forge.gui.framework.IVDoc} */
|
||||
public void removeDoc(final IVDoc<? extends ICDoc> doc0) {
|
||||
boolean wasSelected = (docSelected == doc0);
|
||||
allDocs.remove(doc0);
|
||||
pnlHead.remove(doc0.getTabLabel());
|
||||
if (wasSelected) { //after removing selected doc, select most recent doc if possible
|
||||
setSelected(null);
|
||||
}
|
||||
|
||||
doCellLayout(showGameTabs());
|
||||
}
|
||||
|
||||
/** - Deselects previous selection, if there is one<br>
|
||||
* - Decrements the priorities of all other tabs<br>
|
||||
* - Sets selected as priority 1<br>
|
||||
*
|
||||
* <br><b>null</b> will reset
|
||||
* (deselect all tabs, and then select the first in the group).
|
||||
*
|
||||
* <br><br>Unless there are no tab docs in this cell, there
|
||||
* will always be a selection.
|
||||
*
|
||||
* @param doc0   {@link forge.gui.framework.IVDoc} tab document.
|
||||
*/
|
||||
public void setSelected(final IVDoc<? extends ICDoc> doc0) {
|
||||
if (null != doc0 && docSelected == doc0) {
|
||||
// already selected
|
||||
return;
|
||||
}
|
||||
|
||||
docSelected = null;
|
||||
pnlBody.removeAll();
|
||||
|
||||
// Priorities are used to "remember" tab selection history.
|
||||
for (final IVDoc<? extends ICDoc> doc : allDocs) {
|
||||
if (doc.equals(doc0)) {
|
||||
docSelected = doc0;
|
||||
doc.getTabLabel().priorityOne();
|
||||
doc.getTabLabel().setSelected(true);
|
||||
doc.populate();
|
||||
doc.getLayoutControl().update();
|
||||
}
|
||||
else {
|
||||
doc.getTabLabel().setSelected(false);
|
||||
doc.getTabLabel().priorityDecrease();
|
||||
}
|
||||
}
|
||||
|
||||
pnlBody.revalidate();
|
||||
pnlBody.repaint();
|
||||
|
||||
// Reached the end without a selection? Select the first in the group.
|
||||
if (docSelected == null && allDocs.size() > 0) { setSelected(allDocs.get(0)); }
|
||||
}
|
||||
|
||||
/** Returns currently selected document in this cell.
|
||||
* @return {@link forge.gui.framework.IVDoc} */
|
||||
public IVDoc<? extends ICDoc> getSelected() {
|
||||
return docSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable resize on the X axis for this cell.
|
||||
*
|
||||
* @param enable0   boolean
|
||||
*/
|
||||
public void toggleResizeX(final boolean enable0) {
|
||||
this.removeMouseListener(SResizingUtil.getResizeXListener());
|
||||
|
||||
if (enable0) {
|
||||
this.addMouseListener(SResizingUtil.getResizeXListener());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable resize on the Y axis for this cell.
|
||||
*
|
||||
* @param enable0   boolean
|
||||
*/
|
||||
public void toggleResizeY(final boolean enable0) {
|
||||
this.removeMouseListener(SResizingUtil.getResizeYListener());
|
||||
|
||||
if (enable0) {
|
||||
this.addMouseListener(SResizingUtil.getResizeYListener());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes visual display of head bar.
|
||||
*/
|
||||
public void refresh() {
|
||||
final int headW = pnlHead.getWidth();
|
||||
if (docSelected == null) { return; }
|
||||
if (headW <= 0) { return; }
|
||||
if (allDocs.isEmpty()) { return; }
|
||||
|
||||
// Order tabs by priority
|
||||
final List<DragTab> priority = new ArrayList<DragTab>();
|
||||
final DragTab selectedTab = docSelected.getTabLabel();
|
||||
DragTab nextTab = selectedTab;
|
||||
|
||||
while (nextTab != null) {
|
||||
priority.add(nextTab);
|
||||
nextTab = getNextTabInPriority(nextTab.getPriority());
|
||||
}
|
||||
|
||||
// Like Einstein's cosmological constant, the extra "8" here
|
||||
// makes the whole thing work, but the reason for its existence is unknown.
|
||||
// Part of it is 4px of padding from lblHandle...
|
||||
int tempW = lblOverflow.getWidth() + margin + 8;
|
||||
int docOverflowCounter = 0;
|
||||
|
||||
// Hide/show all other tabs.
|
||||
for (final DragTab tab : priority) {
|
||||
tempW += tab.getWidth() + margin;
|
||||
tab.setVisible(false);
|
||||
tab.setMaximumSize(null);
|
||||
|
||||
if (tab.equals(selectedTab) || tempW < headW) { tab.setVisible(true); }
|
||||
else { docOverflowCounter++; }
|
||||
}
|
||||
|
||||
// Resize selected tab if necessary.
|
||||
tempW = (docOverflowCounter == 0 ? headW - margin : headW - lblOverflow.getWidth() - margin - 10);
|
||||
selectedTab.setMaximumSize(new Dimension(tempW, 20));
|
||||
|
||||
// Update overflow label
|
||||
lblOverflow.setText("+" + docOverflowCounter);
|
||||
if (docOverflowCounter == 0) { lblOverflow.setVisible(false); }
|
||||
else { lblOverflow.setVisible(true); }
|
||||
}
|
||||
|
||||
private DragTab getNextTabInPriority(final int currentPriority0) {
|
||||
DragTab neo = null;
|
||||
DragTab temp;
|
||||
int lowest = Integer.MAX_VALUE;
|
||||
|
||||
for (final IVDoc<? extends ICDoc> d : allDocs) {
|
||||
temp = d.getTabLabel();
|
||||
|
||||
// This line prevents two tabs from having the same priority.
|
||||
if (neo != null && temp.getPriority() == lowest) {
|
||||
temp.priorityDecrease();
|
||||
}
|
||||
|
||||
if (d.equals(docSelected)) { continue; }
|
||||
if (temp.getPriority() > lowest) { continue; }
|
||||
if (temp.getPriority() <= currentPriority0) { continue; }
|
||||
|
||||
// If he's The One, he's made it through the tests.
|
||||
lowest = temp.getPriority();
|
||||
neo = temp;
|
||||
}
|
||||
|
||||
return neo;
|
||||
}
|
||||
|
||||
/** Paints dragging handle image the length of the label. */
|
||||
private class DragHandle extends JLabel {
|
||||
private final SkinImage img = FSkin.getImage(FSkinProp.IMG_HANDLE);
|
||||
private boolean hovered = false;
|
||||
|
||||
public DragHandle() {
|
||||
this.addMouseListener(SRearrangingUtil.getRearrangeClickEvent());
|
||||
this.addMouseMotionListener(SRearrangingUtil.getRearrangeDragEvent());
|
||||
|
||||
this.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(final MouseEvent e) {
|
||||
hovered = true; repaintSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(final MouseEvent e) {
|
||||
hovered = false; repaintSelf();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(final Graphics g) {
|
||||
super.paintComponent(g);
|
||||
if (!hovered) { return; }
|
||||
|
||||
final Dimension imgSize = img.getSizeForPaint(g);
|
||||
final int imgW = imgSize.width;
|
||||
if (imgW < 1) { return; }
|
||||
final int imgH = imgSize.height;
|
||||
|
||||
for (int x = 0; x < getWidth(); x += imgW) {
|
||||
FSkin.drawImage(g, img, x, ((getHeight() - imgH) / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinnedLabel;
|
||||
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* The tab label object in drag layout.
|
||||
* No modification should be necessary to this object.
|
||||
* Simply call the constructor with a title string argument.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public final class DragTab extends SkinnedLabel implements ILocalRepaint {
|
||||
private boolean selected = false;
|
||||
private int priority = 10;
|
||||
|
||||
/**
|
||||
* The tab label object in drag layout.
|
||||
* No modification should be necessary to this object.
|
||||
* Simply call the constructor with a title string argument.
|
||||
*
|
||||
* @param title0   {java.lang.String}
|
||||
*/
|
||||
public DragTab(final String title0) {
|
||||
super(title0);
|
||||
setToolTipText(title0);
|
||||
setOpaque(false);
|
||||
setSelected(false);
|
||||
setBorder(new EmptyBorder(2, 5, 2, 5));
|
||||
this.setForeground(FSkin.getColor(FSkin.Colors.CLR_TEXT));
|
||||
|
||||
this.addMouseListener(SRearrangingUtil.getRearrangeClickEvent());
|
||||
this.addMouseMotionListener(SRearrangingUtil.getRearrangeDragEvent());
|
||||
}
|
||||
|
||||
/** @param isSelected0   boolean */
|
||||
public void setSelected(final boolean isSelected0) {
|
||||
selected = isSelected0;
|
||||
repaintSelf();
|
||||
}
|
||||
|
||||
/** Decreases display priority of this tab in relation to its siblings in an overflow case. */
|
||||
public void priorityDecrease() {
|
||||
priority++;
|
||||
}
|
||||
|
||||
/** Sets this tab as first to be displayed if siblings overflow. */
|
||||
public void priorityOne() {
|
||||
priority = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns display priority of this tab in relation to its siblings in an overflow case.
|
||||
* @return int
|
||||
*/
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
// There should be no need for this method.
|
||||
@SuppressWarnings("unused")
|
||||
private void setPriority() {
|
||||
// Intentionally empty.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void repaintSelf() {
|
||||
final Dimension d = DragTab.this.getSize();
|
||||
repaint(0, 0, d.width, d.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(final Graphics g) {
|
||||
if (!selected) {
|
||||
FSkin.setGraphicsColor(g, FSkin.getColor(FSkin.Colors.CLR_INACTIVE));
|
||||
g.fillRoundRect(0, 0, getWidth() - 1, getHeight() * 2, 6, 6);
|
||||
FSkin.setGraphicsColor(g, FSkin.getColor(FSkin.Colors.CLR_BORDERS));
|
||||
g.drawRoundRect(0, 0, getWidth() - 1, getHeight() * 2, 6, 6);
|
||||
}
|
||||
else {
|
||||
FSkin.setGraphicsColor(g, FSkin.getColor(FSkin.Colors.CLR_ACTIVE));
|
||||
g.fillRoundRect(0, 0, getWidth() - 1, getHeight() * 2, 6, 6);
|
||||
FSkin.setGraphicsColor(g, FSkin.getColor(FSkin.Colors.CLR_BORDERS));
|
||||
g.drawRoundRect(0, 0, getWidth() - 1, getHeight() * 2, 6, 6);
|
||||
}
|
||||
|
||||
super.paintComponent(g);
|
||||
}
|
||||
}
|
||||
123
forge-gui-desktop/src/main/java/forge/gui/framework/EDocID.java
Normal file
123
forge-gui-desktop/src/main/java/forge/gui/framework/EDocID.java
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.screens.deckeditor.views.*;
|
||||
import forge.screens.home.gauntlet.VSubmenuGauntletBuild;
|
||||
import forge.screens.home.gauntlet.VSubmenuGauntletContests;
|
||||
import forge.screens.home.gauntlet.VSubmenuGauntletLoad;
|
||||
import forge.screens.home.gauntlet.VSubmenuGauntletQuick;
|
||||
import forge.screens.home.quest.*;
|
||||
import forge.screens.home.sanctioned.VSubmenuConstructed;
|
||||
import forge.screens.home.sanctioned.VSubmenuDraft;
|
||||
import forge.screens.home.sanctioned.VSubmenuSealed;
|
||||
import forge.screens.home.settings.VSubmenuAvatars;
|
||||
import forge.screens.home.settings.VSubmenuDownloaders;
|
||||
import forge.screens.home.settings.VSubmenuPreferences;
|
||||
import forge.screens.home.settings.VSubmenuReleaseNotes;
|
||||
import forge.screens.match.views.*;
|
||||
import forge.screens.workshop.views.VCardDesigner;
|
||||
import forge.screens.workshop.views.VCardScript;
|
||||
import forge.screens.workshop.views.VWorkshopCatalog;
|
||||
|
||||
/**
|
||||
* These are the identifiers for tabs found in the drag layout.
|
||||
* These IDs are used in the save XML and card layouts.
|
||||
*
|
||||
* <br><br><i>(E at beginning of class name denotes an enum.)</i>
|
||||
*/
|
||||
public enum EDocID { /** */
|
||||
CARD_PICTURE (VPicture.SINGLETON_INSTANCE), /** */
|
||||
CARD_DETAIL (VDetail.SINGLETON_INSTANCE), /** */
|
||||
CARD_ANTES (VAntes.SINGLETON_INSTANCE), /** */
|
||||
|
||||
EDITOR_ALLDECKS (VAllDecks.SINGLETON_INSTANCE), /** */
|
||||
EDITOR_STATISTICS (VStatistics.SINGLETON_INSTANCE), /** */
|
||||
EDITOR_PROBABILITIES (VProbabilities.SINGLETON_INSTANCE), /** */
|
||||
EDITOR_CATALOG (VCardCatalog.SINGLETON_INSTANCE), /** */
|
||||
EDITOR_CURRENTDECK (VCurrentDeck.SINGLETON_INSTANCE), /** */
|
||||
EDITOR_DECKGEN (VDeckgen.SINGLETON_INSTANCE), /** */
|
||||
|
||||
WORKSHOP_CATALOG (VWorkshopCatalog.SINGLETON_INSTANCE), /** */
|
||||
WORKSHOP_CARDDESIGNER (VCardDesigner.SINGLETON_INSTANCE), /** */
|
||||
WORKSHOP_CARDSCRIPT (VCardScript.SINGLETON_INSTANCE), /** */
|
||||
|
||||
HOME_QUESTCHALLENGES (VSubmenuChallenges.SINGLETON_INSTANCE), /** */
|
||||
HOME_QUESTDUELS (VSubmenuDuels.SINGLETON_INSTANCE), /** */
|
||||
HOME_QUESTDATA (VSubmenuQuestData.SINGLETON_INSTANCE), /** */
|
||||
HOME_QUESTDECKS (VSubmenuQuestDecks.SINGLETON_INSTANCE), /** */
|
||||
HOME_QUESTPREFS (VSubmenuQuestPrefs.SINGLETON_INSTANCE), /** */
|
||||
HOME_GAUNTLETBUILD (VSubmenuGauntletBuild.SINGLETON_INSTANCE), /** */
|
||||
HOME_GAUNTLETLOAD (VSubmenuGauntletLoad.SINGLETON_INSTANCE), /** */
|
||||
HOME_GAUNTLETQUICK (VSubmenuGauntletQuick.SINGLETON_INSTANCE), /** */
|
||||
HOME_GAUNTLETCONTESTS (VSubmenuGauntletContests.SINGLETON_INSTANCE), /** */
|
||||
HOME_PREFERENCES (VSubmenuPreferences.SINGLETON_INSTANCE), /** */
|
||||
HOME_AVATARS (VSubmenuAvatars.SINGLETON_INSTANCE), /** */
|
||||
HOME_UTILITIES (VSubmenuDownloaders.SINGLETON_INSTANCE), /** */
|
||||
HOME_CONSTRUCTED (VSubmenuConstructed.SINGLETON_INSTANCE), /** */
|
||||
HOME_DRAFT (VSubmenuDraft.SINGLETON_INSTANCE), /** */
|
||||
HOME_SEALED (VSubmenuSealed.SINGLETON_INSTANCE), /** */
|
||||
HOME_RELEASE_NOTES (VSubmenuReleaseNotes.SINGLETON_INSTANCE),
|
||||
|
||||
REPORT_MESSAGE (VPrompt.SINGLETON_INSTANCE), /** */
|
||||
REPORT_STACK (VStack.SINGLETON_INSTANCE), /** */
|
||||
REPORT_COMBAT (VCombat.SINGLETON_INSTANCE), /** */
|
||||
REPORT_LOG (VLog.SINGLETON_INSTANCE), /** */
|
||||
REPORT_PLAYERS (VPlayers.SINGLETON_INSTANCE), /** */
|
||||
|
||||
DEV_MODE (VDev.SINGLETON_INSTANCE), /** */
|
||||
BUTTON_DOCK (VDock.SINGLETON_INSTANCE), /** */
|
||||
|
||||
// Non-user battlefields (AI or teammate), use setDoc to register.
|
||||
FIELD_0 (null), /** */
|
||||
FIELD_1 (null), /** */
|
||||
FIELD_2 (null), /** */
|
||||
FIELD_3 (null), /** */
|
||||
FIELD_4 (null), /** */
|
||||
FIELD_5 (null), /** */
|
||||
FIELD_6 (null), /** */
|
||||
FIELD_7 (null), /** */
|
||||
|
||||
// Non-user hands (AI or teammate), use setDoc to register.
|
||||
HAND_0 (null), /** */
|
||||
HAND_1 (null), /** */
|
||||
HAND_2 (null), /** */
|
||||
HAND_3 (null), /** */
|
||||
HAND_4 (null), /** */
|
||||
HAND_5 (null), /** */
|
||||
HAND_6 (null), /** */
|
||||
HAND_7 (null), /** */
|
||||
|
||||
COMMAND_0 (null), /** */
|
||||
COMMAND_1 (null), /** */
|
||||
COMMAND_2 (null), /** */
|
||||
COMMAND_3 (null), /** */
|
||||
COMMAND_4 (null), /** */
|
||||
COMMAND_5 (null), /** */
|
||||
COMMAND_6 (null), /** */
|
||||
COMMAND_7 (null); /** */
|
||||
|
||||
public final static EDocID[] Commands = new EDocID[] {COMMAND_0, COMMAND_1, COMMAND_2, COMMAND_3, COMMAND_4, COMMAND_5, COMMAND_6, COMMAND_7};
|
||||
public final static EDocID[] Fields = new EDocID[] {FIELD_0, FIELD_1, FIELD_2, FIELD_3, FIELD_4, FIELD_5, FIELD_6, FIELD_7};
|
||||
public final static EDocID[] Hands = new EDocID[] {HAND_0, HAND_1, HAND_2, HAND_3, HAND_4, HAND_5, HAND_6, HAND_7};
|
||||
|
||||
// End enum declarations, start enum methods.
|
||||
private IVDoc<? extends ICDoc> vDoc;
|
||||
|
||||
/** @param doc0   {@link forge.gui.framework.IVDoc} */
|
||||
EDocID(final IVDoc<? extends ICDoc> doc0) {
|
||||
this.vDoc = doc0;
|
||||
}
|
||||
|
||||
/** @param doc0   {@link forge.gui.framework.IVDoc} */
|
||||
public void setDoc(final IVDoc<? extends ICDoc> doc0) {
|
||||
this.vDoc = doc0;
|
||||
}
|
||||
|
||||
/** @return {@link forge.gui.framework.IVDoc} */
|
||||
public IVDoc<? extends ICDoc> getDoc() {
|
||||
if (vDoc == null) { throw new NullPointerException("No document found for " + this.name() + "."); }
|
||||
return vDoc;
|
||||
}
|
||||
}
|
||||
217
forge-gui-desktop/src/main/java/forge/gui/framework/FScreen.java
Normal file
217
forge-gui-desktop/src/main/java/forge/gui/framework/FScreen.java
Normal file
@@ -0,0 +1,217 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.Singletons;
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.properties.FileLocation;
|
||||
import forge.properties.ForgeConstants;
|
||||
import forge.screens.bazaar.CBazaarUI;
|
||||
import forge.screens.bazaar.VBazaarUI;
|
||||
import forge.screens.deckeditor.CDeckEditorUI;
|
||||
import forge.screens.deckeditor.VDeckEditorUI;
|
||||
import forge.screens.home.CHomeUI;
|
||||
import forge.screens.home.VHomeUI;
|
||||
import forge.screens.match.CMatchUI;
|
||||
import forge.screens.match.VMatchUI;
|
||||
import forge.screens.workshop.CWorkshopUI;
|
||||
import forge.screens.workshop.VWorkshopUI;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinImage;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Definitions for Forge screens
|
||||
*
|
||||
*/
|
||||
public enum FScreen {
|
||||
HOME_SCREEN(
|
||||
VHomeUI.SINGLETON_INSTANCE,
|
||||
CHomeUI.SINGLETON_INSTANCE,
|
||||
"Home",
|
||||
FSkin.getIcon(FSkinProp.ICO_FAVICON),
|
||||
false,
|
||||
"Exit Forge",
|
||||
null),
|
||||
MATCH_SCREEN(
|
||||
VMatchUI.SINGLETON_INSTANCE,
|
||||
CMatchUI.SINGLETON_INSTANCE,
|
||||
"Game",
|
||||
FSkin.getIcon(FSkinProp.ICO_ALPHASTRIKE), //TODO: Create icon for match screen
|
||||
true,
|
||||
"Concede Game",
|
||||
ForgeConstants.MATCH_LAYOUT_FILE),
|
||||
WORKSHOP_SCREEN(
|
||||
VWorkshopUI.SINGLETON_INSTANCE,
|
||||
CWorkshopUI.SINGLETON_INSTANCE,
|
||||
"Workshop",
|
||||
FSkin.getIcon(FSkinProp.ICO_SETTINGS), //TODO: Create icon for workshop screen
|
||||
false,
|
||||
"Back to Home",
|
||||
ForgeConstants.WORKSHOP_LAYOUT_FILE),
|
||||
DECK_EDITOR_CONSTRUCTED(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Deck Editor",
|
||||
FSkin.getImage(FSkinProp.IMG_PACK),
|
||||
false,
|
||||
"Back to Home",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
DECK_EDITOR_ARCHENEMY(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Scheme Deck Editor",
|
||||
FSkin.getImage(FSkinProp.IMG_PACK),
|
||||
true,
|
||||
"Close Editor",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
DECK_EDITOR_COMMANDER(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Commander Deck Editor",
|
||||
FSkin.getImage(FSkinProp.IMG_PACK),
|
||||
true,
|
||||
"Close Editor",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
DECK_EDITOR_PLANECHASE(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Planar Deck Editor",
|
||||
FSkin.getImage(FSkinProp.IMG_PACK),
|
||||
true,
|
||||
"Close Editor",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
DECK_EDITOR_VANGUARD(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Vanguard Deck Editor",
|
||||
FSkin.getImage(FSkinProp.IMG_PACK),
|
||||
true,
|
||||
"Close Editor",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
DECK_EDITOR_DRAFT(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Draft Deck Editor",
|
||||
FSkin.getImage(FSkinProp.IMG_PACK),
|
||||
true,
|
||||
"Close Editor",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
DECK_EDITOR_SEALED(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Sealed Deck Editor",
|
||||
FSkin.getImage(FSkinProp.IMG_PACK),
|
||||
true,
|
||||
"Close Editor",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
DECK_EDITOR_QUEST(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Quest Deck Editor",
|
||||
FSkin.getImage(FSkinProp.IMG_PACK),
|
||||
true,
|
||||
"Close Editor",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
QUEST_CARD_SHOP(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Spell Shop",
|
||||
FSkin.getIcon(FSkinProp.ICO_QUEST_BOOK),
|
||||
true,
|
||||
"Leave Shop",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
DRAFTING_PROCESS(
|
||||
VDeckEditorUI.SINGLETON_INSTANCE,
|
||||
CDeckEditorUI.SINGLETON_INSTANCE,
|
||||
"Draft",
|
||||
FSkin.getImage(FSkinProp.IMG_ZONE_HAND),
|
||||
true,
|
||||
"Leave Draft",
|
||||
ForgeConstants.EDITOR_LAYOUT_FILE),
|
||||
QUEST_BAZAAR(
|
||||
VBazaarUI.SINGLETON_INSTANCE,
|
||||
CBazaarUI.SINGLETON_INSTANCE,
|
||||
"Bazaar",
|
||||
FSkin.getIcon(FSkinProp.ICO_QUEST_BOTTLES),
|
||||
true,
|
||||
"Leave Bazaar",
|
||||
null);
|
||||
|
||||
private final IVTopLevelUI view;
|
||||
private final ICDoc controller;
|
||||
private final String tabCaption;
|
||||
private final SkinImage tabIcon;
|
||||
private final boolean allowTabClose;
|
||||
private final String closeButtonTooltip;
|
||||
private final FileLocation layoutFile;
|
||||
|
||||
private FScreen(IVTopLevelUI view0, ICDoc controller0, String tabCaption0, SkinImage tabIcon0, boolean allowTabClose0, String closeButtonTooltip0, FileLocation layoutFile0) {
|
||||
this.view = view0;
|
||||
this.controller = controller0;
|
||||
this.tabCaption = tabCaption0;
|
||||
this.tabIcon = tabIcon0;
|
||||
this.allowTabClose = allowTabClose0;
|
||||
this.closeButtonTooltip = closeButtonTooltip0;
|
||||
this.layoutFile = layoutFile0;
|
||||
}
|
||||
|
||||
public IVTopLevelUI getView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
public ICDoc getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
public String getTabCaption() {
|
||||
return tabCaption;
|
||||
}
|
||||
|
||||
public SkinImage getTabIcon() {
|
||||
return tabIcon;
|
||||
}
|
||||
|
||||
public boolean allowTabClose() {
|
||||
return allowTabClose;
|
||||
}
|
||||
|
||||
public String getCloseButtonTooltip() {
|
||||
return closeButtonTooltip;
|
||||
}
|
||||
|
||||
public boolean onSwitching(FScreen toScreen) {
|
||||
return view.onSwitching(this, toScreen);
|
||||
}
|
||||
|
||||
public boolean onClosing() {
|
||||
return view.onClosing(this);
|
||||
}
|
||||
|
||||
public FileLocation getLayoutFile() {
|
||||
return layoutFile;
|
||||
}
|
||||
|
||||
public boolean deleteLayoutFile() {
|
||||
if (layoutFile == null) { return false; }
|
||||
|
||||
try {
|
||||
File file = new File(layoutFile.userPrefLoc);
|
||||
file.delete();
|
||||
return true;
|
||||
}
|
||||
catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
FOptionPane.showErrorDialog("Failed to delete layout file.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void open() {
|
||||
Singletons.getControl().setCurrentScreen(this);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
Singletons.getView().getNavigationBar().closeTab(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.UiCommand;
|
||||
|
||||
/**
|
||||
* Dictates methods required for any controller
|
||||
* of an {@link forge.gui.framework.IVDoc}.
|
||||
*
|
||||
* <br><br><i>(I at beginning of class name denotes an interface.)</i>
|
||||
* <br><i>(C at beginning of class name denotes a controller class.)</i>
|
||||
*/
|
||||
public interface ICDoc {
|
||||
/**
|
||||
* Fires when this controller's view tab is selected.
|
||||
* Since this method is fired when all tabs are first
|
||||
* initialized, be wary of NPEs created by referring to
|
||||
* non-existent components.
|
||||
*
|
||||
* @return {@link forge.UiCommand} */
|
||||
UiCommand getCommandOnSelect();
|
||||
|
||||
/**
|
||||
* This method is called once, after the view singleton has been fully realized
|
||||
* for the first time. It should execute operations which should only
|
||||
* be done once, but require non-null view components.
|
||||
* <br><br>
|
||||
* This method should only be called once, in FView, after singletons are populated.
|
||||
*/
|
||||
void initialize();
|
||||
|
||||
/**
|
||||
* Update whatever content is in the panel.
|
||||
*/
|
||||
void update();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
package forge.gui.framework;
|
||||
|
||||
/**
|
||||
* This interface provides a unifying type to all enums
|
||||
* used as tab identifiers in XML and card layouts.
|
||||
*
|
||||
* <br><br><i>(I at beginning of class name denotes an interface.)</i>
|
||||
*/
|
||||
public interface IDocIdList { }
|
||||
@@ -0,0 +1,15 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
/**
|
||||
* This interface requires a repaintThis() method, which
|
||||
* boost performance by locally repaints a component,
|
||||
* rather than repainting the entire screen.
|
||||
*
|
||||
* <br><br><i>(I at beginning of class name denotes an interface.)</i>
|
||||
*/
|
||||
public interface ILocalRepaint {
|
||||
/** Boosts performance on repaints by locally repainting a component,
|
||||
* rather than repainting the entire screen.
|
||||
*/
|
||||
void repaintSelf();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This interface provides a unifying type to any component
|
||||
* (usually JPanels or JScrollPanes) which could be used as
|
||||
* a tab. A single one of these components is referred to as
|
||||
* a "document" throughout the codebase. The tabs and their
|
||||
* documents are contained in "cells" for resizing and dragging.
|
||||
*
|
||||
* <br><br><i>(I at beginning of class name denotes an interface.)</i>
|
||||
* <br><i>(V at beginning of class name denotes a view class.)</i>
|
||||
*/
|
||||
public interface IVDoc<TCDoc extends ICDoc> {
|
||||
/**
|
||||
* Returns the ID used to identify this tab in save XML and card layouts.
|
||||
*
|
||||
* @return {@link forge.gui.framework.EDocID}
|
||||
*/
|
||||
EDocID getDocumentID();
|
||||
|
||||
/**
|
||||
* Returns tab label object used in title bars.
|
||||
*
|
||||
* @return {@link forge.gui.framework.DragTab}
|
||||
*/
|
||||
DragTab getTabLabel();
|
||||
|
||||
/** Retrieves control object associated with this document.
|
||||
* @return {@link forge.gui.home.ICSubmenu}
|
||||
*/
|
||||
TCDoc getLayoutControl();
|
||||
|
||||
/** Sets the current parent cell of this view,
|
||||
* allowing access to its body and head sections.
|
||||
*
|
||||
* @param cell0   {@link forge.gui.framework.DragCell}
|
||||
*/
|
||||
void setParentCell(DragCell cell0);
|
||||
|
||||
/**
|
||||
* Gets parent cell for this view.
|
||||
*
|
||||
* @return {@link forge.gui.framework.DragCell}
|
||||
*/
|
||||
DragCell getParentCell();
|
||||
|
||||
/**
|
||||
* Targets the drag cell body (use <code>parentCell.getBody()</code>).
|
||||
* Populates panel components, independent of constructor.
|
||||
* Expected to provide a completely fresh layout to the body panel.
|
||||
* <br><br>
|
||||
* The body panel will be empty when this method is called.
|
||||
* However, its layout may need to be redefined as required.
|
||||
* <br><br>
|
||||
* This method basically lays out components
|
||||
* that have been previously instantiated (cached) by the constructor.
|
||||
* Non-cached components can be created dynamically if needed.
|
||||
*/
|
||||
void populate();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
/**
|
||||
* This interface provides a unifying type for all top-level
|
||||
* UI components.
|
||||
*
|
||||
* <br><br><i>(I at beginning of class name denotes an interface.)</i>
|
||||
* <br><i>(V at beginning of class name denotes a view class.)</i>
|
||||
*
|
||||
*/
|
||||
public interface IVTopLevelUI {
|
||||
/** Called during the preload sequence, this method caches
|
||||
* all of the view singletons and component instances,
|
||||
* before any operations are performed on them.
|
||||
* <br><br>
|
||||
* Although this is sometimes empty, it's important, since in many cases
|
||||
* non-lazy components must be prepared before each panel is populated.
|
||||
*/
|
||||
void instantiate();
|
||||
|
||||
/**
|
||||
* Removes all children and (re)populates top level content,
|
||||
* independent of constructor. Expected to provide
|
||||
* a completely fresh layout on the component.
|
||||
*/
|
||||
void populate();
|
||||
|
||||
/**
|
||||
* Fires when this view's tab is being switched away from.
|
||||
*
|
||||
* @return true to allow switching away from tab, false otherwise */
|
||||
boolean onSwitching(FScreen fromScreen, FScreen toScreen);
|
||||
|
||||
/**
|
||||
* Fires when this view's tab is closing.
|
||||
*
|
||||
* @return true to allow closing tab, false otherwise */
|
||||
boolean onClosing(FScreen screen);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class InvalidLayoutFileException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 8201703516717298690L;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
/**
|
||||
* Dimensions for a cell, stored as % of available space
|
||||
*
|
||||
*/
|
||||
public class RectangleOfDouble {
|
||||
private final double x;
|
||||
private final double y;
|
||||
private final double w;
|
||||
private final double h;
|
||||
|
||||
|
||||
public RectangleOfDouble(final double x0, final double y0, final double w0, final double h0) {
|
||||
if (x0 > 1) { throw new IllegalArgumentException("X value greater than 100%!"); }
|
||||
x = x0;
|
||||
if (y0 > 1) { throw new IllegalArgumentException("Y value greater than 100%!"); }
|
||||
y = y0;
|
||||
if (w0 > 1) { throw new IllegalArgumentException("W value greater than 100%!"); }
|
||||
w = w0;
|
||||
if (h0 > 1) { throw new IllegalArgumentException("H value greater than 100%!"); }
|
||||
h = h0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(h);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(w);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(x);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(y);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
RectangleOfDouble other = (RectangleOfDouble) obj;
|
||||
if (Double.doubleToLongBits(h) != Double.doubleToLongBits(other.h))
|
||||
return false;
|
||||
if (Double.doubleToLongBits(w) != Double.doubleToLongBits(other.w))
|
||||
return false;
|
||||
if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x))
|
||||
return false;
|
||||
if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public final double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
public final double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
|
||||
public final double getW() {
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
public final double getH() {
|
||||
return h;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Rectangle @(%f, %f) sz=(%f, %f)", x,y, w,h);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.FThreads;
|
||||
import forge.view.FFrame;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* Experimental static factory for generic operations carried out
|
||||
* onto specific members of the framework. Doublestrike 11-04-12
|
||||
*
|
||||
* <br><br><i>(S at beginning of class name denotes a static factory.)</i>
|
||||
*/
|
||||
public class SDisplayUtil {
|
||||
private static boolean remindIsRunning = false;
|
||||
private static int counter = 0;
|
||||
private static int[] newA = null, newR = null, newG = null, newB = null;
|
||||
private static Timer timer1 = null;
|
||||
|
||||
/** Flashes animation on input panel if play is currently waiting on input.
|
||||
*
|
||||
* @param tab0   {@link java.gui.framework.IVDoc}
|
||||
*/
|
||||
public static void remind(final IVDoc<? extends ICDoc> tab0) {
|
||||
showTab(tab0);
|
||||
final JPanel pnl = tab0.getParentCell().getBody();
|
||||
|
||||
// To adjust, only touch these two values.
|
||||
final int steps = 5; // Number of delays
|
||||
final int delay = 80; // Milliseconds between steps
|
||||
|
||||
if (remindIsRunning) { return; }
|
||||
if (pnl == null) { return; }
|
||||
|
||||
remindIsRunning = true;
|
||||
final int oldR = pnl.getBackground().getRed();
|
||||
final int oldG = pnl.getBackground().getGreen();
|
||||
final int oldB = pnl.getBackground().getBlue();
|
||||
final int oldA = pnl.getBackground().getAlpha();
|
||||
counter = 0;
|
||||
newR = new int[steps];
|
||||
newG = new int[steps];
|
||||
newB = new int[steps];
|
||||
newA = new int[steps];
|
||||
|
||||
for (int i = 0; i < steps; i++) {
|
||||
newR[i] = ((255 - oldR) / steps * i);
|
||||
newG[i] = (oldG / steps * i);
|
||||
newB[i] = (oldB / steps * i);
|
||||
newA[i] = ((255 - oldA) / steps * i);
|
||||
}
|
||||
|
||||
final TimerTask tt = new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
counter++;
|
||||
if (counter != (steps - 1)) {
|
||||
SwingUtilities.invokeLater(new Runnable() { @Override
|
||||
public void run() {
|
||||
int r = newR == null ? oldR : newR[counter];
|
||||
int a = newA == null ? oldA : newR[counter];
|
||||
pnl.setBackground(new Color(r, oldG, oldB, a));
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
SwingUtilities.invokeLater(new Runnable() { @Override
|
||||
public void run() { pnl.setBackground(new Color(oldR, oldG, oldB, oldA)); } });
|
||||
remindIsRunning = false;
|
||||
timer1.cancel();
|
||||
newR = null;
|
||||
newG = null;
|
||||
newB = null;
|
||||
newA = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
timer1 = new Timer();
|
||||
timer1.scheduleAtFixedRate(tt, 0, delay);
|
||||
}
|
||||
|
||||
/** @param tab0   {@link java.gui.framework.IVDoc} */
|
||||
public static void showTab(final IVDoc<? extends ICDoc> tab0) {
|
||||
|
||||
Runnable showTabRoutine = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// FThreads.assertExecutedByEdt(true); - always true
|
||||
Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
|
||||
DragCell dc = tab0.getParentCell();
|
||||
if (dc != null)
|
||||
dc.setSelected(tab0);
|
||||
// set focus back to previous owner, if any
|
||||
if (null != c) {
|
||||
c.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
};
|
||||
FThreads.invokeInEdtLater(showTabRoutine);
|
||||
}
|
||||
|
||||
public static GraphicsDevice getGraphicsDevice(Point point) {
|
||||
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
for (GraphicsDevice gd : env.getScreenDevices()) {
|
||||
if (gd.getDefaultConfiguration().getBounds().contains(point)) {
|
||||
return gd;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static GraphicsDevice getGraphicsDevice(Rectangle rect) {
|
||||
return getGraphicsDevice(new Point(rect.x + (rect.width / 2), rect.y + (rect.height / 2)));
|
||||
}
|
||||
|
||||
public static Rectangle getScreenBoundsForPoint(Point point) {
|
||||
GraphicsDevice gd = getGraphicsDevice(point);
|
||||
if (gd == null) {
|
||||
//return bounds of default monitor if point not on any screen
|
||||
return GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
|
||||
}
|
||||
return gd.getDefaultConfiguration().getBounds();
|
||||
}
|
||||
|
||||
public static Rectangle getScreenMaximizedBounds(Rectangle rect) {
|
||||
GraphicsDevice gd = getGraphicsDevice(rect);
|
||||
if (gd == null) {
|
||||
//return bounds of default monitor if center of rect not on any screen
|
||||
return GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
|
||||
}
|
||||
|
||||
GraphicsConfiguration config = gd.getDefaultConfiguration();
|
||||
Rectangle bounds = config.getBounds();
|
||||
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(config);
|
||||
bounds.x += screenInsets.left;
|
||||
bounds.y += screenInsets.top;
|
||||
bounds.width -= screenInsets.left + screenInsets.right;
|
||||
bounds.height -= screenInsets.top + screenInsets.bottom;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public static boolean setFullScreenWindow(FFrame frame, boolean fullScreen) {
|
||||
return setFullScreenWindow(getGraphicsDevice(frame.getNormalBounds()), frame, fullScreen);
|
||||
}
|
||||
|
||||
public static boolean setFullScreenWindow(Window window, boolean fullScreen) {
|
||||
return setFullScreenWindow(getGraphicsDevice(window.getBounds()), window, fullScreen);
|
||||
}
|
||||
|
||||
private static boolean setFullScreenWindow(GraphicsDevice gd, Window window, boolean fullScreen) {
|
||||
if (gd != null && gd.isFullScreenSupported()) {
|
||||
if (fullScreen) {
|
||||
gd.setFullScreenWindow(window);
|
||||
}
|
||||
else if (gd.getFullScreenWindow() == window) {
|
||||
gd.setFullScreenWindow(null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
/**
|
||||
* Holds static constants used throughout layout.
|
||||
*
|
||||
* <br><br><i>(S at beginning of class name denotes a static factory.)</i>
|
||||
*/
|
||||
public class SLayoutConstants {
|
||||
/** Height of head area in drag panel. */
|
||||
public static final int HEAD_H = 20;
|
||||
|
||||
/** Thickness of resize border in drag panel. */
|
||||
public static final int BORDER_T = 5;
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.Singletons;
|
||||
import forge.properties.FileLocation;
|
||||
import forge.properties.ForgeConstants;
|
||||
import forge.toolbox.FAbsolutePositioner;
|
||||
import forge.util.CollectionSuppliers;
|
||||
import forge.util.ThreadUtil;
|
||||
import forge.util.maps.HashMapOfLists;
|
||||
import forge.util.maps.MapOfLists;
|
||||
import forge.view.FFrame;
|
||||
import forge.view.FView;
|
||||
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.xml.stream.*;
|
||||
import javax.xml.stream.events.Attribute;
|
||||
import javax.xml.stream.events.StartElement;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
||||
/**
|
||||
* Handles layout saving and loading.
|
||||
*
|
||||
* <br><br><i>(S at beginning of class name denotes a static factory.)</i>
|
||||
*/
|
||||
public final class SLayoutIO {
|
||||
private static class Property {
|
||||
public final static String x = "x";
|
||||
public final static String y = "y";
|
||||
public final static String w = "w";
|
||||
public final static String h = "h";
|
||||
public final static String sel = "sel";
|
||||
public final static String doc = "doc";
|
||||
public final static String max = "max";
|
||||
public final static String fs = "fs";
|
||||
}
|
||||
|
||||
private static final XMLEventFactory EF = XMLEventFactory.newInstance();
|
||||
private static final XMLEvent NEWLINE = EF.createDTD("\n");
|
||||
private static final XMLEvent TAB = EF.createDTD("\t");
|
||||
|
||||
private final static AtomicBoolean saveWindowRequested = new AtomicBoolean(false);
|
||||
|
||||
public static void saveWindowLayout() {
|
||||
if (saveWindowRequested.getAndSet(true)) { return; }
|
||||
ThreadUtil.delay(500, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finishSaveWindowLayout();
|
||||
saveWindowRequested.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized static void finishSaveWindowLayout() {
|
||||
final FFrame window = FView.SINGLETON_INSTANCE.getFrame();
|
||||
if (window.isMinimized()) { return; } //don't update saved layout if minimized
|
||||
|
||||
final Rectangle normalBounds = window.getNormalBounds();
|
||||
|
||||
final FileLocation file = ForgeConstants.WINDOW_LAYOUT_FILE;
|
||||
final String fWriteTo = file.userPrefLoc;
|
||||
final XMLOutputFactory out = XMLOutputFactory.newInstance();
|
||||
FileOutputStream fos = null;
|
||||
XMLEventWriter writer = null;
|
||||
try {
|
||||
fos = new FileOutputStream(fWriteTo);
|
||||
writer = out.createXMLEventWriter(fos);
|
||||
|
||||
writer.add(EF.createStartDocument());
|
||||
writer.add(NEWLINE);
|
||||
writer.add(EF.createStartElement("", "", "layout"));
|
||||
writer.add(EF.createAttribute(Property.x, String.valueOf(normalBounds.x)));
|
||||
writer.add(EF.createAttribute(Property.y, String.valueOf(normalBounds.y)));
|
||||
writer.add(EF.createAttribute(Property.w, String.valueOf(normalBounds.width)));
|
||||
writer.add(EF.createAttribute(Property.h, String.valueOf(normalBounds.height)));
|
||||
writer.add(EF.createAttribute(Property.max, window.isMaximized() ? "1" : "0"));
|
||||
writer.add(EF.createAttribute(Property.fs, window.isFullScreen() ? "1" : "0"));
|
||||
writer.add(EF.createEndElement("", "", "layout"));
|
||||
writer.flush();
|
||||
writer.add(EF.createEndDocument());
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
|
||||
e.printStackTrace();
|
||||
} catch (XMLStreamException e) {
|
||||
// TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (writer != null ) {
|
||||
try { writer.close(); } catch (XMLStreamException e) {}
|
||||
}
|
||||
if ( fos != null ) {
|
||||
try { fos.close(); } catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadWindowLayout() {
|
||||
final FFrame window = FView.SINGLETON_INSTANCE.getFrame();
|
||||
final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
|
||||
final FileLocation file = ForgeConstants.WINDOW_LAYOUT_FILE;
|
||||
boolean usedCustomPrefsFile = false;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
File userSetting = new File(file.userPrefLoc);
|
||||
if (userSetting.exists()) {
|
||||
usedCustomPrefsFile = true;
|
||||
fis = new FileInputStream(userSetting);
|
||||
}
|
||||
else {
|
||||
fis = new FileInputStream(file.defaultLoc);
|
||||
}
|
||||
|
||||
XMLEventReader reader = null;
|
||||
try {
|
||||
XMLEvent event;
|
||||
StartElement element;
|
||||
Iterator<?> attributes;
|
||||
Attribute attribute;
|
||||
reader = inputFactory.createXMLEventReader(fis);
|
||||
|
||||
while (reader != null && reader.hasNext()) {
|
||||
event = reader.nextEvent();
|
||||
|
||||
if (event.isStartElement()) {
|
||||
element = event.asStartElement();
|
||||
|
||||
if (element.getName().getLocalPart().equals("layout")) {
|
||||
attributes = element.getAttributes();
|
||||
Dimension minSize = window.getMinimumSize();
|
||||
int x = 0, y = 0, w = minSize.width, h = minSize.height;
|
||||
boolean max = false, fs = false;
|
||||
while (attributes.hasNext()) {
|
||||
attribute = (Attribute) attributes.next();
|
||||
switch (attribute.getName().toString()) {
|
||||
case Property.x: x = Integer.parseInt(attribute.getValue()); break;
|
||||
case Property.y: y = Integer.parseInt(attribute.getValue()); break;
|
||||
case Property.w: w = Integer.parseInt(attribute.getValue()); break;
|
||||
case Property.h: h = Integer.parseInt(attribute.getValue()); break;
|
||||
case Property.max: max = attribute.getValue().equals("1"); break;
|
||||
case Property.fs: fs = attribute.getValue().equals("1"); break;
|
||||
}
|
||||
}
|
||||
|
||||
//ensure the window is accessible
|
||||
int centerX = x + w / 2;
|
||||
int centerY = y + h / 2;
|
||||
Rectangle screenBounds = SDisplayUtil.getScreenBoundsForPoint(new Point(centerX, centerY));
|
||||
if (centerX < screenBounds.x) {
|
||||
x = screenBounds.x;
|
||||
}
|
||||
else if (centerX > screenBounds.x + screenBounds.width) {
|
||||
x = screenBounds.x + screenBounds.width - w;
|
||||
if (x < screenBounds.x) {
|
||||
x = screenBounds.x;
|
||||
}
|
||||
}
|
||||
if (centerY < screenBounds.y) {
|
||||
y = screenBounds.y;
|
||||
}
|
||||
else if (centerY > screenBounds.y + screenBounds.height) {
|
||||
y = screenBounds.y + screenBounds.height - h;
|
||||
if (y < screenBounds.y) {
|
||||
y = screenBounds.y;
|
||||
}
|
||||
}
|
||||
|
||||
window.setWindowLayout(x, y, w, h, max, fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (final Exception e) {
|
||||
try {
|
||||
if (reader != null) { reader.close(); };
|
||||
}
|
||||
catch (final XMLStreamException x) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
e.printStackTrace();
|
||||
if (usedCustomPrefsFile) {
|
||||
throw new InvalidLayoutFileException();
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
if (fis != null ) {
|
||||
try {
|
||||
fis.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final static AtomicBoolean saveRequested = new AtomicBoolean(false);
|
||||
/** Publicly-accessible save method, to neatly handle exception handling.
|
||||
* @param f0 file to save layout to, if null, saves to filePreferred
|
||||
*
|
||||
*
|
||||
*/
|
||||
public static void saveLayout(final File f0) {
|
||||
if( saveRequested.getAndSet(true) ) return;
|
||||
ThreadUtil.delay(100, new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
save(f0);
|
||||
saveRequested.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized static void save(final File f0) {
|
||||
final String fWriteTo;
|
||||
FileLocation file = Singletons.getControl().getCurrentScreen().getLayoutFile();
|
||||
|
||||
if (f0 == null) {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
fWriteTo = file.userPrefLoc;
|
||||
}
|
||||
else {
|
||||
fWriteTo = f0.getPath();
|
||||
}
|
||||
|
||||
final XMLOutputFactory out = XMLOutputFactory.newInstance();
|
||||
FileOutputStream fos = null;
|
||||
XMLEventWriter writer = null;
|
||||
try {
|
||||
fos = new FileOutputStream(fWriteTo);
|
||||
writer = out.createXMLEventWriter(fos);
|
||||
final List<DragCell> cells = FView.SINGLETON_INSTANCE.getDragCells();
|
||||
|
||||
writer.add(EF.createStartDocument());
|
||||
writer.add(NEWLINE);
|
||||
writer.add(EF.createStartElement("", "", "layout"));
|
||||
writer.add(NEWLINE);
|
||||
|
||||
for (final DragCell cell : cells) {
|
||||
cell.updateRoughBounds();
|
||||
RectangleOfDouble bounds = cell.getRoughBounds();
|
||||
|
||||
writer.add(TAB);
|
||||
writer.add(EF.createStartElement("", "", "cell"));
|
||||
writer.add(EF.createAttribute(Property.x, String.valueOf(Math.rint(bounds.getX() * 100000) / 100000)));
|
||||
writer.add(EF.createAttribute(Property.y, String.valueOf(Math.rint(bounds.getY() * 100000) / 100000)));
|
||||
writer.add(EF.createAttribute(Property.w, String.valueOf(Math.rint(bounds.getW() * 100000) / 100000)));
|
||||
writer.add(EF.createAttribute(Property.h, String.valueOf(Math.rint(bounds.getH() * 100000) / 100000)));
|
||||
if (cell.getSelected() != null) {
|
||||
writer.add(EF.createAttribute(Property.sel, cell.getSelected().getDocumentID().toString()));
|
||||
}
|
||||
writer.add(NEWLINE);
|
||||
|
||||
for (final IVDoc<? extends ICDoc> vDoc : cell.getDocs()) {
|
||||
createNode(writer, Property.doc, vDoc.getDocumentID().toString());
|
||||
}
|
||||
|
||||
writer.add(TAB);
|
||||
writer.add(EF.createEndElement("", "", "cell"));
|
||||
writer.add(NEWLINE);
|
||||
}
|
||||
writer.flush();
|
||||
writer.add(EF.createEndDocument());
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
|
||||
e.printStackTrace();
|
||||
} catch (XMLStreamException e) {
|
||||
// TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if ( writer != null )
|
||||
try { writer.close(); } catch (XMLStreamException e) {}
|
||||
|
||||
if ( fos != null )
|
||||
try { fos.close(); } catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadLayout(final File f) {
|
||||
final FView view = FView.SINGLETON_INSTANCE;
|
||||
FAbsolutePositioner.SINGLETON_INSTANCE.hideAll();
|
||||
view.getPnlInsets().removeAll();
|
||||
view.getPnlInsets().setLayout(new BorderLayout());
|
||||
view.getPnlInsets().add(view.getPnlContent(), BorderLayout.CENTER);
|
||||
view.getPnlInsets().setBorder(new EmptyBorder(SLayoutConstants.BORDER_T, SLayoutConstants.BORDER_T, 0, 0));
|
||||
view.removeAllDragCells();
|
||||
|
||||
FileLocation file = Singletons.getControl().getCurrentScreen().getLayoutFile();
|
||||
if (file != null) {
|
||||
// Read a model for new layout
|
||||
MapOfLists<LayoutInfo, EDocID> model = null;
|
||||
boolean usedCustomPrefsFile = false;
|
||||
FileInputStream fis = null;
|
||||
|
||||
try {
|
||||
if (f != null && f.exists()) {
|
||||
fis = new FileInputStream(f);
|
||||
}
|
||||
else {
|
||||
File userSetting = new File(file.userPrefLoc);
|
||||
if (userSetting.exists()) {
|
||||
usedCustomPrefsFile = true;
|
||||
fis = new FileInputStream(userSetting);
|
||||
}
|
||||
else {
|
||||
fis = new FileInputStream(file.defaultLoc);
|
||||
}
|
||||
}
|
||||
|
||||
final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
|
||||
XMLEventReader xer = null;
|
||||
try {
|
||||
xer = inputFactory.createXMLEventReader(fis);
|
||||
model = readLayout(xer);
|
||||
} catch (final Exception e) { // I don't care what happened inside, the layout is wrong
|
||||
try {
|
||||
if (xer != null) { xer.close(); }
|
||||
}
|
||||
catch (final XMLStreamException x) {
|
||||
x.printStackTrace();
|
||||
}
|
||||
e.printStackTrace();
|
||||
if (usedCustomPrefsFile) { // the one we can safely delete
|
||||
throw new InvalidLayoutFileException();
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply new layout
|
||||
for (Entry<LayoutInfo, Collection<EDocID>> kv : model.entrySet()) {
|
||||
LayoutInfo layoutInfo = kv.getKey();
|
||||
DragCell cell = new DragCell();
|
||||
cell.setRoughBounds(layoutInfo.getBounds());
|
||||
FView.SINGLETON_INSTANCE.addDragCell(cell);
|
||||
for(EDocID edoc : kv.getValue()) {
|
||||
try {
|
||||
//System.out.println(String.format("adding doc %s -> %s", edoc, edoc.getDoc()));
|
||||
cell.addDoc(edoc.getDoc());
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
System.err.println("Failed to get doc for " + edoc);
|
||||
}
|
||||
}
|
||||
if (layoutInfo.getSelectedId() != null) {
|
||||
cell.setSelected(layoutInfo.getSelectedId().getDoc());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rough bounds are all in place; resize the window.
|
||||
SResizingUtil.resizeWindow();
|
||||
}
|
||||
|
||||
private static class LayoutInfo
|
||||
{
|
||||
private final RectangleOfDouble bounds;
|
||||
private final EDocID selectedId;
|
||||
|
||||
public LayoutInfo(RectangleOfDouble bounds0, EDocID selectedId0) {
|
||||
this.bounds = bounds0;
|
||||
this.selectedId = selectedId0;
|
||||
}
|
||||
|
||||
public RectangleOfDouble getBounds() {
|
||||
return this.bounds;
|
||||
}
|
||||
|
||||
public EDocID getSelectedId() {
|
||||
return this.selectedId;
|
||||
}
|
||||
}
|
||||
|
||||
private static MapOfLists<LayoutInfo, EDocID> readLayout(final XMLEventReader reader) throws XMLStreamException
|
||||
{
|
||||
XMLEvent event;
|
||||
StartElement element;
|
||||
Iterator<?> attributes;
|
||||
Attribute attribute;
|
||||
EDocID selectedId = null;
|
||||
double x0 = 0, y0 = 0, w0 = 0, h0 = 0;
|
||||
|
||||
MapOfLists<LayoutInfo, EDocID> model = new HashMapOfLists<LayoutInfo, EDocID>(CollectionSuppliers.<EDocID>arrayLists());
|
||||
|
||||
LayoutInfo currentKey = null;
|
||||
while (null != reader && reader.hasNext()) {
|
||||
event = reader.nextEvent();
|
||||
|
||||
if (event.isStartElement()) {
|
||||
element = event.asStartElement();
|
||||
|
||||
if (element.getName().getLocalPart().equals("cell")) {
|
||||
attributes = element.getAttributes();
|
||||
while (attributes.hasNext()) {
|
||||
attribute = (Attribute) attributes.next();
|
||||
String atrName = attribute.getName().toString();
|
||||
|
||||
if (atrName.equals(Property.x)) x0 = Double.parseDouble(attribute.getValue());
|
||||
else if (atrName.equals(Property.y)) y0 = Double.parseDouble(attribute.getValue());
|
||||
else if (atrName.equals(Property.w)) w0 = Double.parseDouble(attribute.getValue());
|
||||
else if (atrName.equals(Property.h)) h0 = Double.parseDouble(attribute.getValue());
|
||||
else if (atrName.equals(Property.sel)) selectedId = EDocID.valueOf(attribute.getValue());
|
||||
}
|
||||
currentKey = new LayoutInfo(new RectangleOfDouble(x0, y0, w0, h0), selectedId);
|
||||
}
|
||||
else if (element.getName().getLocalPart().equals(Property.doc)) {
|
||||
event = reader.nextEvent();
|
||||
model.add(currentKey, EDocID.valueOf(event.asCharacters().getData()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
private static void createNode(final XMLEventWriter writer0, final String propertyName, final String value) throws XMLStreamException {
|
||||
writer0.add(TAB);
|
||||
writer0.add(TAB);
|
||||
writer0.add(EF.createStartElement("", "", propertyName));
|
||||
writer0.add(EF.createCharacters(value));
|
||||
writer0.add(EF.createEndElement("", "", propertyName));
|
||||
writer0.add(NEWLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.view.FView;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
/**
|
||||
* Package-private utilities for generic overflow behavior
|
||||
* in title bar for any cell in layout.
|
||||
*
|
||||
* <br><br><i>(S at beginning of class name denotes a static factory.)</i>
|
||||
*/
|
||||
public final class SOverflowUtil {
|
||||
private static final MouseListener MAD_OVERFLOW_SELECT = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(final MouseEvent e) {
|
||||
final JLabel src = ((JLabel) e.getSource());
|
||||
final DragCell pnlParent = ((DragCell) src.getParent().getParent());
|
||||
final JPanel pnlOverflow = FView.SINGLETON_INSTANCE.getPnlTabOverflow();
|
||||
final String constraints = "w 150px!, h 20px!, gap 5px 5px 2px 2px";
|
||||
final int w = 160;
|
||||
int h = 0;
|
||||
|
||||
pnlOverflow.removeAll();
|
||||
for (final IVDoc<? extends ICDoc> t : pnlParent.getDocs()) {
|
||||
if (t.getTabLabel().isVisible()) { continue; }
|
||||
pnlOverflow.add(new OverflowLabel(t, pnlParent), constraints);
|
||||
h += 24;
|
||||
}
|
||||
|
||||
pnlOverflow.revalidate();
|
||||
pnlOverflow.setVisible(true);
|
||||
|
||||
int x = src.getParent().getParent().getX() + src.getX() + SLayoutConstants.BORDER_T;
|
||||
final int y = src.getParent().getParent().getY() + src.getY() + SLayoutConstants.BORDER_T + src.getHeight() + 3;
|
||||
|
||||
// If overflow will appear offscreen, offset.
|
||||
if (x + w > FView.SINGLETON_INSTANCE.getPnlContent().getWidth()) {
|
||||
x += src.getWidth() - w;
|
||||
}
|
||||
|
||||
pnlOverflow.setBounds(x, y, w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(final MouseEvent e) {
|
||||
((JLabel) e.getSource()).setBackground(Color.cyan);
|
||||
((JLabel) e.getSource()).repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(final MouseEvent e) {
|
||||
((JLabel) e.getSource()).setBackground(Color.black);
|
||||
((JLabel) e.getSource()).repaint();
|
||||
}
|
||||
};
|
||||
|
||||
/** @return {@link java.awt.event.MouseListener} */
|
||||
private static final MouseListener MAD_HIDE_OVERFLOW = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(final MouseEvent e) {
|
||||
final JPanel pnl = FView.SINGLETON_INSTANCE.getPnlTabOverflow();
|
||||
if (pnl != null) {
|
||||
pnl.setVisible(pnl.isVisible() ? false : true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @return {@link java.awt.event.MouseListener} */
|
||||
public static MouseListener getOverflowListener() {
|
||||
return MAD_OVERFLOW_SELECT;
|
||||
}
|
||||
|
||||
/** @return {@link java.awt.event.MouseListener} */
|
||||
public static MouseListener getHideOverflowListener() {
|
||||
return MAD_HIDE_OVERFLOW;
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class OverflowLabel extends JLabel implements ILocalRepaint {
|
||||
public OverflowLabel(final IVDoc<? extends ICDoc> tab0, final DragCell parent0) {
|
||||
super(tab0.getTabLabel().getText());
|
||||
setOpaque(true);
|
||||
setBackground(Color.LIGHT_GRAY);
|
||||
|
||||
this.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(final MouseEvent e) {
|
||||
setBackground(Color.ORANGE);
|
||||
repaintSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(final MouseEvent e) {
|
||||
setBackground(Color.LIGHT_GRAY);
|
||||
repaintSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(final MouseEvent e) {
|
||||
FView.SINGLETON_INSTANCE.getPnlTabOverflow().setVisible(false);
|
||||
parent0.setSelected(tab0);
|
||||
parent0.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void repaintSelf() {
|
||||
final Dimension d = OverflowLabel.this.getSize();
|
||||
repaint(0, 0, d.width, d.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.gui.MouseUtil;
|
||||
import forge.toolbox.FSkin;
|
||||
import forge.toolbox.FSkin.SkinCursor;
|
||||
import forge.toolbox.FSkin.SkinnedLayeredPane;
|
||||
import forge.view.FView;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Package-private utilities for rearranging drag behavior using
|
||||
* the draggable panels registered in FView.
|
||||
*
|
||||
* <br><br><i>(S at beginning of class name denotes a static factory.)</i>
|
||||
*/
|
||||
public final class SRearrangingUtil {
|
||||
|
||||
private enum Dropzone {
|
||||
BODY,
|
||||
RIGHT,
|
||||
NONE,
|
||||
TOP,
|
||||
BOTTOM,
|
||||
LEFT
|
||||
}
|
||||
|
||||
private static int evtX;
|
||||
private static int evtY;
|
||||
|
||||
private static int tempX;
|
||||
private static int tempY;
|
||||
private static int tempW;
|
||||
private static int tempH;
|
||||
|
||||
private static JPanel pnlPreview = FView.SINGLETON_INSTANCE.getPnlPreview();
|
||||
private static SkinnedLayeredPane pnlDocument = FView.SINGLETON_INSTANCE.getLpnDocument();
|
||||
private static DragCell cellTarget = null;
|
||||
private static DragCell cellSrc = null;
|
||||
private static DragCell cellNew = null;
|
||||
private static Dropzone dropzone = Dropzone.NONE;
|
||||
private static List<IVDoc<? extends ICDoc>> docsToMove = new ArrayList<IVDoc<? extends ICDoc>>();
|
||||
private static IVDoc<? extends ICDoc> srcSelectedDoc = null;
|
||||
|
||||
private static final SkinCursor CUR_L = FSkin.getCursor(FSkinProp.IMG_CUR_L, 16, 16, "CUR_L");
|
||||
private static final SkinCursor CUR_T = FSkin.getCursor(FSkinProp.IMG_CUR_T, 16, 16, "CUR_T");
|
||||
private static final SkinCursor CUR_B = FSkin.getCursor(FSkinProp.IMG_CUR_B, 16, 16, "CUR_B");
|
||||
private static final SkinCursor CUR_R = FSkin.getCursor(FSkinProp.IMG_CUR_R, 16, 16, "CUR_R");
|
||||
private static final SkinCursor CUR_TAB = FSkin.getCursor(FSkinProp.IMG_CUR_TAB, 16, 16, "CUR_TAB");
|
||||
|
||||
private static final MouseListener MAD_REARRANGE = new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(final MouseEvent e) {
|
||||
SRearrangingUtil.startRearrange(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(final MouseEvent e) {
|
||||
SRearrangingUtil.endRearrange();
|
||||
}
|
||||
};
|
||||
|
||||
private static final MouseMotionListener MMA_REARRANGE = new MouseMotionAdapter() {
|
||||
@Override
|
||||
public void mouseDragged(final MouseEvent e) {
|
||||
SRearrangingUtil.rearrange(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initiates a rearranging of cells or tabs.<br>
|
||||
* - Sets up source cell<br>
|
||||
* - Determines if it's a tab or a cell being dragged<br>
|
||||
* - Resets preview panel
|
||||
*/
|
||||
private static void startRearrange(final MouseEvent e) {
|
||||
cellSrc = (DragCell) ((Container) e.getSource()).getParent().getParent();
|
||||
docsToMove.clear();
|
||||
dropzone = Dropzone.NONE;
|
||||
|
||||
// Save selected tab in case this tab will be dragged.
|
||||
srcSelectedDoc = cellSrc.getSelected();
|
||||
|
||||
// If only a single tab, select it, and add it to docsToMove.
|
||||
if (e.getSource() instanceof DragTab) {
|
||||
for (final IVDoc<? extends ICDoc> vDoc : cellSrc.getDocs()) {
|
||||
if (vDoc.getTabLabel() == (DragTab) (e.getSource())) {
|
||||
cellSrc.setSelected(vDoc);
|
||||
docsToMove.add(vDoc);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, add all of the documents.
|
||||
else {
|
||||
for (final IVDoc<? extends ICDoc> vDoc : cellSrc.getDocs()) {
|
||||
docsToMove.add(vDoc);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset and show preview panel
|
||||
pnlPreview.setVisible(true);
|
||||
pnlPreview.setBounds(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the mouse during a rearrange drag. Updates preview
|
||||
* panel as necessary, to show the user where their doc
|
||||
* or cell will be dropped. dropzone is set to direct resizing
|
||||
* operations when rearrange is finished [see endRearrange()].
|
||||
*
|
||||
* @param e   {@link java.awt.event.MouseEvent}
|
||||
*/
|
||||
private static void rearrange(final MouseEvent e) {
|
||||
// nestingMargin controls the thickness of the "zones" bordering
|
||||
// the center body.
|
||||
final int nestingMargin = 30;
|
||||
evtX = (int) e.getLocationOnScreen().getX();
|
||||
evtY = (int) e.getLocationOnScreen().getY();
|
||||
|
||||
// Find out over which panel the event occurred.
|
||||
for (final DragCell t : FView.SINGLETON_INSTANCE.getDragCells()) {
|
||||
tempX = t.getAbsX();
|
||||
tempY = t.getAbsY();
|
||||
tempW = t.getW();
|
||||
tempH = t.getH();
|
||||
|
||||
if (evtX < tempX) { continue; } // Left
|
||||
if (evtY < tempY) { continue; } // Top
|
||||
if (evtX > tempX + tempW) { continue; } // Right
|
||||
if (evtY > tempY + tempH) { continue; } // Bottom
|
||||
|
||||
cellTarget = t;
|
||||
break;
|
||||
}
|
||||
|
||||
if (evtX < (tempX + nestingMargin)
|
||||
&& (cellTarget.getW() / 2) > SResizingUtil.W_MIN) {
|
||||
dropzone = Dropzone.LEFT;
|
||||
pnlDocument.setCursor(CUR_L);
|
||||
pnlPreview.setBounds(
|
||||
cellTarget.getX() + SLayoutConstants.BORDER_T,
|
||||
cellTarget.getY() + SLayoutConstants.BORDER_T,
|
||||
((tempW - SLayoutConstants.BORDER_T) / 2),
|
||||
tempH - SLayoutConstants.BORDER_T
|
||||
);
|
||||
|
||||
}
|
||||
else if (evtX > (tempX + tempW - nestingMargin)
|
||||
&& (cellTarget.getW() / 2) > SResizingUtil.W_MIN) {
|
||||
dropzone = Dropzone.RIGHT;
|
||||
pnlDocument.setCursor(CUR_R);
|
||||
tempW = cellTarget.getW() / 2;
|
||||
|
||||
pnlPreview.setBounds(
|
||||
cellTarget.getX() + cellTarget.getW() - tempW,
|
||||
cellTarget.getY() + SLayoutConstants.BORDER_T,
|
||||
tempW,
|
||||
tempH - SLayoutConstants.BORDER_T
|
||||
);
|
||||
}
|
||||
else if (evtY < (tempY + nestingMargin + SLayoutConstants.HEAD_H) && evtY > tempY + SLayoutConstants.HEAD_H
|
||||
&& (cellTarget.getH() / 2) > SResizingUtil.H_MIN) {
|
||||
dropzone = Dropzone.TOP;
|
||||
pnlDocument.setCursor(CUR_T);
|
||||
|
||||
pnlPreview.setBounds(
|
||||
cellTarget.getX() + SLayoutConstants.BORDER_T,
|
||||
cellTarget.getY() + SLayoutConstants.BORDER_T,
|
||||
tempW - SLayoutConstants.BORDER_T,
|
||||
(tempH / 2)
|
||||
);
|
||||
}
|
||||
else if (evtY > (tempY + tempH - nestingMargin)
|
||||
&& (cellTarget.getH() / 2) > SResizingUtil.H_MIN) {
|
||||
dropzone = Dropzone.BOTTOM;
|
||||
pnlDocument.setCursor(CUR_B);
|
||||
tempH = cellTarget.getH() / 2;
|
||||
|
||||
pnlPreview.setBounds(
|
||||
cellTarget.getX() + SLayoutConstants.BORDER_T,
|
||||
cellTarget.getY() + cellTarget.getH() - tempH,
|
||||
tempW - SLayoutConstants.BORDER_T,
|
||||
tempH
|
||||
);
|
||||
}
|
||||
else if (cellTarget.equals(cellSrc)) {
|
||||
dropzone = Dropzone.NONE;
|
||||
MouseUtil.resetCursor();
|
||||
pnlPreview.setBounds(0, 0, 0, 0);
|
||||
}
|
||||
else {
|
||||
dropzone = Dropzone.BODY;
|
||||
pnlDocument.setCursor(CUR_TAB);
|
||||
|
||||
pnlPreview.setBounds(
|
||||
cellTarget.getX() + SLayoutConstants.BORDER_T,
|
||||
cellTarget.getY() + SLayoutConstants.BORDER_T,
|
||||
tempW - SLayoutConstants.BORDER_T,
|
||||
tempH - SLayoutConstants.BORDER_T
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes a drop, using dropzone hint and docsToMove to
|
||||
* transfer docs and remove + resize cells as necessary.
|
||||
*/
|
||||
private static void endRearrange() {
|
||||
// Resize preview panel in preparation for next event.
|
||||
MouseUtil.resetCursor();
|
||||
pnlPreview.setVisible(false);
|
||||
pnlPreview.setBounds(0, 0, 0, 0);
|
||||
|
||||
// Source and target are the same?
|
||||
if (dropzone.equals(Dropzone.NONE) || (cellTarget.equals(cellSrc) && cellSrc.getDocs().size() == 1))
|
||||
{
|
||||
if (srcSelectedDoc != cellSrc.getSelected())
|
||||
{
|
||||
SLayoutIO.saveLayout(null); //still need to save layout if selection changed
|
||||
}
|
||||
srcSelectedDoc = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Prep vals for possible resize
|
||||
tempX = cellTarget.getX();
|
||||
tempY = cellTarget.getY();
|
||||
tempW = cellTarget.getW();
|
||||
tempH = cellTarget.getH();
|
||||
cellNew = new DragCell();
|
||||
|
||||
// Insert a new cell if necessary, change bounds on target as appropriate.
|
||||
switch (dropzone) {
|
||||
case LEFT:
|
||||
cellNew.setBounds(
|
||||
tempX, tempY,
|
||||
tempW / 2, tempH);
|
||||
cellTarget.setBounds(
|
||||
tempX + cellNew.getW(), tempY,
|
||||
tempW - cellNew.getW(), tempH);
|
||||
FView.SINGLETON_INSTANCE.addDragCell(cellNew);
|
||||
break;
|
||||
case RIGHT:
|
||||
cellTarget.setBounds(
|
||||
tempX, tempY,
|
||||
tempW / 2, tempH);
|
||||
cellNew.setBounds(
|
||||
cellTarget.getX() + cellTarget.getW(), tempY ,
|
||||
tempW - cellTarget.getW(), tempH);
|
||||
FView.SINGLETON_INSTANCE.addDragCell(cellNew);
|
||||
break;
|
||||
case TOP:
|
||||
cellNew.setBounds(
|
||||
tempX, tempY,
|
||||
tempW, tempH - (tempH / 2));
|
||||
cellTarget.setBounds(
|
||||
tempX, tempY + cellNew.getH(),
|
||||
tempW, tempH - cellNew.getH());
|
||||
FView.SINGLETON_INSTANCE.addDragCell(cellNew);
|
||||
break;
|
||||
case BOTTOM:
|
||||
cellTarget.setBounds(
|
||||
tempX, tempY,
|
||||
tempW, tempH / 2);
|
||||
|
||||
cellNew.setBounds(
|
||||
tempX, cellTarget.getY() + cellTarget.getH(),
|
||||
tempW, tempH - cellTarget.getH());
|
||||
FView.SINGLETON_INSTANCE.addDragCell(cellNew);
|
||||
break;
|
||||
case BODY:
|
||||
cellNew = cellTarget;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
for (final IVDoc<? extends ICDoc> vDoc : docsToMove) {
|
||||
cellSrc.removeDoc(vDoc);
|
||||
cellNew.addDoc(vDoc);
|
||||
cellNew.setSelected(vDoc);
|
||||
}
|
||||
|
||||
// Remove old cell if necessary, or, enforce rough bounds on new cell.
|
||||
if (cellSrc.getDocs().size() == 0) {
|
||||
fillGap();
|
||||
FView.SINGLETON_INSTANCE.removeDragCell(cellSrc);
|
||||
}
|
||||
|
||||
cellNew.updateRoughBounds();
|
||||
cellTarget.updateRoughBounds();
|
||||
|
||||
cellSrc.setSelected(srcSelectedDoc);
|
||||
srcSelectedDoc = null;
|
||||
cellSrc.refresh();
|
||||
cellTarget.refresh();
|
||||
cellNew.validate();
|
||||
cellNew.refresh();
|
||||
updateBorders();
|
||||
|
||||
SLayoutIO.saveLayout(null);
|
||||
}
|
||||
|
||||
/** The gap created by displaced panels must be filled.
|
||||
* from any side which shares corners with the gap. */
|
||||
private static void fillGap() {
|
||||
// Variables to help with matching the borders
|
||||
final List<DragCell> cellsToResize = new ArrayList<DragCell>();
|
||||
final int srcX = cellSrc.getAbsX();
|
||||
final int srcX2 = cellSrc.getAbsX2();
|
||||
final int srcY = cellSrc.getAbsY();
|
||||
final int srcY2 = cellSrc.getAbsY2();
|
||||
final int srcW = cellSrc.getW();
|
||||
final int srcH = cellSrc.getH();
|
||||
|
||||
// Border check flags
|
||||
boolean foundT = false;
|
||||
boolean foundB = false;
|
||||
boolean foundR = false;
|
||||
boolean foundL = false;
|
||||
|
||||
// Start algorithm
|
||||
cellsToResize.clear();
|
||||
foundT = false;
|
||||
foundB = false;
|
||||
// Look for matching panels to left of source, expand them to the right.
|
||||
for (final DragCell cell : FView.SINGLETON_INSTANCE.getDragCells()) {
|
||||
if (cell.getAbsX2() != srcX) { continue; }
|
||||
|
||||
if (cell.getAbsY() == srcY) {
|
||||
foundT = true;
|
||||
cellsToResize.add(cell);
|
||||
}
|
||||
if (cell.getAbsY2() == srcY2) {
|
||||
foundB = true;
|
||||
if (!cellsToResize.contains(cell)) { cellsToResize.add(cell); }
|
||||
}
|
||||
if (cell.getAbsY() > srcY && cell.getAbsY2() < srcY2) { cellsToResize.add(cell); }
|
||||
}
|
||||
|
||||
if (foundT && foundB) {
|
||||
for (final DragCell cell : cellsToResize) {
|
||||
cell.setBounds(cell.getX(), cell.getY(), cell.getW() + srcW, cell.getH());
|
||||
cell.updateRoughBounds();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cellsToResize.clear();
|
||||
foundT = false;
|
||||
foundB = false;
|
||||
// Look for matching panels to right of source, expand them to the left.
|
||||
for (final DragCell cell : FView.SINGLETON_INSTANCE.getDragCells()) {
|
||||
if (cell.getAbsX() != srcX2) { continue; }
|
||||
|
||||
if (cell.getAbsY() == srcY) {
|
||||
foundT = true;
|
||||
cellsToResize.add(cell);
|
||||
}
|
||||
if (cell.getAbsY2() == srcY2) {
|
||||
foundB = true;
|
||||
if (!cellsToResize.contains(cell)) { cellsToResize.add(cell); }
|
||||
}
|
||||
if (cell.getAbsY() > srcY && cell.getAbsY2() < srcY2) { cellsToResize.add(cell); }
|
||||
}
|
||||
|
||||
if (foundT && foundB) {
|
||||
for (final DragCell cell : cellsToResize) {
|
||||
cell.setBounds(cellSrc.getX(), cell.getY(), cell.getW() + srcW, cell.getH());
|
||||
cell.updateRoughBounds();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cellsToResize.clear();
|
||||
foundL = false;
|
||||
foundR = false;
|
||||
// Look for matching panels below source, expand them upwards.
|
||||
for (final DragCell cell : FView.SINGLETON_INSTANCE.getDragCells()) {
|
||||
if (cell.getAbsY() != srcY2) { continue; }
|
||||
|
||||
if (cell.getAbsX() == srcX) {
|
||||
foundL = true;
|
||||
cellsToResize.add(cell);
|
||||
}
|
||||
if (cell.getAbsX2() == srcX2) {
|
||||
foundR = true;
|
||||
if (!cellsToResize.contains(cell)) { cellsToResize.add(cell); }
|
||||
}
|
||||
if (cell.getAbsX() > srcX && cell.getAbsX2() < srcX2) { cellsToResize.add(cell); }
|
||||
}
|
||||
|
||||
if (foundL && foundR) {
|
||||
for (final DragCell cell : cellsToResize) {
|
||||
cell.setBounds(cell.getX(), cellSrc.getY(), cell.getW(), cell.getH() + srcH);
|
||||
|
||||
cell.updateRoughBounds();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cellsToResize.clear();
|
||||
foundL = false;
|
||||
foundR = false;
|
||||
// Look for matching panels above source, expand them downwards.
|
||||
for (final DragCell cell : FView.SINGLETON_INSTANCE.getDragCells()) {
|
||||
if (cell.getAbsY2() != srcY) { continue; }
|
||||
|
||||
if (cell.getAbsX() == srcX) {
|
||||
foundL = true;
|
||||
cellsToResize.add(cell);
|
||||
}
|
||||
if (cell.getAbsX2() == srcX2) {
|
||||
foundR = true;
|
||||
if (!cellsToResize.contains(cell)) { cellsToResize.add(cell); }
|
||||
}
|
||||
if (cell.getAbsX() > srcX && cell.getAbsX2() < srcX2) { cellsToResize.add(cell); }
|
||||
}
|
||||
|
||||
if (foundL && foundR) {
|
||||
for (final DragCell cell : cellsToResize) {
|
||||
cell.setBounds(cell.getX(), cell.getY(), cell.getW(), cell.getH() + srcH);
|
||||
|
||||
cell.updateRoughBounds();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Gap was not filled.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a gap left by a source cell.
|
||||
* <br><br>
|
||||
* Cell will not be removed, but its coordinates will be filled
|
||||
* by its neighbors.
|
||||
*
|
||||
* @param sourceCell0   {@link forge.gui.framework.DragCell}
|
||||
*/
|
||||
public static void fillGap(final DragCell sourceCell0) {
|
||||
cellSrc = sourceCell0;
|
||||
fillGap();
|
||||
}
|
||||
|
||||
/** Hides outer borders for components on edges,
|
||||
* preventing illegal resizing (and misleading cursor). */
|
||||
public static void updateBorders() {
|
||||
final List<DragCell> cells = FView.SINGLETON_INSTANCE.getDragCells();
|
||||
final JPanel pnlContent = FView.SINGLETON_INSTANCE.getPnlContent();
|
||||
|
||||
for (final DragCell t : cells) {
|
||||
if (t.getAbsX2() == (pnlContent.getLocationOnScreen().getX() + pnlContent.getWidth())) {
|
||||
t.getBorderRight().setVisible(false);
|
||||
}
|
||||
else {
|
||||
t.getBorderRight().setVisible(true);
|
||||
}
|
||||
|
||||
if (t.getAbsY2() == (pnlContent.getLocationOnScreen().getY() + pnlContent.getHeight())) {
|
||||
t.getBorderBottom().setVisible(false);
|
||||
}
|
||||
else {
|
||||
t.getBorderBottom().setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
cells.clear();
|
||||
}
|
||||
|
||||
/** @return {@link java.awt.event.MouseListener} */
|
||||
public static MouseListener getRearrangeClickEvent() {
|
||||
return MAD_REARRANGE;
|
||||
}
|
||||
|
||||
/** @return {@link java.awt.event.MouseMotionListener} */
|
||||
public static MouseMotionListener getRearrangeDragEvent() {
|
||||
return MMA_REARRANGE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,417 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
import forge.gui.FNetOverlay;
|
||||
import forge.gui.MouseUtil;
|
||||
import forge.toolbox.FAbsolutePositioner;
|
||||
import forge.toolbox.FOverlay;
|
||||
import forge.view.FDialog;
|
||||
import forge.view.FFrame;
|
||||
import forge.view.FNavigationBar;
|
||||
import forge.view.FView;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Package-private utilities for resizing drag behavior using
|
||||
* the draggable panels registered in FView.
|
||||
*
|
||||
* <br><br><i>(S at beginning of class name denotes a static factory.)</i>
|
||||
*/
|
||||
public final class SResizingUtil {
|
||||
private static final List<DragCell> LEFT_PANELS = new ArrayList<DragCell>();
|
||||
private static final List<DragCell> RIGHT_PANELS = new ArrayList<DragCell>();
|
||||
private static final List<DragCell> TOP_PANELS = new ArrayList<DragCell>();
|
||||
private static final List<DragCell> BOTTOM_PANELS = new ArrayList<DragCell>();
|
||||
|
||||
private static int dX;
|
||||
private static int evtX;
|
||||
private static int dY;
|
||||
private static int evtY;
|
||||
|
||||
/** Minimum cell width. */
|
||||
public static final int W_MIN = 100;
|
||||
/** Minimum cell height. */
|
||||
public static final int H_MIN = 75;
|
||||
|
||||
private static final MouseListener MAD_RESIZE_X = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(final MouseEvent e) {
|
||||
MouseUtil.setCursor(Cursor.E_RESIZE_CURSOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(final MouseEvent e) {
|
||||
MouseUtil.resetCursor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(final MouseEvent e) {
|
||||
SResizingUtil.startResizeX(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(final MouseEvent e) {
|
||||
SResizingUtil.endResize();
|
||||
}
|
||||
};
|
||||
|
||||
private static final MouseListener MAD_RESIZE_Y = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(final MouseEvent e) {
|
||||
MouseUtil.setCursor(Cursor.N_RESIZE_CURSOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(final MouseEvent e) {
|
||||
MouseUtil.resetCursor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(final MouseEvent e) {
|
||||
SResizingUtil.startResizeY(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(final MouseEvent e) {
|
||||
SResizingUtil.endResize();
|
||||
}
|
||||
};
|
||||
|
||||
private static final MouseMotionListener MMA_DRAG_X = new MouseMotionAdapter() {
|
||||
@Override
|
||||
public void mouseDragged(final MouseEvent e) {
|
||||
SResizingUtil.resizeX(e);
|
||||
}
|
||||
};
|
||||
|
||||
private static final MouseMotionListener MMA_DRAG_Y = new MouseMotionAdapter() {
|
||||
@Override
|
||||
public void mouseDragged(final MouseEvent e) {
|
||||
SResizingUtil.resizeY(e);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ComponentListener CAD_RESIZE = new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(final ComponentEvent e) {
|
||||
resizeWindow();
|
||||
SRearrangingUtil.updateBorders();
|
||||
}
|
||||
};
|
||||
|
||||
public static void resizeWindow() {
|
||||
final List<DragCell> cells = FView.SINGLETON_INSTANCE.getDragCells();
|
||||
final FFrame frame = FView.SINGLETON_INSTANCE.getFrame();
|
||||
final FNavigationBar navigationBar = FView.SINGLETON_INSTANCE.getNavigationBar();
|
||||
final JPanel pnlContent = FView.SINGLETON_INSTANCE.getPnlContent();
|
||||
final JPanel pnlInsets = FView.SINGLETON_INSTANCE.getPnlInsets();
|
||||
|
||||
Rectangle mainBounds = frame.getContentPane().getBounds();
|
||||
|
||||
FDialog.getBackdropPanel().setBounds(mainBounds);
|
||||
|
||||
int navigationBarHeight = navigationBar.getPreferredSize().height;
|
||||
navigationBar.setSize(mainBounds.width, navigationBarHeight);
|
||||
navigationBar.validate();
|
||||
|
||||
if (!frame.isTitleBarHidden()) { //adjust bounds for titlebar if not hidden
|
||||
mainBounds.y += navigationBarHeight;
|
||||
mainBounds.height -= navigationBarHeight;
|
||||
}
|
||||
|
||||
FAbsolutePositioner.SINGLETON_INSTANCE.containerResized(mainBounds);
|
||||
FOverlay.SINGLETON_INSTANCE.getPanel().setBounds(mainBounds);
|
||||
FNetOverlay.SINGLETON_INSTANCE.containerResized(mainBounds);
|
||||
|
||||
pnlInsets.setBounds(mainBounds);
|
||||
pnlInsets.validate();
|
||||
|
||||
final int w = pnlContent.getWidth();
|
||||
final int h = pnlContent.getHeight();
|
||||
|
||||
double roughVal = 0;
|
||||
int smoothVal = 0;
|
||||
|
||||
Set<Component> existingComponents = new HashSet<Component>();
|
||||
for (Component c : pnlContent.getComponents()) {
|
||||
existingComponents.add(c);
|
||||
}
|
||||
|
||||
// This is the core of the pixel-perfect layout. To avoid <20>1 px errors on borders
|
||||
// from rounding individual panels, the intermediate values (exactly accurate, in %)
|
||||
// for width and height are rounded based on comparison to other panels in the
|
||||
// layout. This is to avoid errors such as:
|
||||
// 10% = 9.8px -> 10px -> x 3 = 30px
|
||||
// 30% = 29.4px -> 29px (!)
|
||||
for (final DragCell cellA : cells) {
|
||||
RectangleOfDouble cellSizeA = cellA.getRoughBounds();
|
||||
roughVal = cellSizeA.getX() * w + cellSizeA.getW() * w;
|
||||
|
||||
smoothVal = (int) Math.round(roughVal);
|
||||
for (final DragCell cellB : cells) {
|
||||
RectangleOfDouble cellSizeB = cellB.getRoughBounds();
|
||||
if ((cellSizeB.getX() * w + cellSizeB.getW() * w) == roughVal) {
|
||||
cellB.setSmoothW(smoothVal - (int) Math.round(cellSizeB.getX() * w));
|
||||
}
|
||||
}
|
||||
cellA.setSmoothW(smoothVal - (int) Math.round(cellSizeA.getX() * w));
|
||||
|
||||
roughVal = cellSizeA.getY() * h + cellSizeA.getH() * h;
|
||||
smoothVal = (int) Math.round(roughVal);
|
||||
for (final DragCell cellB : cells) {
|
||||
RectangleOfDouble cellSizeB = cellB.getRoughBounds();
|
||||
if (cellSizeB.getY() * h + cellSizeB.getH() * h == roughVal) {
|
||||
cellB.setSmoothH(smoothVal - (int) Math.round(cellSizeB.getY() * h));
|
||||
}
|
||||
}
|
||||
cellA.setSmoothH(smoothVal - (int) Math.round(cellSizeA.getY() * h));
|
||||
|
||||
// X and Y coordinate can be rounded as usual.
|
||||
cellA.setSmoothX((int) Math.round(cellSizeA.getX() * w));
|
||||
cellA.setSmoothY((int) Math.round(cellSizeA.getY() * h));
|
||||
|
||||
// only add component if not already in container; otherwise the keyboard focus
|
||||
// jumps around to the most recenly added component
|
||||
if (!existingComponents.contains(cellA)) {
|
||||
pnlContent.add(cellA);
|
||||
}
|
||||
}
|
||||
|
||||
// Lock in final bounds and build heads.
|
||||
for (final DragCell t : cells) {
|
||||
t.setSmoothBounds();
|
||||
t.validate();
|
||||
t.refresh();
|
||||
}
|
||||
|
||||
cells.clear();
|
||||
}
|
||||
|
||||
/** @param e   {@link java.awt.event.MouseEvent} */
|
||||
public static void resizeX(final MouseEvent e) {
|
||||
dX = (int) e.getLocationOnScreen().getX() - evtX;
|
||||
evtX = (int) e.getLocationOnScreen().getX();
|
||||
boolean leftLock = false;
|
||||
boolean rightLock = false;
|
||||
|
||||
for (final DragCell t : LEFT_PANELS) {
|
||||
if ((t.getW() + dX) < W_MIN) { leftLock = true; break; }
|
||||
}
|
||||
|
||||
for (final DragCell t : RIGHT_PANELS) {
|
||||
if ((t.getW() - dX) < W_MIN) { rightLock = true; break; }
|
||||
}
|
||||
|
||||
if (dX < 0 && leftLock) { return; }
|
||||
if (dX > 0 && rightLock) { return; }
|
||||
|
||||
for (final DragCell t : LEFT_PANELS) {
|
||||
t.setBounds(t.getX(), t.getY(), t.getW() + dX, t.getH());
|
||||
t.refresh();
|
||||
}
|
||||
|
||||
for (final DragCell t : RIGHT_PANELS) {
|
||||
t.setBounds(t.getX() + dX, t.getY(), t.getW() - dX, t.getH());
|
||||
t.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/** @param e   {@link java.awt.event.MouseEvent} */
|
||||
public static void resizeY(final MouseEvent e) {
|
||||
dY = (int) e.getLocationOnScreen().getY() - evtY;
|
||||
evtY = (int) e.getLocationOnScreen().getY();
|
||||
boolean topLock = false;
|
||||
boolean bottomLock = false;
|
||||
|
||||
for (final DragCell t : TOP_PANELS) {
|
||||
if ((t.getH() + dY) < H_MIN) { topLock = true; break; }
|
||||
}
|
||||
|
||||
for (final DragCell t : BOTTOM_PANELS) {
|
||||
if ((t.getH() - dY) < H_MIN) { bottomLock = true; break; }
|
||||
}
|
||||
|
||||
if (dY < 0 && topLock) { return; }
|
||||
if (dY > 0 && bottomLock) { return; }
|
||||
|
||||
for (final DragCell t : TOP_PANELS) {
|
||||
t.setBounds(t.getX(), t.getY(), t.getW(), t.getH() + dY);
|
||||
t.revalidate();
|
||||
t.repaintSelf();
|
||||
}
|
||||
|
||||
for (final DragCell t : BOTTOM_PANELS) {
|
||||
t.setBounds(t.getX(), t.getY() + dY, t.getW(), t.getH() - dY);
|
||||
t.revalidate();
|
||||
t.repaintSelf();
|
||||
}
|
||||
}
|
||||
|
||||
/** @param e   {@link java.awt.event.MouseEvent} */
|
||||
public static void startResizeX(final MouseEvent e) {
|
||||
MouseUtil.lockCursor(); //lock cursor while resizing
|
||||
|
||||
evtX = (int) e.getLocationOnScreen().getX();
|
||||
LEFT_PANELS.clear();
|
||||
RIGHT_PANELS.clear();
|
||||
|
||||
final DragCell src = (DragCell) ((JPanel) e.getSource()).getParent();
|
||||
final int srcX2 = src.getAbsX2();
|
||||
|
||||
int limTop = -1;
|
||||
int limBottom = Integer.MAX_VALUE;
|
||||
int tempX = -1;
|
||||
int tempX2 = -1;
|
||||
int tempY = -1;
|
||||
int tempY2 = -1;
|
||||
|
||||
// Add all panels who share a left or right edge with the
|
||||
// same coordinate as the right edge of the source panel.
|
||||
for (final DragCell t : FView.SINGLETON_INSTANCE.getDragCells()) {
|
||||
tempX = t.getAbsX();
|
||||
tempX2 = t.getAbsX2();
|
||||
|
||||
if (srcX2 == tempX) { RIGHT_PANELS.add(t); }
|
||||
else if (srcX2 == tempX2) { LEFT_PANELS.add(t); }
|
||||
}
|
||||
|
||||
// Set limits at panels which are level at intersections.
|
||||
for (final DragCell pnlL : LEFT_PANELS) {
|
||||
if (pnlL.equals(src)) { continue; }
|
||||
tempY = pnlL.getAbsY();
|
||||
tempY2 = pnlL.getAbsY2();
|
||||
|
||||
for (final DragCell pnlR : RIGHT_PANELS) {
|
||||
// Upper edges match? Set a bottom limit.
|
||||
if (tempY >= src.getAbsY2() && tempY == pnlR.getAbsY() && tempY < limBottom) {
|
||||
limBottom = tempY;
|
||||
}
|
||||
// Lower edges match? Set an upper limit.
|
||||
else if (tempY2 <= src.getAbsY() && tempY2 == pnlR.getAbsY2() && tempY2 > limTop) {
|
||||
limTop = tempY2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove non-contiguous panels from left side using limits.
|
||||
final Iterator<DragCell> itrLeft = LEFT_PANELS.iterator();
|
||||
while (itrLeft.hasNext()) {
|
||||
final DragCell t = itrLeft.next();
|
||||
|
||||
if (t.getAbsY() >= limBottom || t.getAbsY2() <= limTop) {
|
||||
itrLeft.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove non-contiguous panels from right side using limits.
|
||||
final Iterator<DragCell> itrRight = RIGHT_PANELS.iterator();
|
||||
while (itrRight.hasNext()) {
|
||||
final DragCell t = itrRight.next();
|
||||
|
||||
if (t.getAbsY() >= limBottom || t.getAbsY2() <= limTop) {
|
||||
itrRight.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param e   {@link java.awt.event.MouseEvent} */
|
||||
public static void startResizeY(final MouseEvent e) {
|
||||
MouseUtil.lockCursor(); //lock cursor while resizing
|
||||
|
||||
evtY = (int) e.getLocationOnScreen().getY();
|
||||
TOP_PANELS.clear();
|
||||
BOTTOM_PANELS.clear();
|
||||
|
||||
final DragCell src = (DragCell) ((JPanel) e.getSource()).getParent();
|
||||
final int srcY2 = src.getAbsY2();
|
||||
|
||||
int limLeft = -1;
|
||||
int limRight = Integer.MAX_VALUE;
|
||||
int tempX = -1;
|
||||
int tempX2 = -1;
|
||||
int tempY = -1;
|
||||
int tempY2 = -1;
|
||||
|
||||
// Add all panels who share a top or bottom edge with the
|
||||
// same coordinate as the bottom edge of the source panel.
|
||||
for (final DragCell t : FView.SINGLETON_INSTANCE.getDragCells()) {
|
||||
tempY = t.getAbsY();
|
||||
tempY2 = t.getAbsY2();
|
||||
|
||||
if (srcY2 == tempY) { BOTTOM_PANELS.add(t); }
|
||||
else if (srcY2 == tempY2) { TOP_PANELS.add(t); }
|
||||
}
|
||||
|
||||
// Set limits at panels which are level at intersections.
|
||||
for (final DragCell pnlT : TOP_PANELS) {
|
||||
if (pnlT.equals(src)) { continue; }
|
||||
tempX = pnlT.getAbsX();
|
||||
tempX2 = pnlT.getAbsX2();
|
||||
|
||||
for (final DragCell pnlB : BOTTOM_PANELS) {
|
||||
// Right edges match? Set a right limit.
|
||||
if (tempX >= src.getAbsX2() && tempX == pnlB.getAbsX() && tempX < limRight) {
|
||||
limRight = tempX;
|
||||
}
|
||||
// Left edges match? Set an left limit.
|
||||
else if (tempX2 <= src.getAbsX() && tempX2 == pnlB.getAbsX2() && tempX2 > limLeft) {
|
||||
limLeft = tempX2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove non-contiguous panels from left side using limits.
|
||||
final Iterator<DragCell> itrTop = TOP_PANELS.iterator();
|
||||
while (itrTop.hasNext()) {
|
||||
final DragCell t = itrTop.next();
|
||||
if (t.getAbsX() >= limRight || t.getAbsX2() <= limLeft) {
|
||||
itrTop.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove non-contiguous panels from right side using limits.
|
||||
final Iterator<DragCell> itrBottom = BOTTOM_PANELS.iterator();
|
||||
while (itrBottom.hasNext()) {
|
||||
final DragCell t = itrBottom.next();
|
||||
if (t.getAbsX() >= limRight || t.getAbsX2() <= limLeft) {
|
||||
itrBottom.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** */
|
||||
public static void endResize() {
|
||||
MouseUtil.unlockCursor();
|
||||
SLayoutIO.saveLayout(null);
|
||||
}
|
||||
|
||||
/** @return {@link java.awt.event.MouseListener} */
|
||||
public static MouseListener getResizeXListener() {
|
||||
return MAD_RESIZE_X;
|
||||
}
|
||||
|
||||
/** @return {@link java.awt.event.MouseListener} */
|
||||
public static MouseListener getResizeYListener() {
|
||||
return MAD_RESIZE_Y;
|
||||
}
|
||||
|
||||
/** @return {@link java.awt.event.MouseMotionListener} */
|
||||
public static MouseMotionListener getDragXListener() {
|
||||
return MMA_DRAG_X;
|
||||
}
|
||||
|
||||
/** @return {@link java.awt.event.MouseMotionListener} */
|
||||
public static MouseMotionListener getDragYListener() {
|
||||
return MMA_DRAG_Y;
|
||||
}
|
||||
|
||||
/** @return {@link java.awt.event.ComponentListener} */
|
||||
public static ComponentListener getWindowResizeListener() {
|
||||
return CAD_RESIZE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package forge.gui.framework;
|
||||
|
||||
|
||||
/**
|
||||
* An intentionally empty IVDoc to fill field slots unused
|
||||
* by the current layout of a match UI.
|
||||
*/
|
||||
public class VEmptyDoc implements IVDoc<CEmptyDoc> {
|
||||
// Fields used with interface IVDoc
|
||||
private final CEmptyDoc control;
|
||||
private final EDocID docID;
|
||||
|
||||
/**
|
||||
* An intentionally empty IVDoc to fill field slots unused
|
||||
* by the current layout of a match UI.
|
||||
*
|
||||
* @param id0 EDocID
|
||||
*/
|
||||
public VEmptyDoc(final EDocID id0) {
|
||||
id0.setDoc(this);
|
||||
docID = id0;
|
||||
control = new CEmptyDoc();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.framework.IVDoc#getDocumentID()
|
||||
*/
|
||||
@Override
|
||||
public EDocID getDocumentID() {
|
||||
return docID;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.framework.IVDoc#getTabLabel()
|
||||
*/
|
||||
@Override
|
||||
public DragTab getTabLabel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.framework.IVDoc#getLayoutControl()
|
||||
*/
|
||||
@Override
|
||||
public CEmptyDoc getLayoutControl() {
|
||||
return control;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.framework.IVDoc#setParentCell(forge.gui.framework.DragCell)
|
||||
*/
|
||||
@Override
|
||||
public void setParentCell(DragCell cell0) {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.framework.IVDoc#getParentCell()
|
||||
*/
|
||||
@Override
|
||||
public DragCell getParentCell() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.gui.framework.IVDoc#populate()
|
||||
*/
|
||||
@Override
|
||||
public void populate() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
/** Forge Card Game. */
|
||||
package forge.gui.framework;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/** Forge Card Game. */
|
||||
package forge.gui;
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package forge.itemmanager;
|
||||
|
||||
import forge.game.GameFormat;
|
||||
import forge.gui.GuiUtils;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.filters.*;
|
||||
import forge.model.FModel;
|
||||
import forge.quest.QuestWorld;
|
||||
import forge.screens.home.quest.DialogChooseSets;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ItemManager for cards
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class CardManager extends ItemManager<PaperCard> {
|
||||
public CardManager(boolean wantUnique0) {
|
||||
super(PaperCard.class, wantUnique0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addDefaultFilters() {
|
||||
addDefaultFilters(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemFilter<PaperCard> createSearchFilter() {
|
||||
return createSearchFilter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildAddFilterMenu(JMenu menu) {
|
||||
buildAddFilterMenu(menu, this);
|
||||
}
|
||||
|
||||
/* Static overrides shared with SpellShopManager*/
|
||||
|
||||
public static void addDefaultFilters(final ItemManager<? super PaperCard> itemManager) {
|
||||
itemManager.addFilter(new CardColorFilter(itemManager));
|
||||
itemManager.addFilter(new CardTypeFilter(itemManager));
|
||||
itemManager.addFilter(new CardCMCFilter(itemManager));
|
||||
}
|
||||
|
||||
public static ItemFilter<PaperCard> createSearchFilter(final ItemManager<? super PaperCard> itemManager) {
|
||||
return new CardSearchFilter(itemManager);
|
||||
}
|
||||
|
||||
public static void buildAddFilterMenu(JMenu menu, final ItemManager<? super PaperCard> itemManager) {
|
||||
GuiUtils.addSeparator(menu); //separate from current search item
|
||||
|
||||
JMenu fmt = GuiUtils.createMenu("Format");
|
||||
for (final GameFormat f : FModel.getFormats()) {
|
||||
GuiUtils.addMenuItem(fmt, f.getName(), null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
itemManager.addFilter(new CardFormatFilter(itemManager, f));
|
||||
}
|
||||
}, CardFormatFilter.canAddFormat(f, itemManager.getFilter(CardFormatFilter.class)));
|
||||
}
|
||||
menu.add(fmt);
|
||||
|
||||
GuiUtils.addMenuItem(menu, "Sets...", null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
CardSetFilter existingFilter = itemManager.getFilter(CardSetFilter.class);
|
||||
if (existingFilter != null) {
|
||||
existingFilter.edit();
|
||||
}
|
||||
else {
|
||||
final DialogChooseSets dialog = new DialogChooseSets(null, null, true);
|
||||
dialog.setOkCallback(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<String> sets = dialog.getSelectedSets();
|
||||
if (!sets.isEmpty()) {
|
||||
itemManager.addFilter(new CardSetFilter(itemManager, sets, dialog.getWantReprints()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JMenu world = GuiUtils.createMenu("Quest world");
|
||||
for (final QuestWorld w : FModel.getWorlds()) {
|
||||
GuiUtils.addMenuItem(world, w.getName(), null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
itemManager.addFilter(new CardQuestWorldFilter(itemManager, w));
|
||||
}
|
||||
}, CardQuestWorldFilter.canAddQuestWorld(w, itemManager.getFilter(CardQuestWorldFilter.class)));
|
||||
}
|
||||
menu.add(world);
|
||||
|
||||
GuiUtils.addSeparator(menu);
|
||||
|
||||
GuiUtils.addMenuItem(menu, "Colors", null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
itemManager.addFilter(new CardColorFilter(itemManager));
|
||||
}
|
||||
}, itemManager.getFilter(CardColorFilter.class) == null);
|
||||
GuiUtils.addMenuItem(menu, "Types", null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
itemManager.addFilter(new CardTypeFilter(itemManager));
|
||||
}
|
||||
}, itemManager.getFilter(CardTypeFilter.class) == null);
|
||||
GuiUtils.addMenuItem(menu, "Converted mana costs", null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
itemManager.addFilter(new CardCMCFilter(itemManager));
|
||||
}
|
||||
}, itemManager.getFilter(CardCMCFilter.class) == null);
|
||||
|
||||
GuiUtils.addSeparator(menu);
|
||||
|
||||
GuiUtils.addMenuItem(menu, "CMC range", null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
itemManager.addFilter(new CardCMCRangeFilter(itemManager));
|
||||
}
|
||||
}, itemManager.getFilter(CardCMCRangeFilter.class) == null);
|
||||
GuiUtils.addMenuItem(menu, "Power range", null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
itemManager.addFilter(new CardPowerFilter(itemManager));
|
||||
}
|
||||
}, itemManager.getFilter(CardPowerFilter.class) == null);
|
||||
GuiUtils.addMenuItem(menu, "Toughness range", null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
itemManager.addFilter(new CardToughnessFilter(itemManager));
|
||||
}
|
||||
}, itemManager.getFilter(CardToughnessFilter.class) == null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
package forge.itemmanager;
|
||||
|
||||
import forge.UiCommand;
|
||||
import forge.Singletons;
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.deck.DeckBase;
|
||||
import forge.deck.io.DeckPreferences;
|
||||
import forge.game.GameFormat;
|
||||
import forge.game.GameType;
|
||||
import forge.gui.GuiUtils;
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.gui.framework.FScreen;
|
||||
import forge.item.InventoryItem;
|
||||
import forge.itemmanager.ColumnDef;
|
||||
import forge.itemmanager.ItemColumn;
|
||||
import forge.itemmanager.ItemManagerConfig;
|
||||
import forge.itemmanager.filters.*;
|
||||
import forge.itemmanager.views.*;
|
||||
import forge.model.FModel;
|
||||
import forge.quest.QuestWorld;
|
||||
import forge.screens.deckeditor.CDeckEditorUI;
|
||||
import forge.screens.deckeditor.SEditorIO;
|
||||
import forge.screens.deckeditor.controllers.ACEditorBase;
|
||||
import forge.screens.deckeditor.controllers.CEditorLimited;
|
||||
import forge.screens.deckeditor.controllers.CEditorQuest;
|
||||
import forge.screens.home.quest.DialogChooseSets;
|
||||
import forge.toolbox.FOptionPane;
|
||||
import forge.toolbox.FSkin;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* ItemManager for decks
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public final class DeckManager extends ItemManager<DeckProxy> {
|
||||
private static final FSkin.SkinIcon icoDelete = FSkin.getIcon(FSkinProp.ICO_DELETE);
|
||||
private static final FSkin.SkinIcon icoDeleteOver = FSkin.getIcon(FSkinProp.ICO_DELETE_OVER);
|
||||
private static final FSkin.SkinIcon icoEdit = FSkin.getIcon(FSkinProp.ICO_EDIT);
|
||||
private static final FSkin.SkinIcon icoEditOver = FSkin.getIcon(FSkinProp.ICO_EDIT_OVER);
|
||||
|
||||
private final GameType gametype;
|
||||
private UiCommand cmdDelete, cmdSelect;
|
||||
|
||||
/**
|
||||
* Creates deck list for selected decks for quick deleting, editing, and
|
||||
* basic info. "selectable" and "editable" assumed true.
|
||||
*
|
||||
* @param gt
|
||||
*/
|
||||
public DeckManager(final GameType gt) {
|
||||
super(DeckProxy.class, true);
|
||||
this.gametype = gt;
|
||||
|
||||
this.addSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
if (cmdSelect != null) {
|
||||
cmdSelect.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.setItemActivateCommand(new UiCommand() {
|
||||
@Override
|
||||
public void run() {
|
||||
editDeck(getSelectedItem());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup(ItemManagerConfig config0) {
|
||||
boolean wasStringOnly = (this.getConfig() == ItemManagerConfig.STRING_ONLY);
|
||||
boolean isStringOnly = (config0 == ItemManagerConfig.STRING_ONLY);
|
||||
|
||||
Map<ColumnDef, ItemTableColumn> colOverrides = null;
|
||||
if (config0.getCols().containsKey(ColumnDef.DECK_ACTIONS)) {
|
||||
ItemTableColumn column = new ItemTableColumn(new ItemColumn(config0.getCols().get(ColumnDef.DECK_ACTIONS)));
|
||||
column.setCellRenderer(new DeckActionsRenderer());
|
||||
colOverrides = new HashMap<ColumnDef, ItemTableColumn>();
|
||||
colOverrides.put(ColumnDef.DECK_ACTIONS, column);
|
||||
}
|
||||
super.setup(config0, colOverrides);
|
||||
|
||||
if (isStringOnly != wasStringOnly) {
|
||||
this.restoreDefaultFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the delete command.
|
||||
*
|
||||
* @param c0   {@link forge.UiCommand} command executed on delete.
|
||||
*/
|
||||
public void setDeleteCommand(final UiCommand c0) {
|
||||
this.cmdDelete = c0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the select command.
|
||||
*
|
||||
* @param c0   {@link forge.UiCommand} command executed on row select.
|
||||
*/
|
||||
public void setSelectCommand(final UiCommand c0) {
|
||||
this.cmdSelect = c0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addDefaultFilters() {
|
||||
if (this.getConfig() == ItemManagerConfig.STRING_ONLY) { return; }
|
||||
|
||||
addFilter(new DeckColorFilter(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemFilter<DeckProxy> createSearchFilter() {
|
||||
return new DeckSearchFilter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildAddFilterMenu(JMenu menu) {
|
||||
GuiUtils.addSeparator(menu); //separate from current search item
|
||||
|
||||
Set<String> folders = new HashSet<String>();
|
||||
for (final Entry<DeckProxy, Integer> deckEntry : getPool()) {
|
||||
String path = deckEntry.getKey().getPath();
|
||||
if (StringUtils.isNotEmpty(path)) { //don't include root folder as option
|
||||
folders.add(path);
|
||||
}
|
||||
}
|
||||
JMenu folder = GuiUtils.createMenu("Folder");
|
||||
if (folders.size() > 0) {
|
||||
for (final String f : folders) {
|
||||
GuiUtils.addMenuItem(folder, f, null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
addFilter(new DeckFolderFilter(DeckManager.this, f));
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
folder.setEnabled(false);
|
||||
}
|
||||
menu.add(folder);
|
||||
|
||||
JMenu fmt = GuiUtils.createMenu("Format");
|
||||
for (final GameFormat f : FModel.getFormats()) {
|
||||
GuiUtils.addMenuItem(fmt, f.getName(), null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
addFilter(new DeckFormatFilter(DeckManager.this, f));
|
||||
}
|
||||
}, DeckFormatFilter.canAddFormat(f, getFilter(DeckFormatFilter.class)));
|
||||
}
|
||||
menu.add(fmt);
|
||||
|
||||
GuiUtils.addMenuItem(menu, "Sets...", null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DeckSetFilter existingFilter = getFilter(DeckSetFilter.class);
|
||||
if (existingFilter != null) {
|
||||
existingFilter.edit();
|
||||
}
|
||||
else {
|
||||
final DialogChooseSets dialog = new DialogChooseSets(null, null, true);
|
||||
dialog.setOkCallback(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<String> sets = dialog.getSelectedSets();
|
||||
if (!sets.isEmpty()) {
|
||||
addFilter(new DeckSetFilter(DeckManager.this, sets, dialog.getWantReprints()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JMenu world = GuiUtils.createMenu("Quest world");
|
||||
for (final QuestWorld w : FModel.getWorlds()) {
|
||||
GuiUtils.addMenuItem(world, w.getName(), null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
addFilter(new DeckQuestWorldFilter(DeckManager.this, w));
|
||||
}
|
||||
}, DeckQuestWorldFilter.canAddQuestWorld(w, getFilter(DeckQuestWorldFilter.class)));
|
||||
}
|
||||
menu.add(world);
|
||||
|
||||
GuiUtils.addSeparator(menu);
|
||||
|
||||
GuiUtils.addMenuItem(menu, "Colors", null, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
addFilter(new DeckColorFilter(DeckManager.this));
|
||||
}
|
||||
}, getFilter(DeckColorFilter.class) == null);
|
||||
}
|
||||
|
||||
private void editDeck(final DeckProxy deck) {
|
||||
if (deck == null) { return; }
|
||||
|
||||
ACEditorBase<? extends InventoryItem, ? extends DeckBase> editorCtrl = null;
|
||||
FScreen screen = null;
|
||||
|
||||
switch (this.gametype) {
|
||||
case Quest:
|
||||
screen = FScreen.DECK_EDITOR_QUEST;
|
||||
editorCtrl = new CEditorQuest(FModel.getQuest());
|
||||
break;
|
||||
case Constructed:
|
||||
screen = FScreen.DECK_EDITOR_CONSTRUCTED;
|
||||
DeckPreferences.setCurrentDeck(deck.toString());
|
||||
//re-use constructed controller
|
||||
break;
|
||||
case Sealed:
|
||||
screen = FScreen.DECK_EDITOR_SEALED;
|
||||
editorCtrl = new CEditorLimited(FModel.getDecks().getSealed(), screen);
|
||||
break;
|
||||
case Draft:
|
||||
screen = FScreen.DECK_EDITOR_DRAFT;
|
||||
editorCtrl = new CEditorLimited(FModel.getDecks().getDraft(), screen);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Singletons.getControl().ensureScreenActive(screen)) { return; }
|
||||
|
||||
if (editorCtrl != null) {
|
||||
CDeckEditorUI.SINGLETON_INSTANCE.setEditorController(editorCtrl);
|
||||
}
|
||||
|
||||
if (!SEditorIO.confirmSaveChanges(screen, true)) { return; } //ensure previous deck on screen is saved if needed
|
||||
|
||||
CDeckEditorUI.SINGLETON_INSTANCE.getCurrentEditorController().getDeckController().load(deck.getPath(), deck.getName());
|
||||
}
|
||||
|
||||
public boolean deleteDeck(DeckProxy deck) {
|
||||
if (deck == null) { return false; }
|
||||
|
||||
if (!FOptionPane.showConfirmDialog(
|
||||
"Are you sure you want to delete '" + deck.getName() + "'?",
|
||||
"Delete Deck", "Delete", "Cancel", false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// consider using deck proxy's method to delete deck
|
||||
switch(this.gametype) {
|
||||
case Constructed:
|
||||
case Draft:
|
||||
case Sealed:
|
||||
deck.deleteFromStorage();
|
||||
break;
|
||||
case Quest:
|
||||
deck.deleteFromStorage();
|
||||
FModel.getQuest().save();
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Delete not implemneted for game type = " + gametype.toString());
|
||||
}
|
||||
|
||||
this.removeItem(deck, 1);
|
||||
|
||||
if (this.cmdDelete != null) {
|
||||
this.cmdDelete.run();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public class DeckActionsRenderer extends ItemCellRenderer {
|
||||
private int overActionIndex = -1;
|
||||
private static final int imgSize = 20;
|
||||
|
||||
@Override
|
||||
public boolean alwaysShowTooltip() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* javax.swing.table.DefaultTableCellRenderer#getTableCellRendererComponent
|
||||
* (javax.swing.JTable, java.lang.Object, boolean, boolean, int, int)
|
||||
*/
|
||||
@Override
|
||||
public final Component getTableCellRendererComponent(final JTable table, final Object value,
|
||||
final boolean isSelected, final boolean hasFocus, final int row, final int column) {
|
||||
setToolTipText(null);
|
||||
return super.getTableCellRendererComponent(table, "", isSelected, hasFocus, row, column);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends InventoryItem> void processMouseEvent(final MouseEvent e, final ItemListView<T> listView, final Object value, final int row, final int column) {
|
||||
Rectangle cellBounds = listView.getTable().getCellRect(row, column, false);
|
||||
int x = e.getX() - cellBounds.x;
|
||||
|
||||
if (e.getID() == MouseEvent.MOUSE_PRESSED && e.getButton() == 1) {
|
||||
DeckProxy deck = (DeckProxy) value;
|
||||
|
||||
if (x >= 0 && x < imgSize) { //delete button
|
||||
if (DeckManager.this.deleteDeck(deck)) {
|
||||
e.consume();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (x >= imgSize && x < imgSize * 2) { //edit button
|
||||
DeckManager.this.editDeck(deck);
|
||||
}
|
||||
|
||||
listView.getTable().setRowSelectionInterval(row, row);
|
||||
listView.getTable().repaint();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
|
||||
/*private void setOverActionIndex(final ItemListView<?> listView, int overActionIndex0) {
|
||||
if (this.overActionIndex == overActionIndex0) { return; }
|
||||
this.overActionIndex = overActionIndex0;
|
||||
switch (this.overActionIndex) {
|
||||
case -1:
|
||||
this.setToolTipText(null);
|
||||
break;
|
||||
case 0:
|
||||
this.setToolTipText("Delete this deck");
|
||||
break;
|
||||
case 1:
|
||||
this.setToolTipText("Edit this deck");
|
||||
break;
|
||||
}
|
||||
listView.getTable().repaint();
|
||||
}*/
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see javax.swing.JComponent#paint(java.awt.Graphics)
|
||||
*/
|
||||
@Override
|
||||
public final void paint(final Graphics g) {
|
||||
super.paint(g);
|
||||
|
||||
FSkin.drawImage(g, overActionIndex == 0 ? icoDeleteOver : icoDelete, 0, 0, imgSize, imgSize);
|
||||
FSkin.drawImage(g, overActionIndex == 1 ? icoEditOver : icoEdit, imgSize - 1, -1, imgSize, imgSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
1241
forge-gui-desktop/src/main/java/forge/itemmanager/ItemManager.java
Normal file
1241
forge-gui-desktop/src/main/java/forge/itemmanager/ItemManager.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.itemmanager;
|
||||
|
||||
import forge.toolbox.FSkin.SkinnedScrollPane;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
|
||||
|
||||
/**
|
||||
* Simple container pane meant to contain item managers.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public final class ItemManagerContainer extends SkinnedScrollPane {
|
||||
public ItemManagerContainer() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public ItemManagerContainer(ItemManager<?> itemManager) {
|
||||
super(null, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
|
||||
setBorder((Border)null);
|
||||
setOpaque(false);
|
||||
getViewport().setOpaque(false);
|
||||
setItemManager(itemManager);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the item pool.
|
||||
*
|
||||
* @return ItemPoolView
|
||||
*/
|
||||
public void setItemManager(ItemManager<?> itemManager) {
|
||||
if (itemManager != null) {
|
||||
itemManager.initialize(); //ensure item manager is initialized
|
||||
}
|
||||
this.getViewport().setView(itemManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package forge.itemmanager;
|
||||
|
||||
import forge.item.InventoryItem;
|
||||
import forge.itemmanager.filters.ItemFilter;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public final class SpellShopManager extends ItemManager<InventoryItem> {
|
||||
public SpellShopManager(boolean wantUnique0) {
|
||||
super(InventoryItem.class, wantUnique0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addDefaultFilters() {
|
||||
CardManager.addDefaultFilters(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ItemFilter<? extends InventoryItem> createSearchFilter() {
|
||||
return CardManager.createSearchFilter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildAddFilterMenu(JMenu menu) {
|
||||
CardManager.buildAddFilterMenu(menu, this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.card.CardRules;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.itemmanager.SpellShopManager;
|
||||
import forge.itemmanager.SItemManagerUtil.StatTypes;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class CardCMCFilter extends StatTypeFilter<PaperCard> {
|
||||
public CardCMCFilter(ItemManager<? super PaperCard> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
return new CardCMCFilter(itemManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildWidget(JPanel widget) {
|
||||
if (itemManager instanceof SpellShopManager) {
|
||||
addToggleButton(widget, StatTypes.PACK_OR_DECK);
|
||||
}
|
||||
addToggleButton(widget, StatTypes.CMC_0);
|
||||
addToggleButton(widget, StatTypes.CMC_1);
|
||||
addToggleButton(widget, StatTypes.CMC_2);
|
||||
addToggleButton(widget, StatTypes.CMC_3);
|
||||
addToggleButton(widget, StatTypes.CMC_4);
|
||||
addToggleButton(widget, StatTypes.CMC_5);
|
||||
addToggleButton(widget, StatTypes.CMC_6);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Predicate<PaperCard> buildPredicate() {
|
||||
final List<Predicate<CardRules>> cmcs = new ArrayList<Predicate<CardRules>>();
|
||||
|
||||
for (StatTypes s : buttonMap.keySet()) {
|
||||
if (s.predicate != null && buttonMap.get(s).isSelected()) {
|
||||
cmcs.add(s.predicate);
|
||||
}
|
||||
}
|
||||
|
||||
if (cmcs.size() == buttonMap.size()) {
|
||||
return new Predicate<PaperCard>() { //use custom return true delegate to validate the item is a card
|
||||
@Override
|
||||
public boolean apply(PaperCard card) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
return Predicates.compose(Predicates.or(cmcs), PaperCard.FN_GET_RULES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.card.CardRules;
|
||||
import forge.card.CardRulesPredicates;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class CardCMCRangeFilter extends ValueRangeFilter<PaperCard> {
|
||||
public CardCMCRangeFilter(ItemManager<? super PaperCard> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
return new CardCMCRangeFilter(itemManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCaption() {
|
||||
return "CMC";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<PaperCard> buildPredicate() {
|
||||
Predicate<CardRules> predicate = getCardRulesFieldPredicate(CardRulesPredicates.LeafNumber.CardField.CMC);
|
||||
if (predicate == null) {
|
||||
return Predicates.alwaysTrue();
|
||||
}
|
||||
return Predicates.compose(predicate, PaperCard.FN_GET_RULES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.itemmanager.SFilterUtil;
|
||||
import forge.itemmanager.SpellShopManager;
|
||||
import forge.itemmanager.SItemManagerUtil.StatTypes;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
public class CardColorFilter extends StatTypeFilter<PaperCard> {
|
||||
public CardColorFilter(ItemManager<? super PaperCard> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
return new CardColorFilter(itemManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildWidget(JPanel widget) {
|
||||
if (itemManager instanceof SpellShopManager) {
|
||||
addToggleButton(widget, StatTypes.PACK_OR_DECK);
|
||||
}
|
||||
addToggleButton(widget, StatTypes.WHITE);
|
||||
addToggleButton(widget, StatTypes.BLUE);
|
||||
addToggleButton(widget, StatTypes.BLACK);
|
||||
addToggleButton(widget, StatTypes.RED);
|
||||
addToggleButton(widget, StatTypes.GREEN);
|
||||
addToggleButton(widget, StatTypes.COLORLESS);
|
||||
addToggleButton(widget, StatTypes.MULTICOLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Predicate<PaperCard> buildPredicate() {
|
||||
return SFilterUtil.buildColorFilter(buttonMap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.game.GameFormat;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.itemmanager.SFilterUtil;
|
||||
|
||||
|
||||
public class CardFormatFilter extends FormatFilter<PaperCard> {
|
||||
public CardFormatFilter(ItemManager<? super PaperCard> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
public CardFormatFilter(ItemManager<? super PaperCard> itemManager0, GameFormat format0) {
|
||||
super(itemManager0, format0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
CardFormatFilter copy = new CardFormatFilter(itemManager);
|
||||
copy.formats.addAll(this.formats);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Predicate<PaperCard> buildPredicate() {
|
||||
return SFilterUtil.buildFormatFilter(this.formats, this.allowReprints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.card.CardRules;
|
||||
import forge.card.CardRulesPredicates;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class CardPowerFilter extends ValueRangeFilter<PaperCard> {
|
||||
public CardPowerFilter(ItemManager<? super PaperCard> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
return new CardPowerFilter(itemManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCaption() {
|
||||
return "Power";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<PaperCard> buildPredicate() {
|
||||
Predicate<CardRules> predicate = getCardRulesFieldPredicate(CardRulesPredicates.LeafNumber.CardField.POWER);
|
||||
if (predicate == null) {
|
||||
return Predicates.alwaysTrue();
|
||||
}
|
||||
predicate = Predicates.and(predicate, CardRulesPredicates.Presets.IS_CREATURE);
|
||||
return Predicates.compose(predicate, PaperCard.FN_GET_RULES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import forge.game.GameFormat;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.model.FModel;
|
||||
import forge.quest.QuestWorld;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class CardQuestWorldFilter extends CardFormatFilter {
|
||||
private final Set<QuestWorld> questWorlds = new HashSet<QuestWorld>();
|
||||
|
||||
public CardQuestWorldFilter(ItemManager<? super PaperCard> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
public CardQuestWorldFilter(ItemManager<? super PaperCard> itemManager0, QuestWorld questWorld0) {
|
||||
super(itemManager0);
|
||||
this.questWorlds.add(questWorld0);
|
||||
this.formats.add(getQuestWorldFormat(questWorld0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
CardQuestWorldFilter copy = new CardQuestWorldFilter(itemManager);
|
||||
copy.questWorlds.addAll(this.questWorlds);
|
||||
for (QuestWorld w : this.questWorlds) {
|
||||
copy.formats.add(getQuestWorldFormat(w));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.questWorlds.clear();
|
||||
super.reset();
|
||||
}
|
||||
|
||||
public static boolean canAddQuestWorld(QuestWorld questWorld, ItemFilter<PaperCard> existingFilter) {
|
||||
if (questWorld.getFormat() == null && FModel.getQuest().getMainFormat() == null) {
|
||||
return false; //must have format
|
||||
}
|
||||
return existingFilter == null || !((CardQuestWorldFilter)existingFilter).questWorlds.contains(questWorld);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the given filter with this filter if possible
|
||||
* @param filter
|
||||
* @return true if filter merged in or to suppress adding a new filter, false to allow adding new filter
|
||||
*/
|
||||
@Override
|
||||
public boolean merge(ItemFilter<?> filter) {
|
||||
CardQuestWorldFilter cardQuestWorldFilter = (CardQuestWorldFilter)filter;
|
||||
this.questWorlds.addAll(cardQuestWorldFilter.questWorlds);
|
||||
for (QuestWorld w : cardQuestWorldFilter.questWorlds) {
|
||||
this.formats.add(getQuestWorldFormat(w));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCaption() {
|
||||
return "Quest World";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getCount() {
|
||||
return this.questWorlds.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<String> getList() {
|
||||
Set<String> strings = new HashSet<String>();
|
||||
for (QuestWorld w : this.questWorlds) {
|
||||
strings.add(w.getName());
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
private GameFormat getQuestWorldFormat(QuestWorld w) {
|
||||
GameFormat format = w.getFormat();
|
||||
if (format == null) {
|
||||
//assumes that no world other than the main world will have a null format
|
||||
format = FModel.getQuest().getMainFormat();
|
||||
}
|
||||
return format;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.UiCommand;
|
||||
import forge.item.InventoryItem;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.itemmanager.SFilterUtil;
|
||||
import forge.toolbox.FComboBoxWrapper;
|
||||
import forge.toolbox.FLabel;
|
||||
import forge.toolbox.FTextField;
|
||||
import forge.toolbox.LayoutHelper;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
|
||||
public class CardSearchFilter extends TextSearchFilter<PaperCard> {
|
||||
private FComboBoxWrapper<String> cbSearchMode;
|
||||
private FLabel btnName, btnType, btnText;
|
||||
|
||||
public CardSearchFilter(ItemManager<? super PaperCard> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
CardSearchFilter copy = new CardSearchFilter(itemManager);
|
||||
copy.getWidget(); //initialize widget
|
||||
copy.txtSearch.setText(this.txtSearch.getText());
|
||||
copy.cbSearchMode.setSelectedIndex(this.cbSearchMode.getSelectedIndex());
|
||||
copy.btnName.setSelected(this.btnName.isSelected());
|
||||
copy.btnType.setSelected(this.btnType.isSelected());
|
||||
copy.btnText.setSelected(this.btnText.isSelected());
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
this.cbSearchMode.setSelectedIndex(0);
|
||||
this.btnName.setSelected(true);
|
||||
this.btnType.setSelected(true);
|
||||
this.btnText.setSelected(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void buildWidget(JPanel widget) {
|
||||
super.buildWidget(widget);
|
||||
|
||||
cbSearchMode = new FComboBoxWrapper<String>();
|
||||
cbSearchMode.addItem("in");
|
||||
cbSearchMode.addItem("not in");
|
||||
cbSearchMode.addTo(widget);
|
||||
cbSearchMode.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent arg0) {
|
||||
if (!txtSearch.isEmpty()) {
|
||||
applyChange();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
btnName = addButton(widget, "Name");
|
||||
btnType = addButton(widget, "Type");
|
||||
btnText = addButton(widget, "Text");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doWidgetLayout(LayoutHelper helper) {
|
||||
final int comboBoxWidth = 61;
|
||||
final int buttonWidth = 51;
|
||||
|
||||
helper.fillLine(txtSearch, FTextField.HEIGHT, comboBoxWidth + buttonWidth * 3 + 12); //leave space for combo box and buttons
|
||||
helper.include(cbSearchMode.getComponent(), comboBoxWidth, FTextField.HEIGHT);
|
||||
helper.include(btnName, buttonWidth, FTextField.HEIGHT);
|
||||
helper.include(btnType, buttonWidth, FTextField.HEIGHT);
|
||||
helper.include(btnText, buttonWidth, FTextField.HEIGHT);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private FLabel addButton(JPanel widget, String text) {
|
||||
FLabel button = new FLabel.Builder().text(text).hoverable().selectable().selected().build();
|
||||
|
||||
button.setCommand(new UiCommand() {
|
||||
@Override
|
||||
public void run() {
|
||||
applyChange();
|
||||
}
|
||||
});
|
||||
|
||||
widget.add(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<PaperCard> buildPredicate() {
|
||||
return SFilterUtil.buildTextFilter(
|
||||
txtSearch.getText(),
|
||||
cbSearchMode.getSelectedIndex() != 0,
|
||||
btnName.isSelected(),
|
||||
btnType.isSelected(),
|
||||
btnText.isSelected());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <U extends InventoryItem> boolean showUnsupportedItem(U item) {
|
||||
//fallback to regular item text filter if item not PaperCard
|
||||
boolean result = btnName.isSelected() && SFilterUtil.buildItemTextFilter(txtSearch.getText()).apply(item);
|
||||
if (cbSearchMode.getSelectedIndex() != 0) { //invert result if needed
|
||||
result = !result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import forge.game.GameFormat;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.screens.home.quest.DialogChooseSets;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class CardSetFilter extends CardFormatFilter {
|
||||
private final Set<String> sets = new HashSet<String>();
|
||||
|
||||
public CardSetFilter(ItemManager<? super PaperCard> itemManager0, Collection<String> sets0, boolean allowReprints0) {
|
||||
super(itemManager0);
|
||||
this.sets.addAll(sets0);
|
||||
this.formats.add(new GameFormat(null, this.sets, null));
|
||||
this.allowReprints = allowReprints0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
return new CardSetFilter(itemManager, this.sets, this.allowReprints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.sets.clear();
|
||||
super.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the given filter with this filter if possible
|
||||
* @param filter
|
||||
* @return true if filter merged in or to suppress adding a new filter, false to allow adding new filter
|
||||
*/
|
||||
@Override
|
||||
public boolean merge(ItemFilter<?> filter) {
|
||||
CardSetFilter cardSetFilter = (CardSetFilter)filter;
|
||||
this.sets.addAll(cardSetFilter.sets);
|
||||
this.allowReprints = cardSetFilter.allowReprints;
|
||||
this.formats.clear();
|
||||
this.formats.add(new GameFormat(null, this.sets, null));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void edit() {
|
||||
final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true);
|
||||
dialog.setOkCallback(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sets.clear();
|
||||
sets.addAll(dialog.getSelectedSets());
|
||||
allowReprints = dialog.getWantReprints();
|
||||
formats.clear();
|
||||
formats.add(new GameFormat(null, sets, null));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCaption() {
|
||||
return "Set";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getCount() {
|
||||
return this.sets.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<String> getList() {
|
||||
return this.sets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.card.CardRules;
|
||||
import forge.card.CardRulesPredicates;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class CardToughnessFilter extends ValueRangeFilter<PaperCard> {
|
||||
public CardToughnessFilter(ItemManager<? super PaperCard> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
return new CardToughnessFilter(itemManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCaption() {
|
||||
return "Toughness";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<PaperCard> buildPredicate() {
|
||||
Predicate<CardRules> predicate = getCardRulesFieldPredicate(CardRulesPredicates.LeafNumber.CardField.TOUGHNESS);
|
||||
if (predicate == null) {
|
||||
return Predicates.alwaysTrue();
|
||||
}
|
||||
predicate = Predicates.and(predicate, CardRulesPredicates.Presets.IS_CREATURE);
|
||||
return Predicates.compose(predicate, PaperCard.FN_GET_RULES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Predicates;
|
||||
|
||||
import forge.card.CardRules;
|
||||
import forge.item.PaperCard;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.itemmanager.SpellShopManager;
|
||||
import forge.itemmanager.SItemManagerUtil.StatTypes;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class CardTypeFilter extends StatTypeFilter<PaperCard> {
|
||||
public CardTypeFilter(ItemManager<? super PaperCard> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<PaperCard> createCopy() {
|
||||
return new CardTypeFilter(itemManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildWidget(JPanel widget) {
|
||||
if (itemManager instanceof SpellShopManager) {
|
||||
addToggleButton(widget, StatTypes.PACK_OR_DECK);
|
||||
}
|
||||
addToggleButton(widget, StatTypes.LAND);
|
||||
addToggleButton(widget, StatTypes.ARTIFACT);
|
||||
addToggleButton(widget, StatTypes.CREATURE);
|
||||
addToggleButton(widget, StatTypes.ENCHANTMENT);
|
||||
addToggleButton(widget, StatTypes.PLANESWALKER);
|
||||
addToggleButton(widget, StatTypes.INSTANT);
|
||||
addToggleButton(widget, StatTypes.SORCERY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Predicate<PaperCard> buildPredicate() {
|
||||
final List<Predicate<CardRules>> types = new ArrayList<Predicate<CardRules>>();
|
||||
|
||||
for (StatTypes s : buttonMap.keySet()) {
|
||||
if (s.predicate != null && buttonMap.get(s).isSelected()) {
|
||||
types.add(s.predicate);
|
||||
}
|
||||
}
|
||||
|
||||
if (types.size() == buttonMap.size()) {
|
||||
return new Predicate<PaperCard>() { //use custom return true delegate to validate the item is a card
|
||||
@Override
|
||||
public boolean apply(PaperCard card) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
return Predicates.compose(Predicates.or(types), PaperCard.FN_GET_RULES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.MagicColor;
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.itemmanager.SItemManagerUtil.StatTypes;
|
||||
import forge.util.BinaryUtil;
|
||||
import forge.util.ItemPool;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
public class DeckColorFilter extends StatTypeFilter<DeckProxy> {
|
||||
public DeckColorFilter(ItemManager<? super DeckProxy> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<DeckProxy> createCopy() {
|
||||
return new DeckColorFilter(itemManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildWidget(JPanel widget) {
|
||||
addToggleButton(widget, StatTypes.DECK_WHITE);
|
||||
addToggleButton(widget, StatTypes.DECK_BLUE);
|
||||
addToggleButton(widget, StatTypes.DECK_BLACK);
|
||||
addToggleButton(widget, StatTypes.DECK_RED);
|
||||
addToggleButton(widget, StatTypes.DECK_GREEN);
|
||||
addToggleButton(widget, StatTypes.DECK_COLORLESS);
|
||||
addToggleButton(widget, StatTypes.DECK_MULTICOLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Predicate<DeckProxy> buildPredicate() {
|
||||
return new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(DeckProxy input) {
|
||||
byte colorProfile = input.getColor().getColor();
|
||||
if (colorProfile == 0) {
|
||||
return buttonMap.get(StatTypes.DECK_COLORLESS).isSelected();
|
||||
}
|
||||
|
||||
boolean wantMulticolor = buttonMap.get(StatTypes.DECK_MULTICOLOR).isSelected();
|
||||
if (!wantMulticolor && BinaryUtil.bitCount(colorProfile) > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte colors = 0;
|
||||
if (buttonMap.get(StatTypes.DECK_WHITE).isSelected()) {
|
||||
colors |= MagicColor.WHITE;
|
||||
}
|
||||
if (buttonMap.get(StatTypes.DECK_BLUE).isSelected()) {
|
||||
colors |= MagicColor.BLUE;
|
||||
}
|
||||
if (buttonMap.get(StatTypes.DECK_BLACK).isSelected()) {
|
||||
colors |= MagicColor.BLACK;
|
||||
}
|
||||
if (buttonMap.get(StatTypes.DECK_RED).isSelected()) {
|
||||
colors |= MagicColor.RED;
|
||||
}
|
||||
if (buttonMap.get(StatTypes.DECK_GREEN).isSelected()) {
|
||||
colors |= MagicColor.GREEN;
|
||||
}
|
||||
|
||||
if (colors == 0 && wantMulticolor && BinaryUtil.bitCount(colorProfile) > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (colorProfile & colors) == colorProfile;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static final Predicate<DeckProxy> IS_WHITE = new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(final DeckProxy deck) {
|
||||
ColorSet cs = deck.getColor();
|
||||
return cs != null && cs.hasAnyColor(MagicColor.WHITE);
|
||||
}
|
||||
};
|
||||
private static final Predicate<DeckProxy> IS_BLUE = new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(final DeckProxy deck) {
|
||||
ColorSet cs = deck.getColor();
|
||||
return cs != null && cs.hasAnyColor(MagicColor.BLUE);
|
||||
}
|
||||
};
|
||||
public static final Predicate<DeckProxy> IS_BLACK = new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(final DeckProxy deck) {
|
||||
ColorSet cs = deck.getColor();
|
||||
return cs != null && cs.hasAnyColor(MagicColor.BLACK);
|
||||
}
|
||||
};
|
||||
public static final Predicate<DeckProxy> IS_RED = new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(final DeckProxy deck) {
|
||||
ColorSet cs = deck.getColor();
|
||||
return cs != null && cs.hasAnyColor(MagicColor.RED);
|
||||
}
|
||||
};
|
||||
public static final Predicate<DeckProxy> IS_GREEN = new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(final DeckProxy deck) {
|
||||
ColorSet cs = deck.getColor();
|
||||
return cs != null && cs.hasAnyColor(MagicColor.GREEN);
|
||||
}
|
||||
};
|
||||
private static final Predicate<DeckProxy> IS_COLORLESS = new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(final DeckProxy deck) {
|
||||
ColorSet cs = deck.getColor();
|
||||
return cs != null && cs.getColor() == 0;
|
||||
}
|
||||
};
|
||||
private static final Predicate<DeckProxy> IS_MULTICOLOR = new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(final DeckProxy deck) {
|
||||
ColorSet cs = deck.getColor();
|
||||
return cs != null && BinaryUtil.bitCount(cs.getColor()) > 1;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void afterFiltersApplied() {
|
||||
final ItemPool<? super DeckProxy> items = itemManager.getFilteredItems();
|
||||
|
||||
buttonMap.get(StatTypes.DECK_WHITE).setText(String.valueOf(items.countAll(IS_WHITE, DeckProxy.class)));
|
||||
buttonMap.get(StatTypes.DECK_BLUE).setText(String.valueOf(items.countAll(IS_BLUE, DeckProxy.class)));
|
||||
buttonMap.get(StatTypes.DECK_BLACK).setText(String.valueOf(items.countAll(IS_BLACK, DeckProxy.class)));
|
||||
buttonMap.get(StatTypes.DECK_RED).setText(String.valueOf(items.countAll(IS_RED, DeckProxy.class)));
|
||||
buttonMap.get(StatTypes.DECK_GREEN).setText(String.valueOf(items.countAll(IS_GREEN, DeckProxy.class)));
|
||||
buttonMap.get(StatTypes.DECK_COLORLESS).setText(String.valueOf(items.countAll(IS_COLORLESS, DeckProxy.class)));
|
||||
buttonMap.get(StatTypes.DECK_MULTICOLOR).setText(String.valueOf(items.countAll(IS_MULTICOLOR, DeckProxy.class)));
|
||||
|
||||
getWidget().revalidate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.itemmanager.ItemManager;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class DeckFolderFilter extends ListLabelFilter<DeckProxy> {
|
||||
protected final Set<String> folders = new HashSet<String>();
|
||||
|
||||
public DeckFolderFilter(ItemManager<? super DeckProxy> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
public DeckFolderFilter(ItemManager<? super DeckProxy> itemManager0, String folder0) {
|
||||
super(itemManager0);
|
||||
this.folders.add(folder0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<DeckProxy> createCopy() {
|
||||
DeckFolderFilter copy = new DeckFolderFilter(itemManager);
|
||||
copy.folders.addAll(this.folders);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Predicate<DeckProxy> buildPredicate() {
|
||||
return new Predicate<DeckProxy>() {
|
||||
@Override
|
||||
public boolean apply(DeckProxy input) {
|
||||
String path = input.getPath();
|
||||
for (String folder : folders) {
|
||||
if (path.startsWith(folder)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCaption() {
|
||||
return "Folder";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<String> getList() {
|
||||
return this.folders;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTooltip() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getCount() {
|
||||
return this.folders.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.folders.clear();
|
||||
this.updateLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean merge(ItemFilter<?> filter) {
|
||||
DeckFolderFilter formatFilter = (DeckFolderFilter)filter;
|
||||
this.folders.addAll(formatFilter.folders);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import forge.game.GameFormat;
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.itemmanager.SFilterUtil;
|
||||
|
||||
|
||||
public class DeckFormatFilter extends FormatFilter<DeckProxy> {
|
||||
public DeckFormatFilter(ItemManager<? super DeckProxy> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
public DeckFormatFilter(ItemManager<? super DeckProxy> itemManager0, GameFormat format0) {
|
||||
super(itemManager0, format0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<DeckProxy> createCopy() {
|
||||
DeckFormatFilter copy = new DeckFormatFilter(itemManager);
|
||||
copy.formats.addAll(this.formats);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Predicate<DeckProxy> buildPredicate() {
|
||||
return DeckProxy.createPredicate(SFilterUtil.buildFormatFilter(this.formats, this.allowReprints));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import forge.game.GameFormat;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.model.FModel;
|
||||
import forge.quest.QuestWorld;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class DeckQuestWorldFilter extends DeckFormatFilter {
|
||||
private final Set<QuestWorld> questWorlds = new HashSet<QuestWorld>();
|
||||
|
||||
public DeckQuestWorldFilter(ItemManager<? super DeckProxy> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
public DeckQuestWorldFilter(ItemManager<? super DeckProxy> itemManager0, QuestWorld questWorld0) {
|
||||
super(itemManager0);
|
||||
this.questWorlds.add(questWorld0);
|
||||
this.formats.add(getQuestWorldFormat(questWorld0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<DeckProxy> createCopy() {
|
||||
DeckQuestWorldFilter copy = new DeckQuestWorldFilter(itemManager);
|
||||
copy.questWorlds.addAll(this.questWorlds);
|
||||
for (QuestWorld w : this.questWorlds) {
|
||||
copy.formats.add(getQuestWorldFormat(w));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.questWorlds.clear();
|
||||
super.reset();
|
||||
}
|
||||
|
||||
public static boolean canAddQuestWorld(QuestWorld questWorld, ItemFilter<DeckProxy> existingFilter) {
|
||||
if (questWorld.getFormat() == null && FModel.getQuest().getMainFormat() == null) {
|
||||
return false; //must have format
|
||||
}
|
||||
return existingFilter == null || !((DeckQuestWorldFilter)existingFilter).questWorlds.contains(questWorld);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the given filter with this filter if possible
|
||||
* @param filter
|
||||
* @return true if filter merged in or to suppress adding a new filter, false to allow adding new filter
|
||||
*/
|
||||
@Override
|
||||
public boolean merge(ItemFilter<?> filter) {
|
||||
DeckQuestWorldFilter cardQuestWorldFilter = (DeckQuestWorldFilter)filter;
|
||||
this.questWorlds.addAll(cardQuestWorldFilter.questWorlds);
|
||||
for (QuestWorld w : cardQuestWorldFilter.questWorlds) {
|
||||
this.formats.add(getQuestWorldFormat(w));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCaption() {
|
||||
return "Quest World";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getCount() {
|
||||
return this.questWorlds.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<String> getList() {
|
||||
Set<String> strings = new HashSet<String>();
|
||||
for (QuestWorld w : this.questWorlds) {
|
||||
strings.add(w.getName());
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
private GameFormat getQuestWorldFormat(QuestWorld w) {
|
||||
GameFormat format = w.getFormat();
|
||||
if (format == null) {
|
||||
//assumes that no world other than the main world will have a null format
|
||||
format = FModel.getQuest().getMainFormat();
|
||||
}
|
||||
return format;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.itemmanager.ItemManager;
|
||||
|
||||
/**
|
||||
* TODO: Write javadoc for this type.
|
||||
*
|
||||
*/
|
||||
public class DeckSearchFilter extends TextSearchFilter<DeckProxy> {
|
||||
public DeckSearchFilter(ItemManager<? super DeckProxy> itemManager0) {
|
||||
super(itemManager0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<DeckProxy> createCopy() {
|
||||
DeckSearchFilter copy = new DeckSearchFilter(itemManager);
|
||||
copy.getWidget(); //initialize widget
|
||||
copy.txtSearch.setText(this.txtSearch.getText());
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package forge.itemmanager.filters;
|
||||
|
||||
import forge.deck.DeckProxy;
|
||||
import forge.game.GameFormat;
|
||||
import forge.itemmanager.ItemManager;
|
||||
import forge.screens.home.quest.DialogChooseSets;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class DeckSetFilter extends DeckFormatFilter {
|
||||
private final Set<String> sets = new HashSet<String>();
|
||||
|
||||
public DeckSetFilter(ItemManager<? super DeckProxy> itemManager0, Collection<String> sets0, boolean allowReprints0) {
|
||||
super(itemManager0);
|
||||
this.sets.addAll(sets0);
|
||||
this.formats.add(new GameFormat(null, this.sets, null));
|
||||
this.allowReprints = allowReprints0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemFilter<DeckProxy> createCopy() {
|
||||
return new DeckSetFilter(itemManager, this.sets, this.allowReprints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.sets.clear();
|
||||
super.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the given filter with this filter if possible
|
||||
* @param filter
|
||||
* @return true if filter merged in or to suppress adding a new filter, false to allow adding new filter
|
||||
*/
|
||||
@Override
|
||||
public boolean merge(ItemFilter<?> filter) {
|
||||
DeckSetFilter cardSetFilter = (DeckSetFilter)filter;
|
||||
this.sets.addAll(cardSetFilter.sets);
|
||||
this.allowReprints = cardSetFilter.allowReprints;
|
||||
this.formats.clear();
|
||||
this.formats.add(new GameFormat(null, this.sets, null));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void edit() {
|
||||
final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, true);
|
||||
dialog.setOkCallback(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sets.clear();
|
||||
sets.addAll(dialog.getSelectedSets());
|
||||
allowReprints = dialog.getWantReprints();
|
||||
formats.clear();
|
||||
formats.add(new GameFormat(null, sets, null));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCaption() {
|
||||
return "Set";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getCount() {
|
||||
return this.sets.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<String> getList() {
|
||||
return this.sets;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user