4747import javafx .scene .control .TextArea ;
4848import javafx .scene .control .TextField ;
4949import javafx .scene .control .ToggleButton ;
50+ import javafx .scene .control .SeparatorMenuItem ;
5051import javafx .scene .image .Image ;
5152import javafx .scene .image .ImageView ;
53+ import javafx .scene .input .*;
5254import javafx .scene .layout .HBox ;
5355import javafx .scene .layout .VBox ;
5456import javafx .scene .paint .Color ;
57+
58+
59+ import java .net .MalformedURLException ;
60+ import java .net .URL ;
61+ import java .util .regex .Pattern ;
62+ import java .util .regex .Matcher ;
63+ import org .phoebus .logbook .olog .ui .LogbookUIPreferences ;
64+
5565import javafx .util .Callback ;
5666import javafx .util .StringConverter ;
5767import org .phoebus .framework .autocomplete .Proposal ;
7080import org .phoebus .logbook .LogbookPreferences ;
7181import org .phoebus .logbook .Tag ;
7282import org .phoebus .logbook .olog .ui .HelpViewer ;
73- import org .phoebus .logbook .olog .ui .LogbookUIPreferences ;
7483import org .phoebus .logbook .olog .ui .Messages ;
7584import org .phoebus .logbook .olog .ui .PreviewViewer ;
7685import org .phoebus .olog .es .api .model .OlogLog ;
@@ -191,6 +200,8 @@ public class LogEntryEditorController {
191200 @ SuppressWarnings ("unused" )
192201 private Node attachmentsPane ;
193202
203+
204+
194205 private final ContextMenu logbookDropDown = new ContextMenu ();
195206 private final ContextMenu tagDropDown = new ContextMenu ();
196207
@@ -270,6 +281,7 @@ public LogEntryEditorController(LogEntry logEntry, LogEntry inReplyTo, EditMode
270281 @ FXML
271282 public void initialize () {
272283
284+
273285 // Remote log service not reachable, so show error pane.
274286 if (!checkConnectivity ()) {
275287 errorPane .visibleProperty ().set (true );
@@ -392,16 +404,6 @@ public void initialize() {
392404 return text .substring (0 , text .length () - 2 );
393405 }, selectedLogbooks ));
394406
395- tagsSelection .textProperty ().bind (Bindings .createStringBinding (() -> {
396- if (selectedTags .isEmpty ()) {
397- return "" ;
398- }
399- StringBuilder stringBuilder = new StringBuilder ();
400- selectedTags .forEach (l -> stringBuilder .append (l ).append (", " ));
401- String text = stringBuilder .toString ();
402- return text .substring (0 , text .length () - 2 );
403- }, selectedTags ));
404-
405407 logbooksDropdownButton .focusedProperty ().addListener ((changeListener , oldVal , newVal ) ->
406408 {
407409 if (!newVal && !tagDropDown .isShowing () && !logbookDropDown .isShowing ())
@@ -449,16 +451,6 @@ public void initialize() {
449451 newSelection .forEach (t -> updateDropDown (tagDropDown , t , true ));
450452 });
451453
452- selectedLogbooks .addListener ((ListChangeListener <String >) change -> {
453- if (change .getList () == null ) {
454- return ;
455- }
456- List <String > newSelection = new ArrayList <>(change .getList ());
457- logbooksPopOver .setAvailable (availableLogbooksAsStringList , newSelection );
458- logbooksPopOver .setSelected (newSelection );
459- newSelection .forEach (l -> updateDropDown (logbookDropDown , l , true ));
460- });
461-
462454 AutocompleteMenu autocompleteMenu = new AutocompleteMenu (new ProposalService (new ProposalProvider () {
463455 @ Override
464456 public String getName () {
@@ -547,8 +539,139 @@ public LogTemplate fromString(String name) {
547539
548540 // Note: logbooks and tags are retrieved asynchronously from service
549541 getServerSideStaticData ();
542+
543+ setupTextAreaContextMenu ();
544+
545+ }
546+
547+ private void setupTextAreaContextMenu () {
548+ // Create the context menu with default items
549+ ContextMenu contextMenu = new ContextMenu ();
550+
551+ // Standard text editing items
552+ MenuItem undo = new MenuItem ("Undo" );
553+ undo .setOnAction (e -> textArea .undo ());
554+
555+ MenuItem redo = new MenuItem ("Redo" );
556+ redo .setOnAction (e -> textArea .redo ());
557+
558+ MenuItem cut = new MenuItem ("Cut" );
559+ cut .setOnAction (e -> textArea .cut ());
560+
561+ MenuItem copy = new MenuItem ("Copy" );
562+ copy .setOnAction (e -> textArea .copy ());
563+
564+ MenuItem paste = new MenuItem ("Paste" );
565+ paste .setOnAction (e -> textArea .paste ());
566+
567+ MenuItem delete = new MenuItem ("Delete" );
568+ delete .setOnAction (e -> textArea .replaceSelection ("" ));
569+
570+ MenuItem selectAll = new MenuItem ("Select All" );
571+ selectAll .setOnAction (e -> textArea .selectAll ());
572+
573+ // Our custom menu item
574+ MenuItem pasteUrlItem = new MenuItem ("Paste URL as Markdown" );
575+ pasteUrlItem .setOnAction (event -> handleSmartPaste ());
576+ pasteUrlItem .setAccelerator (new KeyCodeCombination (KeyCode .V ,
577+ KeyCombination .SHORTCUT_DOWN , KeyCombination .SHIFT_DOWN ));
578+
579+ // Add all items to the menu
580+ contextMenu .getItems ().addAll (
581+ undo ,
582+ redo ,
583+ new SeparatorMenuItem (),
584+ cut ,
585+ copy ,
586+ paste ,
587+ delete ,
588+ new SeparatorMenuItem (),
589+ selectAll ,
590+ new SeparatorMenuItem (),
591+ pasteUrlItem
592+ );
593+
594+ // Bind the menu items to the text area's state
595+ undo .disableProperty ().bind (textArea .undoableProperty ().not ());
596+ redo .disableProperty ().bind (textArea .redoableProperty ().not ());
597+ cut .disableProperty ().bind (textArea .selectedTextProperty ().isEmpty ());
598+ copy .disableProperty ().bind (textArea .selectedTextProperty ().isEmpty ());
599+ delete .disableProperty ().bind (textArea .selectedTextProperty ().isEmpty ());
600+
601+ // Set the context menu on the text area
602+ textArea .setContextMenu (contextMenu );
550603 }
551604
605+ private void handleSmartPaste () {
606+ final Clipboard clipboard = Clipboard .getSystemClipboard ();
607+ if (!clipboard .hasString ()) {
608+ return ;
609+ }
610+
611+ String clipboardText = clipboard .getString ();
612+ String ologUrl = extractOlogUrl (clipboardText );
613+ String selectedText = textArea .getSelectedText ();
614+
615+ if (ologUrl != null ) {
616+ // It's an Olog URL
617+ String logNumber = extractLogNumber (ologUrl );
618+ if (logNumber != null ) {
619+ if (selectedText != null && !selectedText .isEmpty ()) {
620+ // Use selected text as link text for the Olog reference
621+ String markdownLink = String .format ("[%s](%s)" , selectedText , ologUrl );
622+ textArea .replaceSelection (markdownLink );
623+ } else {
624+ // No selection - create a standard log entry reference
625+ String markdownLink = String .format ("[%s](%s)" , logNumber , ologUrl );
626+ textArea .replaceSelection (markdownLink );
627+ }
628+ }
629+ return ;
630+ }
631+
632+ // Try to identify if clipboard content is a regular URL
633+ try {
634+ new URL (clipboardText );
635+
636+ if (selectedText != null && !selectedText .isEmpty ()) {
637+ // Replace selection with markdown link using selected text
638+ String markdownLink = String .format ("[%s](%s)" , selectedText , clipboardText );
639+ textArea .replaceSelection (markdownLink );
640+ } else {
641+ // No selection - use URL as both link text and target
642+ String markdownLink = String .format ("[%s](%s)" , clipboardText , clipboardText );
643+ textArea .replaceSelection (markdownLink );
644+ }
645+ } catch (MalformedURLException e ) {
646+ // Not a URL - do nothing
647+ }
648+ }
649+ private String extractOlogUrl (String text ) {
650+ String rootUrl = LogbookUIPreferences .web_client_root_URL ;
651+ if (rootUrl == null || rootUrl .isEmpty ()) {
652+ return null ;
653+ }
654+
655+ if (text .toLowerCase ().contains ("olog" ) &&
656+ text .matches (".*?/logs/\\ d+/?$" )) {
657+ return text ;
658+ }
659+ return null ;
660+ }
661+
662+ private String extractLogNumber (String url ) {
663+ if (url == null ) {
664+ return null ;
665+ }
666+ Pattern pattern = Pattern .compile ("/logs/(\\ d+)/?$" );
667+ Matcher matcher = pattern .matcher (url );
668+ if (matcher .find ()) {
669+ return matcher .group (1 );
670+ }
671+ return null ;
672+ }
673+
674+
552675 /**
553676 * Handler for Cancel button. Note that any selections in the {@link SelectionService} are
554677 * cleared to prevent next launch of {@link org.phoebus.logbook.olog.ui.menu.SendToLogBookApp}
@@ -758,7 +881,7 @@ private void getServerSideStaticData() {
758881 List <String > preSelectedLogbooks =
759882 logEntry .getLogbooks ().stream ().map (Logbook ::getName ).toList ();
760883 List <String > defaultLogbooks = Arrays .asList (LogbookUIPreferences .default_logbooks );
761- availableLogbooksAsStringList . forEach ( logbook -> {
884+ for ( String logbook : availableLogbooksAsStringList ) {
762885 CheckBox checkBox = new CheckBox (logbook );
763886 CustomMenuItem newLogbook = new CustomMenuItem (checkBox );
764887 newLogbook .setHideOnClick (false );
@@ -779,7 +902,7 @@ private void getServerSideStaticData() {
779902 selectedLogbooks .add (logbook );
780903 }
781904 logbookDropDown .getItems ().add (newLogbook );
782- });
905+ }
783906
784907 availableTags = logClient .listTags ();
785908 availableTagsAsStringList =
0 commit comments