Skip to content

Commit

Permalink
UI for configuring favorites
Browse files Browse the repository at this point in the history
  • Loading branch information
Riekr committed Oct 31, 2022
1 parent f7edef6 commit cc710aa
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 74 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## In progress:

## v0.2.8
- General:
- Favorites are ordered as in file
- UI for configuring favorites

## v0.2.7
- General:
Expand Down
125 changes: 69 additions & 56 deletions src/main/java/org/riekr/jloga/prefs/Favorites.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.jetbrains.annotations.NotNull;
import org.riekr.jloga.Main;
import org.riekr.jloga.io.LinkedProperties;
import org.riekr.jloga.ui.MenuSelectedListener;
Expand All @@ -15,74 +17,85 @@
public final class Favorites {
private Favorites() {}

private static final class Lazy {
private Lazy() {}
private static final ArrayList<JComponent> _COMPONENTS = new ArrayList<>();
private static JPopupMenu _MENU;

public static final JPopupMenu MENU;
static {
refreshMenu();
Preferences.USER_FAVORITES.subscribe((data) -> refreshMenu());
}

static {
String favoritesFileName = System.getProperty("jloga.favorites");
JPopupMenu menu = null;
if (favoritesFileName != null && !(favoritesFileName = favoritesFileName.trim()).isEmpty()) {
List<JMenuItem> menuItems = new ArrayList<>();
try (FileReader reader = new FileReader(favoritesFileName)) {
LinkedProperties props = new LinkedProperties();
props.load(reader);
for (Map.Entry<Object, Object> entry : props.linkedEntrySet()) {
String title = entry.getKey().toString();
File folder = new File(entry.getValue().toString());
if (folder.isDirectory())
menuItems.add(scan(folder, title));
}
if (!menuItems.isEmpty()) {
menu = new JPopupMenu();
menuItems.forEach(menu::add);
}
} catch (Throwable e) {
System.err.println("Unable to load favorites from: " + favoritesFileName);
e.printStackTrace(System.err);
menu = null;
}
@NotNull
private static Stream<Map.Entry<Object, Object>> getSystemFavorites() {
String favoritesFileName = System.getProperty("jloga.favorites");
if (favoritesFileName != null && !(favoritesFileName = favoritesFileName.trim()).isEmpty()) {
try (FileReader reader = new FileReader(favoritesFileName)) {
LinkedProperties props = new LinkedProperties();
props.load(reader);
return props.entrySet().stream();
} catch (Throwable e) {
System.err.println("Unable to load system favorites from: " + favoritesFileName);
e.printStackTrace(System.err);
}
MENU = menu;
}
return Stream.empty();
}

private static JMenuItem scan(File folder) {
return scan(folder, folder.getName());
private static void refreshMenu() {
List<JMenuItem> menuItems = new ArrayList<>();
Stream.concat(
getSystemFavorites(),
Preferences.USER_FAVORITES.get().entrySet().stream()
).forEach(entry -> {
String title = entry.getKey().toString();
File folder = new File(entry.getValue().toString());
if (folder.isDirectory())
menuItems.add(scan(folder, title));
});
if (!menuItems.isEmpty()) {
JPopupMenu menu = new JPopupMenu();
menuItems.forEach(menu::add);
_MENU = menu;
_COMPONENTS.forEach(comp -> {
comp.setComponentPopupMenu(_MENU);
comp.setEnabled(true);
});
}
}

private static JMenuItem scan(File file, String title) {
if (file.isDirectory()) {
File[] files = file.listFiles();
JMenu menu = new JMenu(title);
if (files != null && files.length > 0) {
menu.addMenuListener((MenuSelectedListener)e -> {
menu.removeAll();
for (File child : files)
menu.add(scan(child));
});
} else
menu.add(new JMenuItem("<empty>"));
menu.setIcon(new TextIcon(menu, "\uD83D\uDCC1"));
menu.setIconTextGap(0);
return menu;
} else {
JMenuItem container = new JMenuItem(title);
container.setIcon(new TextIcon(container, "\uD83D\uDDB9"));
container.setIconTextGap(0);
container.addActionListener((e) -> Main.getMain().openFile(file));
return container;
}
}
private static JMenuItem scan(File folder) {
return scan(folder, folder.getName());
}

public static boolean hasFavorites() {
return Lazy.MENU != null;
private static JMenuItem scan(File file, String title) {
if (file.isDirectory()) {
File[] files = file.listFiles();
JMenu menu = new JMenu(title);
if (files != null && files.length > 0) {
menu.addMenuListener((MenuSelectedListener)e -> {
menu.removeAll();
for (File child : files)
menu.add(scan(child));
});
} else
menu.add(new JMenuItem("<empty>"));
menu.setIcon(new TextIcon(menu, "\uD83D\uDCC1"));
menu.setIconTextGap(0);
return menu;
} else {
JMenuItem container = new JMenuItem(title);
container.setIcon(new TextIcon(container, "\uD83D\uDDB9"));
container.setIconTextGap(0);
container.addActionListener((e) -> Main.getMain().openFile(file));
return container;
}
}

public static void setup(JComponent component) {
if (hasFavorites())
component.setComponentPopupMenu(Lazy.MENU);
if (_MENU != null)
component.setComponentPopupMenu(_MENU);
component.setEnabled(_MENU != null);
_COMPONENTS.add(component);
}

}
161 changes: 161 additions & 0 deletions src/main/java/org/riekr/jloga/prefs/FileMapComponent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package org.riekr.jloga.prefs;

import static java.util.Arrays.asList;
import static javax.swing.SwingUtilities.invokeLater;
import static org.riekr.jloga.utils.FileUtils.selectDirectoryDialog;
import static org.riekr.jloga.utils.MouseListenerBuilder.mouse;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.Map;
import java.util.Objects;
import java.util.Vector;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class FileMapComponent extends JScrollPane {

private static final String REMOVE = "\u274C";
private static final String CHANGE = "\uD83D\uDCC2";

private static final Vector<String> _COLUMNS = new Vector<>(asList("Nick", "Folder", "", ""));

private final JTable _table = new JTable() {
@Override public boolean isCellEditable(int row, int column) {return isDataColumn(column);}

@Override public boolean isCellSelected(int row, int column) {return isDataColumn(column);}
};

private final Vector<Vector<Object>> _data = new Vector<>();
private final DefaultTableModel _model = new DefaultTableModel(_data, _COLUMNS);

private final Supplier<Iterable<Map.Entry<Object, Object>>> _listSupplier;
private final Consumer<Object> _removeAction;
private final BiConsumer<Object, Object> _addAction;

private Runnable _onEditingStopAction;

public FileMapComponent(
Supplier<Iterable<Map.Entry<Object, Object>>> listSupplier,
Consumer<Object> removeAction,
BiConsumer<Object, Object> addAction
) {
_listSupplier = listSupplier;
_removeAction = removeAction;
_addAction = addAction;
_table.setModel(_model);
_table.setShowGrid(false);
_table.addMouseListener(mouse().onClick(this::onClick));
setViewportView(_table);
reload();
_table.addPropertyChangeListener(evt -> {
if ("tableCellEditor".equals(evt.getPropertyName())) {
if (_table.isEditing())
invokeLater(this::onEditingStart);
else
invokeLater(this::onEditingStop);
}
});
}

public void reload() {
_data.clear();
_listSupplier.get().forEach(entry -> {
Vector<Object> row = new Vector<>(2);
row.add(entry.getKey()); // 0
row.add(entry.getValue()); // 1
row.add(REMOVE); // 2
row.add(CHANGE); // 3
_data.add(row);
});
_data.add(new Vector<>(asList("", "", "", CHANGE)));
_model.setDataVector(_data, _COLUMNS);
_table.tableChanged(null);
invokeLater(this::fixColumnWidths);
}

private void fixColumnWidths() {
TableColumnModel columnModel = _table.getColumnModel();
int h = _table.getRowHeight();
for (int i = 0, len = columnModel.getColumnCount(); i < len; i++) {
if (!isDataColumn(i))
columnModel.getColumn(i).setMaxWidth(h);
}
}

private boolean isDataColumn(int column) {
switch (column) {
case 0:
case 1:
return true;
}
return false;
}

private void onClick(MouseEvent evt) {
Point point = evt.getPoint();
int row = _table.rowAtPoint(point);
if (row >= 0) {
int col = _table.columnAtPoint(point);
switch (col) {
case 2:
_removeAction.accept(_data.get(row).get(0));
break;
case 3:
Object oldVal = _data.get(row).get(1);
Object newVal = selectDirectoryDialog(this, oldVal instanceof File ? (File)oldVal : new File("."));
if (!Objects.equals(oldVal, newVal))
_addAction.accept(_data.get(row).get(0), newVal);
break;
}
}
}

private void onEditingStart() {
int editingRow = _table.getEditingRow();
int editingCol = _table.getEditingColumn();
Object editingKey = _data.get(editingRow).get(0);
Object editingVal = _data.get(editingRow).get(1);
if ("".equals(editingVal) || "".equals(editingKey))
return;
switch (editingCol) {
case 0: {
// System.out.println("KEY START " + editingKey + " " + editingRow + "," + editingCol);
_onEditingStopAction = () -> {
Object newKey = _data.get(editingRow).get(0);
if (!editingKey.equals(newKey)) {
Object value = _data.get(editingRow).get(1);
// System.out.println("KEY CHANGE " + newKey + " " + editingRow + "," + editingCol);
_removeAction.accept(editingKey);
_addAction.accept(newKey, value);
}
};
}
break;
case 1: {
// System.out.println("VALUE START " + editingKey + " " + editingRow + "," + editingCol);
_onEditingStopAction = () -> {
Object newValue = _data.get(editingRow).get(1);
if (!editingVal.equals(newValue)) {
File file = new File(newValue.toString());
// System.out.println("VALUE CHANGE " + file + " " + editingRow + "," + editingCol);
_addAction.accept(editingKey, file);
}
};
}
}
}

private void onEditingStop() {
if (_onEditingStopAction != null) {
_onEditingStopAction.run();
_onEditingStopAction = null;
}
}

}
2 changes: 1 addition & 1 deletion src/main/java/org/riekr/jloga/prefs/GUIPreference.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
public class GUIPreference<T> implements Preference<T> {

public enum Type {
Font, Combo, Toggle, Executable, Directory, KeyBinding
Font, Combo, Toggle, Executable, Directory, KeyBinding, FileMap
}

public final BoolBehaviourSubject enabled = new BoolBehaviourSubject(true);
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/org/riekr/jloga/prefs/PrefPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ public PrefPanel(Frame parent) {
case KeyBinding:
panel.add(newKeyBindingComponent((GUIPreference<KeyStroke>)p));
break;
case FileMap:
panel.add(newFileMapComponent((GUIPreference<LinkedHashMap<Object, Object>>)p));
break;

default:
System.err.println("PREFERENCE TYPE " + p.type() + " NOT IMPLEMENTED YET!");
Expand Down Expand Up @@ -239,7 +242,6 @@ private Component newExecComponent(GUIPreference<File> filePref) {
return res;
}

@SuppressWarnings("serial")
private Component newKeyBindingComponent(GUIPreference<KeyStroke> keyPref) {
KeyStrokeToggleButton btn = register(new KeyStrokeToggleButton(_allButtons, keyPref) {
@Override
Expand All @@ -263,6 +265,20 @@ protected void enableOthers() {
return res;
}

private Component newFileMapComponent(GUIPreference<LinkedHashMap<Object, Object>> pref) {
pref.tap((p) -> {
if (p.isEmpty())
p.put("TEST", new File("test/folder/"));
});
FileMapComponent comp = new FileMapComponent(
() -> pref.get().entrySet(),
(key) -> pref.tap(p -> p.remove(key)),
(k, v) -> pref.tap(p -> p.put(k, v))
);
pref.subscribe(comp, (data) -> comp.reload());
return comp;
}

@Override
public void dispose() {
_LAST_SELECTED_TAB = _tabs.getSelectedIndex();
Expand Down

0 comments on commit cc710aa

Please sign in to comment.