-
Notifications
You must be signed in to change notification settings - Fork 108
/
Copy pathMain.java
230 lines (205 loc) · 9.12 KB
/
Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package edu.wpi.grip.ui;
import edu.wpi.grip.core.CoreCommandLineHelper;
import edu.wpi.grip.core.GripCoreModule;
import edu.wpi.grip.core.GripFileManager;
import edu.wpi.grip.core.GripFileModule;
import edu.wpi.grip.core.PipelineRunner;
import edu.wpi.grip.core.events.UnexpectedThrowableEvent;
import edu.wpi.grip.core.exception.GripServerException;
import edu.wpi.grip.core.http.GripServer;
import edu.wpi.grip.core.http.HttpPipelineSwitcher;
import edu.wpi.grip.core.operations.CVOperations;
import edu.wpi.grip.core.operations.Operations;
import edu.wpi.grip.core.operations.network.GripNetworkModule;
import edu.wpi.grip.core.serialization.Project;
import edu.wpi.grip.core.settings.SettingsProvider;
import edu.wpi.grip.core.sources.GripSourcesHardwareModule;
import edu.wpi.grip.core.util.SafeShutdown;
import edu.wpi.grip.ui.util.DPIUtility;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.util.Modules;
import com.sun.javafx.application.PlatformImpl;
import org.apache.commons.cli.CommandLine;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.application.Preloader;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.image.Image;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javax.inject.Inject;
public class Main extends Application {
private final Object dialogLock = new Object();
private static final Logger logger = Logger.getLogger(Main.class.getName());
private static final String MAIN_TITLE = "GRIP Computer Vision Engine";
/**
* JavaFX insists on creating the main application with its own reflection code, so we can't
* create with the Guice and do automatic field injection. However, we can inject it after the
* fact.
*/
@VisibleForTesting
protected Injector injector;
@Inject private EventBus eventBus;
@Inject private PipelineRunner pipelineRunner;
@Inject private Project project;
@Inject private SettingsProvider settingsProvider;
@Inject private Operations operations;
@Inject private CVOperations cvOperations;
@Inject private GripServer server;
@Inject private HttpPipelineSwitcher pipelineSwitcher;
@Inject private ProjectBackupLoader backupLoader;
private Parent root;
private boolean headless;
private final UICommandLineHelper commandLineHelper = new UICommandLineHelper();
private CommandLine parsedArgs;
public static void main(String[] args) {
launch(args);
}
@Override
public void init() throws IOException {
parsedArgs = commandLineHelper.parse(getParameters().getRaw());
if (parsedArgs.hasOption(UICommandLineHelper.HEADLESS_OPTION)) {
// If --headless was specified on the command line,
// run in headless mode (only use the core module)
logger.info("Launching GRIP in headless mode");
injector = Guice.createInjector(Modules.override(new GripCoreModule(), new GripFileModule(),
new GripSourcesHardwareModule()).with(new GripNetworkModule()));
injector.injectMembers(this);
headless = true;
} else {
// Otherwise, run with both the core and UI modules, and show the JavaFX stage
logger.info("Launching GRIP in UI mode");
injector = Guice.createInjector(Modules.override(new GripCoreModule(), new GripFileModule(),
new GripSourcesHardwareModule()).with(new GripNetworkModule(), new GripUiModule()));
injector.injectMembers(this);
notifyPreloader(new Preloader.ProgressNotification(0.15));
System.setProperty("prism.lcdtext", "false");
Font.loadFont(this.getClass().getResource("roboto/Roboto-Regular.ttf").openStream(), -1);
Font.loadFont(this.getClass().getResource("roboto/Roboto-Bold.ttf").openStream(), -1);
Font.loadFont(this.getClass().getResource("roboto/Roboto-Italic.ttf").openStream(), -1);
Font.loadFont(this.getClass().getResource("roboto/Roboto-BoldItalic.ttf").openStream(), -1);
notifyPreloader(new Preloader.ProgressNotification(0.3));
}
notifyPreloader(new Preloader.ProgressNotification(0.45));
server.addHandler(pipelineSwitcher);
notifyPreloader(new Preloader.ProgressNotification(0.6));
pipelineRunner.startAsync();
notifyPreloader(new Preloader.ProgressNotification(0.75));
}
@Override
public void start(Stage stage) throws IOException {
// Load UI elements if we're not in headless mode
if (!headless) {
root = FXMLLoader.load(Main.class.getResource("MainWindow.fxml"), null, null,
injector::getInstance);
root.setStyle("-fx-font-size: " + DPIUtility.FONT_SIZE + "px");
notifyPreloader(new Preloader.ProgressNotification(0.9));
project.addIsSaveDirtyConsumer(newValue -> {
if (newValue) {
Platform.runLater(() -> stage.setTitle(MAIN_TITLE + " | Edited"));
} else {
Platform.runLater(() -> stage.setTitle(MAIN_TITLE));
}
});
project.addIsSaveDirtyConsumer(dirty -> {
if (dirty) {
try (Writer fw = Files.newBufferedWriter(
GripFileManager.BACKUP_FILE.toPath(), StandardCharsets.UTF_8)) {
project.saveRaw(fw);
} catch (IOException e) {
logger.log(Level.WARNING, "Could not save backup file", e);
}
}
});
stage.setTitle(MAIN_TITLE);
stage.getIcons().add(new Image("/edu/wpi/grip/ui/icons/grip.png"));
stage.setScene(new Scene(root));
notifyPreloader(new Preloader.ProgressNotification(1.0));
notifyPreloader(new Preloader.StateChangeNotification(
Preloader.StateChangeNotification.Type.BEFORE_START));
stage.show();
}
operations.addOperations();
cvOperations.addOperations();
if (parsedArgs.hasOption(CoreCommandLineHelper.FILE_OPTION)) {
commandLineHelper.loadFile(parsedArgs, project);
} else {
backupLoader.loadBackupOrPreviousSave();
}
commandLineHelper.setServerPort(parsedArgs, settingsProvider, eventBus);
// This will throw an exception if the port specified by the save file or command line
// argument is already taken. Since we have to have the server running to handle remotely
// loading pipelines and uploading images, as well as potential HTTP publishing operations,
// this will cause the program to exit.
try {
server.start();
} catch (GripServerException e) {
logger.log(Level.SEVERE, "The HTTP server could not be started", e);
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "", ButtonType.YES, ButtonType.NO);
alert.setTitle("The HTTP server could not be started");
alert.setHeaderText("The HTTP server could not be started");
alert.setContentText(
"This is normally caused by the network port being used by another process.\n\n"
+ "HTTP sources and operations will not work until GRIP is restarted. "
+ "Continue without HTTP functionality anyway?"
);
alert.showAndWait().filter(ButtonType.NO::equals).ifPresent(bt -> SafeShutdown.exit(1));
}
}
@Override
public void stop() {
SafeShutdown.flagStopping();
}
@Subscribe
@SuppressWarnings("PMD.AvoidCatchingThrowable")
public final void onUnexpectedThrowableEvent(UnexpectedThrowableEvent event) {
event.handleSafely((throwable, message, isFatal) -> {
// Check this so we can avoid entering the the platform wait
// if the program is shutting down.
if (!SafeShutdown.isStopping()) {
// This should still use PlatformImpl
PlatformImpl.runAndWait(() -> {
// WARNING! Do not post any events from within this! It could result in a deadlock!
synchronized (this.dialogLock) {
// Check again because the value could have been changed while waiting for the javafx
// thread to run.
if (!SafeShutdown.isStopping()) {
try {
// Don't create more than one exception dialog at the same time
final ExceptionAlert exceptionAlert = new ExceptionAlert(root, throwable,
message, isFatal, getHostServices());
exceptionAlert.setInitialFocus();
exceptionAlert.showAndWait();
} catch (Throwable e) {
// Well in this case something has gone very, very wrong
// We don't want to create a feedback loop either.
try {
logger.log(Level.SEVERE, "Failed to show exception alert", e);
} finally {
SafeShutdown.exit(1); // Ensure we shut down the application if we get an
// exception
}
}
}
}
});
} else {
logger.log(Level.INFO, "Did not display exception because UI was stopping", throwable);
}
});
}
}