diff --git a/core/core-awt/src/main/java/org/icepdf/core/pobjects/fonts/zfont/cmap/CMap.java b/core/core-awt/src/main/java/org/icepdf/core/pobjects/fonts/zfont/cmap/CMap.java index 354cb791f..004d46ea5 100644 --- a/core/core-awt/src/main/java/org/icepdf/core/pobjects/fonts/zfont/cmap/CMap.java +++ b/core/core-awt/src/main/java/org/icepdf/core/pobjects/fonts/zfont/cmap/CMap.java @@ -512,7 +512,7 @@ public synchronized void init() { try { cMapInputStream.close(); } catch (IOException e) { - logger.log(Level.FINE, "Error clossing cmap stream", e); + logger.log(Level.FINE, "Error closing cmap stream", e); } } } diff --git a/pom.xml b/pom.xml index e2efd64f3..5a0597cf4 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 3.0.4 2.0.27 5.9.3 + 1.2 @@ -167,6 +168,10 @@ org.apache.maven.plugins maven-compiler-plugin 3.7.0 + + 11 + 11 + org.codehaus.mojo diff --git a/viewer/viewer-awt/build.gradle b/viewer/viewer-awt/build.gradle index f63835401..7bcf6ed97 100644 --- a/viewer/viewer-awt/build.gradle +++ b/viewer/viewer-awt/build.gradle @@ -22,6 +22,10 @@ dependencies { implementation 'org.bouncycastle:bcprov-jdk15on:' + "${BOUNCY_VERSION}" implementation 'org.bouncycastle:bcprov-ext-jdk15on:' + "${BOUNCY_VERSION}" implementation 'org.bouncycastle:bcpkix-jdk15on:' + "${BOUNCY_VERSION}" + compile 'com.github.lookfirst:sardine:5.12' + compile 'org.apache.tika:tika-core:1.23' + compile 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.3' + compile 'org.glassfish.jaxb:jaxb-runtime:2.3.3' } // generatePomFileForViewerJarPublication @@ -101,4 +105,4 @@ task javadocJar(type: Jar, dependsOn: 'javadoc') { artifacts { archives sourcesJar archives javadocJar -} +} \ No newline at end of file diff --git a/viewer/viewer-awt/pom.xml b/viewer/viewer-awt/pom.xml index bfefcaa23..4d5a46a59 100644 --- a/viewer/viewer-awt/pom.xml +++ b/viewer/viewer-awt/pom.xml @@ -1,56 +1,78 @@ - - - 4.0.0 - - com.github.pcorless.icepdf - viewer - 7.2.0-SNAPSHOT - - icepdf-viewer - jar - ICEpdf :: Viewer : Swing/AWT Viewer RI - - ICEpdf Java Swing/AWT reference implementation. - - - - - com.github.pcorless.icepdf - icepdf-core - - - - - - assembly - - - - maven-assembly-plugin - - - - org.icepdf.ri.viewer.Launcher - - - - jar-with-dependencies - - - - - make-assembly - - package - - - single - - - - - - - - - + + + 4.0.0 + + com.github.pcorless.icepdf + viewer + 7.2.0-SNAPSHOT + + icepdf-viewer + jar + ICEpdf :: Viewer : Swing/AWT Viewer RI + + ICEpdf Java Swing/AWT reference implementation. + + + + + com.github.pcorless.icepdf + icepdf-core + + + com.github.lookfirst + sardine + 5.12 + + + org.apache.tika + tika-core + 1.23 + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.3 + + + org.glassfish.jaxb + jaxb-runtime + 2.3.3 + + + + + + assembly + + + + maven-assembly-plugin + + + + org.icepdf.ri.viewer.Launcher + + + + jar-with-dependencies + + + + + make-assembly + + package + + + single + + + + + + + + + + + diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/KeyEventConstants.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/KeyEventConstants.java index 6b06cef90..4e4c884ca 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/KeyEventConstants.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/KeyEventConstants.java @@ -33,6 +33,8 @@ public class KeyEventConstants { public static final int MODIFIER_OPEN_FILE = MENU_SHORTCUT_KEY_MASK; public static final int KEY_CODE_OPEN_URL = KeyEvent.VK_U; public static final int MODIFIER_OPEN_URL = MENU_SHORTCUT_KEY_MASK; + public static final int KEY_CODE_OPEN_DAV = KEY_CODE_OPEN_URL; + public static final int MODIFIER_OPEN_DAV = MENU_SHORTCUT_KEY_MASK | InputEvent.ALT_MASK; public static final int KEY_CODE_CLOSE = KeyEvent.VK_W; public static final int MODIFIER_CLOSE = MENU_SHORTCUT_KEY_MASK; public static final int KEY_CODE_SAVE = KeyEvent.VK_S; diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java index 566e175c6..3734ead8a 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingController.java @@ -15,7 +15,9 @@ */ package org.icepdf.ri.common; +import com.github.sardine.impl.SardineException; import org.icepdf.core.SecurityCallback; +import org.icepdf.core.exceptions.PDFException; import org.icepdf.core.exceptions.PDFSecurityException; import org.icepdf.core.io.SizeInputStream; import org.icepdf.core.pobjects.*; @@ -82,6 +84,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -146,6 +149,7 @@ public class SwingController extends ComponentAdapter private JMenuItem openFileMenuItem; private JMenu recentFilesSubMenu; private JMenuItem openURLMenuItem; + private JMenuItem openDavMenuItem; private JMenuItem closeMenuItem; private JMenuItem saveFileMenuItem; private JMenuItem saveAsFileMenuItem; @@ -280,7 +284,7 @@ public class SwingController extends ComponentAdapter // sub controller for document text searching. protected DocumentSearchController documentSearchController; - + private DavFileClient pdfClient; protected Document document; protected boolean disposed; @@ -432,6 +436,16 @@ public void setOpenURLMenuItem(JMenuItem mi) { mi.addActionListener(this); } + /** + * Called by SwingViewerBuilder, so that Controller can setup event handling + * + * @param mi menu item to assign + */ + public void setOpenDavMenuItem(JMenuItem mi) { + openDavMenuItem = mi; + mi.addActionListener(this); + } + /** * Called by SwingViewerBuilder, so that Controller can setup event handling * @@ -2570,7 +2584,8 @@ public void openDocument(String pathname) { private static String getTempSaveFileName(final String originalFilePath) { final String separator = File.separator; - final String[] pathSplit = originalFilePath.split(Pattern.quote(separator)); + // Also forward slash for url (webdav) + final String[] pathSplit = originalFilePath.split(Pattern.quote(separator) + "|/"); final String name = pathSplit[pathSplit.length - 1]; final String[] dotSplit = name.split("\\."); final String basename = getBasename(dotSplit); @@ -2750,6 +2765,138 @@ protected Object doInBackground() throws Exception { } } + /** + * Utility method for opening a WebDav URL. Shows a dialog for the user to type + * what URL to open + */ + public void openDav() { + String davLocation = ((ViewModel.getDefaultDav() != null) ? ViewModel.getDefaultDav() : ""); + // display webdav url input dialog + String url = (String) JOptionPane.showInputDialog( + viewer, + "WebDav URL:", + "Open WebDav URL", + JOptionPane.QUESTION_MESSAGE, + null, + null, + davLocation); + if (url != null) { + if (viewer != null) { + viewer.toFront(); + viewer.requestFocus(); + } + openDavInSomeViewer(url); + ViewModel.setDefaultDav(url); + } + } + + private void openDavInSomeViewer(final String url) { + final DavFileClient client = new DavFileClient(UriUtils.encodePath(url, StandardCharsets.UTF_8)); + if (document == null) { + openDocument(client); + } else if (windowManagementCallback != null) { + int oldTool = SwingController.this.getDocumentViewToolMode(); + setDisplayTool(DocumentViewModelImpl.DISPLAY_TOOL_WAIT); + try { + windowManagementCallback.newWindow(client); + } finally { + setDisplayTool(oldTool); + } + } + } + + + /** + * Opens a document specified by the DavFileClient. Asks the user their password if needed + * + * @param pdfDavClient The client + */ + public void openDocument(final DavFileClient pdfDavClient) { + pdfClient = pdfDavClient; + if (pdfClient.username() == null || pdfClient.username().isEmpty()) { + pdfClient.setUsername(System.getProperty("user.name")); + } + if (pdfClient.password() == null) { + JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(0, 0, 2, 1, 1, 1, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + JLabel mainLabel = new JLabel(MessageFormat.format(messageBundle.getString("viewer.dialog.dav.credentials" + + ".label"), pdfClient.name())); + JLabel userLabel = new JLabel(messageBundle.getString("viewer.dialog.dav.user.label")); + JTextField userField = new JTextField(pdfClient.username()); + JLabel passwordLabel = new JLabel(messageBundle.getString("viewer.dialog.dav.password.label")); + JPasswordField passwordField = new JPasswordField(); + panel.add(mainLabel, constraints); + constraints.gridy = 1; + constraints.gridwidth = 1; + panel.add(userLabel, constraints); + constraints.gridx = 1; + panel.add(userField, constraints); + constraints.gridx = 0; + constraints.gridy = 2; + panel.add(passwordLabel, constraints); + constraints.gridx = 1; + panel.add(passwordField, constraints); + String[] options = {messageBundle.getString("viewer.dialog.dav.credentials.button.ok"), + messageBundle.getString("viewer.dialog.dav.credentials.button.cancel")}; + int option = JOptionPane.showOptionDialog(getViewerFrame(), panel, messageBundle.getString("viewer.dialog.dav.credentials.title"), + JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE, + null, options, options[0]); + if (option == JOptionPane.OK_OPTION) { + pdfClient.setUsername(userField.getText()); + pdfClient.setPassword(new String(passwordField.getPassword())); + } else if (option == JOptionPane.NO_OPTION) { + return; + } + } + try { + openDavDocument(); + } catch (SardineException e) { + SwingUtilities.invokeLater(() -> { + org.icepdf.ri.util.Resources.showMessageDialog( + viewer, + JOptionPane.INFORMATION_MESSAGE, + messageBundle, + "viewer.dialog.sardine.exception.title", + "viewer.dialog.sardine.exception.msg", + e.getMessage() != null ? e.getMessage() : e.toString()); + //401 is probably wrong password + if (e.getStatusCode() == 401) { + pdfClient.setPassword(null); + openDocument(pdfClient); + } + }); + } catch (IOException | PDFException | PDFSecurityException e) { + SwingUtilities.invokeLater(() -> org.icepdf.ri.util.Resources.showMessageDialog( + viewer, + JOptionPane.INFORMATION_MESSAGE, + messageBundle, + "viewer.dialog.dav.exception.title", + "viewer.dialog.dav.exception.msg", + e.getMessage() != null ? e.getMessage() : e.toString())); + } finally { + setDisplayTool(DocumentViewModelImpl.DISPLAY_TOOL_PAN); + } + } + + protected void openDavDocument() throws IOException, PDFException, PDFSecurityException { + if (pdfClient.exists()) { + final InputStream stream = pdfClient.getContent(); + openDocument(new BufferedInputStream(stream), pdfClient.url(), pdfClient.url()); + } else { + org.icepdf.ri.util.Resources.showMessageDialog( + viewer, + JOptionPane.INFORMATION_MESSAGE, + messageBundle, + "viewer.dialog.dav.notfound.title", + "viewer.dialog.dav.notfound.msg", + pdfClient.url() + ); + } + } + + /** * Opens a Document via the specified InputStream. This method is a convenience method provided for * backwards compatibility. @@ -3133,7 +3280,9 @@ public void commonNewDocumentHandling(String fileDescription) { title = document.getInfo().getTitle(); } String filename = f.exists() ? f.getName() : fileDescription; - Object[] messageArguments = title == null ? new String[]{filename} : new String[]{title, filename}; + final String fileTitle = pdfClient == null ? filename : pdfClient.name(); + + Object[] messageArguments = title == null ? new String[]{fileTitle} : new String[]{title, fileTitle}; String titleResource = title == null ? "notitle" : "default"; MessageFormat formatter = new MessageFormat(messageBundle.getString("viewer.window.title.open." + titleResource)); @@ -3258,6 +3407,7 @@ public void dispose() { openFileMenuItem = null; openURLMenuItem = null; + openDavMenuItem = null; closeMenuItem = null; saveAsFileMenuItem = null; sendMailMenuItem = null; @@ -3431,29 +3581,46 @@ public void dispose() { * when the window is closed. */ public void saveFile() { - if (IS_READONLY) return; - if (document.getStateManager().isChange() && - saveFilePath != null && - !saveFilePath.isEmpty()) { - File out = new File(saveFilePath); - if (out.getParentFile() != null) { - if (Files.isWritable(out.getParentFile().toPath())) { - try (OutputStream stream = new BufferedOutputStream(new FileOutputStream(out))) { - document.saveToOutputStream(stream); - stream.flush(); - document.getStateManager().setChangesSnapshot(); - } catch (IOException e) { - logger.log(Level.FINE, "IO Exception ", e); + if (!IS_READONLY && document.getStateManager().isChange()) { + if (pdfClient != null) { + try { + final PipedInputStream in = new PipedInputStream(); + new Thread(() -> { + try (final PipedOutputStream out = new PipedOutputStream(in)) { + document.saveToOutputStream(out); + } catch (final IOException e) { + logger.log(Level.WARNING, e, () -> "Error writing to output"); + } + }).start(); + pdfClient.save(in); + document.getStateManager().setChangesSnapshot(); + } catch (IOException e) { + logger.log(Level.FINE, "IOException while saving dav", e); + saveFileAs(); + } + } else if (saveFilePath != null && + !saveFilePath.isEmpty()) { + File out = new File(saveFilePath); + if (out.getParentFile() != null) { + if (Files.isWritable(out.getParentFile().toPath())) { + try (OutputStream stream = new BufferedOutputStream(new FileOutputStream(out))) { + document.saveToOutputStream(stream); + stream.flush(); + document.getStateManager().setChangesSnapshot(); + } catch (IOException e) { + logger.log(Level.FINE, "IO Exception ", e); + } } + } else { + //Probably got loaded from an InputStream, can't simply save + saveFileAs(SaveMode.SAVE); } } else { - //Probably got loaded from an InputStream, can't simply save + // show saveAs dialog as this was legacy behaviour for the save button on the toolbar saveFileAs(SaveMode.SAVE); } - } else { - // show saveAs dialog as this was legacy behaviour for the save button on the toolbar - saveFileAs(SaveMode.SAVE); } + } /** @@ -4831,6 +4998,9 @@ public void actionPerformed(ActionEvent event) { } else if (source == openURLMenuItem) { cancelSetFocus = true; openURL(); + } else if (source == openDavMenuItem) { + cancelSetFocus = true; + openDav(); } else if (source == closeMenuItem) { boolean isCanceled = saveChangesDialog(); if (!isCanceled) { diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingViewBuilder.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingViewBuilder.java index 6b0ac2b02..9c7a4c63c 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingViewBuilder.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/SwingViewBuilder.java @@ -574,13 +574,15 @@ public JMenu buildFileMenu() { fileMenu.setMnemonic(buildMnemonic(messageBundle.getString("viewer.menu.file.mnemonic").charAt(0))); JMenuItem openFileMenuItem = buildOpenFileMenuItem(); JMenuItem openURLMenuItem = buildOpenURLMenuItem(); - if (openFileMenuItem != null && openURLMenuItem != null) { + JMenuItem openDavMenuItem = buildOpenDavMenuItem(); + if (openFileMenuItem != null && openURLMenuItem != null && openDavMenuItem != null) { JMenu openSubMenu = new JMenu(messageBundle.getString("viewer.menu.open.label")); openSubMenu.setIcon(new ImageIcon(Images.get("open_a_24.png"))); openSubMenu.setDisabledIcon(new ImageIcon(Images.get("open_i_24.png"))); openSubMenu.setRolloverIcon(new ImageIcon(Images.get("open_r_24.png"))); addToMenu(openSubMenu, openFileMenuItem); addToMenu(openSubMenu, openURLMenuItem); + addToMenu(openSubMenu, openDavMenuItem); addToMenu(fileMenu, openSubMenu); } else if (openFileMenuItem != null || openURLMenuItem != null) { addToMenu(fileMenu, openFileMenuItem); @@ -650,6 +652,15 @@ public JMenuItem buildOpenURLMenuItem() { return mi; } + public JMenuItem buildOpenDavMenuItem() { + JMenuItem mi = makeMenuItem(messageBundle.getString("viewer.menu.open.dav.label"), + buildKeyStroke(KeyEventConstants.KEY_CODE_OPEN_DAV, KeyEventConstants.MODIFIER_OPEN_DAV)); + if (viewerController != null && mi != null) { + viewerController.setOpenDavMenuItem(mi); + } + return mi; + } + public JMenuItem buildCloseMenuItem() { JMenuItem mi = makeMenuItem( messageBundle.getString("viewer.menu.close.label"), null, null, diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/ViewModel.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/ViewModel.java index 9d0a1cd22..502df1414 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/ViewModel.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/ViewModel.java @@ -36,6 +36,9 @@ public class ViewModel { // Store current URL path private static String defaultURL = null; + // Store current DAV path + private static String defaultDav = null; + // store for shrink to fit setting for Controller prints. private boolean isShrinkToPrintableArea = true; @@ -63,6 +66,10 @@ public static String getDefaultURL() { return defaultURL; } + public static String getDefaultDav() { + return defaultDav; + } + public static void setDefaultFile(File f) { defaultFile = f; } @@ -81,6 +88,14 @@ public static void setDefaultURL(String defURL) { defaultURL = defURL; } + public static void setDefaultDav(String defDav) { + if (defDav == null || defDav.isEmpty()) { + defaultDav = null; + } else { + defaultDav = defDav; + } + } + public PrintHelper getPrintHelper() { return printHelper; } diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/WindowManagementCallback.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/WindowManagementCallback.java index e60079a6b..9afedfa24 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/WindowManagementCallback.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/WindowManagementCallback.java @@ -16,6 +16,7 @@ package org.icepdf.ri.common; import org.icepdf.ri.common.views.Controller; +import org.icepdf.ri.util.DavFileClient; import org.icepdf.ri.util.ViewerPropertiesManager; import java.awt.*; @@ -38,6 +39,8 @@ public interface WindowManagementCallback { void newWindow(URL url); + void newWindow(DavFileClient fileClient); + void disposeWindow(Controller controller, Frame viewer, Preferences preferences); void minimiseAllWindows(); diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java index d98809dca..3bc1740c8 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/common/views/Controller.java @@ -22,6 +22,7 @@ import org.icepdf.ri.common.WindowManagementCallback; import org.icepdf.ri.common.print.PrintHelperFactory; import org.icepdf.ri.common.utility.outline.OutlineItemTreeNode; +import org.icepdf.ri.util.DavFileClient; import org.icepdf.ri.util.ViewerPropertiesManager; import java.awt.*; @@ -271,6 +272,13 @@ enum SaveMode { */ void openDocument(final URL location); + /** + * Opens a document specified by the given DavFileClient + * + * @param davFileClient The dav file client + */ + void openDocument(DavFileClient davFileClient); + /** * Load the specified file in a new Viewer RI window. * diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/DavFileClient.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/DavFileClient.java new file mode 100644 index 000000000..38e2ee452 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/DavFileClient.java @@ -0,0 +1,431 @@ +package org.icepdf.ri.util; + +import com.github.sardine.DavResource; +import com.github.sardine.Sardine; +import com.github.sardine.SardineFactory; +import org.apache.tika.Tika; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.time.Instant; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Represents a WebDav connection to a file + */ +public class DavFileClient implements AutoCloseable { + private static final Logger logger = Logger.getLogger(DavFileClient.class.getName()); + private static final Tika TIKA = new Tika(); + private final Sardine sardine; + private final String url; + private final String folderUrl; + private final String name; + private final String extension; + private final boolean readOnly; + private String username; + private String password; + private int revision; + private File file; + private InputStream stream; + private String mimeType; + private String lock; + + /** + * Instantiates a client with the given url and no credentials + * + * @param url The url + */ + public DavFileClient(final String url) { + this(url, null, null); + } + + /** + * Instantiates a client with the given url and credentials + * + * @param url The url + * @param username The username + * @param password The password + */ + public DavFileClient(final String url, final String username, final String password) { + this(url, username, password, false); + } + + /** + * Instantiates a client with the given url and credentials + * + * @param url The url + * @param username The username + * @param password The password + * @param readOnly Whether the client is readonly or not + */ + public DavFileClient(final String url, final String username, final String password, final boolean readOnly) { + this.url = url; + this.username = username; + this.password = password; + this.sardine = username == null || password == null ? SardineFactory.begin() : SardineFactory.begin(username, password); + final String[] split = url.split("/"); + folderUrl = Arrays.stream(split).limit(split.length - 1L).collect(Collectors.joining("/")); + final String[] names = split[split.length - 1].split("\\."); + name = names[0]; + if (names.length == 1) { + extension = ""; + } else { + extension = names[1]; + } + this.readOnly = readOnly; + setPreemptiveAuthenticationEnabled(true); + } + + public void setUsername(final String username) { + this.username = username; + } + + public void setPassword(final String password) { + this.password = password; + } + + /** + * Sets the preemptive authentication value + * Must be set to true if an action causes a NonRepeatableRequestException + * + * @param enabled true or false + */ + + public void setPreemptiveAuthenticationEnabled(final boolean enabled) { + if (enabled) { + sardine.enablePreemptiveAuthentication(url); + } else { + sardine.disablePreemptiveAuthentication(); + } + } + + /** + * @return The contents of the parent folder (including the current file) + * @throws IOException + */ + public List getParentFolderResources() throws IOException { + final String[] split = folderUrl.split("/"); + final String folderName = split[split.length - 1].isEmpty() ? split[split.length - 2] : split[split.length - 1]; + return sardine.list(folderUrl).stream().filter(dr -> !dr.getName().equals(folderName)).collect(Collectors.toList()); + } + + + public List getParentFolderContents() throws IOException { + final String[] split = folderUrl.split("/"); + final String folderName = split[split.length - 1].isEmpty() ? split[split.length - 2] : split[split.length - 1]; + final String slashedFolder = folderName.endsWith("/") ? folderName : folderName + "/"; + return getParentFolderResources().stream().map(dr -> slashedFolder + dr.getName()).collect(Collectors.toList()); + + } + + /** + * @return A temporary copy file for this connection + * @throws IOException + */ + public File getFile() throws IOException { + if (file == null) { + file = File.createTempFile(name, extension.isEmpty() ? "" : "." + extension); + try (final InputStream in = sardine.get(url)) { + Files.copy(in, file.toPath(), StandardCopyOption.REPLACE_EXISTING); + mimeType = new Tika().detect(file); + } + } + return file; + } + + /** + * @return The content stream of this connection + * @throws IOException + */ + public InputStream getContent() throws IOException { + resetStream(); + return stream; + } + + /** + * Resets the stream of this connection + * + * @throws IOException + */ + public void resetStream() throws IOException { + close(); + if (exists()) { + stream = sardine.get(url); + } + } + + /** + * Saves the given inputstream to the remote url + * + * @param inputStream The data to save + * @throws IOException + */ + public void save(final InputStream inputStream) throws IOException { + if (!readOnly) { + if (inputStream.markSupported() && inputStream.available() > 0) { + mimeType = TIKA.detect(inputStream); + } + if (mimeType == null || mimeType.equals("application/octet-stream")) { + mimeType = TIKA.detect(name + (extension.isEmpty() ? "" : "." + extension)); + } + createDirectoryHierarchy(folderUrl); + sardine.put(url, inputStream, mimeType); + revision += 1; + } + } + + + private void createDirectoryHierarchy(final String url) throws IOException { + if (!sardine.exists(url)) { + final boolean isHttps = url.startsWith("https"); + final String startUrl = isHttps ? "https://" : "http://"; + final List folderUrls = Arrays.asList(url.substring(isHttps ? 8 : 7).split("/")); + int startIdx = folderUrls.size(); + while (startIdx > 0) { + final String parentFolder = startUrl + String.join("/", folderUrls.subList(0, startIdx)); + if (sardine.exists(parentFolder)) { + break; + } + startIdx--; + } + if (startIdx < folderUrls.size()) { + for (int idx = startIdx + 1; idx <= folderUrls.size(); ++idx) { + final String toCreate = startUrl + String.join("/", folderUrls.subList(0, idx)); + sardine.createDirectory(toCreate); + } + } + } + } + + @Override + public void close() { + if (stream != null) { + try { + stream.close(); + } catch (final IOException e) { + logger.log(Level.WARNING, "Error closing stream", e); + } + } + } + + /** + * Deletes the resource + * + * @throws IOException + */ + public void delete() throws IOException { + if (!readOnly && exists()) { + sardine.delete(url); + } + } + + /** + * @return Whether the remote file exists or not + * @throws IOException + */ + public boolean exists() throws IOException { + return sardine.exists(url); + } + + /** + * Moves the remote resource to the given url. No-op if the file doesn't exist. + * + * @param destinationUrl The new url + * @param overwrite Whether to overwrite the resource or not + * @return The client managing the resource under the new url + * @throws IOException + */ + public DavFileClient move(final String destinationUrl, final boolean overwrite) throws IOException { + if (!readOnly && exists()) { + final String[] split = destinationUrl.split("/"); + createDirectoryHierarchy(Arrays.stream(split).limit(split.length - 1L).collect(Collectors.joining("/"))); + sardine.move(url, destinationUrl, overwrite); + return new DavFileClient(destinationUrl, username, password); + } else { + return this; + } + } + + + /** + * Copies the resource to the given url. No-op if the file doesn't exist. + * + * @param destinationUrl The new url + * @param overwrite Whether to overwrite or not + * @return The client managing the new resource + * @throws IOException + */ + public DavFileClient copy(final String destinationUrl, final boolean overwrite) throws IOException { + if (exists()) { + final String[] split = destinationUrl.split("/"); + createDirectoryHierarchy(Arrays.stream(split).limit(split.length - 1L).collect(Collectors.joining("/"))); + sardine.copy(url, destinationUrl, overwrite); + return new DavFileClient(destinationUrl, username, password); + } else { + return this; + } + } + + /** + * Creates the directory + * + * @throws IOException + */ + public void createDirectory() throws IOException { + if (!readOnly) { + createDirectoryHierarchy(url); + } + } + + + public boolean isFolder() throws IOException { + final String slashedFolder = folderUrl.endsWith("/") ? folderUrl : folderUrl + "/"; + for (final DavResource dr : getParentFolderResources()) { + final String url = slashedFolder + dr.getName(); + if (url.equals(this.url)) { + return dr.isDirectory(); + } + } + return false; + } + + + public boolean isFile() throws IOException { + final String slashedFolder = folderUrl.endsWith("/") ? folderUrl : folderUrl + "/"; + for (final DavResource dr : getParentFolderResources()) { + final String url = slashedFolder + dr.getName(); + if (url.equals(this.url)) { + return !dr.isDirectory(); + } + } + return false; + } + + /** + * @return The name of the file + */ + public String name() { + return name; + } + + /** + * @return The extension of the file + */ + public String extension() { + return extension; + } + + /** + * @return The revision of the file + */ + public int revision() { + return revision; + } + + /** + * @return The mimetype of the file + */ + public String mimetype() { + return mimeType; + } + + /** + * @return The parent folder url + */ + public String folderUrl() { + return folderUrl; + } + + /** + * @return The url of the resource + */ + public String url() { + return url; + } + + /** + * @return The username for the connection + */ + public String username() { + return username; + } + + /** + * @return The password for the connection + */ + public String password() { + return password; + } + + + /** + * @return Whether the connection is readonly or not + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Locks the resource + */ + public void lock() { + try { + this.lock = sardine.lock(url); + } catch (final IOException e) { + logger.log(Level.WARNING, "Error locking " + url, e); + } + } + + /** + * Unlocks the resource + */ + public void unlock() { + if (lock != null) { + try { + sardine.unlock(url, lock); + } catch (final IOException e) { + logger.log(Level.WARNING, "Error unlocking " + url, e); + } + lock = null; + } + } + + /** + * @return The modification time + */ + public Instant getModificationTime() throws IOException { + return getInstant(DavResource::getModified); + } + + /** + * @return The creation time + */ + public Instant getCreationTime() throws IOException { + return getInstant(DavResource::getCreation); + } + + private Instant getInstant(final Function resourceToDate) throws IOException { + final String slashedFolder = folderUrl.endsWith("/") ? folderUrl : folderUrl + "/"; + final List resources = sardine.list(folderUrl); + return resources.stream().filter(dr -> (slashedFolder + dr.getName()).equals(url)).findFirst() + .map(dr -> resourceToInstant(dr, resourceToDate)).orElse(null); + } + + private static Instant resourceToInstant(final DavResource resource, final Function resourceToDate) { + final Date date = resourceToDate.apply(resource); + if (date == null) { + return null; + } else { + return Instant.ofEpochMilli(date.getTime()); + } + } +} \ No newline at end of file diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/UriUtils.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/UriUtils.java new file mode 100644 index 000000000..85f5b2145 --- /dev/null +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/util/UriUtils.java @@ -0,0 +1,86 @@ +package org.icepdf.ri.util; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; +import java.util.regex.Pattern; + +public final class UriUtils { + + private static final Pattern URL_ESCAPE_PATTERN = Pattern.compile("%[0-9A-Fa-f]{2}"); + + private UriUtils() { + } + + public static String decode(final String source, final Charset charset) { + final int length = source.length(); + if (length == 0) { + return source; + } + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(length); + boolean changed = false; + for (int i = 0; i < length; i++) { + final int ch = source.charAt(i); + if (ch == '%') { + if (i + 2 < length) { + final char hex1 = source.charAt(i + 1); + final char hex2 = source.charAt(i + 2); + final int u = Character.digit(hex1, 16); + final int l = Character.digit(hex2, 16); + if (u == -1 || l == -1) { + throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); + } + baos.write((char) ((u << 4) + l)); + i += 2; + changed = true; + } else { + throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); + } + } else { + baos.write(ch); + } + } + return (changed ? new String(baos.toByteArray(), charset) : source); + } + + public static String encodePath(final String source, final Charset charset) { + //Avoid double encoding + if (source == null || source.isEmpty() || URL_ESCAPE_PATTERN.matcher(source).find()) { + return source; + } + + final byte[] bytes = source.getBytes(charset); + boolean original = true; + for (final byte b : bytes) { + if (!isAllowedPath(b)) { + original = false; + break; + } + } + if (original) { + return source; + } + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length); + for (final byte b : bytes) { + if (isAllowedPath(b)) { + baos.write(b); + } else { + baos.write('%'); + final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)); + final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16)); + baos.write(hex1); + baos.write(hex2); + } + } + return new String(baos.toByteArray(), charset); + } + + private static boolean isAllowedPath(final int c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || + c == '/' || c == '@' || c == ':' || c == '.' || c == '-' || c == '_' || c == '~' || + c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || + c == '+' || c == ',' || c == ';' || c == '='; + } + +} diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java index f7cd973ca..6ba97b99f 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/Launcher.java @@ -18,11 +18,10 @@ import org.icepdf.core.util.Defs; import org.icepdf.core.util.SystemProperties; import org.icepdf.ri.common.ViewModel; -import org.icepdf.ri.util.FontPropertiesManager; -import org.icepdf.ri.util.URLAccess; -import org.icepdf.ri.util.ViewerPropertiesManager; +import org.icepdf.ri.util.*; import javax.swing.*; +import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.util.ResourceBundle; import java.util.logging.Level; @@ -67,6 +66,9 @@ public static void main(String[] argv) { String contentURL = ""; String contentFile = ""; + String password = null; + String contentDav = ""; + String user = SystemProperties.USER_NAME; String printer = null; // parse command line arguments for (int i = 0; i < argv.length; i++) { @@ -82,6 +84,15 @@ public static void main(String[] argv) { case "-loadurl": contentURL = argv[++i].trim(); break; + case "-loaddav": + contentDav = argv[++i].trim(); + break; + case "-user": + user = argv[++i].trim(); + break; + case "-password": + password = argv[++i].trim(); + break; case "-print": printer = argv[++i].trim(); break; @@ -101,7 +112,7 @@ public static void main(String[] argv) { System.exit(1); } // start the viewer - run(contentFile, contentURL, printer, messageBundle); + run(contentFile, contentURL, contentDav, user, password, printer, messageBundle); } /** @@ -111,11 +122,17 @@ public static void main(String[] argv) { * null. * @param contentURL URL of a file which will be loaded at runtime, can be * null. + * @param contentDav URL of a file which will be loaded at runtime, can be null + * @param user The dav username + * @param password the dav password * @param printer The name of the printer to use, can be null * @param messageBundle messageBundle to pull strings from */ private static void run(String contentFile, String contentURL, + String contentDav, + String user, + String password, String printer, ResourceBundle messageBundle) { @@ -172,10 +189,19 @@ private static void run(String contentFile, ViewModel.setDefaultURL(urlAccess.urlLocation); urlAccess.dispose(); } + if (contentDav != null && !contentDav.isEmpty()) { + if (printer != null) { + windowManager.newWindow(new DavFileClient(UriUtils.encodePath(contentDav, StandardCharsets.UTF_8), user, password), printer); + } else { + windowManager.newWindow(new DavFileClient(UriUtils.encodePath(contentDav, StandardCharsets.UTF_8), user, password)); + } + } + // Start an empy viewer if there was no command line parameters if (((contentFile == null || contentFile.isEmpty()) && - (contentURL == null || contentURL.isEmpty())) + (contentURL == null || contentURL.isEmpty()) && + (contentDav == null || contentDav.isEmpty())) || (windowManager.getNumberOfWindows() == 0) ) { windowManager.newWindow(""); diff --git a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java index 8d7a90252..029ffdf2a 100644 --- a/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java +++ b/viewer/viewer-awt/src/main/java/org/icepdf/ri/viewer/WindowManager.java @@ -22,6 +22,7 @@ import org.icepdf.ri.common.views.Controller; import org.icepdf.ri.common.views.DocumentViewController; import org.icepdf.ri.common.views.DocumentViewControllerImpl; +import org.icepdf.ri.util.DavFileClient; import org.icepdf.ri.util.ViewerPropertiesManager; import javax.swing.*; @@ -141,6 +142,15 @@ private void print(Controller controller, String printer) { quit(controller, controller.getViewerFrame(), controller.getPropertiesManager().getPreferences()); } + public void newWindow(DavFileClient davFileClient) { + commonWindowCreation().openDocument(davFileClient); + } + public void newWindow(DavFileClient davFileClient, String printer) { + Controller controller = commonWindowCreation(false); + controller.openDocument(davFileClient); + print(controller, printer); + } + protected Controller commonWindowCreation() { return commonWindowCreation(true); } diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties index a78244083..831e0eef0 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle.properties @@ -176,6 +176,7 @@ viewer.menu.open.label=Open viewer.menu.open.recentFiles.label=Open Recent Files viewer.menu.open.file.label=File... viewer.menu.open.URL.label=URL... +viewer.menu.open.dav.label=WebDav URL... viewer.menu.close.label=Close viewer.menu.save.label=Save viewer.menu.saveAs.label=Save As... @@ -395,6 +396,19 @@ viewer.dialog.security.cancelButton.label=Cancel viewer.dialog.security.cancelButton.mnemonic=C ## Open URL Dialog viewer.dialog.openURL.title=Open URL +## Open Webdav Dialog +viewer.dialog.dav.credentials.title=Enter your credentials +viewer.dialog.dav.credentials.label=Enter your credentials to open {0} +viewer.dialog.dav.user.label=Username +viewer.dialog.dav.password.label=Password +viewer.dialog.dav.credentials.button.ok=Ok +viewer.dialog.dav.credentials.button.cancel=Cancel +viewer.dialog.sardine.exception.title=Communication error +viewer.dialog.sardine.exception.msg=Couldn't open document due to {0} +viewer.dialog.dav.exception.title=Webdav error +viewer.dialog.dav.exception.msg=Couldn't open document due to {0} +viewer.dialog.dav.notfound.title=File not found +viewer.dialog.dav.notfound.msg=Couldn't open file: File not found at\n{0} ### Save Dialogs viewer.dialog.saveAs.title=Save As viewer.dialog.saveAs.extensionError.title=ICEpdf - Save Error diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_da.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_da.properties index 636dc7195..33f424936 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_da.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_da.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf-fremviser +viewer.window.title.open.default=ICEpdf-fremviser - [{0}] #status bar viewer.statusbar.currentPage=Side {0} / {1} ## Top Page Control Toolbar diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_de.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_de.properties index 5959be0ff..2af5b6505 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_de.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_de.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf Viewer +viewer.window.title.open.default=ICEpdf Viewer - [{0}] #status bar viewer.statusbar.currentPage=Seite {0} / {1} ## Top Page Control Toolbar @@ -213,6 +214,19 @@ viewer.dialog.security.cancelButton.label=Abbrechen viewer.dialog.security.cancelButton.mnemonic=C ## Open URL Dialog viewer.dialog.openURL.title=URL \u00F6ffnen +## Open Webdav Dialog +viewer.dialog.dav.credentials.title=Geben Sie Ihre Anmeldedaten ein +viewer.dialog.dav.credentials.label=Geben Sie Ihre Anmeldedaten ein, um {0} zu \u00F6ffnen +viewer.dialog.dav.user.label=Benutzername +viewer.dialog.dav.password.label=Passwort +viewer.dialog.dav.credentials.button.ok=Ok +viewer.dialog.dav.credentials.button.cancel=Abbrechen +viewer.dialog.sardine.exception.title=Kommunikationsfehler +viewer.dialog.sardine.exception.msg=Das Dokument konnte aufgrund von {0} nicht ge\u00F6ffnet werden. +viewer.dialog.dav.exception.title=Webdav-Fehler +viewer.dialog.dav.exception.msg=Konnte Dokument nicht \u00F6ffnen wegen {0} +viewer.dialog.dav.notfound.title=Dokument nicht gefunden +viewer.dialog.dav.notfound.msg=Konnte Dokument nicht \u00F6ffnen: Dokument nicht gefunden am\n{0} ### Save a Copy Dialog viewer.dialog.saveAs.title=Speichern unter viewer.dialog.saveAs.extensionError.title=ICEpdf - Fehler beim Speichern diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_es.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_es.properties index 1ba1b1b7a..98185b053 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_es.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_es.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf Viewer +viewer.window.title.open.default=ICEpdf Viewer - [{0}] #status bar viewer.statusbar.currentPage=P\u00E1gina {0} / {1} ## Top Page Control Toolbar diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fi.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fi.properties index 5b313d0c1..940903ac6 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fi.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fi.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf-katselusovellus +viewer.window.title.open.default=ICEpdf-katselusovellus - [{0}] #status bar viewer.statusbar.currentPage=Sivu {0} / {1} ## Top Page Control Toolbar diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fr.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fr.properties index 024636356..66438ad13 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fr.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_fr.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf Viewer +viewer.window.title.open.default=ICEpdf Viewer - [{0}] #status bar viewer.statusbar.currentPage=Page {0} / {1} ## Top Page Control Toolbar @@ -213,6 +214,19 @@ viewer.dialog.security.cancelButton.label=Annuler viewer.dialog.security.cancelButton.mnemonic=C ## Open URL Dialog viewer.dialog.openURL.title=Ouvrir l'URL +## Open Webdav Dialog +viewer.dialog.dav.credentials.title=Entrez vos identifiants +viewer.dialog.dav.credentials.label=Entrez vos identifiants pour ouvrir {0} +viewer.dialog.dav.user.label=Nom d'utilisateur +viewer.dialog.dav.password.label=Mot de passe +viewer.dialog.dav.credentials.button.ok=Ok +viewer.dialog.dav.credentials.button.cancel=Annuler +viewer.dialog.sardine.exception.title=Erreur de communication +viewer.dialog.sardine.exception.msg=Impossible d'ouvrir le document en raison de {0} +viewer.dialog.dav.exception.title=Erreur Webdav +viewer.dialog.dav.exception.msg=Impossible d'ouvrir le document en raison de {0} +viewer.dialog.dav.notfound.title=Document introuvable +viewer.dialog.dav.notfound.msg=Impossible d'ouvrir le fichier: Fichier introuvable \u00E0 l'adresse\n{0} ### Save a Copy Dialog viewer.dialog.saveAs.title=Enregistrer sous viewer.dialog.saveAs.extensionError.title=ICEpdf - Erreur d'enregistrement diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_it.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_it.properties index c0292e941..68a8e0e5a 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_it.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_it.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf Viewer +viewer.window.title.open.default=ICEpdf Viewer - [{0}] #status bar viewer.statusbar.currentPage=Pag. {0} / {1} ## Top Page Control Toolbar diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_nl.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_nl.properties index eafef48df..c89ae1084 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_nl.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_nl.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf Viewer +viewer.window.title.open.default=ICEpdf Viewer - [{0}] #status bar viewer.statusbar.currentPage=Pag. {0} / {1} ## Top Page Control Toolbar diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_no.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_no.properties index 82b1a13f6..ce04ae305 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_no.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_no.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf Oversikt +viewer.window.title.open.default=ICEpdf Oversikt - [{0}] #status bar viewer.statusbar.currentPage=Side {0} / {1} ## Top Page Control Toolbar diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_pt.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_pt.properties index 2ec2eb9cb..b3721d3b2 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_pt.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_pt.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=Visualizador CEpdf +viewer.window.title.open.default=Visualizador ICEpdf - [{0}] #status bar viewer.statusbar.currentPage=P\u00E1gina {0} / {1} ## Top Page Control Toolbar diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_sv.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_sv.properties index c45d9c94e..bdca58656 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_sv.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_sv.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf Viewer +viewer.window.title.open.default=ICEpdf Viewer - [{0}] #status bar viewer.statusbar.currentPage=Sida {0} / {1} ## Top Page Control Toolbar diff --git a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_zh_CN.properties b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_zh_CN.properties index 23d0cefec..c2eb3bd2b 100644 --- a/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_zh_CN.properties +++ b/viewer/viewer-awt/src/main/resources/org/icepdf/ri/resources/MessageBundle_zh_CN.properties @@ -19,6 +19,7 @@ # ## Window toolbar Title viewer.window.title.default=ICEpdf Viewer +viewer.window.title.open.default=ICEpdf Viewer - [{0}] #status bar viewer.statusbar.currentPage=\u9875 {0} / {1} ## Top Page Control Toolbar