From 35edfd936b09e5931f79c11271f67787b846ff16 Mon Sep 17 00:00:00 2001 From: spr Date: Sat, 7 Sep 2013 04:26:58 +0000 Subject: [PATCH] - Fix : tab visibility now updated without using revertLayout() which could alter the state of the active layout and was possibly causing repaint artifacts. --- .../java/forge/gui/framework/DragCell.java | 54 +- src/main/java/forge/gui/menus/LayoutMenu.java | 23 +- src/main/java/forge/view/FView.java | 780 +++++++++--------- 3 files changed, 429 insertions(+), 428 deletions(-) diff --git a/src/main/java/forge/gui/framework/DragCell.java b/src/main/java/forge/gui/framework/DragCell.java index dad95e235a9..f0b9bbab02f 100644 --- a/src/main/java/forge/gui/framework/DragCell.java +++ b/src/main/java/forge/gui/framework/DragCell.java @@ -60,18 +60,6 @@ public final class DragCell extends JPanel implements ILocalRepaint { public DragCell() { super(new MigLayout("insets 0, gap 0, wrap 2")); - int borderT = SLayoutConstants.BORDER_T; - int headH = (showGameTabs() ? SLayoutConstants.HEAD_H : 0); - - this.add(pnlHead, - "w 100% - " + borderT + "px!" + ", " + "h " + headH + "px!"); - this.add(pnlBorderRight, - "w " + borderT + "px!" + ", " + "h 100% - " + borderT + "px!, span 1 2"); - this.add(pnlBody, - "w 100% - " + borderT + "px!" + ", " + "h 100% - " + (headH + borderT) + "px!"); - this.add(pnlBorderBottom, - "w 100% - " + borderT + "px!" + ", " + "h " + borderT + "px!"); - this.setOpaque(false); pnlHead.setOpaque(false); @@ -99,6 +87,30 @@ public final class DragCell extends JPanel implements ILocalRepaint { pnlHead.add(lblOverflow, "w 20px!, h 100%!, gap " + tabPaddingPx + "px " + tabPaddingPx + "px 0 0", -1); pnlBody.setCornerDiameter(0); + + doCellLayout(showGameTabs()); + } + + /** + * Refreshes the cell layout without affecting contents. + *

+ * Primarily used to toggle visibility of tabs. + */ + public void doCellLayout(boolean showTabs) { + this.removeAll(); + int borderT = SLayoutConstants.BORDER_T; + int headH = (showTabs ? SLayoutConstants.HEAD_H : 0); + this.add(pnlHead, + "w 100% - " + borderT + "px!" + ", " + "h " + headH + "px!"); + this.add(pnlBorderRight, + "w " + borderT + "px!" + ", " + "h 100% - " + borderT + "px!, span 1 2"); + this.add(pnlBody, + "w 100% - " + borderT + "px!" + ", " + "h 100% - " + (headH + borderT) + "px!"); + this.add(pnlBorderBottom, + "w 100% - " + borderT + "px!" + ", " + "h " + borderT + "px!"); + if (this.isShowing()) { + this.validate(); + } } /** @@ -333,24 +345,6 @@ public final class DragCell extends JPanel implements ILocalRepaint { return docSelected; } - /** - * Removes all components in this cell and - * rebuilds it without a header bar. - */ - public void hideHead() { - this.removeAll(); - // These cause the cell to be "bumped" over...hopefully can - // just slice out? Doublestrike 18-09-12 - // Looks good so far... Doublestrike 09-10-12 - - // this.add(pnlBorderRight, "w " + SLayoutConstants.BORDER_T + "px!, " - // + "h 100% - " + SLayoutConstants.BORDER_T + "px!, span 1 2"); - this.add(pnlBody, "w 100% - " + SLayoutConstants.BORDER_T + "px!, " - + "h 100% - " + SLayoutConstants.BORDER_T + "px!"); - // this.add(pnlBorderBottom, "w 100% - " + SLayoutConstants.BORDER_T + "px!, " - // + "h " + SLayoutConstants.BORDER_T + "px!"); - } - /** * Enable/disable resize on the X axis for this cell. * diff --git a/src/main/java/forge/gui/menus/LayoutMenu.java b/src/main/java/forge/gui/menus/LayoutMenu.java index 1e8a86e6288..bd9fc211a33 100644 --- a/src/main/java/forge/gui/menus/LayoutMenu.java +++ b/src/main/java/forge/gui/menus/LayoutMenu.java @@ -13,8 +13,8 @@ import javax.swing.JOptionPane; import javax.swing.JRadioButtonMenuItem; import forge.Singletons; -import forge.control.RestartUtil; import forge.control.FControl.Screens; +import forge.control.RestartUtil; import forge.gui.GuiChoose; import forge.gui.match.controllers.CDock; import forge.gui.menubar.MenuUtil; @@ -99,14 +99,14 @@ public final class LayoutMenu { Object[] options = {"Restart Now", "Restart Later"}; int reply = JOptionPane.showOptionDialog( - null, - "You must restart Forge for " + skin + " theme to take effect.", - "Change Theme", - JOptionPane.YES_NO_OPTION, - JOptionPane.INFORMATION_MESSAGE, - null, - options, - options[0]); + null, + "You must restart Forge for " + skin + " theme to take effect.", + "Change Theme", + JOptionPane.YES_NO_OPTION, + JOptionPane.INFORMATION_MESSAGE, + null, + options, + options[0]); if (reply == JOptionPane.YES_OPTION) { RestartUtil.restartApplication(null); } @@ -148,8 +148,9 @@ public final class LayoutMenu { return new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - prefs.setPref(FPref.UI_HIDE_GAME_TABS, !menuItem.getState()); - controller.revertLayout(); + boolean showTabs = menuItem.getState(); + FView.SINGLETON_INSTANCE.refreshAllCellLayouts(showTabs); + prefs.setPref(FPref.UI_HIDE_GAME_TABS, !showTabs); } }; } diff --git a/src/main/java/forge/view/FView.java b/src/main/java/forge/view/FView.java index b5d127ba5e9..b1e35913062 100644 --- a/src/main/java/forge/view/FView.java +++ b/src/main/java/forge/view/FView.java @@ -1,387 +1,393 @@ -package forge.view; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.util.ArrayList; -import java.util.Deque; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import javax.swing.JFrame; -import javax.swing.JLayeredPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.SwingUtilities; -import javax.swing.WindowConstants; -import javax.swing.border.EmptyBorder; -import javax.swing.border.LineBorder; - -import net.miginfocom.swing.MigLayout; - -import org.apache.commons.lang3.StringUtils; - -import com.google.common.collect.Lists; - -import forge.Singletons; -import forge.control.FControl; -import forge.control.RestartUtil; -import forge.gui.FNetOverlay; -import forge.gui.ImportDialog; -import forge.gui.SOverlayUtils; -import forge.gui.deckeditor.VDeckEditorUI; -import forge.gui.framework.DragCell; -import forge.gui.framework.EDocID; -import forge.gui.framework.SLayoutConstants; -import forge.gui.framework.SLayoutIO; -import forge.gui.home.VHomeUI; -import forge.gui.match.TargetingOverlay; -import forge.gui.match.VMatchUI; -import forge.gui.toolbox.CardFaceSymbols; -import forge.gui.toolbox.FAbsolutePositioner; -import forge.gui.toolbox.FButton; -import forge.gui.toolbox.FLabel; -import forge.gui.toolbox.FOverlay; -import forge.gui.toolbox.FPanel; -import forge.gui.toolbox.FSkin; -import forge.model.BuildInfo; -import forge.properties.NewConstants; - -/** */ -public enum FView { - /** */ - SINGLETON_INSTANCE; - - /** */ - public static final Integer TARGETING_LAYER = JLayeredPane.MODAL_LAYER - 1; - private final List allCells = new ArrayList(); - private SplashFrame frmSplash; - - // Non-singleton instances (deprecated, but not updated yet) - private ViewBazaarUI bazaar = null; - - // Top-level UI components; all have getters. - private final JFrame frmDocument = new JFrame(); - // A layered pane is the frame's viewport, allowing overlay effects. - private final JLayeredPane lpnDocument = new JLayeredPane(); - // The content panel is placed in the layered pane. - private final JPanel pnlContent = new JPanel(); - // An insets panel neatly maintains a space from the edges of the window and - // whatever layout is happening, without having to explicitly define a margin each time. - private FPanel pnlInsets; - // Preview panel is what is shown when a drag cell is being moved around - private final JPanel pnlPreview = new PreviewPanel(); - // Tab overflow is for the +X display for extra tabs. - private final JPanel pnlTabOverflow = new JPanel(new MigLayout("insets 0, gap 0, wrap")); - - private FView() { - frmSplash = new SplashFrame(); - - // Insets panel has background image / texture, which - // must be instantiated after the skin is loaded. - pnlInsets = new FPanel(new BorderLayout()); - } - - /** */ - public void initialize() { - // Frame styling - frmDocument.setMinimumSize(new Dimension(800, 600)); - frmDocument.setLocationRelativeTo(null); - frmDocument.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - frmDocument.setIconImage(FSkin.getIcon(FSkin.InterfaceIcons.ICO_FAVICON).getImage()); - frmDocument.setTitle("Forge: " + BuildInfo.getVersionString()); - - // Frame components - frmDocument.setContentPane(lpnDocument); - lpnDocument.add(pnlInsets, (Integer) 1); - FAbsolutePositioner.SINGLETON_INSTANCE.initialize(lpnDocument, (Integer) 2); - lpnDocument.add(pnlPreview, (Integer) 3); - lpnDocument.add(pnlTabOverflow, (Integer) 4); - lpnDocument.add(FOverlay.SINGLETON_INSTANCE.getPanel(), JLayeredPane.MODAL_LAYER); - // Note: when adding new panels here, keep in mind that the layered pane - // has a null layout, so new components will be W0 x H0 pixels - gotcha! - // FControl has a method called "sizeComponents" which will fix this. - lpnDocument.add(TargetingOverlay.SINGLETON_INSTANCE.getPanel(), TARGETING_LAYER); - lpnDocument.add(FNetOverlay.SINGLETON_INSTANCE.getPanel(), TARGETING_LAYER); - - pnlInsets.add(pnlContent, BorderLayout.CENTER); - pnlInsets.setBackgroundTexture(FSkin.getIcon(FSkin.Backgrounds.BG_TEXTURE)); - pnlInsets.setCornerDiameter(0); - pnlInsets.setBorder(new EmptyBorder( - SLayoutConstants.BORDER_T, SLayoutConstants.BORDER_T, 0, 0)); - - pnlContent.setOpaque(false); - pnlContent.setLayout(null); - - FOverlay.SINGLETON_INSTANCE.getPanel().setBackground(FSkin.getColor(FSkin.Colors.CLR_OVERLAY)); - - // Populate all drag tab components. - this.cacheUIStates(); - - // Does not use progress bar, due to be deprecated with battlefield refactoring. - CardFaceSymbols.loadImages(); - - // Initialize actions on all drag tab components (which should - // be realized / populated already). - for (EDocID doc : EDocID.values()) { - doc.getDoc().getLayoutControl().initialize(); - } - - // All is ready to go - fire up home screen and discard splash frame. - Singletons.getControl().changeState(FControl.Screens.HOME_SCREEN); - - FView.this.frmSplash.dispose(); - FView.this.frmSplash = null; - - SLayoutIO.loadWindowLayout(); - frmDocument.setVisible(true); - - // remove this once our userbase has been migrated to the profile layout - { - // get profile directories -- if one of them is actually under the res directory, don't - // count is as data to migrate - final Set profileDirs = new HashSet(); - for (String dname : NewConstants.PROFILE_DIRS) { - profileDirs.add(new File(dname)); - } - - final List resDirs = new ArrayList(); - for (String resDir : Lists.newArrayList("decks", "gauntlet", "layouts", "pics", "preferences", "quest/data")) { - resDirs.add(new File("res", resDir)); - } - - final Set doNotDeleteDirs = new HashSet(); - for (String dir : Lists.newArrayList("decks", "decks/constructed", "decks/draft", "decks/plane", "decks/scheme", "decks/sealed", "gauntlet", "layouts", "pics", "preferences", "quest/data")) { - doNotDeleteDirs.add(new File("res", dir)); - } - - // if we have any data to migrate, pop up the migration dialog - if (_addRemainingFiles(null, resDirs, profileDirs, doNotDeleteDirs)) { - new ImportDialog("res", new Runnable() { - @Override public void run() { - // remove known cruft files, yes this is ugly, but it's also temporary - for (String cruftFile : Lists.newArrayList("decks/SkieraCube-cards_not_supported_yet.txt", "decks/cube/ArabianExtended.dck", "decks/cube/GtcGuildBoros.dck", "decks/cube/GtcGuildDimir.dck", "decks/cube/GtcGuildGruul.dck", "decks/cube/GtcGuildOrzhov.dck", "decks/cube/GtcGuildSimic.dck", "decks/cube/GtcPromoBoros.dck", "decks/cube/GtcPromoDimir.dck", "decks/cube/GtcPromoGruul.dck", "decks/cube/GtcPromoOrzhov.dck", "decks/cube/GtcPromoSimic.dck", "decks/cube/JuzamjediCube.dck", "decks/cube/RtRGuildAzorius.dck", "decks/cube/RtRGuildGolgari.dck", "decks/cube/RtRGuildIzzet.dck", "decks/cube/RtRGuildRakdos.dck", "decks/cube/RtRGuildSelesnya.dck", "decks/cube/RtRPromoAzorius.dck", "decks/cube/RtRPromoGolgari.dck", "decks/cube/RtRPromoIzzet.dck", "decks/cube/RtRPromoRakdos.dck", "decks/cube/RtRPromoSelesnya.dck", "decks/cube/SkieraCube.dck", "gauntlet/LOCKED_DotP Preconstructed.dat", "gauntlet/LOCKED_Swimming With Sharks.dat", "layouts/editor_default.xml", "layouts/home_default.xml", "layouts/match_default.xml", "pics/snow_covered_forest1.jpg", "pics/snow_covered_forest2.jpg", "pics/snow_covered_forest3.jpg", "pics/snow_covered_island1.jpg", "pics/snow_covered_island2.jpg", "pics/snow_covered_island3.jpg", "pics/snow_covered_mountain1.jpg", "pics/snow_covered_mountain2.jpg", "pics/snow_covered_mountain3.jpg", "pics/snow_covered_plains1.jpg", "pics/snow_covered_plains2.jpg", "pics/snow_covered_plains3.jpg", "pics/snow_covered_swamp1.jpg", "pics/snow_covered_swamp2.jpg", "pics/snow_covered_swamp3.jpg", "pics/VAN/Birds of Paradise Avatar.full.jpg", "pics/VAN/Erhnam Djinn Avatar.full.jpg", "pics/VAN/Goblin Warchief Avatar.full.jpg", "pics/VAN/Grinning Demon Avatar.full.jpg", "pics/VAN/Platinum Angel Avatar.full.jpg", "pics/VAN/Prodigal Sorcerer Avatar.full.jpg", "pics/VAN/Rith, the Awakener Avatar.full.jpg", "pics/VAN/Royal Assassin Avatar.full.jpg", "pics/VAN/Serra Angel Avatar.full.jpg", "pics/VAN/Tradewind Rider Avatar.full.jpg", "pics_product/10E.jpg", "pics_product/2ED.jpg", "pics_product/3ED.jpg", "pics_product/4ED.jpg", "pics_product/5DN.jpg", "pics_product/5ED.jpg", "pics_product/6ED.jpg", "pics_product/7ED.jpg", "pics_product/8ED.jpg", "pics_product/9ED.jpg", "pics_product/ALA.jpg", "pics_product/ALL.jpg", "pics_product/APC.jpg", "pics_product/ARB.jpg", "pics_product/ARN.jpg", "pics_product/ATQ.jpg", "pics_product/BOK.jpg", "pics_product/CFX.jpg", "pics_product/CHK.jpg", "pics_product/CHR.jpg", "pics_product/CSP.jpg", "pics_product/DIS.jpg", "pics_product/DKA.jpg", "pics_product/DRK.jpg", "pics_product/DST.jpg", "pics_product/EVE.jpg", "pics_product/EXO.jpg", "pics_product/FEM.jpg", "pics_product/FUT.jpg", "pics_product/GPT.jpg", "pics_product/HML.jpg", "pics_product/ICE.jpg", "pics_product/INV.jpg", "pics_product/ISD.jpg", "pics_product/JUD.jpg", "pics_product/LEA.jpg", "pics_product/LEB.jpg", "pics_product/LEG.jpg", "pics_product/LGN.jpg", "pics_product/LRW.jpg", "pics_product/M10.jpg", "pics_product/M11.jpg", "pics_product/M12.jpg", "pics_product/MBS.jpg", "pics_product/MIR.jpg", "pics_product/MMQ.jpg", "pics_product/MOR.jpg", "pics_product/MRD.jpg", "pics_product/NMS.jpg", "pics_product/NPH.jpg", "pics_product/ODY.jpg", "pics_product/ONS.jpg", "pics_product/PCY.jpg", "pics_product/PLC.jpg", "pics_product/PLS.jpg", "pics_product/PO2.jpg", "pics_product/POR.jpg", "pics_product/PTK.jpg", "pics_product/RAV.jpg", "pics_product/ROE.jpg", "pics_product/S99.jpg", "pics_product/SCG.jpg", "pics_product/SHM.jpg", "pics_product/SOK.jpg", "pics_product/SOM.jpg", "pics_product/STH.jpg", "pics_product/TMP.jpg", "pics_product/TOR.jpg", "pics_product/TSP.jpg", "pics_product/UDS.jpg", "pics_product/ULG.jpg", "pics_product/USG.jpg", "pics_product/VIS.jpg", "pics_product/WTH.jpg", "pics_product/WWK.jpg", "pics_product/ZEN.jpg", "pics_product/booster/7E.png", "pics_product/booster/AP.png", "pics_product/booster/DPA.png", "pics_product/booster/EX.png", "pics_product/booster/IN.png", "pics_product/booster/MI.png", "pics_product/booster/OD.png", "pics_product/booster/PS.png", "pics_product/booster/ST.png", "pics_product/booster/TE.png", "pics_product/booster/UD.png", "pics_product/booster/UL.png", "pics_product/booster/UZ.png", "pics_product/booster/VI.png", "pics_product/booster/WL.png", "preferences/.project", "preferences/editor.default.preferences", "preferences/main.properties", "quest/quest.preferences", "quest/quest.properties")) { - new File("res", cruftFile).delete(); - } - - // assemble a list of remaining files. - final List remainingFiles = new LinkedList(); - _addRemainingFiles(remainingFiles, resDirs, profileDirs, doNotDeleteDirs); - - // if any files remain, display them and make clear that they should be moved or - // deleted manually or the user will continue to be prompted for migration - final FPanel p = new FPanel(new MigLayout("insets dialog, gap 10, center, wrap")); - p.setOpaque(false); - p.setBackgroundTexture(FSkin.getIcon(FSkin.Backgrounds.BG_TEXTURE)); - - if (remainingFiles.isEmpty()) { - p.add(new FLabel.Builder().text("You're done! It looks like everything went smoothly." + - " Now just restart Forge to load the data from its new home! Note that there is more data available" + - " from the downloaders now. You might want to run through the content downloaders to check for new files.").build()); - } else { - p.add(new FLabel.Builder().text("There seem to be a few files left over in your old data" + - " directories. They should be deleted or moved somewhere else to avoid having the data" + - " migration prompt pop up again!").build()); - - JTextArea files = new JTextArea(StringUtils.join(remainingFiles, '\n')); - files.setFont(new Font("Monospaced", Font.PLAIN, 10)); - files.setOpaque(false); - files.setWrapStyleWord(true); - files.setLineWrap(true); - files.setEditable(false); - JScrollPane scroller = new JScrollPane(files); - p.add(scroller, "w 600:100%:100%, h 100:100%:100%, gaptop 10"); - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - // resize the panel properly for the new log contents - p.getParent().validate(); - p.getParent().invalidate(); - } - }); - } - - final FButton btnOk = new FButton(remainingFiles.isEmpty() ? "Restart Forge" : "Close Forge"); - btnOk.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - if (remainingFiles.isEmpty()) { - RestartUtil.restartApplication(null); - } else { - System.exit(0); - } - } - }); - p.add(btnOk, "center, w pref+64!, h pref+12!, gaptop 20"); - - JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel(); - overlay.setLayout(new MigLayout("insets 0, gap 0, wrap, ax center, ay center")); - overlay.add(p, "w 100::80%, h 50::90%"); - SOverlayUtils.showOverlay(); - - SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { btnOk.requestFocusInWindow(); } - }); - } - }); - } - } - } - - // will populate remainingFiles with remaining files if not null, returns whether any files have - // been added to remainingFiles (or would have been added if remainingFiles is null) - // directories listed in profileDirs will not be searched - // removes empty directories to reduce tree conflicts - private static boolean _addRemainingFiles(List remainingFiles, List dirRoots, Set profileDirs, Set doNotDeleteDirs) { - Deque stack = new LinkedList(dirRoots); - Set seenDirs = new HashSet(); - boolean ret = false; - while (!stack.isEmpty()) { - File cur = stack.peek(); - if (profileDirs.contains(cur)) { - // don't touch active profile dirs - stack.pop(); - continue; - } - - if (seenDirs.contains(cur)) { - cur = stack.pop(); - if (cur.exists() && !doNotDeleteDirs.contains(cur)) { - // remove empty dir (will fail if not empty) - cur.delete(); - } - continue; - } - - seenDirs.add(cur); - File[] curListing = cur.listFiles(); - if (null == curListing) { - continue; - } - for (File f : curListing) { - if (f.isDirectory()) { - if (!".svn".equals(f.getName())) { - stack.push(f); - } - } else { - if (null == remainingFiles) { - return true; - } - remainingFiles.add(f); - ret = true; - } - } - } - - return ret; - } - - /** @return {@link forge.view.SplashFrame} */ - public SplashFrame getSplash() { - return frmSplash; - } - - /** @return {@link javax.swing.JFrame} */ - public JFrame getFrame() { - return frmDocument; - } - - /** @return {@link javax.swing.JLayeredPane} */ - public JLayeredPane getLpnDocument() { - return lpnDocument; - } - - /** @return {@link forge.gui.toolbox.FPanel} */ - public FPanel getPnlInsets() { - return pnlInsets; - } - - /** @return {@link javax.swing.JPanel} */ - public JPanel getPnlContent() { - return pnlContent; - } - - /** @return {@link javax.swing.JPanel} */ - public JPanel getPnlPreview() { - return pnlPreview; - } - - /** @return {@link javax.swing.JPanel} */ - public JPanel getPnlTabOverflow() { - return pnlTabOverflow; - } - - /** @return {@link java.util.List}<{@link forge.gui.framework.DragCell}> */ - public List getDragCells() { - final List clone = new ArrayList(); - clone.addAll(allCells); - return clone; - } - - /** @param pnl0   {@link forge.gui.framework.DragCell} */ - public void addDragCell(final DragCell pnl0) { - allCells.add(pnl0); - pnlContent.add(pnl0); - } - - /** @param pnl0   {@link forge.gui.framework.DragCell} */ - public void removeDragCell(final DragCell pnl0) { - allCells.remove(pnl0); - pnlContent.remove(pnl0); - } - - /** */ - public void removeAllDragCells() { - allCells.clear(); - pnlContent.removeAll(); - } - - /** PreviewPanel shows where a dragged component could - * come to rest when the mouse is released.
- * This class is an unfortunate necessity to overcome - * translucency issues for preview panel. */ - @SuppressWarnings("serial") - class PreviewPanel extends JPanel { - /** PreviewPanel shows where a dragged component could - * come to rest when the mouse is released. */ - public PreviewPanel() { - super(); - setOpaque(false); - setVisible(false); - setBorder(new LineBorder(Color.DARK_GRAY, 2)); - } - - @Override - public void paintComponent(final Graphics g) { - super.paintComponent(g); - g.setColor(new Color(0, 0, 0, 50)); - g.fillRect(0, 0, getWidth(), getHeight()); - } - } - - /** @return {@link forge.view.ViewBazaarUI} */ - public ViewBazaarUI getViewBazaar() { - if (Singletons.getControl().getState() != FControl.Screens.QUEST_BAZAAR) { - throw new IllegalArgumentException("FView$getViewBazaar\n" - + "may only be called while the bazaar UI is showing."); - } - bazaar.refreshLastInstance(); - return FView.this.bazaar; - } - - /** */ - private void cacheUIStates() { - FView.this.bazaar = new ViewBazaarUI(Singletons.getModel().getQuest().getBazaar()); - VMatchUI.SINGLETON_INSTANCE.instantiate(); - VHomeUI.SINGLETON_INSTANCE.instantiate(); - VDeckEditorUI.SINGLETON_INSTANCE.instantiate(); - } -} +package forge.view; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import javax.swing.JFrame; +import javax.swing.JLayeredPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; + +import net.miginfocom.swing.MigLayout; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Lists; + +import forge.Singletons; +import forge.control.FControl; +import forge.control.RestartUtil; +import forge.gui.FNetOverlay; +import forge.gui.ImportDialog; +import forge.gui.SOverlayUtils; +import forge.gui.deckeditor.VDeckEditorUI; +import forge.gui.framework.DragCell; +import forge.gui.framework.EDocID; +import forge.gui.framework.SLayoutConstants; +import forge.gui.framework.SLayoutIO; +import forge.gui.home.VHomeUI; +import forge.gui.match.TargetingOverlay; +import forge.gui.match.VMatchUI; +import forge.gui.toolbox.CardFaceSymbols; +import forge.gui.toolbox.FAbsolutePositioner; +import forge.gui.toolbox.FButton; +import forge.gui.toolbox.FLabel; +import forge.gui.toolbox.FOverlay; +import forge.gui.toolbox.FPanel; +import forge.gui.toolbox.FSkin; +import forge.model.BuildInfo; +import forge.properties.NewConstants; + +/** */ +public enum FView { + /** */ + SINGLETON_INSTANCE; + + /** */ + public static final Integer TARGETING_LAYER = JLayeredPane.MODAL_LAYER - 1; + private final List allCells = new ArrayList(); + private SplashFrame frmSplash; + + // Non-singleton instances (deprecated, but not updated yet) + private ViewBazaarUI bazaar = null; + + // Top-level UI components; all have getters. + private final JFrame frmDocument = new JFrame(); + // A layered pane is the frame's viewport, allowing overlay effects. + private final JLayeredPane lpnDocument = new JLayeredPane(); + // The content panel is placed in the layered pane. + private final JPanel pnlContent = new JPanel(); + // An insets panel neatly maintains a space from the edges of the window and + // whatever layout is happening, without having to explicitly define a margin each time. + private FPanel pnlInsets; + // Preview panel is what is shown when a drag cell is being moved around + private final JPanel pnlPreview = new PreviewPanel(); + // Tab overflow is for the +X display for extra tabs. + private final JPanel pnlTabOverflow = new JPanel(new MigLayout("insets 0, gap 0, wrap")); + + private FView() { + frmSplash = new SplashFrame(); + + // Insets panel has background image / texture, which + // must be instantiated after the skin is loaded. + pnlInsets = new FPanel(new BorderLayout()); + } + + /** */ + public void initialize() { + // Frame styling + frmDocument.setMinimumSize(new Dimension(800, 600)); + frmDocument.setLocationRelativeTo(null); + frmDocument.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frmDocument.setIconImage(FSkin.getIcon(FSkin.InterfaceIcons.ICO_FAVICON).getImage()); + frmDocument.setTitle("Forge: " + BuildInfo.getVersionString()); + + // Frame components + frmDocument.setContentPane(lpnDocument); + lpnDocument.add(pnlInsets, (Integer) 1); + FAbsolutePositioner.SINGLETON_INSTANCE.initialize(lpnDocument, (Integer) 2); + lpnDocument.add(pnlPreview, (Integer) 3); + lpnDocument.add(pnlTabOverflow, (Integer) 4); + lpnDocument.add(FOverlay.SINGLETON_INSTANCE.getPanel(), JLayeredPane.MODAL_LAYER); + // Note: when adding new panels here, keep in mind that the layered pane + // has a null layout, so new components will be W0 x H0 pixels - gotcha! + // FControl has a method called "sizeComponents" which will fix this. + lpnDocument.add(TargetingOverlay.SINGLETON_INSTANCE.getPanel(), TARGETING_LAYER); + lpnDocument.add(FNetOverlay.SINGLETON_INSTANCE.getPanel(), TARGETING_LAYER); + + pnlInsets.add(pnlContent, BorderLayout.CENTER); + pnlInsets.setBackgroundTexture(FSkin.getIcon(FSkin.Backgrounds.BG_TEXTURE)); + pnlInsets.setCornerDiameter(0); + pnlInsets.setBorder(new EmptyBorder( + SLayoutConstants.BORDER_T, SLayoutConstants.BORDER_T, 0, 0)); + + pnlContent.setOpaque(false); + pnlContent.setLayout(null); + + FOverlay.SINGLETON_INSTANCE.getPanel().setBackground(FSkin.getColor(FSkin.Colors.CLR_OVERLAY)); + + // Populate all drag tab components. + this.cacheUIStates(); + + // Does not use progress bar, due to be deprecated with battlefield refactoring. + CardFaceSymbols.loadImages(); + + // Initialize actions on all drag tab components (which should + // be realized / populated already). + for (EDocID doc : EDocID.values()) { + doc.getDoc().getLayoutControl().initialize(); + } + + // All is ready to go - fire up home screen and discard splash frame. + Singletons.getControl().changeState(FControl.Screens.HOME_SCREEN); + + FView.this.frmSplash.dispose(); + FView.this.frmSplash = null; + + SLayoutIO.loadWindowLayout(); + frmDocument.setVisible(true); + + // remove this once our userbase has been migrated to the profile layout + { + // get profile directories -- if one of them is actually under the res directory, don't + // count is as data to migrate + final Set profileDirs = new HashSet(); + for (String dname : NewConstants.PROFILE_DIRS) { + profileDirs.add(new File(dname)); + } + + final List resDirs = new ArrayList(); + for (String resDir : Lists.newArrayList("decks", "gauntlet", "layouts", "pics", "preferences", "quest/data")) { + resDirs.add(new File("res", resDir)); + } + + final Set doNotDeleteDirs = new HashSet(); + for (String dir : Lists.newArrayList("decks", "decks/constructed", "decks/draft", "decks/plane", "decks/scheme", "decks/sealed", "gauntlet", "layouts", "pics", "preferences", "quest/data")) { + doNotDeleteDirs.add(new File("res", dir)); + } + + // if we have any data to migrate, pop up the migration dialog + if (_addRemainingFiles(null, resDirs, profileDirs, doNotDeleteDirs)) { + new ImportDialog("res", new Runnable() { + @Override public void run() { + // remove known cruft files, yes this is ugly, but it's also temporary + for (String cruftFile : Lists.newArrayList("decks/SkieraCube-cards_not_supported_yet.txt", "decks/cube/ArabianExtended.dck", "decks/cube/GtcGuildBoros.dck", "decks/cube/GtcGuildDimir.dck", "decks/cube/GtcGuildGruul.dck", "decks/cube/GtcGuildOrzhov.dck", "decks/cube/GtcGuildSimic.dck", "decks/cube/GtcPromoBoros.dck", "decks/cube/GtcPromoDimir.dck", "decks/cube/GtcPromoGruul.dck", "decks/cube/GtcPromoOrzhov.dck", "decks/cube/GtcPromoSimic.dck", "decks/cube/JuzamjediCube.dck", "decks/cube/RtRGuildAzorius.dck", "decks/cube/RtRGuildGolgari.dck", "decks/cube/RtRGuildIzzet.dck", "decks/cube/RtRGuildRakdos.dck", "decks/cube/RtRGuildSelesnya.dck", "decks/cube/RtRPromoAzorius.dck", "decks/cube/RtRPromoGolgari.dck", "decks/cube/RtRPromoIzzet.dck", "decks/cube/RtRPromoRakdos.dck", "decks/cube/RtRPromoSelesnya.dck", "decks/cube/SkieraCube.dck", "gauntlet/LOCKED_DotP Preconstructed.dat", "gauntlet/LOCKED_Swimming With Sharks.dat", "layouts/editor_default.xml", "layouts/home_default.xml", "layouts/match_default.xml", "pics/snow_covered_forest1.jpg", "pics/snow_covered_forest2.jpg", "pics/snow_covered_forest3.jpg", "pics/snow_covered_island1.jpg", "pics/snow_covered_island2.jpg", "pics/snow_covered_island3.jpg", "pics/snow_covered_mountain1.jpg", "pics/snow_covered_mountain2.jpg", "pics/snow_covered_mountain3.jpg", "pics/snow_covered_plains1.jpg", "pics/snow_covered_plains2.jpg", "pics/snow_covered_plains3.jpg", "pics/snow_covered_swamp1.jpg", "pics/snow_covered_swamp2.jpg", "pics/snow_covered_swamp3.jpg", "pics/VAN/Birds of Paradise Avatar.full.jpg", "pics/VAN/Erhnam Djinn Avatar.full.jpg", "pics/VAN/Goblin Warchief Avatar.full.jpg", "pics/VAN/Grinning Demon Avatar.full.jpg", "pics/VAN/Platinum Angel Avatar.full.jpg", "pics/VAN/Prodigal Sorcerer Avatar.full.jpg", "pics/VAN/Rith, the Awakener Avatar.full.jpg", "pics/VAN/Royal Assassin Avatar.full.jpg", "pics/VAN/Serra Angel Avatar.full.jpg", "pics/VAN/Tradewind Rider Avatar.full.jpg", "pics_product/10E.jpg", "pics_product/2ED.jpg", "pics_product/3ED.jpg", "pics_product/4ED.jpg", "pics_product/5DN.jpg", "pics_product/5ED.jpg", "pics_product/6ED.jpg", "pics_product/7ED.jpg", "pics_product/8ED.jpg", "pics_product/9ED.jpg", "pics_product/ALA.jpg", "pics_product/ALL.jpg", "pics_product/APC.jpg", "pics_product/ARB.jpg", "pics_product/ARN.jpg", "pics_product/ATQ.jpg", "pics_product/BOK.jpg", "pics_product/CFX.jpg", "pics_product/CHK.jpg", "pics_product/CHR.jpg", "pics_product/CSP.jpg", "pics_product/DIS.jpg", "pics_product/DKA.jpg", "pics_product/DRK.jpg", "pics_product/DST.jpg", "pics_product/EVE.jpg", "pics_product/EXO.jpg", "pics_product/FEM.jpg", "pics_product/FUT.jpg", "pics_product/GPT.jpg", "pics_product/HML.jpg", "pics_product/ICE.jpg", "pics_product/INV.jpg", "pics_product/ISD.jpg", "pics_product/JUD.jpg", "pics_product/LEA.jpg", "pics_product/LEB.jpg", "pics_product/LEG.jpg", "pics_product/LGN.jpg", "pics_product/LRW.jpg", "pics_product/M10.jpg", "pics_product/M11.jpg", "pics_product/M12.jpg", "pics_product/MBS.jpg", "pics_product/MIR.jpg", "pics_product/MMQ.jpg", "pics_product/MOR.jpg", "pics_product/MRD.jpg", "pics_product/NMS.jpg", "pics_product/NPH.jpg", "pics_product/ODY.jpg", "pics_product/ONS.jpg", "pics_product/PCY.jpg", "pics_product/PLC.jpg", "pics_product/PLS.jpg", "pics_product/PO2.jpg", "pics_product/POR.jpg", "pics_product/PTK.jpg", "pics_product/RAV.jpg", "pics_product/ROE.jpg", "pics_product/S99.jpg", "pics_product/SCG.jpg", "pics_product/SHM.jpg", "pics_product/SOK.jpg", "pics_product/SOM.jpg", "pics_product/STH.jpg", "pics_product/TMP.jpg", "pics_product/TOR.jpg", "pics_product/TSP.jpg", "pics_product/UDS.jpg", "pics_product/ULG.jpg", "pics_product/USG.jpg", "pics_product/VIS.jpg", "pics_product/WTH.jpg", "pics_product/WWK.jpg", "pics_product/ZEN.jpg", "pics_product/booster/7E.png", "pics_product/booster/AP.png", "pics_product/booster/DPA.png", "pics_product/booster/EX.png", "pics_product/booster/IN.png", "pics_product/booster/MI.png", "pics_product/booster/OD.png", "pics_product/booster/PS.png", "pics_product/booster/ST.png", "pics_product/booster/TE.png", "pics_product/booster/UD.png", "pics_product/booster/UL.png", "pics_product/booster/UZ.png", "pics_product/booster/VI.png", "pics_product/booster/WL.png", "preferences/.project", "preferences/editor.default.preferences", "preferences/main.properties", "quest/quest.preferences", "quest/quest.properties")) { + new File("res", cruftFile).delete(); + } + + // assemble a list of remaining files. + final List remainingFiles = new LinkedList(); + _addRemainingFiles(remainingFiles, resDirs, profileDirs, doNotDeleteDirs); + + // if any files remain, display them and make clear that they should be moved or + // deleted manually or the user will continue to be prompted for migration + final FPanel p = new FPanel(new MigLayout("insets dialog, gap 10, center, wrap")); + p.setOpaque(false); + p.setBackgroundTexture(FSkin.getIcon(FSkin.Backgrounds.BG_TEXTURE)); + + if (remainingFiles.isEmpty()) { + p.add(new FLabel.Builder().text("You're done! It looks like everything went smoothly." + + " Now just restart Forge to load the data from its new home! Note that there is more data available" + + " from the downloaders now. You might want to run through the content downloaders to check for new files.").build()); + } else { + p.add(new FLabel.Builder().text("There seem to be a few files left over in your old data" + + " directories. They should be deleted or moved somewhere else to avoid having the data" + + " migration prompt pop up again!").build()); + + JTextArea files = new JTextArea(StringUtils.join(remainingFiles, '\n')); + files.setFont(new Font("Monospaced", Font.PLAIN, 10)); + files.setOpaque(false); + files.setWrapStyleWord(true); + files.setLineWrap(true); + files.setEditable(false); + JScrollPane scroller = new JScrollPane(files); + p.add(scroller, "w 600:100%:100%, h 100:100%:100%, gaptop 10"); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + // resize the panel properly for the new log contents + p.getParent().validate(); + p.getParent().invalidate(); + } + }); + } + + final FButton btnOk = new FButton(remainingFiles.isEmpty() ? "Restart Forge" : "Close Forge"); + btnOk.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + if (remainingFiles.isEmpty()) { + RestartUtil.restartApplication(null); + } else { + System.exit(0); + } + } + }); + p.add(btnOk, "center, w pref+64!, h pref+12!, gaptop 20"); + + JPanel overlay = FOverlay.SINGLETON_INSTANCE.getPanel(); + overlay.setLayout(new MigLayout("insets 0, gap 0, wrap, ax center, ay center")); + overlay.add(p, "w 100::80%, h 50::90%"); + SOverlayUtils.showOverlay(); + + SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { btnOk.requestFocusInWindow(); } + }); + } + }); + } + } + } + + // will populate remainingFiles with remaining files if not null, returns whether any files have + // been added to remainingFiles (or would have been added if remainingFiles is null) + // directories listed in profileDirs will not be searched + // removes empty directories to reduce tree conflicts + private static boolean _addRemainingFiles(List remainingFiles, List dirRoots, Set profileDirs, Set doNotDeleteDirs) { + Deque stack = new LinkedList(dirRoots); + Set seenDirs = new HashSet(); + boolean ret = false; + while (!stack.isEmpty()) { + File cur = stack.peek(); + if (profileDirs.contains(cur)) { + // don't touch active profile dirs + stack.pop(); + continue; + } + + if (seenDirs.contains(cur)) { + cur = stack.pop(); + if (cur.exists() && !doNotDeleteDirs.contains(cur)) { + // remove empty dir (will fail if not empty) + cur.delete(); + } + continue; + } + + seenDirs.add(cur); + File[] curListing = cur.listFiles(); + if (null == curListing) { + continue; + } + for (File f : curListing) { + if (f.isDirectory()) { + if (!".svn".equals(f.getName())) { + stack.push(f); + } + } else { + if (null == remainingFiles) { + return true; + } + remainingFiles.add(f); + ret = true; + } + } + } + + return ret; + } + + /** @return {@link forge.view.SplashFrame} */ + public SplashFrame getSplash() { + return frmSplash; + } + + /** @return {@link javax.swing.JFrame} */ + public JFrame getFrame() { + return frmDocument; + } + + /** @return {@link javax.swing.JLayeredPane} */ + public JLayeredPane getLpnDocument() { + return lpnDocument; + } + + /** @return {@link forge.gui.toolbox.FPanel} */ + public FPanel getPnlInsets() { + return pnlInsets; + } + + /** @return {@link javax.swing.JPanel} */ + public JPanel getPnlContent() { + return pnlContent; + } + + /** @return {@link javax.swing.JPanel} */ + public JPanel getPnlPreview() { + return pnlPreview; + } + + /** @return {@link javax.swing.JPanel} */ + public JPanel getPnlTabOverflow() { + return pnlTabOverflow; + } + + /** @return {@link java.util.List}<{@link forge.gui.framework.DragCell}> */ + public List getDragCells() { + final List clone = new ArrayList(); + clone.addAll(allCells); + return clone; + } + + /** @param pnl0   {@link forge.gui.framework.DragCell} */ + public void addDragCell(final DragCell pnl0) { + allCells.add(pnl0); + pnlContent.add(pnl0); + } + + /** @param pnl0   {@link forge.gui.framework.DragCell} */ + public void removeDragCell(final DragCell pnl0) { + allCells.remove(pnl0); + pnlContent.remove(pnl0); + } + + /** */ + public void removeAllDragCells() { + allCells.clear(); + pnlContent.removeAll(); + } + + /** PreviewPanel shows where a dragged component could + * come to rest when the mouse is released.
+ * This class is an unfortunate necessity to overcome + * translucency issues for preview panel. */ + @SuppressWarnings("serial") + class PreviewPanel extends JPanel { + /** PreviewPanel shows where a dragged component could + * come to rest when the mouse is released. */ + public PreviewPanel() { + super(); + setOpaque(false); + setVisible(false); + setBorder(new LineBorder(Color.DARK_GRAY, 2)); + } + + @Override + public void paintComponent(final Graphics g) { + super.paintComponent(g); + g.setColor(new Color(0, 0, 0, 50)); + g.fillRect(0, 0, getWidth(), getHeight()); + } + } + + /** @return {@link forge.view.ViewBazaarUI} */ + public ViewBazaarUI getViewBazaar() { + if (Singletons.getControl().getState() != FControl.Screens.QUEST_BAZAAR) { + throw new IllegalArgumentException("FView$getViewBazaar\n" + + "may only be called while the bazaar UI is showing."); + } + bazaar.refreshLastInstance(); + return FView.this.bazaar; + } + + /** */ + private void cacheUIStates() { + FView.this.bazaar = new ViewBazaarUI(Singletons.getModel().getQuest().getBazaar()); + VMatchUI.SINGLETON_INSTANCE.instantiate(); + VHomeUI.SINGLETON_INSTANCE.instantiate(); + VDeckEditorUI.SINGLETON_INSTANCE.instantiate(); + } + + public void refreshAllCellLayouts(boolean showTabs) { + for (DragCell cell : allCells) { + cell.doCellLayout(showTabs); + } + } +}