diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index fbbd75f0bc..22e99b6909 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -92,6 +92,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private static MageFrame instance; private final ConnectDialog connectDialog; + private final WhatsNewDialog whatsNewDialog; private final ErrorDialog errorDialog; private static CallbackClient callbackClient; private static final Preferences PREFS = Preferences.userNodeForPackage(MageFrame.class); @@ -243,6 +244,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { SessionHandler.startSession(this); callbackClient = new CallbackClientImpl(this); connectDialog = new ConnectDialog(); + whatsNewDialog = new WhatsNewDialog(); desktopPane.add(connectDialog, JLayeredPane.MODAL_LAYER); errorDialog = new ErrorDialog(); errorDialog.setLocation(100, 100); @@ -329,6 +331,11 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { SystemUtil.toggleMacOSFullScreenMode(this); } } + + // run what's new checks (loading in background) + SwingUtilities.invokeLater(() -> { + whatsNewDialog.checkUpdatesAndShow(false); + }); } private void setWindowTitle() { @@ -1523,6 +1530,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { addTooltipContainer(); } + + public WhatsNewDialog getWhatsNewDialog() { + return whatsNewDialog; + } } class MagePaneMenuItem extends JCheckBoxMenuItem { diff --git a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.form b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.form index f9036d14f4..3055978fc8 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.form +++ b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.form @@ -27,14 +27,23 @@ - - - - + + + + - - + + + + + + + + + + + @@ -54,8 +63,11 @@ - - + + + + + @@ -64,7 +76,7 @@ - + @@ -95,5 +107,13 @@ + + + + + + + + diff --git a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java index a311aefc20..d900c12b9c 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/AboutDialog.java @@ -1,26 +1,18 @@ - - package mage.client.dialog; +import mage.client.MageFrame; import mage.utils.MageVersion; -/* - * AboutDialog.java - * - * Created on Mar 10, 2010, 8:19:41 AM - */ - +import javax.swing.*; +import java.awt.event.KeyEvent; /** - * - * @author BetaSteward_at_googlemail.com + * @author JayDi85 */ public class AboutDialog extends MageDialog { - /** Creates new form AboutDialog */ public AboutDialog() { - this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); initComponents(); this.modal = false; } @@ -28,10 +20,28 @@ public class AboutDialog extends MageDialog { public void showDialog(MageVersion version) { this.lblVersion.setText(version.toString()); this.setLocation(100, 100); + + // windows settings + MageFrame.getDesktop().remove(this); + if (this.isModal()) { + MageFrame.getDesktop().add(this, JLayeredPane.MODAL_LAYER); + } else { + MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + } + this.makeWindowCentered(); + + // Close on "ESC" + registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + this.setVisible(true); } - /** This method is called from within the constructor to + private void onCancel() { + this.hideDialog(); + } + + /** + * This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. @@ -46,12 +56,17 @@ public class AboutDialog extends MageDialog { jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); jLabel4 = new javax.swing.JLabel(); + btnWhatsNew = new javax.swing.JButton(); setMaximizable(true); setTitle("About XMage"); - btnOk.setText("OK"); - btnOk.addActionListener(this::btnOkActionPerformed); + btnOk.setText("Close"); + btnOk.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnOkActionPerformed(evt); + } + }); jLabel1.setText("XMage client"); @@ -59,53 +74,74 @@ public class AboutDialog extends MageDialog { jLabel2.setText("Courtesy: BetaSteward@googlemail.com. Site: http://XMage.de/"); - jLabel3.setText("Devs: BetaSteward, Noxx, Eugen.Rivniy, North, LevelX2, Jeff, Plopman, dustinconrad, emerald000,"); - jLabel4.setText("fireshoes, lunaskyrise, mnapoleon, jgod, LoneFox, drmDev, spjspj, TheElk801, L_J, JayDi85."); + jLabel3.setText("Devs: BetaSteward, Noxx, Eugen.Rivniy, North, LevelX2, Jeff, Plopman, dustinconrad, emerald000.,"); + + jLabel4.setText("fireshoes, lunaskyrise, mnapoleon, jgod, LoneFox."); + + btnWhatsNew.setText("What's new"); + btnWhatsNew.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnWhatsNewActionPerformed(evt); + } + }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(jLabel3, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addComponent(jLabel1) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(lblVersion)) - .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(btnOk) - .addComponent(jLabel4, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addContainerGap()) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jLabel3, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(btnWhatsNew, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btnOk, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jLabel4, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblVersion)) + .addComponent(jLabel2, javax.swing.GroupLayout.Alignment.LEADING)) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) ); layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) - .addComponent(lblVersion)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel2) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel4) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 71, Short.MAX_VALUE) - .addComponent(btnOk) - .addContainerGap()) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(lblVersion)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel4) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 68, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(btnOk, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(btnWhatsNew, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) ); pack(); }// //GEN-END:initComponents private void btnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOkActionPerformed - this.removeDialog(); + onCancel(); }//GEN-LAST:event_btnOkActionPerformed + private void btnWhatsNewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnWhatsNewActionPerformed + MageFrame.getInstance().getWhatsNewDialog().checkUpdatesAndShow(true); + }//GEN-LAST:event_btnWhatsNewActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton btnOk; + private javax.swing.JButton btnWhatsNew; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; diff --git a/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.form b/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.form new file mode 100644 index 0000000000..af26b1a540 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.form @@ -0,0 +1,73 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.java b/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.java new file mode 100644 index 0000000000..6fe4cfb2fa --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/dialog/WhatsNewDialog.java @@ -0,0 +1,297 @@ +package mage.client.dialog; + +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.concurrent.Worker; +import javafx.embed.swing.JFXPanel; +import javafx.scene.Scene; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; +import mage.client.MageFrame; +import mage.utils.MageVersion; +import org.apache.log4j.Logger; +import org.w3c.dom.events.EventListener; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +/** + * @author JayDi85 + */ +public class WhatsNewDialog extends MageDialog { + + private static final Logger logger = Logger.getLogger(WhatsNewDialog.class); + private static final MageVersion clientVersion = new MageVersion(WhatsNewDialog.class); + + private static final String WHATS_NEW_PAGE = "https://jaydi85.github.io/xmage-web-news/news.html"; + private static final int WHATS_NEW_LOAD_TIMEOUT_SECS = 20; // timeout for page loading + + final JFXPanel fxPanel; + private WebEngine engine; + private boolean isPageReady = false; + + private final SwingWorker backgroundWorker = new SwingWorker() { + @Override + public Void doInBackground() { + try { + logger.info("Checking news..."); + int maxWait = WHATS_NEW_LOAD_TIMEOUT_SECS; + int currentWait = 0; + while (!isPageReady) { + Thread.sleep(1000); + currentWait++; + if (currentWait > maxWait) { + logger.error("Can't load news page: " + WHATS_NEW_PAGE); + break; + } + } + } catch (InterruptedException e) { + logger.error("Checking news was interrupted", e); + Thread.currentThread().interrupt(); + } + return null; + } + + @Override + public void done() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (isPageReady) { + showDialog(); + } + } + }); + } + }; + + public WhatsNewDialog() { + initComponents(); + + fxPanel = new JFXPanel(); + panelData.add(fxPanel); + + createWebView(); + } + + public void checkUpdatesAndShow(boolean forceToShowPage) { + // lazy loading in background + // shows it on page ready or by force + + isPageReady = false; + loadURL(WHATS_NEW_PAGE); + + if (forceToShowPage) { + if (!backgroundWorker.isDone()) { + backgroundWorker.cancel(true); + } + showDialog(); + } else { + backgroundWorker.execute(); + } + } + + private void showDialog() { + this.setModal(true); + this.setResizable(true); + getRootPane().setDefaultButton(buttonCancel); + this.setSize(800, 600); + + // windows settings + MageFrame.getDesktop().remove(this); + if (this.isModal()) { + MageFrame.getDesktop().add(this, JLayeredPane.MODAL_LAYER); + } else { + MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER); + } + this.makeWindowCentered(); + + // Close on "ESC" + registerKeyboardAction(e -> onCancel(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + + this.setVisible(true); + } + + private void createWebView() { + + // init web engine and events + // https://docs.oracle.com/javafx/2/swing/swing-fx-interoperability.htm + + Platform.runLater(new Runnable() { + @Override + public void run() { + + WebView view = new WebView(); + engine = view.getEngine(); + engine.setUserAgent(engine.getUserAgent() + " XMage/" + clientVersion.toString(false)); + view.contextMenuEnabledProperty().setValue(false); + + // on error + engine.getLoadWorker().exceptionProperty().addListener(new ChangeListener() { + public void changed(ObservableValue o, Throwable old, final Throwable value) { + if (engine.getLoadWorker().getState() == Worker.State.FAILED) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + logger.error("Can't load news page: " + (value != null ? value.getMessage() : "null")); + } + }); + } + } + }); + + // on completed + engine.getLoadWorker().stateProperty().addListener(new ChangeListener() { + public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) { + if (newState == Worker.State.SUCCEEDED) { + + // 1. replace urls with custom click processing + // all classes from org.w3c.dom + org.w3c.dom.events.EventListener listener = new EventListener() { + public void handleEvent(org.w3c.dom.events.Event ev) { + String href = ((org.w3c.dom.Element) ev.getTarget()).getAttribute("href"); + ev.preventDefault(); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + URI uri = new URI(href); + desktop.browse(uri); + } catch (IOException | URISyntaxException ex) { + // do nothing + } + } + } + }); + } + }; + org.w3c.dom.Document doc = engine.getDocument(); + org.w3c.dom.NodeList listA = doc.getElementsByTagName("a"); + for (int i = 0; i < listA.getLength(); i++) { + ((org.w3c.dom.events.EventTarget) listA.item(i)).addEventListener("click", listener, false); + } + + // 2. page can be shown + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + isPageReady = true; + } + }); + } + } + }); + + fxPanel.setScene(new Scene(view)); + } + }); + } + + public void loadURL(final String url) { + Platform.runLater(new Runnable() { + @Override + public void run() { + String tmp = toURL(url); + if (url == null) { + tmp = toURL("http://" + url); + } + engine.load(tmp); + } + }); + } + + private static String toURL(String str) { + try { + return new URL(str).toExternalForm(); + } catch (MalformedURLException exception) { + return null; + } + } + + + private void onCancel() { + this.hideDialog(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonCancel = new javax.swing.JButton(); + buttonRefresh = new javax.swing.JButton(); + panelData = new javax.swing.JPanel(); + + buttonCancel.setText("Close"); + buttonCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonCancelActionPerformed(evt); + } + }); + + buttonRefresh.setText("Refresh"); + buttonRefresh.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + buttonRefreshActionPerformed(evt); + } + }); + + panelData.setLayout(new java.awt.BorderLayout()); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(panelData, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(buttonRefresh, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(panelData, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(buttonCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(buttonRefresh, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void buttonCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonCancelActionPerformed + onCancel(); + }//GEN-LAST:event_buttonCancelActionPerformed + + private void buttonRefreshActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonRefreshActionPerformed + loadURL(WHATS_NEW_PAGE); + }//GEN-LAST:event_buttonRefreshActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton buttonCancel; + private javax.swing.JButton buttonRefresh; + private javax.swing.JPanel panelData; + // End of variables declaration//GEN-END:variables +}