From 98b147dfea4e9e35c68aa2e3bf39eab97f8c1c11 Mon Sep 17 00:00:00 2001 From: skylarmt Date: Wed, 25 Mar 2015 16:32:13 -0600 Subject: [PATCH] Functional plugin system complete --- src/net/apocalypselabs/symat/Editor.form | 34 ++++-- src/net/apocalypselabs/symat/Editor.java | 43 ++++--- src/net/apocalypselabs/symat/Main.java | 34 +++++- .../apocalypselabs/symat/PackagePlugin.form | 46 +++++--- .../apocalypselabs/symat/PackagePlugin.java | 109 +++++++++++++----- .../symat/components/TaskList.java | 3 + .../apocalypselabs/symat/images/plugin.png | Bin 0 -> 4592 bytes .../symat/plugin/LoadPlugin.java | 37 +++++- .../apocalypselabs/symat/plugin/Plugin.java | 2 + 9 files changed, 235 insertions(+), 73 deletions(-) create mode 100644 src/net/apocalypselabs/symat/images/plugin.png diff --git a/src/net/apocalypselabs/symat/Editor.form b/src/net/apocalypselabs/symat/Editor.form index 77633d3..9312f49 100644 --- a/src/net/apocalypselabs/symat/Editor.form +++ b/src/net/apocalypselabs/symat/Editor.form @@ -90,17 +90,33 @@ - + - - - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/net/apocalypselabs/symat/Editor.java b/src/net/apocalypselabs/symat/Editor.java index de4ed90..d4fa762 100644 --- a/src/net/apocalypselabs/symat/Editor.java +++ b/src/net/apocalypselabs/symat/Editor.java @@ -90,14 +90,10 @@ public class Editor extends javax.swing.JInternalFrame { public Editor(boolean python) { initComponents(); - FileFilter filter = new FileNameExtensionFilter("SyMAT JavaScript (.syjs)", "syjs"); + FileFilter filter = new FileNameExtensionFilter("JavaScript (syjs, js)", "syjs", "js"); fc.setFileFilter(filter); fc.addChoosableFileFilter(filter); - filter = new FileNameExtensionFilter("SyMAT Python (.sypy)", "sypy"); - fc.addChoosableFileFilter(filter); - filter = new FileNameExtensionFilter("JavaScript file (.js)", "js"); - fc.addChoosableFileFilter(filter); - filter = new FileNameExtensionFilter("Python script (.py)", "py"); + filter = new FileNameExtensionFilter("Python (sypy, py)", "sypy", "py"); fc.addChoosableFileFilter(filter); int font_size = 12; @@ -180,9 +176,10 @@ public class Editor extends javax.swing.JInternalFrame { public Editor() { this(false); } - + /** * Show open dialog. + * * @param openfile Nothing to see here, move along. */ public Editor(int openfile) { @@ -236,7 +233,9 @@ public class Editor extends javax.swing.JInternalFrame { sampleGraph = new javax.swing.JMenuItem(); saveMenu = new javax.swing.JMenuItem(); saveAsMenu = new javax.swing.JMenuItem(); + jMenu1 = new javax.swing.JMenu(); exportMenu = new javax.swing.JMenuItem(); + packPluginMenu = new javax.swing.JMenuItem(); shareMenu = new javax.swing.JMenuItem(); shareAsMenu = new javax.swing.JMenuItem(); editMenu = new javax.swing.JMenu(); @@ -385,14 +384,27 @@ public class Editor extends javax.swing.JInternalFrame { }); fileMenu.add(saveAsMenu); + jMenu1.setText("Publish"); + jMenu1.setToolTipText(""); + exportMenu.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_E, java.awt.event.InputEvent.CTRL_MASK)); - exportMenu.setText("Export..."); + exportMenu.setText("Export Code"); exportMenu.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { exportMenuActionPerformed(evt); } }); - fileMenu.add(exportMenu); + jMenu1.add(exportMenu); + + packPluginMenu.setText("Package Plugin..."); + packPluginMenu.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + packPluginMenuActionPerformed(evt); + } + }); + jMenu1.add(packPluginMenu); + + fileMenu.add(jMenu1); shareMenu.setText("Share..."); shareMenu.addActionListener(new java.awt.event.ActionListener() { @@ -759,10 +771,7 @@ public class Editor extends javax.swing.JInternalFrame { } private void exportMenuActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportMenuActionPerformed - String lang = "js"; - if (pythonOption.isSelected()) { - lang = "python"; - } + String lang = pythonOption.isSelected() ? "python" : "js"; Main.loadFrame(new CodeExport(codeBox.getText(), lang)); }//GEN-LAST:event_exportMenuActionPerformed @@ -833,6 +842,12 @@ public class Editor extends javax.swing.JInternalFrame { } }//GEN-LAST:event_shareAsMenuActionPerformed + private void packPluginMenuActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_packPluginMenuActionPerformed + Main.loadFrame(new PackagePlugin(codeBox.getText(), + javascriptOption.isSelected() ? 0 : 1 + )); + }//GEN-LAST:event_packPluginMenuActionPerformed + private void createShared(String id) { try { String padid = Pads.genPad(id, codeBox.getText()); @@ -912,6 +927,7 @@ public class Editor extends javax.swing.JInternalFrame { private javax.swing.JMenuItem exportMenu; private javax.swing.JMenu fileMenu; private javax.swing.JLabel jLabel1; + private javax.swing.JMenu jMenu1; private javax.swing.JMenu jMenu3; private javax.swing.JMenu jMenu4; private javax.swing.JMenuBar jMenuBar1; @@ -927,6 +943,7 @@ public class Editor extends javax.swing.JInternalFrame { private javax.swing.JMenu openSampleBtn; private javax.swing.JTextArea outputBox; private javax.swing.JPanel outputPanel; + private javax.swing.JMenuItem packPluginMenu; private javax.swing.JMenuItem pasteBtn; private javax.swing.JRadioButtonMenuItem pythonOption; private javax.swing.JMenuItem redoBtn; diff --git a/src/net/apocalypselabs/symat/Main.java b/src/net/apocalypselabs/symat/Main.java index 9256220..58c5cbd 100644 --- a/src/net/apocalypselabs/symat/Main.java +++ b/src/net/apocalypselabs/symat/Main.java @@ -58,6 +58,7 @@ import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.File; +import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -77,6 +78,7 @@ import javax.swing.ListModel; import javax.swing.UIManager; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; +import net.apocalypselabs.symat.plugin.LoadPlugin; import org.pushingpixels.flamingo.api.ribbon.*; import org.pushingpixels.flamingo.api.ribbon.resize.*; import org.pushingpixels.flamingo.api.common.*; @@ -145,6 +147,8 @@ public class Main extends JRibbonFrame { public static Main maingui; + JRibbonBand pluginband = new JRibbonBand("Plugins", null); + /** * Creates the main app window and does some quick things that aren't * threaded in SplashScreen. @@ -265,6 +269,26 @@ public class Main extends JRibbonFrame { } } + /** + * Load plugins from disk. + */ + public void loadPlugins() { + File dir = new File(System.getProperty("user.home") + "\\.symat\\plugins"); + dir.mkdirs(); + File[] files = dir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".sypl"); + } + }); + + for (File pl : files) { + pluginband.addCommandButton( + (new LoadPlugin(pl)).getRibbonBtn(), + RibbonElementPriority.MEDIUM); + } + } + /** * Load the ribbon in all its glory. */ @@ -277,6 +301,7 @@ public class Main extends JRibbonFrame { JRibbonBand appsband = new JRibbonBand("Apps", null); JRibbonBand webband = new JRibbonBand("Community", null); JRibbonBand collabband = new JRibbonBand("Team", null); + loadPlugins(); shellbtn.addActionListener(new ActionListener() { @Override @@ -372,14 +397,19 @@ public class Main extends JRibbonFrame { collabband.setResizePolicies((List) Arrays.asList( new CoreRibbonResizePolicies.None(appsband.getControlPanel()), new IconRibbonBandResizePolicy(appsband.getControlPanel()))); + pluginband.setResizePolicies((List) Arrays.asList( + new CoreRibbonResizePolicies.None(appsband.getControlPanel()), + new IconRibbonBandResizePolicy(appsband.getControlPanel()))); RibbonTask hometask = new RibbonTask("Home", coreband, appsband); RibbonTask webtask = new RibbonTask("Tools", webband, collabband); + RibbonTask plugintask = new RibbonTask("Plugins", pluginband); loadRibbonMenu(null); ribbon.addTask(hometask); ribbon.addTask(webtask); + ribbon.addTask(plugintask); } public static ResizableIcon getRibbonIcon(String name) { @@ -505,8 +535,8 @@ public class Main extends JRibbonFrame { = new FileNameExtensionFilter("SyMAT File" + "(syjs, sypy, js, py, sytt)", "syjs", "sypy", "js", "py", "sytt"); - FileFilter tasklist = - new FileNameExtensionFilter("Task List (sytt)", + FileFilter tasklist + = new FileNameExtensionFilter("Task List (sytt)", "sytt"); fc.setFileFilter(all); fc.addChoosableFileFilter(script); diff --git a/src/net/apocalypselabs/symat/PackagePlugin.form b/src/net/apocalypselabs/symat/PackagePlugin.form index 336c456..6c994f4 100644 --- a/src/net/apocalypselabs/symat/PackagePlugin.form +++ b/src/net/apocalypselabs/symat/PackagePlugin.form @@ -4,8 +4,6 @@ - - @@ -56,7 +54,7 @@ - + @@ -66,19 +64,19 @@ - - + + - + - + - + - + @@ -209,13 +207,16 @@ - + - + + + + - + @@ -229,10 +230,14 @@ - - + + + + + + - + @@ -247,6 +252,7 @@ + @@ -256,7 +262,7 @@ - + @@ -268,6 +274,14 @@ + + + + + + + + diff --git a/src/net/apocalypselabs/symat/PackagePlugin.java b/src/net/apocalypselabs/symat/PackagePlugin.java index d6fcb5f..3955f4b 100644 --- a/src/net/apocalypselabs/symat/PackagePlugin.java +++ b/src/net/apocalypselabs/symat/PackagePlugin.java @@ -45,9 +45,11 @@ */ package net.apocalypselabs.symat; -import java.awt.image.BufferedImage; import java.io.FileOutputStream; +import java.io.IOException; import java.io.ObjectOutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JFileChooser; @@ -61,17 +63,35 @@ import net.apocalypselabs.symat.plugin.Plugin; */ public class PackagePlugin extends javax.swing.JInternalFrame { - private JFileChooser fc = new JFileChooser(); + private final JFileChooser fcimg = new JFileChooser(); + private final JFileChooser fcexp = new JFileChooser(); private ImageIcon icon; + public PackagePlugin(String code, int lang) { + this(); + try { + icon = new ImageIcon( + ImageIO.read(PackagePlugin.class.getResource("images/plugin.png"))); + } catch (IOException ex) { + Debug.stacktrace(ex); + } + langSelect.setSelectedIndex(lang); + codeBox.setText(code); + iconPreview.setIcon(icon); + } + /** * Creates new form PackagePlugin */ public PackagePlugin() { - fc.setFileFilter(new FileNameExtensionFilter( + fcimg.setFileFilter(new FileNameExtensionFilter( "Image (jpeg,jpg,gif,png,bmp)", "jpeg", "jpg", "gif", "png", "bmp")); + fcexp.setFileFilter(new FileNameExtensionFilter( + "Plugin (sypl)", + "sypl")); initComponents(); + author.setText(PrefStorage.getSetting("author")); } /** @@ -105,6 +125,7 @@ public class PackagePlugin extends javax.swing.JInternalFrame { iconPreview = new javax.swing.JLabel(); openIconBtn = new javax.swing.JButton(); jLabel10 = new javax.swing.JLabel(); + defaultIconBtn = new javax.swing.JButton(); jPanel2 = new javax.swing.JPanel(); jLabel8 = new javax.swing.JLabel(); langSelect = new javax.swing.JComboBox(); @@ -115,8 +136,6 @@ public class PackagePlugin extends javax.swing.JInternalFrame { setClosable(true); setIconifiable(true); - setMaximizable(true); - setResizable(true); setTitle("Package Plugin"); jLabel1.setText("Plugin Name:"); @@ -145,7 +164,7 @@ public class PackagePlugin extends javax.swing.JInternalFrame { .addContainerGap() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(jLabel7) - .addComponent(jScrollPane1) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 393, Short.MAX_VALUE) .addGroup(jPanel1Layout.createSequentialGroup() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel1) @@ -154,18 +173,18 @@ public class PackagePlugin extends javax.swing.JInternalFrame { .addComponent(jLabel6, javax.swing.GroupLayout.PREFERRED_SIZE, 72, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() - .addComponent(website) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(website, javax.swing.GroupLayout.DEFAULT_SIZE, 129, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(jLabel5) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(version, javax.swing.GroupLayout.PREFERRED_SIZE, 73, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(version, javax.swing.GroupLayout.DEFAULT_SIZE, 129, Short.MAX_VALUE)) .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(pluginName, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(pluginName) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jLabel4) + .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 39, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(author, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(author)) .addComponent(packageID) .addComponent(shortDesc)))) .addContainerGap()) @@ -204,9 +223,10 @@ public class PackagePlugin extends javax.swing.JInternalFrame { jLabel9.setText("Icon:"); + iconPreview.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); iconPreview.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); - openIconBtn.setText("Open Icon..."); + openIconBtn.setText("Open icon..."); openIconBtn.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { openIconBtnActionPerformed(evt); @@ -216,6 +236,13 @@ public class PackagePlugin extends javax.swing.JInternalFrame { jLabel10.setText("Recommended icon size is 100x76."); jLabel10.setVerticalAlignment(javax.swing.SwingConstants.TOP); + defaultIconBtn.setText("Use default"); + defaultIconBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + defaultIconBtnActionPerformed(evt); + } + }); + javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4); jPanel4.setLayout(jPanel4Layout); jPanel4Layout.setHorizontalGroup( @@ -227,11 +254,13 @@ public class PackagePlugin extends javax.swing.JInternalFrame { .addGroup(jPanel4Layout.createSequentialGroup() .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel4Layout.createSequentialGroup() - .addComponent(iconPreview, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(iconPreview, javax.swing.GroupLayout.PREFERRED_SIZE, 115, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(openIconBtn)) + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(openIconBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(defaultIconBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) .addComponent(jLabel9)) - .addGap(0, 194, Short.MAX_VALUE))) + .addGap(0, 181, Short.MAX_VALUE))) .addContainerGap()) ); jPanel4Layout.setVerticalGroup( @@ -241,9 +270,12 @@ public class PackagePlugin extends javax.swing.JInternalFrame { .addComponent(jLabel9) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(openIconBtn) - .addComponent(iconPreview, javax.swing.GroupLayout.PREFERRED_SIZE, 76, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) + .addGroup(jPanel4Layout.createSequentialGroup() + .addComponent(openIconBtn) + .addGap(18, 18, 18) + .addComponent(defaultIconBtn)) + .addComponent(iconPreview, javax.swing.GroupLayout.PREFERRED_SIZE, 83, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(jLabel10, javax.swing.GroupLayout.DEFAULT_SIZE, 122, Short.MAX_VALUE) .addContainerGap()) ); @@ -340,23 +372,32 @@ public class PackagePlugin extends javax.swing.JInternalFrame { p.setLang(langSelect.getSelectedIndex()); p.setScript(codeBox.getText()); p.setIcon(icon); + if (p.getTitle().equals("") + || p.getAuthor().equals("") + || p.getLongTitle().equals("") + || p.getDesc().equals("") + || p.getPackage().equals("") + || p.getVersion().equals("") + || p.getScript().equals("") + || p.getIcon() == null) { + throw new Exception("One or more required fields are empty."); + } } catch (Exception ex) { Debug.stacktrace(ex); JOptionPane.showInternalMessageDialog(this, "Error. Please check your data.\n\n" + ex.getMessage()); return; } - fc.setFileFilter(new FileNameExtensionFilter( - "Plugin (sypl)", - "sypl")); - int result = fc.showDialog(this, "Publish"); + int result = fcexp.showDialog(this, "Publish"); if (result == JFileChooser.APPROVE_OPTION) { try { - FileOutputStream fout = new FileOutputStream(fc.getSelectedFile()); + FileOutputStream fout = new FileOutputStream(FileUtils.getFileWithExtension(fcexp)); try (ObjectOutputStream oos = new ObjectOutputStream(fout)) { oos.writeObject(p); oos.close(); } + JOptionPane.showInternalMessageDialog(this, + "Publish complete!"); } catch (Exception ex) { Debug.stacktrace(ex); JOptionPane.showInternalMessageDialog(this, @@ -366,13 +407,10 @@ public class PackagePlugin extends javax.swing.JInternalFrame { }//GEN-LAST:event_savePluginActionPerformed private void openIconBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openIconBtnActionPerformed - fc.setFileFilter(new FileNameExtensionFilter( - "Image (jpeg,jpg,gif,png,bmp)", - "jpeg", "jpg", "gif", "png", "bmp")); - int result = fc.showDialog(this, "Select Icon"); + int result = fcimg.showDialog(this, "Select Icon"); if (result == JFileChooser.APPROVE_OPTION) { try { - icon = new ImageIcon(ImageIO.read(fc.getSelectedFile())); + icon = new ImageIcon(ImageIO.read(fcimg.getSelectedFile())); iconPreview.setIcon(icon); } catch (Exception ex) { Debug.stacktrace(ex); @@ -382,10 +420,23 @@ public class PackagePlugin extends javax.swing.JInternalFrame { } }//GEN-LAST:event_openIconBtnActionPerformed + private void defaultIconBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_defaultIconBtnActionPerformed + try { + icon = new ImageIcon( + ImageIO.read(PackagePlugin.class.getResource("images/plugin.png"))); + iconPreview.setIcon(icon); + } catch (IOException ex) { + Debug.stacktrace(ex); + JOptionPane.showInternalMessageDialog(this, + "Error opening default icon.\n\n" + ex.getMessage()); + } + }//GEN-LAST:event_defaultIconBtnActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField author; private javax.swing.JTextArea codeBox; + private javax.swing.JButton defaultIconBtn; private javax.swing.JLabel iconPreview; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel10; diff --git a/src/net/apocalypselabs/symat/components/TaskList.java b/src/net/apocalypselabs/symat/components/TaskList.java index d63c943..0c381fe 100644 --- a/src/net/apocalypselabs/symat/components/TaskList.java +++ b/src/net/apocalypselabs/symat/components/TaskList.java @@ -12,6 +12,9 @@ import java.util.ArrayList; * @author Skylar */ public class TaskList implements Serializable { + + private static final long serialVersionUID = 13370L; + private final ArrayList tasks = new ArrayList<>(); private String title = "Untitled"; diff --git a/src/net/apocalypselabs/symat/images/plugin.png b/src/net/apocalypselabs/symat/images/plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..8125c7df544476cbea9666db859fd193e416f72a GIT binary patch literal 4592 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