/* * Copyright (C) 2006-2014 Gabriel Burca (gburca dash virtmus at ebixio dot com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package com.ebixio.virtmus; import com.ebixio.util.Log; import java.awt.Cursor; import java.awt.Dimension; import java.awt.DisplayMode; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.image.MemoryImageSource; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; import java.security.CodeSource; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.PatternSyntaxException; /** * * @author Gabriel Burca <gburca dash virtmus at ebixio dot com> */ public class Utils { private static Cursor invisibleCursor = null; /** Creates a new instance of Utils */ public Utils() { } // <editor-fold defaultstate="collapsed" desc=" Screen Size "> static Dimension getScreenSize() { return Toolkit.getDefaultToolkit().getScreenSize(); } static Dimension getScreenSize(int screen) { return getScreenSizes()[screen]; } public static Dimension[] getScreenSizes() { ArrayList<Dimension> sizes = new ArrayList<Dimension>(); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] gs = ge.getScreenDevices(); for (int i = 0; i < gs.length; i++) { DisplayMode dm = gs[i].getDisplayMode(); sizes.add(new Dimension(dm.getWidth(), dm.getHeight())); } return sizes.toArray(new Dimension[sizes.size()]); } public static int getNumberOfScreens() { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); try { GraphicsDevice[] gs = ge.getScreenDevices(); return gs.length; } catch (HeadlessException e) { // Thrown if there are no screen devices return 0; } } // </editor-fold> public static Cursor getInvisibleCursor() { if (invisibleCursor == null) { int[] pixels = new int[16 * 16]; Image mouseImage = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(16, 16, pixels, 0, 16)); invisibleCursor = Toolkit.getDefaultToolkit().createCustomCursor(mouseImage, new Point(0,0), "invisibleCursor"); } return invisibleCursor; } // <editor-fold defaultstate="collapsed" desc=" Sizing and scaling "> public static double scaleProportional(Rectangle container, Rectangle item) { double scaleX = (double)container.width / (double)item.width; double scaleY = (double)container.height / (double)item.height; return Math.min(scaleX, scaleY); } public static Rectangle shrinkToFit(Rectangle container, Rectangle item) { double scale = scaleProportional(container, item); if (scale > 1) { return item; } else { return new Rectangle((int)(item.width * scale), (int)(item.height * scale)); } } public static Rectangle stretchToFit(Rectangle container, Rectangle item) { double scale = scaleProportional(container, item); if (scale < 1) { return item; } else { return new Rectangle((int)(item.width * scale), (int)(item.height * scale)); } } public static Rectangle scaleToFit(Rectangle container, Rectangle item) { double scale = scaleProportional(container, item); return new Rectangle((int)(item.width * scale), (int)(item.height * scale)); } public static Point centerItem(Rectangle container, Rectangle item) { int x = (container.width / 2) - (item.width / 2); int y = (container.height / 2) - (item.height / 2); return new Point(x, y); } public static Rectangle scale(Rectangle rect, float scale) { Rectangle res = rect; res.width *= scale; res.height *= scale; return res; } // </editor-fold> /** Search for page in same relative directory. PlayLists (and Songs) store the * full path to the song file (or music page files). If the playlist/song is * moved (along with the song/musicpage files) we try to locate the songs/pages * by assuming they reside in the same directory relative to the playlist/song file. * * Since we don't know where the original PlayList resided, we can't compute * the true relative location of the Songs it included, so we use heuristics. * * If original song = /a/b/c/song.xml * and original page = /a/b/c/d/page.png * And now the song is in /x/y/z/song.xml * * We check for page as follows: * /x/y/z/page.png * /x/y/z/d/page.png * /x/y/z/c/d/page.png * /x/y/z/b/c/d/page.png * /x/y/z/a/b/c/d/page.png * * That should account for the use case where the pages are in a subdirectory * of the song. * * If old song = /a/b/c/song.xml * and old page = /a/b/d/page.png * And now the song is in /x/y/z/song.xml * * We check for page as follows: * /x/y/d/page.png * /x/y/b/d/page.png * /x/y/a/b/d/page.png * * /x/d/page.png * /x/b/d/page.png * /x/a/b/d/page.png * * /d/page.png * /b/d/page.png * /a/b/d/page.png * * @param newSrc The location of the new PlayList (or Song) * @param oldTarget The location of the old Song (or MusicPage) */ static File findFileRelative(File newSrc, File oldTarget) { if (newSrc == null || oldTarget == null) return null; if (oldTarget.exists()) { return oldTarget; } try { newSrc = newSrc.getCanonicalFile(); String newParentName = newSrc.getParent(); // /x/y/z // It's possible for oldTarget to come from a different OS. // Convert oldTarget to use the current host OS path separator. String oldFileName = oldTarget.toString(); oldFileName = oldFileName.replaceAll("/", Matcher.quoteReplacement(File.separator)) .replaceAll("\\\\", Matcher.quoteReplacement(File.separator)); oldTarget = new File(oldFileName); try { // Canonical name can not be computed if drive is not mounted. // Ex: oldTarget = "D:\etc..." but there's no "D:" and songs // have been moved to "C:" instead. We expect to see: // java.io.IOException: The device is not ready oldTarget = oldTarget.getCanonicalFile(); } catch (IOException ex) { // Don't log it. It's just noise, and expected. //Log.log(ex); } oldFileName = oldTarget.getName(); // /a/b/d String oldParentName = oldTarget.getParent(); // See if the page files are in the same directory as the song file File testF = new File(newParentName + File.separator + oldFileName); if (testF.exists()) return testF; String[] oDirs = splitFile(oldParentName); if (oDirs == null) return null; if (oDirs.length > 0) { String test = ""; for (int i = oDirs.length - 1; i >= 0; i--) { if (test.length() > 0) { test = oDirs[i] + File.separator + test; } else { test = oDirs[i]; } testF = new File(newParentName + File.separator + test + File.separator + oldFileName); if (testF.exists()) return testF; } } else { testF = new File(newParentName + File.separator + oldFileName); if (testF.exists()) return testF; } // We could also try different roots (drives) on Windows? //File[] roots = File.listRoots(); Path oPath = oldTarget.getParentFile().toPath(); Path nPath = newSrc.getParentFile().toPath(); Path root = nPath.getRoot(); for (int i = nPath.getNameCount() - 1; i > 0; i--) { for (int j = oPath.getNameCount() - 1; j >= 0; j--) { Path oSub = oPath.subpath(j, oPath.getNameCount()); Path test = nPath.subpath(0, i).resolve(oSub); test = test.resolve(oldFileName); if (test.toFile().exists()) { return test.toFile(); } else if (root != null) { test = root.resolve(test); if (test.toFile().exists()) { return test.toFile(); } } } } } catch (IOException | PatternSyntaxException ex) { Log.log(ex); return null; } return null; } /** Get the file extension (if present). The file extension is the portion * after the LAST dot. * * @param f A file object. * @return The file extension (containing no dots), or an empty string. */ public static String getFileExtension(File f) { return getFileExtension(f.getName()); } public static String getFileExtension(String s) { int i = s.lastIndexOf('.'); if (i > 0 && i < s.length() - 1) { return s.substring(i + 1); } else { return ""; } } /** * Removes the extension from a file name. * @param f The file name to trim * @param ext The extension to remove. If it doesn't start with ".", a dot * will be pre-pended. If null, {@link #getFileExtension(java.lang.String)} * is used to determine the extension. * @return The original file name, sans the extension. */ public static String trimExtension(String f, String ext) { if (ext == null) ext = getFileExtension(f); if (ext.isEmpty()) return f; if (!ext.startsWith(".")) ext = "." + ext; if (ext.length() >= f.length() || !f.endsWith(ext)) { return f; } else { int end = f.lastIndexOf(ext); return f.substring(0, end); } } public static String[] splitFile(String f) { String[] parts; if (File.separator.equals("\\")) { // Double it once to escape from Java and once more to escape from RegEx parts = f.split("\\\\"); } else { parts = f.split(File.separator); } return parts; } /** Attempts to launch an external browser to handle a URL. * @param url The URL to launch the default browser with. * @return <b>true</b> on success */ public static boolean openURL(String url) { String osName = System.getProperty("os.name"); try { if (osName.startsWith("Mac OS")) { Class fileMgr = Class.forName("com.apple.eio.FileManager"); @SuppressWarnings("unchecked") Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] {String.class}); openURL.invoke(null, new Object[] {url}); } else if (osName.startsWith("Windows")) { Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); } else { //assume Unix or Linux String[] browsers = { "firefox", "opera", "konqueror", "epiphany", "google-chrome", "mozilla", "netscape" }; String browser = null; for (int count = 0; count < browsers.length && browser == null; count++) if (Runtime.getRuntime().exec( new String[] {"which", browsers[count]}).waitFor() == 0) browser = browsers[count]; if (browser == null) throw new Exception("Could not find web browser"); else Runtime.getRuntime().exec(new String[] {browser, url}); } } catch (Exception e) { return false; } return true; } /** Provides the path to the top level application folder. * * Due to differences in how files are laid out when running out of the NetBeans IDE * versus an unpacked ZIP distribution, this methods only works on Windows and Linux * when the application was deployed using a ZIP file. * * This method is not guaranteed to work on all JVMs but works on Sun JVMs. * @return The path to the top level application folder. */ public static File getAppPath() { CodeSource source = MainApp.findInstance().getClass().getProtectionDomain().getCodeSource(); if (source == null) return null; File codeLoc; try { URI sourceURI = new URI(source.getLocation().toString()); codeLoc = new File(sourceURI); } catch (URISyntaxException | IllegalArgumentException e) { return null; } if (!codeLoc.isDirectory()) { codeLoc = codeLoc.getParentFile(); if (codeLoc == null) return null; } // The app root directory is two levels up in a zip distribution codeLoc = codeLoc.getParentFile().getParentFile(); return codeLoc; } /** Doesn't work on Windows or Linux * @return The path to the top level application folder. */ public static File getAppPath1() { File appPath = new File(System.getProperty("java.class.path")); try { appPath = appPath.getCanonicalFile().getParentFile(); } catch (IOException e) { return null; } return appPath; } /** Works on windows, but not on Linux * @return The path to the top level application folder. */ public static File getAppPath2() { File appPath = new File("."); try { appPath = appPath.getCanonicalFile(); } catch (IOException e) { return null; } return appPath; } public static File[] listFilesAsArray(File directory, FilenameFilter filter, boolean recurse) { Collection<File> files = listFiles(directory, filter, recurse); File[] arr = new File[files.size()]; return files.toArray(arr); } public static Collection<File> listFiles(File directory, FilenameFilter filter, boolean recurse) { // List of files / directories ArrayList<File> files = new ArrayList<>(); // Get files / directories in the directory File[] entries = directory.listFiles(); // Go over entries for (File entry : entries) { // If there is no filter or the filter accepts the // file / directory, add it to the list if (filter == null || filter.accept(directory, entry.getName())) { files.add(entry); } // If the file is a directory and the recurse flag // is set, recurse into the directory if (recurse && entry.isDirectory()) { files.addAll(listFiles(entry, filter, recurse)); } } return files; } public static String shortenString(String orig, int charsToRemove) { if (charsToRemove <= 0) { return orig; } if (charsToRemove >= orig.length() - 1) { return orig.charAt(0) + "..."; } int cut = orig.length() - charsToRemove; int s1len = cut / 2 + cut % 2; int s2len = cut / 2; String s1 = orig.substring(0, s1len); String s2 = orig.substring(orig.length() - s2len); return s1 + "..." + s2; } /** Convert the song or PlayList tags into a list. * @param tags The tags string. Can be null. Tags are separated by space or comma. * @return A possibly empty list of tag strings. */ public static List<String> tags2list(String tags) { ArrayList<String> t = new ArrayList<>(); if (tags != null && tags.length() > 0) { String ts[] = tags.split("[,\\s]"); for (String tag: ts) { if (tag.length() > 0) { t.add(tag); } } } return t; } }