/** * Copyright 1999-2009 The Pegadi Team * * 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 * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * This is the main editor for articles. As it is a state-of-the-art * editor, it will subcontract out most of the work. * <p>The class will use editors derived from {@link * pegadi.artis.Editor Editor} to to the editing, Artis' task then is to * provide a common framework for these editors. * * @author Håvard Wigtil <havardw at pvv.org> * @author Jørgen Binningsbø <jb@underdusken.no> */ package org.pegadi.artis; import com.kitfox.svg.SVGCache; import com.kitfox.svg.app.beans.SVGIcon; import no.dusken.common.model.Person; import org.pegadi.client.ApplicationLauncher; import org.pegadi.client.ClientContext; import org.pegadi.maildialog.MailDialog; import org.pegadi.model.*; import org.pegadi.publisher.FOPAWTPublisher; import org.pegadi.publisher.FOPPDFPublisher; import org.pegadi.publisher.FOPPrintPublisher; import org.pegadi.server.NoAccessException; import org.pegadi.xml.PegadiErrorHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.awt.*; import java.awt.event.*; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.URI; import java.net.URL; import java.net.UnknownHostException; import java.rmi.RemoteException; import java.text.MessageFormat; import java.util.*; import java.util.List; import java.util.Timer; public class Artis extends JFrame { /** All static declarations and functions first * */ public static java.util.Map<Integer, Artis> openArtises = new java.util.HashMap<Integer, Artis>(6); private static final String PREF_DOMAIN = "artis"; private final Logger log = LoggerFactory.getLogger(getClass()); /** * Returns true if the article is currently open in Artis. * */ public static boolean isOpen(Article art) { return openArtises.containsKey(art.getId()); } public static Artis fetchUniqueArtis(Article art, ArticleLock lock) { Integer articleid = art.getId(); if (openArtises.containsKey(articleid)) { return openArtises.get(articleid); } else { Artis newArtis = new Artis(lock); openArtises.put(articleid, newArtis); newArtis.openArticle(art); return newArtis; } } Timer autoSaveTimer; /** List of currently active editors. */ Vector<Editor> editors; /** Tries to get the article lock */ LockChecker lockChecker; /** Application properties. Loaded on startup. */ ResourceBundle appProps; /** Editor properties. Loaded on startup. */ ResourceBundle editorProps; /** Article properties. Loaded with each article. */ ResourceBundle articleProps; /** All user-visible strings. */ ResourceBundle strings; /** Preferences for different settings **/ Properties preferences; /** The global editor toolbar. */ JToolBar toolBar; /** Sows status items at the bottom of the window. */ JPanel statusPanel; /** The label showing the current character count. */ JLabel charCountLabel; /** int containing the curret text selection count */ int charsSelected; /** int containing the total number of characters */ int chars; /** Tabbed pane to hold the left/top editors. */ JTabbedPane leftPane; /** Tabbed pane to hold the right/bottom editors. */ JTabbedPane rightPane; /** Split pane to hold leftPane and rightPane */ JSplitPane splitPane; /** List of menu items for the current editor. */ JMenuItem[] editorMenuItems, editorMenuItems2; JMenuItem[] characterMenuItems; JMenuItem[] formatMenuItems; /** The 'Edit' menu item. */ JMenu editMenu; JMenu insertMenu; JMenu characterMenu; JMenu statusMenu; JMenu formatMenu; /** Array of all articlestatuses */ List<ArticleStatus> statuses; /** The menu spesific to each editor. */ JMenu editorMenu; /** Action for exiting editor. */ AbstractAction exitAction; AbstractAction showSettingsAction; /** Action for saving articles. */ AbstractAction saveAction; /** Action for printing articles. */ AbstractAction printAction; /** Action for previewing articles. */ AbstractAction previewAction; /** Action for generating PDF from articles. */ AbstractAction pdfAction; /** Action for importing XML files */ AbstractAction importAction; /** Action for exporting XML files */ AbstractAction exportAction; /** Action for copying. */ AbstractAction copyAction; /** Action for pasting. */ AbstractAction pasteAction; /** Action for pasting. */ AbstractAction cutAction; /** cut copy paste buttons */ JButton pasteB; JButton copyB; JButton cutB; /** Action for sending article text as mail */ AbstractAction sendMailAction; /** The article the editor is editing. */ Article article; private String xmlpath; /** When the lock was first created */ long firstLockTime = -1; boolean canEditArticle = true; EasterGlassPane easter = new EasterGlassPane(); public void dispose() { super.dispose(); if (this.article != null) { openArtises.remove(article.getId()); } } public Artis() { super(); strings = ResourceBundle.getBundle("org.pegadi.artis.ArtisStrings"); appProps = ResourceBundle.getBundle("org.pegadi.artis.ArtisApp"); editorProps = ResourceBundle .getBundle("org.pegadi.artis.EditorStrings"); editors = new Vector<Editor>(); loadPreferences(); try { xmlpath = LoginContext.server.getWebXMLRoot(LoginContext.sessionKey); } catch (NoAccessException e) { log.error("Cannot set xmlpath", e); } createUI(); } public Artis(ArticleLock lock) { this(); firstLockTime = lock.getFirstLocked(); } /** * Loads an <code>Article</code> into the editor. * * @param art * The <code>Article</code> to open. * @return <code>true</code> if the <code>Article</code> was * successfully opened. */ public boolean openArticle(Article art) { article = art; String docType; this.setTitle(art.getName() + " - " + getTitle()); // Update article status combo buttons for (int i = 0; i < statusMenu.getItemCount(); i++) { JRadioButtonMenuItem rbmi = (JRadioButtonMenuItem) statusMenu .getItem(i); if (art.getArticleStatus().getName().equals(rbmi.getText())) rbmi.setSelected(true); else rbmi.setSelected(false); } // if the article haven't got any text yet, we insert a the // template for this article type. if (!article.hasText()) { try { String template = LoginContext.server.getTemplate(article .getId(), LoginContext.sessionKey); if (template != null) { article.setText(template); } else { log.error("Template is NULL, check server log for errors."); return false; } } catch (RemoteException re) { log.error("Exception while getting template", re); return false; } } // if validation is active, parse the article with a validating parser Document doc; if (ClientContext.includeUnstable && article.getArticleType().getName().equals("Nyhetsartikkel")) // @todo: This should check the DOCTYPE of the article, to verify if validation is supported by this article type. { // if (false) { log.debug("Re-parsing article with validation turned on."); try { DocumentBuilderFactory vdbf = DocumentBuilderFactory.newInstance(); vdbf.setNamespaceAware(true); vdbf.setValidating(true); vdbf.setAttribute( "http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); // @todo: ouch - another hardcoded location... String[] schemaSources = {"file:///Users/jb/pegadi/trunk/xml/schema/pegadi.xsd", "file:///Users/jb/pegadi/trunk/xml/schema/ud1.xsd"}; vdbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", schemaSources); DocumentBuilder validatingDocBuilder = vdbf.newDocumentBuilder(); PegadiErrorHandler handler = new PegadiErrorHandler(); validatingDocBuilder.setErrorHandler(handler); doc = validatingDocBuilder.parse(new InputSource(new StringReader(article.getText()))); if (handler.isValidationError() ) { log.error("Error valiating article."); return false; } } catch (Exception eeee) { log.error("Error parsing article with validation turned on", eeee); return false; } } else { // if the article is not yet parsed, then parse it. if (article.getDocument() == null) if (!article.parseText()) return false; doc = article.getDocument(); } docType = doc.getDocumentElement().getTagName(); String props; try { props = appProps.getString(docType); } catch (MissingResourceException mre) { log.error("Can't find properties for type {}", docType, mre); return false; } if (props == null) { log.error("Can't find article properties."); JOptionPane.showMessageDialog(this, strings .getString("file_open_error"), strings .getString("file_property_error"), JOptionPane.ERROR_MESSAGE); return false; } DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder; try { factory.setIgnoringElementContentWhitespace(false); builder = factory.newDocumentBuilder(); } catch (ParserConfigurationException pce) { log.error("Parser configuration exception", pce); return false; } Document confDoc = null; try { confDoc = builder.parse(new InputSource(getClass() .getResourceAsStream(props))); } catch (SAXException e) { log.error("SAXException parsing article", e); } catch (IOException e) { log.error("IOException parsing article", e); } NodeList left = confDoc.getElementsByTagName("left"); NodeList top = confDoc.getElementsByTagName("top"); if (left.getLength() > 0) { splitPane.setOrientation(JSplitPane.HORIZONTAL_SPLIT); addEditorsToPane(leftPane, (Element) left.item(0), doc); NodeList right = confDoc.getElementsByTagName("right"); addEditorsToPane(rightPane, (Element) right.item(0), doc); } else if (top.getLength() > 0) { splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); addEditorsToPane(leftPane, (Element) top.item(0), doc); Element bottom = (Element) confDoc.getElementsByTagName("bottom") .item(0); addEditorsToPane(rightPane, bottom, doc); } rightPane.setMinimumSize(new Dimension(0, 0)); leftPane.setMinimumSize(new Dimension(790 - 300, 0)); splitPane.setTopComponent(leftPane); splitPane.setBottomComponent(rightPane); splitPane.setContinuousLayout(false); splitPane.setDividerLocation(600); editorChanged((Editor) leftPane.getSelectedComponent()); updateCharCount(null); saveAction.setEnabled(true); return true; } /** * Alternative version of disableAllEditors() made to let you keep text selection and copy in locked article view. */ public void disableAllEditors_butKeepOptionsRelevantToTextCopying(){ for(Editor editor : editors) { editor.disableEditor_butKeepOptionsRelevantToTextCopying(); editor.pasteAction.setEnabled(false); editor.cutAction.setEnabled(false); } saveAction.setEnabled(false); insertMenu.setEnabled(false); pasteB.setEnabled(false); cutB.setEnabled(false); } // Called if the article lock is not yours when opening a new article public void checkForLock() { canEditArticle = false; if(lockChecker == null) { lockChecker = new LockChecker(this); new Thread(lockChecker).start(); } else if(!lockChecker.isRunning()) { lockChecker.setRunCheck(true); } } public void startAutoSaver() { if(autoSaveTimer != null) { autoSaveTimer.cancel(); } Integer savePeriodMinutes; try { savePeriodMinutes = Integer.parseInt(preferences.getProperty(ArtisPrefsDialog.AUTO_SAVE_PREF)); } catch(NumberFormatException e) { log.debug("No preference found for autoSave"); savePeriodMinutes = 10; } long savePeriod = (1000 * 60) * savePeriodMinutes; autoSaveTimer = new Timer(); autoSaveTimer.scheduleAtFixedRate(new AutoSaver(), savePeriod, savePeriod); } public void addEditorsToPane(JTabbedPane pane, Element editorList, Document doc) { NodeList editorElements = editorList.getElementsByTagName("editor"); for (int i = 0; i < editorElements.getLength(); i++) { Element editorElement = (Element) editorElements.item(i); String tagName = editorElement.getAttribute("tag"); long start = System.currentTimeMillis(); NodeList nl = doc.getElementsByTagName(tagName); Object[] param = new Object[2]; // The parameter for the constructor param[1] = this; if (nl.getLength() > 0) { param[0] = nl.item(0); } else { // The element isn't found, so we have to create it. log.debug("The element '{}' was not found.", tagName); Element elem = doc.createElement(tagName); doc.getDocumentElement().appendChild(elem); param[0] = elem; } log.debug("Element: {}", ((Element) param[0]).getTagName()); Editor editor = null; String className = ((Element) editorElements.item(i)) .getAttribute("class"); try { Class editorClass = Class.forName(className); Constructor[] editorConstructors = editorClass .getConstructors(); // Search for a constructor for (Constructor editorConstructor : editorConstructors) { Class[] types = editorConstructor.getParameterTypes(); if (types.length == 2 && types[0] == Element.class && types[1] == Artis.class) { // Found it! editor = (Editor) editorConstructor .newInstance(param); } } if (editor == null) { log.warn("WARNING! No constructor matching spec. found for {}", className); } } catch (InvocationTargetException e) { log.error("InvokationtargetException {}", className, e); } catch (Exception ex) { log.error("EXCEPTION creating class {}", className, ex); } if (editor != null) { editor.addEasterListener(new EasterListener() { public void easterEventHappened() { runEaster(); } }); editor.addTextListener(new TextListener() { public void textValueChanged(TextEvent e) { updateCharCount(e); } }); editor.addCaretListener(new CaretListener() { public void caretUpdate(CaretEvent e) { updateCaretSelection(e); } }); pane.addTab(editor.getDisplayName(), editor.getDisplayIcon(), editor); editors.addElement(editor); } } // While article has more properties } /** * Creates the user interface for the editor. */ protected void createUI() { createActions(); setJMenuBar(createMenu()); setGlassPane(easter); // toolBar.setFont(new Font("sans-serif", 9, Font.PLAIN)); leftPane = new JTabbedPane(JTabbedPane.TOP); rightPane = new JTabbedPane(JTabbedPane.TOP); splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); splitPane.setOneTouchExpandable(true); // splitPane.setContinuousLayout(true); charCountLabel = new JLabel(strings.getString("status_charcount")); statusPanel = new JPanel(); FlowLayout statusLayout = new FlowLayout(FlowLayout.LEFT); statusLayout.setHgap(10); statusPanel.setLayout(statusLayout); statusPanel.add(charCountLabel); getContentPane().setLayout(new BorderLayout()); getContentPane().add(splitPane, BorderLayout.CENTER); getContentPane().add(statusPanel, BorderLayout.SOUTH); // Set the frame's icon ImageIcon icon = new ImageIcon(getClass().getResource( strings.getString("icon_artis"))); setIconImage(icon.getImage()); setTitle(strings.getString("application_title") + " " + ApplicationLauncher.getVersion()); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { exitAction(null); } }); leftPane.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { editorChanged((Editor) leftPane.getSelectedComponent()); } }); splitPane.setDividerLocation(1.0); } /** * Creates the global toolbar for the editor. */ protected JToolBar createToolBar() { JToolBar tb = new JToolBar(); JButton sb = tb.add(saveAction); sb.setToolTipText(strings.getString("tip_save")); JButton pb = tb.add(printAction); pb.setToolTipText(strings.getString("tip_print")); // Only enable preview and PDF if java.version>1.1 if (!System.getProperty("java.version").startsWith("1.1")) { JButton pvb = tb.add(previewAction); pvb.setToolTipText(strings.getString("tip_preview")); JButton pdfb = tb.add(pdfAction); pdfb.setToolTipText(strings.getString("tip_pdf")); } tb.addSeparator(); if (copyAction != null) { copyB = tb.add(copyAction); copyB.setToolTipText(editorProps.getString("tip_copy")); SVGIcon copyIcon = initSvgIcon(getClass().getResource(strings.getString("icon_copy"))); SVGIcon copyIconDisabled = initSvgIcon(getClass().getResource(strings.getString("icon_copy_disabled"))); copyB.setIcon(copyIcon); copyB.setIcon(copyIconDisabled); } if (pasteAction != null) { pasteB = tb.add(pasteAction); pasteB.setToolTipText(editorProps.getString("tip_paste")); SVGIcon pasteIcon = initSvgIcon(getClass().getResource(strings.getString("icon_paste"))); SVGIcon pasteIconDisabled = initSvgIcon(getClass().getResource(strings.getString("icon_paste_disabled"))); pasteB.setIcon(pasteIcon); pasteB.setDisabledIcon(pasteIconDisabled); } if (cutAction != null) { cutB = tb.add(cutAction); cutB.setToolTipText(editorProps.getString("tip_cut")); SVGIcon cutIcon = initSvgIcon(getClass().getResource(strings.getString("icon_cut"))); SVGIcon cutIconDisabled = initSvgIcon(getClass().getResource(strings.getString("icon_cut_disabled"))); cutB.setIcon(cutIcon); cutB.setDisabledIcon(cutIconDisabled); } tb.addSeparator(); JButton eb = tb.add(exitAction); eb.setToolTipText(strings.getString("tip_exit")); return tb; } /** * Creates the global menu for the editor. */ protected JMenuBar createMenu() { JMenuBar menu = new JMenuBar(); JMenu file = new JMenu(strings.getString("menu_file")); menu.add(file); JMenuItem saveItem = file.add(saveAction); saveItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(83, java.awt.event.KeyEvent.CTRL_MASK, false)); JMenuItem printItem = file.add(printAction); printItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(80, java.awt.event.KeyEvent.CTRL_MASK, false)); file.add(sendMailAction); file.add(showSettingsAction); file.addSeparator(); file.add(importAction); file.add(exportAction); file.addSeparator(); statusMenu = new JMenu(strings.getString("menu_file_save_status")); SVGIcon statusIcon = initSvgIcon(getClass().getResource(strings.getString("icon_status"))); //statusMenu.setIcon(new ImageIcon(getClass().getResource( // strings.getString("icon_status")))); statusMenu.setIcon(statusIcon); statuses = null; try { statuses = LoginContext.server .getArticleStatuses(LoginContext.sessionKey); } catch (Exception e) { JOptionPane.showMessageDialog(this, "Error fetching article statuses from server.", "FATAL ERROR", JOptionPane.ERROR_MESSAGE); } ButtonGroup statusGroup = new ButtonGroup(); for (ArticleStatus statuse : statuses) { JRadioButtonMenuItem rbMenuItem = new JRadioButtonMenuItem( statuse.getName()); rbMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { statusChanged(); } }); statusGroup.add(rbMenuItem); statusMenu.add(rbMenuItem); } file.add(statusMenu); file.addSeparator(); JMenuItem exitItem = file.add(exitAction); exitItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(87, java.awt.event.KeyEvent.CTRL_MASK, false)); file.add(exitItem); editMenu = new JMenu(strings.getString("menu_edit")); menu.add(editMenu); // FIXME: This menu is empty for the time beeing. // JMenu show = new JMenu(strings.getString("menu_show")); // menu.add(show); insertMenu = new JMenu(strings.getString("menu_insert")); characterMenu = new JMenu(strings.getString("menu_chars")); insertMenu.add(characterMenu); menu.add(insertMenu); editorMenuItems = new JMenuItem[0]; editorMenuItems2 = new JMenuItem[0]; editorMenu = new JMenu(); editorMenu.setVisible(false); menu.add(editorMenu); JMenu help = new JMenu(strings.getString("menu_help")); // FIXME: Not implemented // menu.add(help); help.add(strings.getString("menu_about")); formatMenu = new JMenu(strings.getString("menu_format")); menu.add(formatMenu); return menu; } private void showSettingsDialog() { ArtisPrefsDialog dialog = new ArtisPrefsDialog(this, preferences); dialog.setVisible(true); if(dialog.okResult()) { saveChangedSettings(dialog.getChangedPreferences()); } } private void saveChangedSettings(Properties changed) { Enumeration<?> keys = changed.keys(); while(keys.hasMoreElements()) { String key = keys.nextElement().toString(); String value = changed.getProperty(key); log.info("updating pref-key: " + key + "= " + value); preferences.put(key, value); try { LoginContext.server.savePreference(PREF_DOMAIN, key, value, LoginContext.sessionKey); } catch(Exception e) { log.error("Unable to save pref: {}={}", new Object[]{key, value, e}); } if(key.equals(ArtisPrefsDialog.AUTO_SAVE_PREF)) { startAutoSaver(); } } } private void loadPreferences() { try { preferences = LoginContext.server.getPreferences(PREF_DOMAIN, LoginContext.sessionKey); } catch (Exception e) { log.error( "Exception getting preferences for domain " + PREF_DOMAIN, e); preferences = new Properties(); } } public void statusChanged() { for (int i = 0; i < statusMenu.getItemCount(); i++) { JRadioButtonMenuItem rbmi = (JRadioButtonMenuItem) statusMenu .getItem(i); if (rbmi.isSelected()) { article.setArticleStatus(statuses.get(i)); } } } public void runEaster() { easter.go(); } /** * Creates an SVGIcon that can be used by the UI. * @param iconPath the path of the svg file * @return an SVGIcon */ protected SVGIcon initSvgIcon(URL iconPath) { URI iconURI = SVGCache.getSVGUniverse().loadSVG(iconPath); SVGIcon icon = new SVGIcon(); icon.setSvgURI(iconURI); icon.setAntiAlias(true); icon.setPreferredSize(new Dimension(32,32)); icon.setScaleToFit(true); return icon; } /** * Creates the actions used by the toolbar and the menu. */ protected void createActions() { SVGIcon settingsIcon = initSvgIcon(getClass().getResource(strings.getString("icon_settings"))); showSettingsAction = new AbstractAction(strings.getString("action_settings"), settingsIcon) { public void actionPerformed(ActionEvent e) { showSettingsDialog(); } }; showSettingsAction.setEnabled(true); SVGIcon saveIcon = initSvgIcon(getClass().getResource(strings.getString("icon_save"))); saveAction = new AbstractAction(strings.getString("action_save"), saveIcon) { //new ImageIcon(getClass().getResource( // strings.getString("icon_save")))) { public void actionPerformed(ActionEvent e) { saveAction(); } }; saveAction.setEnabled(false); SVGIcon importIcon = initSvgIcon(getClass().getResource(strings.getString("icon_import"))); importAction = new AbstractAction(strings.getString("action_import"), importIcon) { //new ImageIcon(getClass().getResource( // strings.getString("icon_import")))) { public void actionPerformed(ActionEvent e) { importAction(e); } }; SVGIcon exportIcon = initSvgIcon(getClass().getResource(strings.getString("icon_export"))); exportAction = new AbstractAction(strings.getString("action_export"), exportIcon) { //new ImageIcon(getClass().getResource( // strings.getString("icon_export")))) { public void actionPerformed(ActionEvent e) { exportAction(e); } }; SVGIcon printIcon = initSvgIcon(getClass().getResource(strings.getString("icon_print"))); printAction = new AbstractAction(strings.getString("action_print"), printIcon) { //new ImageIcon(getClass().getResource( // strings.getString("icon_print")))) { public void actionPerformed(ActionEvent e) { printAction(e); } }; SVGIcon previewIcon = initSvgIcon(getClass().getResource(strings.getString("icon_preview"))); previewAction = new AbstractAction(strings.getString("action_preview"), previewIcon) { //new ImageIcon(getClass().getResource( // strings.getString("icon_preview")))) { public void actionPerformed(ActionEvent e) { previewArticleFOP(); } }; SVGIcon pdfIcon = initSvgIcon(getClass().getResource(strings.getString("icon_pdf"))); pdfAction = new AbstractAction(strings.getString("action_pdf"), pdfIcon) { //new ImageIcon(getClass().getResource( // strings.getString("icon_pdf")))) { public void actionPerformed(ActionEvent e) { saveArticleAsPDF(); } }; SVGIcon mailIcon = initSvgIcon(getClass().getResource(strings.getString("icon_sendmail"))); sendMailAction = new AbstractAction(strings.getString("action_sendmail"), mailIcon) { //new ImageIcon(getClass().getResource( // strings.getString("icon_sendmail")))) { public void actionPerformed(ActionEvent e) { mailArticleText(); } }; SVGIcon exitIcon = initSvgIcon(getClass().getResource(strings.getString("icon_exit"))); exitAction = new AbstractAction(strings.getString("action_exit"), exitIcon) { //new ImageIcon(getClass().getResource( // strings.getString("icon_exit")))) { public void actionPerformed(ActionEvent e) { exitAction(e); } }; } /** * Called when the current tab changes. */ protected void editorChanged(Editor ed) { log.debug("Ed: {}", ed); if (ed == null) { log.warn("The new tab is NULL, returning..."); return; } // Remove old menu items editMenu.removeAll(); formatMenu.removeAll(); // Insert new menu items AbstractAction[] actions = ed.getEditActions(); AbstractAction[] actions2 = ed.getEditActions2(); AbstractAction[] styleActions = ed.getStyleActions(); AbstractAction[] formatActions = ed.getFormatActions(); AbstractAction autoCorrectAction = ed.getAutoCorrectAction(); editorMenuItems = new JMenuItem[actions.length]; editorMenuItems2 = new JMenuItem[actions2.length + 1]; formatMenuItems = new JMenuItem[styleActions.length]; // Add edit actions for (int i = 0; i < actions.length; i++) { editorMenuItems[i] = editMenu.add(actions[i]); editorMenuItems[i].setAccelerator((KeyStroke) actions[i] .getValue(AbstractAction.ACCELERATOR_KEY)); if (actions[i].getValue(Action.NAME).equals( editorProps.getString("action_copy"))) { copyAction = actions[i]; } else if (actions[i].getValue(Action.NAME).equals( editorProps.getString("action_paste"))) { pasteAction = actions[i]; } else if (actions[i].getValue(Action.NAME).equals( editorProps.getString("action_cut"))) { cutAction = actions[i]; } } if (actions2.length != 0) { editMenu.add(new JSeparator()); for (int i = 0; i < actions2.length; i++) { editorMenuItems2[i] = editMenu.add(actions2[i]); editorMenuItems2[i].setAccelerator((KeyStroke) actions2[i] .getValue(AbstractAction.ACCELERATOR_KEY)); } } if (autoCorrectAction != null) { editMenu.add(new JSeparator()); JCheckBoxMenuItem item = new JCheckBoxMenuItem(autoCorrectAction); item.setState(((AbstractTextEditor) (ed)).getAutoCorrectionState()); editMenu.add(item); } if (formatActions.length != 0) { formatMenu.setEnabled(true); for (int i = 0; i < formatActions.length; i++) { log.debug("fmi: " + i + " " + formatActions[i]); formatMenuItems[i] = formatMenu.add(formatActions[i]); // formatMenuItems[i].setAccelerator((KeyStroke)styleActions[i].getValue(AbstractAction.ACCELERATOR_KEY)); } } else { formatMenu.setEnabled(false); } // Add style actions if (styleActions.length != 0) { formatMenu.add(new JSeparator()); for (int i = 0; i < styleActions.length; ++i) { formatMenuItems[i] = formatMenu.add(styleActions[i]); formatMenuItems[i].setAccelerator((KeyStroke) styleActions[i] .getValue(AbstractAction.ACCELERATOR_KEY)); } } if (characterMenuItems == null) { AbstractAction[] specialChars = ed.getCharacterActions(); characterMenuItems = new JMenuItem[specialChars.length]; for (int i = 0; i < specialChars.length; i++) { characterMenuItems[i] = characterMenu.add(specialChars[i]); characterMenuItems[i] .setAccelerator((KeyStroke) specialChars[i] .getValue(AbstractAction.ACCELERATOR_KEY)); } } if (toolBar != null) getContentPane().remove(toolBar); toolBar = createToolBar(); getContentPane().add(toolBar, BorderLayout.NORTH); validate(); // Needed or the toolbar becomes inaccessible until window // is resized. } public void mailArticleText() { mailArticleText(""); } public void mailArticleText(String to) { String articleText, fullName, emailAddress; articleText = ""; fullName = ""; emailAddress = ""; for (int i = 0; i < editors.size(); i++) { if (editors.elementAt(i) instanceof AbstractTextEditor) { articleText = ((AbstractTextEditor) editors.elementAt(i)).getText(); articleText = articleText.replace('\u2013', '-'); // Dash articleText = articleText.replace('\u2022', '*'); // Bullet break; } } try { Person u = LoginContext.server .getSessionUser(LoginContext.sessionKey); fullName = u.getName(); emailAddress = u.getEmailAddress(); } catch (RemoteException e) { log.error("Could not get user data from server", e); } String url; try { url = LoginContext.server.getWebXMLRoot(LoginContext.sessionKey); url += "/templates/articlemail.template"; } catch (RemoteException e) { log.error("Error getting mailtemplate from server", e); return; } Object[] replaceWith = { fullName, emailAddress, articleText, to }; MailDialog dialog = new MailDialog(this, "", false); dialog.loadTemplate(url, replaceWith); dialog.setFromFieldEditable(false); dialog.setToFieldEditable(true); dialog.setToLabelActive(true); dialog.pack(); dialog.setVisible(true); } private void handleException(Exception e){ log.error("Error during transformation.", e); if (e instanceof IOException) { JOptionPane.showMessageDialog(this, strings.getString("transform_error_save"), strings.getString("transform_error_save_captition"), JOptionPane.ERROR_MESSAGE); }else { JOptionPane.showMessageDialog(this, strings.getString("transform_error_general"), strings.getString("transform_error_captition"), JOptionPane.ERROR_MESSAGE); } } public void saveArticleAsPDF() { setCursor(new Cursor(Cursor.WAIT_CURSOR)); Properties p = getPrintProperties(); JFileChooser chooser = new JFileChooser(); chooser.setSelectedFile(new File(article.suggestFileName(16) +".pdf")); File file; int returnVal = chooser.showSaveDialog(this); if(returnVal == JFileChooser.APPROVE_OPTION) { file = chooser.getSelectedFile(); } else{ return; } if (p == null) { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); return; } updateXML(); FOPPDFPublisher pdf = new FOPPDFPublisher(); pdf.setTransformerProperties(p); try { pdf.publish(article, file, PublishingMediaEnum.PDF); } catch (Exception e) { handleException(e); } setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } public void previewArticleFOP() { setCursor(new Cursor(Cursor.WAIT_CURSOR)); Properties p = getPrintProperties(); if (p == null) { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); return; } updateXML(); FOPAWTPublisher fpp = new FOPAWTPublisher(); fpp.setTransformerProperties(p); try { fpp.publish(article, null, PublishingMediaEnum.PDF); } catch (Exception e) { handleException(e); } setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } public void printArticleFOP() { setCursor(new Cursor(Cursor.WAIT_CURSOR)); FOPPrintPublisher fpp = new FOPPrintPublisher(); Properties p = getPrintProperties(); if (p == null) { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); return; } updateXML(); fpp.setTransformerProperties(p); try { fpp.publish(article, null, PublishingMediaEnum.PDF); } catch (Exception e) { handleException(e); } setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } public Properties getPrintProperties() { PrintDialog printDialog = new PrintDialog(this, strings); printDialog.pack(); printDialog.setVisible(true); if (!printDialog.wasAccepted()) // Was cancel pressed? return null; return printDialog.getPrintProperties(); } /** * Called when "print" is selected from the toolbar or the menu. * * @param e * The action that triggered this event. */ protected void printAction(ActionEvent e) { if (article.getDocument() == null) article.parseText(); printArticleFOP(); } public void updateXML() { for(Editor editor : editors) { editor.updateXML(); } } /** * Called when "import" is selected for the toolbar menu * * @param e * The action that triggered this event. */ protected void importAction(ActionEvent e) { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { // FIXME: Due to trouble when fetching text only and not // notes, the option to not import notes have temporarily // been disabled (handegar) boolean importNotes = true; // Ask user if notes should be imported as well. /* * boolean importNotes = false; int answer = * JOptionPane.showConfirmDialog(this, * strings.getString("import_elements_query"), * strings.getString("action_import"),JOptionPane.YES_NO_OPTION); * if(answer == JOptionPane.YES_OPTION) importNotes = true; */ // Start file loading setCursor(new Cursor(Cursor.WAIT_CURSOR)); File file; file = chooser.getSelectedFile(); if (!file.exists()) { log.error("Could not find specified file '" + chooser.getSelectedFile().getName() + "'"); setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); JOptionPane.showMessageDialog(this, strings .getString("import_filenotfound")); return; } DocumentBuilderFactory factory = DocumentBuilderFactory .newInstance(); DocumentBuilder builder = null; try { factory.setIgnoringElementContentWhitespace(false); builder = factory.newDocumentBuilder(); } catch (ParserConfigurationException pce) { log.error("Parser configuration exception", pce); } FileInputStream stringReader; try { stringReader = new FileInputStream(file); } catch (FileNotFoundException ex) { log.error("Could not find specified file", ex); setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); JOptionPane.showMessageDialog(this, strings .getString("import_filenotfound")); return; } Document importDoc; try { importDoc = builder.parse(new InputSource(stringReader)); } catch (SAXException se) { log.error("couldn't parse text", se); return; } catch (java.io.IOException ioe) { log.error("io-exc", ioe); return; } NotesEditor notesEditor = null; // Lookup the notes editor for (int i = 0; i < editors.size(); i++) { Object ed = editors.elementAt(i); if (ed instanceof NotesEditor) { notesEditor = (NotesEditor) ed; break; } } if (notesEditor == null) return; notesEditor.closeAllNotes(); // === Process new XML content Element notesElem; NodeList notesElements = importDoc.getDocumentElement() .getElementsByTagName("notes"); if (notesElements.getLength() != 0) { notesElem = (Element) notesElements.item(0); } else { // Create the notes element notesElem = article.getDocument().createElement("notes"); article.getDocument().getDocumentElement().appendChild( notesElem); } NodeList nodelist = importDoc.getDocumentElement() .getElementsByTagName("text"); if (nodelist.getLength() != 0) { Element xmlElem = (Element) nodelist.item(0); for (int i = 0; i < editors.size(); i++) { if (editors.elementAt(i).getClass() == AbstractTextEditor.class) { editors.elementAt(i).setXML(xmlElem); } // Only add XML for notes if user agree if (editors.elementAt(i).getClass() == NotesEditor.class) { editors.elementAt(i).setXML(notesElem); } } } else { setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); JOptionPane.showMessageDialog(this, strings .getString("import_failed")); // Failure return; } // FIXME: This does not separate notes and text. Must be // changed later (handegar) article.setDoc(importDoc); updateCharCount(null); for (int i = 0; i < editors.size(); i++) editors.elementAt(i).setChanged(true); updateXML(); setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } } /** * Called when "export" is selected for the toolbar menu * * @param e * The action that triggered this event. */ public void exportAction(ActionEvent e) { // Giving 'null' as argument forcing a FileDialog to be created. updateXML(); try { fileSave(null); } catch (IOException ex) { JOptionPane.showMessageDialog(this, strings .getString("export_failed")); // Failure log.error("Cannot export article", ex); } } /** * Return the article that is open in this Artis */ public Article getArticle() { return article; } /** * Called when "save" is selected from the toolbar or the menu. * */ protected void saveAction() { this.setCursor(new Cursor(Cursor.WAIT_CURSOR)); ArticleLock lock = null; try { lock = LoginContext.server.getArticleLock(article.getId(), false, LoginContext.sessionKey); } catch (RemoteException rme) { log.error("Error getting article lock", rme); } if (firstLockTime == lock.getFirstLocked()) { article.setLastSaved(new Date()); updateXML(); for (int i = 0; i < editors.size(); i++) { editors.elementAt(i).setChanged(false); } try { article.serialize(); LoginContext.server.ping(); LoginContext.server.saveArticle((Article) article.clone(), LoginContext.sessionKey); LoginContext.server.saveArticleText(article.getId(), article .getText(), article.getCurrentNumberOfCharacters(), LoginContext.sessionKey); } catch (Exception rme) { log.error("Error saving to database", rme); // Prompt for file save int fileSave; fileSave = JOptionPane.showConfirmDialog(this, strings .getString("server_save_error"), strings .getString("server_error"), JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); if (fileSave == JOptionPane.YES_OPTION) { // Try to save to file try { fileSave(article.suggestFileName(20) + ".xml"); } catch (IOException ioe) { this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); log.error("ERROR saving article!", ioe); JOptionPane.showMessageDialog(this, strings .getString("file_save_error"), strings .getString("file_error"), JOptionPane.ERROR_MESSAGE); } } } log.info("Article saved."); this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } else { // Fetched lock was not original lock! We will NOT save! String hostname; try { InetAddress address = InetAddress.getByName(lock.getHost()); hostname = address.getHostName(); } catch (UnknownHostException ex) { hostname = lock.getHost(); } String rawmessage = strings.getString("locked_error_long"); Object[] args = { hostname }; String message = MessageFormat.format(rawmessage, args); int fileSave; fileSave = JOptionPane.showConfirmDialog(this, message, strings .getString("locked_error"), JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); if (fileSave == JOptionPane.YES_OPTION) { // Try to save to file try { fileSave(null); } catch (IOException ioe) { this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); log.error("ERROR saving article!", ioe); JOptionPane .showMessageDialog(this, strings .getString("file_save_error"), strings .getString("file_error"), JOptionPane.ERROR_MESSAGE); } } this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } } /** * Saves this article to a file. If filename is <code>null</code> the user * will be presented with a filedialog. * * @return <code>true</code> if the saving was successfull. * @throws IOException * Any error during file handling. */ protected boolean fileSave(String fileName) throws IOException { if (fileName == null) { fileName = getFileName(); if (fileName == null) { return false; } } FileOutputStream fos; try { log.info("Saving file to: " + fileName); fos = new FileOutputStream(fileName); } catch (FileNotFoundException fnfe) { log.error("File not found (illegal file name?)"); return false; } try { Transformer t = TransformerFactory.newInstance().newTransformer(); t.transform(new DOMSource(article.getDocument()), new StreamResult( fos)); } catch (TransformerException e) { log.error("Error transforming article", e); } return true; } /** * Called to get a possibly new file name for an article. * * @return A file name, or <code>null</code> if the user pressed cancel in * the file dialog. */ protected String getFileName() { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showSaveDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); return file.getAbsolutePath(); } else { return null; } } /** * Called when "exit" is selected from the toolbar or the menu. * * @param e * The event for the action. */ protected void exitAction(ActionEvent e) { if(autoSaveTimer != null) autoSaveTimer.cancel(); boolean articleChanged = false; for (int i = 0; i < editors.size(); ++i) { Editor ed = editors.elementAt(i); if (ed.isChanged()) articleChanged = true; } if(lockChecker != null && lockChecker.isRunning()) { lockChecker.setRunCheck(false); articleChanged = false; // There should never be any changes in preview mode, but just is case.. } if (articleChanged) { int save = JOptionPane.showConfirmDialog(this, strings .getString("confirm_exit_save"), strings .getString("confirm_exit"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); if (save == JOptionPane.NO_OPTION) { // Set document to null to ensure unsaved changes are not cached article.setDoc(null); try { LoginContext.server.releaseArticleLock(article.getId(), LoginContext.sessionKey); } catch (RemoteException rex) { log.error("Cannot release article lock", rex); } dispose(); } else if (save == JOptionPane.YES_OPTION) { saveAction(); try { LoginContext.server.releaseArticleLock(article.getId(), LoginContext.sessionKey); } catch (RemoteException rex) { log.error("Error releasing article lock", rex); } dispose(); } } else { try { LoginContext.server.releaseArticleLock(article.getId(), LoginContext.sessionKey); } catch (RemoteException rex) { log.error("Error releasing article lock", rex); } dispose(); } } /** * Updates the character count status display. This method is called when * the text in one of the editors change. * * @param e * The event from the editor. */ protected void updateCharCount(TextEvent e) { chars = 0; for (int i = 0; i < editors.size(); i++) { chars += editors.elementAt(i).getLength(); } article.setCurrentNumberOfCharacters(chars); updateCharCountLabel(); } protected void updateCharCountLabel() { String text = strings.getString("status_charcount") + " " + chars; if (charsSelected > 0) text = text + " (" + charsSelected + ")"; charCountLabel.setText(text); } protected void updateCaretSelection(CaretEvent e) { int dot = e.getDot(); int mark = e.getMark(); charsSelected = Math.abs(dot - mark); updateCharCountLabel(); } public static class PrintDialog extends JDialog { JFrame frame; ResourceBundle strings; GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints constraints = new GridBagConstraints(); Container container = this.getContentPane(); boolean accepted = false; JCheckBox[] checkBox = new JCheckBox[3]; public PrintDialog(JFrame frame, ResourceBundle strings) { super(frame, strings.getString("print_dialog_title"), true); this.strings = strings; createUI(); // FIXME: this breaks JVM 1.1 support. bjorsnos // setLocationRelativeTo(frame); Point p = frame.getLocation(); this.setLocation(p.x + 50, p.y + 50); } private void createUI() { setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); checkBox[0] = new JCheckBox(); checkBox[1] = new JCheckBox(); checkBox[2] = new JCheckBox(); checkBox[0].setText(strings.getString("print_element_article")); checkBox[0].setSelected(true); checkBox[1].setText(strings.getString("print_element_notes")); checkBox[2].setText(strings.getString("print_element_storysketch")); JButton okButton = new JButton(strings.getString("button_ok")); JButton cancelButton = new JButton(strings .getString("button_cancel")); container.setLayout(gridbag); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(5, 5, 0, 5); add(checkBox[0], 0, 0, 0); add(checkBox[1], 0, 1, 1); add(checkBox[2], 0, 2, 1); constraints.insets = new Insets(5, 5, 5, 5); constraints.anchor = GridBagConstraints.WEST; add(okButton, 0, 3, 0); constraints.anchor = GridBagConstraints.EAST; add(cancelButton, 1, 3, 0); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { okPerformed(); } }); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cancelPerformed(); } }); } private void add(Component component, int x, int y, int weight) { constraints.gridx = x; constraints.gridy = y; constraints.weightx = weight; gridbag.setConstraints(component, constraints); container.add(component); } private void cancelPerformed() { this.accepted = false; PrintDialog.this.dispose(); } private void okPerformed() { this.accepted = true; PrintDialog.this.dispose(); } public boolean wasAccepted() { return this.accepted; } public Properties getPrintProperties() { Properties p = new Properties(); if (checkBox[0].isSelected()) p.put("printarticle", "true"); if (checkBox[1].isSelected()) { p.put("printnotes", "true"); } if (checkBox[2].isSelected()) { p.put("printstorysketch", "true"); } return p; } } private class AutoSaver extends TimerTask { static final long oneMinute = 60 * 1000; public void run() { if(System.currentTimeMillis() - article.getLastSaved().getTime() > oneMinute) { log.info("auto-saving"); saveAction(); } } } public boolean canEditArticle() { return canEditArticle; } private class LockChecker implements Runnable { private boolean runCheck; private Artis artis; public LockChecker(Artis artis) { this.artis = artis; } public void run() { runCheck = true; ArticleLock lock = null; int articleId = article.getId(); try { lock = LoginContext.server.getArticleLock(articleId , false, LoginContext.sessionKey); } catch (RemoteException rme) { log.error("Could not get lock", rme); } boolean sameSessionKey = lock.getSessionKey().equals(LoginContext.sessionKey); while(!sameSessionKey) { try { Thread.sleep(10000); // 10 sec if(!runCheck) { return; } lock = LoginContext.server.getArticleLock(articleId , false, LoginContext.sessionKey); sameSessionKey = lock.getSessionKey().equals(LoginContext.sessionKey); } catch(InterruptedException ie) { log.error("Interrupted", ie); return; } catch(RemoteException rme) { log.error("ConnectionError", rme); } } log.debug("same session key detected"); artis.requestFocus(); int openNew = JOptionPane.showConfirmDialog(artis, strings.getString("lock_acquired_long"), strings.getString("lock_acquired"), JOptionPane.YES_NO_OPTION); if(openNew == JOptionPane.NO_OPTION) { try { LoginContext.server.releaseArticleLock(article.getId(), LoginContext.sessionKey); } catch (RemoteException rex) { log.error("Cannot release article lock", rex); } } else if(openNew == JOptionPane.YES_OPTION) { Article fresh; try { fresh = LoginContext.server.getArticleByID(article.getId(), LoginContext.sessionKey); } catch (Exception e) { log.error("Could not get article from server!", e); return; } Artis anArtis = new Artis(lock); anArtis.openArticle(fresh); anArtis.setSize(new Dimension((int) (getSize().getWidth()), (int) (getSize().getHeight()))); Point location = getLocation(); location.translate(20, 20); anArtis.setLocation(location); dispose(); anArtis.startAutoSaver(); anArtis.setVisible(true); } } void setRunCheck(boolean run) { runCheck = run; } boolean isRunning() { return runCheck; } } }