diff --git a/.gitattributes b/.gitattributes index 3fa55c78f89..0ec1b7774ec 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5371,6 +5371,7 @@ res/cardsfolder/i/ikiral_outrider.txt svneol=native#text/plain res/cardsfolder/i/ill_gotten_gains.txt svneol=native#text/plain res/cardsfolder/i/illness_in_the_ranks.txt -text res/cardsfolder/i/illuminate.txt -text +res/cardsfolder/i/illuminated_folio.txt -text res/cardsfolder/i/illuminated_wings.txt svneol=native#text/plain res/cardsfolder/i/illumination.txt svneol=native#text/plain res/cardsfolder/i/illusion_reality.txt -text @@ -14990,6 +14991,11 @@ src/main/java/forge/gui/match/views/VPicture.java -text src/main/java/forge/gui/match/views/VPlayers.java -text src/main/java/forge/gui/match/views/VStack.java -text src/main/java/forge/gui/match/views/package-info.java svneol=native#text/plain +src/main/java/forge/gui/menubar/FMenuBar.java -text +src/main/java/forge/gui/menubar/IMenuProvider.java -text +src/main/java/forge/gui/menubar/MenuUtil.java -text +src/main/java/forge/gui/menus/ForgeMenu.java -text +src/main/java/forge/gui/menus/HelpMenu.java -text src/main/java/forge/gui/package-info.java svneol=native#text/plain src/main/java/forge/gui/toolbox/CardFaceSymbols.java svneol=native#text/plain src/main/java/forge/gui/toolbox/FAbsolutePositioner.java -text @@ -15008,10 +15014,13 @@ src/main/java/forge/gui/toolbox/FSkin.java -text src/main/java/forge/gui/toolbox/FTabbedPane.java -text src/main/java/forge/gui/toolbox/FTextArea.java -text src/main/java/forge/gui/toolbox/FTextField.java -text +src/main/java/forge/gui/toolbox/LayoutHelper.java -text src/main/java/forge/gui/toolbox/SaveOpenDialog.java -text src/main/java/forge/gui/toolbox/imaging/FImagePanel.java -text src/main/java/forge/gui/toolbox/imaging/FImageUtil.java -text src/main/java/forge/gui/toolbox/imaging/ImageUtil.java -text +src/main/java/forge/gui/toolbox/itemmanager/CardManager.java -text +src/main/java/forge/gui/toolbox/itemmanager/InventoryItemManager.java -text src/main/java/forge/gui/toolbox/itemmanager/ItemManager.java -text src/main/java/forge/gui/toolbox/itemmanager/ItemManagerContainer.java -text src/main/java/forge/gui/toolbox/itemmanager/ItemManagerModel.java -text @@ -15023,12 +15032,13 @@ src/main/java/forge/gui/toolbox/itemmanager/filters/CardColorFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/CardFormatFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/CardPowerFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/CardQuestWorldFilter.java -text +src/main/java/forge/gui/toolbox/itemmanager/filters/CardSearchFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/CardSetFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/CardToughnessFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/CardTypeFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/ItemFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/ListLabelFilter.java -text -src/main/java/forge/gui/toolbox/itemmanager/filters/TextFieldFilter.java -text +src/main/java/forge/gui/toolbox/itemmanager/filters/TextSearchFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/ToggleButtonsFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/filters/ValueRangeFilter.java -text src/main/java/forge/gui/toolbox/itemmanager/package-info.java -text @@ -15103,6 +15113,7 @@ src/main/java/forge/net/protocol/toserver/IPacketSrv.java -text src/main/java/forge/net/protocol/toserver/IncorrectPacketSrv.java -text src/main/java/forge/net/protocol/toserver/package-info.java -text src/main/java/forge/package-info.java svneol=native#text/plain +src/main/java/forge/properties/ForgeLookAndFeel.java -text src/main/java/forge/properties/ForgePreferences.java svneol=native#text/plain src/main/java/forge/properties/NewConstants.java svneol=native#text/plain src/main/java/forge/properties/Preferences.java svneol=native#text/plain @@ -15183,6 +15194,7 @@ src/main/java/forge/util/MyRandom.java svneol=native#text/plain src/main/java/forge/util/PredicateString.java -text src/main/java/forge/util/ReflectionUtil.java -text src/main/java/forge/util/TextUtil.java -text +src/main/java/forge/util/TypeUtil.java -text src/main/java/forge/util/XmlUtil.java -text src/main/java/forge/util/maps/CollectionSuppliers.java -text src/main/java/forge/util/maps/EnumMapOfLists.java -text @@ -15197,6 +15209,8 @@ src/main/java/forge/util/package-info.java -text src/main/java/forge/util/storage/IStorage.java -text src/main/java/forge/util/storage/StorageBase.java -text src/main/java/forge/util/storage/StorageImmediatelySerialized.java svneol=native#text/plain +src/main/java/forge/util/storage/StorageNestedFolders.java -text +src/main/java/forge/util/storage/StorageReaderBase.java -text src/main/java/forge/util/storage/StorageReaderFile.java -text src/main/java/forge/util/storage/StorageReaderFileSections.java -text src/main/java/forge/util/storage/StorageReaderFolder.java -text diff --git a/res/cardsfolder/a/archangel_of_strife.txt b/res/cardsfolder/a/archangel_of_strife.txt index af633f7a245..a6f0de1728e 100644 --- a/res/cardsfolder/a/archangel_of_strife.txt +++ b/res/cardsfolder/a/archangel_of_strife.txt @@ -3,23 +3,13 @@ ManaCost:5 W W Types:Creature Angel PT:6/6 K:Flying -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ Tolstoy | Static$ True | TriggerDescription$ As CARDNAME enters the battlefield, each player chooses war or peace. Creatures controlled by players who chose war get +3/+0. Creatures controlled by players who chose peace get +0/+3. -SVar:Tolstoy:AB$ GenericChoice | Cost$ 0 | Defined$ You | Choices$ WarChoice,PeaceChoice | SubAbility$ OppChoice -SVar:OppChoice:DB$ GenericChoice | Cost$ 0 | Defined$ Opponent | Choices$ Attacking,Defensive -SVar:WarChoice:DB$ Effect | Name$ Archangel War Effect | ChoiceDescription$ War | Duration$ UntilHostLeavesPlay | RememberEffect$ True -SVar:PeaceChoice:DB$ Effect | Name$ Archangel Peace Effect | ChoiceDescription$ Peace | Duration$ UntilHostLeavesPlay | RememberEffect$ True -SVar:Attacking:DB$ Effect | Name$ Archangel War Effect | ChoiceDescription$ War | EffectOwner$ Opponent | Duration$ UntilHostLeavesPlay | RememberEffect$ True -SVar:Defensive:DB$ Effect | Name$ Archangel Peace Effect | ChoiceDescription$ Peace | EffectOwner$ Opponent | Duration$ UntilHostLeavesPlay | RememberEffect$ True -S:Mode$ Continuous | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl | AddPower$ 3 | CheckSVar$ WarYou | SVarCompare$ GE1 | References$ WarYou -S:Mode$ Continuous | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl | AddToughness$ 3 | CheckSVar$ PeaceYou | SVarCompare$ GE1 | References$ PeaceYou -S:Mode$ Continuous | AffectedZone$ Battlefield | Affected$ Creature.YouDontCtrl | AddPower$ 3 | CheckSVar$ WarOpp | SVarCompare$ GE1 | References$ WarOpp -S:Mode$ Continuous | AffectedZone$ Battlefield | Affected$ Creature.YouDontCtrl | AddToughness$ 3 | CheckSVar$ PeaceOpp | SVarCompare$ GE1 | References$ PeaceOpp -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Defined$ Self | Execute$ DBCleanup | Static$ True -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:WarYou:Count$ValidCommand Card.namedArchangel War Effect+YouCtrl+IsRemembered -SVar:PeaceYou:Count$ValidCommand Card.namedArchangel Peace Effect+YouCtrl+IsRemembered -SVar:WarOpp:Count$ValidCommand Card.namedArchangel War Effect+YouDontCtrl+IsRemembered -SVar:PeaceOpp:Count$ValidCommand Card.namedArchangel Peace Effect+YouDontCtrl+IsRemembered +K:ETBReplacement:Other:ChooseEach +SVar:ChooseEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChoice | SpellDescription$ As CARDNAME enters the battlefield, each player chooses war or peace. Creatures controlled by players who chose war get +3/+0. Creatures controlled by players who chose peace get +0/+3. +SVar:DBChoice:DB$ GenericChoice | Cost$ 0 | Defined$ Player.IsRemembered | Choices$ WarChoice,PeaceChoice +SVar:WarChoice:DB$ Effect | Name$ Archangel War Effect | StaticAbilities$ WarPump | ChoiceDescription$ War | EffectOwner$ Player.IsRemembered | Duration$ UntilHostLeavesPlay +SVar:PeaceChoice:DB$ Effect | Name$ Archangel Peace Effect | StaticAbilities$ PeacePump | ChoiceDescription$ Peace | EffectOwner$ Player.IsRemembered | Duration$ UntilHostLeavesPlay +SVar:WarPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl | AddPower$ 3 | Description$ Creatures you control get +3/+0. +SVar:PeacePump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl | AddToughness$ 3 | Description$ Creatures you control get +0/+3. SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/archangel_of_strife.jpg Oracle:Flying\nAs Archangel of Strife enters the battlefield, each player chooses war or peace.\nCreatures controlled by players who chose war get +3/+0.\nCreatures controlled by players who chose peace get +0/+3. \ No newline at end of file diff --git a/res/cardsfolder/e/exhume.txt b/res/cardsfolder/e/exhume.txt index 0ed9681a24c..8ec64cab832 100644 --- a/res/cardsfolder/e/exhume.txt +++ b/res/cardsfolder/e/exhume.txt @@ -1,7 +1,7 @@ Name:Exhume ManaCost:1 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 1 B | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Creature.YouCtrl | ChangeNum$ 1 | Hidden$ True | SubAbility$ DBChangeZoneOpp | SpellDescription$ Each player puts a creature card from his or her graveyard onto the battlefield. -SVar:DBChangeZoneOpp:DB$ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Creature.YouDontCtrl | DefinedPlayer$ Opponent | ChangeNum$ 1 | Hidden$ True +A:SP$ RepeatEach | Cost$ 1 B | RepeatSubAbility$ DBChangeZone | RepeatPlayers$ Player | SpellDescription$ Each player puts a creature card from his or her graveyard onto the battlefield. +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Creature.RememberedPlayerCtrl | DefinedPlayer$ Player.IsRemembered | Chooser$ Player.IsRemembered | ChangeNum$ 1 | Hidden$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/exhume.jpg Oracle:Each player puts a creature card from his or her graveyard onto the battlefield. \ No newline at end of file diff --git a/res/cardsfolder/g/goblin_lackey.txt b/res/cardsfolder/g/goblin_lackey.txt index c22cca0cc2f..7447dc8fb02 100644 --- a/res/cardsfolder/g/goblin_lackey.txt +++ b/res/cardsfolder/g/goblin_lackey.txt @@ -3,6 +3,6 @@ ManaCost:R Types:Creature Goblin PT:1/1 T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ TrigChange | TriggerDescription$ Whenever CARDNAME deals damage to a player, you may put a Goblin permanent card from your hand onto the battlefield. -SVar:TrigChange:AB$ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Battlefield | ChangeType$ Permanent.Goblin | ChangeNum$ 1 +SVar:TrigChange:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Battlefield | ChangeType$ Permanent.Goblin | ChangeNum$ 1 SVar:Picture:http://www.wizards.com/global/images/magic/general/goblin_lackey.jpg Oracle:Whenever Goblin Lackey deals damage to a player, you may put a Goblin permanent card from your hand onto the battlefield. \ No newline at end of file diff --git a/res/cardsfolder/i/illuminated_folio.txt b/res/cardsfolder/i/illuminated_folio.txt new file mode 100644 index 00000000000..bea6e9f86b9 --- /dev/null +++ b/res/cardsfolder/i/illuminated_folio.txt @@ -0,0 +1,7 @@ +Name:Illuminated Folio +ManaCost:5 +Types:Artifact +A:AB$ Draw | Cost$ 1 T Reveal<2/SameColor> | NumCards$ 1 | SpellDescription$ Draw a card. +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/illuminated_folio.jpg +Oracle:{1}, {T}, Reveal two cards from your hand that share a color: Draw a card. diff --git a/res/cardsfolder/m/mana_breach.txt b/res/cardsfolder/m/mana_breach.txt index 007e2f81840..f3ff236e42d 100644 --- a/res/cardsfolder/m/mana_breach.txt +++ b/res/cardsfolder/m/mana_breach.txt @@ -1,10 +1,8 @@ Name:Mana Breach ManaCost:2 U Types:Enchantment -T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigBounceYou | TriggerDescription$ Whenever a player casts a spell, that player returns a land he or she controls to its owner's hand. -T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Opponent | TriggerZones$ Battlefield | Execute$ TrigBounceOpp | Secondary$ True | TriggerDescription$ Whenever a player casts a spell, that player returns a land he or she controls to its owner's hand. -SVar:TrigBounceYou:AB$ ChangeZone | Cost$ 0 | Origin$ Battlefield | Destination$ Hand | ChangeNum$ 1 | ChangeType$ Land.YouCtrl | Mandatory$ True | DefinedPlayer$ You | Hidden$ True -SVar:TrigBounceOpp:AB$ ChangeZone | Cost$ 0 | Origin$ Battlefield | Destination$ Hand | ChangeNum$ 1 | ChangeType$ Land.YouDontCtrl | Mandatory$ True | DefinedPlayer$ Opponent | Hidden$ True +T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigBounce | TriggerDescription$ Whenever a player casts a spell, that player returns a land he or she controls to its owner's hand. +SVar:TrigBounce:AB$ ChangeZone | Cost$ 0 | Origin$ Battlefield | Destination$ Hand | ChangeNum$ 1 | ChangeType$ Land | Mandatory$ True | DefinedPlayer$ TriggeredActivator | Chooser$ TriggeredActivator | Hidden$ True SVar:RemRandomDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/mana_breach.jpg Oracle:Whenever a player casts a spell, that player returns a land he or she controls to its owner's hand. \ No newline at end of file diff --git a/res/cardsfolder/m/memory_jar.txt b/res/cardsfolder/m/memory_jar.txt index 4f82d751ac6..691e04f52d2 100644 --- a/res/cardsfolder/m/memory_jar.txt +++ b/res/cardsfolder/m/memory_jar.txt @@ -1,12 +1,11 @@ Name:Memory Jar ManaCost:5 Types:Artifact -A:AB$ ChangeZoneAll | Cost$ T Sac<1/CARDNAME> | ChangeType$ Card | Origin$ Hand | Destination$ Exile | ExileFaceDown$ True | RememberChanged$ True | SubAbility$ YouDraw | SpellDescription$ Each player exiles all cards from his or her hand face down and draws seven cards. At the beginning of the next end step, each player discards his or her hand and returns to his or her hand each card he or she exiled this way. -SVar:YouDraw:DB$ Draw | Defined$ You | NumCards$ 7 | SubAbility$ OppDraw -SVar:OppDraw:DB$ Draw | Defined$ Opponent | NumCards$ 7 | SubAbility$ DelayedReturn -SVar:DelayedReturn:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ YouDiscard | TriggerDescription$ Both players discard their hands. Return exiled cards to their owners' hands. -SVar:YouDiscard: AB$ Discard | Cost$ 0 | Defined$ You | Mode$ Hand | SubAbility$ OppDiscard -SVar:OppDiscard: DB$ Discard | Defined$ Opponent | Mode$ Hand | SubAbility$ ReturnAll +A:AB$ ChangeZoneAll | Cost$ T Sac<1/CARDNAME> | ChangeType$ Card | Origin$ Hand | Destination$ Exile | ExileFaceDown$ True | RememberChanged$ True | SubAbility$ DrawEach | SpellDescription$ Each player exiles all cards from his or her hand face down and draws seven cards. At the beginning of the next end step, each player discards his or her hand and returns to his or her hand each card he or she exiled this way. +SVar:DrawEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBDraw | SubAbility$ DelayedReturn +SVar:DBDraw:DB$ Draw | Defined$ Player.IsRemembered | NumCards$ 7 +SVar:DelayedReturn:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ DiscardEach | TriggerDescription$ Both players discard their hands. Return exiled cards to their owners' hands. +SVar:DiscardEach:AB$ Discard | Cost$ 0 | Defined$ Each | Mode$ Hand | SubAbility$ ReturnAll SVar:ReturnAll:DB$ ChangeZone | Origin$ Exile | Destination$ Hand | Defined$ Remembered | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:RemAIDeck:True diff --git a/res/cardsfolder/o/overburden.txt b/res/cardsfolder/o/overburden.txt index caa8082799c..82cc3c97064 100644 --- a/res/cardsfolder/o/overburden.txt +++ b/res/cardsfolder/o/overburden.txt @@ -1,10 +1,8 @@ Name:Overburden ManaCost:1 U Types:Enchantment -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.nonToken+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigBounceYou | TriggerDescription$ Whenever a player puts a nontoken creature onto the battlefield, that player returns a land he or she controls to its owner's hand. -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.nonToken+YouDontCtrl | TriggerZones$ Battlefield | Execute$ TrigBounceOpp | Secondary$ True | TriggerDescription$ Whenever a player puts a nontoken creature onto the battlefield, that player returns a land he or she controls to its owner's hand. -SVar:TrigBounceYou:AB$ ChangeZone | Cost$ 0 | Origin$ Battlefield | Destination$ Hand | ChangeType$ Land.YouCtrl | ChangeNum$ 1 | Mandatory$ True | DefinedPlayer$ You | Hidden$ True -SVar:TrigBounceOpp:AB$ ChangeZone | Cost$ 0 | Origin$ Battlefield | Destination$ Hand | ChangeType$ Land.YouDontCtrl | ChangeNum$ 1 | Mandatory$ True | DefinedPlayer$ Opponent | Hidden$ True +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.nonToken | TriggerZones$ Battlefield | Execute$ TrigBounce | TriggerDescription$ Whenever a player puts a nontoken creature onto the battlefield, that player returns a land he or she controls to its owner's hand. +SVar:TrigBounce:AB$ ChangeZone | Cost$ 0 | Origin$ Battlefield | Destination$ Hand | ChangeType$ Land | ChangeNum$ 1 | Mandatory$ True | DefinedPlayer$ TriggeredCardController | Chooser$ TriggeredCardController | Hidden$ True SVar:RemRandomDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/overburden.jpg Oracle:Whenever a player puts a nontoken creature onto the battlefield, that player returns a land he or she controls to its owner's hand. \ No newline at end of file diff --git a/res/cardsfolder/s/smelt_ward_gatekeepers.txt b/res/cardsfolder/s/smelt_ward_gatekeepers.txt index 92b2986ee8c..45ebb13bbe9 100644 --- a/res/cardsfolder/s/smelt_ward_gatekeepers.txt +++ b/res/cardsfolder/s/smelt_ward_gatekeepers.txt @@ -5,5 +5,6 @@ PT:2/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Gate.YouCtrl | PresentCompare$ GE2 | Execute$ TrigGainControl | TriggerDescription$ When CARDNAME enters the battlefield, if you control two or more Gates, gain control of target creature an opponent controls until end of turn. Untap that creature. That creature gains haste until end of turn. SVar:TrigGainControl:AB$ GainControl | Cost$ 0 | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | Untap$ True | LoseControl$ EOT | SubAbility$ DBPump SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Haste +SVar:PlayMain1:TRUE SVar:Picture:http://www.wizards.com/global/images/magic/general/smelt_ward_gatekeepers.jpg Oracle:When Smelt-Ward Gatekeepers enters the battlefield, if you control two or more Gates, gain control of target creature an opponent controls until end of turn. Untap that creature. It gains haste until end of turn. \ No newline at end of file diff --git a/res/cardsfolder/s/suqata_assassin.txt b/res/cardsfolder/s/suqata_assassin.txt index f16cfd102b1..cf2abaeb34c 100644 --- a/res/cardsfolder/s/suqata_assassin.txt +++ b/res/cardsfolder/s/suqata_assassin.txt @@ -4,6 +4,6 @@ Types:Creature Human Assassin PT:1/1 K:Fear T:Mode$ AttackerUnblocked | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPoison | TriggerDescription$ Whenever CARDNAME attacks and isn't blocked, defending player gets a poison counter. (A player with ten or more poison counters loses the game.) -SVar:TrigPoison:AB$Poison | Cost$ 0 | Defined$ DefendingPlayer | Num$ 1 +SVar:TrigPoison:AB$ Poison | Cost$ 0 | Defined$ DefendingPlayer | Num$ 1 SVar:Picture:http://www.wizards.com/global/images/magic/general/suqata_assassin.jpg Oracle:Fear (This creature can't be blocked except by artifact creatures and/or black creatures.)\nWhenever Suq'Ata Assassin attacks and isn't blocked, defending player gets a poison counter. (A player with ten or more poison counters loses the game.) \ No newline at end of file diff --git a/res/cardsfolder/s/swamp_mosquito.txt b/res/cardsfolder/s/swamp_mosquito.txt index 293d6fb240b..b78ce089460 100644 --- a/res/cardsfolder/s/swamp_mosquito.txt +++ b/res/cardsfolder/s/swamp_mosquito.txt @@ -4,6 +4,6 @@ Types:Creature Insect PT:0/1 K:Flying T:Mode$ AttackerUnblocked | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPoison | TriggerDescription$ Whenever CARDNAME attacks and isn't blocked, defending player gets a poison counter. (A player with ten or more poison counters loses the game.) -SVar:TrigPoison:AB$Poison | Cost$ 0 | Defined$ DefendingPlayer | Num$ 1 +SVar:TrigPoison:AB$ Poison | Cost$ 0 | Defined$ DefendingPlayer | Num$ 1 SVar:Picture:http://www.wizards.com/global/images/magic/general/swamp_mosquito.jpg Oracle:Flying\nWhenever Swamp Mosquito attacks and isn't blocked, defending player gets a poison counter. (A player with ten or more poison counters loses the game.) \ No newline at end of file diff --git a/res/cardsfolder/z/zealot_il_vec.txt b/res/cardsfolder/z/zealot_il_vec.txt index 12f2b44401f..4a5cbbfd6a2 100644 --- a/res/cardsfolder/z/zealot_il_vec.txt +++ b/res/cardsfolder/z/zealot_il_vec.txt @@ -4,7 +4,7 @@ Types:Creature Human Rebel PT:1/1 K:Shadow T:Mode$ AttackerUnblocked | ValidCard$ Card.Self | Execute$ TrigDamage | OptionalDecider$ You | TriggerDescription$ Whenever CARDNAME attacks and isn't blocked, you may have it deal 1 damage to target creature. If you do, prevent all combat damage CARDNAME would deal this turn. -SVar:TrigDamage:AB$DealDamage | Cost$ 0 | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 1 | SubAbility$ DBPump +SVar:TrigDamage:AB$ DealDamage | Cost$ 0 | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 1 | SubAbility$ DBPump SVar:DBPump:DB$Pump | KW$ HIDDEN Prevent all combat damage that would be dealt by CARDNAME. SVar:Picture:http://www.wizards.com/global/images/magic/general/zealot_il_vec.jpg Oracle:Shadow (This creature can block or be blocked by only creatures with shadow.)\nWhenever Zealot il-Vec attacks and isn't blocked, you may have it deal 1 damage to target creature. If you do, prevent all combat damage Zealot il-Vec would deal this turn. \ No newline at end of file diff --git a/src/main/java/forge/CardPredicates.java b/src/main/java/forge/CardPredicates.java index b1d46323443..f296be02d88 100644 --- a/src/main/java/forge/CardPredicates.java +++ b/src/main/java/forge/CardPredicates.java @@ -109,6 +109,15 @@ public final class CardPredicates { }; } + public static final Predicate sharesColorWith(final Card color) { + return new Predicate() { + @Override + public boolean apply(Card c) { + return c.sharesColorWith(color); + } + }; + } + public static final Predicate possibleBlockers(final Card attacker) { return new Predicate() { @Override diff --git a/src/main/java/forge/card/EditionCollection.java b/src/main/java/forge/card/EditionCollection.java index 62330f70615..d8023df8761 100644 --- a/src/main/java/forge/card/EditionCollection.java +++ b/src/main/java/forge/card/EditionCollection.java @@ -28,6 +28,7 @@ import com.google.common.collect.Lists; import forge.util.IItemReader; import forge.util.storage.StorageBase; +import forge.util.storage.StorageReaderBase; public final class EditionCollection extends StorageBase { @@ -107,7 +108,7 @@ public final class EditionCollection extends StorageBase { */ public IItemReader getBoosterGenerator() { // TODO Auto-generated method stub - return new IItemReader() { + return new StorageReaderBase(null) { @Override public Map readAll() { diff --git a/src/main/java/forge/card/ability/effects/EffectEffect.java b/src/main/java/forge/card/ability/effects/EffectEffect.java index 6fb58c2db95..5cbce705377 100644 --- a/src/main/java/forge/card/ability/effects/EffectEffect.java +++ b/src/main/java/forge/card/ability/effects/EffectEffect.java @@ -188,11 +188,6 @@ public class EffectEffect extends SpellAbilityEffect { eff.setChosenColor(hostCard.getChosenColor()); } - // Remember created effect - if (sa.hasParam("RememberEffect")) { - game.getCardState(hostCard).addRemembered(eff); - } - // Duration final String duration = sa.getParam("Duration"); if ((duration == null) || !duration.equals("Permanent")) { diff --git a/src/main/java/forge/card/cost/CostDiscard.java b/src/main/java/forge/card/cost/CostDiscard.java index 7ab49abe04f..07797188b95 100644 --- a/src/main/java/forge/card/cost/CostDiscard.java +++ b/src/main/java/forge/card/cost/CostDiscard.java @@ -192,30 +192,42 @@ public class CostDiscard extends CostPartWithList { } return executePayment(ability, Aggregates.random(handList, c)); - + } + if (discardType.contains("+WithSameName")) { + String type = discardType.replace("+WithSameName", ""); + handList = CardLists.getValidCards(handList, type.split(";"), activator, ability.getSourceCard()); + final List landList2 = handList; + handList = CardLists.filter(handList, new Predicate() { + @Override + public boolean apply(final Card c) { + for (Card card : landList2) { + if (!card.equals(c) && card.getName().equals(c.getName())) { + return true; + } + } + return false; + } + }); + if (c == 0) return true; + List discarded = new ArrayList(); + while (c > 0) { + InputSelectCards inp = new InputSelectCardsFromList(1, 1, handList); + inp.setMessage("Select one of the cards with the same name to discard. Already chosen: " + discarded); + inp.setCancelAllowed(true); + Singletons.getControl().getInputQueue().setInputAndWait(inp); + if (inp.hasCancelled()) + return false; + final Card first = inp.getSelected().get(0); + discarded.add(first); + handList = CardLists.filter(handList, CardPredicates.nameEquals(first.getName())); + handList.remove(first); + c--; + } + return executePayment(ability, discarded); } else { String type = new String(discardType); - boolean sameName = false; - if (type.contains("+WithSameName")) { - sameName = true; - type = type.replace("+WithSameName", ""); - } final String[] validType = type.split(";"); handList = CardLists.getValidCards(handList, validType, activator, ability.getSourceCard()); - final List landList2 = handList; - if (sameName) { - handList = CardLists.filter(handList, new Predicate() { - @Override - public boolean apply(final Card c) { - for (Card card : landList2) { - if (!card.equals(c) && card.getName().equals(c.getName())) { - return true; - } - } - return false; - } - }); - } if (c == null) { final String sVar = ability.getSVar(amount); diff --git a/src/main/java/forge/card/cost/CostReveal.java b/src/main/java/forge/card/cost/CostReveal.java index c12ca771d6d..5d999d720e2 100644 --- a/src/main/java/forge/card/cost/CostReveal.java +++ b/src/main/java/forge/card/cost/CostReveal.java @@ -20,10 +20,12 @@ package forge.card.cost; import java.util.ArrayList; import java.util.List; +import com.google.common.base.Predicate; import com.google.common.collect.Lists; import forge.Card; import forge.CardLists; +import forge.CardPredicates; import forge.Singletons; import forge.card.ability.AbilityUtils; import forge.card.spellability.SpellAbility; @@ -40,7 +42,7 @@ import forge.gui.input.InputSelectCardsFromList; */ public class CostReveal extends CostPartWithList { // Reveal - + /** * Instantiates a new cost reveal. * @@ -83,6 +85,22 @@ public class CostReveal extends CostPartWithList { } } else if (this.getType().equals("Hand")) { return true; + } else if (this.getType().equals("SameColor")) { + if (amount == null) { + return false; + } else { + for (final Card card : handList) { + if (CardLists.filter(handList, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.sharesColorWith(card); + } + }).size() >= amount) { + return true; + } + } + return false; + } } else { if (ability.isSpell()) { handList.remove(source); // can't pay for itself @@ -117,6 +135,10 @@ public class CostReveal extends CostPartWithList { if (this.getType().equals("Hand")) return new PaymentDecision(new ArrayList(ai.getCardsIn(ZoneType.Hand))); + if (this.getType().equals("SameColor")) { + return null; + } + hand = CardLists.getValidCards(hand, type.split(";"), ai, source); Integer c = this.convertAmount(); if (c == null) { @@ -152,6 +174,37 @@ public class CostReveal extends CostPartWithList { for(Card c : activator.getCardsIn(ZoneType.Hand)) executePayment(ability, c); return true; + } else if (this.getType().equals("SameColor")) { + Integer num = this.convertAmount(); + List handList = activator.getCardsIn(ZoneType.Hand); + final List handList2 = handList; + handList = CardLists.filter(handList, new Predicate() { + @Override + public boolean apply(final Card c) { + for (Card card : handList2) { + if (!card.equals(c) && card.sharesColorWith(c)) { + return true; + } + } + return false; + } + }); + if (num == 0) return true; + List revealed = new ArrayList(); + while (num > 0) { + InputSelectCards inp = new InputSelectCardsFromList(1, 1, handList); + inp.setMessage("Select one of cards to reveal. Already chosen:" + revealed); + inp.setCancelAllowed(true); + Singletons.getControl().getInputQueue().setInputAndWait(inp); + if (inp.hasCancelled()) + return false; + final Card first = inp.getSelected().get(0); + revealed.add(first); + handList = CardLists.filter(handList, CardPredicates.sharesColorWith(first)); + handList.remove(first); + num--; + } + return executePayment(ability, revealed); } else { Integer num = this.convertAmount(); @@ -193,6 +246,8 @@ public class CostReveal extends CostPartWithList { sb.append(this.getType()); } else if (this.getType().equals("Hand")) { return ("Reveal you hand"); + } else if (this.getType().equals("SameColor")) { + return ("Reveal " + i + " cards from your hand that share a color"); } else { final StringBuilder desc = new StringBuilder(); diff --git a/src/main/java/forge/card/cost/CostTapType.java b/src/main/java/forge/card/cost/CostTapType.java index 9de27ad8df5..24bbafb49de 100644 --- a/src/main/java/forge/card/cost/CostTapType.java +++ b/src/main/java/forge/card/cost/CostTapType.java @@ -161,6 +161,10 @@ public class CostTapType extends CostPartWithList { public final boolean payHuman(final SpellAbility ability, final Game game) { List typeList = new ArrayList(ability.getActivatingPlayer().getCardsIn(ZoneType.Battlefield)); String type = this.getType(); + final String amount = this.getAmount(); + final Card source = ability.getSourceCard(); + Integer c = this.convertAmount(); + boolean sameType = false; if (type.contains("sharesCreatureTypeWith")) { sameType = true; @@ -168,6 +172,16 @@ public class CostTapType extends CostPartWithList { } typeList = CardLists.getValidCards(typeList, type.split(";"), ability.getActivatingPlayer(), ability.getSourceCard()); typeList = CardLists.filter(typeList, Presets.UNTAPPED); + if (c == null) { + final String sVar = ability.getSVar(amount); + // Generalize this + if (sVar.equals("XChoice")) { + c = Cost.chooseXValue(source, ability, typeList.size()); + } else { + c = AbilityUtils.calculateAmount(source, amount, ability); + } + } + if (sameType) { final List List2 = typeList; typeList = CardLists.filter(typeList, new Predicate() { @@ -181,21 +195,28 @@ public class CostTapType extends CostPartWithList { return false; } }); - } - - final String amount = this.getAmount(); - final Card source = ability.getSourceCard(); - Integer c = this.convertAmount(); - if (c == null) { - final String sVar = ability.getSVar(amount); - // Generalize this - if (sVar.equals("XChoice")) { - c = Cost.chooseXValue(source, ability, typeList.size()); - } else { - c = AbilityUtils.calculateAmount(source, amount, ability); + if (c == 0) return true; + List tapped = new ArrayList(); + while (c > 0) { + InputSelectCards inp = new InputSelectCardsFromList(1, 1, typeList); + inp.setMessage("Select one of the cards to tap. Already chosen: " + tapped); + inp.setCancelAllowed(true); + Singletons.getControl().getInputQueue().setInputAndWait(inp); + if (inp.hasCancelled()) + return false; + final Card first = inp.getSelected().get(0); + tapped.add(first); + typeList = CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.sharesCreatureTypeWith(first); + } + }); + typeList.remove(first); + c--; } - } - + return executePayment(ability, tapped); + } InputSelectCards inp = new InputSelectCardsFromList(c, c, typeList); inp.setMessage("Select a " + getDescriptiveType() + " to tap (%d left)"); diff --git a/src/main/java/forge/control/FControl.java b/src/main/java/forge/control/FControl.java index d0fc52ced9d..efce9c8f6a6 100644 --- a/src/main/java/forge/control/FControl.java +++ b/src/main/java/forge/control/FControl.java @@ -29,7 +29,6 @@ import java.util.List; import javax.swing.ImageIcon; import javax.swing.JLayeredPane; import javax.swing.SwingUtilities; -import javax.swing.UIManager; import javax.swing.WindowConstants; import forge.Card; @@ -61,8 +60,11 @@ import forge.gui.match.controllers.CMessage; import forge.gui.match.controllers.CStack; import forge.gui.match.nonsingleton.VField; import forge.gui.match.views.VAntes; +import forge.gui.menubar.FMenuBar; +import forge.gui.menubar.MenuUtil; import forge.gui.toolbox.FSkin; import forge.net.FServer; +import forge.properties.ForgeLookAndFeel; import forge.properties.ForgePreferences.FPref; import forge.properties.NewConstants; import forge.quest.QuestController; @@ -81,7 +83,8 @@ import forge.view.FView; */ public enum FControl { instance; - + + private FMenuBar menuBar; private List shortcuts; private JLayeredPane display; private Screens state = Screens.UNKNOWN; @@ -166,13 +169,13 @@ public enum FControl { public void initialize() { // Preloads skin components (using progress bar). FSkin.loadFull(); - - //This must be done here or at least between the skin being loaded and any FTabbedPanes being created. - //Why,Swing? Why is this not a property of JTabbbedPane? - UIManager.put("TabbedPane.selected", FSkin.getColor(FSkin.Colors.CLR_ACTIVE)); - UIManager.put("TabbedPane.contentOpaque", FSkin.getColor(FSkin.Colors.CLR_THEME)); - UIManager.put("TabbedPane.unselectedBackground", FSkin.getColor(FSkin.Colors.CLR_THEME2)); - setComboBoxLookAndFeel(); + + // This must be done here or at least between the skin being loaded + // and any GUI controls being created. + FSkin.setProgessBarMessage("Setting look and feel..."); + setForgeLookAndFeel(); + + createMenuBar(); this.shortcuts = KeyboardShortcuts.attachKeyboardShortcuts(); this.display = FView.SINGLETON_INSTANCE.getLpnDocument(); @@ -208,21 +211,16 @@ public enum FControl { public void run() { Singletons.getView().initialize(); } }); } - /** - * @see UIManager Defaults - */ - private void setComboBoxLookAndFeel() { - if (Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_THEMED_COMBOBOX)) { - UIManager.put("ComboBox.background", FSkin.getColor(FSkin.Colors.CLR_THEME2)); - UIManager.put("ComboBox.foreground", FSkin.getColor(FSkin.Colors.CLR_TEXT)); - UIManager.put("ComboBox.selectionBackground", FSkin.getColor(FSkin.Colors.CLR_ACTIVE)); - UIManager.put("ComboBox.selectionForeground", FSkin.getColor(FSkin.Colors.CLR_TEXT)); - UIManager.put("ComboBox.disabledBackground", FSkin.getColor(FSkin.Colors.CLR_THEME2)); - UIManager.put("ComboBox.disabledForeground", FSkin.getColor(FSkin.Colors.CLR_THEME2).darker()); - UIManager.put("Button.select", FSkin.getColor(FSkin.Colors.CLR_ACTIVE)); - UIManager.put("ComboBox.font", FSkin.getFont(UIManager.getFont("ComboBox.font").getSize())); - } + private void setForgeLookAndFeel() { + ForgeLookAndFeel laf = new ForgeLookAndFeel(); + laf.setForgeLookAndFeel(Singletons.getView().getFrame()); } + + private void createMenuBar() { + if (MenuUtil.isMenuBarVisible()) { + this.menuBar = new FMenuBar(Singletons.getView().getFrame()); + } + } /** * Switches between display states in top level JFrame. diff --git a/src/main/java/forge/deck/CardCollections.java b/src/main/java/forge/deck/CardCollections.java index 902580a7d77..50ac0cd3aee 100644 --- a/src/main/java/forge/deck/CardCollections.java +++ b/src/main/java/forge/deck/CardCollections.java @@ -49,7 +49,7 @@ public class CardCollections { public CardCollections() { StopWatch sw = new StopWatch(); sw.start(); - this.constructed = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_CONSTRUCTED_DIR), true)); + this.constructed = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_CONSTRUCTED_DIR), true), true); this.draft = new StorageImmediatelySerialized(new DeckGroupSerializer(new File(NewConstants.DECK_DRAFT_DIR))); this.sealed = new StorageImmediatelySerialized(new DeckGroupSerializer(new File(NewConstants.DECK_SEALED_DIR))); this.cube = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_CUBE_DIR))); diff --git a/src/main/java/forge/deck/Deck.java b/src/main/java/forge/deck/Deck.java index ea716322215..4ce33c9a83f 100644 --- a/src/main/java/forge/deck/Deck.java +++ b/src/main/java/forge/deck/Deck.java @@ -77,7 +77,14 @@ public class Deck extends DeckBase implements Iterable { +public abstract class DeckBase implements Serializable, Comparable, InventoryItem { private static final long serialVersionUID = -7538150536939660052L; // gameType is from Constant.GameType, like GameType.Regular diff --git a/src/main/java/forge/deck/DeckGroup.java b/src/main/java/forge/deck/DeckGroup.java index 13147718fb3..03b7abae2a2 100644 --- a/src/main/java/forge/deck/DeckGroup.java +++ b/src/main/java/forge/deck/DeckGroup.java @@ -135,4 +135,13 @@ public class DeckGroup extends DeckBase { } }; + /* (non-Javadoc) + * @see forge.item.InventoryItem#getItemType() + */ + @Override + public String getItemType() { + // TODO Auto-generated method stub + return "Set of limited mode decks"; + } + } diff --git a/src/main/java/forge/deck/DeckgenUtil.java b/src/main/java/forge/deck/DeckgenUtil.java index 5c505afc302..48cc3841795 100644 --- a/src/main/java/forge/deck/DeckgenUtil.java +++ b/src/main/java/forge/deck/DeckgenUtil.java @@ -105,7 +105,16 @@ public class DeckgenUtil { * @return {@link forge.deck.Deck} */ public static Deck getConstructedDeck(final String selection) { - return Singletons.getModel().getDecks().getConstructed().get(selection); + IStorage path = Singletons.getModel().getDecks().getConstructed(); + String name = selection; + int idxSlash = name.indexOf('/'); + while( idxSlash > 0 ) { + String sf = name.substring(0, idxSlash).trim(); + path = path.getFolders().get(sf); + name = name.substring(idxSlash+1).trim(); + idxSlash = name.indexOf('/'); + }; + return path.get(name); } public static Deck getPreconDeck(String selection) { diff --git a/src/main/java/forge/deck/io/DeckGroupSerializer.java b/src/main/java/forge/deck/io/DeckGroupSerializer.java index 4c8229250e3..96a9a47a7d0 100644 --- a/src/main/java/forge/deck/io/DeckGroupSerializer.java +++ b/src/main/java/forge/deck/io/DeckGroupSerializer.java @@ -23,6 +23,8 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; +import com.google.common.collect.ImmutableList; + import forge.deck.Deck; import forge.deck.DeckGroup; import forge.util.FileUtil; @@ -111,7 +113,7 @@ public class DeckGroupSerializer extends StorageReaderFolder implemen * @return the file */ public File makeFileFor(final DeckGroup decks) { - return new File(this.getDirectory(), decks.getBestFileName()); + return new File(this.directory, decks.getBestFileName()); } /* @@ -134,4 +136,10 @@ public class DeckGroupSerializer extends StorageReaderFolder implemen }; } + @Override + public Iterable getSubFolders() { + // Sealed decks are kept in separate folders, no further drilling possible + return ImmutableList.of(); + } + } diff --git a/src/main/java/forge/deck/io/DeckSerializer.java b/src/main/java/forge/deck/io/DeckSerializer.java index 292db1ae134..1d98751d261 100644 --- a/src/main/java/forge/deck/io/DeckSerializer.java +++ b/src/main/java/forge/deck/io/DeckSerializer.java @@ -40,6 +40,7 @@ import forge.properties.NewConstants; import forge.util.FileSection; import forge.util.FileSectionManual; import forge.util.FileUtil; +import forge.util.IItemReader; import forge.util.IItemSerializer; import forge.util.storage.StorageReaderFolder; import freemarker.template.Configuration; @@ -194,7 +195,7 @@ public class DeckSerializer extends StorageReaderFolder implements IItemSe } public File makeFileFor(final Deck deck) { - return new File(this.getDirectory(), deck.getBestFileName() + FILE_EXTENSION); + return new File(this.directory, deck.getBestFileName() + FILE_EXTENSION); } @Override @@ -245,4 +246,14 @@ public class DeckSerializer extends StorageReaderFolder implements IItemSe return null; } + + /* (non-Javadoc) + * @see forge.util.storage.StorageReaderBase#getReaderForFolder(java.io.File) + */ + @Override + public IItemReader getReaderForFolder(File subfolder) { + if ( !subfolder.getParentFile().equals(directory) ) + throw new UnsupportedOperationException("Only child folders of " + directory + " may be processed"); + return new DeckSerializer(subfolder, false); + } } diff --git a/src/main/java/forge/game/ai/AiBlockController.java b/src/main/java/forge/game/ai/AiBlockController.java index 1a071f93d06..beeef3fd36a 100644 --- a/src/main/java/forge/game/ai/AiBlockController.java +++ b/src/main/java/forge/game/ai/AiBlockController.java @@ -18,6 +18,7 @@ package forge.game.ai; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import com.google.common.base.Predicate; @@ -28,6 +29,9 @@ import forge.CardLists; import forge.CardPredicates; import forge.CounterType; import forge.GameEntity; +import forge.card.TriggerReplacementBase; +import forge.card.trigger.Trigger; +import forge.card.trigger.TriggerType; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.player.Player; @@ -197,7 +201,7 @@ public class AiBlockController { // 1.Blockers that can destroy the attacker but won't get // destroyed killingBlockers = getKillingBlockers(combat, attacker, safeBlockers); - if (killingBlockers.size() > 0) { + if (!killingBlockers.isEmpty()) { blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); } else if (!attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) { blocker = ComputerUtilCard.getWorstCreatureAI(safeBlockers); @@ -215,11 +219,34 @@ public class AiBlockController { } } // 4.Blockers that can destroy the attacker and are worth less - if (blocker == null && killingBlockers.size() > 0) { + if (blocker == null && !killingBlockers.isEmpty()) { final Card worst = ComputerUtilCard.getWorstCreatureAI(killingBlockers); + int value = ComputerUtilCard.evaluateCreature(attacker); + + // check for triggers when unblocked + for (Trigger trigger : attacker.getTriggers()) { + final HashMap trigParams = trigger.getMapParams(); + TriggerType mode = trigger.getMode(); - if ((ComputerUtilCard.evaluateCreature(worst) + diff) < ComputerUtilCard - .evaluateCreature(attacker)) { + if (!trigger.requirementsCheck(attacker.getGame())) { + continue; + } + + if (mode == TriggerType.DamageDone) { + if (TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidSource").split(","), attacker) + && attacker.getNetCombatDamage() > 0 + && (!trigParams.containsKey("ValidTarget") + || TriggerReplacementBase.matchesValid(combat.getDefenderByAttacker(attacker), trigParams.get("ValidTarget").split(","), attacker))) { + value += 50; + } + } else if (mode == TriggerType.AttackerUnblocked) { + if (TriggerReplacementBase.matchesValid(attacker, trigParams.get("ValidCard").split(","), attacker)) { + value += 50; + } + } + } + + if ((ComputerUtilCard.evaluateCreature(worst) + diff) < value) { blocker = worst; } } @@ -378,14 +405,13 @@ public class AiBlockController { List possibleBlockers = getPossibleBlockers(combat, attacker, blockersLeft, true); killingBlockers = getKillingBlockers(combat, attacker, possibleBlockers); - if ((killingBlockers.size() > 0) && ComputerUtilCombat.lifeInDanger(ai, combat)) { + if (!killingBlockers.isEmpty() && ComputerUtilCombat.lifeInDanger(ai, combat)) { final Card blocker = ComputerUtilCard.getWorstCreatureAI(killingBlockers); combat.addBlocker(attacker, blocker); currentAttackers.remove(attacker); } } attackersLeft = (new ArrayList(currentAttackers)); - } // Chump Blocks (should only be made if life is in danger) diff --git a/src/main/java/forge/game/limited/CustomLimited.java b/src/main/java/forge/game/limited/CustomLimited.java index 10b6bf52e6d..e84368d76f9 100644 --- a/src/main/java/forge/game/limited/CustomLimited.java +++ b/src/main/java/forge/game/limited/CustomLimited.java @@ -82,6 +82,15 @@ public class CustomLimited extends DeckBase { return this.getName(); } + /* (non-Javadoc) + * @see forge.item.InventoryItem#getItemType() + */ + @Override + public String getItemType() { + // TODO Auto-generated method stub + return "Limited game setup"; + } + /** * Parses the. * diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorCommander.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorCommander.java index da39439d537..07b75c4f50e 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorCommander.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorCommander.java @@ -38,6 +38,7 @@ import forge.gui.deckeditor.views.VDeckgen; import forge.gui.framework.DragCell; import forge.gui.framework.EDocID; import forge.gui.toolbox.FLabel; +import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.ItemManager; import forge.gui.toolbox.itemmanager.SItemManagerIO; import forge.gui.toolbox.itemmanager.SItemManagerUtil; @@ -86,9 +87,11 @@ public final class CEditorCommander extends ACEditorBase { commanderPool = ItemPool.createFrom(CardDb.instance().getAllCards(Predicates.compose(Predicates.and(CardRulesPredicates.Presets.IS_CREATURE,CardRulesPredicates.Presets.IS_LEGENDARY), PaperCard.FN_GET_RULES)),PaperCard.class); normalPool = ItemPool.createFrom(CardDb.instance().getAllCards(), PaperCard.class); - final ItemManager catalogManager = new ItemManager(PaperCard.class, VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), true); - final ItemManager deckManager = new ItemManager(PaperCard.class, VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), true); + boolean wantUnique = SItemManagerIO.getPref(EditorPreference.display_unique_only); + final CardManager catalogManager = new CardManager(VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), wantUnique); + final CardManager deckManager = new CardManager(VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), wantUnique); + VCardCatalog.SINGLETON_INSTANCE.setItemManager(catalogManager); VCurrentDeck.SINGLETON_INSTANCE.setItemManager(deckManager); diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java index 49c6b7c7f19..3751f5055ed 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java @@ -35,7 +35,7 @@ import forge.gui.deckeditor.views.VCardCatalog; import forge.gui.deckeditor.views.VCurrentDeck; import forge.gui.framework.EDocID; import forge.gui.toolbox.FLabel; -import forge.gui.toolbox.itemmanager.ItemManager; +import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.SItemManagerIO; import forge.gui.toolbox.itemmanager.SItemManagerUtil; import forge.gui.toolbox.itemmanager.SItemManagerIO.EditorPreference; @@ -92,8 +92,8 @@ public final class CEditorConstructed extends ACEditorBase { boolean wantUnique = SItemManagerIO.getPref(EditorPreference.display_unique_only); - final ItemManager catalogManager = new ItemManager(PaperCard.class, VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), wantUnique); - final ItemManager deckManager = new ItemManager(PaperCard.class, VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), wantUnique); + final CardManager catalogManager = new CardManager(VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), wantUnique); + final CardManager deckManager = new CardManager(VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), wantUnique); VCardCatalog.SINGLETON_INSTANCE.setItemManager(catalogManager); VCurrentDeck.SINGLETON_INSTANCE.setItemManager(deckManager); diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorDraftingProcess.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorDraftingProcess.java index ab2aa028905..5b89e430461 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorDraftingProcess.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorDraftingProcess.java @@ -37,7 +37,7 @@ import forge.gui.deckeditor.views.VCurrentDeck; import forge.gui.deckeditor.views.VDeckgen; import forge.gui.framework.DragCell; import forge.gui.home.sanctioned.CSubmenuDraft; -import forge.gui.toolbox.itemmanager.ItemManager; +import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.table.SColumnUtil; import forge.item.PaperCard; import forge.item.InventoryItem; @@ -64,8 +64,8 @@ public class CEditorDraftingProcess extends ACEditorBase { * Updates the deck editor UI as necessary draft selection mode. */ public CEditorDraftingProcess() { - final ItemManager catalogManager = new ItemManager(PaperCard.class, VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), false); - final ItemManager deckManager = new ItemManager(PaperCard.class, VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), false); + final CardManager catalogManager = new CardManager(VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), false); + final CardManager deckManager = new CardManager(VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), false); VCardCatalog.SINGLETON_INSTANCE.setItemManager(catalogManager); VCurrentDeck.SINGLETON_INSTANCE.setItemManager(deckManager); diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorLimited.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorLimited.java index 824aed8da01..fb384b7495e 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorLimited.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorLimited.java @@ -30,7 +30,7 @@ import forge.gui.deckeditor.views.VDeckgen; import forge.gui.framework.DragCell; import forge.gui.home.sanctioned.CSubmenuDraft; import forge.gui.home.sanctioned.CSubmenuSealed; -import forge.gui.toolbox.itemmanager.ItemManager; +import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.SItemManagerUtil; import forge.gui.toolbox.itemmanager.table.SColumnUtil; import forge.item.PaperCard; @@ -59,8 +59,8 @@ public final class CEditorLimited extends ACEditorBase { * @param deckMap0   {@link forge.deck.DeckGroup}<{@link forge.util.storage.IStorage}> */ public CEditorLimited(final IStorage deckMap0) { - final ItemManager catalogManager = new ItemManager(PaperCard.class, VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), false); - final ItemManager deckManager = new ItemManager(PaperCard.class, VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), false); + final CardManager catalogManager = new CardManager(VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), false); + final CardManager deckManager = new CardManager(VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), false); VCardCatalog.SINGLETON_INSTANCE.setItemManager(catalogManager); VCurrentDeck.SINGLETON_INSTANCE.setItemManager(deckManager); diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorQuest.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorQuest.java index 1caa013e288..c88848a706c 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorQuest.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorQuest.java @@ -38,7 +38,7 @@ import forge.gui.deckeditor.views.VDeckgen; import forge.gui.framework.DragCell; import forge.gui.home.quest.CSubmenuQuestDecks; import forge.gui.toolbox.FLabel; -import forge.gui.toolbox.itemmanager.ItemManager; +import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.SItemManagerUtil; import forge.gui.toolbox.itemmanager.table.TableColumnInfo; import forge.gui.toolbox.itemmanager.table.SColumnUtil; @@ -95,8 +95,8 @@ public final class CEditorQuest extends ACEditorBase { public CEditorQuest(final QuestController questData0) { this.questData = questData0; - final ItemManager catalogManager = new ItemManager(PaperCard.class, VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), false); - final ItemManager deckManager = new ItemManager(PaperCard.class, VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), false); + final CardManager catalogManager = new CardManager(VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), false); + final CardManager deckManager = new CardManager(VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), false); catalogManager.setAlwaysNonUnique(true); deckManager.setAlwaysNonUnique(true); diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorQuestCardShop.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorQuestCardShop.java index 6b503cf0312..12d5f302eb2 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorQuestCardShop.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorQuestCardShop.java @@ -48,7 +48,7 @@ import forge.gui.framework.DragCell; import forge.gui.home.quest.CSubmenuQuestDecks; import forge.gui.toolbox.FLabel; import forge.gui.toolbox.FSkin; -import forge.gui.toolbox.itemmanager.ItemManager; +import forge.gui.toolbox.itemmanager.InventoryItemManager; import forge.gui.toolbox.itemmanager.SItemManagerUtil; import forge.gui.toolbox.itemmanager.table.TableColumnInfo; import forge.gui.toolbox.itemmanager.table.SColumnUtil; @@ -127,8 +127,8 @@ public final class CEditorQuestCardShop extends ACEditorBase catalogManager = new ItemManager(InventoryItem.class, VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), false); - final ItemManager deckManager = new ItemManager(InventoryItem.class, VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), false); + final InventoryItemManager catalogManager = new InventoryItemManager(VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), false); + final InventoryItemManager deckManager = new InventoryItemManager(VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), false); catalogManager.setAlwaysNonUnique(true); deckManager.setAlwaysNonUnique(true); diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorVariant.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorVariant.java index 93059f9151a..53f45041308 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorVariant.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorVariant.java @@ -34,7 +34,7 @@ import forge.gui.deckeditor.views.VCurrentDeck; import forge.gui.deckeditor.views.VDeckgen; import forge.gui.framework.DragCell; import forge.gui.framework.EDocID; -import forge.gui.toolbox.itemmanager.ItemManager; +import forge.gui.toolbox.itemmanager.CardManager; import forge.gui.toolbox.itemmanager.SItemManagerUtil; import forge.gui.toolbox.itemmanager.table.TableColumnInfo; import forge.gui.toolbox.itemmanager.table.SColumnUtil; @@ -74,8 +74,8 @@ public final class CEditorVariant extends ACEditorBase { cardPoolCondition = poolCondition; exitToScreen = exitTo; - final ItemManager catalogManager = new ItemManager(PaperCard.class, VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), true); - final ItemManager deckManager = new ItemManager(PaperCard.class, VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), true); + final CardManager catalogManager = new CardManager(VCardCatalog.SINGLETON_INSTANCE.getStatLabels(), true); + final CardManager deckManager = new CardManager(VCurrentDeck.SINGLETON_INSTANCE.getStatLabels(), true); VCardCatalog.SINGLETON_INSTANCE.setItemManager(catalogManager); VCurrentDeck.SINGLETON_INSTANCE.setItemManager(deckManager); diff --git a/src/main/java/forge/gui/framework/SResizingUtil.java b/src/main/java/forge/gui/framework/SResizingUtil.java index 7bdc95cbcc3..65837a0dd4f 100644 --- a/src/main/java/forge/gui/framework/SResizingUtil.java +++ b/src/main/java/forge/gui/framework/SResizingUtil.java @@ -1,406 +1,407 @@ -package forge.gui.framework; - -import java.awt.Component; -import java.awt.Cursor; -import java.awt.Rectangle; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.ComponentListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionAdapter; -import java.awt.event.MouseMotionListener; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import javax.swing.JPanel; - -import forge.gui.FNetOverlay; -import forge.gui.toolbox.FAbsolutePositioner; -import forge.gui.toolbox.FOverlay; -import forge.view.FView; - -/** - * Package-private utilities for resizing drag behavior using - * the draggable panels registered in FView. - * - *

(S at beginning of class name denotes a static factory.) - */ -public final class SResizingUtil { - private static final List LEFT_PANELS = new ArrayList(); - private static final List RIGHT_PANELS = new ArrayList(); - private static final List TOP_PANELS = new ArrayList(); - private static final List BOTTOM_PANELS = new ArrayList(); - - 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) { - FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); - } - - @Override - public void mouseExited(final MouseEvent e) { - FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - - @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) { - FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR)); - } - - @Override - public void mouseExited(final MouseEvent e) { - FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - - @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 cells = FView.SINGLETON_INSTANCE.getDragCells(); - final JPanel pnlContent = FView.SINGLETON_INSTANCE.getPnlContent(); - final JPanel pnlInsets = FView.SINGLETON_INSTANCE.getPnlInsets(); - - Rectangle mainBounds = FView.SINGLETON_INSTANCE.getFrame().getContentPane().getBounds(); - 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 existingComponents = new HashSet(); - for (Component c : pnlContent.getComponents()) { - existingComponents.add(c); - } - - // This is the core of the pixel-perfect layout. To avoid ±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) { - 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 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 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) { - 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 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 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() { - 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; - } -} +package forge.gui.framework; + +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Rectangle; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionAdapter; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.swing.JPanel; + +import forge.gui.FNetOverlay; +import forge.gui.toolbox.FAbsolutePositioner; +import forge.gui.toolbox.FOverlay; +import forge.view.FView; + +/** + * Package-private utilities for resizing drag behavior using + * the draggable panels registered in FView. + * + *

(S at beginning of class name denotes a static factory.) + */ +public final class SResizingUtil { + private static final List LEFT_PANELS = new ArrayList(); + private static final List RIGHT_PANELS = new ArrayList(); + private static final List TOP_PANELS = new ArrayList(); + private static final List BOTTOM_PANELS = new ArrayList(); + + 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) { + FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); + } + + @Override + public void mouseExited(final MouseEvent e) { + FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + @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) { + FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR)); + } + + @Override + public void mouseExited(final MouseEvent e) { + FView.SINGLETON_INSTANCE.getLpnDocument().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + @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 cells = FView.SINGLETON_INSTANCE.getDragCells(); + final JPanel pnlContent = FView.SINGLETON_INSTANCE.getPnlContent(); + final JPanel pnlInsets = FView.SINGLETON_INSTANCE.getPnlInsets(); + + Rectangle mainBounds = FView.SINGLETON_INSTANCE.getFrame().getContentPane().getBounds(); + mainBounds.y = 0; // Play nicely with MenuBar if visible or not. + 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 existingComponents = new HashSet(); + for (Component c : pnlContent.getComponents()) { + existingComponents.add(c); + } + + // This is the core of the pixel-perfect layout. To avoid ±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) { + 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 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 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) { + 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 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 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() { + 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; + } +} diff --git a/src/main/java/forge/gui/menubar/FMenuBar.java b/src/main/java/forge/gui/menubar/FMenuBar.java new file mode 100644 index 00000000000..6032fc4d881 --- /dev/null +++ b/src/main/java/forge/gui/menubar/FMenuBar.java @@ -0,0 +1,51 @@ +package forge.gui.menubar; + +import java.awt.Component; +import java.awt.Dimension; + +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; + +import forge.gui.menus.ForgeMenu; +import forge.gui.menus.HelpMenu; + +@SuppressWarnings("serial") +public class FMenuBar extends JMenuBar { + + public FMenuBar(JFrame f) { + f.setJMenuBar(this); + setPreferredSize(new Dimension(f.getWidth(), 26)); + setupMenuBar(null); + } + + public void setupMenuBar(IMenuProvider provider) { + removeAll(); + add(ForgeMenu.getMenu()); + addProviderMenus(provider); + add(HelpMenu.getMenu()); + repaint(); + } + + private void addProviderMenus(IMenuProvider provider) { + if (provider != null && provider.getMenus() != null) { + for (JMenu m : provider.getMenus()) { + m.setBorderPainted(false); + add(m); + } + } + } + + /** + * Enables or disables the MenuBar. + *

+ * Note: Disabling a component does not disable its children. + */ + public void setMenuBarEnabled(boolean isEnabled) { + setEnabled(isEnabled); + for (Component c : getComponents()) { + c.setEnabled(isEnabled); + } + } + +} diff --git a/src/main/java/forge/gui/menubar/IMenuProvider.java b/src/main/java/forge/gui/menubar/IMenuProvider.java new file mode 100644 index 00000000000..061aacf03c3 --- /dev/null +++ b/src/main/java/forge/gui/menubar/IMenuProvider.java @@ -0,0 +1,19 @@ +package forge.gui.menubar; + +import java.util.List; + +import javax.swing.JMenu; + +/** + * By implementing this interface a class can add menus to the menu bar + * by calling the {@code MenuBarManager.SetupMenuBar()} method. + * + */ +public interface IMenuProvider { + + /** + * Returns a list of JMenu objects for display in MenuBar. + */ + List getMenus(); + +} diff --git a/src/main/java/forge/gui/menubar/MenuUtil.java b/src/main/java/forge/gui/menubar/MenuUtil.java new file mode 100644 index 00000000000..35912930709 --- /dev/null +++ b/src/main/java/forge/gui/menubar/MenuUtil.java @@ -0,0 +1,61 @@ +package forge.gui.menubar; + +import java.awt.Toolkit; +import java.io.IOException; + +import javax.swing.ImageIcon; +import javax.swing.JOptionPane; +import javax.swing.KeyStroke; + +import forge.Singletons; +import forge.gui.toolbox.FSkin; +import forge.gui.toolbox.FSkin.SkinProp; +import forge.gui.toolbox.imaging.ImageUtil; +import forge.properties.ForgePreferences; +import forge.properties.ForgePreferences.FPref; + +public final class MenuUtil { + private MenuUtil() { } + + private static ForgePreferences prefs = Singletons.getModel().getPreferences(); + + // Get appropriate OS standard accelerator key for menu shortcuts. + private static final int DEFAULT_MenuShortcutKeyMask = + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + + public static boolean isMenuBarVisible() { + return !prefs.getPrefBoolean(FPref.UI_HIDE_MENUBAR); + } + + public static void openUrlInBrowser(String url) { + try { + java.awt.Desktop.getDesktop().browse(java.net.URI.create(url)); + } catch (IOException e) { + // Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log. + e.printStackTrace(); + } + } + + public static ImageIcon getMenuIcon(SkinProp ico) { + return ImageUtil.getMenuIcon(FSkin.getIcon(ico)); + } + + public static KeyStroke getAcceleratorKey(int key) { + return KeyStroke.getKeyStroke(key, DEFAULT_MenuShortcutKeyMask); + } + + public static boolean getUserConfirmation(String prompt, String dialogTitle) { + Object[] options = {"Yes", "No"}; + int reply = JOptionPane.showOptionDialog( + null, + prompt, + dialogTitle, + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[1]); + return (reply == JOptionPane.YES_OPTION); + } + +} diff --git a/src/main/java/forge/gui/menus/ForgeMenu.java b/src/main/java/forge/gui/menus/ForgeMenu.java new file mode 100644 index 00000000000..50019ff512c --- /dev/null +++ b/src/main/java/forge/gui/menus/ForgeMenu.java @@ -0,0 +1,70 @@ +package forge.gui.menus; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import forge.Singletons; +import forge.control.RestartUtil; +import forge.control.FControl.Screens; +import forge.gui.menubar.MenuUtil; + +public final class ForgeMenu { + private ForgeMenu() { } + + public static JMenu getMenu() { + JMenu menu = new JMenu("Forge"); + menu.setMnemonic(KeyEvent.VK_F); + menu.add(getMenuItem_Restart()); + menu.add(getMenuItem_Exit()); + return menu; + } + + private static JMenuItem getMenuItem_Exit() { + JMenuItem menuItem = new JMenuItem("Exit"); + menuItem.addActionListener(getExitAction()); + return menuItem; + } + + private static ActionListener getExitAction() { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (!isHomeScreenActive()) { + String userPrompt = "Please confirm you want to close Forge.\n\n"; + if (!MenuUtil.getUserConfirmation(userPrompt, "Exit Forge")) { + return; + } + } + System.exit(0); + } + }; + } + + private static JMenuItem getMenuItem_Restart() { + JMenuItem menuItem = new JMenuItem("Restart"); + menuItem.addActionListener(getRestartAction()); + return menuItem; + } + + private static ActionListener getRestartAction() { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (!isHomeScreenActive()) { + String userPrompt = "Please confirm you want to restart Forge.\n\n"; + if (!MenuUtil.getUserConfirmation(userPrompt, "Restart Forge")) { + return; + } + } + RestartUtil.restartApplication(null); + } + }; + } + + private static boolean isHomeScreenActive() { + return Singletons.getControl().getState() == Screens.HOME_SCREEN; + } + +} diff --git a/src/main/java/forge/gui/menus/HelpMenu.java b/src/main/java/forge/gui/menus/HelpMenu.java new file mode 100644 index 00000000000..7a3264cd298 --- /dev/null +++ b/src/main/java/forge/gui/menus/HelpMenu.java @@ -0,0 +1,124 @@ +package forge.gui.menus; + +import java.awt.Desktop; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.io.File; +import java.io.IOException; + +import javax.swing.JMenu; +import javax.swing.JMenuItem; + +import forge.gui.menubar.MenuUtil; +import forge.util.FileUtil; + +public final class HelpMenu { + private HelpMenu() { } + + public static JMenu getMenu() { + JMenu menu = new JMenu("Help"); + menu.setMnemonic(KeyEvent.VK_H); + menu.add(getMenu_GettingStarted()); + menu.add(getMenu_Articles()); + menu.add(getMenu_Troubleshooting()); + menu.addSeparator(); + menu.add(getMenuItem_ReleaseNotes()); + menu.add(getMenuItem_License()); + return menu; + } + + private static JMenu getMenu_Troubleshooting() { + JMenu mnu = new JMenu("Troubleshooting"); + mnu.add(getMenuItem_UrlLink("How to Provide a Useful Bug Report", "http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=9621")); + mnu.addSeparator(); + mnu.add(getMenuItem_ReadMeFile()); + return mnu; + } + + private static JMenu getMenu_Articles() { + JMenu mnu = new JMenu("Articles"); + mnu.add(getMenuItem_UrlLink("HOW-TO: Customize your Sealed Deck games with fantasy blocks", "http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8164")); + mnu.add(getMenuItem_UrlLink("Quest Mode: Guide to Formats, Worlds, and everything", "http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=9258")); + return mnu; + } + + private static JMenu getMenu_GettingStarted() { + JMenu mnu = new JMenu("Getting Started"); + mnu.add(getMenuItem_UrlLink("Forge Wiki", "http://www.slightlymagic.net/wiki/Forge")); + mnu.add(getMenuItem_UrlLink("What is Forge?", "http://www.slightlymagic.net/forum/viewtopic.php?f=26&t=468")); + return mnu; + } + + private static JMenuItem getMenuItem_ReadMeFile() { + JMenuItem menuItem = new JMenuItem("README.txt"); + menuItem.addActionListener(getOpenFileAction(getFile("README.txt"))); + return menuItem; + } + + private static JMenuItem getMenuItem_License() { + JMenuItem menuItem = new JMenuItem("Forge License"); + menuItem.addActionListener(getOpenFileAction(getFile("LICENSE.txt"))); + return menuItem; + } + + private static JMenuItem getMenuItem_ReleaseNotes() { + JMenuItem menuItem = new JMenuItem("Release Notes"); + menuItem.addActionListener(getOpenFileAction(getFile("CHANGES.txt"))); + return menuItem; + } + + private static ActionListener getOpenFileAction(final File file) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + try { + openFile(file); + } catch (IOException e1) { + // Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log. + e1.printStackTrace(); + } + } + }; + } + + protected static File getFile(String filename) { + // !! Linux is case-sensitive so file name and extension need to match exactly !! + File file = null; + String filePath = FileUtil.pathCombine(System.getProperty("user.dir"), filename); + if (FileUtil.doesFileExist(filePath)) { + file = new File(filePath); + } + return file; + } + + /** + * @see http://stackoverflow.com/questions/6273221/open-a-text-file-in-the-default-text-editor-via-java + */ + private static void openFile(File file) throws IOException { + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + String cmd = "rundll32 url.dll,FileProtocolHandler " + file.getCanonicalPath(); + Runtime.getRuntime().exec(cmd); + } + else { + Desktop.getDesktop().open(file); + } + } + + + private static JMenuItem getMenuItem_UrlLink(String caption, String url) { + JMenuItem menuItem = new JMenuItem(caption); + menuItem.addActionListener(getLaunchUrlAction(url)); + return menuItem; + } + + private static ActionListener getLaunchUrlAction(final String url) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + MenuUtil.openUrlInBrowser(url); + } + }; + } + +} diff --git a/src/main/java/forge/gui/toolbox/LayoutHelper.java b/src/main/java/forge/gui/toolbox/LayoutHelper.java new file mode 100644 index 00000000000..9d91ccbae85 --- /dev/null +++ b/src/main/java/forge/gui/toolbox/LayoutHelper.java @@ -0,0 +1,116 @@ +package forge.gui.toolbox; + +import javax.swing.JComponent; + +/** + * Helper class for doing custom layout + * + */ +public final class LayoutHelper { + private final int parentWidth, parentHeight; + private int x, y, lineBottom; + + public LayoutHelper(JComponent parent) { + parentWidth = parent.getWidth(); + parentHeight = parent.getHeight(); + } + + /** + * Layout component to fill remaining space of parent + * @param comp + */ + public void fill(final JComponent comp) { + if (x >= parentWidth) { + newLine(); + } + include(comp, parentWidth - x, parentHeight - y); + } + + /** + * Layout component to fill remaining space of current line + * @param comp + * @param height + */ + public void fillLine(final JComponent comp, int height) { + if (x >= parentWidth) { + newLine(); + } + include(comp, parentWidth - x, height); + } + + /** + * Include component in layout with a percentage width and fixed height + * @param comp + * @param widthPercent + * @param height + */ + public void include(final JComponent comp, float widthPercent, int height) { + include(comp, Math.round(parentWidth * widthPercent), height); + } + + /** + * Include component in layout with a fixed width and percentage height + * @param comp + * @param width + * @param heightPercent + */ + public void include(final JComponent comp, int width, float heightPercent) { + include(comp, width, Math.round(parentHeight * heightPercent)); + } + + /** + * Include component in layout with a percentage width and height + * @param comp + * @param widthPercent + * @param heightPercent + */ + public void include(final JComponent comp, float widthPercent, float heightPercent) { + include(comp, Math.round(parentWidth * widthPercent), Math.round(parentHeight * heightPercent)); + } + + /** + * Include component in layout with a fixed width and height + * @param comp + * @param width + * @param height + */ + public void include(final JComponent comp, int width, int height) { + if (width <= 0 || height <= 0) { return; } + + if (x + width > parentWidth) { + newLine(); + if (width > parentWidth) { + width = parentWidth; + } + } + if (y + height > parentHeight) { + y = parentHeight - height; + if (y >= parentHeight) { return; } + } + comp.setBounds(x, y, width, height); + x += width + 3; + if (y + height > lineBottom) { + lineBottom = y + height; + } + } + + /** + * Offset current layout helper position + * @param dx + * @param dy + */ + public void offset(int dx, int dy) { + x += dx; + y += dy; + } + + /** + * Start new line of layout + */ + public void newLine() { + if (lineBottom == y) { return; } + x = 0; + y = lineBottom + 3; + lineBottom = y; + } +} diff --git a/src/main/java/forge/gui/toolbox/imaging/ImageUtil.java b/src/main/java/forge/gui/toolbox/imaging/ImageUtil.java index 5a5de1f8cd4..a943521e6fc 100644 --- a/src/main/java/forge/gui/toolbox/imaging/ImageUtil.java +++ b/src/main/java/forge/gui/toolbox/imaging/ImageUtil.java @@ -19,6 +19,9 @@ package forge.gui.toolbox.imaging; import java.awt.Dimension; +import java.awt.Image; + +import javax.swing.ImageIcon; /** * Useful general imaging routines. @@ -29,6 +32,12 @@ import java.awt.Dimension; public final class ImageUtil { private ImageUtil() {} + public static ImageIcon getMenuIcon(ImageIcon sourceIcon) { + Image img = sourceIcon.getImage(); + Image newimg = img.getScaledInstance(16, 16, java.awt.Image.SCALE_SMOOTH); + return new ImageIcon(newimg); + } + /** * Gets the nearest rotation for a requested rotation. *

diff --git a/src/main/java/forge/gui/toolbox/itemmanager/CardManager.java b/src/main/java/forge/gui/toolbox/itemmanager/CardManager.java new file mode 100644 index 00000000000..88a713ee071 --- /dev/null +++ b/src/main/java/forge/gui/toolbox/itemmanager/CardManager.java @@ -0,0 +1,113 @@ +package forge.gui.toolbox.itemmanager; + +import java.util.List; +import java.util.Map; + +import javax.swing.JMenu; +import javax.swing.JPopupMenu; +import forge.Singletons; +import forge.game.GameFormat; +import forge.gui.GuiUtils; +import forge.gui.home.quest.DialogChooseSets; +import forge.gui.toolbox.FLabel; +import forge.gui.toolbox.itemmanager.SItemManagerUtil.StatTypes; +import forge.gui.toolbox.itemmanager.filters.CardCMCFilter; +import forge.gui.toolbox.itemmanager.filters.CardColorFilter; +import forge.gui.toolbox.itemmanager.filters.CardFormatFilter; +import forge.gui.toolbox.itemmanager.filters.CardPowerFilter; +import forge.gui.toolbox.itemmanager.filters.CardQuestWorldFilter; +import forge.gui.toolbox.itemmanager.filters.CardSearchFilter; +import forge.gui.toolbox.itemmanager.filters.CardSetFilter; +import forge.gui.toolbox.itemmanager.filters.CardToughnessFilter; +import forge.gui.toolbox.itemmanager.filters.CardTypeFilter; +import forge.gui.toolbox.itemmanager.filters.ItemFilter; +import forge.item.PaperCard; +import forge.quest.QuestWorld; + +/** + * ItemManager for cards + * + */ +@SuppressWarnings("serial") +public final class CardManager extends ItemManager { + + public CardManager(Map statLabels0, boolean wantUnique0) { + super(PaperCard.class, statLabels0, wantUnique0); + + this.addFilter(new CardColorFilter(this)); + this.addFilter(new CardTypeFilter(this)); + } + + @Override + protected ItemFilter createSearchFilter(String text) { + return new CardSearchFilter(this, text); + } + + @Override + protected void buildFilterMenu(JPopupMenu menu) { + JMenu fmt = new JMenu("Format"); + for (final GameFormat f : Singletons.getModel().getFormats()) { + GuiUtils.addMenuItem(fmt, f.getName(), null, new Runnable() { + @Override + public void run() { + addFilter(new CardFormatFilter(CardManager.this, f)); + } + }, CardFormatFilter.canAddFormat(f, getFilter(CardFormatFilter.class))); + } + menu.add(fmt); + + GuiUtils.addMenuItem(menu, "Sets...", null, new Runnable() { + @Override + public void run() { + CardSetFilter existingFilter = 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 sets = dialog.getSelectedSets(); + if (!sets.isEmpty()) { + addFilter(new CardSetFilter(CardManager.this, sets)); + } + } + }); + } + } + }); + + JMenu range = new JMenu("Value range"); + GuiUtils.addMenuItem(range, "CMC", null, new Runnable() { + @Override + public void run() { + addFilter(new CardCMCFilter(CardManager.this)); + } + }, getFilter(CardCMCFilter.class) == null); + GuiUtils.addMenuItem(range, "Power", null, new Runnable() { + @Override + public void run() { + addFilter(new CardPowerFilter(CardManager.this)); + } + }, getFilter(CardPowerFilter.class) == null); + GuiUtils.addMenuItem(range, "Toughness", null, new Runnable() { + @Override + public void run() { + addFilter(new CardToughnessFilter(CardManager.this)); + } + }, getFilter(CardToughnessFilter.class) == null); + menu.add(range); + + JMenu world = new JMenu("Quest world"); + for (final QuestWorld w : Singletons.getModel().getWorlds()) { + GuiUtils.addMenuItem(world, w.getName(), null, new Runnable() { + @Override + public void run() { + addFilter(new CardQuestWorldFilter(CardManager.this, w)); + } + }, CardQuestWorldFilter.canAddQuestWorld(w, getFilter(CardQuestWorldFilter.class))); + } + menu.add(world); + } +} diff --git a/src/main/java/forge/gui/toolbox/itemmanager/InventoryItemManager.java b/src/main/java/forge/gui/toolbox/itemmanager/InventoryItemManager.java new file mode 100644 index 00000000000..5709dd6b6d0 --- /dev/null +++ b/src/main/java/forge/gui/toolbox/itemmanager/InventoryItemManager.java @@ -0,0 +1,32 @@ +package forge.gui.toolbox.itemmanager; + +import java.util.Map; + +import javax.swing.JPopupMenu; + +import forge.gui.toolbox.FLabel; +import forge.gui.toolbox.itemmanager.SItemManagerUtil.StatTypes; +import forge.gui.toolbox.itemmanager.filters.ItemFilter; +import forge.item.InventoryItem; + +/** + * TODO: Write javadoc for this type. + * + */ +@SuppressWarnings("serial") +public final class InventoryItemManager extends ItemManager { + + public InventoryItemManager(Map statLabels0, boolean wantUnique0) { + super(InventoryItem.class, statLabels0, wantUnique0); + } + + @Override + protected ItemFilter createSearchFilter(String text) { + return null; + } + + @Override + protected void buildFilterMenu(JPopupMenu menu) { + + } +} diff --git a/src/main/java/forge/gui/toolbox/itemmanager/ItemManager.java b/src/main/java/forge/gui/toolbox/itemmanager/ItemManager.java index 42fd0a148d6..98ce5ca536b 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/ItemManager.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/ItemManager.java @@ -17,6 +17,9 @@ */ package forge.gui.toolbox.itemmanager; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -24,8 +27,9 @@ import java.util.Map; import java.util.Map.Entry; import javax.swing.JPanel; +import javax.swing.JPopupMenu; import javax.swing.JScrollPane; -import javax.swing.JTextField; +import javax.swing.KeyStroke; import com.google.common.base.Predicate; @@ -33,8 +37,11 @@ import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; +import forge.Command; +import forge.gui.GuiUtils; import forge.gui.toolbox.FLabel; import forge.gui.toolbox.FTextField; +import forge.gui.toolbox.LayoutHelper; import forge.gui.toolbox.ToolTipListener; import forge.gui.toolbox.itemmanager.filters.ItemFilter; import forge.gui.toolbox.itemmanager.table.ItemTable; @@ -43,6 +50,7 @@ import forge.item.InventoryItem; import forge.item.ItemPool; import forge.item.ItemPoolView; import forge.util.Aggregates; +import forge.util.TypeUtil; /** @@ -51,21 +59,26 @@ import forge.util.Aggregates; * @param * the generic type */ -public final class ItemManager extends JPanel { - private static final long serialVersionUID = 3164349984277267922L; +@SuppressWarnings("serial") +public abstract class ItemManager extends JPanel { private ItemPool pool; private final ItemManagerModel model; private Predicate filterPredicate = null; - private final Map> filters = - new HashMap>(); + private final Map>, List>> filters = + new HashMap>, List>>(); + private final List> orderedFilters = new ArrayList>(); private boolean wantUnique = false; private boolean alwaysNonUnique = false; private final Class genericType; private final Map statLabels; + private final FLabel btnAddFilter = new FLabel.ButtonBuilder() + .text("Add") + .tooltip("Click to add filters to the list") + .reactOnMouseDown().build(); + private final FTextField txtSearch = new FTextField.Builder().ghostText("Search").build(); private final ItemTable table; private final JScrollPane tableScroller; - private final JTextField txtSearch = new FTextField.Builder().ghostText("Search").build(); /** * ItemManager Constructor. @@ -74,7 +87,7 @@ public final class ItemManager extends JPanel { * @param statLabels0 stat labels for this item manager * @param wantUnique0 whether this table should display only one item with the same name */ - public ItemManager(final Class genericType0, Map statLabels0, final boolean wantUnique0) { + protected ItemManager(final Class genericType0, Map statLabels0, final boolean wantUnique0) { this.genericType = genericType0; this.statLabels = statLabels0; this.wantUnique = wantUnique0; @@ -92,25 +105,45 @@ public final class ItemManager extends JPanel { //build display this.setOpaque(false); this.setLayout(null); + this.add(this.btnAddFilter); this.add(this.txtSearch); this.add(this.tableScroller); + + //setup command for btnAddFilter + final Command addFilterCommand = new Command() { + @Override + public void run() { + JPopupMenu menu = new JPopupMenu("FilterMenu"); + GuiUtils.addMenuItem(menu, "Current text search", + KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), + new Runnable() { + @Override + public void run() { + ItemFilter searchFilter = createSearchFilter(txtSearch.getText()); + if (searchFilter != null) { + addFilter(searchFilter); + } + } + }, !txtSearch.isEmpty()); + buildFilterMenu(menu); + menu.show(btnAddFilter, 0, btnAddFilter.getHeight()); + } + }; + this.btnAddFilter.setCommand(addFilterCommand); + this.btnAddFilter.setRightClickCommand(addFilterCommand); //show menu on right-click too } @Override public void doLayout() { - int x = 0; - int y = 0; - int width = this.getWidth(); - int height = this.getHeight(); - - //position toolbar components //TODO: Uncomment - /*int toolbarHeight = FTextField.HEIGHT + 3; - this.txtSearch.setBounds(x, y, width / 2, FTextField.HEIGHT); - y += toolbarHeight;*/ - - //position current item view - this.tableScroller.setBounds(x, y, width, height - y); + LayoutHelper helper = new LayoutHelper(this); + /*helper.include(this.btnAddFilter, 30, FTextField.HEIGHT); + helper.include(this.txtSearch, 0.5f, FTextField.HEIGHT); + helper.newLine(); + for (ItemFilter filter : this.orderedFilters) { + helper.fillLine(filter.getPanel(), ItemFilter.PANEL_HEIGHT); + }*/ + helper.fill(this.tableScroller); } /** @@ -337,16 +370,48 @@ public final class ItemManager extends JPanel { return this.statLabels.get(s); } + protected abstract ItemFilter createSearchFilter(String text); + protected abstract void buildFilterMenu(JPopupMenu menu); + + protected > F getFilter(Class filterClass) { + return TypeUtil.safeCast(this.filters.get(filterClass), filterClass); + } + + @SuppressWarnings("unchecked") public void addFilter(ItemFilter filter) { - this.filters.put(filter.getType(), filter); - this.add(filter); + final Class> filterClass = (Class>) filter.getClass(); + List> classFilters = this.filters.get(filterClass); + if (classFilters == null) { + classFilters = new ArrayList>(); + this.filters.put(filterClass, classFilters); + } + if (classFilters.size() > 0) { + //if filter with the same class already exists, try to merge if allowed + //NOTE: can always use first filter for these checks since if + //merge is supported, only one will ever exist + ItemFilter existingFilter = classFilters.get(0); + if (existingFilter.merge(filter)) { + //if new filter merged with existing filter, just update layout + this.revalidate(); + return; + } + } + classFilters.add(filter); + this.add(filter.getPanel()); this.revalidate(); } + @SuppressWarnings("unchecked") public void removeFilter(ItemFilter filter) { - this.filters.remove(filter.getType()); - this.remove(filter); - this.revalidate(); + final Class> filterClass = (Class>) filter.getClass(); + final List> classFilters = this.filters.get(filterClass); + if (classFilters != null && classFilters.remove(filter)) { + if (classFilters.size() == 0) { + this.filters.remove(filterClass); + } + this.remove(filter.getPanel()); + this.revalidate(); + } } public void buildFilterPredicate() { diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardCMCFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardCMCFilter.java index 5bb7f9efcd9..5d174ffd896 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardCMCFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardCMCFilter.java @@ -1,5 +1,7 @@ package forge.gui.toolbox.itemmanager.filters; +import javax.swing.JPanel; + import forge.gui.toolbox.itemmanager.ItemManager; import forge.item.PaperCard; @@ -7,7 +9,6 @@ import forge.item.PaperCard; * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public class CardCMCFilter extends ValueRangeFilter { public CardCMCFilter(ItemManager itemManager0) { @@ -15,12 +16,7 @@ public class CardCMCFilter extends ValueRangeFilter { } @Override - public FilterTypes getType() { - return FilterTypes.CardCMC; - } - - @Override - protected void addComponents() { + protected void buildPanel(JPanel panel) { } diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardColorFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardColorFilter.java index d774846c44c..deb5c15662b 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardColorFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardColorFilter.java @@ -1,26 +1,29 @@ package forge.gui.toolbox.itemmanager.filters; +import javax.swing.JPanel; + import forge.gui.toolbox.itemmanager.ItemManager; +import forge.gui.toolbox.itemmanager.SItemManagerUtil.StatTypes; import forge.item.PaperCard; /** * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public class CardColorFilter extends ToggleButtonsFilter { public CardColorFilter(ItemManager itemManager0) { super(itemManager0); } @Override - public FilterTypes getType() { - return FilterTypes.CardColor; - } - - @Override - protected void addComponents() { - + protected void buildPanel(JPanel panel) { + addToggleButton(panel, StatTypes.WHITE); + addToggleButton(panel, StatTypes.BLUE); + addToggleButton(panel, StatTypes.BLACK); + addToggleButton(panel, StatTypes.RED); + addToggleButton(panel, StatTypes.GREEN); + addToggleButton(panel, StatTypes.COLORLESS); + addToggleButton(panel, StatTypes.MULTICOLOR); } @Override diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardFormatFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardFormatFilter.java index ced3cb035c8..45211c76367 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardFormatFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardFormatFilter.java @@ -1,5 +1,11 @@ package forge.gui.toolbox.itemmanager.filters; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JPanel; + +import forge.game.GameFormat; import forge.gui.toolbox.itemmanager.ItemManager; import forge.item.PaperCard; @@ -7,19 +13,33 @@ import forge.item.PaperCard; * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public class CardFormatFilter extends ListLabelFilter { - public CardFormatFilter(ItemManager itemManager0) { + private final Set formats = new HashSet(); + + public CardFormatFilter(ItemManager itemManager0, GameFormat format0) { super(itemManager0); + this.formats.add(format0); + } + + public static boolean canAddFormat(GameFormat format, ItemFilter existingFilter) { + return existingFilter == null || !((CardFormatFilter)existingFilter).formats.contains(format); + } + + /** + * 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 + @SuppressWarnings("rawtypes") + public boolean merge(ItemFilter filter) { + CardFormatFilter cardFormatFilter = (CardFormatFilter)filter; + this.formats.addAll(cardFormatFilter.formats); + return true; } @Override - public FilterTypes getType() { - return FilterTypes.CardFormat; - } - - @Override - protected void addComponents() { + protected void buildPanel(JPanel panel) { } @@ -27,4 +47,13 @@ public class CardFormatFilter extends ListLabelFilter { protected void onRemoved() { } + + @Override + protected Iterable getList() { + Set strings = new HashSet(); + for (GameFormat f : this.formats) { + strings.add(f.getName()); + } + return strings; + } } diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardPowerFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardPowerFilter.java index 5e1d6734a06..8e194d3cc2f 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardPowerFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardPowerFilter.java @@ -1,5 +1,7 @@ package forge.gui.toolbox.itemmanager.filters; +import javax.swing.JPanel; + import forge.gui.toolbox.itemmanager.ItemManager; import forge.item.PaperCard; @@ -7,19 +9,13 @@ import forge.item.PaperCard; * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public class CardPowerFilter extends ValueRangeFilter { public CardPowerFilter(ItemManager itemManager0) { super(itemManager0); } @Override - public FilterTypes getType() { - return FilterTypes.CardPower; - } - - @Override - protected void addComponents() { + protected void buildPanel(JPanel panel) { } diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardQuestWorldFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardQuestWorldFilter.java index 440712fc696..8737cb95ef8 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardQuestWorldFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardQuestWorldFilter.java @@ -1,25 +1,49 @@ package forge.gui.toolbox.itemmanager.filters; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JPanel; + +import forge.Singletons; import forge.gui.toolbox.itemmanager.ItemManager; import forge.item.PaperCard; +import forge.quest.QuestWorld; /** * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public class CardQuestWorldFilter extends ListLabelFilter { - public CardQuestWorldFilter(ItemManager itemManager0) { + private final Set questWorlds = new HashSet(); + + public CardQuestWorldFilter(ItemManager itemManager0, QuestWorld questWorld0) { super(itemManager0); + this.questWorlds.add(questWorld0); + } + + public static boolean canAddQuestWorld(QuestWorld questWorld, ItemFilter existingFilter) { + if (questWorld.getFormat() == null && Singletons.getModel().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 + @SuppressWarnings("rawtypes") + public boolean merge(ItemFilter filter) { + CardQuestWorldFilter cardQuestWorldFilter = (CardQuestWorldFilter)filter; + this.questWorlds.addAll(cardQuestWorldFilter.questWorlds); + return true; } @Override - public FilterTypes getType() { - return FilterTypes.CardQuestWorld; - } - - @Override - protected void addComponents() { + protected void buildPanel(JPanel panel) { } @@ -27,4 +51,13 @@ public class CardQuestWorldFilter extends ListLabelFilter { protected void onRemoved() { } + + @Override + protected Iterable getList() { + Set strings = new HashSet(); + for (QuestWorld w : this.questWorlds) { + strings.add(w.getName()); + } + return strings; + } } diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardSearchFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardSearchFilter.java new file mode 100644 index 00000000000..12ad0cdba1a --- /dev/null +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardSearchFilter.java @@ -0,0 +1,26 @@ +package forge.gui.toolbox.itemmanager.filters; + +import javax.swing.JPanel; + +import forge.gui.toolbox.itemmanager.ItemManager; +import forge.item.PaperCard; + +/** + * TODO: Write javadoc for this type. + * + */ +public class CardSearchFilter extends TextSearchFilter { + public CardSearchFilter(ItemManager itemManager0, String text0) { + super(itemManager0, text0); + } + + @Override + protected void buildPanel(JPanel panel) { + + } + + @Override + protected void onRemoved() { + + } +} diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardSetFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardSetFilter.java index a63303b49e0..2dadfd4b01b 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardSetFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardSetFilter.java @@ -1,5 +1,12 @@ package forge.gui.toolbox.itemmanager.filters; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JPanel; + +import forge.gui.home.quest.DialogChooseSets; import forge.gui.toolbox.itemmanager.ItemManager; import forge.item.PaperCard; @@ -7,19 +14,40 @@ import forge.item.PaperCard; * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public class CardSetFilter extends ListLabelFilter { - public CardSetFilter(ItemManager itemManager0) { + private final Set sets = new HashSet(); + + public CardSetFilter(ItemManager itemManager0, Collection sets) { super(itemManager0); + this.sets.addAll(sets); + } + + /** + * 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 + @SuppressWarnings("rawtypes") + public boolean merge(ItemFilter filter) { + CardSetFilter cardSetFilter = (CardSetFilter)filter; + this.sets.addAll(cardSetFilter.sets); + 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()); + } + }); } @Override - public FilterTypes getType() { - return FilterTypes.CardSet; - } - - @Override - protected void addComponents() { + protected void buildPanel(JPanel panel) { } @@ -27,4 +55,9 @@ public class CardSetFilter extends ListLabelFilter { protected void onRemoved() { } + + @Override + protected Iterable getList() { + return this.sets; + } } diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardToughnessFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardToughnessFilter.java index 1847f707b53..a47dc0d07f4 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardToughnessFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardToughnessFilter.java @@ -1,5 +1,7 @@ package forge.gui.toolbox.itemmanager.filters; +import javax.swing.JPanel; + import forge.gui.toolbox.itemmanager.ItemManager; import forge.item.PaperCard; @@ -7,19 +9,13 @@ import forge.item.PaperCard; * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public class CardToughnessFilter extends ValueRangeFilter { public CardToughnessFilter(ItemManager itemManager0) { super(itemManager0); } @Override - public FilterTypes getType() { - return FilterTypes.CardToughness; - } - - @Override - protected void addComponents() { + protected void buildPanel(JPanel panel) { } diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardTypeFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardTypeFilter.java index 717c7d3a340..7d949f90f42 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/CardTypeFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/CardTypeFilter.java @@ -1,26 +1,29 @@ package forge.gui.toolbox.itemmanager.filters; +import javax.swing.JPanel; + import forge.gui.toolbox.itemmanager.ItemManager; +import forge.gui.toolbox.itemmanager.SItemManagerUtil.StatTypes; import forge.item.PaperCard; /** * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public class CardTypeFilter extends ToggleButtonsFilter { public CardTypeFilter(ItemManager itemManager0) { super(itemManager0); } @Override - public FilterTypes getType() { - return FilterTypes.CardType; - } - - @Override - protected void addComponents() { - + protected void buildPanel(JPanel panel) { + addToggleButton(panel, StatTypes.LAND); + addToggleButton(panel, StatTypes.ARTIFACT); + addToggleButton(panel, StatTypes.CREATURE); + addToggleButton(panel, StatTypes.ENCHANTMENT); + addToggleButton(panel, StatTypes.PLANESWALKER); + addToggleButton(panel, StatTypes.INSTANT); + addToggleButton(panel, StatTypes.SORCERY); } @Override diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/ItemFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/ItemFilter.java index 17db43ec326..d4c74fa095c 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/ItemFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/ItemFilter.java @@ -11,40 +11,62 @@ import forge.item.InventoryItem; * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") -public abstract class ItemFilter extends JPanel { +public abstract class ItemFilter { private final ItemManager itemManager; + private int number; + private JPanel panel; - public enum FilterTypes { - CardCMC, - CardColor, - CardFormat, - CardPower, - CardQuestWorld, - CardSet, - CardToughness, - CardType - } + public static int PANEL_HEIGHT = 30; protected ItemFilter(ItemManager itemManager0) { this.itemManager = itemManager0; - this.setOpaque(false); - this.addComponents(); - this.add(new FLabel.Builder().text("X").fontSize(10).hoverable(true) - .tooltip("Remove filter").cmdClick(new Command() { + } + + public int getNumber() { + return this.number; + } + + public void setNumber(int number0) { + this.number = number0; + } + + @SuppressWarnings("serial") + public JPanel getPanel() { + if (this.panel == null) { + this.panel = new JPanel(); + this.panel.setOpaque(false); + + this.buildPanel(panel); + + //add button to remove filter + this.panel.add(new FLabel.Builder() + .text("X") + .fontSize(10) + .hoverable(true) + .tooltip("Remove filter") + .cmdClick(new Command() { @Override public void run() { itemManager.removeFilter(ItemFilter.this); ItemFilter.this.onRemoved(); } }).build(), "top"); + } + return this.panel; } protected void applyChange() { - itemManager.buildFilterPredicate(); + this.itemManager.buildFilterPredicate(); } - public abstract FilterTypes getType(); - protected abstract void addComponents(); + /** + * 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 + */ + @SuppressWarnings("rawtypes") + public abstract boolean merge(ItemFilter filter); + + protected abstract void buildPanel(JPanel panel); protected abstract void onRemoved(); } diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/ListLabelFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/ListLabelFilter.java index fa43ccb5f19..3c4e671a6e0 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/ListLabelFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/ListLabelFilter.java @@ -7,10 +7,32 @@ import forge.item.InventoryItem; * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public abstract class ListLabelFilter extends ItemFilter { protected ListLabelFilter(ItemManager itemManager0) { super(itemManager0); } + + protected abstract Iterable getList(); + + public void buildPanel() { + StringBuilder label = new StringBuilder(); + boolean truncated = false; + for (String str : getList()) { + // don't let the full label get too long + if (label.length() < 32) { + label.append(" ").append(str).append(";"); + } else { + truncated = true; + break; + } + } + + // chop off last semicolons + label.delete(label.length() - 1, label.length()); + + if (truncated) { + label.append("..."); + } + } } diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/TextFieldFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/TextFieldFilter.java deleted file mode 100644 index 4d7611eadac..00000000000 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/TextFieldFilter.java +++ /dev/null @@ -1,16 +0,0 @@ -package forge.gui.toolbox.itemmanager.filters; - -import forge.gui.toolbox.itemmanager.ItemManager; -import forge.item.InventoryItem; - -/** - * TODO: Write javadoc for this type. - * - */ -@SuppressWarnings("serial") -public abstract class TextFieldFilter extends ItemFilter { - - protected TextFieldFilter(ItemManager itemManager0) { - super(itemManager0); - } -} diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/TextSearchFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/TextSearchFilter.java new file mode 100644 index 00000000000..45b0fb2364c --- /dev/null +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/TextSearchFilter.java @@ -0,0 +1,36 @@ +package forge.gui.toolbox.itemmanager.filters; + +import javax.swing.JPanel; + +import forge.gui.toolbox.FTextField; +import forge.gui.toolbox.itemmanager.ItemManager; +import forge.item.InventoryItem; + +/** + * TODO: Write javadoc for this type. + * + */ +public abstract class TextSearchFilter extends ItemFilter { + private String text; + + protected TextSearchFilter(ItemManager itemManager0, String text0) { + super(itemManager0); + this.text = text0; + } + + /** + * 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 + @SuppressWarnings("rawtypes") + public boolean merge(ItemFilter filter) { + return false; + } + + @Override + protected void buildPanel(JPanel panel) { + panel.add(new FTextField.Builder().text(this.text).build()); + } +} diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/ToggleButtonsFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/ToggleButtonsFilter.java index 2f8f0c47571..5ca5adec5fb 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/ToggleButtonsFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/ToggleButtonsFilter.java @@ -1,6 +1,15 @@ package forge.gui.toolbox.itemmanager.filters; +import java.awt.Dimension; +import java.util.ArrayList; + +import javax.swing.ImageIcon; +import javax.swing.JPanel; + +import forge.Command; +import forge.gui.toolbox.FLabel; import forge.gui.toolbox.itemmanager.ItemManager; +import forge.gui.toolbox.itemmanager.SItemManagerUtil.StatTypes; import forge.item.InventoryItem; /** @@ -9,8 +18,60 @@ import forge.item.InventoryItem; */ @SuppressWarnings("serial") public abstract class ToggleButtonsFilter extends ItemFilter { + private static final Dimension BUTTON_SIZE = new Dimension(60, 24); + + private final ArrayList buttons = new ArrayList(); protected ToggleButtonsFilter(ItemManager itemManager0) { super(itemManager0); } + + protected void addToggleButton(JPanel panel, StatTypes s) { + addToggleButton(panel, s.toLabelString(), s.img); + } + + protected void addToggleButton(JPanel panel, String filterName, ImageIcon icon) { + final FLabel button = new FLabel.Builder() + .icon(icon).iconScaleAuto(false) + .fontSize(11) + .tooltip(filterName + " (click to toggle the filter, right-click to show only " + filterName.toLowerCase() + ")") + .hoverable().selectable(true).selected(true) + .build(); + + button.setPreferredSize(BUTTON_SIZE); + button.setMinimumSize(BUTTON_SIZE); + + button.setCommand(new Command() { + @Override + public void run() { + applyChange(); + } + }); + + //hook so right-clicking a button toggles itself on and toggles off all other buttons + button.setRightClickCommand(new Command() { + @Override + public void run() { + for(FLabel btn : buttons) { + btn.setSelected(false); + } + button.setSelected(true); + applyChange(); + } + }); + + this.buttons.add(button); + panel.add(button); + } + + /** + * 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 + @SuppressWarnings("rawtypes") + public boolean merge(ItemFilter filter) { + return true; + } } diff --git a/src/main/java/forge/gui/toolbox/itemmanager/filters/ValueRangeFilter.java b/src/main/java/forge/gui/toolbox/itemmanager/filters/ValueRangeFilter.java index 646727d939d..12e3e90e64a 100644 --- a/src/main/java/forge/gui/toolbox/itemmanager/filters/ValueRangeFilter.java +++ b/src/main/java/forge/gui/toolbox/itemmanager/filters/ValueRangeFilter.java @@ -7,10 +7,20 @@ import forge.item.InventoryItem; * TODO: Write javadoc for this type. * */ -@SuppressWarnings("serial") public abstract class ValueRangeFilter extends ItemFilter { protected ValueRangeFilter(ItemManager itemManager0) { super(itemManager0); } + + /** + * 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 + @SuppressWarnings("rawtypes") + public boolean merge(ItemFilter filter) { + return true; + } } diff --git a/src/main/java/forge/gui/toolbox/special/FDeckChooser.java b/src/main/java/forge/gui/toolbox/special/FDeckChooser.java index 2d149c74f2e..0799e3adcd6 100644 --- a/src/main/java/forge/gui/toolbox/special/FDeckChooser.java +++ b/src/main/java/forge/gui/toolbox/special/FDeckChooser.java @@ -29,7 +29,7 @@ import forge.gui.toolbox.FList; import forge.gui.toolbox.FRadioButton; import forge.gui.toolbox.FScrollPane; import forge.gui.toolbox.JXButtonPanel; -import forge.item.PreconDeck; +import forge.item.InventoryItem; import forge.quest.QuestController; import forge.quest.QuestEvent; import forge.quest.QuestEventChallenge; @@ -194,8 +194,7 @@ public class FDeckChooser extends JPanel { lst.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); final List customNames = new ArrayList(); - final IStorage allDecks = Singletons.getModel().getDecks().getConstructed(); - for (final Deck d : allDecks) { customNames.add(d.getName()); } + addDecksRecursive(Singletons.getModel().getDecks().getConstructed(), customNames, null); lst.setListData(customNames.toArray(ArrayUtils.EMPTY_STRING_ARRAY)); lst.setName(DeckgenUtil.DeckTypes.CUSTOM.toString()); @@ -210,14 +209,23 @@ public class FDeckChooser extends JPanel { lst.setSelectedIndex(0); } + private void addDecksRecursive(IStorage node, List customNames, String namePrefix ) { + String path = namePrefix == null ? "" : namePrefix + " / "; + for (final String fn : node.getFolders().getNames() ) + { + IStorage f = node.getFolders().get(fn); + addDecksRecursive(f, customNames, path + fn); + } + for (final T d : node) { customNames.add(path + d.getName()); } + } + /** Handles all control for "custom" radio button click. */ private void updatePrecons() { final JList lst = getLstDecks(); lst.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); final List customNames = new ArrayList(); - final IStorage allDecks = QuestController.getPrecons(); - for (final PreconDeck d : allDecks) { customNames.add(d.getName()); } + addDecksRecursive(QuestController.getPrecons(), customNames, null); lst.setListData(customNames.toArray(ArrayUtils.EMPTY_STRING_ARRAY)); lst.setName(DeckgenUtil.DeckTypes.PRECON.toString()); diff --git a/src/main/java/forge/properties/ForgeLookAndFeel.java b/src/main/java/forge/properties/ForgeLookAndFeel.java new file mode 100644 index 00000000000..9d2e51ea9e3 --- /dev/null +++ b/src/main/java/forge/properties/ForgeLookAndFeel.java @@ -0,0 +1,166 @@ +package forge.properties; + +import java.awt.Color; +import java.awt.Font; +import java.util.ArrayList; + +import javax.swing.BorderFactory; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.border.Border; + +import forge.Singletons; +import forge.gui.toolbox.FSkin; +import forge.properties.ForgePreferences.FPref; + +/** + * Sets the look and feel of the GUI based on the selected Forge theme. + * + * @see UIManager Defaults + */ +public final class ForgeLookAndFeel { + + private Color FORE_COLOR = FSkin.getColor(FSkin.Colors.CLR_TEXT); + private Color BACK_COLOR = FSkin.getColor(FSkin.Colors.CLR_THEME2); + private Color HIGHLIGHT_COLOR = BACK_COLOR.brighter(); + private Border LINE_BORDER = BorderFactory.createLineBorder(FORE_COLOR.darker(), 1); + private Border EMPTY_BORDER = BorderFactory.createEmptyBorder(2,2,2,2); + + /** + * Sets the look and feel of the GUI based on the selected Forge theme. + */ + public void setForgeLookAndFeel(JFrame appFrame) { + if (isUIManagerEnabled()) { + if (setMetalLookAndFeel(appFrame)) { + setMenusLookAndFeel(); + setComboBoxLookAndFeel(); + setTabbedPaneLookAndFeel(); + setButtonLookAndFeel(); + } + } + } + + /** + * Sets the standard "Java L&F" (also called "Metal") that looks the same on all platforms. + *

+ * If not explicitly set then the Mac uses its native L&F which does + * not support various settings (eg. combobox background color). + */ + private boolean setMetalLookAndFeel(JFrame appFrame) { + boolean isMetalLafSet = false; + try { + UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); + SwingUtilities.updateComponentTreeUI(appFrame); + isMetalLafSet = true; + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) { + // Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log. + e.printStackTrace(); + } + return isMetalLafSet; + } + + /** + * Sets the look and feel for a JMenuBar, JMenu, JMenuItem & variations. + */ + private void setMenusLookAndFeel() { + // JMenuBar + Color clrTheme = FSkin.getColor(FSkin.Colors.CLR_THEME); + Color backgroundColor = FSkin.stepColor(clrTheme, 0); + Color menuBarEdgeColor = FSkin.stepColor(clrTheme, -80); + UIManager.put("MenuBar.foreground", FORE_COLOR); + UIManager.put("MenuBar.gradient", getColorGradients(backgroundColor.darker(), backgroundColor)); + UIManager.put("MenuBar.border", BorderFactory.createMatteBorder(0, 0, 1, 0, menuBarEdgeColor)); + // JMenu + UIManager.put("Menu.foreground", FORE_COLOR); + UIManager.put("Menu.background", BACK_COLOR); + UIManager.put("Menu.borderPainted", false); + UIManager.put("Menu.selectionBackground", HIGHLIGHT_COLOR); + UIManager.put("Menu.selectionForeground", FORE_COLOR); + UIManager.put("Menu.border", EMPTY_BORDER); + UIManager.put("Menu.opaque", false); + // JPopupMenu + UIManager.put("PopupMenu.border", LINE_BORDER); + UIManager.put("PopupMenu.background", BACK_COLOR); + UIManager.put("PopupMenu.foreground", FORE_COLOR); + // JMenuItem + UIManager.put("MenuItem.foreground", FORE_COLOR); + UIManager.put("MenuItem.background", BACK_COLOR); + UIManager.put("MenuItem.border", EMPTY_BORDER); + UIManager.put("MenuItem.selectionBackground", HIGHLIGHT_COLOR); + UIManager.put("MenuItem.selectionForeground", FORE_COLOR); + UIManager.put("MenuItem.acceleratorForeground", FORE_COLOR.darker()); + UIManager.put("MenuItem.opaque", true); + // JSeparator (needs to be opaque!). + UIManager.put("Separator.foreground", FORE_COLOR.darker()); + UIManager.put("Separator.background", BACK_COLOR); + // JRadioButtonMenuItem + UIManager.put("RadioButtonMenuItem.foreground", FORE_COLOR); + UIManager.put("RadioButtonMenuItem.background", BACK_COLOR); + UIManager.put("RadioButtonMenuItem.selectionBackground", HIGHLIGHT_COLOR); + UIManager.put("RadioButtonMenuItem.selectionForeground", FORE_COLOR); + UIManager.put("RadioButtonMenuItem.border", EMPTY_BORDER); + UIManager.put("RadioButtonMenuItem.acceleratorForeground", FORE_COLOR.darker()); + // JCheckboxMenuItem + UIManager.put("CheckBoxMenuItem.foreground", FORE_COLOR); + UIManager.put("CheckBoxMenuItem.background", BACK_COLOR); + UIManager.put("CheckBoxMenuItem.selectionBackground", HIGHLIGHT_COLOR); + UIManager.put("CheckBoxMenuItem.selectionForeground", FORE_COLOR); + UIManager.put("CheckBoxMenuItem.border", EMPTY_BORDER); + UIManager.put("CheckBoxMenuItem.acceleratorForeground", FORE_COLOR.darker()); + } + + private void setTabbedPaneLookAndFeel() { + UIManager.put("TabbedPane.selected", HIGHLIGHT_COLOR); + UIManager.put("TabbedPane.contentOpaque", FSkin.getColor(FSkin.Colors.CLR_THEME)); + UIManager.put("TabbedPane.unselectedBackground", BACK_COLOR); + } + + /** + * Sets the look and feel for a non-editable JComboBox. + */ + private void setComboBoxLookAndFeel() { + UIManager.put("ComboBox.background", BACK_COLOR); + UIManager.put("ComboBox.foreground", FORE_COLOR); + UIManager.put("ComboBox.selectionBackground", HIGHLIGHT_COLOR); + UIManager.put("ComboBox.selectionForeground", FORE_COLOR); + UIManager.put("ComboBox.disabledBackground", BACK_COLOR); + UIManager.put("ComboBox.disabledForeground", BACK_COLOR.darker()); + UIManager.put("ComboBox.font", getDefaultFont("ComboBox.font")); + UIManager.put("Button.select", HIGHLIGHT_COLOR); + } + + private void setButtonLookAndFeel() { + UIManager.put("Button.foreground", FORE_COLOR); + UIManager.put("Button.background", BACK_COLOR); + UIManager.put("Button.select", HIGHLIGHT_COLOR); + UIManager.put("Button.focus", FORE_COLOR.darker()); + UIManager.put("Button.rollover", false); + } + + /** + * Determines whether theme styles should be applied to GUI. + *

+ * TODO: Currently is using UI_THEMED_COMBOBOX setting but will + * eventually want to rename for clarity. + */ + private boolean isUIManagerEnabled() { + return Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_THEMED_COMBOBOX); + } + + private Font getDefaultFont(String component) { + return FSkin.getFont(UIManager.getFont(component).getSize()); + } + + private ArrayList getColorGradients(Color bottom, Color top) { + ArrayList gradients = new ArrayList<>(); + gradients.add(0.0); + gradients.add(0.0); + gradients.add(top); + gradients.add(bottom); + gradients.add(bottom); + return gradients; + } + +} diff --git a/src/main/java/forge/properties/ForgePreferences.java b/src/main/java/forge/properties/ForgePreferences.java index a76344cf22b..1d579414b91 100644 --- a/src/main/java/forge/properties/ForgePreferences.java +++ b/src/main/java/forge/properties/ForgePreferences.java @@ -61,6 +61,7 @@ public class ForgePreferences extends PreferencesStore { UI_CLONE_MODE_SOURCE ("false"), /** */ UI_MATCH_IMAGE_VISIBLE ("true"), UI_THEMED_COMBOBOX ("true"), + UI_HIDE_MENUBAR ("false"), // Dev setting only - cannot be set from GUI. UI_FOR_TOUCHSCREN("false"), diff --git a/src/main/java/forge/util/IItemReader.java b/src/main/java/forge/util/IItemReader.java index a084452cbb0..4f57bcea2df 100644 --- a/src/main/java/forge/util/IItemReader.java +++ b/src/main/java/forge/util/IItemReader.java @@ -17,6 +17,7 @@ */ package forge.util; +import java.io.File; import java.util.Map; /** @@ -41,4 +42,8 @@ public interface IItemReader { * @return the item key */ String getItemKey(T item); + + Iterable getSubFolders(); + + IItemReader getReaderForFolder(File subfolder); } diff --git a/src/main/java/forge/util/TypeUtil.java b/src/main/java/forge/util/TypeUtil.java new file mode 100644 index 00000000000..ba92f20105f --- /dev/null +++ b/src/main/java/forge/util/TypeUtil.java @@ -0,0 +1,21 @@ +package forge.util; + +/** + * TODO: Write javadoc for this type. + * + */ +public class TypeUtil { + + /** + * Cast object to a given type if possible, returning null if not possible + * @param obj + * @param type + */ + @SuppressWarnings("unchecked") + public static T safeCast(Object obj, Class type) { + if (type.isInstance(obj)) { + return (T) obj; + } + return null; + } +} diff --git a/src/main/java/forge/util/storage/StorageImmediatelySerialized.java b/src/main/java/forge/util/storage/StorageImmediatelySerialized.java index bcc500f5381..2d984edc125 100644 --- a/src/main/java/forge/util/storage/StorageImmediatelySerialized.java +++ b/src/main/java/forge/util/storage/StorageImmediatelySerialized.java @@ -17,6 +17,9 @@ */ package forge.util.storage; +import com.google.common.base.Function; + +import forge.util.IItemReader; import forge.util.IItemSerializer; //reads and writeDeck Deck objects @@ -32,6 +35,15 @@ import forge.util.IItemSerializer; public class StorageImmediatelySerialized extends StorageBase { private final IItemSerializer serializer; + private final IStorage> subfolders; + + private final Function, IStorage> nestedFactory = new Function, IStorage>() { + @Override + public IStorage apply(IItemReader io) { + return new StorageImmediatelySerialized((IItemSerializer) io, true); + } + }; + /** *

* Constructor for DeckManager. @@ -40,8 +52,14 @@ public class StorageImmediatelySerialized extends StorageBase { * @param io the io */ public StorageImmediatelySerialized(final IItemSerializer io) { + this(io, false); + } + + + public StorageImmediatelySerialized(final IItemSerializer io, boolean withSubFolders) { super(io); this.serializer = io; + subfolders = withSubFolders ? new StorageNestedFolders(io, nestedFactory) : null; } /* @@ -65,4 +83,12 @@ public class StorageImmediatelySerialized extends StorageBase { public final void delete(final String deckName) { this.serializer.erase(this.map.remove(deckName)); } + + /* (non-Javadoc) + * @see forge.util.storage.StorageBase#getFolders() + */ + @Override + public IStorage> getFolders() { + return subfolders == null ? super.getFolders() : subfolders; + } } diff --git a/src/main/java/forge/util/storage/StorageNestedFolders.java b/src/main/java/forge/util/storage/StorageNestedFolders.java new file mode 100644 index 00000000000..ffdbdb4d858 --- /dev/null +++ b/src/main/java/forge/util/storage/StorageNestedFolders.java @@ -0,0 +1,36 @@ +package forge.util.storage; + +import java.io.File; +import java.util.HashMap; + +import org.apache.commons.lang.NotImplementedException; + +import com.google.common.base.Function; + +import forge.util.IItemReader; + + +public class StorageNestedFolders extends StorageBase> { + + public StorageNestedFolders(IItemReader io, Function, IStorage> factory) { + super(new HashMap>()); + for(File sf : io.getSubFolders() ) + { + map.put(sf.getName(), factory.apply(io.getReaderForFolder(sf))); + } + } + + // need code implementations for folder create/delete operations + + @Override + public void add(IStorage deck) { + // need folder name here! + throw new NotImplementedException(); + } + + @Override + public void delete(String deckName) { + throw new NotImplementedException(); + } + +} diff --git a/src/main/java/forge/util/storage/StorageReaderBase.java b/src/main/java/forge/util/storage/StorageReaderBase.java new file mode 100644 index 00000000000..9d343ca3ed5 --- /dev/null +++ b/src/main/java/forge/util/storage/StorageReaderBase.java @@ -0,0 +1,27 @@ +package forge.util.storage; + +import java.io.File; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; + +import forge.util.IItemReader; + +public abstract class StorageReaderBase implements IItemReader { + + protected final Function keySelector; + public StorageReaderBase(final Function keySelector0) { + keySelector = keySelector0; + } + + @Override + public Iterable getSubFolders() { + // TODO Auto-generated method stub + return ImmutableList.of(); + } + + @Override + public IItemReader getReaderForFolder(File subfolder) { + throw new UnsupportedOperationException("This reader is not supposed to have nested folders"); + } +} \ No newline at end of file diff --git a/src/main/java/forge/util/storage/StorageReaderFile.java b/src/main/java/forge/util/storage/StorageReaderFile.java index 0cc53b4850b..2fd938a6166 100644 --- a/src/main/java/forge/util/storage/StorageReaderFile.java +++ b/src/main/java/forge/util/storage/StorageReaderFile.java @@ -28,7 +28,6 @@ import org.apache.commons.lang3.StringUtils; import com.google.common.base.Function; import forge.util.FileUtil; -import forge.util.IItemReader; /** * This class treats every line of a given file as a source for a named object. @@ -36,10 +35,10 @@ import forge.util.IItemReader; * @param * the generic type */ -public abstract class StorageReaderFile implements IItemReader { +public abstract class StorageReaderFile extends StorageReaderBase { private final File file; - private final Function keySelector; + /** * Instantiates a new storage reader file. @@ -58,8 +57,8 @@ public abstract class StorageReaderFile implements IItemReader { * @param keySelector0 the key selector0 */ public StorageReaderFile(final File file0, final Function keySelector0) { + super(keySelector0); this.file = file0; - this.keySelector = keySelector0; } /* (non-Javadoc) @@ -121,5 +120,4 @@ public abstract class StorageReaderFile implements IItemReader { public String getItemKey(final T item) { return this.keySelector.apply(item); } - } diff --git a/src/main/java/forge/util/storage/StorageReaderFileSections.java b/src/main/java/forge/util/storage/StorageReaderFileSections.java index 93cc75b6dab..08881df2169 100644 --- a/src/main/java/forge/util/storage/StorageReaderFileSections.java +++ b/src/main/java/forge/util/storage/StorageReaderFileSections.java @@ -30,7 +30,6 @@ import org.apache.commons.lang3.StringUtils; import com.google.common.base.Function; import forge.util.FileUtil; -import forge.util.IItemReader; /** * This class treats every line of a given file as a source for a named object. @@ -38,18 +37,18 @@ import forge.util.IItemReader; * @param * the generic type */ -public abstract class StorageReaderFileSections implements IItemReader { +public abstract class StorageReaderFileSections extends StorageReaderBase { private final File file; - private final Function keySelector; public StorageReaderFileSections(final String pathname, final Function keySelector0) { this(new File(pathname), keySelector0); } public StorageReaderFileSections(final File file0, final Function keySelector0) { + super(keySelector0); this.file = file0; - this.keySelector = keySelector0; + } /* (non-Javadoc) diff --git a/src/main/java/forge/util/storage/StorageReaderFolder.java b/src/main/java/forge/util/storage/StorageReaderFolder.java index fca958cb83b..54f1a314e96 100644 --- a/src/main/java/forge/util/storage/StorageReaderFolder.java +++ b/src/main/java/forge/util/storage/StorageReaderFolder.java @@ -18,9 +18,11 @@ package forge.util.storage; import java.io.File; +import java.io.FileFilter; import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -34,7 +36,6 @@ import com.google.common.base.Function; import forge.deck.io.OldDeckFileFormatException; import forge.error.BugReporter; -import forge.util.IItemReader; /** * This class treats every file in the given folder as a source for a named @@ -43,19 +44,9 @@ import forge.util.IItemReader; * * @param the generic type */ -public abstract class StorageReaderFolder implements IItemReader { +public abstract class StorageReaderFolder extends StorageReaderBase { - private final File directory; - private final Function keySelector; - - /** - * Gets the directory. - * - * @return the directory - */ - protected final File getDirectory() { - return this.directory; - } + protected final File directory; /** * Instantiates a new storage reader folder. @@ -63,9 +54,9 @@ public abstract class StorageReaderFolder implements IItemReader { * @param deckDir0 the deck dir0 */ public StorageReaderFolder(final File deckDir0, Function keySelector0) { - + super(keySelector0); + this.directory = deckDir0; - keySelector = keySelector0; if (this.directory == null) { throw new IllegalArgumentException("No deck directory specified"); @@ -153,5 +144,18 @@ public abstract class StorageReaderFolder implements IItemReader { public String getItemKey(T item) { return keySelector.apply(item); } + + // methods handling nested folders are provided. It's up to consumer whether to use these or not. + @Override + public Iterable getSubFolders() { + File[] list = this.directory.listFiles(new FileFilter() { + + @Override + public boolean accept(File file) { + return file.isDirectory() && !file.isHidden(); + } + }); + return Arrays.asList(list); + } }