1

Sorry to be asking this, since there are a few other posts about the same topic, but I just didn't manage to apply those solutions for my issue.

Simple scenario. I have a JTextPane where I have a search method. It detects all matched words and highlights them grey. To navigate through different matches, I have a second Highlighter which highlights the current "active" match red. By pressing buttons the active highlight goes to the next match. (Basically how chromes search works)

Everything works except removing the old active highlighter. There is a method called .removeHighlight() which I would need to remove the old highlight, but no matter what I plug in as parameter, I get either exceptions or nothing happens. I would use .removeAllHighlights() but because I have other highlights( highlighting all hits grey) I would loose them, so I have to only remove the last active highlight. Official oracle docs did something with removeHighlight(hilites[i]) but honestly I have no clue what was going on there. here The code I've got so far:

private DefaultHighlightPainter highlightOne =  new DefaultHighlightPainter(Color.RED);
private Object last;

public void paintAt(int pos){
    try {
        if (last != null){
            motherFrame.tField.getHighlighter().removeHighlight(last);
        }
        last = motherFrame.tField.getHighlighter().addHighlight(pos, pos + searchWordLength, highlightOne);
    } catch (BadLocationException e) {
        //TODO
    }
}  

And here a pic:

enter image description here

UPDATE Here is a runnable: (Sorry for the messy code.)

http://hostcode.sourceforge.net/view/2563 and http://hostcode.sourceforge.net/view/2564

Haeri
  • 621
  • 1
  • 12
  • 27

2 Answers2

2

Highlighter#addHighlight returns an Object tag which represents the current highlight. This tag should be used when calling Highlighter#removeHighlight, this, I assume, means you can use the same instance of the HighlightPainter to highlight multiple parts of the document, but still manage them separately, for example...

Highlight

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Document;

public class TestEditorPane01 {

    public static void main(String[] args) {
        new TestEditorPane01();
    }

    public TestEditorPane01() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new EditorPane());
                frame.setSize(400, 400);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

            }
        });
    }

    public class EditorPane extends JPanel {

        private JTextPane editor = new JTextPane();
        private int lastMatch;
        private String find = "Method";
        private DefaultHighlighter.DefaultHighlightPainter highlightPainter;
        private Object highlightTag;

        private JTextField searchField;
        private JButton searchButton;

        public EditorPane() {
            setLayout(new BorderLayout());
            editor = new JTextPane();
            try (FileReader reader = new FileReader(new File("/some file.txt"))) {
                editor.read(reader, "script");
            } catch (IOException exp) {
                exp.printStackTrace();
            }
            add(new JScrollPane(editor));

            JPanel searchPane = new JPanel(new GridBagLayout());
            searchField = new JTextField(10);
            searchButton = new JButton("Search");
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            gbc.weightx = 1;
            searchPane.add(searchField, gbc);

            gbc.gridx++;
            gbc.fill = GridBagConstraints.NONE;
            gbc.weightx = 0;
            searchPane.add(searchButton, gbc);

            searchButton.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    String text = searchField.getText();
                    if (!text.equals(find)) {
                        find = text;
                        lastMatch = 0;
                    }
                    highlightNext();
                }
            });

            add(searchPane, BorderLayout.SOUTH);

        }

        public void highlightNext() {

            Document document = editor.getDocument();
            try {

                if (lastMatch + find.length() >= document.getLength()) {
                    lastMatch = 0;
                }

                for (; lastMatch + find.length() < document.getLength(); lastMatch++) {
                    String match = document.getText(lastMatch, find.length());
                    if (find.equalsIgnoreCase(match)) {
                        if (highlightTag != null) {
                            editor.getHighlighter().removeHighlight(highlightTag);
                        }

                        if (highlightPainter == null) {
                            highlightPainter = new javax.swing.text.DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW);
                        }

                        highlightTag = editor.getHighlighter().addHighlight(lastMatch, lastMatch + find.length(), highlightPainter);

                        Rectangle viewRect = editor.modelToView(lastMatch);
                        editor.scrollRectToVisible(viewRect);

                        lastMatch += find.length();
                        break;
                    }
                }
            } catch (BadLocationException ex) {
                ex.printStackTrace();
            }

        }

    }
}

Since you are doing this, and it's not working, this would suggest that there is something else wrong with your code which is not evident in the snippet you have provided. Consider providing a runnable example which demonstrates your problem. This will result in less confusion and better responses

Updated...

  1. Don't link your example code to an external site, not everyone can access external sites or can be bothered to follow links anyway...
  2. A runnable example should be self contained and have no reliance on other libraries or resources, like icons and should be contained within a single file
  3. Don't expose your UI components unnecessarily, that is, I don't think SearchDialog really needs to know about TestFrame, all it's interested in is the JTextComponent...
  4. Your "new" example and your existing code snippet are inconsistent. You are no longer assigning the result of addHighlight to last??
  5. Don't use null layouts...

When I finally got your example code to compile, this is what I was presented with...

Bad Screen 01

...Okay, so thought, I'd just expand the window...

Bad Screen 02

...well, there's a problem...which I don't have the time to solve.

Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify

Take a look at Why is it frowned upon to use a null layout in SWING? for more details...

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Hmm ok thank you for the comment. I am going to try to extract the search dialog into a runnable snippet. – Haeri Nov 05 '14 at 22:09
  • Ok thank you for your time. This is my first java application so I am still learning things. I will Update everything as you said. PS: I linked to an external site because I still haven't figured out how to post long code snippets in here.. PSS: Damn.. I made every single JDialog with a null Layout.. But it looked so nice on my systems.. :( – Haeri Nov 05 '14 at 23:52
  • I use a different font and font point then normal - mostly because the system I'm working on the previous developer screwed up the layouts big time, so I use a different font/size to catch these problems, this is like 90% of my day job...now you know why I don't like `null` layouts or people who use `setPreferredSize` ... ;) – MadProgrammer Nov 05 '14 at 23:55
  • So instead of fixing the example code I fiddled around with the original code and found this: The original code sample I posted totally works. It was not showing up for me, because I have that second Highlighter, which for some reason does not allow my red Highlighter to overpaint those highlights. So I have to change the question to: How to manage multiple Highlighter accessing the same text snippet. – Haeri Nov 06 '14 at 08:29
  • Best to another, new, question ;) – MadProgrammer Nov 06 '14 at 09:00
  • I solved it. It is not the best way, but it works. As I found out, the Highlighter cannot be over-painted by a second Highlighter, which is called later. The first paint stays until a remove. So I had to first call `removeAllHighlights()` then paint red and finally paint grey. This is really counter intuitive... And also very inefficient, because now I have to always repaint the entire document at every step. – Haeri Nov 06 '14 at 09:25
  • I'm wondering if you can maintain some kind of list of "book marks" and only remove the highlights as they overlap... – MadProgrammer Nov 06 '14 at 09:33
  • I did also thought about this... you would have to make an object array with all the hits, and as active Highlight changes, you would have to first remove object[active+1], remove active, paint active+1, paint object[active] grey.... Ufff... Maybe another day xD PS: if you are interested, I have this project posted on GitHub: https://github.com/Haeri/MonolithTextEditor (Gonna update it with the Highlighterfix in the next few days.) – Haeri Nov 06 '14 at 09:54
  • I was thinking more along the lines of a "range" via (start, length) and the highlight tag, so you could then walk through the list comparing ranges, as you may need to "adjust" several ;) – MadProgrammer Nov 06 '14 at 10:11
0

Thanks to MadProgrammer I found out what the problem was:

For the original question "How to remove the old highlighter" the answer is simple: It is the sample code I posted.

private DefaultHighlightPainter highlightOne =  new DefaultHighlightPainter(Color.RED);
private Object last;

public void paintAt(int pos){
    try {
        if (last != null){
            textPane.getHighlighter().removeHighlight(last);
        }
        last = textPane.getHighlighter().addHighlight(pos, pos + searchWordLength, highlightOne);
    } catch (BadLocationException e) {
        //TODO
    }
}  

(Since I have two Highlighter the first one was overwriting the second one, so the second one wasn't showing up. That's why I thought my code was not working.)

For the updated question "How to handle multiple Highlighter" the answer is also simple. There is just one catch to the whole thing one has to know. The paint from the Highlighter does not get over-painted by an other Highlighter which is called later. So basically I had to invert the order of how the Highlighter were called. Which looks something like this:

textPane.getHighlighter().removeAllHighlights();
textPane.getHighlighter().addHighlight(pos, pos + searchWordLength, red);
for int i = 0; i < matchList.size(); i++){
    int position = matchList.get(i);
    textPane.getHighlighter().addHighlight(position, possition + searchWordLength, grey);
}

This is not a optimal solution since you have to repaint every Highlighter with every change. But one could optimize this with an delta-change-draw (only redraw the parts that change).

Haeri
  • 621
  • 1
  • 12
  • 27