Revision 3102
org.gvsig.tools/library/tags/org.gvsig.tools-3.0.376/org.gvsig.tools.swing/org.gvsig.tools.swing.impl/src/test/java/org/gvsig/tools/AppTest.java | ||
---|---|---|
1 |
/** |
|
2 |
* gvSIG. Desktop Geographic Information System. |
|
3 |
* |
|
4 |
* Copyright (C) 2007-2013 gvSIG Association. |
|
5 |
* |
|
6 |
* This program is free software; you can redistribute it and/or |
|
7 |
* modify it under the terms of the GNU General Public License |
|
8 |
* as published by the Free Software Foundation; either version 2 |
|
9 |
* of the License, or (at your option) any later version. |
|
10 |
* |
|
11 |
* This program is distributed in the hope that it will be useful, |
|
12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
* GNU General Public License for more details. |
|
15 |
* |
|
16 |
* You should have received a copy of the GNU General Public License |
|
17 |
* along with this program; if not, write to the Free Software |
|
18 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 |
* MA 02110-1301, USA. |
|
20 |
* |
|
21 |
* For any additional information, do not hesitate to contact us |
|
22 |
* at info AT gvsig.com, or visit our website www.gvsig.com. |
|
23 |
*/ |
|
24 |
package org.gvsig.tools; |
|
25 |
|
|
26 |
import junit.framework.Test; |
|
27 |
import junit.framework.TestCase; |
|
28 |
import junit.framework.TestSuite; |
|
29 |
|
|
30 |
/** |
|
31 |
* Unit test for simple App. |
|
32 |
*/ |
|
33 |
public class AppTest extends TestCase { |
|
34 |
|
|
35 |
/** |
|
36 |
* @return the suite of tests being tested |
|
37 |
*/ |
|
38 |
public static Test suite() { |
|
39 |
return new TestSuite(AppTest.class); |
|
40 |
} |
|
41 |
|
|
42 |
/** |
|
43 |
* Create the test case |
|
44 |
* |
|
45 |
* @param testName |
|
46 |
* name of the test case |
|
47 |
*/ |
|
48 |
public AppTest(String testName) { |
|
49 |
super(testName); |
|
50 |
} |
|
51 |
|
|
52 |
/** |
|
53 |
* Rigourous Test :-) |
|
54 |
*/ |
|
55 |
public void testApp() { |
|
56 |
assertTrue(true); |
|
57 |
} |
|
58 |
} |
org.gvsig.tools/library/tags/org.gvsig.tools-3.0.376/org.gvsig.tools.swing/org.gvsig.tools.swing.impl/src/main/java/org/gvsig/texteditor/AbstractJTextEditor.java | ||
---|---|---|
1 |
package org.gvsig.texteditor; |
|
2 |
|
|
3 |
import java.awt.Container; |
|
4 |
import java.awt.Point; |
|
5 |
import java.awt.Rectangle; |
|
6 |
import java.awt.event.ActionEvent; |
|
7 |
import java.awt.event.ActionListener; |
|
8 |
import javax.swing.JComponent; |
|
9 |
import javax.swing.JPanel; |
|
10 |
import javax.swing.JViewport; |
|
11 |
import javax.swing.SwingUtilities; |
|
12 |
import javax.swing.event.CaretEvent; |
|
13 |
import javax.swing.event.ChangeEvent; |
|
14 |
import javax.swing.event.ChangeListener; |
|
15 |
import javax.swing.event.DocumentEvent; |
|
16 |
import javax.swing.event.DocumentListener; |
|
17 |
import javax.swing.text.BadLocationException; |
|
18 |
import javax.swing.text.Document; |
|
19 |
import javax.swing.text.Element; |
|
20 |
import javax.swing.text.JTextComponent; |
|
21 |
import org.gvsig.tools.swing.api.ChangeListenerHelper; |
|
22 |
import org.gvsig.tools.swing.api.ToolsSwingLocator; |
|
23 |
import org.gvsig.tools.swing.api.viewer.AbstractJViewer; |
|
24 |
import org.slf4j.Logger; |
|
25 |
import org.slf4j.LoggerFactory; |
|
26 |
|
|
27 |
/** |
|
28 |
* |
|
29 |
* @author jjdelcerro |
|
30 |
*/ |
|
31 |
public abstract class AbstractJTextEditor extends AbstractJViewer implements JTextEditor { |
|
32 |
protected static final Logger LOGGER = LoggerFactory.getLogger(DefaultJTextEditor.class); |
|
33 |
|
|
34 |
protected static class UpdateCaretPositionActionEventImpl |
|
35 |
extends ActionEvent |
|
36 |
implements UpdateCaretPositionActionEvent |
|
37 |
{ |
|
38 |
|
|
39 |
private final int line; |
|
40 |
private final int column; |
|
41 |
private final boolean hasLineAndCol; |
|
42 |
|
|
43 |
public UpdateCaretPositionActionEventImpl(Object comp, int line, int column, boolean hasLineAndCol) { |
|
44 |
super(comp, 1, "UpdateCaretPosition"); |
|
45 |
this.line = line; |
|
46 |
this.column = column; |
|
47 |
this.hasLineAndCol = hasLineAndCol; |
|
48 |
} |
|
49 |
|
|
50 |
@Override |
|
51 |
public int getLine() { |
|
52 |
return this.line; |
|
53 |
} |
|
54 |
|
|
55 |
@Override |
|
56 |
public int getColumn() { |
|
57 |
return this.column; |
|
58 |
} |
|
59 |
|
|
60 |
@Override |
|
61 |
public boolean hasLineAndColumn() { |
|
62 |
return this.hasLineAndCol; |
|
63 |
} |
|
64 |
|
|
65 |
} |
|
66 |
|
|
67 |
protected JPanel panel; |
|
68 |
protected ChangeListenerHelper changeListeners; |
|
69 |
protected ActionListener updateCaretPositionListener; |
|
70 |
|
|
71 |
public AbstractJTextEditor(TextEditorManager factory) { |
|
72 |
super(factory); |
|
73 |
this.panel = new JPanel(); |
|
74 |
this.changeListeners = ToolsSwingLocator.getToolsSwingManager().createChangeListenerHelper(); |
|
75 |
|
|
76 |
final JTextComponent textComponent = this.getJTextComponent(); |
|
77 |
final Document textDocument = textComponent.getDocument(); |
|
78 |
textDocument.addDocumentListener(new DocumentListener() { |
|
79 |
@Override |
|
80 |
public void insertUpdate(DocumentEvent e) { |
|
81 |
changeListeners.fireEvent(new ChangeEvent(AbstractJTextEditor.this)); |
|
82 |
} |
|
83 |
|
|
84 |
@Override |
|
85 |
public void removeUpdate(DocumentEvent e) { |
|
86 |
changeListeners.fireEvent(new ChangeEvent(AbstractJTextEditor.this)); |
|
87 |
} |
|
88 |
|
|
89 |
@Override |
|
90 |
public void changedUpdate(DocumentEvent e) { |
|
91 |
changeListeners.fireEvent(new ChangeEvent(AbstractJTextEditor.this)); |
|
92 |
} |
|
93 |
}); |
|
94 |
|
|
95 |
textComponent.addCaretListener((CaretEvent e) -> { |
|
96 |
if (updateCaretPositionListener == null) { |
|
97 |
return; |
|
98 |
} |
|
99 |
int lineNumber = 1; |
|
100 |
int columnNumber = 0; |
|
101 |
int currentPosition = textComponent.getCaretPosition(); |
|
102 |
try { |
|
103 |
String strText = textDocument.getText(0, currentPosition); |
|
104 |
char arrText[] = strText.toCharArray(); |
|
105 |
for (int i = 0; i < strText.length(); i++) { |
|
106 |
if (arrText[i] == '\n') { |
|
107 |
lineNumber++; |
|
108 |
} |
|
109 |
} |
|
110 |
columnNumber = currentPosition - strText.lastIndexOf('\n'); |
|
111 |
} catch (BadLocationException ble) { |
|
112 |
System.err.println(ble); |
|
113 |
} |
|
114 |
updateCaretPositionListener.actionPerformed( |
|
115 |
new UpdateCaretPositionActionEventImpl( |
|
116 |
this, lineNumber, columnNumber, true |
|
117 |
) |
|
118 |
); |
|
119 |
}); |
|
120 |
|
|
121 |
} |
|
122 |
|
|
123 |
@Override |
|
124 |
public JComponent asJComponent() { |
|
125 |
return this.panel; |
|
126 |
} |
|
127 |
|
|
128 |
@Override |
|
129 |
public void addChangeListener(ChangeListener listener) { |
|
130 |
this.changeListeners.addChangeListener(listener); |
|
131 |
} |
|
132 |
|
|
133 |
@Override |
|
134 |
public ChangeListener[] getChangeListeners() { |
|
135 |
return this.changeListeners.getChangeListeners(); |
|
136 |
} |
|
137 |
|
|
138 |
@Override |
|
139 |
public void removeChangeListener(ChangeListener listener) { |
|
140 |
this.changeListeners.removeChangeListener(listener); |
|
141 |
} |
|
142 |
|
|
143 |
@Override |
|
144 |
public void removeAllChangeListener() { |
|
145 |
this.changeListeners.removeAllChangeListener(); |
|
146 |
} |
|
147 |
|
|
148 |
@Override |
|
149 |
public boolean hasChangeListeners() { |
|
150 |
return this.changeListeners.hasChangeListeners(); |
|
151 |
} |
|
152 |
|
|
153 |
@Override |
|
154 |
public void selectLine(int line) { |
|
155 |
JTextComponent editor = this.getJTextComponent(); |
|
156 |
editor.requestFocusInWindow(); |
|
157 |
String code = editor.getText(); |
|
158 |
int lineCounter = 0; |
|
159 |
int initialSelection = 0; |
|
160 |
int finalSelection = 0; |
|
161 |
for (int j = 0; j < code.length(); j++) { |
|
162 |
if (code.charAt(j) == '\n') { |
|
163 |
lineCounter++; |
|
164 |
if (lineCounter == line - 1) { |
|
165 |
initialSelection = j; |
|
166 |
} |
|
167 |
if (lineCounter == line || (finalSelection == 0 && j == code.length() - 1)) { |
|
168 |
finalSelection = j; |
|
169 |
} |
|
170 |
} |
|
171 |
} |
|
172 |
editor.select(initialSelection, finalSelection); |
|
173 |
} |
|
174 |
|
|
175 |
@Override |
|
176 |
public void gotoline(int line) { |
|
177 |
JTextComponent component = getJTextComponent(); |
|
178 |
Element root = component.getDocument().getDefaultRootElement(); |
|
179 |
int lineno = Math.max(line, 1); |
|
180 |
int maxlines = root.getElementCount(); |
|
181 |
lineno = Math.min(lineno, maxlines); |
|
182 |
int startOfLineOffset = root.getElement(lineno - 1).getStartOffset(); |
|
183 |
component.setCaretPosition(startOfLineOffset); |
|
184 |
Container container = SwingUtilities.getAncestorOfClass(JViewport.class, component); |
|
185 |
if (container == null) { |
|
186 |
return; |
|
187 |
} |
|
188 |
int y = -1; |
|
189 |
try { |
|
190 |
Rectangle r = component.modelToView(component.getCaretPosition()); |
|
191 |
JViewport viewport = (JViewport) container; |
|
192 |
int extentHeight = viewport.getExtentSize().height; |
|
193 |
int viewHeight = viewport.getViewSize().height; |
|
194 |
y = Math.max(0, r.y - ((extentHeight - r.height) / 2)); |
|
195 |
y = Math.min(y, viewHeight - extentHeight); |
|
196 |
viewport.setViewPosition(new Point(0, y)); |
|
197 |
} catch (BadLocationException ble) { |
|
198 |
LOGGER.warn("Can't set view position (y=" + y + ").", ble); |
|
199 |
} |
|
200 |
try { |
|
201 |
component.setCaretPosition(startOfLineOffset + 1); |
|
202 |
} catch (Exception e) { |
|
203 |
LOGGER.warn("Can't set caret position (position=" + (startOfLineOffset + 1) + ").", e); |
|
204 |
} |
|
205 |
} |
|
206 |
|
|
207 |
@Override |
|
208 |
public int getLineCount() { |
|
209 |
JTextComponent textComponent = this.getJTextComponent(); |
|
210 |
Element map = textComponent.getDocument().getDefaultRootElement(); |
|
211 |
return map.getElementCount(); |
|
212 |
} |
|
213 |
|
|
214 |
@Override |
|
215 |
public void addUpdateCaretPositionActionListener(ActionListener listener) { |
|
216 |
this.updateCaretPositionListener = listener; |
|
217 |
} |
|
218 |
|
|
219 |
@Override |
|
220 |
public boolean isModified() { |
|
221 |
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. |
|
222 |
} |
|
223 |
|
|
224 |
} |
org.gvsig.tools/library/tags/org.gvsig.tools-3.0.376/org.gvsig.tools.swing/org.gvsig.tools.swing.impl/src/main/java/org/gvsig/texteditor/DefaultJTextEditor.java | ||
---|---|---|
1 |
package org.gvsig.texteditor; |
|
2 |
|
|
3 |
import java.awt.BorderLayout; |
|
4 |
import java.awt.Font; |
|
5 |
import java.awt.event.ActionEvent; |
|
6 |
import java.awt.event.ActionListener; |
|
7 |
import java.io.File; |
|
8 |
import java.io.IOException; |
|
9 |
import java.nio.charset.StandardCharsets; |
|
10 |
import java.util.Objects; |
|
11 |
import javax.swing.JFileChooser; |
|
12 |
import javax.swing.JMenu; |
|
13 |
import javax.swing.JMenuBar; |
|
14 |
import javax.swing.JMenuItem; |
|
15 |
import javax.swing.JOptionPane; |
|
16 |
import javax.swing.JScrollPane; |
|
17 |
import javax.swing.JTextArea; |
|
18 |
import javax.swing.event.UndoableEditEvent; |
|
19 |
import javax.swing.text.JTextComponent; |
|
20 |
import javax.swing.undo.CannotUndoException; |
|
21 |
import javax.swing.undo.UndoManager; |
|
22 |
import org.apache.commons.io.FileUtils; |
|
23 |
|
|
24 |
/** |
|
25 |
* |
|
26 |
* @author gvSIG Team |
|
27 |
*/ |
|
28 |
public class DefaultJTextEditor extends AbstractJTextEditor { |
|
29 |
|
|
30 |
private JTextArea textArea; |
|
31 |
|
|
32 |
// Is File Saved/Opened |
|
33 |
private boolean opened = false; |
|
34 |
private boolean saved = false; |
|
35 |
|
|
36 |
private File openedFile; |
|
37 |
|
|
38 |
// Undo manager for managing the storage of the undos |
|
39 |
// so that the can be redone if requested |
|
40 |
private UndoManager undo; |
|
41 |
private String mimeType; |
|
42 |
|
|
43 |
|
|
44 |
@SuppressWarnings("OverridableMethodCallInConstructor") |
|
45 |
public DefaultJTextEditor(TextEditorManager factory) { |
|
46 |
super(factory); |
|
47 |
|
|
48 |
JMenuBar menuBar = new JMenuBar(); |
|
49 |
JMenu fileMenu = new JMenu("File"); |
|
50 |
addMenuItem(fileMenu, "Open...", (ActionEvent e) -> { |
|
51 |
doOpenFile(); |
|
52 |
}); |
|
53 |
addMenuItem(fileMenu, "Save", (ActionEvent e) -> { |
|
54 |
doSaveFile(); |
|
55 |
}); |
|
56 |
addMenuItem(fileMenu, "Save as...", (ActionEvent e) -> { |
|
57 |
doSaveAsFile(); |
|
58 |
}); |
|
59 |
addMenuItem(fileMenu, "Close", (ActionEvent e) -> { |
|
60 |
doClose(); |
|
61 |
}); |
|
62 |
|
|
63 |
JMenu editMenu = new JMenu("Edit"); |
|
64 |
addMenuItem(editMenu, "Undo", (ActionEvent e) -> { |
|
65 |
doUndo(); |
|
66 |
}); |
|
67 |
addMenuItem(editMenu, "Redo", (ActionEvent e) -> { |
|
68 |
doRedo(); |
|
69 |
}); |
|
70 |
addMenuItem(editMenu, "Select all", (ActionEvent e) -> { |
|
71 |
doSelectAll(); |
|
72 |
}); |
|
73 |
addMenuItem(editMenu, "Cut", (ActionEvent e) -> { |
|
74 |
doCut(); |
|
75 |
}); |
|
76 |
addMenuItem(editMenu, "Copy", (ActionEvent e) -> { |
|
77 |
doCopy(); |
|
78 |
}); |
|
79 |
addMenuItem(editMenu, "Paste", (ActionEvent e) -> { |
|
80 |
doPaste(); |
|
81 |
}); |
|
82 |
menuBar.add(fileMenu); |
|
83 |
menuBar.add(editMenu); |
|
84 |
|
|
85 |
undo = new UndoManager(); |
|
86 |
textArea.getDocument().addUndoableEditListener((UndoableEditEvent e) -> { |
|
87 |
undo.addEdit(e.getEdit()); |
|
88 |
}); |
|
89 |
|
|
90 |
this.panel.setLayout(new BorderLayout()); |
|
91 |
this.panel.add(menuBar, BorderLayout.NORTH); |
|
92 |
|
|
93 |
JScrollPane scroll = new JScrollPane( |
|
94 |
getJTextComponent(), |
|
95 |
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, |
|
96 |
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS |
|
97 |
); |
|
98 |
this.panel.add(scroll, BorderLayout.CENTER); |
|
99 |
|
|
100 |
} |
|
101 |
|
|
102 |
private void addMenuItem(JMenu menu, String text, ActionListener listener) { |
|
103 |
JMenuItem item = new JMenuItem(text); |
|
104 |
item.addActionListener(listener); |
|
105 |
item.setEnabled(true); |
|
106 |
menu.add(item); |
|
107 |
} |
|
108 |
|
|
109 |
private void saveFile(File file) { |
|
110 |
try { |
|
111 |
FileUtils.write(file, textArea.getText()); |
|
112 |
saved = true; |
|
113 |
} catch (IOException ex) { |
|
114 |
LOGGER.warn("Can't save file (" + Objects.toString(file) + ")", ex); |
|
115 |
} |
|
116 |
} |
|
117 |
|
|
118 |
private void doOpenFile() { |
|
119 |
JFileChooser open = new JFileChooser(); |
|
120 |
open.showOpenDialog(null); |
|
121 |
File file = open.getSelectedFile(); |
|
122 |
try { |
|
123 |
openedFile = file; |
|
124 |
String s = FileUtils.readFileToString(file); |
|
125 |
textArea.setText(s); |
|
126 |
opened = true; |
|
127 |
} catch (IOException ex) { |
|
128 |
LOGGER.warn("Can't open file (" + Objects.toString(file) + ")", ex); |
|
129 |
} |
|
130 |
} |
|
131 |
|
|
132 |
private void doSaveFile() { |
|
133 |
JFileChooser save = new JFileChooser(); |
|
134 |
File file = save.getSelectedFile(); |
|
135 |
if (opened == false && saved == false) { |
|
136 |
save.showSaveDialog(null); |
|
137 |
int confirmationResult; |
|
138 |
if (file.exists()) { |
|
139 |
confirmationResult = JOptionPane.showConfirmDialog(this.panel, "Replace existing file?"); |
|
140 |
if (confirmationResult == JOptionPane.YES_OPTION) { |
|
141 |
saveFile(file); |
|
142 |
} |
|
143 |
} else { |
|
144 |
saveFile(file); |
|
145 |
} |
|
146 |
} else { |
|
147 |
try { |
|
148 |
FileUtils.write(openedFile, textArea.getText()); |
|
149 |
} catch (IOException ex) { |
|
150 |
LOGGER.warn("Can't save file (" + Objects.toString(file) + ")", ex); |
|
151 |
} |
|
152 |
} |
|
153 |
} |
|
154 |
|
|
155 |
private void doSaveAsFile() { |
|
156 |
JFileChooser saveAs = new JFileChooser(); |
|
157 |
saveAs.showSaveDialog(null); |
|
158 |
File filename = saveAs.getSelectedFile(); |
|
159 |
int confirmationResult; |
|
160 |
if (filename.exists()) { |
|
161 |
confirmationResult = JOptionPane.showConfirmDialog(this.panel, "Replace existing file?"); |
|
162 |
if (confirmationResult == JOptionPane.YES_OPTION) { |
|
163 |
saveFile(filename); |
|
164 |
} |
|
165 |
} else { |
|
166 |
saveFile(filename); |
|
167 |
} |
|
168 |
} |
|
169 |
|
|
170 |
private void doClose() { |
|
171 |
this.panel.setVisible(false); |
|
172 |
} |
|
173 |
|
|
174 |
private void doUndo() { |
|
175 |
try { |
|
176 |
undo.undo(); |
|
177 |
} catch (CannotUndoException ex) { |
|
178 |
LOGGER.warn("Can't undo", ex); |
|
179 |
} |
|
180 |
} |
|
181 |
|
|
182 |
private void doRedo() { |
|
183 |
try { |
|
184 |
undo.redo(); |
|
185 |
} catch (CannotUndoException ex) { |
|
186 |
LOGGER.warn("Can't redo", ex); |
|
187 |
} |
|
188 |
} |
|
189 |
|
|
190 |
private void doSelectAll() { |
|
191 |
textArea.selectAll(); |
|
192 |
} |
|
193 |
|
|
194 |
private void doCopy() { |
|
195 |
textArea.copy(); |
|
196 |
} |
|
197 |
|
|
198 |
private void doPaste() { |
|
199 |
textArea.paste(); |
|
200 |
} |
|
201 |
|
|
202 |
private void doCut() { |
|
203 |
textArea.cut(); |
|
204 |
} |
|
205 |
|
|
206 |
@Override |
|
207 |
public void setMimetype(String mimeType) { |
|
208 |
this.mimeType = mimeType; |
|
209 |
} |
|
210 |
|
|
211 |
@Override |
|
212 |
public String getMimetype() { |
|
213 |
return this.mimeType; |
|
214 |
} |
|
215 |
|
|
216 |
@Override |
|
217 |
public void setContents(Object data) { |
|
218 |
if( data instanceof String ) { |
|
219 |
this.textArea.setText((String) data); |
|
220 |
return; |
|
221 |
} |
|
222 |
if( data instanceof byte[] ) { |
|
223 |
this.textArea.setText(new String((byte[]) data)); |
|
224 |
return; |
|
225 |
} |
|
226 |
super.setContents(data); |
|
227 |
} |
|
228 |
|
|
229 |
@Override |
|
230 |
public byte[] getBytes() { |
|
231 |
return this.textArea.getText().getBytes(StandardCharsets.UTF_8); |
|
232 |
} |
|
233 |
|
|
234 |
@Override |
|
235 |
public String getContents() { |
|
236 |
return this.textArea.getText(); |
|
237 |
} |
|
238 |
|
|
239 |
|
|
240 |
@Override |
|
241 |
public void clean() { |
|
242 |
this.setContents(""); |
|
243 |
} |
|
244 |
|
|
245 |
@Override |
|
246 |
public JTextComponent getJTextComponent() { |
|
247 |
if (this.textArea == null) { |
|
248 |
textArea = new JTextArea(30, 50); |
|
249 |
textArea.setEditable(true); |
|
250 |
|
|
251 |
Font textFont = new Font("Verdana", 0, 14); |
|
252 |
textArea.setFont(textFont); |
|
253 |
|
|
254 |
} |
|
255 |
return this.textArea; |
|
256 |
} |
|
257 |
|
|
258 |
} |
org.gvsig.tools/library/tags/org.gvsig.tools-3.0.376/org.gvsig.tools.swing/org.gvsig.tools.swing.impl/src/main/java/org/gvsig/texteditor/DefaultTextEditorManager.java | ||
---|---|---|
1 |
package org.gvsig.texteditor; |
|
2 |
|
|
3 |
import org.gvsig.tools.swing.api.viewer.AbstractViewerFactory; |
|
4 |
|
|
5 |
|
|
6 |
public class DefaultTextEditorManager extends AbstractViewerFactory implements TextEditorManager { |
|
7 |
|
|
8 |
public DefaultTextEditorManager() { |
|
9 |
super("TextEditor", "Text", "text/plain", true); |
|
10 |
} |
|
11 |
|
|
12 |
@Override |
|
13 |
public JTextEditor createTextEditor() { |
|
14 |
DefaultJTextEditor editor = new DefaultJTextEditor(this); |
|
15 |
return editor; |
|
16 |
} |
|
17 |
|
|
18 |
@Override |
|
19 |
public boolean isApplicable(Object... args) { |
|
20 |
Object data = args[0]; |
|
21 |
return data instanceof String || data instanceof byte[]; |
|
22 |
} |
|
23 |
|
|
24 |
} |
org.gvsig.tools/library/tags/org.gvsig.tools-3.0.376/org.gvsig.tools.swing/org.gvsig.tools.swing.impl/src/main/java/org/gvsig/tools/swing/impl/hexeditor/HexEditorFactory.java | ||
---|---|---|
1 |
/** |
|
2 |
* gvSIG. Desktop Geographic Information System. |
|
3 |
* |
|
4 |
* Copyright (C) 2007-2023 gvSIG Association. |
|
5 |
* |
|
6 |
* This program is free software; you can redistribute it and/or |
|
7 |
* modify it under the terms of the GNU General Public License |
|
8 |
* as published by the Free Software Foundation; either version 3 |
|
9 |
* of the License, or (at your option) any later version. |
|
10 |
* |
|
11 |
* This program is distributed in the hope that it will be useful, |
|
12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
* GNU General Public License for more details. |
|
15 |
* |
|
16 |
* You should have received a copy of the GNU General Public License |
|
17 |
* along with this program; if not, write to the Free Software |
|
18 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
19 |
* MA 02110-1301, USA. |
|
20 |
* |
|
21 |
* For any additional information, do not hesitate to contact us |
|
22 |
* at info AT gvsig.com, or visit our website www.gvsig.com. |
|
23 |
*/ |
|
24 |
package org.gvsig.tools.swing.impl.hexeditor; |
|
25 |
|
|
26 |
import java.io.InputStream; |
|
27 |
import org.gvsig.tools.swing.api.viewer.AbstractViewerFactory; |
|
28 |
import org.gvsig.tools.swing.api.viewer.JViewer; |
|
29 |
|
|
30 |
/** |
|
31 |
* |
|
32 |
* @author jjdelcerro |
|
33 |
* @param <T> |
|
34 |
*/ |
|
35 |
public class HexEditorFactory<T> extends AbstractViewerFactory<T> { |
|
36 |
|
|
37 |
public HexEditorFactory() { |
|
38 |
super("HexEditor", "Hexadecimal", "application/octet-stream", false); |
|
39 |
} |
|
40 |
|
|
41 |
@Override |
|
42 |
public JViewer<T> createViewer() { |
|
43 |
return new HexEditor(this); |
|
44 |
} |
|
45 |
|
|
46 |
@Override |
|
47 |
public boolean isApplicable(Object... args) { |
|
48 |
Object data = args[0]; |
|
49 |
if( data instanceof byte[] ) { |
|
50 |
return true; |
|
51 |
} |
|
52 |
if( data instanceof String ) { |
|
53 |
return true; |
|
54 |
} |
|
55 |
if( data instanceof InputStream ) { |
|
56 |
return true; |
|
57 |
} |
|
58 |
return false; |
|
59 |
} |
|
60 |
|
|
61 |
} |
org.gvsig.tools/library/tags/org.gvsig.tools-3.0.376/org.gvsig.tools.swing/org.gvsig.tools.swing.impl/src/main/java/org/gvsig/tools/swing/impl/hexeditor/swing/ByteArrayTransferable.java | ||
---|---|---|
1 |
package org.gvsig.tools.swing.impl.hexeditor.swing; |
|
2 |
|
|
3 |
import java.awt.datatransfer.DataFlavor; |
|
4 |
import java.awt.datatransfer.Transferable; |
|
5 |
import java.awt.datatransfer.UnsupportedFlavorException; |
|
6 |
import java.io.IOException; |
|
7 |
import java.io.StringReader; |
|
8 |
|
|
9 |
|
|
10 |
/* |
|
11 |
* Based on portions of code from ajaxproxy in GitHub |
|
12 |
* Copyright (c) 2008 Robert Futrell |
|
13 |
* https://github.com/mdeanda/ajaxproxy/tree/master/src/main/java/org/fife/ui/hex |
|
14 |
*/ |
|
15 |
|
|
16 |
class ByteArrayTransferable implements Transferable { |
|
17 |
|
|
18 |
private int offset; |
|
19 |
private byte[] bytes; |
|
20 |
|
|
21 |
private static final DataFlavor[] FLAVORS = { |
|
22 |
DataFlavor.stringFlavor, |
|
23 |
DataFlavor.plainTextFlavor, |
|
24 |
}; |
|
25 |
|
|
26 |
|
|
27 |
/** |
|
28 |
* Creates a transferable object. |
|
29 |
* |
|
30 |
* @param bytes The bytes to transfer. |
|
31 |
*/ |
|
32 |
public ByteArrayTransferable(int offset, byte[] bytes) { |
|
33 |
this.offset = offset; |
|
34 |
if (bytes!=null) { |
|
35 |
this.bytes = (byte[])bytes.clone(); |
|
36 |
} |
|
37 |
else { |
|
38 |
this.bytes = new byte[0]; |
|
39 |
} |
|
40 |
} |
|
41 |
|
|
42 |
|
|
43 |
/** |
|
44 |
* Returns the number of bytes being transferred. |
|
45 |
* |
|
46 |
* @return The number of bytes being transferred. |
|
47 |
* @see #getOffset() |
|
48 |
*/ |
|
49 |
public int getLength() { |
|
50 |
return bytes.length; |
|
51 |
} |
|
52 |
|
|
53 |
|
|
54 |
/** |
|
55 |
* Returns the offset of the first byte being transferred. |
|
56 |
* |
|
57 |
* @return The offset of the first byte. |
|
58 |
* @see #getLength() |
|
59 |
*/ |
|
60 |
public int getOffset() { |
|
61 |
return offset; |
|
62 |
} |
|
63 |
|
|
64 |
|
|
65 |
/** |
|
66 |
* Returns the data being transferred in a format specified by the |
|
67 |
* <code>DataFlavor</code>. |
|
68 |
* |
|
69 |
* @param flavor Dictates in what format the data should be returned. |
|
70 |
* @throws UnsupportedFlavorException If the specified flavor is not |
|
71 |
* supported. |
|
72 |
* @throws IOException If an IO error occurs. |
|
73 |
* @see DataFlavor#getRepresentationClass() |
|
74 |
*/ |
|
75 |
public Object getTransferData(DataFlavor flavor) |
|
76 |
throws UnsupportedFlavorException, IOException { |
|
77 |
if (flavor.equals(FLAVORS[0])) { |
|
78 |
return new String(bytes); // Use platform default charset. |
|
79 |
} |
|
80 |
else if (flavor.equals(FLAVORS[1])) { |
|
81 |
return new StringReader(new String(bytes)); |
|
82 |
} |
|
83 |
throw new UnsupportedFlavorException(flavor); |
|
84 |
} |
|
85 |
|
|
86 |
|
|
87 |
/** |
|
88 |
* Returns an array of DataFlavor objects indicating the flavors the data |
|
89 |
* can be provided in. The array is ordered according to preference for |
|
90 |
* providing the data (from most richly descriptive to least descriptive). |
|
91 |
* |
|
92 |
* @return An array of data flavors in which this data can be transferred. |
|
93 |
*/ |
|
94 |
public DataFlavor[] getTransferDataFlavors() { |
|
95 |
return (DataFlavor[])FLAVORS.clone(); |
|
96 |
} |
|
97 |
|
|
98 |
|
|
99 |
/** |
|
100 |
* Returns whether a data flavor is supported. |
|
101 |
* |
|
102 |
* @param flavor The flavor to check. |
|
103 |
* @return Whether the specified flavor is supported. |
|
104 |
*/ |
|
105 |
public boolean isDataFlavorSupported(DataFlavor flavor) { |
|
106 |
for (int i=0; i<FLAVORS.length; i++) { |
|
107 |
if (flavor.equals(FLAVORS[i])) { |
|
108 |
return true; |
|
109 |
} |
|
110 |
} |
|
111 |
return false; |
|
112 |
} |
|
113 |
|
|
114 |
|
|
115 |
} |
org.gvsig.tools/library/tags/org.gvsig.tools-3.0.376/org.gvsig.tools.swing/org.gvsig.tools.swing.impl/src/main/java/org/gvsig/tools/swing/impl/hexeditor/swing/HexEditorRowHeader.java | ||
---|---|---|
1 |
package org.gvsig.tools.swing.impl.hexeditor.swing; |
|
2 |
|
|
3 |
import java.awt.Component; |
|
4 |
import java.awt.Graphics; |
|
5 |
import javax.swing.AbstractListModel; |
|
6 |
import javax.swing.BorderFactory; |
|
7 |
import javax.swing.DefaultListCellRenderer; |
|
8 |
import javax.swing.JLabel; |
|
9 |
import javax.swing.JList; |
|
10 |
import javax.swing.ListSelectionModel; |
|
11 |
|
|
12 |
|
|
13 |
import javax.swing.border.EmptyBorder; |
|
14 |
import javax.swing.border.Border; |
|
15 |
import javax.swing.event.TableModelEvent; |
|
16 |
import javax.swing.event.TableModelListener; |
|
17 |
|
|
18 |
/* |
|
19 |
* Based on portions of code from ajaxproxy in GitHub |
|
20 |
* Copyright (c) 2008 Robert Futrell |
|
21 |
* https://github.com/mdeanda/ajaxproxy/tree/master/src/main/java/org/fife/ui/hex |
|
22 |
*/ |
|
23 |
|
|
24 |
|
|
25 |
/** |
|
26 |
* Header of the hex table; displays address of the first byte on the |
|
27 |
* row. |
|
28 |
*/ |
|
29 |
class HexEditorRowHeader extends JList implements TableModelListener { |
|
30 |
|
|
31 |
private static final long serialVersionUID = 1L; |
|
32 |
|
|
33 |
private HexTable table; |
|
34 |
private RowHeaderListModel model; |
|
35 |
|
|
36 |
private static final Border CELL_BORDER = |
|
37 |
BorderFactory.createEmptyBorder(0,5,0,5); |
|
38 |
|
|
39 |
|
|
40 |
/** |
|
41 |
* Constructor. |
|
42 |
* |
|
43 |
* @param table The table displaying the hex content. |
|
44 |
*/ |
|
45 |
public HexEditorRowHeader(HexTable table) { |
|
46 |
this.table = table; |
|
47 |
model = new RowHeaderListModel(); |
|
48 |
setModel(model); |
|
49 |
setFocusable(false); |
|
50 |
setFont(table.getFont()); |
|
51 |
setFixedCellHeight(table.getRowHeight()); |
|
52 |
setCellRenderer(new CellRenderer()); |
|
53 |
setBorder(new RowHeaderBorder()); |
|
54 |
setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); |
|
55 |
syncRowCount(); // Initialize to initial size of table. |
|
56 |
table.getModel().addTableModelListener(this); |
|
57 |
} |
|
58 |
|
|
59 |
|
|
60 |
public void addSelectionInterval(int anchor, int lead) { |
|
61 |
super.addSelectionInterval(anchor, lead); |
|
62 |
int min = Math.min(anchor, lead); |
|
63 |
int max = Math.max(anchor, lead); |
|
64 |
table.setSelectedRows(min, max); |
|
65 |
} |
|
66 |
|
|
67 |
|
|
68 |
public void removeSelectionInterval(int index0, int index1) { |
|
69 |
super.removeSelectionInterval(index0, index1); |
|
70 |
int anchor = getAnchorSelectionIndex(); |
|
71 |
int lead = getLeadSelectionIndex(); |
|
72 |
table.setSelectedRows(Math.min(anchor, lead), Math.max(anchor, lead)); |
|
73 |
} |
|
74 |
|
|
75 |
|
|
76 |
public void setSelectionInterval(int anchor, int lead) { |
|
77 |
super.setSelectionInterval(anchor, lead); |
|
78 |
int min = Math.min(anchor, lead); |
|
79 |
int max = Math.max(anchor, lead); |
|
80 |
// Table may be showing 0 bytes, but we're showing 1 row header |
|
81 |
if (max<table.getRowCount()) { |
|
82 |
table.setSelectedRows(min, max); |
|
83 |
} |
|
84 |
} |
|
85 |
|
|
86 |
|
|
87 |
private void syncRowCount() { |
|
88 |
if (table.getRowCount()!=model.getSize()) { |
|
89 |
// Always keep 1 row, even if showing 0 bytes in editor |
|
90 |
model.setSize(Math.max(1, table.getRowCount())); |
|
91 |
} |
|
92 |
} |
|
93 |
|
|
94 |
|
|
95 |
public void tableChanged(TableModelEvent e) { |
|
96 |
syncRowCount(); |
|
97 |
} |
|
98 |
|
|
99 |
|
|
100 |
/** |
|
101 |
* Renders the cells of the row header. |
|
102 |
* |
|
103 |
* @author Robert Futrell |
|
104 |
* @version 1.0 |
|
105 |
*/ |
|
106 |
private class CellRenderer extends DefaultListCellRenderer { |
|
107 |
|
|
108 |
private static final long serialVersionUID = 1L; |
|
109 |
|
|
110 |
public CellRenderer() { |
|
111 |
setHorizontalAlignment(JLabel.RIGHT); |
|
112 |
} |
|
113 |
|
|
114 |
public Component getListCellRendererComponent(JList list, Object value, |
|
115 |
int index, boolean selected, boolean hasFocus) { |
|
116 |
// Never paint cells as "selected." |
|
117 |
super.getListCellRendererComponent(list, value, index, |
|
118 |
false, hasFocus); |
|
119 |
setBorder(CELL_BORDER); |
|
120 |
// setBackground(table.getBackground()); |
|
121 |
return this; |
|
122 |
} |
|
123 |
|
|
124 |
} |
|
125 |
|
|
126 |
|
|
127 |
/** |
|
128 |
* List model used by the header for the hex table. |
|
129 |
* |
|
130 |
* @author Robert Futrell |
|
131 |
* @version 1.0 |
|
132 |
*/ |
|
133 |
private static class RowHeaderListModel extends AbstractListModel { |
|
134 |
|
|
135 |
private static final long serialVersionUID = 1L; |
|
136 |
|
|
137 |
private int size; |
|
138 |
|
|
139 |
public Object getElementAt(int index) { |
|
140 |
return "0x" + Integer.toHexString(index*16); |
|
141 |
} |
|
142 |
|
|
143 |
public int getSize() { |
|
144 |
return size; |
|
145 |
} |
|
146 |
|
|
147 |
public void setSize(int size) { |
|
148 |
int old = this.size; |
|
149 |
this.size = size; |
|
150 |
int diff = size - old; |
|
151 |
if (diff>0) { |
|
152 |
fireIntervalAdded(this, old, size-1); |
|
153 |
} |
|
154 |
else if (diff<0) { |
|
155 |
fireIntervalRemoved(this, size+1, old-1); |
|
156 |
} |
|
157 |
} |
|
158 |
|
|
159 |
} |
|
160 |
|
|
161 |
|
|
162 |
/** |
|
163 |
* Border for the entire row header. This draws a line to separate the |
|
164 |
* header from the table contents, and gives a small amount of whitespace |
|
165 |
* to separate the two. |
|
166 |
* |
|
167 |
* @author Robert Futrell |
|
168 |
* @version 1.0 |
|
169 |
*/ |
|
170 |
private class RowHeaderBorder extends EmptyBorder { |
|
171 |
|
|
172 |
private static final long serialVersionUID = 1L; |
|
173 |
|
|
174 |
public RowHeaderBorder() { |
|
175 |
super(0,0,0,2); |
|
176 |
} |
|
177 |
|
|
178 |
public void paintBorder(Component c, Graphics g, int x, int y, |
|
179 |
int width, int height) { |
|
180 |
x = x + width - this.right; |
|
181 |
// g.setColor(table.getBackground()); |
|
182 |
// g.fillRect(x,y, width,height); |
|
183 |
g.setColor(table.getGridColor()); |
|
184 |
g.drawLine(x,y, x,y+height); |
|
185 |
} |
|
186 |
|
|
187 |
} |
|
188 |
|
|
189 |
|
|
190 |
} |
org.gvsig.tools/library/tags/org.gvsig.tools-3.0.376/org.gvsig.tools.swing/org.gvsig.tools.swing.impl/src/main/java/org/gvsig/tools/swing/impl/hexeditor/swing/HexTable.java | ||
---|---|---|
1 |
package org.gvsig.tools.swing.impl.hexeditor.swing; |
|
2 |
|
|
3 |
import java.awt.AWTEvent; |
|
4 |
import java.awt.Color; |
|
5 |
import java.awt.Component; |
|
6 |
import java.awt.Dimension; |
|
7 |
import java.awt.Font; |
|
8 |
import java.awt.FontMetrics; |
|
9 |
import java.awt.Graphics; |
|
10 |
import java.awt.Graphics2D; |
|
11 |
import java.awt.Point; |
|
12 |
import java.awt.Rectangle; |
|
13 |
import java.awt.event.FocusEvent; |
|
14 |
import java.awt.event.FocusListener; |
|
15 |
import java.awt.event.KeyEvent; |
|
16 |
import java.io.IOException; |
|
17 |
import java.io.InputStream; |
|
18 |
import java.util.Map; |
|
19 |
import javax.swing.DefaultCellEditor; |
|
20 |
import javax.swing.JTable; |
|
21 |
import javax.swing.JTextField; |
|
22 |
import javax.swing.ListSelectionModel; |
|
23 |
import javax.swing.UIManager; |
|
24 |
import javax.swing.table.DefaultTableCellRenderer; |
|
25 |
import javax.swing.table.TableCellRenderer; |
|
26 |
import javax.swing.table.TableColumn; |
|
27 |
import javax.swing.text.AbstractDocument; |
|
28 |
import javax.swing.text.AttributeSet; |
|
29 |
import javax.swing.text.BadLocationException; |
|
30 |
import javax.swing.text.Document; |
|
31 |
import javax.swing.text.DocumentFilter; |
|
32 |
import javax.swing.text.JTextComponent; |
|
33 |
import org.gvsig.tools.swing.impl.hexeditor.event.SelectionChangedEvent; |
|
34 |
import org.gvsig.tools.swing.impl.hexeditor.event.SelectionChangedListener; |
|
35 |
|
|
36 |
|
|
37 |
|
|
38 |
/* |
|
39 |
* Based on portions of code from ajaxproxy in GitHub |
|
40 |
* Copyright (c) 2008 Robert Futrell |
|
41 |
* https://github.com/mdeanda/ajaxproxy/tree/master/src/main/java/org/fife/ui/hex |
|
42 |
*/ |
|
43 |
|
|
44 |
|
|
45 |
/** |
|
46 |
* The table displaying the hex content of a file. This is the meat of |
|
47 |
* the hex viewer. |
|
48 |
* |
|
49 |
*/ |
|
50 |
class HexTable extends JTable { |
|
51 |
|
|
52 |
private static final long serialVersionUID = 1L; |
|
53 |
|
|
54 |
private final HexEditorComponent hexEditor; |
|
55 |
private HexTableModel model; |
|
56 |
int leadSelectionIndex; |
|
57 |
int anchorSelectionIndex; |
|
58 |
|
|
59 |
private static final Color ANTERNATING_CELL_COLOR = new Color(240,240,240); |
|
60 |
|
|
61 |
private CellEditor cellEditor = new CellEditor(); |
|
62 |
|
|
63 |
/** |
|
64 |
* Creates a new table to display hex data. |
|
65 |
* |
|
66 |
* @param hexEditor The parent hex editor component. |
|
67 |
* @param model The table model to use. |
|
68 |
*/ |
|
69 |
public HexTable(HexEditorComponent hexEditor, HexTableModel model) { |
|
70 |
|
|
71 |
super(model); |
|
72 |
this.hexEditor = hexEditor; |
|
73 |
this.model = model; |
|
74 |
enableEvents(AWTEvent.KEY_EVENT_MASK); |
|
75 |
setAutoResizeMode(JTable.AUTO_RESIZE_OFF); |
|
76 |
setFont(new Font("Monospaced", Font.PLAIN, 14)); |
|
77 |
//setRowHeight(28); |
|
78 |
setCellSelectionEnabled(true); |
|
79 |
setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); |
|
80 |
setDefaultEditor(Object.class, cellEditor); |
|
81 |
setDefaultRenderer(Object.class, new CellRenderer()); |
|
82 |
getTableHeader().setReorderingAllowed(false); |
|
83 |
setShowGrid(false); |
|
84 |
|
|
85 |
FontMetrics fm = getFontMetrics(getFont()); |
|
86 |
Font headerFont = UIManager.getFont("TableHeader.font"); |
|
87 |
FontMetrics headerFM = hexEditor.getFontMetrics(headerFont); |
|
88 |
int w = fm.stringWidth("www"); // cell contents, 0-255 |
|
89 |
w = Math.max(w, headerFM.stringWidth("+99")); |
|
90 |
for (int i=0; i<getColumnCount(); i++) { |
|
91 |
TableColumn column = getColumnModel().getColumn(i); |
|
92 |
if (i<16) { |
|
93 |
column.setPreferredWidth(w); |
|
94 |
} |
|
95 |
else { |
|
96 |
column.setPreferredWidth(HexEditorComponent.DUMP_COLUMN_WIDTH); |
|
97 |
} |
|
98 |
} |
|
99 |
|
|
100 |
setPreferredScrollableViewportSize( |
|
101 |
new Dimension(w*16+HexEditorComponent.DUMP_COLUMN_WIDTH, 25*getRowHeight())); |
|
102 |
|
|
103 |
anchorSelectionIndex = leadSelectionIndex = 0; |
|
104 |
|
|
105 |
} |
|
106 |
|
|
107 |
|
|
108 |
/** |
|
109 |
* Registers a prospect who is interested when the text selection from the |
|
110 |
* hex editor becomes changed. |
|
111 |
* |
|
112 |
* @param l The concerning listener. |
|
113 |
* @see #removeSelectionChangedListener(SelectionChangedListener) |
|
114 |
*/ |
|
115 |
public void addSelectionChangedListener(SelectionChangedListener l) { |
|
116 |
listenerList.add(SelectionChangedListener.class, l); |
|
117 |
} |
|
118 |
|
|
119 |
|
|
120 |
/** |
|
121 |
* Returns the column for the cell containing data that is the closest |
|
122 |
* to the specified cell. This is used when, for example, the user clicks |
|
123 |
* on an "empty" cell in the last row of the table. |
|
124 |
* |
|
125 |
* @param row The row of the cell clicked on. |
|
126 |
* @param col The column of the cell clicked on. |
|
127 |
* @return The column of the closest cell containing data. |
|
128 |
*/ |
|
129 |
private int adjustColumn(int row, int col) { |
|
130 |
if (col<0) { |
|
131 |
return 0; |
|
132 |
} |
|
133 |
if (row==getRowCount()-1) { |
|
134 |
int lastRowCount = model.getByteCount()%16; |
|
135 |
if (lastRowCount==0) { |
|
136 |
lastRowCount = 16; |
|
137 |
} |
|
138 |
if (lastRowCount<16) { // Last row's not entirely full |
|
139 |
return Math.min(col, (model.getByteCount()%16)-1); |
|
140 |
} |
|
141 |
} |
|
142 |
return Math.min(col, getColumnCount()-1-1); |
|
143 |
} |
|
144 |
|
|
145 |
|
|
146 |
/** |
|
147 |
* Returns the offset into the bytes being edited represented at the |
|
148 |
* specified cell in the table, if any. |
|
149 |
* |
|
150 |
* @param row The row in the table. |
|
151 |
* @param col The column in the table. |
|
152 |
* @return The offset into the byte array, or <code>-1</code> if the |
|
153 |
* cell does not represent part of the byte array (such as the |
|
154 |
* tailing "ascii dump" column's cells). |
|
155 |
* @see #offsetToCell(int) |
|
156 |
*/ |
|
157 |
public int cellToOffset(int row, int col) { |
|
158 |
// Check row and column individually to prevent them being invalid |
|
159 |
// values but still pointing to a valid offset in the buffer. |
|
160 |
if (row<0 || row>=getRowCount() || |
|
161 |
col<0 || col>15) { // Don't include last column (ascii dump) |
|
162 |
return -1; |
|
163 |
} |
|
164 |
int offs = row*16 + col; |
|
165 |
return (offs>=0 && offs<model.getByteCount()) ? offs : -1; |
|
166 |
} |
|
167 |
|
|
168 |
|
|
169 |
/** |
|
170 |
* Changes the selected byte range. |
|
171 |
* |
|
172 |
* @param row |
|
173 |
* @param col |
|
174 |
* @param toggle |
|
175 |
* @param extend |
|
176 |
* @see #changeSelectionByOffset(int, boolean) |
|
177 |
* @see #setSelectedRows(int, int) |
|
178 |
* @see #setSelectionByOffsets(int, int) |
|
179 |
*/ |
|
180 |
public void changeSelection(int row, int col, boolean toggle, |
|
181 |
boolean extend) { |
|
182 |
|
|
183 |
// remind previous selection range |
|
184 |
int prevSmallest = getSmallestSelectionIndex(); |
|
185 |
int prevLargest = getLargestSelectionIndex(); |
|
186 |
|
|
187 |
// Don't allow the user to select the "ascii dump" or any |
|
188 |
// empty cells in the last row of the table. |
|
189 |
col = adjustColumn(row, col); |
|
190 |
if (row<0) { |
|
191 |
row = 0; |
|
192 |
} |
|
193 |
|
|
194 |
// Clear the old selection (may not be necessary). |
|
195 |
repaintSelection(); |
|
196 |
|
|
197 |
if (extend) { |
|
198 |
leadSelectionIndex = cellToOffset(row, col); |
|
199 |
} |
|
200 |
else { |
|
201 |
anchorSelectionIndex = leadSelectionIndex = cellToOffset(row, col); |
|
202 |
} |
|
203 |
|
|
204 |
// Scroll after changing the selection as blit scrolling is |
|
205 |
// immediate, so that if we cause the repaint after the scroll we |
|
206 |
// end up painting everything! |
|
207 |
if (getAutoscrolls()) { |
|
208 |
ensureCellIsVisible(row, col); |
|
209 |
} |
|
210 |
|
|
211 |
// Draw the new selection. |
|
212 |
repaintSelection(); |
|
213 |
|
|
214 |
fireSelectionChangedEvent(prevSmallest, prevLargest); |
|
215 |
|
|
216 |
} |
|
217 |
|
|
218 |
|
|
219 |
/** |
|
220 |
* Changes the selection by an offset into the bytes being edited. |
|
221 |
* |
|
222 |
* @param offset |
|
223 |
* @param extend |
|
224 |
* @see #changeSelection(int, int, boolean, boolean) |
|
225 |
* @see #setSelectedRows(int, int) |
|
226 |
* @see #setSelectionByOffsets(int, int) |
|
227 |
*/ |
|
228 |
public void changeSelectionByOffset(int offset, boolean extend) { |
|
229 |
offset = Math.max(0, offset); |
|
230 |
offset = Math.min(offset, model.getByteCount()-1); |
|
231 |
int row = offset/16; |
|
232 |
int col = offset%16; |
|
233 |
changeSelection(row, col, false, extend); |
|
234 |
} |
|
235 |
|
|
236 |
|
|
237 |
/** |
|
238 |
* Clears the selection. The "lead" of the selection is set back to the |
|
239 |
* position of the "anchor." |
|
240 |
*/ |
|
241 |
public void clearSelection() { |
|
242 |
if (anchorSelectionIndex>-1) { // Always true unless an error |
|
243 |
leadSelectionIndex = anchorSelectionIndex; |
|
244 |
} |
|
245 |
else { |
|
246 |
anchorSelectionIndex = leadSelectionIndex = 0; |
|
247 |
} |
|
248 |
repaintSelection(); |
|
249 |
} |
|
250 |
|
|
251 |
|
|
252 |
/** |
|
253 |
* Ensures the specified cell is visible. |
|
254 |
* |
|
255 |
* @param row The row of the cell. |
|
256 |
* @param col The column of the cell. |
|
257 |
*/ |
|
258 |
private void ensureCellIsVisible(int row, int col) { |
|
259 |
Rectangle cellRect = getCellRect(row, col, false); |
|
260 |
if (cellRect != null) { |
|
261 |
scrollRectToVisible(cellRect); |
|
262 |
} |
|
263 |
} |
|
264 |
|
|
265 |
|
|
266 |
/** |
|
267 |
* Notifies any listeners that the selection has changed. |
|
268 |
* |
|
269 |
* @see #addSelectionChangedListener(SelectionChangedListener) |
|
270 |
* @see #removeSelectionChangedListener(SelectionChangedListener) |
|
271 |
* @param e Contains proper information. |
|
272 |
*/ |
|
273 |
private void fireSelectionChangedEvent(int prevSmallest, int prevLargest) { |
|
274 |
|
|
275 |
// Lazily create the event |
|
276 |
SelectionChangedEvent e = null; |
|
277 |
|
|
278 |
// Guaranteed non-null array |
|
279 |
Object[] listeners = listenerList.getListenerList(); |
|
280 |
// Process last to first |
|
281 |
for (int i=listeners.length-2; i>=0; i-=2) { |
|
282 |
if (listeners[i]==SelectionChangedListener.class) { |
|
283 |
if (e==null) { |
|
284 |
e = new SelectionChangedEvent(this, |
|
285 |
prevSmallest, prevLargest, |
|
286 |
getSmallestSelectionIndex(), getLargestSelectionIndex()); |
|
287 |
} |
|
288 |
((SelectionChangedListener)listeners[i+1]).selectionChanged(e); |
|
289 |
} |
|
290 |
} |
|
291 |
|
|
292 |
} |
|
293 |
|
|
294 |
|
|
295 |
/** |
|
296 |
* Returns the byte at the specified offset. |
|
297 |
* |
|
298 |
* @param offset The offset. |
|
299 |
* @return The byte. |
|
300 |
*/ |
|
301 |
public byte getByte(int offset) { |
|
302 |
return model.getByte(offset); |
|
303 |
} |
|
304 |
|
|
305 |
public byte[] getBytes() { |
|
306 |
return this.model.getBytes(); |
|
307 |
} |
|
308 |
|
|
309 |
/** |
|
310 |
* Returns the number of bytes being edited. |
|
311 |
* |
|
312 |
* @return The number of bytes. |
|
313 |
*/ |
|
314 |
public int getByteCount() { |
|
315 |
return model.getByteCount(); |
|
316 |
} |
|
317 |
|
|
318 |
|
|
319 |
/** |
|
320 |
* Returns the rendering hints for text that will most accurately reflect |
|
321 |
* those of the native windowing system. |
|
322 |
* |
|
323 |
* @return The rendering hints, or <code>null</code> if they cannot be |
|
324 |
* determined. |
|
325 |
*/ |
|
326 |
private Map getDesktopAntiAliasHints() { |
|
327 |
return (Map)getToolkit().getDesktopProperty("awt.font.desktophints"); |
|
328 |
} |
|
329 |
|
|
330 |
|
|
331 |
/** |
|
332 |
* Returns the largest selection index. |
|
333 |
* |
|
334 |
* @return The largest selection index. |
|
335 |
* @see #getSmallestSelectionIndex() |
|
336 |
*/ |
|
337 |
public int getLargestSelectionIndex() { |
|
338 |
int index = Math.max(leadSelectionIndex, anchorSelectionIndex); |
|
339 |
return Math.max(index, 0); // Don't return -1 if table is empty |
|
340 |
} |
|
341 |
|
|
342 |
|
|
343 |
/** |
|
344 |
* Returns the smallest selection index. |
|
345 |
* |
|
346 |
* @return The smallest selection index. |
|
347 |
* @see #getLargestSelectionIndex() |
|
348 |
*/ |
|
349 |
public int getSmallestSelectionIndex() { |
|
350 |
int index = Math.min(leadSelectionIndex, anchorSelectionIndex); |
|
351 |
return Math.max(index, 0); // Don't return -1 if table is empty |
|
352 |
} |
|
353 |
|
|
354 |
|
|
355 |
public boolean isCellEditable(int row, int col) { |
|
356 |
return cellToOffset(row, col)>-1; |
|
357 |
} |
|
358 |
|
|
359 |
|
|
360 |
public boolean isCellSelected(int row, int col) { |
|
361 |
int offset = cellToOffset(row, col); |
|
362 |
if (offset==-1) { // "Ascii dump" column |
|
363 |
return false; |
|
364 |
} |
|
365 |
int start = getSmallestSelectionIndex(); |
|
366 |
int end = getLargestSelectionIndex(); |
|
367 |
return offset>=start && offset<=end; |
|
368 |
} |
|
369 |
|
|
370 |
|
|
371 |
/** |
|
372 |
* Returns the cell representing the specified offset into the hex |
|
373 |
* document. |
|
374 |
* |
|
375 |
* @param offset The offset into the document. |
|
376 |
* @return The cell, in the form <code>(row, col)</code>. If the |
|
377 |
* specified offset is invalid, <code>(-1, -1)</code> is returned. |
|
378 |
* @see #cellToOffset(int, int) |
|
379 |
*/ |
|
380 |
public Point offsetToCell(int offset) { |
|
381 |
if (offset<0 || offset>=model.getByteCount()) { |
|
382 |
return new Point(-1, -1); |
|
383 |
} |
|
384 |
int row = offset/16; |
|
385 |
int col = offset%16; |
|
386 |
return new Point(row, col); |
|
387 |
} |
|
388 |
|
|
389 |
|
|
390 |
/** |
|
391 |
* Sets the contents in the hex editor to the contents of the specified |
|
392 |
* file. |
|
393 |
* |
|
394 |
* @param fileName The name of the file to open. |
|
395 |
* @throws IOException If an IO error occurs. |
|
396 |
*/ |
|
397 |
public void open(String fileName) throws IOException { |
|
398 |
model.setBytes(fileName); // Fires tableDataChanged event |
|
399 |
} |
|
400 |
|
|
401 |
|
|
402 |
/** |
|
403 |
* Sets the contents in the hex editor to the contents of the specified |
|
404 |
* input stream. |
|
405 |
* |
|
406 |
* @param in An input stream. |
|
407 |
* @throws IOException If an IO error occurs. |
|
408 |
*/ |
|
409 |
public void open(InputStream in) throws IOException { |
|
410 |
model.setBytes(in); |
|
411 |
} |
|
412 |
|
|
413 |
|
|
414 |
public Component prepareRenderer(TableCellRenderer renderer, int row, |
|
415 |
int column) { |
|
416 |
Object value = getValueAt(row, column); |
|
417 |
boolean isSelected = isCellSelected(row, column); |
|
418 |
boolean hasFocus = cellToOffset(row, column)==leadSelectionIndex; |
|
419 |
|
|
420 |
return renderer.getTableCellRendererComponent(this, value, |
|
421 |
isSelected, hasFocus, row, column); |
|
422 |
} |
|
423 |
|
|
424 |
|
|
425 |
protected void processKeyEvent (java.awt.event.KeyEvent e) { |
|
426 |
|
|
427 |
// TODO: Convert into Actions and put into InputMap/ActionMap? |
|
428 |
if (e.getID()==KeyEvent.KEY_PRESSED) { |
|
429 |
switch (e.getKeyCode()) { |
|
430 |
case KeyEvent.VK_LEFT: |
|
431 |
boolean extend = e.isShiftDown(); |
|
432 |
int offs = Math.max(leadSelectionIndex-1, 0); |
|
433 |
changeSelectionByOffset(offs, extend); |
|
434 |
e.consume(); |
|
435 |
break; |
|
436 |
case KeyEvent.VK_RIGHT: |
|
437 |
extend = e.isShiftDown(); |
|
438 |
offs = Math.min(leadSelectionIndex+1, model.getByteCount()-1); |
|
439 |
changeSelectionByOffset(offs, extend); |
|
440 |
e.consume(); |
|
441 |
break; |
|
442 |
case KeyEvent.VK_UP: |
|
443 |
extend = e.isShiftDown(); |
|
444 |
offs = Math.max(leadSelectionIndex-16, 0); |
|
445 |
changeSelectionByOffset(offs, extend); |
|
446 |
e.consume(); |
|
447 |
break; |
|
448 |
case KeyEvent.VK_DOWN: |
|
449 |
extend = e.isShiftDown(); |
|
450 |
offs = Math.min(leadSelectionIndex+16, model.getByteCount()-1); |
|
451 |
changeSelectionByOffset(offs, extend); |
|
452 |
e.consume(); |
|
453 |
break; |
|
454 |
case KeyEvent.VK_PAGE_DOWN: |
|
455 |
extend = e.isShiftDown(); |
|
456 |
int visibleRowCount = getVisibleRect().height/getRowHeight(); |
|
457 |
offs = Math.min(leadSelectionIndex+visibleRowCount*16, |
|
458 |
model.getByteCount()-1); |
|
459 |
changeSelectionByOffset(offs, extend); |
|
460 |
e.consume(); |
|
461 |
break; |
|
462 |
case KeyEvent.VK_PAGE_UP: |
|
463 |
extend = e.isShiftDown(); |
|
464 |
visibleRowCount = getVisibleRect().height/getRowHeight(); |
|
465 |
offs = Math.max(leadSelectionIndex-visibleRowCount*16, 0); |
|
466 |
changeSelectionByOffset(offs, extend); |
|
467 |
e.consume(); |
|
468 |
break; |
|
469 |
case KeyEvent.VK_HOME: |
|
470 |
extend = e.isShiftDown(); |
|
471 |
offs = (leadSelectionIndex/16)*16; |
|
472 |
changeSelectionByOffset(offs, extend); |
|
473 |
e.consume(); |
|
474 |
break; |
|
475 |
case KeyEvent.VK_END: |
|
476 |
extend = e.isShiftDown(); |
|
477 |
offs = (leadSelectionIndex/16)*16 + 15; |
|
478 |
offs = Math.min(offs, model.getByteCount()-1); |
|
479 |
changeSelectionByOffset(offs, extend); |
|
480 |
e.consume(); |
|
481 |
break; |
|
482 |
} |
|
483 |
} |
|
484 |
|
|
485 |
super.processKeyEvent(e); |
|
486 |
|
|
487 |
} |
|
488 |
|
|
489 |
|
|
490 |
/** |
|
491 |
* Tries to redo the last action undone. |
|
492 |
* |
|
493 |
* @return Whether there is another action to redo after this one. |
|
494 |
* @see #undo() |
|
495 |
*/ |
|
496 |
public boolean redo() { |
|
497 |
return model.redo(); |
|
498 |
} |
|
499 |
|
|
500 |
|
|
501 |
/** |
|
502 |
* Removes a range of bytes. |
|
503 |
* |
|
504 |
* @param offs The offset of the range of bytes to remove. |
|
505 |
* @param len The number of bytes to remove. |
|
506 |
* @see #replaceBytes(int, int, byte[]) |
|
507 |
*/ |
|
508 |
void removeBytes(int offs, int len) { |
|
509 |
model.removeBytes(offs, len); |
|
510 |
} |
|
511 |
|
|
512 |
|
|
513 |
private void repaintSelection() { |
|
514 |
// TODO: Repaint only selected lines. |
|
515 |
repaint(); |
|
516 |
} |
|
517 |
|
|
518 |
|
|
519 |
/** |
|
520 |
* Removes a listener who isn't any longer interested whether the text |
|
521 |
* selection from the hex editor becomes changed. |
|
522 |
* |
|
523 |
* @param l The concerning previous prospect. |
|
524 |
* @see #addSelectionChangedListener(SelectionChangedListener) |
|
525 |
*/ |
|
526 |
public void removeSelectionChangedListener(SelectionChangedListener l) { |
|
527 |
listenerList.remove(SelectionChangedListener.class, l); |
|
528 |
} |
|
529 |
|
Also available in: Unified diff