Swing StyledDoc - setCharAttributes

diggaa1984

Top Contributor
hallo,

ich verzweifel langsam echt, da ich den Fehler nicht finden kann, ich vermute mir fehlt da Verständnis über die interne Verarbeitung, aber ich will nich ausschließen das mein Algo falsch ist :)

Also das Problem ist, dass das Highlighting nicht ganz funktioniert. Korrekt wäre, wenn im afterRemove.png der komplette String unterstrichen ist. Ich entferne den einen Buchstaben per Backspace und dann wird durch meine Klasse bisher das komplette Highlighten des gesamten Textes im Document erzwungen, damit ich falsche Indexangaben als Fehler erstmal ausschliessen kann.
Die Methode zum Highlighten ist wie folgt:
Java:
	private void highlightString(int startOffset, int endOffset) throws BadLocationException {
		String str = this.getText(startOffset, endOffset-startOffset);		
		List<Token> tokenList = GfeAccessor.getInstance().getScanner().scan(str);
		HashSet<String> toHighlight;
		int suboffs = 0;
		int begin = 0;
		//TODO: remove
		SimpleLog.print(tokenList);
		
		//highlight each token data
		for(Token t: tokenList) {
			begin = str.indexOf(t.data,suboffs);
			
			switch (t.type) {
				case LINECOMMENT: //fall through
				case MULTILINECOMMENT: {
						super.setCharacterAttributes(begin+startOffset, 
								t.data.length(), 
								context.getStyle(tokenToStyle.get(t.type.name()).getLabel()).copyAttributes(), 
								true);
						multilineCommentOffset.put(begin+startOffset, begin+startOffset+t.data.length());
						break;
					}
				case UNKNOWN:
					if (showErrors)
						super.setCharacterAttributes(begin+startOffset, t.data.length(), errorAttributeSet.copyAttributes(), true);
					
					break;
				case INT: //fall through
				case FLOAT: //fall through
				case MACRO: //fall through
					super.setCharacterAttributes(begin+startOffset, 
							 t.data.length(), 
							 context.getStyle(tokenToStyle.get(t.type.name()).getLabel()).copyAttributes(), true);
					break;
				case TERMINAL: //fall through
				case PLACE:
						if ((toHighlight = table.get(tokenToStyle.get(t.type.name()))) != null) {
							if (toHighlight.contains(t.data)) {
		    					super.setCharacterAttributes(begin+startOffset, 
		    												 t.data.length(), 
		    												 context.getStyle(tokenToStyle.get(t.type.name()).getLabel()).copyAttributes(), 
		    												 true);
		    					break;
							}//if
						}//if
				default: 
					super.setCharacterAttributes(begin+startOffset, t.data.length(), defaultAttributeSet.copyAttributes(), true);
			}//switch
			
			suboffs = begin + t.data.length();
		}//for
	}//highlightString

Die Aufruf von
Code:
SimpleLog.print
ist das was unten im Editor zu sehen ist, wo ich auch markiert habe. Das sind sozusagen die Tokentypen samt Inhalt aus dem Scanner. Der Scanner arbeitet 100% korrekt, das heisst die Tokentypen werden immer korrekt erkannt und der Inhalt ist auch entsprechend korrekt, da fehlen keine Zeichen oder dergleichen. Der Fehler muss also im Frontend liegen.
Die Ausgabe:
Code:
UNKNOWN: aaaaaaa
bedeutet also, der Scanner hat genau eine Zeichenkette gefunden, welche keinem gültigen Typ zugeordnet werden konnte, daher UNKNOWN. Alle diese unbekannten Token sollen im Frontend rot unterstrichen werden, damit der User gleich die optische Rückmeldung vom Backend hat, das definitiv an einer Formel was nicht stimmt. Die Manipulation von Zeichen wird nur in der obigen Methode vorgenommen, sie allein bestimmt den Style der Zeichen im Editor.

die 2 wesentlichen AttributSets sind wie folgt definiert:
Java:
defaultAttributeSet = (MutableAttributeSet) context.getStyle(EditorStyleModel.StyleName.DEFAULT.getLabel()).copyAttributes();
defaultAttributeSet.addAttribute(ErrorUnderlinedEditorKit.ERROR, false);

errorAttributeSet = (MutableAttributeSet) defaultAttributeSet.copyAttributes();
errorAttributeSet.addAttribute(ErrorUnderlinedEditorKit.ERROR, true);
das ERROR-Attribute legt fest ob das Zeichen unterstrichen wird oder nicht.

code dafür dankenderweise vor ewiger zeit von Ebenius erhalten :)
Java:
public class ErrorUnderlinedEditorKit extends StyledEditorKit {

<..>

@Override
	public ViewFactory getViewFactory() {
		final ViewFactory viewFactory = super.getViewFactory();
			
		return new ViewFactory() {
	 
			public View create(Element elem) {
				final String kind = elem.getName();
				
				if (AbstractDocument.ContentElementName.equals(kind)) {
					return new LabelView(elem) {
						private boolean error = false;
						
						
					    public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
					        super.changedUpdate(e, a, f);
					        if (this.getAttributes().containsAttribute(ERROR, true)) {
					        	error = true;
					        } else {
					        	error = false;
					        }//if
					    }//if
						
					    
						@Override
						public void paint(Graphics g, Shape a) {
							super.paint(g, a);
							if (error) {
								Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a.getBounds();
							
								g.setColor(Color.RED);
								g.drawLine(alloc.x, alloc.y + alloc.height - 2, alloc.x + alloc.width, alloc.y + alloc.height - 2);
							}//if
						}//paint
					};
				}//if
				
				return viewFactory.create(elem);
			}//create
		};
	}//getViewFactory
ich vermute mal es liegt nicht daran, gebe ihn aber doch mal mit an.
Tja ich weiss echt nicht warum der eine Buchstabe, welcher direkt hinter dem entfernten steht IMMER wieder den roten Strich verliert. Zu 100% reproduzierbar. Wenn ich dann weiterschreibe, wird der komplette String wieder komplett unterstrichen. :eek:

Hat vielleicht jemand eine Idee?
 

Ebenius

Top Contributor
code dafür dankenderweise vor ewiger zeit von Ebenius erhalten :)
[...]
ich vermute mal es liegt nicht daran, gebe ihn aber doch mal mit an.
Ich erinnere mich und kann sagen, dass ich den Code nicht unbedingt wochenlang getestet hatte. ;-)

Häng Dir doch mal einen DocumentListener an das Dokument und frag bei jeder Änderung die Attribute jedes einzelnen Characters ab. Dann weißt Du genau, ob die Attributsätze falsch sind, oder ob die ViewFactory falsch tickt. Verständlich?

Ebenius
 

diggaa1984

Top Contributor
ja schon verständlich .... wird aber ne hässliche arbeit :) ... und selbst wenn da error-attribut tatsache fehlen sollte .. warum - wenn ich doch explizit jedesmal das entsprechende Set verwende? :D das will mir ja nich einleuchten

ausserdem ist dein code ja schon recht übersichtlich und kurz

mal schaun
 

diggaa1984

Top Contributor
also beim schrittweise debuggen war folgendes:

  • postRemove wird aufgerufen
  • super-aufruf wird durchlaufen (kein gui-update)
  • highlightString wird aufgerufen
  • switch wird abgearbeitet
  • beenden von highlight (kein gui-update bisher .. nach wie vor alle zeichen da, auch intern)
  • postRemove kehrt zurück und wird beendet
  • intern verschwindet nun das entfernte zeichen und das nachfolgende wird als neues Element angelegt
  • neue LabelView wird erzeugt, und bekommt initial error = false .. highlightString erwischt also nicht dieses neue Element, da es noch nicht existiert hat

kann man das updaten der GUI zwischen super.postRemove und highlightString erzwingen? Ich denke mal, die Events liegen in der Queue und werden erst nach Abarbeitung der Methoden ausgeführt .. das müsste man explizit anwerfen !?

so gesehen hat sich meine vermutung bestätigt, das es am internen ablauf liegt ^^
 
Zuletzt bearbeitet:

Ebenius

Top Contributor
Ehrlich gesagt steige ich in Deinem Code da leider nicht durch. Ich kann so ein Problem aber bei keinem meiner ViewFactory-Beispiele nachvollziehen. :-(

Kannst Du nicht mal ein KSKB so in dieser Art bauen:
Java:
/* (@)TextNoteFun.java */

/* Copyright 2009 Sebastian Haufe

 * Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       [url]http://www.apache.org/licenses/LICENSE-2.0[/url]

 * Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

package com.ebenius;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Random;

import javax.swing.*;
import javax.swing.text.*;

public class TextNoteFun {

  // -------------------------------------------------------------------------
  // Editor kit, providing the view factory
  // -------------------------------------------------------------------------

  static class NoteSupportingEditorKit extends StyledEditorKit {

    private static final long serialVersionUID = 1L;
    private final ViewFactory viewFactory = new NoteMarkupViewFactory();

    @Override
    public ViewFactory getViewFactory() {
      return viewFactory;
    }
  }

  // -------------------------------------------------------------------------
  // Label view, marking up text with notes
  // -------------------------------------------------------------------------

  static class NoteMarkupLabelView extends LabelView {

    private static final Stroke stroke =
          new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1,
                new float[] { 5.f, 2.f }, 2.5f);

    NoteMarkupLabelView(Element elem) {
      super(elem);
    }

    private boolean hasNote() {
      return null != getAttributes().getAttribute("textNote");
    }

    @Override
    public String getToolTipText(float x, float y, Shape allocation) {
      final Object text = getAttributes().getAttribute("textNote");
      return text == null ? null : text.toString();
    }

    @Override
    public void paint(Graphics g, Shape allocation) {
      super.paint(g, allocation);
      if (hasNote()) {
        final Stroke oldStroke = ((Graphics2D) g).getStroke();
        try {
          ((Graphics2D) g).setStroke(stroke);
          g.setColor(Color.RED);
          final Rectangle b = allocation.getBounds();
          final int y = b.y + b.height - 2;
          g.drawLine(b.x, y, b.x + b.width - 1, y);
        } finally {
          ((Graphics2D) g).setStroke(oldStroke);
        }
      }
    }
  }

  // -------------------------------------------------------------------------
  // View Factory creating my own views instead of standard label views
  // -------------------------------------------------------------------------

  static class NoteMarkupViewFactory implements ViewFactory {

    // The code has been copied from StyledEditorKit.StyledViewFactory, only
    // LabelView has been replaced by NoteMarkupLabelView
    public View create(Element elem) {
      String kind = elem.getName();
      if (kind != null) {
        if (kind.equals(AbstractDocument.ContentElementName)) {
          return new NoteMarkupLabelView(elem);
        } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
          return new ParagraphView(elem);
        } else if (kind.equals(AbstractDocument.SectionElementName)) {
          return new BoxView(elem, View.Y_AXIS);
        } else if (kind.equals(StyleConstants.ComponentElementName)) {
          return new ComponentView(elem);
        } else if (kind.equals(StyleConstants.IconElementName)) {
          return new IconView(elem);
        }
      }

      // default to text display
      return new NoteMarkupLabelView(elem);
    }
  }

  // -------------------------------------------------------------------------
  // Program Entry Point
  // -------------------------------------------------------------------------

  /**
   * Test main method.
   * 
   * @param args ignored
   */
  public static void main(String[] args) {

    // create a document with random content
    final StyledDocument document = new DefaultStyledDocument();
    try {
      document.insertString(0, createRandomString(1024, 78), null);
    } catch (BadLocationException ex) {
      // location 0 is not bad!
    }

    // create a text pane with my editor kit, document, size and tool tip
    final JTextPane textPane = new JTextPane();
    textPane.setEditorKit(new NoteSupportingEditorKit());

    textPane.setStyledDocument(document);
    textPane.setPreferredSize(new Dimension(400, 400));
    ToolTipManager.sharedInstance().registerComponent(textPane);

    // Action to add a note to the selected text
    final AbstractAction addNoteAction = new AbstractAction("Add Note") {

      private static final long serialVersionUID = 1L;

      public void actionPerformed(ActionEvent e) {
        final int selStart = textPane.getSelectionStart();
        final int selEnd = textPane.getSelectionEnd();
        if (selStart < selEnd) {
          final String noteText =
                JOptionPane.showInputDialog("Type your note:");
          if (noteText != null) {
            final SimpleAttributeSet aset = new SimpleAttributeSet();
            aset.addAttribute("textNote", noteText);
            document.setCharacterAttributes(selStart, selEnd - selStart,
                  aset, true);
          }
        }
      }
    };

    // standard GUI template
    final JPanel bPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
    bPanel.add(new JButton(addNoteAction));

    final JPanel contentPane = new JPanel(new BorderLayout(6, 6));
    contentPane.add(new JScrollPane(textPane));
    contentPane.add(bPanel, BorderLayout.SOUTH);

    final JFrame f = new JFrame("Test Frame: TextNoteFun"); //$NON-NLS-1$
    f.setContentPane(contentPane);
    f.pack();
    f.setLocationRelativeTo(null);
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.setVisible(true);
  }

  // -------------------------------------------------------------------------
  // Create random string
  // -------------------------------------------------------------------------

  private static final char[] RANDOM_ALPHABET =
        { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
          'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
          'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C',
          'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
          'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ' };

  private static String createRandomString(int len, int maxSpaceDistance) {
    final char[] chars = new char[len];
    final Random rnd = new Random();
    int spaceDistance = 0;
    for (int i = 0; i < chars.length; i++) {
      final char c =
            spaceDistance == maxSpaceDistance ? ' ' : RANDOM_ALPHABET[rnd
                  .nextInt(RANDOM_ALPHABET.length)];
      if (c == ' ') {
        spaceDistance = 0;
      } else {
        spaceDistance++;
      }
      chars[i] = c;
    }
    return new String(chars);
  }
}
Ebenius
 
Zuletzt bearbeitet:

diggaa1984

Top Contributor
also grob gesagt, super.postRemove setzt ein Event ab, das später den char aus dem Document entfernt und Listener benachrichtigt .. bevor dieses Event aber verarbeitet wird, wird highlightString aufgerufen .. das bedeutet, ich highlighte den alten Text, und nicht den wo das Zeichen entfernt wurde.

Beim Entfernen des Zeichens wird dann java-intern eine neue LabelView erstellt, welche initial error = false hat, somit keinen roten Strich besitzt. Da das Highlighting in dem moment schon vorbei ist, bleibt das auch so.

Das Event wird erst verarbeitet wenn die postRemove-Methode beendet ist .. das ist das eigentliche Problem.

Gibt es eine Variante die Events explizit zu verarbeiten? Das müsste ja helfen, also ich müsste zwischen super.postRemove() .. hier wird Event gesetzt .. und highlightString() das Event erst verarbeiten, damit ich den aktuelleren Text highlighte und nicht den alten.

seh grad, die postRemove hatte ich ja gar net gegeben :D
Java:
@Override
protected void postRemoveUpdate(DefaultDocumentEvent chng) {
	try {
		super.postRemoveUpdate(chng);
		if (table.size() > 0) {
			//TODO: force eventqueue to be processed to correct highlighting
			int[] offsets = getScanOffset(chng.getOffset(),-chng.getLength());
			highlightString(offsets[0],offsets[1]);
		}//if
	} catch (BadLocationException e) {
		e.printStackTrace();
	}//try
}//postRemoveUpdate

lustigerweise steht ja da, dass diese methode erst aufgerufen wird NACHDEM das zeichen entfernt wurde, aber laut debugger ist zu dem zeitpunkt noch alles vorhanden!

ich probier aber gerne mal ein KSKB ^^
 
Zuletzt bearbeitet:

diggaa1984

Top Contributor
klappt erstmal so wies aussieht ... nu seh ich aber grad das beim normalen schreiben per insertString da letzte Zeichen nicht sofort gehighlightet wird ^^ .. aber das ergründe ich noch ... auf deine Lösung hätte ich eigentlich auch kommen müssen, aber ist eher ne Krücke oder?
 

Ebenius

Top Contributor
[...] ist eher ne Krücke oder?
Naja - mir fehlt aufgrund der Komplexität noch immer der Überblick über den Ablauf. Ich kann nicht recht einschätzen, ob das eine Krücke ist oder der richtige Weg. Du schriebst etwas weiter oben einfach davon, dass Du's erst ausführen willst, wenn der Event richtig abgearbeitet ist. [c]invokeLater()[/c] ist pauschal genau der Weg, Dinge auszuführen, wenn alle bisher angefallenen Ereignisse durch sind. Ergo: Maybe Krücke, maybe not. :-D

Ebenius
 

Neue Themen


Oben