Skip to content

Commit

Permalink
Merge pull request #77 from emustudio/feature-76
Browse files Browse the repository at this point in the history
Feature 76
  • Loading branch information
vbmacher authored Apr 4, 2023
2 parents 514b425 + 1f0bfb1 commit bd409f0
Show file tree
Hide file tree
Showing 10 changed files with 460 additions and 46 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ dependencies {
implementation 'net.jcip:jcip-annotations:1.0'

testImplementation 'junit:junit:4.13.2'
testImplementation 'org.easymock:easymock:5.0.1'
testImplementation 'org.easymock:easymock:5.1.0'
}

java {
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/net/emustudio/emulib/plugins/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@

/**
* A plugin context is a runtime structure implemented and used by plugins in order to communicate between each other.
*
* <p>
* At first, plugins should register their contexts in the plugin root class constructor. Then, within plugin
* initialization, it can requests contexts of other plugins.
*
* initialization, it can request contexts of other plugins.
* <p>
* Whether they get the requested context is given by several factors, like the plugins must be interconnected in
* the abstract schema, and the contexts must be compatible.
*
* <p>
* Each plugin can implement none, one or more contexts. Implementing the same interface multiple times is allowed.
* A plugin can also extend this context interface (directly or indirectly).
*
* <p>
* Another requirement is to annotate custom context interfaces with {@link PluginContext} annotation.
*
* <p>
* Contexts should be thread-safe, since there's no guarantee in which threads plugins communicate.
*/
@PluginContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,11 @@
*/
package net.emustudio.emulib.plugins.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;

/**
* Indicates that the annotated interface represent plugin context.
*
* <p>
* This annotation should be used only on classes.
*/
@SuppressWarnings("unused")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* This file is part of emuLib.
*
* Copyright (C) 2006-2023 Peter Jakubčo
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package net.emustudio.emulib.runtime.interaction;

import javax.swing.*;
import java.nio.file.Path;
import java.util.function.Consumer;

/**
* BrowseButton
* <p>
* A button with "Browse..." text, opening a dialog for selecting files or directories.
*/
@SuppressWarnings("unused")
public class BrowseButton extends JButton {
private final LimitedCache<Path> pathCache = new LimitedCache<>(10);

/**
* Constructs new BrowseButton with directory-choosing action
*
* @param dialogs emuStudio dialogs
* @param dialogTitle Open/Save dialog title
* @param approveButtonText Approve button text (in the Open/Save dialog)
* @param onApprove Approved path consumer
*/
public BrowseButton(Dialogs dialogs, String dialogTitle, String approveButtonText, Consumer<Path> onApprove) {
super("Browse...");
this.addActionListener(e -> dialogs
.chooseDirectory(dialogTitle, approveButtonText, getCurrentDirectory())
.ifPresent(path -> {
pathCache.put(path);
onApprove.accept(path);
}));
}

/**
* Constructs new BrowseButton with file-choosing action.
*
* @param dialogs emuStudio dialogs
* @param dialogTitle Open/Save dialog title
* @param approveButtonText Approve button text (in the Open/Save dialog)
* @param onApprove Approved path consumer
* @param appendMissingExtensions Append extension to selected file if it doesn't have it (useful for Save dialog)
* @param filters list of file filters
*/
public BrowseButton(Dialogs dialogs, String dialogTitle, String approveButtonText,
boolean appendMissingExtensions,
Consumer<Path> onApprove, FileExtensionsFilter... filters) {
super("Browse...");
addActionListener(e -> dialogs
.chooseFile(dialogTitle, approveButtonText, getCurrentDirectory(), appendMissingExtensions, filters)
.ifPresent(path -> {
pathCache.put(path);
onApprove.accept(path);
}));
}

private Path getCurrentDirectory() {
return pathCache.first().orElse(Path.of(System.getProperty("user.dir")));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* This file is part of emuLib.
*
* Copyright (C) 2006-2023 Peter Jakubčo
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package net.emustudio.emulib.runtime.interaction;

import javax.swing.*;
import java.util.Iterator;

/**
* Cached ComboBox model. Saves up to 10 items
*/
@SuppressWarnings("unused")
public class CachedComboBoxModel<T> extends AbstractListModel<T> implements ComboBoxModel<T> {
private final LimitedCache<T> cache = new LimitedCache<>(10);
private Object selected;

public void add(T item) {
int origSize = cache.getSize();
cache.put(item);
if (origSize < cache.getSize()) {
fireIntervalAdded(this, cache.getSize() - 1, cache.getSize() -1);
}
fireContentsChanged(this, 0, cache.getSize() - 1);
if (selected == null && cache.getSize() > 0) {
cache.first().ifPresent(f -> selected = f);
}
}

@Override
public void setSelectedItem(Object item) {
if ((selected != null && !selected.equals(item)) || selected == null && item != null) {
this.selected = item;
fireContentsChanged(this, -1, -1);
}
}

@Override
public Object getSelectedItem() {
return selected;
}

@Override
public int getSize() {
return cache.getSize();
}

@Override
public T getElementAt(int index) {
int i = 0;
Iterator<T> it = cache.iterator();
while (it.hasNext()) {
T item = it.next();
if (i++ == index) {
return item;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,4 +246,23 @@ Optional<Path> chooseFile(String title, String approveButtonText, Path baseDirec
*/
Optional<Path> chooseFile(String title, String approveButtonText, Path baseDirectory, boolean appendMissingExtension,
List<FileExtensionsFilter> filters);

/**
* Ask user to choose a directory.
*
* @param title dialog title
* @param approveButtonText approve button text (e.g. "Open", "Save", ...)
* @return Selected directory if provided, or Optional.empty() if user cancelled the dialog
*/
Optional<Path> chooseDirectory(String title, String approveButtonText);

/**
* Ask user to choose a directory.
*
* @param title dialog title
* @param approveButtonText approve button text (e.g. "Open", "Save", ...)
* @param baseDirectory Base directory of the dialog (where will the dialog point to)
* @return Selected directory if provided, or Optional.empty() if user cancelled the dialog
*/
Optional<Path> chooseDirectory(String title, String approveButtonText, Path baseDirectory);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* This file is part of emuLib.
*
* Copyright (C) 2006-2023 Peter Jakubčo
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
package net.emustudio.emulib.runtime.interaction;

import net.jcip.annotations.NotThreadSafe;

import java.util.*;

/**
* Limited Cache
* A cache with max items limit, retaining most used items, and removing the least used items above given limit.
*/
@NotThreadSafe
public class LimitedCache<V> {
private final SortedMap<Integer, V> cache = new TreeMap<>();

private final int limit;

/**
* Constructs a new cache.
*
* @param limit the cache is allowed to contain only given number of items
*/
public LimitedCache(int limit) {
this.limit = limit;
}

/**
* Clears the cache
*/
public void clear() {
cache.clear();
}

/**
* Adds new value to the cache.
* If the cache items are above the limit, the least used item is removed.
* Added value is always considered as the most used.
*
* @param value value to be cached
*/
public void put(V value) {
Map<V, Integer> newCache = new HashMap<>();

int index = 0;
newCache.put(value, index++);
for (Map.Entry<Integer, V> e : cache.entrySet()) {
if (!e.getValue().equals(value)) {
newCache.put(e.getValue(), index++);
}
}

cache.clear();
for (Map.Entry<V, Integer> e : newCache.entrySet()) {
cache.put(e.getValue(), e.getKey());
}

if (index > limit) {
cache.remove(cache.lastKey());
}
}

/**
* Creates an iterator of values stored in this cache.
* The values are returned in the "most recently used" order.
* A value inserted more than once will not increase cache size.
*
* @return cached values iterator
*/
public Iterator<V> iterator() {
return cache.values().iterator();
}

/**
* Get the first (the most recently used) value from the cache, if it exists.
*
* @return the most recently used value from the cache
*/
public Optional<V> first() {
Iterator<V> it = iterator();
if (it.hasNext()) {
return Optional.of(it.next());
}
return Optional.empty();
}

/**
* Gets the cache limit
*
* @return cache limit
*/
public int getLimit() {
return limit;
}

/**
* Gets the cache size (number of items stored in the cache).
* If a value was stored multiple times, it is considered as put once.
*
* @return cache size
*/
public int getSize() {
return cache.size();
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@

import static javax.swing.Action.SHORT_DESCRIPTION;

/**
* Toolbar button - a button ready to add to a toolbar.
* Properties:
* - button text is hidden
* - tooltip is set from Action.getValue(SHORT_DESCRIPTION) by default
* - is not focusable
* - icon is set from icon resource path
* - button action is external
*/
@SuppressWarnings("unused")
public class ToolbarButton extends JButton {

public ToolbarButton(Action action, String iconResource) {
Expand Down
Loading

0 comments on commit bd409f0

Please sign in to comment.