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
49 changes: 0 additions & 49 deletions CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
Original file line number Diff line number Diff line change
Expand Up @@ -5352,55 +5352,6 @@ public String editorPeerQuery(PeerComponent peer, String name, String arg) {
return null;
}

/// Returns true when this platform can bind a `com.codename1.ui.TextInputClient` to a low level text
/// input source (soft keyboard / IME / hardware keyboard) so a component can capture raw text input
/// while rendering the document itself. When false the pure Codename One editors fall back to their
/// `BrowserComponent` backend. The default returns false.
public boolean isTextInputSupported() {
return false;
}

/// Binds a `com.codename1.ui.TextInputClient` to the platform text input source and shows the soft
/// keyboard on touch devices. The platform then routes committed text, IME composition, deletions and
/// key commands into the client, and reads back the client's editing state and caret rectangle. The
/// returned handle identifies this binding for `#updateTextInputState` and `#stopTextInput`. The
/// default returns null (unsupported).
///
/// #### Parameters
///
/// - `client`: the input client to bind
///
/// - `config`: the desired keyboard type and input behavior
///
/// #### Returns
///
/// an opaque handle for this binding, or null when unsupported
public Object startTextInput(com.codename1.ui.TextInputClient client, com.codename1.ui.TextInputConfig config) {
return null;
}

/// Pushes the client's authoritative editing state (surrounding text, selection, composition and
/// caret rectangle) down to the platform input source so autocorrect, prediction and the IME
/// candidate window stay in sync after a Codename One side edit (programmatic change, undo, reflow).
/// No-op by default.
///
/// #### Parameters
///
/// - `handle`: the handle returned by `#startTextInput`
///
/// - `state`: the current editing state
public void updateTextInputState(Object handle, com.codename1.ui.TextInputState state) {
}

/// Unbinds a text input client bound with `#startTextInput` and hides the soft keyboard on touch
/// devices. No-op by default.
///
/// #### Parameters
///
/// - `handle`: the handle returned by `#startTextInput`
public void stopTextInput(Object handle) {
}

/// Posts a message to the window in a BrowserComponent. This is intended to be an abstraction of the Javascript postMessage() API.
///
/// This is only overridden by the Javascript port to provide proper CORS handling. Other ports use the implementation
Expand Down
140 changes: 38 additions & 102 deletions CodenameOne/src/com/codename1/ui/AbstractEditorComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
*/
package com.codename1.ui;

import com.codename1.ui.editor.EditorHost;
import com.codename1.ui.editor.PureEditor;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.layouts.BorderLayout;
Expand All @@ -42,34 +40,27 @@
/// and the inbound `#onEditorEvent(String, String)` dispatch. Two interchangeable backends honor
/// that channel:
///
/// 1. The pure Codename One text engine (`com.codename1.ui.editor`) which renders the document itself
/// with `Graphics`/`Font` and binds to the platform text input source (soft keyboard, hardware
/// keyboard and IME). This is the default where the port exposes low-level text input.
/// 2. A `BrowserComponent` fallback on ports that don't expose low-level text input. Its
/// `contenteditable` surface remains editable through the platform web view.
/// 3. An optional native backend supplied by the platform port (see
/// 1. A 100% cross platform fallback backed by `BrowserComponent` (a `contenteditable` surface for
/// rich text, a syntax highlighting surface for code). This works on every platform that supports
/// the native web widget and gets virtual keyboard handling on phones/tablets and physical keyboard
/// handling on desktop for free.
/// 2. An optional native backend supplied by the platform port (see
/// `com.codename1.impl.CodenameOneImplementation#createNativeEditorPeer(AbstractEditorComponent, String)`).
/// When a port returns a non-null native peer the editor drives it through
/// `editorPeerCommand` / `editorPeerQuery` instead of the pure engine, allowing a platform to provide a
/// genuinely native experience.
/// `editorPeerCommand` / `editorPeerQuery` instead of the browser, allowing a platform to provide a
/// genuinely native experience that can exceed an HTML based app.
///
/// All backends are addressed with the same vocabulary so concrete editors never need to know which
/// Both backends are addressed with the same vocabulary so concrete editors never need to know which
/// one is active.
///
/// @author Shai Almog
public abstract class AbstractEditorComponent extends Container implements EditorHost {
/// Prefix used for messages sent from the browser fallback to Codename One.
public abstract class AbstractEditorComponent extends Container {
/// Prefix used for all messages that travel from the web editor back to Codename One over the
/// `BrowserComponent` message bridge.
static final String MESSAGE_PREFIX = "cn1ed:";

/// Backend identifier: the pure Codename One text engine.
public static final int BACKEND_PURE = 0;

/// Backend identifier for the `BrowserComponent` compatibility fallback.
public static final int BACKEND_BROWSER = 1;

private BrowserComponent browser;
private PeerComponent nativePeer;
private PureEditor pureEditor;
private boolean nativeMode;
private boolean ready;
private boolean editable = true;
Expand Down Expand Up @@ -110,19 +101,8 @@ private void initBackend() {
return;
}
nativeMode = false;
if (isTextInputSupported()) {
pureEditor = createPureEditor();
removeComponent(placeholder);
addComponent(BorderLayout.CENTER, pureEditor.getView());
markReady();
revalidateLater();
return;
}
initBrowserBackend();
}

private void initBrowserBackend() {
browser = new BrowserComponent();
// keep the editor chrome supplied by the surrounding form, the editing surface is transparent
browser.setProperty("BackgroundColor", 0xffffff);
browser.addWebEventListener(BrowserComponent.onMessage, new ActionListener() {
@Override
Expand All @@ -133,6 +113,7 @@ public void actionPerformed(ActionEvent evt) {
browser.addWebEventListener(BrowserComponent.onLoad, new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
// the page defines window.cn1editor synchronously so it is ready once the page loaded
markReady();
}
});
Expand All @@ -157,8 +138,16 @@ private void handleBrowserMessage(String msg) {
}
String body = msg.substring(MESSAGE_PREFIX.length());
int colon = body.indexOf(':');
onEditorEvent(colon < 0 ? body : body.substring(0, colon),
colon < 0 ? null : body.substring(colon + 1));
String type;
String value;
if (colon < 0) {
type = body;
value = null;
} else {
type = body.substring(0, colon);
value = body.substring(colon + 1);
}
onEditorEvent(type, value);
}

private void markReady() {
Expand All @@ -175,41 +164,25 @@ private void markReady() {
readyListeners.fireActionEvent(new ActionEvent(this));
}

/// Deprecated compatibility hook. Backend selection is automatic: the pure engine is used when
/// low-level text input is available and the browser fallback is used otherwise.
///
/// #### Parameters
///
/// - `backend`: ignored
@Deprecated
public static void setDefaultBackend(int backend) {
}

/// Returns the preferred pure backend. Actual selection may fall back to the browser at runtime.
@Deprecated
public static int getDefaultBackend() {
return BACKEND_PURE;
}

/// Creates the pure Codename One backend for this editor. Subclasses override to supply a code or
/// rich text feature layer.
PureEditor createPureEditor() {
return new PureEditor(this, getEditorType());
}

/// Returns the editor type identifier passed to the native peer factory, e.g.
/// `"richtext"` or `"code"`.
abstract String getEditorType();

/// Returns the self-contained page used by the browser fallback.
/// Returns the bootstrap HTML page used by the `BrowserComponent` fallback backend. The page must
/// define a global `window.cn1editor` object exposing `cmd(name, arg)` and `query(name, arg)`
/// functions and post change/ready events back through `window.cn1PostMessage`.
abstract String createEditorHtml();

/// Base URL for relative resources in the browser fallback page.
/// Base URL used when loading the editor page, allowing relative resources (such as a bundled code
/// editor library) to resolve. The default returns a synthetic origin.
String getEditorBaseURL() {
return "https://cn1editor.codenameone.com/";
}

/// Optional app-hierarchy URL for a custom browser editor engine.
/// When non-null the browser fallback loads this app-hierarchy URL (via
/// `BrowserComponent#setURLHierarchy(String)`) as a custom editor engine instead of the built-in
/// `#createEditorHtml()` page. Subclasses override to allow an application to supply a richer editor
/// backend that speaks the same `window.cn1editor` bridge.
String getEngineURL() {
return null;
}
Expand Down Expand Up @@ -242,7 +215,6 @@ void onEditorEvent(String type, String value) {
/// - `type`: the event type
///
/// - `value`: optional payload, may be null
@Override
public void fireEditorEvent(final String type, final String value) {
if (CN.isEdt()) {
onEditorEvent(type, value);
Expand Down Expand Up @@ -276,8 +248,6 @@ public void run() {
}
if (nativeMode) {
Display.impl.editorPeerCommand(nativePeer, name, arg);
} else if (pureEditor != null) {
pureEditor.cmd(name, arg);
} else {
browser.execute("window.cn1editor.cmd(${0}, ${1})", new Object[]{name, arg == null ? "" : arg});
}
Expand Down Expand Up @@ -307,12 +277,9 @@ public void run() {
callback.onSucess(Display.impl.editorPeerQuery(nativePeer, name, arg));
return;
}
if (pureEditor != null) {
callback.onSucess(pureEditor.query(name, arg));
return;
}
browser.execute("callback.onSuccess(window.cn1editor.query(${0}, ${1}))",
new Object[]{name, arg == null ? "" : arg}, new JSRefStringCallback(callback));
new Object[]{name, arg == null ? "" : arg},
new JSRefStringCallback(callback));
}

private static final class JSRefStringCallback implements SuccessCallback<BrowserComponent.JSRef> {
Expand Down Expand Up @@ -347,13 +314,14 @@ public boolean isEditorReady() {
return ready;
}

/// True when a platform supplied native editor backend is in use, false for the pure and browser
/// backends.
/// True when a platform supplied native editor backend is in use, false when the cross platform
/// `BrowserComponent` fallback is active.
public boolean isNativeEditor() {
return nativeMode;
}

/// Returns the browser fallback, or null when the pure or native backend is active.
/// Returns the underlying `BrowserComponent` used by the fallback backend, or null when a native
/// backend is active. Exposed for advanced customization; most apps never need this.
public BrowserComponent getInternalBrowser() {
return browser;
}
Expand Down Expand Up @@ -429,36 +397,4 @@ public void focusEditor() {
public void blurEditor() {
command("blur", null);
}

// ---- EditorHost (pure backend bridge to the platform text input source) ----

/// {@inheritDoc}
@Override
public boolean isTextInputSupported() {
return Display.impl.isTextInputSupported();
}

/// {@inheritDoc}
@Override
public Object startTextInput(TextInputClient client, TextInputConfig config) {
return Display.impl.startTextInput(client, config);
}

/// {@inheritDoc}
@Override
public void updateTextInputState(Object handle, TextInputState state) {
Display.impl.updateTextInputState(handle, state);
}

/// {@inheritDoc}
@Override
public void stopTextInput(Object handle) {
Display.impl.stopTextInput(handle);
}

/// {@inheritDoc}
@Override
public void editorChanged() {
onEditorEvent("change", null);
}
}
2 changes: 1 addition & 1 deletion CodenameOne/src/com/codename1/ui/BrowserComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -1683,7 +1683,7 @@ public void run() {
/// When a Dialog is shown, the parent Form is deinitialized, which normally causes
/// BrowserComponent's iframe to be removed from the DOM. When the Dialog is dismissed,
/// the Form is re-shown but the iframe must be recreated from scratch, losing all
/// JavaScript state (editor content, event listeners, etc.).
/// JavaScript state (Monaco editor content, event listeners, etc.).
///
/// Setting this to `Boolean.FALSE` preserves the iframe in the DOM across
/// deinitialization cycles, which is essential for:
Expand Down
36 changes: 19 additions & 17 deletions CodenameOne/src/com/codename1/ui/CodeEditor.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
*/
package com.codename1.ui;

import com.codename1.ui.editor.CodePureEditor;
import com.codename1.ui.editor.PureEditor;
import com.codename1.util.SuccessCallback;

import java.util.List;
Expand All @@ -36,10 +34,12 @@
/// desktops (with a physical keyboard). Code completion is driven by a `CodeCompletionProvider`, which
/// can resolve proposals locally or from a remote language server.
///
/// Like `RichTextArea`, ports with low-level text input use the pure Codename One text engine
/// (`com.codename1.ui.editor`) with a built-in incremental syntax highlighter. Ports without that input
/// contract use an editable `BrowserComponent` fallback, and a port can transparently supply a native
/// code editor instead.
/// Like `RichTextArea`, the editor uses a 100% cross platform `BrowserComponent` backend by default and
/// can be transparently upgraded to a native code editor by a platform port. The built-in editing
/// surface is fully self contained and offline. When the optional bundled CodeMirror library is present
/// (the Codename One build server adds it automatically when, and only when, an app actually uses
/// `CodeEditor`) the editor upgrades to CodeMirror for richer language support; otherwise it uses a
/// lightweight built-in highlighter.
///
/// #### Example
///
Expand Down Expand Up @@ -88,11 +88,6 @@ String getEditorType() {
return "code";
}

@Override
PureEditor createPureEditor() {
return new CodePureEditor(this, getEditorType());
}

/// Replaces the entire editor content.
///
/// #### Parameters
Expand Down Expand Up @@ -259,23 +254,31 @@ private static String diagnosticsJson(List<CodeDiagnostic> diagnostics) {
return sb.toString();
}

/// Points the browser fallback at a custom editor page in the app hierarchy. Ports with low-level
/// text input continue to use the pure editor; this URL is used only where the browser fallback is
/// required.
/// Points the editor at a custom engine page bundled in the app's HTML hierarchy (loaded with
/// `BrowserComponent#setURLHierarchy(String)`) instead of the self contained built-in engine. The
/// custom page must implement the same `window.cn1editor` command/query bridge and post the same
/// `cn1ed:` events, which lets an application back `CodeEditor` with a richer editor (e.g. CodeMirror
/// or Monaco) when maximum polish is required while keeping the exact same Java API. Must be set
/// before the editor is shown.
///
/// #### Parameters
///
/// - `url`: an app-hierarchy URL, or null for the built-in page
/// - `url`: an app-hierarchy URL such as `"/my-editor/index.html"`, or null to use the built-in engine
public void setEngineURL(String url) {
this.engineUrl = url;
}

/// Returns the custom browser-engine URL, or null for the built-in page.
/// Returns the custom engine URL set with `#setEngineURL(String)`, or null when the built-in engine
/// is used.
@Override
public String getEngineURL() {
return engineUrl;
}

String getEngineURLInternal() {
return engineUrl;
}

/// Sets the provider that supplies code completion proposals. Passing null disables completion.
///
/// #### Parameters
Expand Down Expand Up @@ -397,5 +400,4 @@ private static String jsonEscape(String s) {
String createEditorHtml() {
return CodeEditorHtml.PAGE;
}

}
Loading
Loading