-
Notifications
You must be signed in to change notification settings - Fork 10
Context Menu Guide
A comprehensive guide to using and understanding the context menu system in Java Leaflet.
- Overview
- Key Components
- Architecture
- Basic Usage
- Advanced Usage
- Platform-Specific Considerations
- Best Practices
- Troubleshooting
- Complete Working Example
- Extension Points
The context menu system provides a platform-independent way to add interactive context menus to all map elements (markers, polygons, polylines, etc.) in the Java Leaflet library. It uses a sophisticated multi-layered architecture based on the mediator pattern to abstract platform-specific implementations while maintaining a consistent API.
- Platform Independence: Works seamlessly across different UI frameworks (Vaadin, JavaFX)
- Type Safety: Leverages Java's type system to prevent runtime errors
- Performance: Minimizes overhead through lazy initialization and caching
- Extensibility: Supports new UI frameworks without modifying existing code
- Thread Safety: Handles concurrent access in multi-threaded environments
- Memory Efficiency: Prevents memory leaks through proper resource management
- Vaadin: Native integration with Vaadin's ContextMenu component
- JavaFX: Direct integration with JavaFX scene graph
- Custom: Extensible architecture supports custom UI framework implementations
All JL objects implement this interface, which provides the core context menu functionality:
public interface JLHasContextMenu<T extends JLObject<T>> {
@NonNull JLContextMenu<T> getContextMenu();
boolean hasContextMenu();
@NonNull T setContextMenuEnabled(boolean enabled);
boolean isContextMenuEnabled();
}
Key Methods:
-
getContextMenu()
- Gets or creates the context menu (lazy initialization) -
hasContextMenu()
- Checks if the object has visible menu items -
setContextMenuEnabled(boolean)
- Enables/disables the context menu -
isContextMenuEnabled()
- Checks if the context menu is enabled
Design Features:
-
Type Parameter Constraint:
T extends JLObject<T>
ensures type safety - Lazy Initialization: Context menus are created only when first accessed
- Enable/Disable Support: Allows runtime control of context menu behavior
Central coordinator for menu items, event handling, and mediator interaction.
public class JLContextMenu<T extends JLObject<T>> {
private final T owner;
private final Map<String, JLMenuItem> menuItems = new ConcurrentHashMap<>();
private OnJLContextMenuItemListener onMenuItemListener;
private boolean enabled = true;
}
Responsibilities:
- Add, remove, and update menu items
- Handle menu item selections
- Manage menu lifecycle events
- Coordinate with platform-specific mediators
Thread Safety:
- Uses
ConcurrentHashMap
for menu items storage - All public methods are thread-safe
- Atomic operations for menu modifications
Immutable value object representing a menu item with builder pattern support:
@Value
@Builder(toBuilder = true)
public class JLMenuItem {
@NonNull String id;
@NonNull String text;
String icon;
@Builder.Default boolean enabled = true;
@Builder.Default boolean visible = true;
}
Features:
- Immutability: Prevents accidental modifications after creation
- Builder Pattern: Flexible object creation with default values
- Value Semantics: Equality based on content, not identity
- Properties: ID, text, icon, enabled/disabled state, visible/hidden state
Platform-specific implementations that bridge JL objects with UI frameworks:
- VaadinContextMenuMediator - For Vaadin applications
- JavaFXContextMenuMediator - For JavaFX applications
- Custom Mediators - Extensible for new platforms
Discovery Mechanism:
- Automatically discovered via Java's ServiceLoader
- Priority-based selection when multiple mediators are available
- Runtime capability checking
The system employs the Mediator pattern to decouple JL objects from platform-specific UI implementations:
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
│ JL Objects │◄──►│ Context Menu System │◄──►│ Platform Mediators │
│ │ │ │ │ │
│ - JLMarker │ │ - JLContextMenu │ │ - VaadinMediator │
│ - JLPolygon │ │ - JLMenuItem │ │ - JavaFXMediator │
│ - JLPolyline │ │ - Event System │ │ - CustomMediator │
└─────────────────┘ └──────────────────────┘ └─────────────────────┘
Benefits:
- JL objects remain platform-agnostic
- New UI frameworks can be added without modifying existing code
- Platform-specific optimizations can be implemented independently
The JLContextMenuMediatorLocator
implements the Service Locator pattern for dynamic mediator discovery:
ServiceLoader<JLContextMenuMediator> → Filter by support & availability →
Sort by priority → Cache result → Return mediator
Event notification uses the Observer pattern for loose coupling between menu lifecycle and application logic:
JLContextMenu → OnJLActionListener → Application Code
↓
ContextMenuEvent → Event Handler → Business Logic
Complete event flow from user interaction to application response:
1. User right-clicks on object
↓
2. Platform UI detects mouse event
↓
3. Mediator receives platform event
↓
4. JLContextMenu processes event
↓
5. Application listener is notified
↓
6. User clicks menu item
↓
7. Platform UI detects selection
↓
8. Mediator notifies JLContextMenu
↓
9. OnJLContextMenuItemListener is invoked
↓
10. Menu closes and cleanup occurs
Event Types:
-
Menu Lifecycle Events: Handled through
OnJLActionListener
-
ContextMenuEvent.OPEN
- Menu is opened -
ContextMenuEvent.CLOSE
- Menu is closed
-
-
Item Selection Events: Handled through
OnJLContextMenuItemListener
- Contains the selected
JLMenuItem
- Contains the selected
The system uses Java's built-in ServiceLoader mechanism for mediator discovery:
Service Provider Configuration:
For Vaadin:
META-INF/services/io.github.makbn.jlmap.element.menu.JLContextMenuMediator
io.github.makbn.jlmap.vaadin.element.menu.VaadinContextMenuMediator
For JavaFX:
META-INF/services/io.github.makbn.jlmap.element.menu.JLContextMenuMediator
io.github.makbn.jlmap.fx.element.menu.JavaFXContextMenuMediator
Discovery Algorithm:
public <T extends JLObject<T>> JLContextMenuMediator findMediator(@NonNull Class<T> objectType) {
// 1. Check cache
JLContextMenuMediator cached = mediatorCache.get(objectType);
if (cached != null) return cached;
// 2. Discover and filter
List<JLContextMenuMediator> candidates = getAvailableMediators().stream()
.filter(m -> m.supportsObjectType(objectType) && m.isAvailable())
.sorted((m1, m2) -> Integer.compare(m2.getPriority(), m1.getPriority()))
.toList();
// 3. Select and cache
JLContextMenuMediator selected = candidates.get(0);
mediatorCache.put(objectType, selected);
return selected;
}
Priority-Based Selection:
Priority Range | Usage |
---|---|
100+ | Framework-specific mediators (Vaadin, JavaFX) |
50-99 | General-purpose mediators |
1-49 | Fallback or test mediators |
// Create a marker
JLMarker marker = JLMarkerBuilder.create()
.latLng(new JLLatLng(51.505, -0.09))
.build();
// Get the context menu (created lazily)
JLContextMenu<JLMarker> contextMenu = marker.getContextMenu();
// Add menu items
contextMenu
.addItem("edit", "Edit Marker", "edit-icon.png")
.addItem("info", "Show Info")
.addItem("delete", "Delete Marker");
contextMenu.setOnMenuItemListener(selectedItem -> {
switch (selectedItem.getId()) {
case "edit" -> openEditDialog(marker);
case "info" -> showMarkerInfo(marker);
case "delete" -> marker.remove();
}
});
Track when the context menu is opened or closed:
marker.setOnActionListener((source, event) -> {
if (event instanceof ContextMenuEvent contextEvent) {
if (contextEvent.isOpen()) {
System.out.println("Context menu opened");
// Perform actions when menu opens
} else if (contextEvent.isClose()) {
System.out.println("Context menu closed");
// Perform cleanup when menu closes
}
}
});
All JL objects support context menus with the same API:
// Polygon with context menu
JLPolygon polygon = JLPolygonBuilder.create()
.latLngs(coordinates)
.build();
JLContextMenu<JLPolygon> polygonMenu = polygon.getContextMenu();
polygonMenu
.addItem("edit-shape", "Edit Shape")
.addItem("change-color", "Change Color")
.addItem("delete", "Delete Polygon");
// Polyline with context menu
JLPolyline polyline = JLPolylineBuilder.create()
.latLngs(coordinates)
.build();
JLContextMenu<JLPolyline> polylineMenu = polyline.getContextMenu();
polylineMenu.addItem("edit-path", "Edit Path");
Use the builder pattern for detailed configuration:
JLMenuItem complexItem = JLMenuItem.builder()
.id("advanced-action")
.text("Advanced Action")
.icon("advanced-icon.png")
.enabled(userHasPermission)
.visible(featureEnabled)
.build();
contextMenu.addItem(complexItem);
Update menu items at runtime:
// Add items dynamically
contextMenu.addItem("new-action", "New Action");
// Remove items
contextMenu.removeItem("unwanted-action");
// Update existing items using toBuilder()
JLMenuItem existingItem = contextMenu.getItem("edit");
JLMenuItem updatedItem = existingItem.toBuilder()
.text("Updated Text")
.enabled(false)
.build();
contextMenu.updateItem(updatedItem);
// Clear all items
contextMenu.clearItems();
Control when context menus appear:
// Enable/disable context menu based on conditions
marker.setContextMenuEnabled(userHasPermission);
// Check menu state
if (marker.hasContextMenu() && marker.isContextMenuEnabled()) {
// Context menu will be shown on right-click
}
// Conditionally show/hide specific items
contextMenu.getItem("delete").toBuilder()
.visible(userCanDelete)
.build();
contextMenu.updateItem(updatedItem);
Update menu items based on application state:
// Update menu based on marker state
marker.setOnActionListener((source, event) -> {
if (event instanceof ContextMenuEvent contextEvent && contextEvent.isOpen()) {
// Update items before menu is shown
JLMenuItem editItem = contextMenu.getItem("edit").toBuilder()
.enabled(!marker.isLocked())
.build();
JLMenuItem deleteItem = contextMenu.getItem("delete").toBuilder()
.visible(userHasDeletePermission())
.build();
contextMenu.updateItem(editItem);
contextMenu.updateItem(deleteItem);
}
});
Show or hide the context menu programmatically:
// Show context menu at specific coordinates
marker.getContextMenu().show(screenX, screenY);
// Hide context menu
marker.getContextMenu().hide();
Integration:
- Context menus are automatically integrated with Vaadin's component tree
- Uses Vaadin's ContextMenu component for native look and feel
- Events are handled through Vaadin's server-side event system
Threading Model:
- Server-side execution on UI thread
- Event handling through Vaadin's session lock
- No additional synchronization required
Features:
- Native integration with Vaadin's ContextMenu component
- Automatic UI updates through Vaadin's push mechanism
- Server-side event handling
Example:
// Vaadin-specific mediator automatically handles component binding
VaadinContextMenuMediator mediator = new VaadinContextMenuMediator();
mediator.registerContextMenu(marker, contextMenu);
// Mediator creates Vaadin ContextMenu and binds to component
Integration:
- Context menus are attached to JavaFX nodes in the scene graph
- Supports icons through JavaFX ImageView components
- Events are handled on the JavaFX Application Thread
Threading Model:
// Mediator ensures operations run on JavaFX Application Thread
if (!Platform.isFxApplicationThread()) {
Platform.runLater(() -> registerContextMenu(object, contextMenu));
return;
}
Features:
- Direct integration with JavaFX scene graph
- Mouse event handling for right-click detection
- Icon support through ImageView components
Example:
// JavaFX-specific mediator handles scene graph integration
JavaFXContextMenuMediator mediator = new JavaFXContextMenuMediator();
mediator.registerContextMenu(marker, contextMenu);
// Mediator creates JavaFX ContextMenu and attaches to Node
Best Practices:
- Use platform-agnostic APIs provided by
JLContextMenu
- Avoid direct access to platform-specific UI components
- Test on all target platforms
- Use priority-based mediator selection for flexibility
Platform Detection:
// Get active mediator information
JLContextMenuMediatorLocator locator = JLContextMenuMediatorLocator.getInstance();
JLContextMenuMediator mediator = locator.findMediator(marker.getClass());
String platformName = mediator.getName(); // "Vaadin" or "JavaFX"
Clear and Actionable Text:
// Good: Clear action verbs
contextMenu
.addItem("edit", "Edit Marker")
.addItem("delete", "Delete Marker")
.addItem("share", "Share Location");
// Avoid: Vague or unclear text
contextMenu
.addItem("item1", "Thing")
.addItem("item2", "Do stuff");
Meaningful IDs:
// Good: Descriptive IDs
contextMenu.addItem("export-geojson", "Export as GeoJSON");
// Avoid: Generic IDs
contextMenu.addItem("action1", "Export as GeoJSON");
Icon Usage:
// Include icons when they enhance usability
contextMenu
.addItem("zoom-in", "Zoom In", "zoom-in-icon.png")
.addItem("zoom-out", "Zoom Out", "zoom-out-icon.png");
Disable vs. Hide:
// Prefer disabling over hiding for unavailable actions
JLMenuItem saveItem = JLMenuItem.builder()
.id("save")
.text("Save Changes")
.enabled(hasUnsavedChanges) // Disabled if no changes
.visible(true) // Always visible
.build();
Lightweight Handlers:
// Good: Lightweight handler delegates to methods
contextMenu.setOnMenuItemListener(item -> {
switch (item.getId()) {
case "edit" -> handleEdit(marker);
case "delete" -> handleDelete(marker);
}
});
// Avoid: Heavy processing in handlers
contextMenu.setOnMenuItemListener(item -> {
// Don't perform expensive operations directly
performLongRunningOperation();
});
Exception Handling:
// Graceful error handling
contextMenu.setOnMenuItemListener(item -> {
try {
switch (item.getId()) {
case "export" -> exportData(marker);
case "process" -> processMarker(marker);
}
} catch (Exception e) {
showErrorMessage("Action failed: " + e.getMessage());
log.error("Menu action failed", e);
}
});
Method Delegation:
// Separate business logic from event handling
private void handleMarkerEdit(JLMarker marker) {
validateMarkerState(marker);
openEditDialog(marker);
logUserAction("edit", marker.getId());
}
contextMenu.setOnMenuItemListener(item -> {
if ("edit".equals(item.getId())) {
handleMarkerEdit(marker);
}
});
Lazy Initialization:
// Context menus are created only when needed
// Don't access getContextMenu() unless you need it
if (needsContextMenu) {
marker.getContextMenu().addItem("action", "Action");
}
Efficient Updates:
// Batch updates when possible
List<JLMenuItem> items = List.of(
JLMenuItem.builder().id("edit").text("Edit").build(),
JLMenuItem.builder().id("delete").text("Delete").build()
);
items.forEach(contextMenu::addItem);
Resource Cleanup:
// Clean up when objects are no longer needed
@Override
public void dispose() {
if (hasContextMenu()) {
getContextMenu().clearItems();
setContextMenuEnabled(false);
}
}
Descriptive Text:
// Use descriptive text for screen readers
contextMenu.addItem("zoom", "Zoom to Marker Location");
// Not just "Zoom"
Keyboard Support:
// Consider providing keyboard shortcuts
// Document keyboard alternatives in UI
contextMenu.addItem("delete", "Delete Marker (Del)");
Alternative Access:
// Provide alternative access to critical functions
// Don't rely solely on context menus for essential operations
// Also provide toolbar buttons, menu bar items, etc.
Concurrent Access:
// JLContextMenu is thread-safe
// But consider synchronizing complex operations
synchronized (marker) {
if (!marker.hasContextMenu()) {
JLContextMenu<JLMarker> menu = marker.getContextMenu();
menu.addItem("action", "Action");
}
}
Platform Threading:
// Respect platform threading requirements
// Mediators handle platform-specific threading automatically
// But external resources may need explicit threading
Platform.runLater(() -> {
updateExternalResource(marker);
});
Check if context menu is enabled:
if (!marker.isContextMenuEnabled()) {
marker.setContextMenuEnabled(true);
}
Verify menu has visible items:
if (!marker.getContextMenu().hasVisibleItems()) {
// Add at least one visible item
marker.getContextMenu().addItem("action", "Action");
}
Ensure appropriate mediator is available:
try {
JLContextMenuMediatorLocator locator = JLContextMenuMediatorLocator.getInstance();
JLContextMenuMediator mediator = locator.findMediator(marker.getClass());
System.out.println("Using mediator: " + mediator.getName());
} catch (Exception e) {
System.err.println("No mediator found: " + e.getMessage());
}
Check console for errors:
- Look for mediator registration errors
- Verify ServiceLoader configuration files
- Check for platform availability issues
Verify listener is set:
if (marker.getContextMenu().getOnMenuItemListener() == null) {
marker.getContextMenu().setOnMenuItemListener(item -> {
// Handle item selection
});
}
Check item state:
JLMenuItem item = contextMenu.getItem("action");
if (item != null) {
System.out.println("Enabled: " + item.isEnabled());
System.out.println("Visible: " + item.isVisible());
}
Verify unique IDs:
// Ensure all menu items have unique IDs within the same menu
Set<String> ids = new HashSet<>();
for (JLMenuItem item : contextMenu.getItems()) {
if (!ids.add(item.getId())) {
System.err.println("Duplicate ID: " + item.getId());
}
}
Check for exceptions:
// Add try-catch in handler to catch and log exceptions
contextMenu.setOnMenuItemListener(item -> {
try {
handleMenuItem(item);
} catch (Exception e) {
e.printStackTrace();
System.err.println("Error handling menu item: " + e.getMessage());
}
});
Avoid creating too many context menus:
// Bad: Creating context menus for thousands of markers
for (JLMarker marker : thousands_of_markers) {
marker.getContextMenu().addItem("action", "Action");
}
// Good: Only create context menus when needed
marker.setOnActionListener((source, event) -> {
if (event instanceof ContextMenuEvent) {
source.getContextMenu().addItem("action", "Action");
}
});
Remove unused context menus:
// Clean up when markers are removed
void removeMarker(JLMarker marker) {
if (marker.hasContextMenu()) {
marker.getContextMenu().clearItems();
}
marker.remove();
}
Use lazy initialization:
// Don't access getContextMenu() unless needed
if (markerRequiresContextMenu(marker)) {
marker.getContextMenu().addItem("action", "Action");
}
Consider item pooling:
// Reuse menu item objects for frequently changing menus
private final Map<String, JLMenuItem> itemPool = new HashMap<>();
private JLMenuItem getOrCreateItem(String id, String text) {
return itemPool.computeIfAbsent(id, k ->
JLMenuItem.builder().id(id).text(text).build()
);
}
Verify ServiceLoader configuration:
# Check that service provider file exists
ls -la META-INF/services/io.github.makbn.jlmap.element.menu.JLContextMenuMediator
# Verify file contains correct class name
cat META-INF/services/io.github.makbn.jlmap.element.menu.JLContextMenuMediator
Check mediator availability:
JLContextMenuMediatorLocator locator = JLContextMenuMediatorLocator.getInstance();
List<MediatorInfo> mediators = locator.getMediatorInfo();
for (MediatorInfo info : mediators) {
System.out.println("Mediator: " + info.getName());
System.out.println(" Class: " + info.getClassName());
System.out.println(" Priority: " + info.getPriority());
System.out.println(" Available: " + info.isAvailable());
}
Clear mediator cache:
// If mediator discovery seems incorrect, clear cache
JLContextMenuMediatorLocator.getInstance().clearCache();
Here's a comprehensive example demonstrating all features of the context menu system:
import io.github.makbn.jlmap.element.JLMarker;
import io.github.makbn.jlmap.element.JLPolygon;
import io.github.makbn.jlmap.element.builder.JLMarkerBuilder;
import io.github.makbn.jlmap.element.builder.JLPolygonBuilder;
import io.github.makbn.jlmap.element.menu.JLContextMenu;
import io.github.makbn.jlmap.element.menu.JLMenuItem;
import io.github.makbn.jlmap.event.ContextMenuEvent;
import io.github.makbn.jlmap.model.JLLatLng;
import java.util.List;
public class ContextMenuExample {
public static void main(String[] args) {
// Example 1: Basic marker with context menu
basicMarkerExample();
// Example 2: Polygon with dynamic menu
polygonWithDynamicMenuExample();
// Example 3: Advanced menu with state management
advancedMenuExample();
// Example 4: Menu lifecycle events
menuLifecycleExample();
}
/**
* Basic example: Create a marker with a simple context menu
*/
private static void basicMarkerExample() {
// Create marker
JLMarker marker = JLMarkerBuilder.create()
.latLng(new JLLatLng(51.505, -0.09))
.draggable(true)
.build();
// Get context menu and add items
JLContextMenu<JLMarker> contextMenu = marker.getContextMenu();
contextMenu
.addItem("edit", "Edit Marker", "edit-icon.png")
.addItem("info", "Show Info")
.addItem("delete", "Delete Marker");
// Handle menu item selections
contextMenu.setOnMenuItemListener(selectedItem -> {
System.out.println("Selected: " + selectedItem.getText());
switch (selectedItem.getId()) {
case "edit" -> editMarker(marker);
case "info" -> showMarkerInfo(marker);
case "delete" -> deleteMarker(marker);
}
});
}
/**
* Polygon example: Dynamic menu based on polygon state
*/
private static void polygonWithDynamicMenuExample() {
List<JLLatLng> coordinates = List.of(
new JLLatLng(51.509, -0.08),
new JLLatLng(51.503, -0.06),
new JLLatLng(51.51, -0.047)
);
JLPolygon polygon = JLPolygonBuilder.create()
.latLngs(coordinates)
.build();
JLContextMenu<JLPolygon> contextMenu = polygon.getContextMenu();
// Add initial menu items
contextMenu
.addItem("edit-vertices", "Edit Vertices")
.addItem("change-color", "Change Color")
.addItem("calculate-area", "Calculate Area")
.addItem("delete", "Delete Polygon");
// Update menu dynamically based on state
polygon.setOnActionListener((source, event) -> {
if (event instanceof ContextMenuEvent contextEvent && contextEvent.isOpen()) {
// Update menu items before showing
updatePolygonMenu(polygon);
}
});
// Handle selections
contextMenu.setOnMenuItemListener(item -> {
switch (item.getId()) {
case "edit-vertices" -> editPolygonVertices(polygon);
case "change-color" -> changePolygonColor(polygon);
case "calculate-area" -> calculateArea(polygon);
case "delete" -> deletePolygon(polygon);
}
});
}
/**
* Advanced example: Complex menu items with state management
*/
private static void advancedMenuExample() {
JLMarker marker = JLMarkerBuilder.create()
.latLng(new JLLatLng(51.5, -0.09))
.build();
JLContextMenu<JLMarker> contextMenu = marker.getContextMenu();
// Create complex menu items using builder
JLMenuItem editItem = JLMenuItem.builder()
.id("edit")
.text("Edit Marker")
.icon("edit-icon.png")
.enabled(hasEditPermission())
.visible(true)
.build();
JLMenuItem shareItem = JLMenuItem.builder()
.id("share")
.text("Share Location")
.icon("share-icon.png")
.enabled(isOnline())
.visible(hasShareFeature())
.build();
JLMenuItem exportItem = JLMenuItem.builder()
.id("export")
.text("Export Data")
.enabled(hasData())
.build();
// Add items
contextMenu
.addItem(editItem)
.addItem(shareItem)
.addItem(exportItem)
.addItem("delete", "Delete Marker");
// Handle selections with error handling
contextMenu.setOnMenuItemListener(item -> {
try {
handleAdvancedMenuItem(marker, item);
} catch (Exception e) {
System.err.println("Error handling menu item: " + e.getMessage());
showErrorDialog("Action failed: " + e.getMessage());
}
});
// Update items dynamically
updateMenuItemStates(contextMenu);
}
/**
* Lifecycle example: Track menu open/close events
*/
private static void menuLifecycleExample() {
JLMarker marker = JLMarkerBuilder.create()
.latLng(new JLLatLng(51.505, -0.09))
.build();
// Setup context menu
JLContextMenu<JLMarker> contextMenu = marker.getContextMenu();
contextMenu
.addItem("action1", "Action 1")
.addItem("action2", "Action 2");
// Track lifecycle events
marker.setOnActionListener((source, event) -> {
if (event instanceof ContextMenuEvent contextEvent) {
if (contextEvent.isOpen()) {
System.out.println("Context menu opened");
onMenuOpen(source);
} else if (contextEvent.isClose()) {
System.out.println("Context menu closed");
onMenuClose(source);
}
}
});
// Handle item selections
contextMenu.setOnMenuItemListener(item -> {
System.out.println("Item selected: " + item.getText());
executeAction(item.getId());
});
// Enable/disable menu based on conditions
marker.setContextMenuEnabled(shouldEnableMenu());
}
// Helper methods
private static void editMarker(JLMarker marker) {
System.out.println("Editing marker at " + marker.getLatLng());
// Implementation
}
private static void showMarkerInfo(JLMarker marker) {
System.out.println("Marker info: " + marker.getLatLng());
// Implementation
}
private static void deleteMarker(JLMarker marker) {
System.out.println("Deleting marker");
marker.remove();
}
private static void updatePolygonMenu(JLPolygon polygon) {
JLContextMenu<JLPolygon> menu = polygon.getContextMenu();
// Update edit item based on lock state
JLMenuItem editItem = menu.getItem("edit-vertices").toBuilder()
.enabled(!polygon.isLocked())
.build();
menu.updateItem(editItem);
// Show/hide calculate area based on polygon validity
JLMenuItem areaItem = menu.getItem("calculate-area").toBuilder()
.visible(polygon.isValid())
.build();
menu.updateItem(areaItem);
}
private static void editPolygonVertices(JLPolygon polygon) {
System.out.println("Editing polygon vertices");
// Implementation
}
private static void changePolygonColor(JLPolygon polygon) {
System.out.println("Changing polygon color");
// Implementation
}
private static void calculateArea(JLPolygon polygon) {
System.out.println("Calculating polygon area");
// Implementation
}
private static void deletePolygon(JLPolygon polygon) {
System.out.println("Deleting polygon");
polygon.remove();
}
private static void handleAdvancedMenuItem(JLMarker marker, JLMenuItem item) {
switch (item.getId()) {
case "edit" -> editMarker(marker);
case "share" -> shareLocation(marker);
case "export" -> exportMarkerData(marker);
case "delete" -> deleteMarker(marker);
}
}
private static void shareLocation(JLMarker marker) {
System.out.println("Sharing location: " + marker.getLatLng());
// Implementation
}
private static void exportMarkerData(JLMarker marker) {
System.out.println("Exporting marker data");
// Implementation
}
private static void updateMenuItemStates(JLContextMenu<?> contextMenu) {
// Update edit item
JLMenuItem editItem = contextMenu.getItem("edit").toBuilder()
.enabled(hasEditPermission())
.build();
contextMenu.updateItem(editItem);
// Update share item
JLMenuItem shareItem = contextMenu.getItem("share").toBuilder()
.enabled(isOnline())
.build();
contextMenu.updateItem(shareItem);
}
private static void onMenuOpen(JLMarker marker) {
System.out.println("Preparing menu for marker: " + marker.getId());
// Refresh menu items, load dynamic data, etc.
}
private static void onMenuClose(JLMarker marker) {
System.out.println("Cleaning up menu for marker: " + marker.getId());
// Cleanup, release resources, etc.
}
private static void executeAction(String actionId) {
System.out.println("Executing action: " + actionId);
// Implementation
}
private static void showErrorDialog(String message) {
System.err.println("Error: " + message);
// Show UI error dialog
}
// State check methods
private static boolean hasEditPermission() {
return true; // Check actual permissions
}
private static boolean isOnline() {
return true; // Check network status
}
private static boolean hasShareFeature() {
return true; // Check feature flags
}
private static boolean hasData() {
return true; // Check if data is available
}
private static boolean shouldEnableMenu() {
return true; // Check application state
}
}
- Simple Setup: Creating context menus requires minimal code
- Flexible Configuration: Builder pattern for complex scenarios
- Event Handling: Both item selection and lifecycle events
- Dynamic Updates: Menu items can be updated based on state
- Error Handling: Proper exception management in handlers
- Type Safety: Generic types ensure compile-time safety
To add support for a new UI framework, implement the JLContextMenuMediator
interface:
package your.package;
import io.github.makbn.jlmap.element.JLObject;
import io.github.makbn.jlmap.element.menu.JLContextMenu;
import io.github.makbn.jlmap.element.menu.JLContextMenuMediator;
import lombok.NonNull;
public class CustomFrameworkMediator implements JLContextMenuMediator {
@Override
public <T extends JLObject<T>> void registerContextMenu(
@NonNull T object,
@NonNull JLContextMenu<T> contextMenu) {
// Create framework-specific context menu
CustomFrameworkMenu menu = new CustomFrameworkMenu();
// Populate menu items
for (JLMenuItem item : contextMenu.getVisibleItems()) {
CustomFrameworkMenuItem menuItem = menu.addItem(item.getText());
menuItem.setIcon(item.getIcon());
menuItem.setEnabled(item.isEnabled());
// Handle item selection
menuItem.setOnAction(() ->
contextMenu.handleMenuItemSelection(item));
}
// Attach to framework component
CustomFrameworkComponent component = getComponent(object);
component.setContextMenu(menu);
// Store for later updates
contextMenuCache.put(object, menu);
}
@Override
public <T extends JLObject<T>> void unregisterContextMenu(@NonNull T object) {
CustomFrameworkMenu menu = contextMenuCache.remove(object);
if (menu != null) {
menu.dispose();
}
}
@Override
public <T extends JLObject<T>> void updateContextMenu(
@NonNull T object,
@NonNull JLContextMenu<T> contextMenu) {
// Clear and rebuild menu
unregisterContextMenu(object);
registerContextMenu(object, contextMenu);
}
@Override
public <T extends JLObject<T>> void showContextMenu(
@NonNull T object,
double x,
double y) {
CustomFrameworkMenu menu = contextMenuCache.get(object);
if (menu != null) {
menu.show(x, y);
}
}
@Override
public <T extends JLObject<T>> void hideContextMenu(@NonNull T object) {
CustomFrameworkMenu menu = contextMenuCache.get(object);
if (menu != null) {
menu.hide();
}
}
@Override
public boolean supportsObjectType(@NonNull Class<? extends JLObject<?>> objectType) {
// Return true for supported object types
return true;
}
@Override
public boolean isAvailable() {
// Check if framework is available
return CustomFramework.isInitialized();
}
@Override
public int getPriority() {
// Return priority (100+ for framework-specific)
return 100;
}
@Override
@NonNull
public String getName() {
return "CustomFramework";
}
private CustomFrameworkComponent getComponent(JLObject<?> object) {
// Get or create framework-specific component
return null; // Implementation specific
}
}
Create a service provider configuration file:
File: META-INF/services/io.github.makbn.jlmap.element.menu.JLContextMenuMediator
Content:
your.package.CustomFrameworkMediator
Extend the event system for framework-specific events:
public class CustomContextMenuEvent implements Event {
private final String customData;
public CustomContextMenuEvent(String customData) {
this.customData = customData;
}
@Override
public JLAction action() {
return JLAction.CUSTOM_CONTEXT_ACTION;
}
public String getCustomData() {
return customData;
}
}
// Use in your mediator
contextMenu.getOwner().notifyActionListener(
new CustomContextMenuEvent("data")
);
Add custom monitoring to track menu performance:
public class MonitoredContextMenuMediator implements JLContextMenuMediator {
private final JLContextMenuMediator delegate;
private final PerformanceMonitor monitor;
@Override
public <T extends JLObject<T>> void registerContextMenu(
T object,
JLContextMenu<T> contextMenu) {
long start = System.nanoTime();
try {
delegate.registerContextMenu(object, contextMenu);
} finally {
long duration = System.nanoTime() - start;
monitor.recordRegistration(duration);
}
}
// Implement other methods with similar monitoring
}
- Architecture Guide - Detailed system architecture
- Getting Started - General library setup
- API Reference - Complete API documentation
- Complete working examples:
ContextMenuExample.java
- Unit tests:
JLContextMenuTest.java
- Integration tests:
ContextMenuIntegrationTest.java
- GitHub Issues: Report bugs and request features
- Discussions: Ask questions and share ideas
- Wiki: Additional guides and tutorials
Last Updated: 2025-10-03 Version: 2.0.0