--- /dev/null
+/.classpath
+/target/
+/bin
+/.settings
+/.idea/
+/*.iml
+*.html
--- /dev/null
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
--- /dev/null
+#+SETUPFILE: ~/.emacs.d/org-styles/html/darksun.theme
+#+TITLE: Instantlauncher - navigate to your files and programs quickly!
+#+LANGUAGE: en
+#+LATEX_HEADER: \usepackage[margin=1.0in]{geometry}
+#+LATEX_HEADER: \usepackage{parskip}
+#+LATEX_HEADER: \usepackage[none]{hyphenat}
+
+#+OPTIONS: H:20 num:20
+#+OPTIONS: author:nil
+
+* Introduction
+
+Quick filesystem based document/application launcher. It starts by
+showing content of [[id:5ced6bf5-165a-49a0-8de4-3755d810d987][predefined directory]]. Sub directories can be chosen
+by simply moving mouse pointer over corresponding directory.
+
+[[file:screenshot.png]]
+
+Files types are color-coded.
+
+** File type colors
+:PROPERTIES:
+:ID: ab6a1982-b856-403c-9960-43680720399a
+:END:
+
+Colors come from the file type (selected entry = brighter shade):
+
++ blue :: Directory - including a symlink that points to a directory
++ red :: Executable file
++ green :: Non-executable file
++ olive/yellow :: Anything that is neither directory nor regular file,
+ e.g. a broken symlink
+
+Note: a symlink pointing to a file takes the *file* colors
+(red/green), not blue. The Usage table's "Directory/symlink (blue)"
+holds only for symlinks pointing to directories.
+
+** Usage
+Simply hovering mouse pointer over directory expands its contents in
+the next right column.
+
+
++ Executable file (red):
+ + Left mouse click:
+ + Run executable file.
+ + Right mouse click:
+ + Toggle executable state.
+
++ Non-executable file (green):
+ + Left mouse click:
+ + Open file using corresponding application.
+ + Right mouse click:
+ + Toggle executable state.
+
++ Directory/symlink (blue):
+ + Left mouse click:
+ + Open directory using configured filesystem browser.
+ + Right mouse click:
+ + Open directory using configured text based terminal emulator.
+
+* Installation
+:PROPERTIES:
+:ID: aea98e61-17aa-4c69-acd3-a11e043088a6
+:END:
+
+Run the install script from the project root (asks for sudo):
+#+BEGIN_SRC sh
+./install
+#+END_SRC
+
+The script does everything: builds the jar (=mvn clean package=,
+requires JDK 21 and Maven), copies =instantlauncher.jar= and the
+=instantlauncher= launcher script to =/opt/instantlauncher/=, and
+symlinks the launcher into =/usr/bin/=.
+
+After that the program starts by typing =instantlauncher= in a terminal.
+Only a Java runtime is needed at launch; the launcher script runs
+=java -jar /opt/instantlauncher/instantlauncher.jar=.
+
+The jar name carries no version number, so these scripts never change
+when the project version does. Re-running =install= is safe: it
+rebuilds and updates the installed copy.
+
+** Configuration
+When instantlauncher starts, it looks for configuration file under
+: ~/.instantlauncher
+
+If file is not found, it creates new default configuration file in
+YAML format:
+#+BEGIN_SRC
+!configuration
+directoryOpenCommand: nautilus -w {file}
+directoryTerminalOpenCommand: gnome-terminal --working-directory={file}
+fileAssociations:
+- command: vlc {file}
+ fileRegex: .*\.mkv$
+- command: emacs {file}
+ fileRegex: .*\.txt$
+- command: eog {file}
+ fileRegex: .*\.jpeg$
+- command: eog {file}
+ fileRegex: .*\.png$
+- command: vlc {file}
+ fileRegex: .*\.avi$
+- command: vlc {file}
+ fileRegex: .*\.mp4$
+- command: eog {file}
+ fileRegex: .*\.jpg$
+- command: emacs {file}
+ fileRegex: .*\.org$
+navigationRootPath: /home/svjatoslav/
+#+END_SRC
+
+*** navigationRootPath
+ :PROPERTIES:
+ :ID: 5ced6bf5-165a-49a0-8de4-3755d810d987
+ :END:
+Root directory from where to start navigation.
+
+This parameter is user specific! In my case instantlauncher by default
+pointed this to my home directory. But it can be any directory user
+chooses.
+
+I think good use-case is to create special directory to be used for
+instantlauncher root. This special root directory can in turn contain
+symlinks to various places user wants to visit often. Instantlauncher
+will follow symlinks as if they were directories.
+
+: navigationRootPath: /home/svjatoslav/
+
+*** fileAssociations
+Allows to map file name/extension using regular expression pattern
+matching to corresponding application.
+
+Example:
+#+BEGIN_SRC conf
+- command: vlc {file}
+ fileRegex: .*\.mkv$
+#+END_SRC
+
+Where:
+- command :: Command to execute to open given file. Special variable
+ {file} will point to user chosen file using absolute file
+ path.
+
+- fileRegex :: Regular expression that must match the file.
+
+*** directoryOpenCommand
+Command to use when opening directory.
+
+By default instantlauncher assumes that Gnome desktop environment is
+installed and uses Nautilus file manager:
+: directoryOpenCommand: nautilus -w {file}
+
+*** directoryTerminalOpenCommand
+Command to use when opening directory in text terminal based console.
+
+By default instantlauncher assumes that Gnome desktop environment is
+installed and uses Gnome Terminal:
+
+: directoryTerminalOpenCommand: gnome-terminal --working-directory={file}
+* Source code
+- This program is free software: released under Creative Commons Zero
+ (CC0) license
+
+- Program author:
+ - Svjatoslav Agejenko
+ - Homepage: https://svjatoslav.eu
+ - Email: mailto://svjatoslav@svjatoslav.eu
+
+- [[https://www.svjatoslav.eu/projects/][Other software projects hosted at svjatoslav.eu]]
+
+** Source code
+- [[https://www2.svjatoslav.eu/gitweb/?p=instantlauncher.git;a=snapshot;h=HEAD;sf=tgz][Download latest snapshot in TAR GZ format]]
+
+- [[https://www2.svjatoslav.eu/gitweb/?p=instantlauncher.git;a=summary][Browse Git repository online]]
+
+- Clone Git repository using command:
+ : git clone https://www2.svjatoslav.eu/git/instantlauncher.git
+
--- /dev/null
+#!/bin/bash
+
+#
+# This is a helper bash script that starts IntelliJ with the current project.
+# Script is written is such a way that you can simply click on it in file
+# navigator to run it.
+#
+#
+# Script assumes:
+#
+# + GNU operating system
+# + IntelliJ is installed and commandline launcher "idea" is enabled.
+#
+
+cd "${0%/*}"
+cd ..
+
+setsid idea . &>/dev/null
--- /dev/null
+#!/bin/bash
+cd "${0%/*}"; if [ "$1" != "T" ]; then gnome-terminal -e "'$0' T"; exit; fi;
+
+cd ..
+
+# Function to export org to html using emacs in batch mode
+export_org_to_html() {
+ local org_file=$1
+ local dir=$(dirname "$org_file")
+ local base=$(basename "$org_file" .org)
+ (
+ cd "$dir" || return 1
+ local html_file="${base}.html"
+ if [ -f "$html_file" ]; then
+ rm -f "$html_file"
+ fi
+ echo "Exporting: $org_file → $dir/$html_file"
+ emacs --batch -l ~/.emacs --visit="${base}.org" --funcall=org-html-export-to-html --kill
+ if [ $? -eq 0 ]; then
+ echo "✓ Successfully exported $org_file"
+ else
+ echo "✗ Failed to export $org_file"
+ return 1
+ fi
+ )
+}
+
+export_org_files_to_html() {
+ echo "🔍 Searching for .org files in Documentation/ ..."
+ echo "======================================="
+
+ mapfile -t ORG_FILES < <(find Documentation -type f -name "*.org" | sort)
+
+ if [ ${#ORG_FILES[@]} -eq 0 ]; then
+ echo "❌ No .org files found!"
+ return 1
+ fi
+
+ echo "Found ${#ORG_FILES[@]} .org file(s):"
+ printf '%s\n' "${ORG_FILES[@]}"
+ echo "======================================="
+
+ SUCCESS_COUNT=0
+ FAILED_COUNT=0
+
+ for org_file in "${ORG_FILES[@]}"; do
+ export_org_to_html "$org_file"
+ if [ $? -eq 0 ]; then
+ ((SUCCESS_COUNT++))
+ else
+ ((FAILED_COUNT++))
+ fi
+ done
+
+ echo "======================================="
+ echo "📊 SUMMARY:"
+ echo " ✓ Successful: $SUCCESS_COUNT"
+ echo " ✗ Failed: $FAILED_COUNT"
+ echo " Total: $((SUCCESS_COUNT + FAILED_COUNT))"
+ echo ""
+
+ return $FAILED_COUNT
+}
+
+# Compile Emacs org-mode files into HTML format
+export_org_files_to_html
+
+if [ $? -ne 0 ]; then
+ echo "✗ HTML export failed - NOT publishing."
+ echo ""
+ echo "Press ENTER to close this window."
+ read
+ exit 1
+fi
+
+# Upload assembled documentation to server
+echo "📤 Uploading to server..."
+rsync -avz --delete -e 'ssh -p 10006' Documentation/ \
+ n0@www3.svjatoslav.eu:/mnt/big/projects/instantlauncher/
+
+if [ $? -eq 0 ]; then
+ echo "✓ Upload completed successfully!"
+else
+ echo "✗ Upload failed!"
+fi
+
+echo ""
+echo "Press ENTER to close this window."
+read
--- /dev/null
+#!/bin/bash
+
+# Requires: JDK 21 and Maven to build, sudo to install.
+# Idempotent: safe to re-run, updates the installed copy.
+
+mvn clean package
+
+sudo mkdir -p /opt/instantlauncher
+sudo cp target/instantlauncher.jar /opt/instantlauncher/
+sudo cp instantlauncher /opt/instantlauncher/
+sudo ln -sf /opt/instantlauncher/instantlauncher /usr/bin/
--- /dev/null
+#!/bin/bash
+
+set -f
+
+java -jar /opt/instantlauncher/instantlauncher.jar "$@"
--- /dev/null
+<project
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>eu.svjatoslav</groupId>
+ <artifactId>instantlauncher</artifactId>
+ <version>1.0</version>
+ <packaging>jar</packaging>
+ <name>Instant Launcher</name>
+ <description>popup panel with shortcuts for quick application launching</description>
+
+ <properties>
+ <java.version>21</java.version>
+ <maven.compiler.source>21</maven.compiler.source>
+ <maven.compiler.target>21</maven.compiler.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>eu.svjatoslav</groupId>
+ <artifactId>svjatoslavcommons</artifactId>
+ <version>1.9</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.esotericsoftware.yamlbeans</groupId>
+ <artifactId>yamlbeans</artifactId>
+ <version>1.12</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>instantlauncher</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ <configuration>
+ <source>21</source>
+ <target>21</target>
+ <optimize>true</optimize>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>eu.svjatoslav.instantlauncher.Main</mainClass>
+ </manifest>
+ </archive>
+ <descriptorRefs>
+ <descriptorRef>jar-with-dependencies</descriptorRef>
+ </descriptorRefs>
+ </configuration>
+
+ <executions>
+
+ <execution>
+ <id>package-jar-with-dependencies</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <appendAssemblyId>false</appendAssemblyId>
+ <descriptorRefs>
+ <descriptorRef>jar-with-dependencies</descriptorRef>
+ </descriptorRefs>
+ <archive>
+ <manifest>
+ <mainClass>eu.svjatoslav.instantlauncher.Main</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.5.2</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.scm</groupId>
+ <artifactId>maven-scm-provider-gitexe</artifactId>
+ <version>1.9.4</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+
+ </plugins>
+ </build>
+
+
+ <repositories>
+ <repository>
+ <id>svjatoslav.eu</id>
+ <name>Svjatoslav repository</name>
+ <url>https://www3.svjatoslav.eu/maven/</url>
+ </repository>
+ </repositories>
+
+ <organization>
+ <name>svjatoslav.eu</name>
+ <url>https://svjatoslav.eu</url>
+ </organization>
+
+
+</project>
\ No newline at end of file
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher;
+
+import eu.svjatoslav.instantlauncher.configuration.ConfigurationManager;
+import eu.svjatoslav.instantlauncher.configuration.FileAssociation;
+
+import java.io.File;
+import java.io.IOException;
+
+import static eu.svjatoslav.instantlauncher.Utils.runOpeningApplication;
+
+public class InstantLauncher {
+
+ private final MainFrame mainFrame;
+ public ConfigurationManager configurationManager;
+
+ public InstantLauncher() throws IOException {
+ configurationManager = new ConfigurationManager();
+ mainFrame = new MainFrame(this);
+ }
+
+ public static void exitProgram() {
+ System.exit(0);
+ }
+
+ /**
+ * @return <code>true</code> if file was opened. <code>false</code> if
+ * unknown file type.
+ */
+ public boolean openFile(final File file) {
+ FileAssociation fileAssociation = configurationManager.getConfiguration().findFileAssociation(file);
+ if (fileAssociation == null) return false;
+
+ runOpeningApplication(fileAssociation.command, file);
+ return true;
+ }
+
+ public void run() {
+ mainFrame.show();
+ }
+
+ public void openDirectory(File chosenFile) {
+ runOpeningApplication(configurationManager.getConfiguration().directoryOpenCommand, chosenFile);
+ }
+
+ public void openDirectoryInTerminal(File chosenFile) {
+ runOpeningApplication(configurationManager.getConfiguration().directoryTerminalOpenCommand, chosenFile);
+ }
+
+}
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher;
+
+import java.io.IOException;
+
+class Main {
+
+ public static void main(final String[] args) throws IOException {
+ new InstantLauncher().run();
+ }
+
+}
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher;
+
+import eu.svjatoslav.instantlauncher.menu.MultiLevelMenu;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+
+class MainFrame {
+
+ private final InstantLauncher instantLauncher;
+ private JFrame frame;
+ private boolean isShowing;
+
+ public MainFrame(final InstantLauncher instantLauncher) {
+ this.instantLauncher = instantLauncher;
+ }
+
+ private void buildContentPane() {
+
+ final MultiLevelMenu multiLevelMenu = new MultiLevelMenu(instantLauncher);
+ frame.getContentPane().add(multiLevelMenu.buildContentPanel(), BorderLayout.CENTER);
+
+ }
+
+ public void show() {
+
+ if (!isShowing) {
+ // initialize frame
+ frame = new JFrame("InstantLauncher");
+
+ buildContentPane();
+
+ // registor window listener
+ final FrameWindowListener windowListener = new FrameWindowListener();
+ frame.addWindowListener(windowListener);
+
+ // maximize window
+ frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
+ frame.setVisible(true);
+
+ isShowing = true;
+ }
+
+ }
+
+ private static class FrameWindowListener implements WindowListener {
+
+ @Override
+ public void windowActivated(final WindowEvent e) {
+ }
+
+ @Override
+ public void windowClosed(final WindowEvent e) {
+ }
+
+ @Override
+ public void windowClosing(final WindowEvent e) {
+ InstantLauncher.exitProgram();
+ }
+
+ @Override
+ public void windowDeactivated(final WindowEvent e) {
+ }
+
+ @Override
+ public void windowDeiconified(final WindowEvent e) {
+ }
+
+ @Override
+ public void windowIconified(final WindowEvent e) {
+ }
+
+ @Override
+ public void windowOpened(final WindowEvent e) {
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher;
+
+import eu.svjatoslav.commons.gui.dialog.ExceptionDialog;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.File;
+import java.io.IOException;
+
+public class Utils {
+
+ private static final String FILE_INDICATOR = "{file}";
+
+ public static void setComponentSize(JComponent component, Dimension size) {
+ component.setMinimumSize(size);
+ component.setMaximumSize(size);
+ component.setSize(size);
+ component.setPreferredSize(size);
+ }
+
+ public static void runOpeningApplication(String commands, final File file) {
+ try {
+ String[] commandsArray = commands.split("\\s+");
+ for (int i=0; i< commandsArray.length; i++)
+ commandsArray[i] = commandsArray[i].replaceAll("\\{file\\}", file.getAbsolutePath());
+
+ Runtime.getRuntime().exec(commandsArray);
+ } catch (final IOException e) {
+ new ExceptionDialog(e);
+ }
+ }
+
+ public static void executeCommand(String... c) {
+ try {
+ Runtime.getRuntime().exec(c);
+ } catch (final IOException e) {
+ new ExceptionDialog(e);
+ }
+ }
+
+
+}
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher.configuration;
+
+import java.io.File;
+import java.util.Set;
+
+public class Configuration {
+ public Set<FileAssociation> fileAssociations;
+ public String navigationRootPath;
+ public String directoryOpenCommand;
+ public String directoryTerminalOpenCommand;
+
+ public FileAssociation findFileAssociation(File file) {
+ for (FileAssociation association : fileAssociations)
+ if (association.matchesFile(file)) return association;
+
+ return null;
+ }
+
+ public File getNavigationRootDirectory() {
+ return new File(navigationRootPath);
+ }
+
+}
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher.configuration;
+
+import com.esotericsoftware.yamlbeans.YamlConfig;
+import com.esotericsoftware.yamlbeans.YamlReader;
+import com.esotericsoftware.yamlbeans.YamlWriter;
+
+import java.io.*;
+import java.util.HashSet;
+
+public class ConfigurationManager {
+
+ private static final String CONFIG_FILE_NAME = ".instantlauncher";
+ private Configuration configuration;
+
+ public ConfigurationManager() throws IOException {
+ initConfig();
+ }
+
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ private File getPropertiesFile() {
+ return new File(System.getProperty("user.home") + "/" + CONFIG_FILE_NAME);
+ }
+
+
+ private void initConfig() throws IOException {
+ final File propertiesFile = getPropertiesFile();
+ if (!propertiesFile.exists()) {
+ initDefaultConfiguration();
+ } else
+ loadConfigFile(propertiesFile);
+ }
+
+ private void loadConfigFile(File propertiesFile) throws IOException {
+ YamlReader reader = new YamlReader(new FileReader(propertiesFile));
+ configureYaml(reader.getConfig());
+ configuration = reader.read(Configuration.class);
+ if (configuration == null)
+ initDefaultConfiguration();
+ }
+
+ private void saveConfigFile() throws IOException {
+ YamlWriter writer = new YamlWriter(new FileWriter(getPropertiesFile()));
+ configureYaml(writer.getConfig());
+ writer.write(configuration);
+ writer.close();
+ }
+
+ private void configureYaml(YamlConfig config) {
+ config.setClassTag("configuration", Configuration.class);
+ config.setPropertyElementType(Configuration.class, "fileAssociations", FileAssociation.class);
+ }
+
+ private void registerFileAssociation(String fileRegex, String command) {
+ FileAssociation association = new FileAssociation(fileRegex, command);
+ configuration.fileAssociations.add(association);
+ }
+
+ private void initDefaultConfiguration() throws IOException {
+ configuration = new Configuration();
+ configuration.fileAssociations = new HashSet<>();
+
+ configuration.navigationRootPath = System.getProperty("user.home") + "/";
+ configuration.directoryOpenCommand = "nautilus -w {file}";
+ configuration.directoryTerminalOpenCommand = "gnome-terminal --working-directory={file}";
+
+ registerFileAssociation(".*\\.jpeg$", "eog {file}");
+ registerFileAssociation(".*\\.jpg$", "eog {file}");
+ registerFileAssociation(".*\\.png$", "eog {file}");
+ registerFileAssociation(".*\\.txt$", "emacs {file}");
+ registerFileAssociation(".*\\.org$", "emacs {file}");
+ registerFileAssociation(".*\\.avi$", "vlc {file}");
+ registerFileAssociation(".*\\.mp4$", "vlc {file}");
+ registerFileAssociation(".*\\.mkv$", "vlc {file}");
+
+ saveConfigFile();
+ }
+}
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher.configuration;
+
+import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class FileAssociation implements Comparable<FileAssociation> {
+ public String command;
+ public String fileRegex;
+
+ public FileAssociation() {
+ }
+
+ public FileAssociation(String fileRegex, String command) {
+ this.fileRegex = fileRegex;
+ this.command = command;
+ }
+
+ @Override
+ public int compareTo(FileAssociation o) {
+ return fileRegex.compareTo(o.fileRegex);
+ }
+
+ public boolean matchesFile(File file) {
+ String absolutePath = file.getAbsolutePath();
+
+ Pattern pattern = Pattern.compile(fileRegex);
+ Matcher matcher = pattern.matcher(absolutePath);
+ return matcher.matches();
+ }
+
+}
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher.menu;
+
+import eu.svjatoslav.commons.gui.dialog.ExceptionDialog;
+import eu.svjatoslav.instantlauncher.Utils;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+class Menu {
+
+ public static final Dimension SIZE_MENU_PANEL = new Dimension(210, 900);
+ private static final Color BUTTON_BG_DIRECTORY = new Color(0, 0, 60);
+
+ private static final Color BUTTON_BG_DIRECTORY_SELECTED = new Color(0, 0, 100);
+ private static final Color BUTTON_BG_LINK = new Color(60, 60, 0);
+
+ private static final Color BUTTON_BG_LINK_SELECTED = new Color(100, 100, 0);
+ private static final Color BUTTON_BG_EXECUTABLE_FILE = new Color(60, 0, 0);
+
+ private static final Color BUTTON_BG_EXECUTABLE_FILE_SELECTED = new Color(100, 0, 0);
+ private static final Color BUTTON_BG_NONEXECUTABLE_FILE = new Color(0, 60, 0);
+
+ private static final Color BUTTON_BG_NONEXECUTABLE_FILE_SELECTED = new Color(0, 100, 0);
+ private static final Dimension SIZE_BUTTON = new Dimension(200, 30);
+ private final Set<MenuListener> menuListeners = new HashSet<>();
+ private JPanel menuPanel;
+ private JButton selectedButton;
+ private File selectedFile;
+
+ public Menu(final File directory) {
+ buildContentPanel(directory);
+ }
+
+ /**
+ * <pre>
+ * Assign specific colors to:
+ * Directory
+ * Executable file
+ * Non executable file
+ * </pre>
+ */
+ private static Color getFileColor(final File file, final boolean selected) {
+ if (file.isDirectory())
+ if (selected)
+ return BUTTON_BG_DIRECTORY_SELECTED;
+ else
+ return BUTTON_BG_DIRECTORY;
+
+ if (file.isFile())
+ if (file.canExecute()) {
+ if (selected)
+ return BUTTON_BG_EXECUTABLE_FILE_SELECTED;
+ else
+ return BUTTON_BG_EXECUTABLE_FILE;
+ } else if (selected)
+ return BUTTON_BG_NONEXECUTABLE_FILE_SELECTED;
+ else
+ return BUTTON_BG_NONEXECUTABLE_FILE;
+
+ if (selected)
+ return BUTTON_BG_LINK_SELECTED;
+ else
+ return BUTTON_BG_LINK;
+ }
+
+ public void addMenuListener(final MenuListener menuListener) {
+ menuListeners.add(menuListener);
+ }
+
+ private JButton buildButton(final File file) {
+ JButton button;
+ if (file.isDirectory())
+ button = new JButton("[ " + file.getName() + " ]");
+ else
+ button = new JButton(file.getName());
+
+ button.setBackground(getFileColor(file, false));
+ button.setForeground(new Color(255, 255, 255));
+
+ button.setBorder(new EmptyBorder(5, 10, 5, 10));
+
+ final Menu.ButtonMouseListener mouseListener = new ButtonMouseListener(button, this, file);
+ button.addMouseListener(mouseListener);
+
+ button.setHorizontalTextPosition(SwingConstants.CENTER);
+
+ // set button size
+ Utils.setComponentSize(button, SIZE_BUTTON);
+
+ return button;
+ }
+
+ private void buildContentPanel(final File directory) {
+ menuPanel = new JPanel();
+
+ Utils.setComponentSize(menuPanel, SIZE_MENU_PANEL);
+
+ final BoxLayout layout = new BoxLayout(menuPanel, BoxLayout.Y_AXIS);
+ menuPanel.setLayout(layout);
+ menuPanel.setBackground(Color.BLACK);
+
+ final File[] listFiles = directory.listFiles();
+
+ // Sort files by name
+ Arrays.sort(listFiles, (f1, f2) -> {
+
+ // directories come before files
+ if (f1.isFile() && f2.isDirectory())
+ return 1;
+ if (f2.isFile() && f1.isDirectory())
+ return -1;
+
+ // order by file name
+ return f1.getName().toUpperCase().compareTo(f2.getName().toUpperCase());
+ });
+
+ // add files to the panel
+ for (final File file : listFiles)
+ // ignore hidden files
+ if (!file.getName().startsWith(".")) {
+ menuPanel.add(Box.createVerticalStrut(10));
+ menuPanel.add(buildButton(file));
+ }
+ }
+
+ public JPanel getMenuPanel() {
+ return menuPanel;
+ }
+
+ private JButton getSelectedButton() {
+ return selectedButton;
+ }
+
+ private File getSelectedFile() {
+ return selectedFile;
+ }
+
+ private void menuItemHighlighted(final File chosenFile) {
+ for (final MenuListener menuListener : menuListeners)
+ menuListener.menuItemHighlighted(this, chosenFile);
+ }
+
+ private void menuItemSelected(final File chosenFile) {
+ for (final MenuListener menuListener : menuListeners)
+ menuListener.menuItemSelected(chosenFile);
+ }
+
+ private void menuItemSelectedAlternative(final File chosenFile) {
+ for (final MenuListener menuListener : menuListeners)
+ menuListener.menuItemSelectedAlternative(chosenFile);
+ }
+
+ private void menuItemSelectedAlternative2(final File chosenFile) {
+ for (final MenuListener menuListener : menuListeners)
+ menuListener.menuItemSelectedAlternative2(chosenFile);
+ }
+
+ private void setSelected(final JButton selectedButton, final File selectedFile) {
+ this.selectedButton = selectedButton;
+ this.selectedFile = selectedFile;
+ }
+
+ public static class ButtonMouseListener implements MouseListener {
+
+ final JButton parentButton;
+ final Menu parentMenu;
+ final File file;
+
+ public ButtonMouseListener(final JButton button, final Menu parentMenu, final File file) {
+ parentButton = button;
+ this.parentMenu = parentMenu;
+ this.file = file;
+ }
+
+ @Override
+ public void mouseClicked(final MouseEvent e) {
+ try {
+
+ if (e.getButton() == 1) // left mouse click
+ parentMenu.menuItemSelected(file);
+ if (e.getButton() == 2) // middle mouse click
+ parentMenu.menuItemSelectedAlternative2(file);
+ if (e.getButton() == 3) //right mouse click
+ parentMenu.menuItemSelectedAlternative(file);
+ } catch (final Exception ex) {
+ new ExceptionDialog(ex);
+ }
+ }
+
+ @Override
+ public void mouseEntered(final MouseEvent e) {
+ try {
+ if (parentButton != parentMenu.getSelectedButton()) {
+
+ if (parentMenu.getSelectedButton() != null)
+ parentMenu.getSelectedButton().setBackground(getFileColor(parentMenu.getSelectedFile(), false));
+
+ parentMenu.setSelected(parentButton, file);
+ parentButton.setBackground(getFileColor(file, true));
+
+ parentMenu.menuItemHighlighted(file);
+ }
+ } catch (final Exception ex) {
+ new ExceptionDialog(ex);
+ }
+ }
+
+ @Override
+ public void mouseExited(final MouseEvent e) {
+ }
+
+ @Override
+ public void mousePressed(final MouseEvent e) {
+ }
+
+ @Override
+ public void mouseReleased(final MouseEvent e) {
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher.menu;
+
+import java.io.File;
+
+interface MenuListener {
+
+ /**
+ * mouse over
+ */
+ void menuItemHighlighted(Menu menu, java.io.File chosenFile);
+
+ /**
+ * left click
+ */
+ void menuItemSelected(File chosenFile);
+
+ /**
+ * right click
+ */
+ void menuItemSelectedAlternative(File chosenFile);
+
+ /**
+ * middle click
+ */
+ void menuItemSelectedAlternative2(File chosenFile);
+
+}
--- /dev/null
+/*
+ * Instantlauncher. Author: Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.instantlauncher.menu;
+
+import eu.svjatoslav.instantlauncher.InstantLauncher;
+import eu.svjatoslav.instantlauncher.Utils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import static eu.svjatoslav.instantlauncher.Utils.executeCommand;
+
+public class MultiLevelMenu implements MenuListener {
+
+ private static final int VERTICAL_MENUS_COUNT = 7;
+ private static final Dimension CONTENT_PANEL_SIZE = new Dimension(1024, 900);
+ private final InstantLauncher instantLauncher;
+ private final ArrayList<Menu> menus = new ArrayList<>();
+
+ private final ArrayList<JPanel> panels = new ArrayList<>();
+ private JPanel contentPane;
+
+ public MultiLevelMenu(final InstantLauncher instantLauncher) {
+ this.instantLauncher = instantLauncher;
+ }
+
+ /**
+ * Adds new vertical menu.
+ *
+ * @return <code>true</code> if operation succeeded, or <code>false</code>
+ * if there was not enough space left for a new menu.
+ */
+ private boolean addMenu(final File directory) {
+ final int newMenuHorizontalIndex = menus.size();
+
+ if (newMenuHorizontalIndex >= VERTICAL_MENUS_COUNT) {
+ return false;
+ }
+
+ final Menu menu = new Menu(directory);
+ menu.addMenuListener(this);
+
+ final JPanel placeholderPanel = panels.get(newMenuHorizontalIndex);
+ final JPanel menuPanel = menu.getMenuPanel();
+ placeholderPanel.add(menuPanel, BorderLayout.CENTER);
+
+ menus.add(menu);
+ return true;
+ }
+
+ public JPanel buildContentPanel() {
+
+ contentPane = new JPanel();
+ contentPane.setBackground(Color.BLACK);
+ contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.X_AXIS));
+ Utils.setComponentSize(contentPane, CONTENT_PANEL_SIZE);
+
+ // initialize panels
+ for (int i = 0; i < VERTICAL_MENUS_COUNT; i++) {
+ contentPane.add(Box.createHorizontalStrut(10));
+
+ final JPanel panel = new JPanel(new BorderLayout());
+ panel.setBackground(Color.BLACK);
+
+ Utils.setComponentSize(panel, Menu.SIZE_MENU_PANEL);
+
+ panels.add(panel);
+ contentPane.add(panel);
+ }
+
+ final File directory = instantLauncher.configurationManager.getConfiguration().getNavigationRootDirectory();
+ addMenu(directory);
+
+ return contentPane;
+ }
+
+ private int getMenuIndex(final Menu menu) {
+ int i = 0;
+ for (final Menu m : menus) {
+ if (m == menu) {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ }
+
+ @Override
+ public void menuItemHighlighted(final Menu menu, final File chosenFile) {
+
+ if (chosenFile.isDirectory()) {
+ final int menuIndex = getMenuIndex(menu);
+
+ if (menuIndex >= 0) {
+ removeMenus(menuIndex + 1);
+ if (addMenu(chosenFile)) {
+ contentPane.validate();
+ contentPane.repaint();
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public void menuItemSelected(final File chosenFile) {
+ if (chosenFile.isFile()) {
+ if (chosenFile.canExecute()) {
+ executeCommand(chosenFile.getAbsolutePath());
+ InstantLauncher.exitProgram();
+ } else {
+ boolean fileOpened;
+ try {
+ fileOpened = instantLauncher.openFile(chosenFile.getCanonicalFile());
+ if (fileOpened)
+ InstantLauncher.exitProgram();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ if (chosenFile.isDirectory()) {
+ try {
+ instantLauncher.openDirectory(chosenFile.getCanonicalFile());
+ InstantLauncher.exitProgram();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void menuItemSelectedAlternative(final File chosenFile) {
+ if (chosenFile.isFile())
+ chosenFile.setExecutable(!chosenFile.canExecute());
+ else {
+ instantLauncher.openDirectoryInTerminal(chosenFile);
+ InstantLauncher.exitProgram();
+ }
+ }
+
+ private void removeMenus(final int fromIndex) {
+
+ for (int i = fromIndex; i < menus.size(); i++) {
+ final JPanel jPanel = panels.get(i);
+ jPanel.removeAll();
+ }
+
+ while (menus.size() > fromIndex) {
+ menus.remove(fromIndex);
+ }
+ }
+
+ @Override
+ public void menuItemSelectedAlternative2(File chosenFile) {
+ // TODO: define some middle mouse click functions
+ }
+
+
+}
--- /dev/null
+broken
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+
+xclock
+
+
+
--- /dev/null
+another directory/
\ No newline at end of file