nicer stuff ; cleanup of AI stuff
This commit is contained in:
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
<option name="applicationTheme" value="default" />
|
||||
<option name="iconsTheme" value="default" />
|
||||
<option name="button1Title" value="" />
|
||||
<option name="button1Url" value="" />
|
||||
<option name="button2Title" value="" />
|
||||
<option name="button2Url" value="" />
|
||||
<option name="customApplicationId" value="" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+124
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Palette2">
|
||||
<group name="Swing">
|
||||
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
|
||||
</item>
|
||||
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
|
||||
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
|
||||
<initial-values>
|
||||
<property name="text" value="Button" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="RadioButton" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="CheckBox" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
|
||||
<initial-values>
|
||||
<property name="text" value="Label" />
|
||||
</initial-values>
|
||||
</item>
|
||||
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
|
||||
<preferred-size width="150" height="-1" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
|
||||
<preferred-size width="150" height="50" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
|
||||
<preferred-size width="200" height="200" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
|
||||
</item>
|
||||
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
|
||||
<preferred-size width="-1" height="20" />
|
||||
</default-constraints>
|
||||
</item>
|
||||
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
|
||||
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
|
||||
</item>
|
||||
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
|
||||
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
|
||||
</item>
|
||||
</group>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -14,6 +14,19 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<configuration>
|
||||
<mainClass>com.rdesk.Main</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<!-- Source: https://mvnrepository.com/artifact/com.google.code.gson/gson -->
|
||||
<dependency>
|
||||
@@ -29,6 +42,56 @@
|
||||
<version>3.7.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.friwi</groupId>
|
||||
<artifactId>jcefmaven</artifactId>
|
||||
<version>146.0.10</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
<version>5.14.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna-platform</artifactId>
|
||||
<version>5.14.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>21.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<version>21.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-swing</artifactId>
|
||||
<version>21.0.2</version>
|
||||
</dependency>
|
||||
<!-- Source: https://mvnrepository.com/artifact/com.fifesoft/rsyntaxtextarea -->
|
||||
<dependency>
|
||||
<groupId>com.fifesoft</groupId>
|
||||
<artifactId>rsyntaxtextarea</artifactId>
|
||||
<version>3.6.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fifesoft</groupId>
|
||||
<artifactId>autocomplete</artifactId>
|
||||
<version>3.3.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fifesoft</groupId>
|
||||
<artifactId>languagesupport</artifactId>
|
||||
<version>3.4.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.rdesk;
|
||||
|
||||
import com.rdesk.applications.productivity.Calculator;
|
||||
import com.rdesk.applications.productivity.browsing.TabbedBrowser;
|
||||
import com.rdesk.applications.productivity.code.CodeDesk;
|
||||
import com.rdesk.applications.system.Exit;
|
||||
import com.rdesk.applications.productivity.Notes;
|
||||
import com.rdesk.applications.system.TaskManager;
|
||||
@@ -19,11 +22,10 @@ public class Main {
|
||||
}
|
||||
|
||||
private static void boot() {
|
||||
|
||||
Kernel kernel = Kernel.get();
|
||||
kernel.boot();
|
||||
JFrame frame = new JFrame("rDesk");
|
||||
|
||||
JFrame frame = new JFrame("Java OS");
|
||||
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||
frame.addWindowListener(new java.awt.event.WindowAdapter() {
|
||||
@Override
|
||||
@@ -80,7 +82,11 @@ public class Main {
|
||||
}
|
||||
|
||||
private static void registerSystemApps(AppLauncher launcher) {
|
||||
|
||||
launcher.addApp(new Notes(), "Productivity");
|
||||
launcher.addApp(new TabbedBrowser(), "Productivity");
|
||||
launcher.addApp(new CodeDesk(), "Productivity");
|
||||
launcher.addApp(new Calculator(), "Productivity");
|
||||
launcher.addApp(new Exit(), "System");
|
||||
launcher.addApp(new TaskManager(), "System");
|
||||
launcher.addApp(new FileExplorerApp(), "System");
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
package com.rdesk.applications.productivity;
|
||||
import com.rdesk.kernel.*;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class Calculator implements DesktopApp {
|
||||
|
||||
@Override
|
||||
public String getId() { return "calculator"; }
|
||||
|
||||
@Override
|
||||
public String getName() { return "Calculator"; }
|
||||
|
||||
@Override
|
||||
public JInternalFrame createWindow(Kernel kernel) {
|
||||
|
||||
JInternalFrame frame = new JInternalFrame(
|
||||
"Calculator",
|
||||
true, true, true, true
|
||||
);
|
||||
|
||||
frame.setSize(420, 480);
|
||||
frame.setLayout(new BorderLayout(6,6));
|
||||
|
||||
JPanel top = new JPanel(new BorderLayout(6,6));
|
||||
String[] modes = {"Standard", "Scientific", "Programmer"};
|
||||
JComboBox<String> modeBox = new JComboBox<>(modes);
|
||||
top.add(modeBox, BorderLayout.WEST);
|
||||
|
||||
JTextField display = new JTextField();
|
||||
display.setEditable(false);
|
||||
display.setHorizontalAlignment(SwingConstants.RIGHT);
|
||||
display.setFont(display.getFont().deriveFont(20f));
|
||||
top.add(display, BorderLayout.CENTER);
|
||||
|
||||
frame.add(top, BorderLayout.NORTH);
|
||||
|
||||
JPanel cards = new JPanel(new CardLayout());
|
||||
JPanel stdPanel = buildStandardPanel(display);
|
||||
JPanel sciPanel = buildScientificPanel(display);
|
||||
JPanel progPanel = buildProgrammerPanel(display);
|
||||
|
||||
cards.add(stdPanel, "Standard");
|
||||
cards.add(sciPanel, "Scientific");
|
||||
cards.add(progPanel, "Programmer");
|
||||
|
||||
frame.add(cards, BorderLayout.CENTER);
|
||||
|
||||
modeBox.addActionListener(e -> {
|
||||
CardLayout cl = (CardLayout)(cards.getLayout());
|
||||
cl.show(cards, (String)modeBox.getSelectedItem());
|
||||
});
|
||||
|
||||
frame.setVisible(true);
|
||||
return frame;
|
||||
}
|
||||
|
||||
private JPanel buildStandardPanel(JTextField display) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
JPanel buttons = new JPanel(new GridLayout(5,4,5,5));
|
||||
String[] labels = {
|
||||
"C", "+-", "%", "/",
|
||||
"7", "8", "9", "*",
|
||||
"4", "5", "6", "-",
|
||||
"1", "2", "3", "+",
|
||||
"0", ".", "=", "OFF"
|
||||
};
|
||||
buttons.setBorder(BorderFactory.createEmptyBorder(8,8,8,8));
|
||||
CalculatorState state = new CalculatorState(display);
|
||||
addButtons(buttons, labels, state);
|
||||
panel.add(buttons, BorderLayout.CENTER);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel buildScientificPanel(JTextField display) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
JPanel topRow = new JPanel(new GridLayout(1,4,5,5));
|
||||
topRow.setBorder(BorderFactory.createEmptyBorder(8,8,0,8));
|
||||
|
||||
JButton sin = new JButton("sin");
|
||||
JButton cos = new JButton("cos");
|
||||
JButton tan = new JButton("tan");
|
||||
JButton sqrt = new JButton("√");
|
||||
topRow.add(sin); topRow.add(cos); topRow.add(tan); topRow.add(sqrt);
|
||||
|
||||
JPanel buttons = new JPanel(new GridLayout(5,4,5,5));
|
||||
String[] labels = {
|
||||
"C", "±", "%", "/",
|
||||
"7", "8", "9", "*",
|
||||
"4", "5", "6", "-",
|
||||
"1", "2", "3", "+",
|
||||
"0", ".", "=", "ln"
|
||||
};
|
||||
buttons.setBorder(BorderFactory.createEmptyBorder(0,8,8,8));
|
||||
CalculatorState state = new CalculatorState(display);
|
||||
addButtons(buttons, labels, state);
|
||||
|
||||
ActionListener sciListener = e -> {
|
||||
String cmd = ((JButton)e.getSource()).getText();
|
||||
try {
|
||||
double v = state.currentAsDoubleOrLast();
|
||||
double result;
|
||||
switch (cmd) {
|
||||
case "sin": result = Math.sin(Math.toRadians(v)); break;
|
||||
case "cos": result = Math.cos(Math.toRadians(v)); break;
|
||||
case "tan": result = Math.tan(Math.toRadians(v)); break;
|
||||
case "sqrt": result = Math.sqrt(v); break;
|
||||
case "ln": result = Math.log(v); break;
|
||||
default: return;
|
||||
}
|
||||
state.setResult(result);
|
||||
} catch (NumberFormatException ex) {
|
||||
state.error();
|
||||
}
|
||||
};
|
||||
sin.addActionListener(sciListener);
|
||||
cos.addActionListener(sciListener);
|
||||
tan.addActionListener(sciListener);
|
||||
sqrt.addActionListener(sciListener);
|
||||
|
||||
panel.add(topRow, BorderLayout.NORTH);
|
||||
panel.add(buttons, BorderLayout.CENTER);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel buildProgrammerPanel(JTextField display) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
JPanel baseRow = new JPanel(new GridLayout(1,4,5,5));
|
||||
baseRow.setBorder(BorderFactory.createEmptyBorder(8,8,0,8));
|
||||
JButton binBtn = new JButton("BIN");
|
||||
JButton octBtn = new JButton("OCT");
|
||||
JButton decBtn = new JButton("DEC");
|
||||
JButton hexBtn = new JButton("HEX");
|
||||
baseRow.add(binBtn); baseRow.add(octBtn); baseRow.add(decBtn); baseRow.add(hexBtn);
|
||||
|
||||
JPanel buttons = new JPanel(new GridLayout(6,4,5,5));
|
||||
buttons.setBorder(BorderFactory.createEmptyBorder(0,8,8,8));
|
||||
String[] labels = {
|
||||
"A","B","C","D",
|
||||
"E","F","AND","OR",
|
||||
"<<",">>","^","~",
|
||||
"7","8","9","/",
|
||||
"4","5","6","*",
|
||||
"1","2","3","-",
|
||||
"0","","=", "+"
|
||||
};
|
||||
CalculatorStateProg state = new CalculatorStateProg(display);
|
||||
addButtonsProg(buttons, labels, state);
|
||||
|
||||
// base switching
|
||||
binBtn.addActionListener(e -> state.setBase(2));
|
||||
octBtn.addActionListener(e -> state.setBase(8));
|
||||
decBtn.addActionListener(e -> state.setBase(10));
|
||||
hexBtn.addActionListener(e -> state.setBase(16));
|
||||
state.setBase(10);
|
||||
|
||||
panel.add(baseRow, BorderLayout.NORTH);
|
||||
panel.add(buttons, BorderLayout.CENTER);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void addButtons(JPanel panel, String[] labels, CalculatorState state) {
|
||||
for (String label : labels) {
|
||||
if (label.isEmpty()) { panel.add(new JLabel()); continue; }
|
||||
JButton b = new JButton(label);
|
||||
b.setFont(b.getFont().deriveFont(14f));
|
||||
b.addActionListener(e -> state.handle(((JButton)e.getSource()).getText()));
|
||||
panel.add(b);
|
||||
}
|
||||
}
|
||||
|
||||
private void addButtonsProg(JPanel panel, String[] labels, CalculatorStateProg state) {
|
||||
for (String label : labels) {
|
||||
if (label.isEmpty()) { panel.add(new JLabel()); continue; }
|
||||
JButton b = new JButton(label);
|
||||
b.setFont(b.getFont().deriveFont(13f));
|
||||
b.addActionListener(e -> state.handle(((JButton)e.getSource()).getText()));
|
||||
panel.add(b);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CalculatorState {
|
||||
private final JTextField display;
|
||||
private final StringBuilder current = new StringBuilder();
|
||||
private double lastValue = 0;
|
||||
private String op = null;
|
||||
private boolean resetOnNext = false;
|
||||
|
||||
CalculatorState(JTextField display) { this.display = display; }
|
||||
|
||||
void handle(String cmd) {
|
||||
try {
|
||||
switch (cmd) {
|
||||
case "C": clear(); break;
|
||||
case "±": negate(); break;
|
||||
case "%": percent(); break;
|
||||
case "+": case "-": case "*": case "/": setOperator(cmd); break;
|
||||
case "=": compute(); break;
|
||||
case ".":
|
||||
if (resetOnNext) { current.setLength(0); resetOnNext = false; }
|
||||
if (current.indexOf(".") < 0) {
|
||||
if (current.isEmpty()) current.append("0");
|
||||
current.append(".");
|
||||
display.setText(current.toString());
|
||||
}
|
||||
break;
|
||||
default: // digits
|
||||
if (resetOnNext) { current.setLength(0); resetOnNext = false; }
|
||||
if (current.length() == 1 && current.charAt(0) == '0' && cmd.equals("0")) return;
|
||||
current.append(cmd);
|
||||
display.setText(current.toString());
|
||||
break;
|
||||
}
|
||||
} catch (NumberFormatException ex) { error(); }
|
||||
}
|
||||
|
||||
double currentAsDoubleOrLast() {
|
||||
if (!current.isEmpty()) return Double.parseDouble(current.toString());
|
||||
return lastValue;
|
||||
}
|
||||
|
||||
void setOperatorInternal(String operator) {
|
||||
if (!current.isEmpty()) {
|
||||
double cur = Double.parseDouble(current.toString());
|
||||
if (op != null) lastValue = applyOp(lastValue, cur, op);
|
||||
else lastValue = cur;
|
||||
display.setText(strip(lastValue));
|
||||
current.setLength(0);
|
||||
}
|
||||
op = operator;
|
||||
resetOnNext = false;
|
||||
}
|
||||
|
||||
void setOperator(String operator) { setOperatorInternal(operator); }
|
||||
|
||||
void compute() {
|
||||
if (op == null) {
|
||||
if (!current.isEmpty()) display.setText(current.toString());
|
||||
return;
|
||||
}
|
||||
double cur = !current.isEmpty() ? Double.parseDouble(current.toString()) : lastValue;
|
||||
double result = applyOp(lastValue, cur, op);
|
||||
display.setText(strip(result));
|
||||
current.setLength(0);
|
||||
current.append(strip(result));
|
||||
lastValue = result;
|
||||
op = null;
|
||||
resetOnNext = true;
|
||||
}
|
||||
|
||||
void clear() { current.setLength(0); lastValue = 0; op = null; display.setText(""); }
|
||||
|
||||
void negate() {
|
||||
if (current.isEmpty()) return;
|
||||
if (current.charAt(0)=='-') current.deleteCharAt(0); else current.insert(0,'-');
|
||||
display.setText(current.toString());
|
||||
}
|
||||
|
||||
void percent() {
|
||||
if (current.isEmpty()) return;
|
||||
double v = Double.parseDouble(current.toString())/100.0;
|
||||
current.setLength(0); current.append(strip(v));
|
||||
display.setText(current.toString());
|
||||
}
|
||||
|
||||
void setResult(double v) {
|
||||
display.setText(strip(v));
|
||||
current.setLength(0); current.append(strip(v));
|
||||
lastValue = v; op = null; resetOnNext = true;
|
||||
}
|
||||
|
||||
void error() { display.setText("Error"); current.setLength(0); op = null; lastValue = 0; }
|
||||
|
||||
private double applyOp(double a, double b, String operator) {
|
||||
return switch (operator) {
|
||||
case "+" -> a + b;
|
||||
case "-" -> a - b;
|
||||
case "*" -> a * b;
|
||||
case "/" -> b == 0 ? Double.NaN : a / b;
|
||||
default -> b;
|
||||
};
|
||||
}
|
||||
|
||||
private String strip(double v) {
|
||||
if (Double.isNaN(v)) return "Error";
|
||||
if (v == (long)v) return String.format("%d", (long)v);
|
||||
return String.format("%s", v).replaceAll("\\.?0+$", "");
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------
|
||||
Programmer state
|
||||
- supports bases 2/8/10/16
|
||||
- simple bitwise ops using BigInteger for safety
|
||||
------------------------- */
|
||||
private static class CalculatorStateProg {
|
||||
private final JTextField display;
|
||||
private int base = 10;
|
||||
private BigInteger acc = BigInteger.ZERO;
|
||||
private final StringBuilder current = new StringBuilder();
|
||||
private String pendingOp = null;
|
||||
|
||||
CalculatorStateProg(JTextField display) { this.display = display; }
|
||||
|
||||
void setBase(int b) {
|
||||
// convert displayed/current values to new base
|
||||
String from = !current.isEmpty() ? current.toString() : acc.toString();
|
||||
try {
|
||||
BigInteger val = !current.isEmpty() ? new BigInteger(from, base) : acc;
|
||||
base = b;
|
||||
current.setLength(0);
|
||||
current.append(val.toString(base).toUpperCase());
|
||||
display.setText(current.toString());
|
||||
} catch (Exception e) {
|
||||
// if conversion fails, reset
|
||||
base = b;
|
||||
current.setLength(0);
|
||||
acc = BigInteger.ZERO;
|
||||
display.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
void handle(String cmd) {
|
||||
try {
|
||||
switch (cmd) {
|
||||
case "AND": commitBitwise("AND"); break;
|
||||
case "OR": commitBitwise("OR"); break;
|
||||
case "^": commitBitwise("XOR"); break;
|
||||
case "~": applyNot(); break;
|
||||
case "<<": commitShiftLeft(); break;
|
||||
case ">>": commitShiftRight(); break;
|
||||
case "C": clear(); break;
|
||||
case "=": compute(); break;
|
||||
default:
|
||||
// digit or hex letter or operator symbols used as digits
|
||||
if (isDigitForBase(cmd)) {
|
||||
current.append(cmd);
|
||||
display.setText(current.toString());
|
||||
} else if (cmd.equals("+")||cmd.equals("-")||cmd.equals("*")||cmd.equals("/")) {
|
||||
// allow arithmetic ops in programmer mode by converting to big integer math
|
||||
commitArithmetic(cmd);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) { display.setText("Error"); current.setLength(0); acc = BigInteger.ZERO; pendingOp = null; }
|
||||
}
|
||||
|
||||
private boolean isDigitForBase(String s) {
|
||||
if (s.length()!=1) return false;
|
||||
char c = s.charAt(0);
|
||||
if (Character.isDigit(c)) {
|
||||
int d = c - '0';
|
||||
return d < base;
|
||||
} else {
|
||||
// hex letters
|
||||
if (base<=10) return false;
|
||||
return "ABCDEF".indexOf(Character.toUpperCase(c))>=0;
|
||||
}
|
||||
}
|
||||
|
||||
private BigInteger currentBI() {
|
||||
if (current.isEmpty()) return acc;
|
||||
return new BigInteger(current.toString(), base);
|
||||
}
|
||||
|
||||
private void commitBitwise(String op) {
|
||||
BigInteger cur = currentBI();
|
||||
if (pendingOp == null) {
|
||||
acc = cur;
|
||||
} else {
|
||||
acc = applyBitwise(acc, cur, pendingOp);
|
||||
}
|
||||
pendingOp = op;
|
||||
current.setLength(0);
|
||||
display.setText(acc.toString(base).toUpperCase());
|
||||
}
|
||||
|
||||
private void applyNot() {
|
||||
BigInteger cur = currentBI();
|
||||
// bitwise not in two's complement: choose a width; using 32-bit mask for common programmer behavior
|
||||
int width = Math.max(32, cur.bitLength()+1);
|
||||
BigInteger mask = BigInteger.ONE.shiftLeft(width).subtract(BigInteger.ONE);
|
||||
BigInteger result = cur.not().and(mask);
|
||||
current.setLength(0);
|
||||
current.append(result.toString(base).toUpperCase());
|
||||
display.setText(current.toString());
|
||||
}
|
||||
|
||||
private void commitShiftLeft() {
|
||||
BigInteger cur = currentBI();
|
||||
BigInteger res = cur.shiftLeft(1);
|
||||
current.setLength(0);
|
||||
current.append(res.toString(base).toUpperCase());
|
||||
display.setText(current.toString());
|
||||
}
|
||||
|
||||
private void commitShiftRight() {
|
||||
BigInteger cur = currentBI();
|
||||
BigInteger res = cur.shiftRight(1);
|
||||
current.setLength(0);
|
||||
current.append(res.toString(base).toUpperCase());
|
||||
display.setText(current.toString());
|
||||
}
|
||||
|
||||
private void commitArithmetic(String operator) {
|
||||
BigInteger cur = currentBI();
|
||||
if (pendingOp == null) {
|
||||
acc = cur;
|
||||
} else {
|
||||
acc = applyArithmetic(acc, cur, pendingOp);
|
||||
}
|
||||
pendingOp = operator;
|
||||
current.setLength(0);
|
||||
display.setText(acc.toString(base).toUpperCase());
|
||||
}
|
||||
|
||||
private void compute() {
|
||||
BigInteger cur = currentBI();
|
||||
if (pendingOp == null) {
|
||||
acc = cur;
|
||||
} else {
|
||||
if (pendingOp.equals("AND")||pendingOp.equals("OR")||pendingOp.equals("XOR")) {
|
||||
acc = applyBitwise(acc, cur, pendingOp);
|
||||
} else {
|
||||
acc = applyArithmetic(acc, cur, pendingOp);
|
||||
}
|
||||
}
|
||||
current.setLength(0);
|
||||
current.append(acc.toString(base).toUpperCase());
|
||||
display.setText(current.toString());
|
||||
pendingOp = null;
|
||||
}
|
||||
|
||||
private void clear() { current.setLength(0); acc = BigInteger.ZERO; pendingOp = null; display.setText(""); }
|
||||
|
||||
private BigInteger applyBitwise(BigInteger a, BigInteger b, String op) {
|
||||
return switch (op) {
|
||||
case "AND" -> a.and(b);
|
||||
case "OR" -> a.or(b);
|
||||
case "XOR" -> a.xor(b);
|
||||
default -> b;
|
||||
};
|
||||
}
|
||||
|
||||
private BigInteger applyArithmetic(BigInteger a, BigInteger b, String op) {
|
||||
return switch (op) {
|
||||
case "+" -> a.add(b);
|
||||
case "-" -> a.subtract(b);
|
||||
case "*" -> a.multiply(b);
|
||||
case "/" -> b.equals(BigInteger.ZERO) ? BigInteger.ZERO : a.divide(b);
|
||||
default -> b;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package com.rdesk.applications.productivity.browsing;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebErrorEvent;
|
||||
import javafx.scene.web.WebHistory;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BrowserUI {
|
||||
|
||||
private final String homeUrl;
|
||||
private final TabPane tabPane = new TabPane();
|
||||
private final Map<Tab, WebEngine> engineMap = new HashMap<>();
|
||||
private final TextField urlBar = new TextField();
|
||||
private final ProgressIndicator globalProgress = new ProgressIndicator();
|
||||
private final Button backBtn = new Button("<");
|
||||
private final Button forwardBtn = new Button(">");
|
||||
private final Button refreshBtn = new Button("Refresh");
|
||||
private final Button homeBtn = new Button("Home");
|
||||
private final Button newTabBtn = new Button("+");
|
||||
private final Button openFileBtn = new Button("Open");
|
||||
|
||||
private Scene scene;
|
||||
|
||||
public BrowserUI(String homeUrl) {
|
||||
this.homeUrl = homeUrl;
|
||||
}
|
||||
|
||||
public Scene createScene() {
|
||||
BorderPane root = new BorderPane();
|
||||
root.setTop(createToolbar());
|
||||
root.setCenter(createTabPane());
|
||||
configureTabSelection();
|
||||
this.scene = new Scene(root);
|
||||
return scene;
|
||||
}
|
||||
|
||||
private HBox createToolbar() {
|
||||
HBox toolbar = new HBox(5);
|
||||
toolbar.setPadding(new Insets(5));
|
||||
toolbar.setAlignment(Pos.CENTER_LEFT);
|
||||
urlBar.setPrefWidth(500);
|
||||
globalProgress.setVisible(false);
|
||||
globalProgress.setPrefSize(20, 20);
|
||||
|
||||
toolbar.getChildren().addAll(
|
||||
backBtn, forwardBtn, refreshBtn, homeBtn, newTabBtn,
|
||||
openFileBtn, urlBar, globalProgress);
|
||||
|
||||
attachToolbarActions();
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
private TabPane createTabPane() {
|
||||
tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS);
|
||||
addNewTab(homeUrl);
|
||||
return tabPane;
|
||||
}
|
||||
|
||||
private void attachToolbarActions() {
|
||||
Consumer<Consumer<WebEngine>> onCurrentTab = operation -> {
|
||||
try {
|
||||
Tab current = tabPane.getSelectionModel().getSelectedItem();
|
||||
if (current != null) {
|
||||
WebEngine engine = engineMap.get(current);
|
||||
if (engine != null) operation.accept(engine);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logError("Toolbar action failed", ex);
|
||||
}
|
||||
};
|
||||
|
||||
backBtn.setOnAction(e -> onCurrentTab.accept(engine -> {
|
||||
WebHistory history = engine.getHistory();
|
||||
if (history.getCurrentIndex() > 0) history.go(-1);
|
||||
}));
|
||||
|
||||
forwardBtn.setOnAction(e -> onCurrentTab.accept(engine -> {
|
||||
WebHistory history = engine.getHistory();
|
||||
if (history.getCurrentIndex() < history.getEntries().size() - 1) history.go(1);
|
||||
}));
|
||||
|
||||
refreshBtn.setOnAction(e -> onCurrentTab.accept(WebEngine::reload));
|
||||
homeBtn.setOnAction(e -> onCurrentTab.accept(engine -> engine.load(homeUrl)));
|
||||
newTabBtn.setOnAction(e -> addNewTab(homeUrl));
|
||||
|
||||
// --- Open local file button (fixed: no owner window cast) ---
|
||||
openFileBtn.setOnAction(e -> {
|
||||
try {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Open HTML File");
|
||||
fileChooser.getExtensionFilters().add(
|
||||
new FileChooser.ExtensionFilter(
|
||||
"Web Pages", "*.html", "*.htm", "*.xhtml", "*.svg", "*.xml", "*.txt"));
|
||||
// Show without an owner window – works inside an embedded JFXPanel
|
||||
File selectedFile = fileChooser.showOpenDialog(null);
|
||||
if (selectedFile != null) {
|
||||
String fileUrl = selectedFile.toURI().toString();
|
||||
onCurrentTab.accept(engine -> engine.load(fileUrl));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logError("Open file action failed", ex);
|
||||
}
|
||||
});
|
||||
|
||||
// --- URL bar ---
|
||||
urlBar.setOnAction(e -> {
|
||||
try {
|
||||
String input = urlBar.getText().trim();
|
||||
if (input.isEmpty()) return;
|
||||
|
||||
String url = input;
|
||||
if (!url.contains("://")) {
|
||||
if (isLikelyFilePath(url)) {
|
||||
try {
|
||||
File file = new File(url);
|
||||
url = file.toURI().toString();
|
||||
} catch (Exception ex) {
|
||||
url = "file://" + url;
|
||||
}
|
||||
} else {
|
||||
url = "https://" + url;
|
||||
}
|
||||
urlBar.setText(url);
|
||||
}
|
||||
final String finalUrl = url;
|
||||
onCurrentTab.accept(engine -> engine.load(finalUrl));
|
||||
} catch (Exception ex) {
|
||||
logError("URL bar action failed", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isLikelyFilePath(String input) {
|
||||
if (input.startsWith("/") || input.startsWith("~")) {
|
||||
return true;
|
||||
}
|
||||
if (input.length() >= 2 && Character.isLetter(input.charAt(0))
|
||||
&& input.charAt(1) == ':') {
|
||||
char third = input.length() > 2 ? input.charAt(2) : 0;
|
||||
if (third == '\\' || third == '/') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void configureTabSelection() {
|
||||
tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> {
|
||||
try {
|
||||
if (newTab != null) {
|
||||
WebEngine engine = engineMap.get(newTab);
|
||||
if (engine != null) {
|
||||
urlBar.setText(engine.getLocation());
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logError("Tab selection change failed", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addNewTab(String initialUrl) {
|
||||
try {
|
||||
WebView webView = new WebView();
|
||||
WebEngine engine = webView.getEngine();
|
||||
|
||||
engine.getLoadWorker().exceptionProperty().addListener((obs, oldExc, newExc) -> {
|
||||
if (newExc != null) {
|
||||
logError("WebEngine load exception", newExc);
|
||||
showErrorPage(engine, newExc);
|
||||
}
|
||||
});
|
||||
|
||||
engine.setOnError((WebErrorEvent event) -> {
|
||||
logError("WebEngine onError: " + event.getMessage(), null);
|
||||
showErrorPage(engine, new Exception(event.getMessage()));
|
||||
});
|
||||
|
||||
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
|
||||
if (newState == Worker.State.FAILED) {
|
||||
Throwable ex = engine.getLoadWorker().getException();
|
||||
if (ex != null) {
|
||||
showErrorPage(engine, ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
engine.load(initialUrl);
|
||||
|
||||
ProgressIndicator tabProgress = new ProgressIndicator();
|
||||
tabProgress.setPrefSize(16, 16);
|
||||
|
||||
Tab tab = new Tab("Loading...", webView);
|
||||
tab.setGraphic(tabProgress);
|
||||
tab.setClosable(true);
|
||||
|
||||
engineMap.put(tab, engine);
|
||||
|
||||
engine.titleProperty().addListener((obs, old, newTitle) -> {
|
||||
try {
|
||||
if (newTitle != null && !newTitle.isEmpty()) {
|
||||
tab.setText(newTitle);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logError("Title listener error", ex);
|
||||
}
|
||||
});
|
||||
|
||||
engine.locationProperty().addListener((obs, old, newUrl) -> {
|
||||
try {
|
||||
if (newUrl != null && !newUrl.isEmpty()) {
|
||||
Tab selected = tabPane.getSelectionModel().getSelectedItem();
|
||||
if (selected == tab) {
|
||||
urlBar.setText(newUrl);
|
||||
}
|
||||
if (engine.getTitle() == null || engine.getTitle().isEmpty()) {
|
||||
String domain = newUrl.replaceFirst("^https?://", "")
|
||||
.replaceFirst("/.*$", "");
|
||||
tab.setText(domain);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logError("Location listener error", ex);
|
||||
}
|
||||
});
|
||||
|
||||
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
|
||||
try {
|
||||
boolean running = newState == Worker.State.RUNNING;
|
||||
tabProgress.setVisible(running);
|
||||
if (!running && tab.getGraphic() == tabProgress) {
|
||||
tab.setGraphic(null);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logError("State listener error", ex);
|
||||
}
|
||||
});
|
||||
|
||||
engine.getLoadWorker().progressProperty().addListener((obs, old, prog) -> {
|
||||
try {
|
||||
Tab selected = tabPane.getSelectionModel().getSelectedItem();
|
||||
if (selected == tab) {
|
||||
double value = prog.doubleValue();
|
||||
globalProgress.setVisible(value > 0 && value < 1);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logError("Progress listener error", ex);
|
||||
}
|
||||
});
|
||||
|
||||
tabPane.getTabs().add(tab);
|
||||
tabPane.getSelectionModel().select(tab);
|
||||
tab.setOnClosed(event -> engineMap.remove(tab));
|
||||
|
||||
} catch (Exception ex) {
|
||||
logError("Failed to create new tab", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void showErrorPage(WebEngine engine, Throwable exception) {
|
||||
StringWriter sw = new StringWriter();
|
||||
exception.printStackTrace(new PrintWriter(sw));
|
||||
String errorHtml = "<html><body style='font-family:sans-serif;padding:20px;'>"
|
||||
+ "<h2>Page could not be loaded</h2>"
|
||||
+ "<p style='color:red;'>" + exception.getMessage() + "</p>"
|
||||
+ "<pre style='font-size:smaller;white-space:pre-wrap;'>"
|
||||
+ sw.toString().replace("&", "&").replace("<", "<")
|
||||
+ "</pre>"
|
||||
+ "</body></html>";
|
||||
Platform.runLater(() -> engine.loadContent(errorHtml));
|
||||
}
|
||||
|
||||
private static void logError(String message, Throwable t) {
|
||||
System.err.println("[BrowserUI ERROR] " + message);
|
||||
if (t != null) {
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.rdesk.applications.productivity.browsing;
|
||||
|
||||
import com.rdesk.kernel.DesktopApp;
|
||||
import com.rdesk.kernel.Kernel;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
|
||||
public class TabbedBrowser implements DesktopApp {
|
||||
|
||||
private static final String HOME_URL = "https://www.google.com";
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "sharkbrowser";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "SharkBrowser";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JInternalFrame createWindow(Kernel kernel) {
|
||||
JInternalFrame frame = new JInternalFrame(
|
||||
"SharkBrowser", true, true, true, false);
|
||||
frame.setSize(1200, 800);
|
||||
frame.setLayout(new BorderLayout());
|
||||
|
||||
JFXPanel fxPanel = new JFXPanel();
|
||||
frame.add(fxPanel, BorderLayout.CENTER);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
BrowserUI ui = new BrowserUI(HOME_URL);
|
||||
Scene scene = ui.createScene();
|
||||
fxPanel.setScene(scene);
|
||||
|
||||
frame.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
Platform.runLater(() -> {
|
||||
double w = fxPanel.getWidth();
|
||||
double h = fxPanel.getHeight();
|
||||
if (w > 0 && h > 0) {
|
||||
scene.getWindow().setWidth(w);
|
||||
scene.getWindow().setHeight(h);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
frame.setVisible(true);
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.rdesk.applications.productivity.code;
|
||||
|
||||
import com.rdesk.kernel.*;
|
||||
import javax.swing.*;
|
||||
|
||||
public class CodeDesk implements DesktopApp {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "CodeDesk";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "CodeDesk";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JInternalFrame createWindow(Kernel kernel) {
|
||||
EditorWindow window = new EditorWindow();
|
||||
return window.getFrame();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.rdesk.applications.productivity.code;
|
||||
|
||||
import org.fife.rsta.ac.AbstractLanguageSupport;
|
||||
import org.fife.ui.rsyntaxtextarea.*;
|
||||
import org.fife.ui.rtextarea.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.*;
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
|
||||
public class EditorTab {
|
||||
|
||||
private final TabManager tabManager;
|
||||
private RSyntaxTextArea textArea;
|
||||
private RTextScrollPane scrollPane;
|
||||
private File file;
|
||||
private boolean dirty;
|
||||
private AbstractLanguageSupport langSupport;
|
||||
private String title; // base title without dirty marker
|
||||
private JPanel tabHeaderComponent;
|
||||
|
||||
public EditorTab(String title, TabManager tabManager) {
|
||||
this.tabManager = tabManager;
|
||||
this.title = title;
|
||||
this.file = null;
|
||||
this.dirty = false;
|
||||
buildTextArea();
|
||||
buildScrollPane();
|
||||
installDocumentListener();
|
||||
installLanguageSupport(null);
|
||||
}
|
||||
|
||||
|
||||
public RSyntaxTextArea getTextArea() { return textArea; }
|
||||
public RTextScrollPane getScrollPane() { return scrollPane; }
|
||||
public File getFile() { return file; }
|
||||
public boolean isDirty() { return dirty; }
|
||||
public String getTitle() { return title; }
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
tabManager.updateTabTitle(this);
|
||||
}
|
||||
|
||||
|
||||
public void loadFile(File file) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
textArea.read(reader, null);
|
||||
}
|
||||
this.file = file;
|
||||
this.dirty = false;
|
||||
setTitle(file.getName());
|
||||
String style = LanguageSupport.syntaxStyleFor(file.getName());
|
||||
textArea.setSyntaxEditingStyle(style);
|
||||
installLanguageSupport(style);
|
||||
tabManager.updateTabTitle(this);
|
||||
}
|
||||
|
||||
public void saveToFile(File file) {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
|
||||
textArea.write(writer);
|
||||
this.file = file;
|
||||
this.dirty = false;
|
||||
setTitle(file.getName());
|
||||
tabManager.updateTabTitle(this);
|
||||
} catch (IOException ex) {
|
||||
JOptionPane.showMessageDialog(tabManager.window.getFrame(),
|
||||
"Error saving file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
public void performSaveAs(Component parent) {
|
||||
JFileChooser fc = tabManager.window.buildFileChooser();
|
||||
if (fc.showSaveDialog(parent) != JFileChooser.APPROVE_OPTION) return;
|
||||
File chosen = fc.getSelectedFile();
|
||||
saveToFile(chosen);
|
||||
String style = LanguageSupport.syntaxStyleFor(chosen.getName());
|
||||
textArea.setSyntaxEditingStyle(style);
|
||||
installLanguageSupport(style);
|
||||
}
|
||||
|
||||
|
||||
public JPanel getTabHeaderComponent() {
|
||||
if (tabHeaderComponent == null) {
|
||||
tabHeaderComponent = buildTabHeader();
|
||||
}
|
||||
return tabHeaderComponent;
|
||||
}
|
||||
|
||||
private JPanel buildTabHeader() {
|
||||
JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
|
||||
panel.setOpaque(false);
|
||||
JLabel label = new JLabel(title);
|
||||
JButton close = new JButton("✕");
|
||||
close.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0));
|
||||
close.setContentAreaFilled(false);
|
||||
close.setFocusable(false);
|
||||
close.addActionListener(e -> tabManager.closeTab(this));
|
||||
panel.add(label);
|
||||
panel.add(close);
|
||||
return panel;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (langSupport != null) {
|
||||
langSupport.uninstall(textArea);
|
||||
langSupport = null;
|
||||
}
|
||||
file = null;
|
||||
}
|
||||
|
||||
|
||||
private void buildTextArea() {
|
||||
textArea = new RSyntaxTextArea(25, 80);
|
||||
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_NONE);
|
||||
textArea.setCodeFoldingEnabled(true);
|
||||
textArea.setAntiAliasingEnabled(true);
|
||||
textArea.setBracketMatchingEnabled(true);
|
||||
textArea.setPaintMatchedBracketPair(true);
|
||||
textArea.setMarkOccurrences(true);
|
||||
textArea.setAutoIndentEnabled(true);
|
||||
textArea.setTabSize(4);
|
||||
textArea.setTabsEmulated(true);
|
||||
}
|
||||
|
||||
private void buildScrollPane() {
|
||||
scrollPane = new RTextScrollPane(textArea);
|
||||
scrollPane.setFoldIndicatorEnabled(true);
|
||||
scrollPane.setLineNumbersEnabled(true);
|
||||
}
|
||||
|
||||
private void installDocumentListener() {
|
||||
textArea.getDocument().addDocumentListener(new DocumentListener() {
|
||||
private void markDirty() {
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
tabManager.updateTabTitle(EditorTab.this);
|
||||
}
|
||||
}
|
||||
@Override public void insertUpdate(DocumentEvent e) { markDirty(); }
|
||||
@Override public void removeUpdate(DocumentEvent e) { markDirty(); }
|
||||
@Override public void changedUpdate(DocumentEvent e) { markDirty(); }
|
||||
});
|
||||
}
|
||||
|
||||
private void installLanguageSupport(String style) {
|
||||
if (langSupport != null) {
|
||||
langSupport.uninstall(textArea);
|
||||
langSupport = null;
|
||||
}
|
||||
langSupport = LanguageSupport.install(textArea, style);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.rdesk.applications.productivity.code;
|
||||
|
||||
import org.fife.ui.rtextarea.*;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.io.*;
|
||||
|
||||
public class EditorWindow {
|
||||
|
||||
private JInternalFrame frame;
|
||||
final TabManager tabManager;
|
||||
private JToolBar toolbar;
|
||||
|
||||
public EditorWindow() {
|
||||
frame = new JInternalFrame("CodeDesk", true, true, true, true);
|
||||
frame.setSize(960, 680);
|
||||
frame.setLayout(new BorderLayout());
|
||||
|
||||
tabManager = new TabManager(this);
|
||||
|
||||
toolbar = buildToolBar();
|
||||
frame.add(toolbar, BorderLayout.NORTH);
|
||||
frame.add(tabManager.getTabbedPane(), BorderLayout.CENTER);
|
||||
|
||||
installGlobalShortcuts();
|
||||
tabManager.addTab("Untitled");
|
||||
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
public JInternalFrame getFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
private JToolBar buildToolBar() {
|
||||
JToolBar bar = new JToolBar();
|
||||
bar.setFloatable(false);
|
||||
|
||||
JButton newTabBtn = new JButton("New Tab");
|
||||
JButton openBtn = new JButton("Open");
|
||||
JButton saveBtn = new JButton("Save");
|
||||
JButton saveAsBtn = new JButton("Save As");
|
||||
|
||||
newTabBtn.addActionListener(e -> tabManager.addTab("Untitled"));
|
||||
openBtn.addActionListener(e -> actionOpen());
|
||||
saveBtn.addActionListener(e -> actionSave());
|
||||
saveAsBtn.addActionListener(e -> actionSaveAs());
|
||||
|
||||
bar.add(newTabBtn);
|
||||
bar.add(openBtn);
|
||||
bar.add(saveBtn);
|
||||
bar.add(saveAsBtn);
|
||||
return bar;
|
||||
}
|
||||
|
||||
private void installGlobalShortcuts() {
|
||||
InputMap im = frame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||
ActionMap am = frame.getRootPane().getActionMap();
|
||||
|
||||
bind(im, am, "open", KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK),
|
||||
e -> actionOpen());
|
||||
bind(im, am, "save", KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK),
|
||||
e -> actionSave());
|
||||
bind(im, am, "saveAs", KeyStroke.getKeyStroke(KeyEvent.VK_S,
|
||||
InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK),
|
||||
e -> actionSaveAs());
|
||||
bind(im, am, "closeTab", KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK),
|
||||
e -> tabManager.closeCurrentTab());
|
||||
bind(im, am, "newTab", KeyStroke.getKeyStroke(KeyEvent.VK_T, InputEvent.CTRL_DOWN_MASK),
|
||||
e -> tabManager.addTab("Untitled"));
|
||||
}
|
||||
|
||||
private void bind(InputMap im, ActionMap am, String key, KeyStroke ks, ActionListener handler) {
|
||||
im.put(ks, key);
|
||||
am.put(key, new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
handler.actionPerformed(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void actionOpen() {
|
||||
JFileChooser fc = buildFileChooser();
|
||||
if (fc.showOpenDialog(frame) != JFileChooser.APPROVE_OPTION) return;
|
||||
tabManager.openFile(fc.getSelectedFile());
|
||||
}
|
||||
|
||||
private void actionSave() {
|
||||
EditorTab tab = tabManager.getCurrentTab();
|
||||
if (tab == null) return;
|
||||
if (tab.getFile() != null) {
|
||||
tab.saveToFile(tab.getFile());
|
||||
} else {
|
||||
tab.performSaveAs(frame);
|
||||
}
|
||||
}
|
||||
|
||||
private void actionSaveAs() {
|
||||
EditorTab tab = tabManager.getCurrentTab();
|
||||
if (tab != null) tab.performSaveAs(frame);
|
||||
}
|
||||
|
||||
|
||||
JFileChooser buildFileChooser() {
|
||||
JFileChooser fc = new JFileChooser();
|
||||
fc.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter(
|
||||
"Source Files (java, js, ts, html, css, php, c, cpp, py, rb, pl, sh, sql, xml)",
|
||||
"java", "js", "ts", "html", "htm", "css", "php",
|
||||
"c", "h", "cpp", "cc", "cxx", "hpp",
|
||||
"py", "rb", "pl", "sh", "bash", "sql", "xml"));
|
||||
fc.setAcceptAllFileFilterUsed(true);
|
||||
return fc;
|
||||
}
|
||||
|
||||
void showError(String prefix, Exception ex) {
|
||||
JOptionPane.showMessageDialog(frame,
|
||||
prefix + ": " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.rdesk.applications.productivity.code;
|
||||
|
||||
import org.fife.rsta.ac.AbstractLanguageSupport;
|
||||
import org.fife.rsta.ac.css.CssLanguageSupport;
|
||||
import org.fife.rsta.ac.html.HtmlLanguageSupport;
|
||||
import org.fife.rsta.ac.java.JavaLanguageSupport;
|
||||
import org.fife.rsta.ac.js.JavaScriptLanguageSupport;
|
||||
import org.fife.ui.autocomplete.*;
|
||||
import org.fife.ui.rsyntaxtextarea.*;
|
||||
import javax.swing.*;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.io.IOException;
|
||||
|
||||
public class LanguageSupport {
|
||||
public static String syntaxStyleFor(String fileName) {
|
||||
if (fileName == null) return SyntaxConstants.SYNTAX_STYLE_NONE;
|
||||
String n = fileName.toLowerCase();
|
||||
if (n.endsWith(".java")) return SyntaxConstants.SYNTAX_STYLE_JAVA;
|
||||
if (n.endsWith(".js") || n.endsWith(".ts")) return SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT;
|
||||
if (n.endsWith(".html") || n.endsWith(".htm")) return SyntaxConstants.SYNTAX_STYLE_HTML;
|
||||
if (n.endsWith(".css")) return SyntaxConstants.SYNTAX_STYLE_CSS;
|
||||
if (n.endsWith(".php")) return SyntaxConstants.SYNTAX_STYLE_PHP;
|
||||
if (n.endsWith(".c") || n.endsWith(".h")) return SyntaxConstants.SYNTAX_STYLE_C;
|
||||
if (n.endsWith(".cpp") || n.endsWith(".cc")
|
||||
|| n.endsWith(".cxx") || n.endsWith(".hpp")) return SyntaxConstants.SYNTAX_STYLE_CPLUSPLUS;
|
||||
if (n.endsWith(".py")) return SyntaxConstants.SYNTAX_STYLE_PYTHON;
|
||||
if (n.endsWith(".rb")) return SyntaxConstants.SYNTAX_STYLE_RUBY;
|
||||
if (n.endsWith(".pl")) return SyntaxConstants.SYNTAX_STYLE_PERL;
|
||||
if (n.endsWith(".sh") || n.endsWith(".bash")) return SyntaxConstants.SYNTAX_STYLE_UNIX_SHELL;
|
||||
if (n.endsWith(".sql")) return SyntaxConstants.SYNTAX_STYLE_SQL;
|
||||
if (n.endsWith(".xml")) return SyntaxConstants.SYNTAX_STYLE_XML;
|
||||
return SyntaxConstants.SYNTAX_STYLE_NONE;
|
||||
}
|
||||
|
||||
public static AbstractLanguageSupport install(RSyntaxTextArea area, String style) {
|
||||
if (style == null) style = area.getSyntaxEditingStyle();
|
||||
|
||||
AbstractLanguageSupport ls = buildLanguageSupport(style);
|
||||
if (ls != null) {
|
||||
try {
|
||||
configureSupport(ls);
|
||||
ls.install(area);
|
||||
return ls;
|
||||
} catch (Exception ex) {
|
||||
System.err.println("CodeDesk: could not install language support for "
|
||||
+ style + ": " + ex.getMessage());
|
||||
installFallbackCompletion(area);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
installFallbackCompletion(area);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static AbstractLanguageSupport buildLanguageSupport(String style) {
|
||||
return switch (style) {
|
||||
case SyntaxConstants.SYNTAX_STYLE_JAVA -> new JavaLanguageSupport();
|
||||
case SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT -> new JavaScriptLanguageSupport();
|
||||
case SyntaxConstants.SYNTAX_STYLE_HTML -> new HtmlLanguageSupport();
|
||||
case SyntaxConstants.SYNTAX_STYLE_CSS -> new CssLanguageSupport();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private static void configureSupport(AbstractLanguageSupport ls) throws IOException {
|
||||
ls.setAutoActivationEnabled(true);
|
||||
ls.setAutoActivationDelay(400);
|
||||
ls.setAutoCompleteEnabled(true);
|
||||
ls.setParameterAssistanceEnabled(true);
|
||||
ls.setShowDescWindow(true);
|
||||
if (ls instanceof JavaLanguageSupport jls) {
|
||||
jls.getJarManager().addCurrentJreClassFileSource();
|
||||
}
|
||||
}
|
||||
|
||||
private static void installFallbackCompletion(RSyntaxTextArea area) {
|
||||
AutoCompletion ac = new AutoCompletion(new DefaultCompletionProvider());
|
||||
ac.setAutoCompleteEnabled(true);
|
||||
ac.setAutoActivationEnabled(false);
|
||||
ac.setTriggerKey(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_DOWN_MASK));
|
||||
ac.install(area);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.rdesk.applications.productivity.code;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.*;
|
||||
import org.fife.ui.rtextarea.RTextScrollPane;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class TabManager {
|
||||
|
||||
final EditorWindow window;
|
||||
private final JTabbedPane tabbedPane;
|
||||
private final List<EditorTab> tabs = new ArrayList<>();
|
||||
|
||||
public TabManager(EditorWindow window) {
|
||||
this.window = window;
|
||||
this.tabbedPane = new JTabbedPane();
|
||||
tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||
}
|
||||
|
||||
public JTabbedPane getTabbedPane() {
|
||||
return tabbedPane;
|
||||
}
|
||||
|
||||
|
||||
public void addTab(String title) {
|
||||
EditorTab tab = new EditorTab(title, this);
|
||||
addTabComponent(tab);
|
||||
}
|
||||
|
||||
public void openFile(File file) {
|
||||
for (EditorTab tab : tabs) {
|
||||
if (file.equals(tab.getFile())) {
|
||||
selectTabFor(tab);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EditorTab current = getCurrentTab();
|
||||
if (current != null
|
||||
&& current.getTextArea().getText().isEmpty()
|
||||
&& current.getFile() == null) {
|
||||
loadFileInTab(current, file);
|
||||
} else {
|
||||
EditorTab newTab = new EditorTab(file.getName(), this);
|
||||
addTabComponent(newTab);
|
||||
loadFileInTab(newTab, file);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeCurrentTab() {
|
||||
int idx = tabbedPane.getSelectedIndex();
|
||||
if (idx < 0) return;
|
||||
closeTab(tabs.get(idx));
|
||||
}
|
||||
|
||||
public void closeTab(EditorTab tab) {
|
||||
if (!confirmDiscardOrSave(tab)) return;
|
||||
|
||||
int idx = tabs.indexOf(tab);
|
||||
if (idx >= 0) {
|
||||
tabbedPane.removeTabAt(idx);
|
||||
tabs.remove(idx);
|
||||
}
|
||||
tab.dispose();
|
||||
}
|
||||
|
||||
public void updateTabTitle(EditorTab tab) {
|
||||
int idx = tabs.indexOf(tab);
|
||||
if (idx < 0) return;
|
||||
|
||||
String title = (tab.isDirty() ? "*" : "") +
|
||||
(tab.getFile() != null ? tab.getFile().getName() : "Untitled");
|
||||
tabbedPane.setTitleAt(idx, title);
|
||||
|
||||
// Update the label inside the custom tab component
|
||||
Component comp = tabbedPane.getTabComponentAt(idx);
|
||||
if (comp instanceof JPanel p && p.getComponentCount() > 0
|
||||
&& p.getComponent(0) instanceof JLabel lbl) {
|
||||
lbl.setText(title);
|
||||
}
|
||||
}
|
||||
|
||||
public EditorTab getCurrentTab() {
|
||||
int idx = tabbedPane.getSelectedIndex();
|
||||
return (idx >= 0 && idx < tabs.size()) ? tabs.get(idx) : null;
|
||||
}
|
||||
|
||||
|
||||
private void addTabComponent(EditorTab tab) {
|
||||
tabs.add(tab);
|
||||
RTextScrollPane scroll = tab.getScrollPane();
|
||||
tabbedPane.addTab(tab.getTitle(), scroll);
|
||||
int idx = tabbedPane.indexOfComponent(scroll);
|
||||
tabbedPane.setTabComponentAt(idx, tab.getTabHeaderComponent());
|
||||
tabbedPane.setSelectedIndex(idx);
|
||||
updateTabTitle(tab);
|
||||
}
|
||||
|
||||
private void loadFileInTab(EditorTab tab, File file) {
|
||||
try {
|
||||
tab.loadFile(file);
|
||||
} catch (IOException ex) {
|
||||
window.showError("Error opening file", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectTabFor(EditorTab tab) {
|
||||
int idx = tabs.indexOf(tab);
|
||||
if (idx >= 0) tabbedPane.setSelectedIndex(idx);
|
||||
}
|
||||
|
||||
private boolean confirmDiscardOrSave(EditorTab tab) {
|
||||
if (!tab.isDirty()) return true;
|
||||
|
||||
String name = (tab.getFile() != null) ? tab.getFile().getName() : "Untitled";
|
||||
int result = JOptionPane.showConfirmDialog(
|
||||
window.getFrame(),
|
||||
"Save changes to \"" + name + "\"?",
|
||||
"Unsaved Changes",
|
||||
JOptionPane.YES_NO_CANCEL_OPTION);
|
||||
|
||||
if (result == JOptionPane.YES_OPTION) {
|
||||
if (tab.getFile() != null) {
|
||||
tab.saveToFile(tab.getFile());
|
||||
} else {
|
||||
tab.performSaveAs(window.getFrame());
|
||||
}
|
||||
}
|
||||
return result != JOptionPane.CANCEL_OPTION;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.rdesk.kernel.Kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Exit implements DesktopApp {
|
||||
|
||||
@@ -35,10 +36,20 @@ public class Exit implements DesktopApp {
|
||||
label.setFont(label.getFont().deriveFont(Font.BOLD));
|
||||
mainPanel.add(label, gbc);
|
||||
|
||||
JButton shutdownButton = new JButton("Shutdown OS");
|
||||
JButton shutdownButton = new JButton("Shutdown rDesk");
|
||||
JButton shutdownOS = new JButton("Shutdown Host OS");
|
||||
shutdownButton.setFocusPainted(false);
|
||||
shutdownButton.addActionListener(e -> confirmAndShutdown(kernel, frame));
|
||||
shutdownOS.setFocusPainted(false);
|
||||
shutdownOS.addActionListener(e -> {
|
||||
try {
|
||||
shutdownHost(kernel, frame);
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
});
|
||||
mainPanel.add(shutdownButton, gbc);
|
||||
mainPanel.add(shutdownOS, gbc);
|
||||
|
||||
JButton cancelButton = new JButton("Cancel");
|
||||
cancelButton.setFocusPainted(false);
|
||||
@@ -50,10 +61,47 @@ public class Exit implements DesktopApp {
|
||||
return frame;
|
||||
}
|
||||
|
||||
public void performShutdownCmd() throws IOException, InterruptedException {
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
String[] cmd;
|
||||
|
||||
if (os.contains("win")) {
|
||||
cmd = new String[] {"shutdown.exe", "-s", "-t", "2"};
|
||||
} else if (os.contains("mac") || os.contains("darwin") || os.contains("nix") || os.contains("nux") || os.contains("aix")) {
|
||||
cmd = new String[] {"shutdown", "-h", "now"};
|
||||
} else {
|
||||
throw new RuntimeException("Unknown OS: " + os);
|
||||
}
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmd);
|
||||
pb.inheritIO();
|
||||
Process p = pb.start();
|
||||
int exit = p.waitFor();
|
||||
if (exit != 0) {
|
||||
throw new IOException("Shutdown command exited with code " + exit);
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownHost(Kernel kernel, JInternalFrame frame) throws IOException, InterruptedException {
|
||||
int choice = JOptionPane.showConfirmDialog(
|
||||
frame,
|
||||
"Confirm Host Shutdown",
|
||||
"Are you sure? This will shutdown the Host OS",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
|
||||
if (choice == JOptionPane.YES_OPTION) {
|
||||
frame.dispose();
|
||||
performShutdownCmd(); // cause else the call wouldnt be called
|
||||
kernel.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
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.",
|
||||
"Are you sure?",
|
||||
"Confirm Shutdown",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
|
||||
@@ -4,6 +4,7 @@ import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
@@ -144,7 +145,12 @@ public class AppLauncher extends JPanel {
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(
|
||||
null,
|
||||
"Kernel Fault",
|
||||
"The Kernel has encoutered a fault when trying to find DesktopAppClass" + e.getStackTrace(),
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -198,7 +204,18 @@ public class AppLauncher extends JPanel {
|
||||
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()));
|
||||
appButton.addActionListener(e -> {
|
||||
try {
|
||||
kernel.apps().launch(app.getId());
|
||||
} catch (IOException ex) {
|
||||
JOptionPane.showMessageDialog(
|
||||
appsContainer,
|
||||
"Kernel Fault",
|
||||
"The Kernel encoutered a critical error when launching an Application",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Make each app button fill the width of its container
|
||||
appButton.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class AppManager {
|
||||
@@ -22,7 +23,7 @@ public class AppManager {
|
||||
return installedApps.containsKey(id);
|
||||
}
|
||||
|
||||
public void launch(String id) {
|
||||
public void launch(String id) throws IOException {
|
||||
DesktopApp app = installedApps.get(id);
|
||||
if (app == null) {
|
||||
System.err.println("[AppManager] App not found: " + id);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.rdesk.kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface DesktopApp {
|
||||
|
||||
@@ -12,7 +13,7 @@ public interface DesktopApp {
|
||||
return null;
|
||||
}
|
||||
|
||||
JInternalFrame createWindow(Kernel kernel);
|
||||
JInternalFrame createWindow(Kernel kernel) throws IOException;
|
||||
|
||||
default void onStart(Kernel kernel) {}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.rdesk.kernel;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.jar.*;
|
||||
@@ -171,7 +172,11 @@ public class DesktopIconManager {
|
||||
if (!kernel.apps().isRegistered(app.getId())) {
|
||||
kernel.apps().register(app);
|
||||
}
|
||||
kernel.apps().launch(app.getId());
|
||||
try {
|
||||
kernel.apps().launch(app.getId());
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
desktop.add(button);
|
||||
|
||||
@@ -23,7 +23,6 @@ public class Kernel {
|
||||
this.windowManager = new WindowManager(this);
|
||||
}
|
||||
|
||||
/** Thread-safe singleton accessor. */
|
||||
public static Kernel get() {
|
||||
if (instance == null) {
|
||||
synchronized (Kernel.class) {
|
||||
@@ -45,29 +44,10 @@ public class Kernel {
|
||||
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(
|
||||
@@ -96,10 +76,6 @@ public class Kernel {
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// Accessors //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
public AppManager apps() { return appManager; }
|
||||
public WindowManager windows() { return windowManager; }
|
||||
public EventBus events() { return eventBus; }
|
||||
|
||||
Reference in New Issue
Block a user