initial AI slop commit. to be refactored!
This commit is contained in:
+40
@@ -0,0 +1,40 @@
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
.kotlin
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
/.idea/
|
||||
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
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>com.rdesk</groupId>
|
||||
<artifactId>rdesk</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Source: https://mvnrepository.com/artifact/com.google.code.gson/gson -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.14.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- Source: https://mvnrepository.com/artifact/com.formdev/flatlaf -->
|
||||
<dependency>
|
||||
<groupId>com.formdev</groupId>
|
||||
<artifactId>flatlaf</artifactId>
|
||||
<version>3.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.rdesk;
|
||||
|
||||
import com.rdesk.applications.system.Exit;
|
||||
import com.rdesk.applications.productivity.Notes;
|
||||
import com.rdesk.applications.system.TaskManager;
|
||||
import com.rdesk.applications.system.filehaj.FileExplorerApp;
|
||||
import com.rdesk.kernel.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class Main {
|
||||
|
||||
private static final boolean RUN_IN_FS = true;
|
||||
private static final boolean USE_SYSDEFAULT_LAF = false;
|
||||
private static final boolean USE_FLATLAF = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SwingUtilities.invokeLater(Main::boot);
|
||||
}
|
||||
|
||||
private static void boot() {
|
||||
|
||||
Kernel kernel = Kernel.get();
|
||||
kernel.boot();
|
||||
|
||||
JFrame frame = new JFrame("Java OS");
|
||||
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||
frame.addWindowListener(new java.awt.event.WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(java.awt.event.WindowEvent e) {
|
||||
kernel.shutdown();
|
||||
}
|
||||
});
|
||||
|
||||
if (USE_FLATLAF) {
|
||||
try {
|
||||
UIManager.setLookAndFeel(
|
||||
"com.formdev.flatlaf.themes.FlatMacLightLaf"
|
||||
);
|
||||
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
|
||||
JOptionPane.showMessageDialog(
|
||||
null,
|
||||
ex.getMessage() + "@USE_FLATLAF"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (USE_SYSDEFAULT_LAF) {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
|
||||
JOptionPane.showMessageDialog(
|
||||
null,
|
||||
ex.getMessage() + "@USE_SYSDEFAULT_LAF"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (RUN_IN_FS) {
|
||||
frame.setUndecorated(true);
|
||||
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
|
||||
} else {
|
||||
frame.setSize(1200, 800);
|
||||
frame.setLocationRelativeTo(null);
|
||||
}
|
||||
|
||||
JDesktopPane desktop = new JDesktopPane();
|
||||
kernel.windows().attachDesktop(desktop);
|
||||
|
||||
AppLauncher launcher = new AppLauncher(kernel);
|
||||
|
||||
registerSystemApps(launcher);
|
||||
|
||||
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, launcher, desktop);
|
||||
split.setDividerSize(0);
|
||||
split.setEnabled(false);
|
||||
|
||||
frame.setContentPane(split);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
private static void registerSystemApps(AppLauncher launcher) {
|
||||
launcher.addApp(new Notes(), "Productivity");
|
||||
launcher.addApp(new Exit(), "System");
|
||||
launcher.addApp(new TaskManager(), "System");
|
||||
launcher.addApp(new FileExplorerApp(), "System");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.rdesk.applications.productivity;
|
||||
import com.rdesk.kernel.*;
|
||||
import javax.swing.*;
|
||||
|
||||
public class Notes implements DesktopApp {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "notes";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Notes";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JInternalFrame createWindow(Kernel kernel) {
|
||||
|
||||
JInternalFrame frame = new JInternalFrame(
|
||||
"Notes",
|
||||
true, // resizable
|
||||
true, // closable
|
||||
true, // maximizable
|
||||
true // iconifiable
|
||||
);
|
||||
|
||||
frame.setSize(400, 300);
|
||||
frame.setLayout(new java.awt.BorderLayout());
|
||||
|
||||
JTextArea textArea = new JTextArea();
|
||||
frame.add(new JScrollPane(textArea), java.awt.BorderLayout.CENTER);
|
||||
|
||||
frame.setVisible(true);
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.rdesk.applications.system;
|
||||
|
||||
import com.rdesk.kernel.DesktopApp;
|
||||
import com.rdesk.kernel.Kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class Exit implements DesktopApp {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "exit";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "System Exit";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JInternalFrame createWindow(Kernel kernel) {
|
||||
JInternalFrame frame = new JInternalFrame("System Controls", true, true);
|
||||
frame.setSize(350, 180);
|
||||
frame.setResizable(false);
|
||||
|
||||
JPanel mainPanel = new JPanel(new GridBagLayout());
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbc.insets = new Insets(5, 5, 5, 5);
|
||||
|
||||
JLabel label = new JLabel("Choose an action:");
|
||||
label.setFont(label.getFont().deriveFont(Font.BOLD));
|
||||
mainPanel.add(label, gbc);
|
||||
|
||||
JButton shutdownButton = new JButton("Shutdown OS");
|
||||
shutdownButton.setFocusPainted(false);
|
||||
shutdownButton.addActionListener(e -> confirmAndShutdown(kernel, frame));
|
||||
mainPanel.add(shutdownButton, gbc);
|
||||
|
||||
JButton cancelButton = new JButton("Cancel");
|
||||
cancelButton.setFocusPainted(false);
|
||||
cancelButton.addActionListener(e -> frame.dispose());
|
||||
mainPanel.add(cancelButton, gbc);
|
||||
|
||||
frame.add(mainPanel);
|
||||
frame.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
|
||||
return frame;
|
||||
}
|
||||
|
||||
private void confirmAndShutdown(Kernel kernel, JInternalFrame frame) {
|
||||
int choice = JOptionPane.showConfirmDialog(
|
||||
frame,
|
||||
"Are you sure you want to shut down the operating system?\nAll unsaved work will be lost.",
|
||||
"Confirm Shutdown",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
if (choice == JOptionPane.YES_OPTION) {
|
||||
frame.dispose(); // close the confirmation window first
|
||||
kernel.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
package com.rdesk.applications.system;
|
||||
|
||||
import com.rdesk.kernel.DesktopApp;
|
||||
import com.rdesk.kernel.Kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.event.InternalFrameAdapter;
|
||||
import javax.swing.event.InternalFrameEvent;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryUsage;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TaskManager implements DesktopApp {
|
||||
|
||||
private ScheduledExecutorService scheduler;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "taskmanager";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Task Manager";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JInternalFrame createWindow(Kernel kernel) {
|
||||
JInternalFrame frame = new JInternalFrame("Task Manager", true, true, true, true);
|
||||
frame.setSize(750, 550);
|
||||
frame.setMinimumSize(new Dimension(600, 400));
|
||||
frame.setResizable(true);
|
||||
|
||||
JTabbedPane tabbedPane = new JTabbedPane();
|
||||
|
||||
// ---- Processes Tab ----
|
||||
JPanel processesPanel = createProcessesPanel(kernel);
|
||||
tabbedPane.addTab("Processes", processesPanel);
|
||||
|
||||
// ---- Performance Tab ----
|
||||
JPanel performancePanel = createPerformancePanel();
|
||||
tabbedPane.addTab("Performance", performancePanel);
|
||||
|
||||
frame.add(tabbedPane, BorderLayout.CENTER);
|
||||
|
||||
// Start background updater for the graphs
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
Runnable updateTask = () -> {
|
||||
if (!frame.isVisible()) return;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
// Update performance graphs
|
||||
Component[] comps = performancePanel.getComponents();
|
||||
for (Component c : comps) {
|
||||
if (c instanceof GraphPanel) {
|
||||
((GraphPanel) c).addDataPoint();
|
||||
((GraphPanel) c).repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
scheduler.scheduleAtFixedRate(updateTask, 0, 1, TimeUnit.SECONDS);
|
||||
|
||||
frame.addInternalFrameListener(new InternalFrameAdapter() {
|
||||
@Override
|
||||
public void internalFrameClosed(InternalFrameEvent e) {
|
||||
if (scheduler != null && !scheduler.isShutdown()) {
|
||||
scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frame.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
|
||||
return frame;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Processes Tab
|
||||
// ------------------------------------------------------------------
|
||||
private JPanel createProcessesPanel(Kernel kernel) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
|
||||
ProcessTableModel model = new ProcessTableModel(kernel);
|
||||
JTable table = new JTable(model);
|
||||
table.setFillsViewportHeight(true);
|
||||
table.getColumnModel().getColumn(0).setPreferredWidth(200);
|
||||
table.getColumnModel().getColumn(1).setPreferredWidth(100);
|
||||
table.getColumnModel().getColumn(2).setPreferredWidth(80);
|
||||
table.setRowHeight(22);
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(table);
|
||||
panel.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||
JButton endTaskButton = new JButton("End Task");
|
||||
endTaskButton.addActionListener(e -> {
|
||||
int selected = table.getSelectedRow();
|
||||
if (selected >= 0) {
|
||||
String appId = model.getAppIdAt(selected);
|
||||
confirmAndKill(kernel, appId, model);
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(panel, "Select a task to end.", "No Selection", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
});
|
||||
JButton refreshButton = new JButton("Refresh");
|
||||
refreshButton.addActionListener(e -> model.refresh());
|
||||
toolbar.add(endTaskButton);
|
||||
toolbar.add(refreshButton);
|
||||
panel.add(toolbar, BorderLayout.SOUTH);
|
||||
|
||||
Timer timer = new Timer(2000, e -> model.refresh());
|
||||
timer.start();
|
||||
panel.putClientProperty("timer", timer);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void confirmAndKill(Kernel kernel, String appId, ProcessTableModel model) {
|
||||
/*
|
||||
int confirm = JOptionPane.showConfirmDialog(null,
|
||||
|
||||
"End task '" + appId + "'?\nAll unsaved work will be lost.",
|
||||
"Confirm End Task",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
if (confirm == JOptionPane.YES_OPTION) {
|
||||
*/
|
||||
kernel.windows().closeAllByAppId(appId);
|
||||
model.refresh();
|
||||
|
||||
}
|
||||
|
||||
private JPanel createPerformancePanel() {
|
||||
JPanel panel = new JPanel(new GridLayout(2, 1, 10, 10));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
long maxMemoryMB = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax() / (1024 * 1024);
|
||||
GraphPanel cpuGraph = new GraphPanel("CPU Usage", "%", Color.BLUE, 0, 100);
|
||||
GraphPanel memoryGraph = new GraphPanel("Memory Usage", "MB", Color.GREEN, 0, (int) maxMemoryMB);
|
||||
|
||||
panel.add(cpuGraph);
|
||||
panel.add(memoryGraph);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private static class GraphPanel extends JPanel {
|
||||
private final String title;
|
||||
private final String unit;
|
||||
private final Color lineColor;
|
||||
private final int maxY;
|
||||
private final LinkedList<Integer> data = new LinkedList<>();
|
||||
private final int maxDataPoints = 60; // 60 points = 1 minute at 1 update/sec
|
||||
|
||||
public GraphPanel(String title, String unit, Color lineColor, int minY, int maxY) {
|
||||
this.title = title;
|
||||
this.unit = unit;
|
||||
this.lineColor = lineColor;
|
||||
this.maxY = maxY;
|
||||
setBackground(Color.WHITE);
|
||||
setBorder(BorderFactory.createTitledBorder(title));
|
||||
// Initialize with zeros
|
||||
for (int i = 0; i < maxDataPoints; i++) data.add(0);
|
||||
setPreferredSize(new Dimension(400, 200));
|
||||
}
|
||||
|
||||
public void addDataPoint() {
|
||||
int value = 0;
|
||||
if (title.contains("CPU")) {
|
||||
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
|
||||
double load = osBean.getSystemLoadAverage();
|
||||
if (load > 0) {
|
||||
int processors = osBean.getAvailableProcessors();
|
||||
double cpuPercent = (load / processors) * 100;
|
||||
value = (int) Math.min(Math.max(cpuPercent, 0), maxY);
|
||||
}
|
||||
} else if (title.contains("Memory")) {
|
||||
MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
|
||||
long usedMB = heap.getUsed() / (1024 * 1024);
|
||||
value = (int) Math.min(usedMB, maxY);
|
||||
}
|
||||
synchronized (data) {
|
||||
data.addLast(value);
|
||||
if (data.size() > maxDataPoints) data.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
int leftMargin = 50;
|
||||
int rightMargin = 20;
|
||||
int topMargin = 40; // increased for title space
|
||||
int bottomMargin = 30;
|
||||
int graphWidth = w - leftMargin - rightMargin;
|
||||
int graphHeight = h - topMargin - bottomMargin;
|
||||
|
||||
if (graphWidth <= 0 || graphHeight <= 0) return;
|
||||
|
||||
// Clear background
|
||||
g2.setColor(Color.WHITE);
|
||||
g2.fillRect(0, 0, w, h);
|
||||
|
||||
// Draw axes
|
||||
g2.setColor(Color.BLACK);
|
||||
g2.drawLine(leftMargin, topMargin, leftMargin, h - bottomMargin);
|
||||
g2.drawLine(leftMargin, h - bottomMargin, w - rightMargin, h - bottomMargin);
|
||||
|
||||
// Draw grid lines (horizontal)
|
||||
g2.setColor(Color.LIGHT_GRAY);
|
||||
for (int i = 0; i <= 4; i++) {
|
||||
int y = topMargin + (graphHeight * i / 4);
|
||||
g2.drawLine(leftMargin, y, w - rightMargin, y);
|
||||
}
|
||||
|
||||
// Draw Y-axis labels
|
||||
g2.setColor(Color.BLACK);
|
||||
FontMetrics fm = g2.getFontMetrics();
|
||||
for (int i = 0; i <= 4; i++) {
|
||||
int value = maxY - (maxY * i / 4);
|
||||
String label = String.valueOf(value);
|
||||
int labelWidth = fm.stringWidth(label);
|
||||
int y = topMargin + (graphHeight * i / 4) + fm.getHeight() / 2 - 2;
|
||||
g2.drawString(label, leftMargin - labelWidth - 5, y);
|
||||
}
|
||||
|
||||
// Draw unit on Y axis
|
||||
g2.drawString(unit, leftMargin - 25, topMargin - 5);
|
||||
|
||||
// Plot data
|
||||
if (data.isEmpty()) return;
|
||||
synchronized (data) {
|
||||
Path2D path = new Path2D.Double();
|
||||
double xStep = (double) graphWidth / (data.size() - 1);
|
||||
boolean first = true;
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
double x = leftMargin + i * xStep;
|
||||
double value = data.get(i);
|
||||
double y = h - bottomMargin - (value * graphHeight / maxY);
|
||||
if (first) {
|
||||
path.moveTo(x, y);
|
||||
first = false;
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
g2.setColor(lineColor);
|
||||
g2.setStroke(new BasicStroke(2f));
|
||||
g2.draw(path);
|
||||
}
|
||||
|
||||
// Draw current value in top-right corner
|
||||
int currentValue = data.isEmpty() ? 0 : data.getLast();
|
||||
String valueStr = String.format("Current: %d %s", currentValue, unit);
|
||||
g2.setColor(Color.BLACK);
|
||||
Font smallFont = g2.getFont().deriveFont(Font.PLAIN, 11f);
|
||||
g2.setFont(smallFont);
|
||||
int valueWidth = fm.stringWidth(valueStr);
|
||||
g2.drawString(valueStr, w - rightMargin - valueWidth - 5, topMargin - 10);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ProcessTableModel extends AbstractTableModel {
|
||||
private final Kernel kernel;
|
||||
private List<ProcessInfo> processes = new ArrayList<>();
|
||||
private final String[] columns = {"Application", "Memory (MB)", "Windows"};
|
||||
|
||||
ProcessTableModel(Kernel kernel) {
|
||||
this.kernel = kernel;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
processes.clear();
|
||||
|
||||
Map<String, Integer> windowCounts = new LinkedHashMap<>();
|
||||
for (JInternalFrame frame : kernel.windows().getAllWindows()) {
|
||||
Object prop = frame.getClientProperty("appId");
|
||||
if (prop instanceof String) {
|
||||
String appId = (String) prop;
|
||||
windowCounts.put(appId, windowCounts.getOrDefault(appId, 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Integer> entry : windowCounts.entrySet()) {
|
||||
String appId = entry.getKey();
|
||||
DesktopApp app = kernel.apps().getApp(appId);
|
||||
String appName = (app != null) ? app.getName() : appId;
|
||||
long memoryMB = entry.getValue() * 8L; // rough estimation
|
||||
processes.add(new ProcessInfo(appName, appId, entry.getValue(), memoryMB));
|
||||
}
|
||||
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
String getAppIdAt(int row) {
|
||||
return processes.get(row).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() { return processes.size(); }
|
||||
|
||||
@Override
|
||||
public int getColumnCount() { return columns.length; }
|
||||
|
||||
@Override
|
||||
public String getColumnName(int col) { return columns[col]; }
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int row, int col) {
|
||||
ProcessInfo info = processes.get(row);
|
||||
switch (col) {
|
||||
case 0: return info.name;
|
||||
case 1: return info.memoryMB;
|
||||
case 2: return info.windowCount;
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static class ProcessInfo {
|
||||
String name, id;
|
||||
int windowCount;
|
||||
long memoryMB;
|
||||
ProcessInfo(String name, String id, int windowCount, long memoryMB) {
|
||||
this.name = name; this.id = id; this.windowCount = windowCount; this.memoryMB = memoryMB;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// FileExplorerApp.java - fix border and import
|
||||
package com.rdesk.applications.system.filehaj;
|
||||
|
||||
import com.rdesk.kernel.DesktopApp;
|
||||
import com.rdesk.kernel.Kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class FileExplorerApp implements DesktopApp {
|
||||
private JInternalFrame frame;
|
||||
private File currentDirectory;
|
||||
private final java.util.Stack<File> backStack = new java.util.Stack<>();
|
||||
private final java.util.Stack<File> forwardStack = new java.util.Stack<>();
|
||||
|
||||
private NavigationPanel navPanel;
|
||||
private FileTreePanel treePanel;
|
||||
private FileListPanel listPanel;
|
||||
private JLabel statusBar;
|
||||
|
||||
@Override
|
||||
public String getId() { return "filehaj"; }
|
||||
@Override
|
||||
public String getName() { return "FileHaj"; }
|
||||
|
||||
@Override
|
||||
public JInternalFrame createWindow(Kernel kernel) {
|
||||
frame = new JInternalFrame("FileHaj", true, true, true, true);
|
||||
frame.setSize(750, 450);
|
||||
frame.setMinimumSize(new Dimension(550, 300));
|
||||
|
||||
currentDirectory = new File(System.getProperty("user.home"));
|
||||
|
||||
navPanel = new NavigationPanel(
|
||||
this::goBack, this::goForward, this::goUp,
|
||||
this::refreshAll, this::navigateTo, this::refreshAll
|
||||
);
|
||||
treePanel = new FileTreePanel(this::navigateTo);
|
||||
listPanel = new FileListPanel(f -> {
|
||||
if (f.isDirectory()) navigateTo(f);
|
||||
else openFile(f);
|
||||
});
|
||||
statusBar = new JLabel(" ");
|
||||
statusBar.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
|
||||
|
||||
frame.add(navPanel, BorderLayout.NORTH);
|
||||
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treePanel, listPanel);
|
||||
split.setDividerLocation(200);
|
||||
frame.add(split, BorderLayout.CENTER);
|
||||
frame.add(statusBar, BorderLayout.SOUTH);
|
||||
|
||||
refreshAll();
|
||||
|
||||
frame.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
|
||||
return frame;
|
||||
}
|
||||
|
||||
private void navigateTo(File dir) {
|
||||
if (dir == null || !dir.exists() || !dir.isDirectory() || !dir.canRead()) return;
|
||||
if (!dir.equals(currentDirectory)) {
|
||||
backStack.push(currentDirectory);
|
||||
forwardStack.clear();
|
||||
}
|
||||
currentDirectory = dir;
|
||||
refreshAll();
|
||||
}
|
||||
|
||||
private void goBack() {
|
||||
if (!backStack.isEmpty()) {
|
||||
forwardStack.push(currentDirectory);
|
||||
currentDirectory = backStack.pop();
|
||||
refreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void goForward() {
|
||||
if (!forwardStack.isEmpty()) {
|
||||
backStack.push(currentDirectory);
|
||||
currentDirectory = forwardStack.pop();
|
||||
refreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void goUp() {
|
||||
File parent = currentDirectory.getParentFile();
|
||||
if (parent != null && parent.canRead()) navigateTo(parent);
|
||||
}
|
||||
|
||||
private void refreshAll() {
|
||||
navPanel.setAddress(currentDirectory.getAbsolutePath());
|
||||
navPanel.updateNavButtons(!backStack.isEmpty(), !forwardStack.isEmpty());
|
||||
statusBar.setText("Loading...");
|
||||
|
||||
FileUtils.runAsync(() -> FileUtils.filterAndSort(currentDirectory.listFiles()), items -> {
|
||||
listPanel.setFiles(items);
|
||||
statusBar.setText(currentDirectory.getAbsolutePath() + " — " + items.size() + " items");
|
||||
treePanel.selectFile(currentDirectory);
|
||||
});
|
||||
}
|
||||
|
||||
private void openFile(File file) {
|
||||
try {
|
||||
Desktop.getDesktop().open(file);
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(frame, "Cannot open file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.rdesk.applications.system.filehaj;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class FileListPanel extends JPanel {
|
||||
private DefaultListModel<File> listModel;
|
||||
private JList<File> fileList;
|
||||
|
||||
public FileListPanel(java.util.function.Consumer<File> onDoubleClick) {
|
||||
setLayout(new BorderLayout());
|
||||
listModel = new DefaultListModel<>();
|
||||
fileList = new JList<>(listModel);
|
||||
fileList.setCellRenderer(new FileListCellRenderer());
|
||||
fileList.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(java.awt.event.MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
File f = fileList.getSelectedValue();
|
||||
if (f != null) onDoubleClick.accept(f);
|
||||
}
|
||||
}
|
||||
});
|
||||
add(new JScrollPane(fileList), BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
public void setFiles(List<File> files) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
listModel.clear();
|
||||
files.forEach(listModel::addElement);
|
||||
});
|
||||
}
|
||||
|
||||
private static class FileListCellRenderer extends DefaultListCellRenderer {
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value,
|
||||
int index, boolean isSelected, boolean cellHasFocus) {
|
||||
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
if (value instanceof File) {
|
||||
File f = (File) value;
|
||||
setText(f.getName());
|
||||
setIcon(UIManager.getIcon(f.isDirectory() ? "FileView.directoryIcon" : "FileView.fileIcon"));
|
||||
if (f.isFile()) {
|
||||
setText(f.getName() + " (" + FileUtils.formatFileSize(f.length()) + ")");
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.rdesk.applications.system.filehaj;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.TreeExpansionEvent;
|
||||
import javax.swing.event.TreeWillExpandListener;
|
||||
import javax.swing.tree.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class FileTreePanel extends JPanel {
|
||||
private JTree tree;
|
||||
private DefaultTreeModel treeModel;
|
||||
private DefaultMutableTreeNode rootNode;
|
||||
|
||||
public FileTreePanel(java.util.function.Consumer<File> onNavigate) {
|
||||
setLayout(new BorderLayout());
|
||||
rootNode = new DefaultMutableTreeNode("Computer");
|
||||
treeModel = new DefaultTreeModel(rootNode);
|
||||
tree = new JTree(treeModel);
|
||||
tree.setCellRenderer(new MinimalTreeCellRenderer());
|
||||
tree.setShowsRootHandles(true);
|
||||
tree.addTreeSelectionListener(e -> {
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
|
||||
if (node != null && node.getUserObject() instanceof File) {
|
||||
onNavigate.accept((File) node.getUserObject());
|
||||
}
|
||||
});
|
||||
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
|
||||
@Override public void treeWillExpand(TreeExpansionEvent event) {
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) event.getPath().getLastPathComponent();
|
||||
if (node.getChildCount() == 1 && node.getFirstChild() instanceof PlaceholderNode) {
|
||||
loadTreeChildren(node);
|
||||
}
|
||||
}
|
||||
@Override public void treeWillCollapse(TreeExpansionEvent event) {}
|
||||
});
|
||||
add(new JScrollPane(tree), BorderLayout.CENTER);
|
||||
|
||||
// Build initial tree
|
||||
buildInitialTree();
|
||||
}
|
||||
|
||||
private void buildInitialTree() {
|
||||
String home = System.getProperty("user.home");
|
||||
addNode(new NamedFile(new File(home), "Home"), true);
|
||||
addNode(new NamedFile(new File(home, "Desktop"), "Desktop"), true);
|
||||
addNode(new NamedFile(new File(home, "Documents"), "Documents"), true);
|
||||
addNode(new NamedFile(new File(home, "Downloads"), "Downloads"), true);
|
||||
addNode(new NamedFile(new File(home, "Music"), "Music"), true);
|
||||
addNode(new NamedFile(new File(home, "Pictures"), "Pictures"), true);
|
||||
addNode(new NamedFile(new File(home, "Videos"), "Videos"), true);
|
||||
|
||||
for (File root : File.listRoots()) {
|
||||
if (root.getAbsolutePath().startsWith("C:")) {
|
||||
addNode(root, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Other drives asynchronously
|
||||
FileUtils.runAsync(() -> {
|
||||
List<DefaultMutableTreeNode> others = new ArrayList<>();
|
||||
for (File root : File.listRoots()) {
|
||||
if (!root.getAbsolutePath().startsWith("C:") && root.exists() && root.canRead()) {
|
||||
DefaultMutableTreeNode node = new DefaultMutableTreeNode(root);
|
||||
node.add(new PlaceholderNode());
|
||||
others.add(node);
|
||||
}
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
for (DefaultMutableTreeNode node : others) {
|
||||
rootNode.add(node);
|
||||
}
|
||||
treeModel.nodeStructureChanged(rootNode);
|
||||
});
|
||||
}, () -> tree.expandRow(0));
|
||||
}
|
||||
|
||||
private void addNode(Object userObject, boolean withPlaceholder) {
|
||||
DefaultMutableTreeNode node = new DefaultMutableTreeNode(userObject);
|
||||
if (withPlaceholder) node.add(new PlaceholderNode());
|
||||
rootNode.add(node);
|
||||
treeModel.nodeStructureChanged(rootNode);
|
||||
}
|
||||
|
||||
private void loadTreeChildren(DefaultMutableTreeNode parent) {
|
||||
File dir = (File) parent.getUserObject();
|
||||
parent.removeAllChildren();
|
||||
parent.add(new PlaceholderNode());
|
||||
treeModel.nodeStructureChanged(parent);
|
||||
|
||||
FileUtils.runAsync(() -> {
|
||||
File[] subdirs = dir.listFiles(f -> f.isDirectory() && FileUtils.shouldShow(f));
|
||||
List<DefaultMutableTreeNode> children = new ArrayList<>();
|
||||
if (subdirs != null) {
|
||||
Arrays.sort(subdirs);
|
||||
for (File child : subdirs) {
|
||||
DefaultMutableTreeNode node = new DefaultMutableTreeNode(child);
|
||||
if (hasVisibleSubdirs(child)) node.add(new PlaceholderNode());
|
||||
children.add(node);
|
||||
}
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
parent.removeAllChildren();
|
||||
children.forEach(parent::add);
|
||||
treeModel.nodeStructureChanged(parent);
|
||||
});
|
||||
}, () -> {});
|
||||
}
|
||||
|
||||
private boolean hasVisibleSubdirs(File dir) {
|
||||
File[] sub = dir.listFiles(f -> f.isDirectory() && FileUtils.shouldShow(f));
|
||||
return sub != null && sub.length > 0;
|
||||
}
|
||||
|
||||
public void selectFile(File target) {
|
||||
FileUtils.runAsync(() -> findNode(rootNode, target), node -> {
|
||||
if (node != null) {
|
||||
TreePath path = new TreePath(node.getPath());
|
||||
tree.setSelectionPath(path);
|
||||
tree.scrollPathToVisible(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private DefaultMutableTreeNode findNode(DefaultMutableTreeNode node, File target) {
|
||||
Object obj = node.getUserObject();
|
||||
File file = (obj instanceof File) ? (File) obj : null;
|
||||
if (file != null && file.equals(target)) return node;
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
DefaultMutableTreeNode found = findNode((DefaultMutableTreeNode) node.getChildAt(i), target);
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class PlaceholderNode extends DefaultMutableTreeNode {
|
||||
PlaceholderNode() { super("..."); }
|
||||
}
|
||||
|
||||
private static class MinimalTreeCellRenderer extends DefaultTreeCellRenderer {
|
||||
@Override
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object value,
|
||||
boolean sel, boolean expanded,
|
||||
boolean leaf, int row, boolean hasFocus) {
|
||||
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
|
||||
if (value instanceof DefaultMutableTreeNode) {
|
||||
Object obj = ((DefaultMutableTreeNode) value).getUserObject();
|
||||
if (obj instanceof File) {
|
||||
File f = (File) obj;
|
||||
setText(f.getName().isEmpty() ? f.getPath() : f.getName());
|
||||
setIcon(UIManager.getIcon(f.isDirectory() ? "FileView.directoryIcon" : "FileView.fileIcon"));
|
||||
} else if (obj instanceof NamedFile) {
|
||||
setText(obj.toString());
|
||||
setIcon(UIManager.getIcon("FileView.directoryIcon"));
|
||||
} else if (obj instanceof String && "...".equals(obj)) {
|
||||
setText("Loading...");
|
||||
setIcon(null);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.rdesk.applications.system.filehaj;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class FileUtils {
|
||||
private static boolean showHidden = false;
|
||||
|
||||
public static boolean isShowHidden() { return showHidden; }
|
||||
public static void setShowHidden(boolean show) { showHidden = show; }
|
||||
|
||||
public static boolean shouldShow(File f) {
|
||||
if (showHidden) return true;
|
||||
return !f.isHidden() && !f.getName().startsWith(".");
|
||||
}
|
||||
|
||||
public static List<File> filterAndSort(File[] files) {
|
||||
List<File> result = new ArrayList<>();
|
||||
if (files == null) return result;
|
||||
for (File f : files) {
|
||||
if (shouldShow(f)) result.add(f);
|
||||
}
|
||||
result.sort((a, b) -> {
|
||||
if (a.isDirectory() && !b.isDirectory()) return -1;
|
||||
if (!a.isDirectory() && b.isDirectory()) return 1;
|
||||
return a.getName().compareToIgnoreCase(b.getName());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String formatFileSize(long size) {
|
||||
if (size < 1024) return size + " B";
|
||||
int exp = (int) (Math.log(size) / Math.log(1024));
|
||||
char pre = "KMGTPE".charAt(exp - 1);
|
||||
return String.format("%.1f %sB", size / Math.pow(1024, exp), pre);
|
||||
}
|
||||
|
||||
public static void runAsync(Runnable background, Runnable uiUpdate) {
|
||||
new SwingWorker<Void, Void>() {
|
||||
@Override protected Void doInBackground() { background.run(); return null; }
|
||||
@Override protected void done() { uiUpdate.run(); }
|
||||
}.execute();
|
||||
}
|
||||
|
||||
// Generic async with result
|
||||
public static <T> void runAsync(Supplier<T> background, Consumer<T> uiUpdate) {
|
||||
new SwingWorker<T, Void>() {
|
||||
@Override protected T doInBackground() { return background.get(); }
|
||||
@Override protected void done() {
|
||||
try { uiUpdate.accept(get()); } catch (Exception ignored) {}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.rdesk.applications.system.filehaj;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class NamedFile extends File {
|
||||
private final String displayName;
|
||||
|
||||
public NamedFile(File file, String displayName) {
|
||||
super(file.getAbsolutePath());
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.rdesk.applications.system.filehaj;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
|
||||
public class NavigationPanel extends JPanel {
|
||||
private JButton backButton, forwardButton, upButton, refreshButton;
|
||||
private JTextField addressBar;
|
||||
private JCheckBox hiddenToggle;
|
||||
|
||||
public NavigationPanel(Runnable onBack, Runnable onForward, Runnable onUp,
|
||||
Runnable onRefresh, java.util.function.Consumer<File> onNavigate,
|
||||
Runnable onToggleHidden) {
|
||||
setLayout(new BorderLayout(5, 5));
|
||||
setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0));
|
||||
backButton = new JButton("<");
|
||||
forwardButton = new JButton(">");
|
||||
upButton = new JButton("^");
|
||||
refreshButton = new JButton("R");
|
||||
|
||||
backButton.setToolTipText("Back");
|
||||
forwardButton.setToolTipText("Forward");
|
||||
upButton.setToolTipText("Up");
|
||||
refreshButton.setToolTipText("Refresh");
|
||||
|
||||
backButton.addActionListener(e -> onBack.run());
|
||||
forwardButton.addActionListener(e -> onForward.run());
|
||||
upButton.addActionListener(e -> onUp.run());
|
||||
refreshButton.addActionListener(e -> onRefresh.run());
|
||||
|
||||
buttonPanel.add(backButton);
|
||||
buttonPanel.add(forwardButton);
|
||||
buttonPanel.add(upButton);
|
||||
buttonPanel.add(refreshButton);
|
||||
|
||||
hiddenToggle = new JCheckBox("Show hidden items");
|
||||
hiddenToggle.setToolTipText("Show hidden files and folders");
|
||||
hiddenToggle.addActionListener(e -> {
|
||||
FileUtils.setShowHidden(hiddenToggle.isSelected());
|
||||
onToggleHidden.run();
|
||||
});
|
||||
hiddenToggle.setSelected(FileUtils.isShowHidden());
|
||||
buttonPanel.add(hiddenToggle);
|
||||
|
||||
addressBar = new JTextField();
|
||||
addressBar.addActionListener(e -> onNavigate.accept(new File(addressBar.getText())));
|
||||
|
||||
add(buttonPanel, BorderLayout.WEST);
|
||||
add(addressBar, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
public void setAddress(String path) { addressBar.setText(path); }
|
||||
public void updateNavButtons(boolean backEnabled, boolean forwardEnabled) {
|
||||
backButton.setEnabled(backEnabled);
|
||||
forwardButton.setEnabled(forwardEnabled);
|
||||
}
|
||||
public void updateHiddenToggle() { hiddenToggle.setSelected(FileUtils.isShowHidden()); }
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* A clean, functional sidebar that lists applications by category.
|
||||
* Categories tile vertically, each taking full width.
|
||||
*/
|
||||
public class AppLauncher extends JPanel {
|
||||
|
||||
private static final String DEFAULT_CATEGORY = "General";
|
||||
private static final int EXPANDED_WIDTH = 220;
|
||||
private static final int COLLAPSED_WIDTH = 40;
|
||||
|
||||
private final Kernel kernel;
|
||||
private final Map<String, CategoryPanel> categories = new LinkedHashMap<>();
|
||||
private final JPanel mainPanel;
|
||||
private final JButton globalToggleButton;
|
||||
private final JScrollPane scrollPane;
|
||||
private boolean expanded = true;
|
||||
|
||||
public AppLauncher(Kernel kernel) {
|
||||
this.kernel = kernel;
|
||||
setLayout(new BorderLayout());
|
||||
setPreferredSize(new Dimension(EXPANDED_WIDTH, 0));
|
||||
|
||||
globalToggleButton = new JButton("<<");
|
||||
globalToggleButton.setToolTipText("Collapse sidebar");
|
||||
globalToggleButton.setFocusPainted(false);
|
||||
globalToggleButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
globalToggleButton.addActionListener(this::toggleSidebar);
|
||||
add(globalToggleButton, BorderLayout.NORTH);
|
||||
|
||||
mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
|
||||
scrollPane = new JScrollPane(mainPanel);
|
||||
scrollPane.setBorder(null);
|
||||
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
|
||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
add(scrollPane, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
// Public API -----------------------------------------------------------------
|
||||
|
||||
public void addApp(DesktopApp app) {
|
||||
addApp(app, DEFAULT_CATEGORY);
|
||||
}
|
||||
|
||||
public void addApp(DesktopApp app, String category) {
|
||||
if (!kernel.apps().isRegistered(app.getId())) {
|
||||
kernel.apps().register(app);
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> getCategoryPanel(category).addApp(app));
|
||||
}
|
||||
|
||||
public void loadAppsFromFolder(String folderPath) {
|
||||
loadAppsFromFolder(folderPath, DEFAULT_CATEGORY);
|
||||
}
|
||||
|
||||
public void loadAppsFromFolder(String folderPath, String category) {
|
||||
File folder = new File(folderPath);
|
||||
if (!folder.exists() || !folder.isDirectory()) {
|
||||
System.err.println("[AppLauncher] Folder not found: " + folderPath);
|
||||
return;
|
||||
}
|
||||
File[] jars = folder.listFiles((dir, name) -> name.endsWith(".jar"));
|
||||
if (jars == null) return;
|
||||
for (File jar : jars) loadJarApp(jar, category);
|
||||
}
|
||||
|
||||
public void setCategoryExpanded(String category, boolean expand) {
|
||||
CategoryPanel panel = categories.get(category);
|
||||
if (panel != null) panel.setExpanded(expand);
|
||||
}
|
||||
|
||||
// Internal logic ------------------------------------------------------------
|
||||
|
||||
private void toggleSidebar(ActionEvent e) {
|
||||
expanded = !expanded;
|
||||
globalToggleButton.setText(expanded ? "<<" : ">>");
|
||||
globalToggleButton.setToolTipText(expanded ? "Collapse sidebar" : "Expand sidebar");
|
||||
|
||||
scrollPane.setVisible(expanded);
|
||||
setPreferredSize(new Dimension(expanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH, 0));
|
||||
revalidate();
|
||||
|
||||
Container parent = getParent();
|
||||
if (parent instanceof JSplitPane split) {
|
||||
split.setDividerLocation(expanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH);
|
||||
}
|
||||
}
|
||||
|
||||
private CategoryPanel getCategoryPanel(String name) {
|
||||
return categories.computeIfAbsent(name, n -> {
|
||||
CategoryPanel panel = new CategoryPanel(n);
|
||||
mainPanel.add(panel);
|
||||
mainPanel.revalidate();
|
||||
return panel;
|
||||
});
|
||||
}
|
||||
|
||||
private void loadJarApp(File jarFile, String category) {
|
||||
try (URLClassLoader loader = new URLClassLoader(
|
||||
new URL[]{jarFile.toURI().toURL()},
|
||||
getClass().getClassLoader())) {
|
||||
|
||||
String className = findDesktopAppClass(loader, jarFile);
|
||||
if (className == null) {
|
||||
System.err.println("[AppLauncher] No DesktopApp in " + jarFile.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> clazz = Class.forName(className, true, loader);
|
||||
DesktopApp app = (DesktopApp) clazz.getDeclaredConstructor().newInstance();
|
||||
addApp(app, category);
|
||||
System.out.println("[AppLauncher] Loaded: " + app.getName());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("[AppLauncher] Failed to load " + jarFile.getName());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String findDesktopAppClass(URLClassLoader loader, File jarFile) {
|
||||
try (JarFile jar = new JarFile(jarFile)) {
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
if (!entry.getName().endsWith(".class")) continue;
|
||||
String className = entry.getName().replace("/", ".").replace(".class", "");
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className, false, loader);
|
||||
for (Class<?> iface : clazz.getInterfaces()) {
|
||||
if (iface == DesktopApp.class) return className;
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Inner classes -------------------------------------------------------------
|
||||
|
||||
private class CategoryPanel extends JPanel {
|
||||
private final String categoryName;
|
||||
private final JButton headerButton;
|
||||
private final JPanel appsContainer;
|
||||
private boolean expanded = true;
|
||||
|
||||
CategoryPanel(String categoryName) {
|
||||
this.categoryName = categoryName;
|
||||
setLayout(new BorderLayout());
|
||||
setAlignmentX(LEFT_ALIGNMENT);
|
||||
setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0));
|
||||
|
||||
headerButton = new JButton();
|
||||
headerButton.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
headerButton.setFocusPainted(false);
|
||||
headerButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
headerButton.addActionListener(e -> setExpanded(!expanded));
|
||||
updateHeaderText();
|
||||
|
||||
appsContainer = new JPanel();
|
||||
appsContainer.setLayout(new BoxLayout(appsContainer, BoxLayout.Y_AXIS));
|
||||
appsContainer.setBorder(BorderFactory.createEmptyBorder(2, 12, 2, 2));
|
||||
appsContainer.setVisible(true);
|
||||
appsContainer.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
add(headerButton, BorderLayout.NORTH);
|
||||
add(appsContainer, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
// Override getMaximumSize to allow full width while respecting preferred height
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
Dimension preferred = getPreferredSize();
|
||||
return new Dimension(Integer.MAX_VALUE, preferred.height);
|
||||
}
|
||||
|
||||
private void updateHeaderText() {
|
||||
String prefix = expanded ? "[-] " : "[+] ";
|
||||
headerButton.setText(prefix + categoryName);
|
||||
}
|
||||
|
||||
void addApp(DesktopApp app) {
|
||||
JButton appButton = new JButton(app.getName());
|
||||
appButton.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
appButton.setFocusPainted(false);
|
||||
appButton.setBorder(BorderFactory.createEmptyBorder(4, 8, 4, 8));
|
||||
appButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
appButton.addActionListener(e -> kernel.apps().launch(app.getId()));
|
||||
|
||||
// Make each app button fill the width of its container
|
||||
appButton.setAlignmentX(LEFT_ALIGNMENT);
|
||||
appButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, appButton.getPreferredSize().height));
|
||||
|
||||
appsContainer.add(appButton);
|
||||
appsContainer.revalidate();
|
||||
appsContainer.repaint();
|
||||
}
|
||||
|
||||
void setExpanded(boolean expand) {
|
||||
if (this.expanded == expand) return;
|
||||
this.expanded = expand;
|
||||
appsContainer.setVisible(expanded);
|
||||
updateHeaderText();
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.*;
|
||||
|
||||
public class AppManager {
|
||||
|
||||
private final Kernel kernel;
|
||||
private final Map<String, DesktopApp> installedApps = new HashMap<>();
|
||||
private final Map<String, DesktopApp> runningApps = new HashMap<>();
|
||||
|
||||
public AppManager(Kernel kernel) {
|
||||
this.kernel = kernel;
|
||||
}
|
||||
|
||||
public void register(DesktopApp app) {
|
||||
installedApps.put(app.getId(), app);
|
||||
System.out.println("[AppManager] Registered: " + app.getName());
|
||||
}
|
||||
|
||||
public boolean isRegistered(String id) {
|
||||
return installedApps.containsKey(id);
|
||||
}
|
||||
|
||||
public void launch(String id) {
|
||||
DesktopApp app = installedApps.get(id);
|
||||
if (app == null) {
|
||||
System.err.println("[AppManager] App not found: " + id);
|
||||
return;
|
||||
}
|
||||
|
||||
JInternalFrame frame = app.createWindow(kernel);
|
||||
|
||||
frame.putClientProperty("appId", id);
|
||||
|
||||
runningApps.put(id, app);
|
||||
kernel.windows().open(frame);
|
||||
app.onStart(kernel);
|
||||
System.out.println("[AppManager] Launched: " + app.getName());
|
||||
}
|
||||
|
||||
public void stopAll() {
|
||||
for (DesktopApp app : runningApps.values()) {
|
||||
try {
|
||||
app.onStop(kernel);
|
||||
System.out.println("[AppManager] Stopped: " + app.getName());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
runningApps.clear();
|
||||
}
|
||||
|
||||
public DesktopApp getApp(String id) {
|
||||
return installedApps.get(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public interface DesktopApp {
|
||||
|
||||
String getId();
|
||||
|
||||
String getName();
|
||||
|
||||
default ImageIcon getIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
JInternalFrame createWindow(Kernel kernel);
|
||||
|
||||
default void onStart(Kernel kernel) {}
|
||||
|
||||
default void onStop(Kernel kernel) {}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.jar.*;
|
||||
|
||||
public class DesktopIconManager {
|
||||
|
||||
private final Kernel kernel;
|
||||
private final JDesktopPane desktop;
|
||||
|
||||
private final int cellWidth = 110;
|
||||
private final int cellHeight = 110;
|
||||
private final int iconWidth = 90;
|
||||
private final int iconHeight = 70;
|
||||
|
||||
/** Tracks how many icons have been placed so far. */
|
||||
private int index = 0;
|
||||
|
||||
public DesktopIconManager(Kernel kernel, JDesktopPane desktop) {
|
||||
this.kernel = kernel;
|
||||
this.desktop = desktop;
|
||||
this.desktop.setLayout(null);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Public API //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
/**
|
||||
* Scan a folder for {@code .jar} files and create a desktop icon for
|
||||
* every {@link DesktopApp} implementation found inside them.
|
||||
*/
|
||||
public void loadAppsFromFolder(String path) {
|
||||
File folder = new File(path);
|
||||
|
||||
if (!folder.exists() || !folder.isDirectory()) {
|
||||
System.out.println("[IconManager] applications folder not found: " + path);
|
||||
return;
|
||||
}
|
||||
|
||||
File[] jars = folder.listFiles((d, name) -> name.endsWith(".jar"));
|
||||
if (jars == null) return;
|
||||
|
||||
for (File jar : jars) {
|
||||
loadJar(jar);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly add a {@link DesktopApp} icon to the desktop.
|
||||
* Use this for system / built-in apps registered from {@code main()}.
|
||||
*/
|
||||
public void addApp(DesktopApp app) {
|
||||
SwingUtilities.invokeLater(() -> createIcon(app));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// JAR loading //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
private void loadJar(File jarFile) {
|
||||
// URLClassLoader implements Closeable — always close it when done
|
||||
try (URLClassLoader loader = new URLClassLoader(
|
||||
new URL[]{jarFile.toURI().toURL()},
|
||||
getClass().getClassLoader())) {
|
||||
|
||||
String className = findAppClass(loader, jarFile);
|
||||
if (className == null) {
|
||||
System.out.println("[IconManager] No DesktopApp found in " + jarFile.getName());
|
||||
return;
|
||||
}
|
||||
|
||||
Class<?> clazz = Class.forName(className, true, loader);
|
||||
DesktopApp app = (DesktopApp) clazz.getDeclaredConstructor().newInstance();
|
||||
|
||||
SwingUtilities.invokeLater(() -> createIcon(app));
|
||||
System.out.println("[IconManager] Loaded: " + app.getName());
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("[IconManager] Failed to load " + jarFile.getName() + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String findAppClass(URLClassLoader loader, File jarFile) {
|
||||
try (JarFile jar = new JarFile(jarFile)) {
|
||||
|
||||
var entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
|
||||
if (!entry.getName().endsWith(".class")) continue;
|
||||
|
||||
String className = entry.getName()
|
||||
.replace("/", ".")
|
||||
.replace(".class", "");
|
||||
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className, false, loader);
|
||||
for (Class<?> iface : clazz.getInterfaces()) {
|
||||
if (iface == DesktopApp.class) {
|
||||
return className;
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Icon creation & layout //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
/**
|
||||
* Calculate the next grid position.
|
||||
*
|
||||
* Column count is derived lazily from the desktop's current width so it
|
||||
* remains correct even when the frame has been packed / resized before
|
||||
* icons are added. Falls back to 1 column if the pane has no width yet.
|
||||
*/
|
||||
private Point nextGridPosition() {
|
||||
int desktopWidth = desktop.getWidth();
|
||||
int cols = (desktopWidth > 0) ? Math.max(1, desktopWidth / cellWidth) : 1;
|
||||
|
||||
int row = index / cols;
|
||||
int col = index % cols;
|
||||
|
||||
int x = col * cellWidth + (cellWidth - iconWidth) / 2;
|
||||
int y = row * cellHeight + (cellHeight - iconHeight) / 2;
|
||||
|
||||
index++;
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
private void createIcon(DesktopApp app) {
|
||||
JButton button = new JButton("<html><center>" + app.getName() + "</center></html>");
|
||||
|
||||
// Style — a clean, dark desktop-icon look
|
||||
button.setFont(new Font("Segoe UI", Font.PLAIN, 11));
|
||||
button.setForeground(Color.WHITE);
|
||||
button.setBackground(new Color(40, 40, 60, 200));
|
||||
button.setBorderPainted(false);
|
||||
button.setFocusPainted(false);
|
||||
button.setOpaque(true);
|
||||
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
button.setToolTipText(app.getName());
|
||||
|
||||
button.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||
@Override public void mouseEntered(java.awt.event.MouseEvent e) {
|
||||
button.setBackground(new Color(70, 70, 100, 220));
|
||||
}
|
||||
@Override public void mouseExited(java.awt.event.MouseEvent e) {
|
||||
button.setBackground(new Color(40, 40, 60, 200));
|
||||
}
|
||||
});
|
||||
|
||||
Point p = nextGridPosition();
|
||||
button.setBounds(p.x, p.y, iconWidth, iconHeight);
|
||||
|
||||
button.addActionListener(e -> {
|
||||
// app may be pre registered, so dont do shit
|
||||
if (!kernel.apps().isRegistered(app.getId())) {
|
||||
kernel.apps().register(app);
|
||||
}
|
||||
kernel.apps().launch(app.getId());
|
||||
});
|
||||
|
||||
desktop.add(button);
|
||||
desktop.repaint();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class EventBus {
|
||||
|
||||
private final Map<String, List<Consumer<Object>>> listeners = new HashMap<>();
|
||||
|
||||
public void on(String event, Consumer<Object> handler) {
|
||||
listeners.computeIfAbsent(event, k -> new ArrayList<>()).add(handler);
|
||||
}
|
||||
|
||||
public void emit(String event) {
|
||||
emit(event, null);
|
||||
}
|
||||
|
||||
public void emit(String event, Object data) {
|
||||
List<Consumer<Object>> list = listeners.get(event);
|
||||
if (list == null) return;
|
||||
|
||||
for (Consumer<Object> h : list) {
|
||||
h.accept(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class Kernel {
|
||||
|
||||
private static volatile Kernel instance;
|
||||
|
||||
private final AppManager appManager;
|
||||
private final WindowManager windowManager;
|
||||
private final EventBus eventBus;
|
||||
private final ServiceRegistry services;
|
||||
|
||||
private DesktopIconManager iconManager;
|
||||
private JFrame desktopFrame;
|
||||
|
||||
private boolean running = false;
|
||||
|
||||
private Kernel() {
|
||||
this.eventBus = new EventBus();
|
||||
this.services = new ServiceRegistry();
|
||||
this.appManager = new AppManager(this);
|
||||
this.windowManager = new WindowManager(this);
|
||||
}
|
||||
|
||||
/** Thread-safe singleton accessor. */
|
||||
public static Kernel get() {
|
||||
if (instance == null) {
|
||||
synchronized (Kernel.class) {
|
||||
if (instance == null) {
|
||||
instance = new Kernel();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void boot() {
|
||||
running = true;
|
||||
System.out.println("[Kernel] Booting...");
|
||||
eventBus.emit("kernel.boot");
|
||||
}
|
||||
|
||||
public void attachFrame(JFrame frame) {
|
||||
this.desktopFrame = frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the icon manager so the kernel can delegate system-app
|
||||
* registration to it. Call this after the desktop pane is ready.
|
||||
*/
|
||||
public void attachIconManager(DesktopIconManager iconManager) {
|
||||
this.iconManager = iconManager;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// System-app API — call from main() before or after boot() //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
/**
|
||||
* Register a built-in / system app and place its icon on the desktop.
|
||||
*
|
||||
* <pre>{@code
|
||||
* Kernel kernel = Kernel.get();
|
||||
* kernel.registerSystemApp(new MyCalculatorApp());
|
||||
* }</pre>
|
||||
*
|
||||
* @param app the {@link DesktopApp} implementation to register
|
||||
* @throws IllegalStateException if {@link #attachIconManager} has not been called yet
|
||||
*/
|
||||
public void registerSystemApp(DesktopApp app) {
|
||||
if (iconManager == null) {
|
||||
throw new IllegalStateException(
|
||||
"[Kernel] attachIconManager() must be called before registerSystemApp()");
|
||||
}
|
||||
appManager.register(app);
|
||||
iconManager.addApp(app);
|
||||
System.out.println("[Kernel] System app registered: " + app.getName());
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (!running) return;
|
||||
|
||||
System.out.println("[Kernel] Shutdown started");
|
||||
running = false;
|
||||
|
||||
appManager.stopAll();
|
||||
windowManager.closeAll();
|
||||
|
||||
if (desktopFrame != null) {
|
||||
desktopFrame.dispose();
|
||||
}
|
||||
|
||||
eventBus.emit("kernel.shutdown");
|
||||
System.out.println("[Kernel] Shutdown complete");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Accessors //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
public AppManager apps() { return appManager; }
|
||||
public WindowManager windows() { return windowManager; }
|
||||
public EventBus events() { return eventBus; }
|
||||
public ServiceRegistry services() { return services; }
|
||||
public boolean isRunning() { return running; }
|
||||
public JFrame getDesktopFrame() { return desktopFrame; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ServiceRegistry {
|
||||
|
||||
private final Map<String, Object> services = new HashMap<>();
|
||||
|
||||
public void register(String name, Object service) {
|
||||
services.put(name, service);
|
||||
}
|
||||
|
||||
public <T> T get(String name, Class<T> type) {
|
||||
return type.cast(services.get(name));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WindowManager {
|
||||
|
||||
private final Kernel kernel;
|
||||
private JDesktopPane desktop;
|
||||
|
||||
public WindowManager(Kernel kernel) {
|
||||
this.kernel = kernel;
|
||||
}
|
||||
|
||||
public void attachDesktop(JDesktopPane desktop) {
|
||||
this.desktop = desktop;
|
||||
}
|
||||
|
||||
public void open(JInternalFrame frame) {
|
||||
desktop.add(frame);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
public void closeAll() {
|
||||
if (desktop == null) return;
|
||||
for (JInternalFrame frame : desktop.getAllFrames()) {
|
||||
try {
|
||||
frame.setClosed(true);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public JInternalFrame[] getAllWindows() {
|
||||
if (desktop == null) return new JInternalFrame[0];
|
||||
return desktop.getAllFrames();
|
||||
}
|
||||
|
||||
public void closeAllByAppId(String appId) {
|
||||
if (desktop == null) return;
|
||||
for (JInternalFrame frame : desktop.getAllFrames()) {
|
||||
Object prop = frame.getClientProperty("appId");
|
||||
if (appId.equals(prop)) {
|
||||
try {
|
||||
frame.setClosed(true);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public JDesktopPane getDesktop() {
|
||||
// allows to change stuff for the desktop pane, essential for our bgchanger class
|
||||
return desktop;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user