在JavaFX中创建文本注释功能

时间:2016-06-22 17:20:17

标签: javafx

我正在计划一个程序,除其他外,它允许评论文本文档的特定文本元素。基本上,我想创建突出显示特定文本部分并添加注释的功能。我打算使用JavaFX,虽然我会对其他java包开放,比如swing,甚至是JS中的东西。

Word做得很好,虽然我想添加其他功能,但它是一个很好的基础。另一种可以满足我需求的产品是Google Books。可以添加评论并放置亮点。 PDF阅读器也很好,但我想避免使用PDF文件,至少目前如此。

我已经四处寻找其他一些建议。有一些资源可以添加亮点,这看起来有点复杂但并非不可能。 TextFlow对象可能会很好地处理这个问题。我会更多地研究,但评论功能是至关重要的,所以我不想走一条完全无法完成工作的路线。

有关如何在JavaFX中执行此操作的任何建议?还是在另一个框架?

编辑: 用户应该能够修改注释并删除它们,但通常它们应该是永久性的,这样当用户在保存注释后打开相关文件时,应该保留相同的注释。

1 个答案:

答案 0 :(得分:2)

我提供了一种JavaFX方法来获取想法。它包含了您之后的一些主要功能,但是您可能希望修改它们以更好地满足您的需求 - notes 下的一些建议

功能

  • 添加评论:选择并右键单击感兴趣的文本会向默认ContextMenu公开一个附加选项,以提示用户发表评论:

    Add comment option Example of a comment being added

  • 突出显示文字:关键字在评论窗格中以红色突出显示。在此窗格中选择评论后,TextArea将再次突出显示/选择文本,如上一步

  • 阅读旧评论:我已经在ToDo中添加了一个脏的实现,因为我希望您拥有自己想要存储的方法,阅读评论:
  

/ * ToDo:假设结构:rangeStart,rangeEnd |相关文字|   评论           添加验证以确认这一点,或者用a完全替换它           更好的结构,如xml           实现基于保存的功能,无论您决定采用哪种方法   * /

来自Lorem Ipsum的示例,如果你想在以下实现的基础上构建:

6, 11|ipsum|Test comment to apply against ipsum
409, 418|deserunt |This is a long comment that should wrap around
0, 11|Lorem ipsum|Lorem ipsum inception: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

<小时/> 注意:

SSCCE

public class CommentAnnotation extends Application{

    public class DocumentAnnotator extends HBox {
        private File documentToAnnotate;
        private ScrollPane textViewer, commentViewer;
        private TextArea textArea;
        private VBox commentContainer;
        private MenuItem addCommentItem;

        private String textInViewer;
        private ObservableList<Node> comments = FXCollections.observableArrayList();

        public DocumentAnnotator(File document){
            documentToAnnotate = document;
            readInFile();
            setupElements();
        }

        public DocumentAnnotator(File document, File storedComments){
            this(document);
            //Re-load previous comments
            try{
                Files.lines(storedComments.toPath()).forEach(this::parseAndPopulateComment);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void readInFile(){
            try {
                textInViewer = Files.lines(documentToAnnotate.toPath())
                        .collect(Collectors.joining("\n"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void parseAndPopulateComment(String comment){
            /*ToDo: Assumes structure: rangeStart , rangeEnd | associated text | comment
                    Either add validation to confirm this, or replace it completely with a
                    better structure such as xml
                    Implement the save-based functionality of whichever approach you decide on
            */
            String[] splitResult = comment.split("\\|");
            createComment(IndexRange.valueOf(splitResult[0]), splitResult[1], splitResult[2]);
        }

        private void setupElements(){
            setupViewers();
            getChildren().setAll(textViewer, new Separator(Orientation.VERTICAL), commentViewer);
        }

        private void setupViewers(){
            setupTextViewer();
            setupCommentViewer();
        }

        private void setupTextViewer(){
            setupTextArea();

            textViewer = new ScrollPane(textArea);
            textViewer.minHeightProperty().bind(heightProperty());
            textViewer.maxHeightProperty().bind(heightProperty());
            textArea.maxWidthProperty().bind(textViewer.widthProperty());
            textArea.minHeightProperty().bind(textViewer.heightProperty());
        }

        private void setupTextArea(){
            textArea = new TextArea(textInViewer);
            //Ensure that if this controls dimensions change, the text will wrap around again
            textArea.setWrapText(true);

            addCommentItem = new MenuItem("Add comment");
            addCommentItem.setOnAction(event -> {
                IndexRange range = textArea.getSelection();
                String selectedText = textArea.getSelectedText();
                String commentText = promptUserForComment(selectedText);
                if(selectedText.isEmpty() || commentText.isEmpty()){ return; }
                createComment(range, selectedText, commentText);
            });

            //Append an "Add comment" option to the default menu which contains cut|copy|paste etc
            TextAreaSkin modifiedSkin = new TextAreaSkin(textArea){
                @Override
                public void populateContextMenu(ContextMenu contextMenu) {
                    super.populateContextMenu(contextMenu);
                    contextMenu.getItems().add(0, addCommentItem);
                    contextMenu.getItems().add(1, new SeparatorMenuItem());
                }
            };
            textArea.setSkin(modifiedSkin);
            textArea.setEditable(false);
        }

        private String promptUserForComment(String selectedText){
            TextInputDialog inputDialog = new TextInputDialog();
            inputDialog.setHeaderText(null);
            inputDialog.setTitle("Input comment");
            inputDialog.setContentText("Enter the comment to associate against: " + selectedText);
            return inputDialog.showAndWait().get();
        }

        private void setupCommentViewer(){
            commentContainer = new VBox(5);
            Bindings.bindContentBidirectional(commentContainer.getChildren(), comments);
            commentViewer = new ScrollPane(commentContainer);
            //Use 30% of the control's width to display comments
            commentViewer.minWidthProperty().bind(widthProperty().multiply(0.30));
            commentViewer.maxWidthProperty().bind(widthProperty().multiply(0.30));
            commentViewer.minHeightProperty().bind(heightProperty());
            commentViewer.maxHeightProperty().bind(heightProperty());
            //Imitate wrapping
            commentViewer.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
            //Account for scroller width
            commentContainer.maxWidthProperty().bind(commentViewer.widthProperty().subtract(5));
        }

        private void createComment(IndexRange range, String selectedText, String commentText){
            AssociatedComment comment = new AssociatedComment(range, selectedText, commentText);
            //Re-select the range when the comment is clicked
            comment.setOnMouseClicked(clickEvent -> textArea.selectRange(
                    comment.getAssociatedRange().getStart(), comment.getAssociatedRange().getEnd()));
            comments.add(comment);
        }
    }

    public class AssociatedComment extends TextFlow {
        private IndexRange associatedRange;
        private Text associatedText, associatedComment;

        public AssociatedComment(IndexRange range, String text, String comment){
            associatedRange = range;
            associatedText = new Text(text);
            associatedText.setFill(Color.RED);
            associatedComment = new Text(comment);
            getChildren().setAll(associatedText, new Text(" :  "), associatedComment);
        }

        public IndexRange getAssociatedRange(){
            return associatedRange;
        }

        public String getAssociatedText(){
            return associatedText.getText();
        }

        public String getAssociatedComment(){
            return associatedComment.getText();
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        File lorem = new File(getClass().getClassLoader().getResource("loremIpsum.txt").toURI());
        File loremComments = new File(getClass().getClassLoader().getResource("loremComments.txt").toURI());
        DocumentAnnotator annotator = new DocumentAnnotator(lorem);
        //DocumentAnnotator annotator = new DocumentAnnotator(lorem, loremComments);

        Scene scene = new Scene(annotator, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Document annotation");
        primaryStage.show();
    }
}