diff --git a/.gitignore b/.gitignore index f68c122..d3692fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ bin/ dist/ nbproject/ -*.exe \ No newline at end of file +*.exe +/src/padkey \ No newline at end of file diff --git a/src/Ubuntu-R info b/src/Ubuntu-R info deleted file mode 100644 index 800c150..0000000 --- a/src/Ubuntu-R info +++ /dev/null @@ -1,2 +0,0 @@ -The ubuntu.ttf font file was originally Ubuntu-R.ttf. -It was changed for several petty reasons. \ No newline at end of file diff --git a/src/ubuntu.ttf b/src/Ubuntu-R.ttf similarity index 100% rename from src/ubuntu.ttf rename to src/Ubuntu-R.ttf diff --git a/src/net/apocalypselabs/symat/CodeEditor.form b/src/net/apocalypselabs/symat/CodeEditor.form index c70c93c..77633d3 100644 --- a/src/net/apocalypselabs/symat/CodeEditor.form +++ b/src/net/apocalypselabs/symat/CodeEditor.form @@ -101,6 +101,22 @@ + + + + + + + + + + + + + + + + diff --git a/src/net/apocalypselabs/symat/CodeEditor.java b/src/net/apocalypselabs/symat/CodeEditor.java index 2fe4dfb..5247a5f 100644 --- a/src/net/apocalypselabs/symat/CodeEditor.java +++ b/src/net/apocalypselabs/symat/CodeEditor.java @@ -52,6 +52,8 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; @@ -151,6 +153,18 @@ public class CodeEditor extends javax.swing.JInternalFrame { }); } + public CodeEditor(String text) { + this(); + codeBox.setText(text); + } + + public CodeEditor(String text, boolean openSaveDialog) { + this(text); + if (openSaveDialog) { + saveAsMenuActionPerformed(null); + } + } + private void setEditorTheme(String themeName) { try { Theme theme = Theme.load(CodeEditor.class @@ -205,6 +219,8 @@ public class CodeEditor extends javax.swing.JInternalFrame { saveMenu = new javax.swing.JMenuItem(); saveAsMenu = new javax.swing.JMenuItem(); exportMenu = new javax.swing.JMenuItem(); + shareMenu = new javax.swing.JMenuItem(); + shareAsMenu = new javax.swing.JMenuItem(); editMenu = new javax.swing.JMenu(); undoBtn = new javax.swing.JMenuItem(); redoBtn = new javax.swing.JMenuItem(); @@ -360,6 +376,22 @@ public class CodeEditor extends javax.swing.JInternalFrame { }); fileMenu.add(exportMenu); + shareMenu.setText("Share..."); + shareMenu.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + shareMenuActionPerformed(evt); + } + }); + fileMenu.add(shareMenu); + + shareAsMenu.setText("Share as..."); + shareAsMenu.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + shareAsMenuActionPerformed(evt); + } + }); + fileMenu.add(shareAsMenu); + jMenuBar1.add(fileMenu); editMenu.setText("Edit"); @@ -762,6 +794,41 @@ public class CodeEditor extends javax.swing.JInternalFrame { codeBox.redoLastAction(); }//GEN-LAST:event_redoBtnActionPerformed + private void shareMenuActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_shareMenuActionPerformed + createShared(""); + }//GEN-LAST:event_shareMenuActionPerformed + + private void shareAsMenuActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_shareAsMenuActionPerformed + String id = JOptionPane.showInternalInputDialog(this, + "Enter the pad ID to share to. " + + "If the pad exists, it will be overwritten.", + "Share", + JOptionPane.QUESTION_MESSAGE); + if (!(id == null || id.equals(""))) { + createShared(id); + } + }//GEN-LAST:event_shareAsMenuActionPerformed + + private void createShared(String id) { + try { + String padid = Pads.genPad(id, codeBox.getText()); + Pads.addPad(padid); + MainGUI.loadFrame(new WebBrowser("Pad " + padid, + Pads.PADS_URL + "/p/" + padid, + WebBrowser.PAD_LOGO)); + JOptionPane.showInternalMessageDialog(this, + new SharePad(padid), + "Share Pad", + JOptionPane.PLAIN_MESSAGE); + } catch (Exception ex) { + Debug.stacktrace(ex); + JOptionPane.showInternalMessageDialog(this, + "Could not create new pad: " + ex.getMessage(), + "Error", + JOptionPane.ERROR_MESSAGE); + } + } + /** * Open a sample code file with the given name.

* Uses the current language. @@ -847,6 +914,8 @@ public class CodeEditor extends javax.swing.JInternalFrame { private javax.swing.JMenuItem sampleHelloWorld; private javax.swing.JMenuItem saveAsMenu; private javax.swing.JMenuItem saveMenu; + private javax.swing.JMenuItem shareAsMenu; + private javax.swing.JMenuItem shareMenu; private javax.swing.JMenuItem undoBtn; // End of variables declaration//GEN-END:variables } diff --git a/src/net/apocalypselabs/symat/Interpreter.java b/src/net/apocalypselabs/symat/Interpreter.java index e0b2eeb..2cdeefa 100644 --- a/src/net/apocalypselabs/symat/Interpreter.java +++ b/src/net/apocalypselabs/symat/Interpreter.java @@ -90,7 +90,7 @@ public class Interpreter extends javax.swing.JInternalFrame { if (lang.equals("default")) { lang = PrefStorage.getSetting("shellLang", "javascript"); } - cr = new CodeRunner(lang, true); + cr = new CodeRunner(lang); // Setup language if (lang.equals("python")) { diff --git a/src/net/apocalypselabs/symat/MainGUI.form b/src/net/apocalypselabs/symat/MainGUI.form index 10a24d8..9c0fead 100644 --- a/src/net/apocalypselabs/symat/MainGUI.form +++ b/src/net/apocalypselabs/symat/MainGUI.form @@ -319,8 +319,8 @@ - - + + @@ -333,8 +333,10 @@ - - + + + + @@ -345,6 +347,7 @@ + @@ -404,6 +407,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/net/apocalypselabs/symat/MainGUI.java b/src/net/apocalypselabs/symat/MainGUI.java index 7cfcd88..a79645b 100644 --- a/src/net/apocalypselabs/symat/MainGUI.java +++ b/src/net/apocalypselabs/symat/MainGUI.java @@ -65,7 +65,6 @@ import javax.swing.JInternalFrame; import javax.swing.JOptionPane; import javax.swing.ListModel; import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; /** * This class is like the Force: A light theme, a dark theme, and it binds the @@ -84,7 +83,7 @@ public class MainGUI extends javax.swing.JFrame { /** * Version name, as it should be displayed. */ - public static final String VERSION_NAME = "1.3"; + public static final String VERSION_NAME = "1.4"; /** * Program name, with version name */ @@ -92,7 +91,7 @@ public class MainGUI extends javax.swing.JFrame { /** * Version number, for updates and //needs in scripts */ - public static final double APP_CODE = 15; + public static final double APP_CODE = 16; /** * Base URL for building API calls */ @@ -349,6 +348,7 @@ public class MainGUI extends javax.swing.JFrame { wikiBtn = new javax.swing.JButton(); jLabel4 = new javax.swing.JLabel(); forumBtn = new javax.swing.JButton(); + padsBtn = new javax.swing.JButton(); mainPane = mainPane = new javax.swing.JDesktopPane() { @Override protected void paintComponent(Graphics g) { @@ -580,6 +580,19 @@ public class MainGUI extends javax.swing.JFrame { } }); + padsBtn.setIcon(new javax.swing.ImageIcon(getClass().getResource("/net/apocalypselabs/symat/images/pads.png"))); // NOI18N + padsBtn.setText("Pads"); + padsBtn.setBorder(javax.swing.BorderFactory.createEmptyBorder(5, 5, 5, 5)); + padsBtn.setFocusable(false); + padsBtn.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + padsBtn.setOpaque(false); + padsBtn.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + padsBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + padsBtnActionPerformed(evt); + } + }); + javax.swing.GroupLayout jPanel5Layout = new javax.swing.GroupLayout(jPanel5); jPanel5.setLayout(jPanel5Layout); jPanel5Layout.setHorizontalGroup( @@ -589,8 +602,10 @@ public class MainGUI extends javax.swing.JFrame { .addComponent(wikiBtn) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(forumBtn) - .addGap(12, 12, 12) - .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, 542, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(padsBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 82, Short.MAX_VALUE) + .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 405, javax.swing.GroupLayout.PREFERRED_SIZE)) ); jPanel5Layout.setVerticalGroup( jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -598,11 +613,12 @@ public class MainGUI extends javax.swing.JFrame { .addGroup(jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) .addComponent(jLabel4, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(wikiBtn, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(forumBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(forumBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(padsBtn, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); - tabs.addTab("Community", jPanel5); + tabs.addTab("Web", jPanel5); mainPane.setBackground(new java.awt.Color(204, 204, 204)); mainPane.setAutoscrolls(true); @@ -821,6 +837,10 @@ public class MainGUI extends javax.swing.JFrame { loadFrame(new Notepad()); }//GEN-LAST:event_notepadBtnActionPerformed + private void padsBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_padsBtnActionPerformed + loadFrame(new Pads()); + }//GEN-LAST:event_padsBtnActionPerformed + /* End the button handlers. */ @@ -918,7 +938,7 @@ public class MainGUI extends javax.swing.JFrame { try { // Ubuntu font for prettifying ubuntuRegular = Font.createFont(Font.TRUETYPE_FONT, MainGUI.class. - getResourceAsStream("/ubuntu.ttf")); + getResourceAsStream("/Ubuntu-R.ttf")); } catch (FontFormatException | IOException ex) { ubuntuRegular = Font.getFont(Font.SANS_SERIF); System.err.println("Error loading fonts: " + ex.getMessage()); @@ -982,6 +1002,7 @@ public class MainGUI extends javax.swing.JFrame { public static javax.swing.JScrollPane jScrollPane1; public static javax.swing.JDesktopPane mainPane; public static javax.swing.JButton notepadBtn; + public static javax.swing.JButton padsBtn; public static javax.swing.JButton recentFileBtn; public static javax.swing.JList recentFileList; public static javax.swing.JPanel recentItemsPanel; diff --git a/src/net/apocalypselabs/symat/Pads.form b/src/net/apocalypselabs/symat/Pads.form new file mode 100644 index 0000000..d583de6 --- /dev/null +++ b/src/net/apocalypselabs/symat/Pads.form @@ -0,0 +1,216 @@ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/net/apocalypselabs/symat/Pads.java b/src/net/apocalypselabs/symat/Pads.java new file mode 100644 index 0000000..50bbe93 --- /dev/null +++ b/src/net/apocalypselabs/symat/Pads.java @@ -0,0 +1,512 @@ +/* + * CODE LICENSE ===================== + * Copyright (c) 2015, Apocalypse Laboratories + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * 4. You adhere to the Media License detailed below. If you do not, this license + * is automatically revoked and you must purge all copies of the software you + * possess, in source or binary form. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * MEDIA LICENSE ==================== + * All images and other graphical files (the "graphics") included with this + * software are copyright (c) 2015 Apocalypse Laboratories. You may not distribute + * the graphics or any program, source code repository, or other digital storage + * media containing them without written permission from Apocalypse Laboratories. + * This ban on distribution only applies to publicly available systems. + * A password-protected network file share, USB drive, or other storage scheme that + * cannot be easily accessed by the public is generally allowed. If in doubt, + * contact Apocalypse Laboratories. If Apocalypse Laboratories allows or denies + * you permission, that decision is considered final and binding. + */ +package net.apocalypselabs.symat; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Random; +import javax.swing.JOptionPane; +import org.etherpad_lite_client.EPLiteClient; +import org.etherpad_lite_client.EPLiteException; + +/** + * + * @author Skylar + */ +public class Pads extends javax.swing.JInternalFrame { + + public static final String PADS_URL = "https://pad.symatapp.com"; + + /** + * Creates new form Pads + */ + public Pads() { + initComponents(); + padPane.setListData(getPads()); + } + + /** + * Add the given pad ID to the local pad list. + * + * @param id the pad ID. + */ + public static void addPad(String id) { + String pads = PrefStorage.getSetting("pads"); + + // Check for dupes + for (String p : pads.split("\\|")) { + if (p.equals(id)) { + return; + } + } + + if (pads.equals("")) { + pads = id; + } else { + pads += "|" + id; + } + PrefStorage.saveSetting("pads", pads); + } + + /** + * Delete the pad with the given ID from local memory. + *
It will still exist online. + * + * @param id the pad ID. + */ + public static void delPad(String id) { + String pads = PrefStorage.getSetting("pads"); + String result = ""; + int i = 0; + for (String pad : pads.split("\\|")) { + if (!pad.equals(id)) { + if (i > 0) { + result += "|"; + } + result += pad; + i++; + } + } + + PrefStorage.saveSetting("pads", result); + } + + /** + * Get an array of saved pads. + * + * @return String[] of pad IDs + */ + public static String[] getPads() { + String pads = PrefStorage.getSetting("pads"); + if (!pads.equals("")) { + if (pads.contains("|")) { + return pads.split("\\|"); + } else { + String[] padlist = {pads}; + return padlist; + } + } else { + String[] padlist = {}; + return padlist; + } + } + + /** + * Create a new pad on the server with the given ID. + * + * @param id The pad ID to create. If blank is auto-generated. + * @param content Text to initialize the pad with. + * @return Created pad ID. + * @throws Exception if things break. + */ + public static String genPad(String id, String content) throws Exception { + // Generate ID if blank + if (id.equals("")) { + id = genID(); + } + + // Create pad with given text. + try { + getClient().createPad(id, content); + } catch (EPLiteException ex) { + getClient().setText(id, content); + } + return id; + } + + /** + * Generate a random pad ID. + * + * @return the ID. + */ + public static String genID() { + int length = 15; + String[] chars = ("0123456789" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz").split(""); + String out = ""; + Random rng = new Random(); + + for (int i = 0; i < length; i++) { + out += chars[rng.nextInt(chars.length)]; + } + + return out; + } + + /** + * Get a client for the pad API. + * + * @return the client. + * @throws IOException if things break. + */ + public static EPLiteClient getClient() throws Exception { + // Load the API key from a file, so it's not included with Git. + String apikey; + BufferedReader reader = new BufferedReader( + new InputStreamReader( + Pads.class + .getResourceAsStream("/padkey"))); + apikey = reader.readLine(); + + // New client + return new EPLiteClient(PADS_URL, apikey); + } + + /** + * Get the pad text. + * + * @param id the pad ID. + * @return the text, or error message. + */ + public static String getPad(String id) { + String text = ""; + try { + text = getClient().getText(id).getOrDefault("text", "").toString(); + } catch (Exception ex) { + text = "Error: Could not get pad contents: " + ex.getMessage(); + } + return text; + } + + public static void setPad(String id, String content) { + try { + getClient().setText(id, content); + } catch (Exception ex) { + Debug.stacktrace(ex); + JOptionPane.showInternalMessageDialog( + MainGUI.mainPane, + "Could not sync pad contents: " + ex.getMessage(), + "Error", + JOptionPane.ERROR_MESSAGE); + } + } + + /** + * Delete the given pad ID. + * + * @param id the pad ID. + * @param purge If true will delete from server also. + */ + public static void delPad(String id, boolean purge) { + delPad(id); + if (purge) { + try { + getClient().deletePad(id); + } catch (Exception ex) { + // Meh. No big deal. + } + } + } + + /** + * 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() { + + jScrollPane1 = new javax.swing.JScrollPane(); + padPane = new javax.swing.JList(); + jLabel1 = new javax.swing.JLabel(); + openBtn = new javax.swing.JButton(); + delBtn = new javax.swing.JButton(); + previewBtn = new javax.swing.JButton(); + saveBtn = new javax.swing.JButton(); + jScrollPane2 = new javax.swing.JScrollPane(); + previewPane = new javax.swing.JTextArea(); + shareBtn = new javax.swing.JButton(); + jLabel2 = new javax.swing.JLabel(); + purgeBtn = new javax.swing.JButton(); + addBtn = new javax.swing.JButton(); + + setClosable(true); + setIconifiable(true); + setMaximizable(true); + setResizable(true); + setTitle("Collaboration"); + setMinimumSize(new java.awt.Dimension(450, 280)); + setPreferredSize(new java.awt.Dimension(450, 280)); + + padPane.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + padPane.addListSelectionListener(new javax.swing.event.ListSelectionListener() { + public void valueChanged(javax.swing.event.ListSelectionEvent evt) { + padPaneValueChanged(evt); + } + }); + jScrollPane1.setViewportView(padPane); + + jLabel1.setText("My Pads:"); + + openBtn.setText("Open"); + openBtn.setToolTipText("Open pad for editing"); + openBtn.setEnabled(false); + openBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + openBtnActionPerformed(evt); + } + }); + + delBtn.setText("Delete"); + delBtn.setToolTipText("Remove pad from list"); + delBtn.setEnabled(false); + delBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + delBtnActionPerformed(evt); + } + }); + + previewBtn.setText("Preview"); + previewBtn.setToolTipText("Preview pad contents"); + previewBtn.setEnabled(false); + previewBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + previewBtnActionPerformed(evt); + } + }); + + saveBtn.setText("Save"); + saveBtn.setToolTipText("Save pad locally"); + saveBtn.setEnabled(false); + saveBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + saveBtnActionPerformed(evt); + } + }); + + previewPane.setEditable(false); + previewPane.setColumns(1); + previewPane.setRows(1); + previewPane.setTabSize(4); + jScrollPane2.setViewportView(previewPane); + + shareBtn.setText("Share"); + shareBtn.setToolTipText("Share pad"); + shareBtn.setEnabled(false); + shareBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + shareBtnActionPerformed(evt); + } + }); + + jLabel2.setText("Preview:"); + + purgeBtn.setText("Purge"); + purgeBtn.setToolTipText("Delete pad completely"); + purgeBtn.setEnabled(false); + purgeBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + purgeBtnActionPerformed(evt); + } + }); + + addBtn.setText("+"); + addBtn.setToolTipText("Add pad by ID"); + addBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + addBtnActionPerformed(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, false) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 132, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 69, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(addBtn))) + .addGap(6, 6, 6) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(delBtn, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(saveBtn, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(openBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(previewBtn, javax.swing.GroupLayout.DEFAULT_SIZE, 84, Short.MAX_VALUE) + .addComponent(shareBtn, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(purgeBtn, 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.LEADING) + .addComponent(jScrollPane2) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel2) + .addGap(0, 144, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jLabel2) + .addComponent(addBtn)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane2) + .addComponent(jScrollPane1) + .addGroup(layout.createSequentialGroup() + .addComponent(openBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(saveBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(delBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(purgeBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(previewBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(shareBtn))) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void delBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_delBtnActionPerformed + int ans = JOptionPane.showInternalConfirmDialog(this, + "Remove pad from list? It will not be removed from the server.", + "Delete?", + JOptionPane.OK_CANCEL_OPTION); + if (ans == JOptionPane.OK_OPTION) { + delPad(getSelectedPad()); + } + updateList(); + }//GEN-LAST:event_delBtnActionPerformed + + private void previewBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_previewBtnActionPerformed + previewPane.setText(getPad(getSelectedPad())); + updateList(); + }//GEN-LAST:event_previewBtnActionPerformed + + private void openBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openBtnActionPerformed + MainGUI.loadFrame(new WebBrowser("Pad " + getSelectedPad(), + PADS_URL + "/p/" + getSelectedPad(), + WebBrowser.PAD_LOGO)); + }//GEN-LAST:event_openBtnActionPerformed + + private void purgeBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_purgeBtnActionPerformed + int ans = JOptionPane.showInternalConfirmDialog(this, + "Remove pad from server and list? This cannot be undone!", + "Delete?", + JOptionPane.OK_CANCEL_OPTION); + if (ans == JOptionPane.OK_OPTION) { + delPad(getSelectedPad(), true); + } + updateList(); + }//GEN-LAST:event_purgeBtnActionPerformed + + private void saveBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveBtnActionPerformed + MainGUI.loadFrame(new CodeEditor(getPad(getSelectedPad()), true)); + updateList(); + }//GEN-LAST:event_saveBtnActionPerformed + + private void padPaneValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_padPaneValueChanged + // Enable/disable action buttons to prevent errors. + boolean enable = true; + if (padPane.getSelectedValue() == null) { + enable = false; + } + openBtn.setEnabled(enable); + saveBtn.setEnabled(enable); + delBtn.setEnabled(enable); + shareBtn.setEnabled(enable); + previewBtn.setEnabled(enable); + purgeBtn.setEnabled(enable); + }//GEN-LAST:event_padPaneValueChanged + + private void addBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addBtnActionPerformed + String id = JOptionPane.showInternalInputDialog(this, + "What is the pad ID?", + "Add Pad", + JOptionPane.QUESTION_MESSAGE); + if (id != null && !id.equals("")) { + if (id.contains("pad.symatapp.com/p/")) { + id = id.substring(id.lastIndexOf('/') + 1); + Debug.println(id); + } + addPad(id); + } + updateList(); + }//GEN-LAST:event_addBtnActionPerformed + + private void shareBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_shareBtnActionPerformed + JOptionPane.showInternalMessageDialog(this, + new SharePad(getSelectedPad()), + "Share Pad", + JOptionPane.PLAIN_MESSAGE); + }//GEN-LAST:event_shareBtnActionPerformed + + private String getSelectedPad() { + return padPane.getSelectedValue().toString(); + } + + private void updateList() { + padPane.setListData(getPads()); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton addBtn; + private javax.swing.JButton delBtn; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane jScrollPane2; + private javax.swing.JButton openBtn; + private javax.swing.JList padPane; + private javax.swing.JButton previewBtn; + private javax.swing.JTextArea previewPane; + private javax.swing.JButton purgeBtn; + private javax.swing.JButton saveBtn; + private javax.swing.JButton shareBtn; + // End of variables declaration//GEN-END:variables +} diff --git a/src/net/apocalypselabs/symat/SharePad.form b/src/net/apocalypselabs/symat/SharePad.form new file mode 100644 index 0000000..826baf0 --- /dev/null +++ b/src/net/apocalypselabs/symat/SharePad.form @@ -0,0 +1,114 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/net/apocalypselabs/symat/SharePad.java b/src/net/apocalypselabs/symat/SharePad.java new file mode 100644 index 0000000..9f8d879 --- /dev/null +++ b/src/net/apocalypselabs/symat/SharePad.java @@ -0,0 +1,171 @@ +/* + * CODE LICENSE ===================== + * Copyright (c) 2015, Apocalypse Laboratories + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * 4. You adhere to the Media License detailed below. If you do not, this license + * is automatically revoked and you must purge all copies of the software you + * possess, in source or binary form. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * MEDIA LICENSE ==================== + * All images and other graphical files (the "graphics") included with this + * software are copyright (c) 2015 Apocalypse Laboratories. You may not distribute + * the graphics or any program, source code repository, or other digital storage + * media containing them without written permission from Apocalypse Laboratories. + * This ban on distribution only applies to publicly available systems. + * A password-protected network file share, USB drive, or other storage scheme that + * cannot be easily accessed by the public is generally allowed. If in doubt, + * contact Apocalypse Laboratories. If Apocalypse Laboratories allows or denies + * you permission, that decision is considered final and binding. + */ +package net.apocalypselabs.symat; + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; + +/** + * + * @author Skylar + */ +public class SharePad extends javax.swing.JPanel { + + String padid = ""; + /** + * Creates new form SharePad + * @param id The pad ID to share. + */ + public SharePad(String id) { + initComponents(); + padid = id; + padIDBox.setText(padid); + } + + /** + * 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() { + + jLabel1 = new javax.swing.JLabel(); + padIDBox = new javax.swing.JTextField(); + jPanel1 = new javax.swing.JPanel(); + copyIDBtn = new javax.swing.JButton(); + copyLinkBtn = new javax.swing.JButton(); + + setMinimumSize(new java.awt.Dimension(188, 172)); + + jLabel1.setText("Pad ID:"); + + padIDBox.setEditable(false); + + jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder("Share")); + + copyIDBtn.setText("Copy ID"); + copyIDBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + copyIDBtnActionPerformed(evt); + } + }); + + copyLinkBtn.setText("Copy Link"); + copyLinkBtn.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + copyLinkBtnActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(copyIDBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(copyLinkBtn)) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(copyIDBtn) + .addComponent(copyLinkBtn)) + .addGap(0, 77, Short.MAX_VALUE)) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(padIDBox))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(padIDBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + }// //GEN-END:initComponents + + private void copyIDBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_copyIDBtnActionPerformed + toClipboard(padid); + }//GEN-LAST:event_copyIDBtnActionPerformed + + private void copyLinkBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_copyLinkBtnActionPerformed + toClipboard(Pads.PADS_URL+"/p/"+padid); + }//GEN-LAST:event_copyLinkBtnActionPerformed + + private void toClipboard(String text) { + StringSelection stringSelection = new StringSelection(text); + Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard(); + clpbrd.setContents(stringSelection, null); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton copyIDBtn; + private javax.swing.JButton copyLinkBtn; + private javax.swing.JLabel jLabel1; + private javax.swing.JPanel jPanel1; + private javax.swing.JTextField padIDBox; + // End of variables declaration//GEN-END:variables +} diff --git a/src/net/apocalypselabs/symat/WebBrowser.form b/src/net/apocalypselabs/symat/WebBrowser.form index 07cd873..e95d797 100644 --- a/src/net/apocalypselabs/symat/WebBrowser.form +++ b/src/net/apocalypselabs/symat/WebBrowser.form @@ -10,7 +10,6 @@ - diff --git a/src/net/apocalypselabs/symat/WebBrowser.java b/src/net/apocalypselabs/symat/WebBrowser.java index 78b6454..2e5a2d0 100644 --- a/src/net/apocalypselabs/symat/WebBrowser.java +++ b/src/net/apocalypselabs/symat/WebBrowser.java @@ -71,6 +71,7 @@ public class WebBrowser extends javax.swing.JInternalFrame { public static final int DEFAULT_LOGO = 0; public static final int WIKI_LOGO = 1; public static final int FORUM_LOGO = 2; + public static final int PAD_LOGO = 3; /** * Creates new form WebBrowser @@ -89,7 +90,7 @@ public class WebBrowser extends javax.swing.JInternalFrame { children.add(browser); jfxPanel.setScene(scene); webEngine = browser.getEngine(); - webEngine.setUserAgent("SyMAT "+MainGUI.VERSION_NAME); + webEngine.setUserAgent("SyMAT " + MainGUI.VERSION_NAME); webEngine.loadContent("

Loading...

"); } }); @@ -117,7 +118,9 @@ public class WebBrowser extends javax.swing.JInternalFrame { case FORUM_LOGO: setFrameIcon(new ImageIcon(getClass().getResource("/net/apocalypselabs/symat/icons/forum.png"))); break; - case DEFAULT_LOGO: + case PAD_LOGO: + setFrameIcon(new ImageIcon(getClass().getResource("/net/apocalypselabs/symat/icons/editor.png"))); + break; default: setFrameIcon(new ImageIcon(getClass().getResource("/net/apocalypselabs/symat/icons/browser.png"))); } @@ -215,8 +218,8 @@ public class WebBrowser extends javax.swing.JInternalFrame { @Override public void run() { jfxPanel.setSize(getWidth(), getHeight()); - browser.setPrefSize(getWidth()-12, getHeight()-12); - browser.resize(getWidth() - 12, getHeight() - 12); + browser.setPrefSize(getWidth() - 12, getHeight() - 32); + browser.resize(getWidth() - 12, getHeight() - 32); } }); } diff --git a/src/net/apocalypselabs/symat/help/licenses.html b/src/net/apocalypselabs/symat/help/licenses.html index e826281..6ac32b9 100644 --- a/src/net/apocalypselabs/symat/help/licenses.html +++ b/src/net/apocalypselabs/symat/help/licenses.html @@ -48,7 +48,7 @@ contact Apocalypse Laboratories. If Apocalypse Laboratories allows or denies you permission, that decision is considered final and binding.

This application also uses libraries from third-parties.

-

Symja (parser), log4j, Java-Prettify:

+

Symja (parser), log4j, Java-Prettify, json-simple, java-etherpad-lite:

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
diff --git a/src/net/apocalypselabs/symat/images/pads.png b/src/net/apocalypselabs/symat/images/pads.png new file mode 100644 index 0000000..d392b3a Binary files /dev/null and b/src/net/apocalypselabs/symat/images/pads.png differ diff --git a/src/net/apocalypselabs/symat/images/shell.png b/src/net/apocalypselabs/symat/images/shell.png index 5b67b1a..db0fe07 100644 Binary files a/src/net/apocalypselabs/symat/images/shell.png and b/src/net/apocalypselabs/symat/images/shell.png differ diff --git a/src/org/etherpad_lite_client/EPLiteClient.java b/src/org/etherpad_lite_client/EPLiteClient.java new file mode 100644 index 0000000..e941f1f --- /dev/null +++ b/src/org/etherpad_lite_client/EPLiteClient.java @@ -0,0 +1,639 @@ +package org.etherpad_lite_client; + +import java.util.Date; +import java.util.HashMap; + +/** + * A client for talking to Etherpad Lite's HTTP JSON API.
+ *
+ * Example:
+ *
+ * + * EPLiteClient api = new EPLiteClient("http://etherpad.mysite.com", "FJ7jksalksdfj83jsdflkj");
+ * HashMap pad = api.getText("my_pad");
+ * String pad = pad.get("text").toString(); + *
+ */ +public class EPLiteClient { + /** + * The Etherpad Lite API version this client targets by default + */ + public static final String DEFAULT_API_VERSION = "1.2.11"; + + /** + * The connection object + */ + public EPLiteConnection connection; + + /** + * Initializes a new org.etherpad_lite_client.EPLiteClient object. + * The default Etherpad Lite API version (in DEFAULT_API_VERSION) will be used. + * + * @param url an absolute url, including protocol, to the EPL api + * @param apiKey the API Key + */ + public EPLiteClient(String url, String apiKey) { + this.connection = new EPLiteConnection(url, apiKey, DEFAULT_API_VERSION); + } + + /** + * Initializes a new org.etherpad_lite_client.EPLiteClient object. + * The specified Etherpad Lite API version will be used. + * + * @param url an absolute url, including protocol, to the EPL api + * @param apiKey the API Key + * @param apiVersion the API version + */ + public EPLiteClient(String url, String apiKey, String apiVersion) { + this.connection = new EPLiteConnection(url, apiKey, apiVersion); + } + + // Groups + // Pads may belong to a group. These pads are not considered "public", and won't be available through the Web UI without a session. + + /** + * Creates a new Group. The group id is returned in "groupID" in the HashMap. + * + * @return HashMap + */ + public HashMap createGroup() { + return this.connection.post("createGroup"); + } + + /** + * Creates a new Group for groupMapper if one doesn't already exist. Helps you map your application's groups to Etherpad Lite's groups. + * The group id is returned in "groupID" in the HashMap. + * + * @param groupMapper your group mapper string + * @return HashMap + */ + public HashMap createGroupIfNotExistsFor(String groupMapper) { + HashMap args = new HashMap(); + args.put("groupMapper", groupMapper); + return this.connection.post("createGroupIfNotExistsFor", args); + } + + /** + * Delete group. + * + * @param groupID string + */ + public void deleteGroup(String groupID) { + HashMap args = new HashMap(); + args.put("groupID", groupID); + this.connection.post("deleteGroup", args); + } + + /** + * List all the padIDs in a group. They will be in an array inside "padIDs". + * + * @param groupID string + * @return HashMap + */ + public HashMap listPads(String groupID) { + HashMap args = new HashMap(); + args.put("groupID", groupID); + return this.connection.get("listPads", args); + } + + /** + * Create a pad in this group. + * + * @param groupID string + * @param padName string + */ + public HashMap createGroupPad(String groupID, String padName) { + HashMap args = new HashMap(); + args.put("groupID", groupID); + args.put("padName", padName); + return this.connection.post("createGroupPad", args); + } + + /** + * Create a pad in this group. + * + * @param groupID string + * @param padName string + * @param text string + */ + public void createGroupPad(String groupID, String padName, String text) { + HashMap args = new HashMap(); + args.put("groupID", groupID); + args.put("padName", padName); + args.put("text", text); + this.connection.post("createGroupPad", args); + } + + /** + * Lists all existing groups. The group ids are returned in "groupIDs". + * + * @return HashMap + */ + public HashMap listAllGroups() { + return this.connection.get("listAllGroups"); + } + + // Authors + // These authors are bound to the attributes the users choose (color and name). The author id is returned in "authorID". + + /** + * Create a new author. + * + * @return HashMap + */ + public HashMap createAuthor() { + return this.connection.post("createAuthor"); + } + + /** + * Create a new author with the given name. The author id is returned in "authorID". + * + * @param name string + * @return HashMap + */ + public HashMap createAuthor(String name) { + HashMap args = new HashMap(); + args.put("name", name); + return this.connection.post("createAuthor", args); + } + + /** + * Creates a new Author for authorMapper if one doesn't already exist. Helps you map your application's authors to Etherpad Lite's authors. + * The author id is returned in "authorID". + * + * @param authorMapper string + * @return HashMap + */ + public HashMap createAuthorIfNotExistsFor(String authorMapper) { + HashMap args = new HashMap(); + args.put("authorMapper", authorMapper); + return this.connection.post("createAuthorIfNotExistsFor", args); + } + + /** + * Creates a new Author for authorMapper if one doesn't already exist. Helps you map your application's authors to Etherpad Lite's authors. + * The author id is returned in "authorID". + * + * @param authorMapper string + * @param name string + * @return HashMap + */ + public HashMap createAuthorIfNotExistsFor(String authorMapper, String name) { + HashMap args = new HashMap(); + args.put("authorMapper", authorMapper); + args.put("name", name); + return this.connection.post("createAuthorIfNotExistsFor", args); + } + + /** + * List the ids of pads the author has edited. They will be in an array inside "padIDs". + * + * @param authorId the authors's id string + * @return HashMap + */ + public HashMap listPadsOfAuthor(String authorId) { + HashMap args = new HashMap(); + args.put("authorID", authorId); + return this.connection.get("listPadsOfAuthor", args); + } + + /** + * Returns the Author Name of the author. + * + * @param authorId the author's id string + * @return String + */ + public String getAuthorName(String authorId) { + HashMap args = new HashMap(); + args.put("authorID", authorId); + return this.connection.get("getAuthorName", args).toString(); + } + + // Sessions + // Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a + // cookie to the client and is valid until a certain date. Only users with a valid session for this group, can access group pads. You can create a + // session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session + // and delete it after the user logged out. + + /** + * Create a new session for the given author in the given group, valid until the given UNIX time. + * The session id will be returned in "sessionID".
+ *
+ * Example:
+ *
+ * + * import java.util.Date;
+ * ...
+ * Date now = new Date();
+ * long in1Hour = (now.getTime() + (60L * 60L * 1000L) / 1000L);
+ * String sessID1 = api.createSession(groupID, authorID, in1Hour).get("sessionID").toString(); + *
+ * + * @param groupID string + * @param authorID string + * @param validUntil long UNIX timestamp in seconds + * @return HashMap + */ + public HashMap createSession(String groupID, String authorID, long validUntil) { + HashMap args = new HashMap(); + args.put("groupID", groupID); + args.put("authorID", authorID); + args.put("validUntil", String.valueOf(validUntil)); + return this.connection.post("createSession", args); + } + + /** + * Create a new session for the given author in the given group valid for the given number of hours. + * The session id will be returned in "sessionID".
+ *
+ * Example:
+ *
+ * + * // in 2 hours
+ * String sessID1 = api.createSession(groupID, authorID, 2).get("sessionID").toString(); + *
+ * + * @param groupID string + * @param authorID string + * @param validUntil int length of session in hours + * @return HashMap + */ + public HashMap createSession(String groupID, String authorID, int length) { + long inNHours = ((new Date()).getTime() + ((long)length * 60L * 60L * 1000L)) / 1000L; + return this.createSession(groupID, authorID, inNHours); + } + + /** + * Create a new session for the given author in the given group, valid until the given datetime. + * The session id will be returned in "sessionID".
+ *
+ * Example:
+ *
+ * + * import java.util.Date;
+ * import java.text.DateFormat;
+ * import java.text.SimpleDateFormat;
+ * import java.util.TimeZone;
+ * ...
+ * DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ * dfm.setTimeZone(TimeZone.getTimeZone("GMT-5"));
+ * Date longTime = dfm.parse("2056-01-15 20:15:00");
+ * String sessID = api.createSession(groupID, authorID, longTime).get("sessionID").toString(); + *
+ * + * @param groupID string + * @param authorID string + * @param validUntil Date + * @return HashMap + */ + public HashMap createSession(String groupID, String authorID, Date validUntil) { + long seconds = validUntil.getTime() / 1000L; + return this.createSession(groupID, authorID, seconds); + } + + /** + * Delete a session. + * + * @param sessionID string + */ + public void deleteSession(String sessionID) { + HashMap args = new HashMap(); + args.put("sessionID", sessionID); + this.connection.post("deleteSession", args); + } + + /** + * Returns information about a session: authorID, groupID and validUntil. + * + * @param sessionID string + * @return HashMap + */ + public HashMap getSessionInfo(String sessionID) { + HashMap args = new HashMap(); + args.put("sessionID", sessionID); + return this.connection.get("getSessionInfo", args); + } + + /** + * List all the sessions IDs in a group. Returned as a HashMap of sessionIDs keys, with values of HashMaps containing + * groupID, authorID, and validUntil. + * + * @param groupID string + * @return HashMap + */ + public HashMap listSessionsOfGroup(String groupID) { + HashMap args = new HashMap(); + args.put("groupID", groupID); + return this.connection.get("listSessionsOfGroup", args); + } + + /** + * List all the sessions IDs belonging to an author. Returned as a HashMap of sessionIDs keys, with values of HashMaps containing + * groupID, authorID, and validUntil. + * + * @param authorID string + * @return HashMap + */ + public HashMap listSessionsOfAuthor(String authorID) { + HashMap args = new HashMap(); + args.put("authorID", authorID); + return this.connection.get("listSessionsOfAuthor", args); + } + + // Pad content + + /** + * Returns a list of all pads. + * + * @return HashMap + */ + public HashMap listAllPads() { + return this.connection.get("listAllPads"); + } + + /** + * Returns a HashMap containing the latest revision of the pad's text. + * The text is stored under "text". + * + * @param padId the pad's id string + * @return HashMap + */ + public HashMap getText(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + return this.connection.get("getText", args); + } + + /** + * Returns a HashMap containing the a specific revision of the pad's text. + * The text is stored under "text". + * + * @param padId the pad's id string + * @param rev the revision number + * @return HashMap + */ + public HashMap getText(String padId, int rev) { + HashMap args = new HashMap(); + args.put("padID", padId); + args.put("rev", new Integer(rev)); + return this.connection.get("getText", args); + } + + /** + * Creates a new revision with the given text (or creates a new pad). + * + * @param padId the pad's id string + * @param text the pad's new text + */ + public void setText(String padId, String text) { + HashMap args = new HashMap(); + args.put("padID", padId); + args.put("text", text); + this.connection.post("setText", args); + } + + /** + * Returns a HashMap containing the current revision of the pad's text as HTML. + * The html is stored under "html". + * + * @param padId the pad's id string + * @return HashMap + */ + public HashMap getHTML(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + return this.connection.get("getHTML", args); + } + + /** + * Returns a HashMap containing the a specific revision of the pad's text as HTML. + * The html is stored under "html". + * + * @param padId the pad's id string + * @param rev the revision number + * @return HashMap + */ + public HashMap getHTML(String padId, int rev) { + HashMap args = new HashMap(); + args.put("padID", padId); + args.put("rev", new Integer(rev)); + return this.connection.get("getHTML", args); + } + + /** + * Creates a new revision with the given html (or creates a new pad). + * + * @param padId the pad's id string + * @param html the pad's new html text + */ + public void setHTML(String padId, String html) { + HashMap args = new HashMap(); + args.put("padID", padId); + args.put("html", html); + this.connection.post("setHTML", args); + } + + // Pads + // Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and its + // forbidden for normal pads to include a $ in the name. + + /** + * Create a new pad. + * + * @param padId the pad's id string + */ + public void createPad(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + this.connection.post("createPad", args); + } + + /** + * Create a new pad with the given initial text. + * + * @param padId the pad's id string + * @param text the initial text string + */ + public void createPad(String padId, String text) { + HashMap args = new HashMap(); + args.put("padID", padId); + args.put("text", text); + this.connection.post("createPad", args); + } + + /** + * Returns the number of revisions of this pad. The number is in "revisions". + * + * @param padId the pad's id string + * @return HashMap + */ + public HashMap getRevisionsCount(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + return this.connection.get("getRevisionsCount", args); + } + + /** + * List the ids of authors who have edited a pad. They will be in an array inside "authorIDs". + * + * @param padId the pad's id string + * @return HashMap + */ + public HashMap listAuthorsOfPad(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + return this.connection.get("listAuthorsOfPad", args); + } + + /** + * Deletes a pad. + * + * @param padId the pad's id string + */ + public void deletePad(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + this.connection.post("deletePad", args); + } + + /** + * Get the pad's read-only id. The id will be in "readOnlyID". + * + * @param padId the pad's id string + * @return HashMap + */ + public HashMap getReadOnlyID(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + return this.connection.get("getReadOnlyID", args); + } + + /** + * Get the pad's last edit date as a Unix timestamp. The timestamp will be in "lastEdited". + * + * @param padId the pad's id string + * @return HashMap + */ + public HashMap getLastEdited(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + return this.connection.get("getLastEdited", args); + } + + /** + * Get the number of users currently editing a pad. + * + * @param padId the pad's id string + * @return Long + */ + public Long padUsersCount(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + Long userCount = (Long) this.connection.get("padUsersCount", args).get("padUsersCount"); + return userCount; + } + + /** + * Returns the list of users that are currently editing this pad. + * A padUser has the values: "colorId", "name" and "timestamp". + * + * @param padId + * @return HashMap + */ + public HashMap padUsers(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + return this.connection.get("padUsers", args); + } + + /** + * Sets the pad's public status. + * This is only applicable to group pads. + * + * @param padId the pad's id string + * @param publicStatus boolean + */ + public void setPublicStatus(String padId, Boolean publicStatus) { + HashMap args = new HashMap(); + args.put("padID", padId); + args.put("publicStatus", publicStatus); + this.connection.post("setPublicStatus", args); + } + + /** + * Gets the pad's public status. The boolean is in "publicStatus". + * This is only applicable to group pads.
+ *
+ * Example:
+ *
+ * + * Boolean is_public = (Boolean)api.getPublicStatus("g.kjsdfj7ask$foo").get("publicStatus"); + * + * + * @param padId the pad's id string + * @return HashMap + */ + public HashMap getPublicStatus(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + return this.connection.get("getPublicStatus", args); + } + + /** + * Sets the pad's password. This is only applicable to group pads. + * + * @param padId the pad's id string + * @param password string + */ + public void setPassword(String padId, String password) { + HashMap args = new HashMap(); + args.put("padID", padId); + args.put("password", password); + this.connection.post("setPassword", args); + } + + /** + * Checks whether the pad is password-protected or not. The boolean is in "isPasswordProtected". + * This is only applicable to group pads.
+ *
+ * Example:
+ *
+ * + * Boolean pass = (Boolean)api.isPasswordProtected("g.kjsdfj7ask$foo").get("isPasswordProtected"); + * + * + * @param padId the pad's id string + * @return HashMap + */ + public HashMap isPasswordProtected(String padId) { + HashMap args = new HashMap(); + args.put("padID", padId); + return this.connection.get("isPasswordProtected", args); + } + + /** + * Sends a custom message of type msg to the pad. + * + * @param padId + * @param msg + */ + public void sendClientsMessage(String padId, String msg) { + HashMap args = new HashMap(); + args.put("padID", padId); + args.put("msg", msg); + this.connection.post("sendClientsMessage", args); + } + + /** + * Returns true if the connection is using SSL/TLS, false if not. + * + * @return boolean + */ + public boolean isSecure() { + if (this.connection.uri.getPort() == 443) { + return true; + } else { + return false; + } + } +} diff --git a/src/org/etherpad_lite_client/EPLiteConnection.java b/src/org/etherpad_lite_client/EPLiteConnection.java new file mode 100644 index 0000000..857202c --- /dev/null +++ b/src/org/etherpad_lite_client/EPLiteConnection.java @@ -0,0 +1,258 @@ +package org.etherpad_lite_client; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import net.apocalypselabs.symat.Debug; + +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +/** + * Connection object for talking to and parsing responses from the Etherpad Lite Server. + */ +public class EPLiteConnection { + public static final int CODE_OK = 0; + public static final int CODE_INVALID_PARAMETERS = 1; + public static final int CODE_INTERNAL_ERROR = 2; + public static final int CODE_INVALID_METHOD = 3; + public static final int CODE_INVALID_API_KEY = 4; + + /** + * The url of the API + */ + public URI uri; + + /** + * The API key + */ + public String apiKey; + + /** + * The Etherpad Lite API version + */ + public String apiVersion; + + /** + * Initializes a new org.etherpad_lite_client.EPLiteConnection object. + * + * @param url an absolute url, including protocol, to the EPL api + * @param apiKey the API Key + * @param apiVersion the API version + */ + public EPLiteConnection(String url, String apiKey, String apiVersion) { + this.uri = URI.create(url); + this.apiKey = apiKey; + this.apiVersion = apiVersion; + } + + /** + * GETs from the HTTP JSON API. + * + * @param apiMethod the name of the API method to call + * @return HashMap + */ + public HashMap get(String apiMethod) { + return this.get(apiMethod, new HashMap()); + } + + /** + * GETs from the HTTP JSON API. + * + * @param apiMethod the name of the API method to call + * @param apiArgs a HashMap of url/form parameters. apikey will be set automatically + * @return HashMap + */ + public HashMap get(String apiMethod, HashMap apiArgs) { + String path = this.apiPath(apiMethod); + String query = this.queryString(apiArgs); + URL url = apiUrl(path, query); + Request request = new GETRequest(url); + return this.call(request); + } + + /** + * POSTs to the HTTP JSON API. + * + * @param apiMethod the name of the API method to call + * @return HashMap + */ + public HashMap post(String apiMethod) { + return this.post(apiMethod, new HashMap()); + } + + /** + * POSTs to the HTTP JSON API. + * + * @param apiMethod the name of the API method to call + * @param apiArgs a HashMap of url/form parameters. apikey will be set automatically + * @return HashMap + */ + public HashMap post(String apiMethod, HashMap apiArgs) { + String path = this.apiPath(apiMethod); + String query = this.queryString(apiArgs); + URL url = apiUrl(path, null); + Request request = new POSTRequest(url, query); + return this.call(request); + } + + /** + * Calls the HTTP JSON API. + * + * @param request the request object to send + * @return HashMap + */ + private HashMap call(Request request) { + trustServerAndCertificate(); + + try { + String response = request.send(); + return this.handleResponse(response); + } + catch (EPLiteException e) { + throw new EPLiteException(e.getMessage()); + } + catch (Exception e) { + throw new EPLiteException("Unable to connect to SyMAT: " + e.getMessage()); + } + } + + /** + * Converts the API resonse's JSON string into a HashMap. + * + * @param jsonString a valid JSON string + * @return HashMap + */ + private HashMap handleResponse(String jsonString) { + try { + JSONParser parser = new JSONParser(); + Map response = (Map) parser.parse(jsonString); + // Act on the response code + if (!response.get("code").equals(null)) { + int code = ((Long) response.get("code")).intValue(); + switch ( code ) { + // Valid code, parse the response + case CODE_OK: + HashMap data = (HashMap) response.get("data"); + return data != null ? data: new HashMap(); + // Invalid code, throw an exception with the message + case CODE_INVALID_PARAMETERS: + case CODE_INVALID_API_KEY: + case CODE_INVALID_METHOD: + throw new EPLiteException((String)response.get("message")); + default: + throw new EPLiteException("An unknown error has occurred while handling the response: " + jsonString); + } + // No response code, something's really wrong + } else { + throw new EPLiteException("An unknown error has occurred while handling the response: " + jsonString); + } + } catch (ParseException e) { + System.err.println("Unable to parse JSON response (" + jsonString + "): " + e.getMessage()); + return new HashMap(); + } + } + + /** + * Returns the URL for the api path and query. + * + * @param path the api path + * @param query the query string (may be null) + * @return URL + */ + private URL apiUrl(String path, String query) { + try { + URL url = new URL(new URI(this.uri.getScheme(), null, this.uri.getHost(), this.uri.getPort(), path, query, null).toString()); + return url; + } catch (Exception e) { + throw new EPLiteException("Unable to connect to SyMAT: " + e.getMessage()); + } + } + + /** + * Returns a URI path for the API method + * + * @param apiMethod the api method + * @return String + */ + private String apiPath(String apiMethod) { + return this.uri.getPath() + "/api/" + this.apiVersion + "/" + apiMethod; + } + + /** + * Returns a query string made from HashMap keys and values + * + * @param apiArgs the api arguments in a HashMap + * @return String + */ + private String queryString(HashMap apiArgs) { + String strArgs = ""; + apiArgs.put("apikey", this.apiKey); + Iterator i = apiArgs.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry e = (Map.Entry)i.next(); + Object value = e.getValue(); + if (value != null) { + try { + strArgs += e.getKey() + "=" + URLEncoder.encode(value.toString(), "utf-8"); + } catch (UnsupportedEncodingException ex) { + Debug.stacktrace(ex); + } + if (i.hasNext()) { + strArgs += "&"; + } + } + } + return strArgs; + } + + /** + * Creates a trust manager to trust all certificates if you open a ssl connection + */ + private void trustServerAndCertificate() { + // Create a trust manager that does not validate certificate chains + TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + } + }; + + // Install the all-trusting trust manager + try { + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } catch (Exception e) { + } + + HostnameVerifier hv = new HostnameVerifier() { + //@Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + HttpsURLConnection.setDefaultHostnameVerifier(hv); + } +} diff --git a/src/org/etherpad_lite_client/EPLiteException.java b/src/org/etherpad_lite_client/EPLiteException.java new file mode 100644 index 0000000..773af32 --- /dev/null +++ b/src/org/etherpad_lite_client/EPLiteException.java @@ -0,0 +1,7 @@ +package org.etherpad_lite_client; + +public class EPLiteException extends RuntimeException { + public EPLiteException(String msg) { + super(msg); + } +} diff --git a/src/org/etherpad_lite_client/GETRequest.java b/src/org/etherpad_lite_client/GETRequest.java new file mode 100644 index 0000000..c9186d3 --- /dev/null +++ b/src/org/etherpad_lite_client/GETRequest.java @@ -0,0 +1,47 @@ +package org.etherpad_lite_client; + +import java.net.URL; +import java.io.BufferedReader; +import java.io.InputStreamReader; + +/** + * A class for easily executing an HTTP GET request.
+ *
+ * Example:
+ *
+ * + * Request req = new GETRequest(url_object);
+ * String resp = req.send();
+ *
+ */ +public class GETRequest implements Request { + /** + * The URL object. + */ + private URL url; + + /** + * Instantiates a new GETRequest. + * + * @param url the URL object + */ + public GETRequest(URL url) { + this.url = url; + } + + /** + * Sends the request and returns the response. + * + * @return String + */ + public String send() throws Exception { + BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream())); + String response = ""; + String buffer; + while ((buffer = in.readLine()) != null) { + response += buffer; + } + in.close(); + return response; + } +} diff --git a/src/org/etherpad_lite_client/POSTRequest.java b/src/org/etherpad_lite_client/POSTRequest.java new file mode 100644 index 0000000..bf8d619 --- /dev/null +++ b/src/org/etherpad_lite_client/POSTRequest.java @@ -0,0 +1,59 @@ +package org.etherpad_lite_client; + +import java.net.URL; +import java.net.URLConnection; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; + +/** + * A class for easily executing an HTTP POST request.
+ *
+ * Example:
+ *
+ * + * Request req = new POSTRequest(url_object);
+ * String resp = req.send();
+ *
+ */ +public class POSTRequest implements Request { + /** + * The URL object. + */ + private URL url; + + /** + * Instantiates a new POSTRequest. + * + * @param url the URL object + */ + private String args; + + public POSTRequest(URL url, String args) { + this.url = url; + this.args = args; + } + + /** + * Sends the request and returns the response. + * + * @return String + */ + public String send() throws Exception { + URLConnection con = this.url.openConnection(); + con.setDoOutput(true); + + OutputStreamWriter out = new OutputStreamWriter(con.getOutputStream()); + out.write(this.args); + out.close(); + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String response = ""; + String buffer; + while ((buffer = in.readLine()) != null) { + response += buffer; + } + in.close(); + return response; + } +} diff --git a/src/org/etherpad_lite_client/Request.java b/src/org/etherpad_lite_client/Request.java new file mode 100644 index 0000000..add1941 --- /dev/null +++ b/src/org/etherpad_lite_client/Request.java @@ -0,0 +1,9 @@ +package org.etherpad_lite_client; + +import java.net.URL; + +public interface Request { + URL url = null; + + public abstract String send() throws Exception; +}