Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,34 +1,62 @@
package com.codename1.maven;

import org.apache.commons.io.IOUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

import static com.codename1.maven.PathUtil.path;

/**
* Generates The SEWrapper, or the main class for a JavaSE desktop app. This is used by the javase module
* of the cn1app-archetype.
* Generates the desktop SE wrapper (the {@code <MainName>Stub} class containing
* the {@code main(String[])} that boots the Swing JFrame and instantiates the
* Codename One {@code Lifecycle} subclass). This Mojo is the JavaSE analogue of
* the stub generation that {@link com.codename1.builders.AndroidGradleBuilder}
* and {@link com.codename1.builders.IPhoneBuilder} do for their respective
* targets - taking values from {@code codenameone_settings.properties} (and
* {@code codename1.arg.desktop.*} build hints) and emitting the platform's
* entry-point class so the app-level project does not have to ship a
* hand-maintained stub.
*
* <p>If the developer drops {@code javase/src/desktop/java/<pkg>/<MainName>Stub.java}
* by hand it wins: this Mojo will skip generation, and the existing source-root
* registration below picks it up. That makes the static file a complete
* override - useful when an app needs custom Swing setup that's outside what
* the build-hint surface covers.
*/
@Mojo(name="generate-desktop-app-wrapper")
public class GenerateDesktopAppWrapperMojo extends AbstractCN1Mojo {
private static final String GENERATED_SOURCES_DIR = "cn1-desktop";

@Override
protected void executeImpl() throws MojoExecutionException, MojoFailureException {
generateIcons();
generateStub();
registerCustomStubSourceRoot();
}

private void generateIcons() throws MojoExecutionException {
String iconPath = properties.getProperty("codename1.icon");
if (iconPath == null) {
getLog().warn("codename1.icon not set in codenameone_settings.properties. Skipping desktop app icon generation.");
return;
}
File iconFile = new File(iconPath);
if (!iconFile.isAbsolute()) {
iconFile = new File(getCN1ProjectDir(), iconFile.getPath());
}
if (!iconFile.exists()) {
getLog().warn("Icon file "+iconFile+" not found. Skipping desktop appp icon generation.");
getLog().warn("Icon file "+iconFile+" not found. Skipping desktop app icon generation.");
return;
}
File outputDir = new File(project.getBuild().getOutputDirectory());
Expand All @@ -46,17 +74,127 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException
} catch (IOException ex) {
throw new MojoExecutionException("Failed to generate icons", ex);
}
}

// Now get the wrapper
private void registerCustomStubSourceRoot() {
File wrapperSources = new File(project.getBasedir(), path("src", "desktop", "java"));
if (wrapperSources.exists()) {
project.addCompileSourceRoot(wrapperSources.getAbsolutePath());
}
}

private void generateStub() throws MojoExecutionException {
String packageName = properties.getProperty("codename1.packageName");
String mainName = properties.getProperty("codename1.mainName");
if (packageName == null || mainName == null) {
getLog().warn("codename1.packageName or codename1.mainName not set in codenameone_settings.properties. Skipping desktop stub generation.");
return;
}

// If the developer hand-rolls a stub under src/desktop/java/<pkg>/<MainName>Stub.java
// treat it as a full override and skip generation - the source root that
// registerCustomStubSourceRoot adds will pick it up.
String packagePath = packageName.replace('.', File.separatorChar);
File customStub = new File(project.getBasedir(),
path("src", "desktop", "java", packagePath, mainName + "Stub.java"));
if (customStub.exists()) {
getLog().info("Custom desktop stub found at " + customStub.getAbsolutePath() + " - skipping generation.");
return;
}

File generatedRoot = new File(project.getBuild().getDirectory(),
path("generated-sources", GENERATED_SOURCES_DIR));
File generatedPkgDir = new File(generatedRoot, packagePath);
generatedPkgDir.mkdirs();
File generatedStub = new File(generatedPkgDir, mainName + "Stub.java");

String template;
try (InputStream in = getClass().getResourceAsStream("desktop-app-stub-template.java")) {
if (in == null) {
throw new MojoExecutionException("Could not load desktop-app-stub-template.java resource from plugin jar.");
}
template = IOUtils.toString(in, StandardCharsets.UTF_8);
} catch (IOException ex) {
throw new MojoExecutionException("Failed to read desktop stub template", ex);
}

String stubSource = applyTemplate(template, packageName, mainName);

try {
Files.write(generatedStub.toPath(), stubSource.getBytes(StandardCharsets.UTF_8));
getLog().info("Generated desktop stub: " + generatedStub.getAbsolutePath());
} catch (IOException ex) {
throw new MojoExecutionException("Failed to write generated desktop stub " + generatedStub, ex);
}

project.addCompileSourceRoot(generatedRoot.getAbsolutePath());
}

private String applyTemplate(String template, String packageName, String mainName) {
// Default APP_TITLE to displayName; fall back to mainName so we never inject an empty literal.
String displayName = properties.getProperty("codename1.displayName", mainName);
String appTitle = arg("desktop.title", displayName);
String appVersion = properties.getProperty("codename1.version", "1.0");

String width = arg("desktop.width", "800");
String height = arg("desktop.height", "600");
String resizable = arg("desktop.resizable", "true");
String fullscreen = arg("desktop.fullscreen", "false");
String adaptToRetina = arg("desktop.adaptToRetina", "true");

String result = template;
result = result.replace("__PACKAGE__", packageName);
result = result.replace("__MAIN_NAME__", mainName);
result = result.replace("__APP_TITLE__", escapeJavaStringLiteral(appTitle));
result = result.replace("__APP_VERSION__", escapeJavaStringLiteral(appVersion));
result = result.replace("__APP_WIDTH__", sanitizeInt(width, "800"));
result = result.replace("__APP_HEIGHT__", sanitizeInt(height, "600"));
result = result.replace("__APP_RESIZEABLE__", sanitizeBoolean(resizable, true));
result = result.replace("__APP_FULLSCREEN__", sanitizeBoolean(fullscreen, false));
result = result.replace("__APP_ADAPT_TO_RETINA__", sanitizeBoolean(adaptToRetina, true));
return result;
}

private String arg(String name, String defaultValue) {
String v = properties.getProperty("codename1.arg." + name);
return (v == null || v.isEmpty()) ? defaultValue : v;
}

private String sanitizeInt(String value, String fallback) {
try {
return String.valueOf(Integer.parseInt(value.trim()));
} catch (NumberFormatException ex) {
getLog().warn("Invalid integer in desktop build hint: '" + value + "'. Using " + fallback + ".");
return fallback;
}
}

private String sanitizeBoolean(String value, boolean fallback) {
if (value == null) return String.valueOf(fallback);
String trimmed = value.trim().toLowerCase();
if ("true".equals(trimmed) || "false".equals(trimmed)) {
return trimmed;
}
getLog().warn("Invalid boolean in desktop build hint: '" + value + "'. Using " + fallback + ".");
return String.valueOf(fallback);
}

private String escapeJavaStringLiteral(String s) {
if (s == null) return "";
StringBuilder out = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '\\': out.append("\\\\"); break;
case '"': out.append("\\\""); break;
case '\n': out.append("\\n"); break;
case '\r': out.append("\\r"); break;
case '\t': out.append("\\t"); break;
default:
out.append(c);
}
}
return out.toString();
}

private void createIconFile(File f, BufferedImage icon, int w, int h) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package __PACKAGE__;

import com.codename1.impl.javase.JavaSEPort;
import com.codename1.ui.Display;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.util.Arrays;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

/**
* Generated desktop wrapper for a Codename One app. Generated at build time by
* the codenameone-maven-plugin's generate-desktop-app-wrapper goal from values
* in codenameone_settings.properties and the codename1.arg.desktop.* build
* hints. Drop a hand-written __MAIN_NAME__Stub.java into javase/src/desktop/java
* to override generation completely.
*/
public class __MAIN_NAME__Stub implements Runnable, WindowListener {
private static final String APP_TITLE = "__APP_TITLE__";
private static final String APP_NAME = "__MAIN_NAME__";
private static final String APP_VERSION = "__APP_VERSION__";
private static final int APP_WIDTH = __APP_WIDTH__;
private static final int APP_HEIGHT = __APP_HEIGHT__;
private static final boolean APP_ADAPT_TO_RETINA = __APP_ADAPT_TO_RETINA__;
private static final boolean APP_RESIZEABLE = __APP_RESIZEABLE__;
private static final boolean APP_FULLSCREEN = __APP_FULLSCREEN__;
public static final String BUILD_KEY = "";
public static final String PACKAGE_NAME = "";
public static final String BUILT_BY_USER = "";
private static final boolean isWindows;
static {
isWindows = File.separatorChar == '\\';
}

private static final String[] fontFaces = null;

private static JFrame frm;
private __MAIN_NAME__ mainApp;

public static void main(String[] args) {
try {
Class.forName("org.cef.CefApp");
System.setProperty("cn1.javase.implementation", "cef");
} catch (Throwable ex){}

JavaSEPort.setNativeTheme("/NativeTheme.res");
JavaSEPort.blockMonitors();
JavaSEPort.setAppHomeDir("." + APP_NAME);
JavaSEPort.setExposeFilesystem(true);
JavaSEPort.setTablet(true);
JavaSEPort.setUseNativeInput(true);
JavaSEPort.setShowEDTViolationStacks(false);
JavaSEPort.setShowEDTWarnings(false);
JavaSEPort.setFullScreen(APP_FULLSCREEN);

if(fontFaces != null) {
JavaSEPort.setFontFaces(fontFaces[0], fontFaces[1], fontFaces[2]);
} else {
if(isWindows) {
JavaSEPort.setFontFaces("ArialUnicodeMS", "SansSerif", "Monospaced");
} else {
JavaSEPort.setFontFaces("Arial", "SansSerif", "Monospaced");
}
}

frm = new JFrame(APP_TITLE);
Toolkit tk = Toolkit.getDefaultToolkit();
JavaSEPort.setDefaultPixelMilliRatio(tk.getScreenResolution() / 25.4 * JavaSEPort.getRetinaScale());
Display.init(frm.getContentPane());
Display.getInstance().setProperty("build_key", BUILD_KEY);
Display.getInstance().setProperty("package_name", PACKAGE_NAME);
Display.getInstance().setProperty("built_by_user", BUILT_BY_USER);
Display.getInstance().setProperty("AppName", APP_NAME);
Display.getInstance().setProperty("AppVersion", APP_VERSION);
Display.getInstance().setProperty("Platform", System.getProperty("os.name"));
Display.getInstance().setProperty("OSVer", System.getProperty("os.version"));

SwingUtilities.invokeLater(new __MAIN_NAME__Stub());
}

public void run() {
frm.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frm.addWindowListener(this);
ImageIcon ic16 = new ImageIcon(getClass().getResource("/applicationIconImage_16x16.png"));
ImageIcon ic20 = new ImageIcon(getClass().getResource("/applicationIconImage_16x16.png"));
ImageIcon ic32 = new ImageIcon(getClass().getResource("/applicationIconImage_16x16.png"));
ImageIcon ic40 = new ImageIcon(getClass().getResource("/applicationIconImage_16x16.png"));
ImageIcon ic64 = new ImageIcon(getClass().getResource("/applicationIconImage_16x16.png"));
frm.setIconImages(Arrays.asList(ic16.getImage(), ic20.getImage(), ic32.getImage(), ic40.getImage(), ic64.getImage()));
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
if(APP_FULLSCREEN && gd.isFullScreenSupported()) {
frm.setResizable(false);
frm.setUndecorated(true);
gd.setFullScreenWindow(frm);
} else {
frm.setLocationByPlatform(true);
frm.setResizable(APP_RESIZEABLE);
int w = APP_WIDTH;
int h = APP_HEIGHT;

frm.getContentPane().setPreferredSize(new java.awt.Dimension(w, h));
frm.getContentPane().setMinimumSize(new java.awt.Dimension(w, h));
frm.getContentPane().setMaximumSize(new java.awt.Dimension(w, h));

framePrepare(frm);
}
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
if(Display.getInstance().isEdt()) {
mainApp = new __MAIN_NAME__();
mainApp.init(this);
mainApp.start();
SwingUtilities.invokeLater(this);
} else {
frameShow(frm);
}
}
});
}

private void framePrepare(JFrame frm) {
frm.pack();
}

private void frameShow(JFrame frm) {
frm.setVisible(true);
}

@Override
public void windowOpened(WindowEvent e) {
}

@Override
public void windowClosing(WindowEvent e) {
Display.getInstance().callSerially(new Runnable() {
@Override
public void run() {
mainApp.stop();
mainApp.destroy();
Display.getInstance().exitApplication();
}
});
}

@Override
public void windowClosed(WindowEvent e) {
}

@Override
public void windowIconified(WindowEvent e) {
}

@Override
public void windowDeiconified(WindowEvent e) {
}

@Override
public void windowActivated(WindowEvent e) {
}

@Override
public void windowDeactivated(WindowEvent e) {
if(APP_FULLSCREEN) {
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
frm.setExtendedState(JFrame.MAXIMIZED_BOTH);
if(gd.isFullScreenSupported()) {
frm.setResizable(false);
frm.setUndecorated(true);
gd.setFullScreenWindow(frm);
}
}
}
}
Loading