Skip to content

Commit

Permalink
#537 documentation and a few minor updates.
Browse files Browse the repository at this point in the history
  • Loading branch information
davetcc committed Nov 10, 2024
1 parent 4678c15 commit b28c841
Show file tree
Hide file tree
Showing 13 changed files with 121 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
import static com.thecoderscorner.embedcontrol.core.controlmgr.RedrawingMode.SHOW_NAME_VALUE;
import static com.thecoderscorner.embedcontrol.customization.MenuFormItem.FONT_100_PERCENT;

/**
* When automatic menu layout is used, the layout is described in terms of font, color, position and justification using
* this class. It can either be auto-generated on the fly, or selected by a user and serialized by the layout persister
* for later reloading. This is essentially a value class.
*
*/
/// This class describes how a menu item should be rendered onto the display. It contains the most important drawing
/// settings along with grid positioning data. It also allows for conditional colouring and custom drawing.
///
/// For automatic menu layout cases, the layout is described in terms of font, color, position and justification using
/// this class based on some standard defaults.
/// @see CustomDrawingConfiguration
/// @see ConditionalColoring
public class ComponentSettings {
public static final ComponentSettings NO_COMPONENT = new ComponentSettings(new NullConditionalColoring(),
FONT_100_PERCENT, PortableAlignment.LEFT, new ComponentPositioning(0, 0), SHOW_NAME_VALUE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
import static com.thecoderscorner.embedcontrol.customization.customdraw.CustomDrawingConfiguration.NO_CUSTOM_DRAWING;
import static com.thecoderscorner.menu.domain.util.MenuItemHelper.asSubMenu;

/// MenuGridComponent is the auto UI, it is responsible for taking part of a menu tree and rendering it onto the display.
/// It is used by both the Local Embedded UI, the Preview function and also all forms of Embed Control Fx. It is provided
/// with a starting point and if the render is recursive, and then renders either all items in the current menu, or
/// everything from that point down.
///
/// It is an abstract class, in that the absolute methods by with the controls are put into the grid are implemented
/// elsewhere.
/// @param <T> The type of UI component, normally Node
public abstract class MenuGridComponent<T> {
private final MenuItemStore menuItemStore;
private final JfxNavigationManager navMgr;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package com.thecoderscorner.embedcontrol.core.controlmgr;

import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;

import java.io.IOException;

/**
* Represents a panel that can be displayed onto a UI, it has a name, a UI representation and the possibility to close
* or remove it.
* @param <T> the UI panel type
* or remove it. In all cases they will be in a card like layout where panels are pushed onto the layout, and only
* one can be shown at once, either in a stack, or the panels may be in a selectable list.
* @param <T> the UI panel type, usually Node
*/
public interface PanelPresentable<T> {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.ERROR;

/// This provides a somewhat complete implementation of remote that simulates a real connection. It allows you to
/// test EmbedControl without needing a full connection to a device. It take a copied menu tree (usually from designer)
/// and bootstraps it like a real device would.
public class SimulatedRemoteConnection implements RemoteConnector {
private static final UUID uuid = UUID.fromString("D5F0C35F-AF43-4CB6-BA79-512DF536B558");
private final System.Logger logger = System.getLogger(SimulatedRemoteConnection.class.getSimpleName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/// Within TcMenu framework is a very basic table mapping system. It is extremely lightweight and performance is not
/// considered critical, its main purpose is for reading configuration and other settings.
/// This describes a table mapping and should be applied to a value class.
@Retention(RetentionPolicy.RUNTIME)
public @interface TableMapping {
String tableName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,28 @@
import java.util.Optional;
import java.util.stream.Collectors;

/**
* This is a utility mapping class specifically for TCC applications, it has some raw SQL functions and the smallest
* possible set of ORM functionality for our own purposes. Note that it is not intended as a general purpose ORM and
* is just enough of an ORM for our purposes. You are welcome at your own risk to use this more widely but such use
* is not recommended
*/
/// This is a utility mapping class for TCC applications, it has some raw SQL functions and the smallest
/// possible set of ORM functionality for our own purposes. Note that it is not intended as a general purpose ORM and
/// is just enough of an ORM for our purposes. You are welcome to use within TcMenu applications for the most basic of
/// activities, but it is not intended for wider distribution as it is somewhat incomplete.
///
/// The ORM works by a class being annotated with `TableMapping` on the class, and `FieldMapping` on fields that should
/// be loaded/saved to the database. For example:
///
/// ```
/// @TableMapping(tableName = "MY_TABLE_NAME", uniqueKeyField = "LOCAL_ID")
/// public class MyPersistedType {
/// // LocalID is the primary key, it must be integer.
/// @FieldMapping(fieldName = "LOCAL_ID", primaryKey = true, fieldType = FieldType.INTEGER)
/// private int localId;
/// // Mapping a varchar field,
/// @FieldMapping(fieldName = "MY_NAME", fieldType = FieldType.VARCHAR)
/// private String myName;
/// }
/// ```
///
/// @see FieldMapping
/// @see TableMapping
public class TccDatabaseUtilities {
private final System.Logger logger = System.getLogger(TccDatabaseUtilities.class.getSimpleName());
private final String newLine = System.getProperty("line.separator");
Expand All @@ -25,10 +41,17 @@ public TccDatabaseUtilities(Connection c) throws SQLException {
connection = c;
}

/// close out the database connection
public void close() throws Exception {
if(connection != null) connection.close();
}

/// query for records and convert them into the database type provided, it must be annotated with @TableMapping
/// and any @FieldMapping entries will be populated.
/// @param databaseType the class with the mappings
/// @param whereClause the where clause of the statement
/// @param params the parameters for the query
/// @return a list of elements of `databaseType`
public <T> List<T> queryRecords(Class<T> databaseType, String whereClause, Object... params) throws DataException {
var tableInfo = databaseType.getAnnotation(TableMapping.class);

Expand All @@ -50,6 +73,10 @@ public <T> List<T> queryRecords(Class<T> databaseType, String whereClause, Objec
}
}

/// Converts a result set row into the type provided if it is possible to do so. IE the class is annotated with
/// `@TableMapping` and the fields are annotated with `@FieldMapping`.
/// @param rs the result set
/// @param databaseType the database value type that is correctly annotated
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> T fromResultSet(ResultSet rs, Class<T> databaseType) throws Exception {
var item = databaseType.getConstructor().newInstance();
Expand All @@ -75,6 +102,7 @@ public <T> T fromResultSet(ResultSet rs, Class<T> databaseType) throws Exception
return item;
}

/// Query by the primary key, this must only return one result, otherwise acts like `queryRecords`
public <T> Optional<T> queryPrimaryKey(Class<T> databaseType, Object primaryKey) throws DataException {
var tableInfo = databaseType.getAnnotation(TableMapping.class);

Expand All @@ -91,6 +119,11 @@ public <T> Optional<T> queryPrimaryKey(Class<T> databaseType, Object primaryKey)
}
}

/// Update a record in the database either by insert if it is new, or by update otherwise. It again can only
/// persist classes that are annotated with `@TableMapping` with fields annotated with `@FieldMapping`
/// @param databaseType the type that is correctly annotated
/// @data the item to persist
/// @return the record loaded from the database
public <T> int updateRecord(Class<T> databaseType, T data) throws DataException {
var tableInfo = databaseType.getAnnotation(TableMapping.class);

Expand Down Expand Up @@ -161,6 +194,9 @@ private boolean isPrimaryKey(Field field) {
return field.getAnnotation(FieldMapping.class).primaryKey();
}

/// This should be called for each database type during start up, it will ensure that the database is up-to-date
/// with any changes in the schema, note that only small incremental changes can be handled by this utility.
/// It is intended that only columns are added, they should not be renamed or deleted.
public void ensureTableFormatCorrect(Class<?>... databaseTypes) throws DataException {
for(var databaseType : databaseTypes) {
var tableMapping = databaseType.getAnnotation(TableMapping.class);
Expand Down Expand Up @@ -234,6 +270,9 @@ private void createTableFully(TableMapping tableMapping, FieldMapping[] fieldMap
executeRaw(sql);
}

/// Execute a raw query given SQL and parameters for the query
/// @param sql the SQL to execute
/// @param params the parameters for the query
public void executeRaw(String sql, Object... params) throws DataException {
logger.log(System.Logger.Level.DEBUG, "Execute raw sql " + sql);
try (var stmt = connection.prepareStatement(sql)) {
Expand All @@ -252,6 +291,10 @@ private String toTypeDecl(FieldMapping fm) {
};
return fm.primaryKey() ? strTy + " PRIMARY KEY" : strTy;
}

/// Query for a single raw integer value, the result must be able to return as an integer.
/// @param sql the SQL to execute that returns a single integer value
/// @param data any parameters required to execute.
public int queryRawSqlSingleInt(String sql, Object... data) throws DataException {
logger.log(System.Logger.Level.DEBUG, "Query for int " + sql);
try (var stmt = connection.prepareStatement(sql)) {
Expand All @@ -267,6 +310,7 @@ public int queryRawSqlSingleInt(String sql, Object... data) throws DataException
}
}

/// Same as queryRawSqlSingleInt but without an exception returning 0 on failure.
public int queryRawSqlSingleIntNoException(String sql, Object... data) {
try {
return queryRawSqlSingleInt(sql, data);
Expand All @@ -287,13 +331,20 @@ private static void addParamsToStmt(Object[] data, PreparedStatement stmt) throw
}
}

/// Perform a raw select and handle the results using `ResultSetConsumer`
/// @param s the sql to process
/// @param resultConsumer will be called to process the results.
public void rawSelect(String s, ResultSetConsumer resultConsumer, Object... args) throws Exception {
try (var stmt = connection.createStatement()) {
var rs = stmt.executeQuery(s);
resultConsumer.processResults(rs);
}
}

/// Query for a list of string items
/// @param sql the SQL to execute
/// @param params the parameters for the sql
/// @return a list of strings. There must only be 1 column in the dataset.
public List<String> queryStrings(String sql, Object... params) throws DataException {
logger.log(System.Logger.Level.DEBUG, "Query for strings " + sql);
try (var stmt = connection.prepareStatement(sql)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package com.thecoderscorner.embedcontrol.customization;

/// Used by component settings to indicate what size of font is needed for a given item, either absolute or percentage
/// of the existing font.
public record FontInformation(int fontSize, SizeMeasurement sizeMeasurement) {
public enum SizeMeasurement {ABS_SIZE, PERCENT}

/// Calculates the font size to be used based on the existing font.
///
/// @param size the existing size to base the calculation on
/// @return the calculated font size, either the absolute size or a percentage-based size
public int fontSizeFromExisting(int size) {
return (sizeMeasurement == SizeMeasurement.ABS_SIZE) ? size : (int)((size) * (fontSize / 100.0));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import static com.thecoderscorner.embedcontrol.core.controlmgr.EditorComponent.RenderingStatus;
import static com.thecoderscorner.embedcontrol.core.controlmgr.color.ConditionalColoring.ColorComponentType;

/// Custom drawing allows for the colour to change based on certain parameters, for example a numeric menu item may
/// change color based on range, or a boolean may change color based on it being on or off.
public interface CustomDrawingConfiguration {
NoOpCustomDrawingConfiguration NO_CUSTOM_DRAWING = new NoOpCustomDrawingConfiguration();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
import static com.thecoderscorner.embedcontrol.core.controlmgr.color.ControlColor.asFxColor;
import static com.thecoderscorner.embedcontrol.customization.FontInformation.SizeMeasurement;

/// The standard panel that is responsible for rendering either all or part of a menu onto the display automatically.
/// IE when there is no custom override for a menu item. It has an inner class implementing the `MenuComponentControl`
/// so that it can put the controls suggested into an on display grid.
///
/// This panel then implements `UpdatablePanel` so that it can be informed of menu item changes, and update the UI
/// accordingly.
public class JfxMenuPresentable implements PanelPresentable<Node>, UpdatablePanel {
private final SubMenuItem subMenuItem;
protected final MenuGridComponent<Node> gridComponent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.thecoderscorner.menu.remote.commands.AckStatus;
import com.thecoderscorner.menu.remote.protocol.CorrelationId;

/// Represents a `PanelPresentable` that can be updated when menu items change, it is also provided with a
/// Represents a `PanelPresentable` that can is interested in menu items changes, it is also provided with a
/// tick function so that the implementor can tick down updates that occur. When an item updates the update
/// will be sent through the `itemHasUpdated` method, and you will be on the JavaFx thread when it occurs.
public interface UpdatablePanel {
Expand All @@ -23,4 +23,8 @@ public interface UpdatablePanel {
/// @param correlationId the correlation id of the acknowledgement
/// @param status the status of the acknowledgement
void acknowledgedCorrelationId(CorrelationId correlationId, AckStatus status);

/// Something such as a bootstrap has caused the menu structure to change significantly enough
/// that a complete redraw may be needed.
void entirelyRebuildGrid();
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
import static com.thecoderscorner.embedcontrol.core.controlmgr.color.ConditionalColoring.ColorComponentType.TEXT_FIELD;
import static com.thecoderscorner.embedcontrol.core.controlmgr.color.ControlColor.asFxColor;

/// This class is the starting point for building your own panel to present a sub menu. It has
/// the main functionality to easily add your own components that will update automatically
/// as menu items change. It already implements `UpdatablePanel` so gets updates as menu items
/// change, and also a 1/10sec tick to handle any animations such as update highlighting.
/// Although it is not mandated that you use this to implement custom panels it provides a lot
/// of helper functions.
public abstract class BaseCustomMenuPanel implements PanelPresentable<Node>, UpdatablePanel {
protected final boolean panelCanBeClosed;
protected final MenuEditorFactory<Node> editorFactory;
Expand Down Expand Up @@ -119,4 +125,9 @@ public void tickAll() {
component.tick();
}
}

@Override
public boolean canBeRemoved() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import com.thecoderscorner.embedcontrol.core.util.StringHelper;
import com.thecoderscorner.embedcontrol.customization.GlobalColorCustomizable;
import com.thecoderscorner.embedcontrol.customization.MenuItemStore;
import com.thecoderscorner.embedcontrol.jfx.controlmgr.JfxMenuPresentable;
import com.thecoderscorner.embedcontrol.jfx.controlmgr.JfxNavigationHeader;
import com.thecoderscorner.embedcontrol.jfx.controlmgr.TitleWidget;
import com.thecoderscorner.embedcontrol.jfx.controlmgr.UpdatablePanel;
import com.thecoderscorner.embedcontrol.jfx.controlmgr.panels.ColorSettingsPresentable;
import com.thecoderscorner.menu.domain.MenuItem;
import com.thecoderscorner.menu.domain.state.MenuTree;
Expand Down Expand Up @@ -287,7 +287,7 @@ public void statusHasChanged(AuthStatus status) {
}

private void notifyControlGrid(boolean up) {
if(navigationManager.currentNavigationPanel() instanceof JfxMenuPresentable menuPresentable) {
if(navigationManager.currentNavigationPanel() instanceof UpdatablePanel menuPresentable) {
menuPresentable.connectionIsUp(up);
}
}
Expand Down Expand Up @@ -329,8 +329,8 @@ private void createNewController() {
remoteListener = new RemoteControllerListener() {
@Override
public void menuItemChanged(MenuItem item, boolean valueOnly) {
if(navigationManager.currentNavigationPanel() instanceof JfxMenuPresentable menuPanel) {
menuPanel.getGridComponent().itemHasUpdated(item);
if(navigationManager.currentNavigationPanel() instanceof UpdatablePanel menuPanel) {
menuPanel.itemHasUpdated(item);
}
}

Expand Down Expand Up @@ -362,8 +362,8 @@ public void connectionState(RemoteInformation remoteInformation, AuthStatus conn
@Override
public void ackReceived(CorrelationId key, MenuItem item, AckStatus status) {
if(item == null) return; // we ignore dialog acks at the moment.
if(navigationManager.currentNavigationPanel() instanceof JfxMenuPresentable menuPanel) {
menuPanel.getGridComponent().acknowledgementReceived(key, status);
if(navigationManager.currentNavigationPanel() instanceof UpdatablePanel menuPanel) {
menuPanel.acknowledgedCorrelationId(key, status);
}
}

Expand All @@ -376,7 +376,7 @@ public void dialogUpdate(MenuDialogCommand cmd) {

// handle the case where it's already connected really quick!
if (controller.getConnector().getAuthenticationStatus() == AuthStatus.CONNECTION_READY) {
if(navigationManager.currentNavigationPanel() instanceof JfxMenuPresentable) {
if(navigationManager.currentNavigationPanel() instanceof UpdatablePanel) {
statusHasChanged(AuthStatus.CONNECTION_READY);
}
}
Expand Down
Loading

0 comments on commit b28c841

Please sign in to comment.