Prohlížeč-editor strukturovaných souborů
Úlohou je vytvořit jednoduchý editor ve swingu, který bude umožňovat editaci textových souborů
obsahující strukturu nadpisů ve WIKI formátu.
Struktura souboru
Soubor se bude skládat z obyčejného textu a WIKI-like nadpisů první a druhé úrovně.
Nadpisy jsou formátovány jako řádky souboru, které mají následující formát
<bílé znaky>=<Text nadpisu>=<bílé znaky>
o V případě nadpisu druhé úrovně jsou tam znaky rovná se (=) dva.
Layout editoru
Editor se bude skládat z tree view (JTree), textového pole (JTextArea) a hlavního menu.
Tree view bude od textového pole oddělen posuvným sliderem (JSplitPane).
o Textové pole i tree view budou scrollovatelné.
Hlavní menu bude obsahovat submenu File, které bude obsahovat následující položky:
o New – uzavře právě otevřený soubor a umožní editaci nového souboru (samotný
soubor vznikne až po případném uložení).
o Open – otevře soubor pomocí standardního dialogu.
o Save – uloží otevřený soubor nebo vytvoří nový pomocí standardního dialogu.
o Exit – ukončí editor
Na začátku je editor v módu vytváření nového souboru.
Poznámka: při hodnocení má přednost správnost editace souboru před funkcemi open/save
Editace souboru
Při editaci se v tee view zobrazuje aktuální struktura nadpisů v dokumentu.
Struktura se upravuje okamžitě přímo při psaní.
o JTextArea reprezentuje vlastní text třídou Document (getDocument()), která
umožňuje například reagovat na změny textu apod.
o JTextArea má kurzor (jeho pozici, vzhled atd.) reprezentován pod jménem Caret
(existuje také přímo třída Caret, se ketrou je také možno manipulovat)
Tree view má jako kořen uzel s textem „Document“
Pokud text začímá nadpisem druhé úrovně, tree view k němu zobrazí položku první úrovně
s popisem „<Undefined>“
Ostatní uzly tree view obsahují text nadpisu (text mezi znaky =)
Všechny nově vytvořené nadpisy první i druhé úrovně budou v tree view vyditelné (tree view
bude rozbalený).
Po kliknutí na danou položku v tree view se přehodí fokus na editační okno s textem a kurzor
bude na pozici kde začíná text odpovídajícího nadpisu (nikoli začátek odpovídajícího řádku ale
přímo text mezi znaky =)
Není kladen důraz na efektivitu implementace.
Použití JTree:
Jde o klasický view v klasickém MVC patternu (neboli pouze zobrazuje data, neobsahuje však
tyto data sám o sobě).
Jako zdroj dat používa instanci implementující rozhraní TreeModel (my budeme používat
třídu DefaultTreeModel).
Jednotlivé prvky modelu tvoří klasickou stromovou strukturu. Prvky musí implementovat
interface TreeNode (my budeme používat DefaultMutableTreeNode, který umožňuje
uložit uživatelská data ve formě Object reference a umožňuje k nim snadno přistupovat).
JTree využívá pro správu selekce tzv. selection model (getSelectionModel()), který
umožňuje mimo jiné reagovat na události změny selekce v JTree. Také umožňuje nastavit,
kolik prvků se dá označit (my budeme používat mód
TreeSelectionModel.SINGLE_TREE_SELECTION).
Další užitečné funkce:
o JTree.expandPath(...)
o DefaultTreeModel.reload()
o DefaultTreeModel(TreeNode root)
Kód: Vybrat vše
package zap;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
public class MyFrame
{
private JFrame frame = new JFrame( "wiki");
private JSplitPane pane;
private JTree tree = new JTree();
private JTextArea text = new JTextArea();
private JMenuBar bar = new JMenuBar();
private JMenu menuFile = new JMenu("File");
private JMenuItem menuFileNew = new JMenuItem("New");
private JMenuItem menuFileOpen = new JMenuItem("Open");
private JMenuItem menuFileSave = new JMenuItem("Save");
private JMenuItem menuFileClose = new JMenuItem("Close");
public MyFrame()
{
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE);
menuFileNew.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
text.setText("");
}
});
menuFileClose.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
menuFileOpen.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
JFileChooser chooser = new JFileChooser();
int val = chooser.showOpenDialog(null);
if (val == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
new MyReader( file.getPath());
}
}
});
menuFileSave.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
JFileChooser chooser = new JFileChooser();
int val = chooser.showSaveDialog(null);
if (val == JFileChooser.APPROVE_OPTION) {
File file = chooser.getSelectedFile();
new MyWriter( file.getPath());
}
}
});
menuFile.add( menuFileNew);
menuFile.add( menuFileOpen);
menuFile.add( menuFileSave);
menuFile.add( menuFileClose);
bar.add( menuFile);
frame.setJMenuBar( bar);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.addTreeSelectionListener( new MyTreeSelectionListener());
text.getDocument().addDocumentListener(new MyDocumentListener());
pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(tree), new JScrollPane(text));
pane.setDividerLocation(150);
frame.add( pane);
makeTree();
makeText();
frame.pack();
frame.setVisible( true);
}
private void makeTree()
{
tree.clearSelection();
DefaultMutableTreeNode root = new DefaultMutableTreeNode(new NodeData(0, "Document", 0));
DefaultMutableTreeNode lvl = null;
DefaultTreeModel model = new DefaultTreeModel( root);
tree.setModel( model);
String[] lines = text.getText().split("\
");
String tmp;
DefaultMutableTreeNode node;
for (int i = 0; i < lines.length; i++) {
if ( lines[i].matches("[ \t]*==[A-Za-z0-9]+==[ \t]*")) {
tmp = lines[i].split("==")[ 1];
node = new DefaultMutableTreeNode(new NodeData(i, tmp, 2));
root.insert(node, root.getChildCount());
lvl = node;
}
if ( lines[i].matches("[ \t]*=[A-Za-z0-9]+=[ \t]*")) {
tmp = lines[i].split("=")[ 1];
node = new DefaultMutableTreeNode(new NodeData(i, tmp, 1));
if ( lvl == null) {
lvl = new DefaultMutableTreeNode( new NodeData(i, "<Undefined>", 0));
root.insert(lvl, root.getChildCount());
}
lvl.insert(node, lvl.getChildCount());
}
}
for (int i = 0; i < tree.getRowCount(); i++)
tree.expandRow(i);
}
private void makeText()
{
text.append("bla bla bla
");
text.append("bla bla bla
");
text.append("
");
text.append("=nadpis=
");
text.append("bla bla bla
");
text.append("bla bla bla
");
text.append("
");
text.append("==nadpis==
");
text.append("=nadpis=
");
text.append("bla bla bla
");
text.append("bla bla bla
");
text.append("
");
text.append("=nadpis=
");
text.append("=nadpis=
");
text.append("bla bla bla
");
text.append("bla bla bla
");
text.append("
");
}
private class NodeData
{
private int line;
private String text;
private int off;
public NodeData(int line, String text, int off)
{
this.line = line;
this.text = text;
this.off = off;
}
public int getLine()
{
return line;
}
public String getText()
{
return text;
}
public int getOff()
{
return off;
}
@Override
public String toString()
{
return text;
}
}
private class MyTreeSelectionListener implements TreeSelectionListener
{
public void valueChanged(TreeSelectionEvent e)
{
int line = 0;
int off = 0;
try {
TreePath path = tree.getSelectionPath();
if ( path == null || path.getLastPathComponent() == null)
return;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent( );
line = ((NodeData) node.getUserObject()).getLine();
off = ((NodeData) node.getUserObject()).getOff();
if ( line == 0) line--;
text.setCaretPosition(text.getLineStartOffset(line)+off);
text.requestFocus();
} catch (BadLocationException ex) {
text.setCaretPosition(0);
text.requestFocus();
}
}
}
private class MyDocumentListener implements DocumentListener
{
public void insertUpdate(DocumentEvent e)
{
makeTree();
}
public void removeUpdate(DocumentEvent e)
{
makeTree();
}
public void changedUpdate(DocumentEvent e)
{
}
}
public class MyReader
{
public MyReader(String file)
{
try {
FileInputStream fstream = new FileInputStream( file);
DataInputStream in = new DataInputStream( fstream);
BufferedReader br = new BufferedReader( new InputStreamReader( in));
String strLine;
StringBuilder sb = new StringBuilder();
while ( (strLine = br.readLine()) != null) {
sb.append(strLine+"
");
}
text.setText(sb.toString());
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class MyWriter
{
public MyWriter(String file)
{
try {
FileWriter fstream = new FileWriter( file);
BufferedWriter out = new BufferedWriter( fstream);
out.write(text.getText());
out.close();
} catch ( Exception e) {
e.printStackTrace();
}
}
}
}