Reputation: 51
I am trying to implement user selectable text alignment in an html document produced in Java. I have tried:
JMenuItem leftAlignMenuItem =
new JMenuItem(new StyledEditorKit.AlignmentAction("Left Align", StyleConstants.ALIGN_LEFT));
JMenuItem centerMenuItem =
new JMenuItem(new StyledEditorKit.AlignmentAction("Center", StyleConstants.ALIGN_CENTER));
JMenuItem rightAlignMenuItem =
new JMenuItem(new StyledEditorKit.AlignmentAction("Right Align", StyleConstants.ALIGN_RIGHT));
and various variations on this theme. Selecting the menu items causes the text to align correctly in the text pane, and adds the appropriate html tag to the document that is saved. The problem is, once the tag is added, clicking another align menu item doesn't change it, so it is not possible to change the text alignment from the default (left) more than once and save the change.
I know I'm not the first person to have this problem but I haven't found any solutions so far, so any help would be most appreciated.
Here is my "M"CVE, which unfortunately is still massive, but I can't remove any more code or it will not demonstrate the problem:
package aligntest;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.JFrame;
public class AlignTest extends JFrame implements ActionListener {
private HTMLDocument doc; // Stores the formatted text.
private JTextPane textPane = new JTextPane(); // The Pane itself.
String FilePath = ""; // Stores the file path.
public AlignTest() { // This method is called automatically when the app is launched.
HTMLEditorKit editorKit = new HTMLEditorKit();
doc = (HTMLDocument)editorKit.createDefaultDocument();
init(); // Calls interface method below.
}
public static void main(String[] args) {
AlignTest editor = new AlignTest();
}
public void init(){
JMenuBar menuBar = new JMenuBar();
getContentPane().add(menuBar, BorderLayout.NORTH);
JMenu fileMenu = new JMenu("File");
JMenu alignMenu = new JMenu("Text Align");
menuBar.add(fileMenu);
menuBar.add(alignMenu);
JMenuItem openItem = new JMenuItem("Open"); //
JMenuItem saveItem = new JMenuItem("Save"); //
openItem.addActionListener(this);
saveItem.addActionListener(this);
fileMenu.add(openItem);
fileMenu.add(saveItem);
JMenuItem leftAlignMenuItem = new JMenuItem(new StyledEditorKit.AlignmentAction("Left Align", StyleConstants.ALIGN_LEFT));
JMenuItem centerMenuItem = new JMenuItem(new StyledEditorKit.AlignmentAction("Center", StyleConstants.ALIGN_CENTER));
JMenuItem rightAlignMenuItem = new JMenuItem(new StyledEditorKit.AlignmentAction("Right Align", StyleConstants.ALIGN_RIGHT));
leftAlignMenuItem.setText("Left");
centerMenuItem.setText("Center");
rightAlignMenuItem.setText("Right");
alignMenu.add(leftAlignMenuItem);
alignMenu.add(centerMenuItem);
alignMenu.add(rightAlignMenuItem);
textPane = new JTextPane(doc); // Create object from doc and set this as value of textPane.
textPane.setContentType("text/html"); // textPane holds html.
JScrollPane scrollPane = new JScrollPane(textPane); // textPane in JScrollPane to allow scrolling if more text than space.
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // Get screen size to use below.
Dimension scrollPaneSize = new Dimension(1*screenSize.width/2,1*screenSize.height/2); // Together with next line, sets dimensions of textPane relative to screen size.
scrollPane.setPreferredSize(scrollPaneSize);
getContentPane().add(scrollPane, BorderLayout.SOUTH);
pack();
setLocationRelativeTo(null);
show(); // Actually displays the interface.
}
public void actionPerformed(ActionEvent ae) { // Method called with action commands from interface objects above. Which action depends on the text of the interface element.
String actionCommand = ae.getActionCommand();
if (actionCommand.compareTo("Open") == 0){ // Calls method when action command received.
openDocument();
} else if (actionCommand.compareTo("Save") == 0){
saveDocument();
}
}
public void saveDocument(){
String FP = FilePath; // This paragraph calls Save As instead of Save if file not already saved.
String unsaved = "";
int saved = FP.compareTo(unsaved);
if (saved == 0) {
saveDocumentAs();
} else {
save();
}
}
public void saveDocumentAs(){
JFileChooser SaveDialog = new javax.swing.JFileChooser();
int returnVal = SaveDialog.showSaveDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
java.io.File saved_file = SaveDialog.getSelectedFile();
FilePath = saved_file.toString();
save();
}
}
public void save(){
try {
WriteFile objPane = new WriteFile(FilePath, false);
String PaneText = textPane.getText(); // Gets text from Title Pane.
objPane.writeToFile(PaneText);
}
catch (Exception ex) {
}
}
public void openDocument(){
JFileChooser OpenDialog = new javax.swing.JFileChooser(); // Creates file chooser object.
int returnVal = OpenDialog.showOpenDialog(this); // Defines 'returnVal' according to what user clicks in file chooser.
if (returnVal == JFileChooser.APPROVE_OPTION) { // Returns value depending on whether user clicks 'yes' or 'OK' etc.
java.io.File file = OpenDialog.getSelectedFile(); // Gets path of selected file.
FilePath = file.toString( ); // Converts path of selected file to String.
// The problem seems to be related to the code that starts here...
try {
ReadFile readPane = new ReadFile(FilePath); // Creates "readPane" object from "FilePath" string, using my ReadFile class.
String[] aryPane = readPane.OpenFile(); // Creates string array "aryPane" from "readPane" object.
int i; // Creates integer variable "i".
String PaneText = "";
for (i=0; i < aryPane.length; i++) { // Creates a for loop with starting "i" value of 0, adding 1 to i each time round and ending when i = the number of lines in the aryLines array.
PaneText = PaneText + aryPane[i]; // Add present line to "PaneText".
}
textPane.setText(PaneText); // Displays "PaneText" in "TextPane".
} catch (Exception ex) {
// and ends here. This code also calls ReadFile, so code in that class may be at fault.
}
}
}
}
It also has to call methods in the following two classes in order to work:
package aligntest;
import java.io.IOException;
import java.io.FileReader;
import java.io.BufferedReader;
public class ReadFile {
private String path;
public ReadFile(String file_path) {
path = file_path;
}
public String[] OpenFile() throws IOException {
FileReader fr = new FileReader(path);
BufferedReader textReader = new BufferedReader(fr);
int numberOfLines = readLines( );
String[] textData = new String[numberOfLines];
int i;
for (i=0; i < numberOfLines; i++) {
textData[i] = textReader.readLine();
}
textReader.close( );
return textData;
}
int readLines() throws IOException {
FileReader file_to_read = new FileReader(path);
BufferedReader bf = new BufferedReader(file_to_read);
String aLine;
int numberOfLines = 0;
while ((aLine = bf.readLine()) != null) {
numberOfLines++;
}
bf.close();
return numberOfLines;
}
}
&
package aligntest;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;
public class WriteFile {
private String path;
private boolean append_to_file = false;
public WriteFile(String file_path) {
path = file_path;
}
public WriteFile(String file_path, boolean append_value) {
path = file_path;
}
public WriteFile(File SectionPath, boolean success) {
throw new UnsupportedOperationException("Not yet implemented");
}
public void writeToFile( String textLine ) throws IOException {
FileWriter write = new FileWriter(path, append_to_file);
PrintWriter print_line = new PrintWriter(write);
print_line.printf( "%s" + "%n" , textLine);
print_line.close();
}
}
The problem appears to be related to opening the document (lines 126 - 138), or the 'ReadFile' class: when viewing the saved file in another program I can see that the tags are changing until the document is closed and then opened again with 'AlignTest'. After this any alignment changes are not reflected in the html.
Hoping someone can help.
Edit: Here is some html produced by 'AlignTest'. If this is pasted into a text file and then opened in 'AlignTest' it should reproduce the problem: 'AlignTest' is unable to change the align tag.
<html>
<head>
<meta id="_moz_html_fragment">
</head>
<body>
<p align="right" style="margin-top: 0pt">
Another
</p>
</body>
</html>
Upvotes: 1
Views: 992
Reputation: 127
I've tried to implement the solution suggested by @user1803551 but like I've said in my comment above, I could not find a way to use removeAttribute() on the read only paragraph AttributeSet.
I've implemented a different version of the suggested solution, cloning all the paragraph's AttributeSet except the alignment related attributes. Then I override the current attributes, using setParagraphAttributes with replace=true and I apply the requested modification, using setParagraphAttributes with replace=false. And it seems to work pretty well.
public class ExtendedHTMLDocument extends HTMLDocument {
@Override
public void setParagraphAttributes(int offset, int length, AttributeSet attr, boolean replace) {
AttributeSet paragraphAttributes = this.getParagraphElement(offset).getAttributes();
MutableAttributeSet to = new SimpleAttributeSet();
Enumeration<?> keys = paragraphAttributes.getAttributeNames();
String value = "";
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
if (key instanceof CSS.Attribute) {
if (!key.equals(CSS.Attribute.TEXT_ALIGN)) {
value = value + " " + key + "=" + paragraphAttributes.getAttribute(key) + ";";
}
}
else {
if (!key.equals(HTML.Attribute.ALIGN)) {
to.addAttribute(key, paragraphAttributes.getAttribute(key));
}
}
}
if (value.length() > 0) {
to.addAttribute(HTML.Attribute.STYLE, value);
}
super.setParagraphAttributes(offset, length, to, true);
super.setParagraphAttributes(offset, length, attr, replace);
}
}
Upvotes: 0
Reputation: 13427
This proved to be more difficult then I had thought. Let me explain what happens behind the scenes and then I'll give a few scenarios.
AlignmentAction
's action calls the document's setParagraphAttributes
with the boolean replace
set to false
. In setParagraphAttributes
, the given attribute (Alignment.XXX
) is added to the current list of attributes of the paragraph tag via MutableAttributeSet.addAttributes
. Here is the effect:
align="xxx"
is added. The file is saved with this new attribute.text-align=xxx
. The file is saved only with the HTML attribute (the CSS attribute is discarded, I don't know why, could be that "
needs to be replaced with '
).The summary is that, for some reason, only the HTML attribute can be saved regardless of which attributes exist in runtime. Since it is not modified, it has to be removed first, and then the new attribute added. It could be that a different writer needs to be used.
One attempt at a solution would be creating your own alignment action and setting the replace value to true
. The problem is that it replaces the whole paragraph element:
<html>
<head>
<meta id="_moz_html_fragment">
</head>
<body>
<body align="center">
Another
</body>
</body>
</html>
What you need to do is access the element and replace the attribute "manually". Create a class extending HTMLDocument
and @override
its setParagraphAttributes
so that it contains the line
// attr is the current attribute set of the paragraph element
attr.removeAttribute(HTML.Attribute.ALIGN); // remove the HTML attribute
before
attr.addAttributes(s); // s is the given attributes containing the Alignment.XXX style.
Then saving the file will keep the right alignment as per the above 1-4 scenarios.
Eventually you will want to use an HTML parser, like jsoup; just Google for Java HTML parsers. See also Which HTML Parser is the best?
Upvotes: 1
Reputation: 51
Here is what I have come up with to change text alignment in a JTextPane (may also work in other Swing components):
public void alignLeft() {
String text = textPane.getText();
text = text.replace("<p style=\"margin-top: 0\">", "<p align=left style=\"margin-top: 0\">");
text = text.replace("align=\"center\"", "align=\"left\"");
text = text.replace("align=\"right\"", "align=\"left\"");
textPane.setText(text);
and equivalents for center and right align.
If anyone else is thinking about using please be aware:
Upvotes: 0