xkcd application ; fatjar packaging
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
@@ -24,6 +25,34 @@
|
||||
<mainClass>com.rdesk.Main</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
|
||||
<configuration>
|
||||
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.rdesk.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.rdesk;
|
||||
|
||||
import com.rdesk.applications.entertainment.comic.Xkcd;
|
||||
import com.rdesk.applications.productivity.Calculator;
|
||||
import com.rdesk.applications.productivity.browsing.TabbedBrowser;
|
||||
import com.rdesk.applications.productivity.code.CodeDesk;
|
||||
@@ -88,6 +89,7 @@ public class Main {
|
||||
launcher.addApp(new TabbedBrowser(), "Productivity");
|
||||
launcher.addApp(new CodeDesk(), "Productivity");
|
||||
launcher.addApp(new Calculator(), "Productivity");
|
||||
launcher.addApp(new Xkcd(), "Entertainment");
|
||||
launcher.addApp(new Exit(), "System");
|
||||
launcher.addApp(new TaskManager(), "System");
|
||||
launcher.addApp(new FileExplorerApp(), "System");
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.rdesk.applications.entertainment.comic;
|
||||
|
||||
public class ComicData {
|
||||
private int num;
|
||||
private String title;
|
||||
private String img;
|
||||
private String alt;
|
||||
|
||||
// Gson requires default constructor
|
||||
public ComicData() {}
|
||||
|
||||
public int getNum() {
|
||||
return num;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getImg() {
|
||||
return img;
|
||||
}
|
||||
|
||||
public String getAlt() {
|
||||
return alt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.rdesk.applications.entertainment.comic;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
public class ComicFetcher {
|
||||
private static final String BASE_URL = "https://xkcd.com/";
|
||||
private static final String INFO_URL = BASE_URL + "info.0.json";
|
||||
private static final String COMIC_URL_TEMPLATE = BASE_URL + "%d/info.0.json";
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final Gson gson;
|
||||
|
||||
public ComicFetcher() {
|
||||
this.httpClient = HttpClient.newHttpClient();
|
||||
this.gson = new Gson();
|
||||
}
|
||||
|
||||
public int fetchLatestComicNumber() throws IOException, InterruptedException {
|
||||
String json = fetchJson(INFO_URL);
|
||||
LatestInfo info = gson.fromJson(json, LatestInfo.class);
|
||||
if (info == null || info.num == 0) {
|
||||
throw new IOException("api response error");
|
||||
}
|
||||
return info.num;
|
||||
}
|
||||
|
||||
public ComicData fetchComicData(int num) throws IOException, InterruptedException {
|
||||
String url = String.format(COMIC_URL_TEMPLATE, num);
|
||||
String json = fetchJson(url);
|
||||
ComicData comic = gson.fromJson(json, ComicData.class);
|
||||
if (comic == null || comic.getImg() == null) {
|
||||
throw new IOException("error , idk bro #" + num);
|
||||
}
|
||||
return comic;
|
||||
}
|
||||
|
||||
private String fetchJson(String urlString) throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(urlString))
|
||||
.GET()
|
||||
.build();
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() != 200) {
|
||||
throw new IOException("HTTP " + response.statusCode() + " for " + urlString);
|
||||
}
|
||||
return response.body();
|
||||
}
|
||||
|
||||
private static class LatestInfo {
|
||||
int num;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.rdesk.applications.entertainment.comic;
|
||||
|
||||
import com.rdesk.kernel.DesktopApp;
|
||||
import com.rdesk.kernel.Kernel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class Xkcd implements DesktopApp {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "xkcd";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "XKCD";
|
||||
}
|
||||
|
||||
@Override
|
||||
public JInternalFrame createWindow(Kernel kernel) {
|
||||
JInternalFrame frame = new JInternalFrame(
|
||||
"XKCD",
|
||||
true, true, true, true
|
||||
);
|
||||
frame.setSize(800, 700);
|
||||
frame.setLayout(new BorderLayout(5, 5));
|
||||
|
||||
JPanel topPanel = new JPanel(new BorderLayout(5, 0));
|
||||
JButton refreshButton = new JButton("Random Comic");
|
||||
JLabel titleLabel = new JLabel(" ", SwingConstants.CENTER);
|
||||
titleLabel.setFont(new Font("SansSerif", Font.BOLD, 14));
|
||||
topPanel.add(refreshButton, BorderLayout.WEST);
|
||||
topPanel.add(titleLabel, BorderLayout.CENTER);
|
||||
frame.add(topPanel, BorderLayout.NORTH);
|
||||
|
||||
ZoomableImagePanel imagePanel = new ZoomableImagePanel();
|
||||
frame.add(imagePanel, BorderLayout.CENTER);
|
||||
|
||||
JTextArea altTextArea = new JTextArea(4, 40);
|
||||
altTextArea.setEditable(false);
|
||||
altTextArea.setLineWrap(true);
|
||||
altTextArea.setWrapStyleWord(true);
|
||||
altTextArea.setFont(new Font("SansSerif", Font.PLAIN, 12));
|
||||
JScrollPane altScrollPane = new JScrollPane(altTextArea);
|
||||
frame.add(altScrollPane, BorderLayout.SOUTH);
|
||||
|
||||
new XkcdController(refreshButton, titleLabel, altTextArea, imagePanel).loadRandomComic();
|
||||
|
||||
frame.setVisible(true);
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.rdesk.applications.entertainment.comic;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class XkcdController {
|
||||
private final JButton refreshButton;
|
||||
private final JLabel titleLabel;
|
||||
private final JTextArea altTextArea;
|
||||
private final ZoomableImagePanel imagePanel;
|
||||
private final ComicFetcher fetcher;
|
||||
|
||||
public XkcdController(JButton refreshButton, JLabel titleLabel,
|
||||
JTextArea altTextArea, ZoomableImagePanel imagePanel) {
|
||||
this.refreshButton = refreshButton;
|
||||
this.titleLabel = titleLabel;
|
||||
this.altTextArea = altTextArea;
|
||||
this.imagePanel = imagePanel;
|
||||
this.fetcher = new ComicFetcher();
|
||||
|
||||
refreshButton.addActionListener(e -> loadRandomComic());
|
||||
}
|
||||
|
||||
public void loadRandomComic() {
|
||||
setLoadingState(true);
|
||||
SwingWorker<ComicData, Void> worker = new SwingWorker<>() {
|
||||
@Override
|
||||
protected ComicData doInBackground() throws Exception {
|
||||
int latest = fetcher.fetchLatestComicNumber();
|
||||
int randomNum = (int) (Math.random() * latest) + 1;
|
||||
return fetcher.fetchComicData(randomNum);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
setLoadingState(false);
|
||||
try {
|
||||
ComicData comic = get();
|
||||
if (comic != null) {
|
||||
displayComic(comic);
|
||||
} else {
|
||||
showError("cant load comic");
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
showError("Error: " + e.getCause().getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
worker.execute();
|
||||
}
|
||||
|
||||
private void displayComic(ComicData comic) {
|
||||
titleLabel.setText(String.format("#%d: %s", comic.getNum(), comic.getTitle()));
|
||||
altTextArea.setText(comic.getAlt());
|
||||
|
||||
SwingWorker<BufferedImage, Void> imageWorker = new SwingWorker<>() {
|
||||
@Override
|
||||
protected BufferedImage doInBackground() throws Exception {
|
||||
URL url = new URL(comic.getImg());
|
||||
return javax.imageio.ImageIO.read(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
BufferedImage img = get();
|
||||
imagePanel.setImage(img);
|
||||
} catch (Exception e) {
|
||||
showError("failed to load image: " + e.getMessage());
|
||||
imagePanel.clearImage();
|
||||
}
|
||||
}
|
||||
};
|
||||
imageWorker.execute();
|
||||
}
|
||||
|
||||
private void setLoadingState(boolean loading) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
refreshButton.setEnabled(!loading);
|
||||
if (loading) {
|
||||
titleLabel.setText("fetching image");
|
||||
altTextArea.setText("");
|
||||
imagePanel.clearImage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showError(String message) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
titleLabel.setText(" ");
|
||||
altTextArea.setText("");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.rdesk.applications.entertainment.comic;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class ZoomableImagePanel extends JPanel {
|
||||
private BufferedImage originalImage;
|
||||
private double zoom = 1.0;
|
||||
private Point dragStart;
|
||||
private Point viewportStart;
|
||||
private final JViewport viewport;
|
||||
|
||||
private static final double ZOOM_STEP = 0.1;
|
||||
private static final double MIN_ZOOM = 0.2;
|
||||
private static final double MAX_ZOOM = 3.0;
|
||||
|
||||
public ZoomableImagePanel() {
|
||||
setLayout(new BorderLayout());
|
||||
JLabel imageLabel = new JLabel("", SwingConstants.CENTER);
|
||||
JScrollPane scrollPane = new JScrollPane(imageLabel);
|
||||
add(scrollPane, BorderLayout.CENTER);
|
||||
this.viewport = scrollPane.getViewport();
|
||||
|
||||
imageLabel.addMouseWheelListener(this::onMouseWheel);
|
||||
|
||||
DragHandler dragHandler = new DragHandler(imageLabel);
|
||||
imageLabel.addMouseListener(dragHandler);
|
||||
imageLabel.addMouseMotionListener(dragHandler);
|
||||
}
|
||||
|
||||
public void setImage(BufferedImage image) {
|
||||
this.originalImage = image;
|
||||
this.zoom = 1.0;
|
||||
updateDisplay();
|
||||
SwingUtilities.invokeLater(() -> viewport.setViewPosition(new Point(0, 0)));
|
||||
}
|
||||
|
||||
public void clearImage() {
|
||||
this.originalImage = null;
|
||||
removeImageFromLabel();
|
||||
}
|
||||
|
||||
private void updateDisplay() {
|
||||
if (originalImage == null) {
|
||||
removeImageFromLabel();
|
||||
return;
|
||||
}
|
||||
JLabel label = getImageLabel();
|
||||
int newWidth = (int) (originalImage.getWidth() * zoom);
|
||||
int newHeight = (int) (originalImage.getHeight() * zoom);
|
||||
Image scaled = originalImage.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);
|
||||
label.setIcon(new ImageIcon(scaled));
|
||||
label.setText("");
|
||||
label.setPreferredSize(new Dimension(newWidth, newHeight));
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
private void onMouseWheel(MouseWheelEvent e) {
|
||||
if (originalImage == null) return;
|
||||
double oldZoom = zoom;
|
||||
double newZoom = zoom + (e.getWheelRotation() < 0 ? ZOOM_STEP : -ZOOM_STEP);
|
||||
newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoom));
|
||||
if (newZoom == zoom) return;
|
||||
|
||||
Point mouseOnScreen = e.getPoint();
|
||||
Point viewPos = viewport.getViewPosition();
|
||||
double xRatio = (viewPos.x + mouseOnScreen.x) / (double) viewport.getExtentSize().width;
|
||||
double yRatio = (viewPos.y + mouseOnScreen.y) / (double) viewport.getExtentSize().height;
|
||||
|
||||
zoom = newZoom;
|
||||
updateDisplay();
|
||||
|
||||
Dimension contentSize = getImageLabel().getPreferredSize();
|
||||
Dimension viewSize = viewport.getExtentSize();
|
||||
int newX = (int) (contentSize.width * xRatio - viewSize.width / 2.0);
|
||||
int newY = (int) (contentSize.height * yRatio - viewSize.height / 2.0);
|
||||
newX = Math.max(0, Math.min(newX, contentSize.width - viewSize.width));
|
||||
newY = Math.max(0, Math.min(newY, contentSize.height - viewSize.height));
|
||||
viewport.setViewPosition(new Point(newX, newY));
|
||||
}
|
||||
|
||||
private JLabel getImageLabel() {
|
||||
return (JLabel) ((JScrollPane) getComponent(0)).getViewport().getView();
|
||||
}
|
||||
|
||||
private void removeImageFromLabel() {
|
||||
JLabel label = getImageLabel();
|
||||
label.setIcon(null);
|
||||
label.setText("Loading");
|
||||
label.setPreferredSize(null);
|
||||
revalidate();
|
||||
}
|
||||
|
||||
private class DragHandler extends MouseAdapter {
|
||||
private final JLabel label;
|
||||
|
||||
DragHandler(JLabel label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (!SwingUtilities.isLeftMouseButton(e)) return;
|
||||
dragStart = e.getPoint();
|
||||
viewportStart = viewport.getViewPosition();
|
||||
label.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (!SwingUtilities.isLeftMouseButton(e)) return;
|
||||
dragStart = null;
|
||||
viewportStart = null;
|
||||
label.setCursor(Cursor.getDefaultCursor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
if (dragStart == null || viewportStart == null) return;
|
||||
int deltaX = dragStart.x - e.getX();
|
||||
int deltaY = dragStart.y - e.getY();
|
||||
Point newPos = new Point(viewportStart.x + deltaX, viewportStart.y + deltaY);
|
||||
|
||||
Dimension contentSize = label.getPreferredSize();
|
||||
Dimension viewSize = viewport.getExtentSize();
|
||||
newPos.x = Math.max(0, Math.min(newPos.x, contentSize.width - viewSize.width));
|
||||
newPos.y = Math.max(0, Math.min(newPos.y, contentSize.height - viewSize.height));
|
||||
viewport.setViewPosition(newPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user