gvsig-scripting / trunk / org.gvsig.scripting.thing / src / main / java / thing / Handler.java @ 4
History | View | Annotate | Download (55.3 KB)
1 |
// jEdit settings:
|
---|---|
2 |
// :tabSize=4:indentSize=4:noTabs=true:folding=explicit:collapseFolds=1:
|
3 |
|
4 |
package thing; |
5 |
|
6 |
|
7 |
import java.awt.Color; |
8 |
import java.awt.Font; |
9 |
import java.awt.Frame; |
10 |
import java.awt.Image; |
11 |
import java.awt.datatransfer.DataFlavor; |
12 |
import java.awt.datatransfer.StringSelection; |
13 |
import java.awt.datatransfer.Transferable; |
14 |
import java.awt.datatransfer.UnsupportedFlavorException; |
15 |
import java.io.BufferedWriter; |
16 |
import java.io.File; |
17 |
import java.io.FileReader; |
18 |
import java.io.FileWriter; |
19 |
import java.io.IOException; |
20 |
import java.io.Reader; |
21 |
import java.io.StringReader; |
22 |
import java.text.ParseException; |
23 |
import java.util.Enumeration; |
24 |
import java.util.HashMap; |
25 |
import java.util.Hashtable; |
26 |
import java.util.Iterator; |
27 |
import java.util.Map; |
28 |
import java.util.Stack; |
29 |
import java.util.StringTokenizer; |
30 |
import java.util.logging.Level; |
31 |
import java.util.logging.Logger; |
32 |
|
33 |
import thinlet.Thinlet; |
34 |
|
35 |
import utils.AWTUtils; |
36 |
|
37 |
import thinletcommons.ColorChooser; |
38 |
import thinletcommons.FileChooser; |
39 |
import thinletcommons.FileFilter; |
40 |
import thinletcommons.ExtensionFileFilter; |
41 |
import thinletcommons.FontChooser; |
42 |
import thinletcommons.MessageDialog; |
43 |
|
44 |
|
45 |
/**
|
46 |
* ThinG GUI logic handler/controller.
|
47 |
*
|
48 |
* @author Dirk Moebius
|
49 |
*/
|
50 |
public class Handler |
51 |
{ |
52 |
|
53 |
//{{{ logging
|
54 |
private static final Logger log = Logger.getLogger("thing"); |
55 |
private static final boolean debug() { return log.isLoggable(Level.FINE); } |
56 |
//}}}
|
57 |
|
58 |
|
59 |
//{{{ constants
|
60 |
private static final int TAB_XML = 0; |
61 |
private static final int TAB_PREVIEW = 1; |
62 |
//}}}
|
63 |
|
64 |
|
65 |
//{{{ private members
|
66 |
private Thinlet thinlet;
|
67 |
private HashMap components = new HashMap(); |
68 |
private PropertyManager propmgr;
|
69 |
private File file; |
70 |
private boolean isChanged = false; |
71 |
private String clipboard; |
72 |
|
73 |
// components of main panel
|
74 |
private Object root; |
75 |
private Object preview; |
76 |
private Object tree; |
77 |
private Object props; |
78 |
private Object tabs; |
79 |
private Object statusbar; |
80 |
private Object toolbar; |
81 |
//}}}
|
82 |
|
83 |
|
84 |
//{{{ constructor Handler(Thinlet thinlet)
|
85 |
Handler(Thinlet thinlet)
|
86 |
{ |
87 |
this.thinlet = thinlet;
|
88 |
init(); |
89 |
setStatusText("ThinG v" + Main.getVersion() + " - created by Dirk Moebius (dmoebius@gmx.net)"); |
90 |
} |
91 |
//}}}
|
92 |
|
93 |
|
94 |
//{{{ public handler callbacks
|
95 |
|
96 |
//{{{ public void menu_file_new()
|
97 |
public void menu_file_new() |
98 |
{ |
99 |
if(!checkForChanges())
|
100 |
return;
|
101 |
setSelectedFile(null);
|
102 |
init(); |
103 |
setChanged(false);
|
104 |
setStatusText("New gui created.");
|
105 |
} |
106 |
//}}}
|
107 |
|
108 |
|
109 |
//{{{ public void menu_file_open()
|
110 |
public void menu_file_open() |
111 |
{ |
112 |
if(!checkForChanges())
|
113 |
return;
|
114 |
File selectedFile = getFile(FileChooser.MODE_OPEN, "Load Thinlet XML file...", thinlet, this.file); |
115 |
if(selectedFile != null) |
116 |
{ |
117 |
setSelectedFile(selectedFile); |
118 |
load(); |
119 |
} |
120 |
} |
121 |
//}}}
|
122 |
|
123 |
|
124 |
//{{{ public void menu_file_reload()
|
125 |
public void menu_file_reload() |
126 |
{ |
127 |
if(!checkForChanges())
|
128 |
return;
|
129 |
if(this.file != null) |
130 |
load(); |
131 |
} |
132 |
//}}}
|
133 |
|
134 |
|
135 |
//{{{ public void menu_file_save()
|
136 |
public void menu_file_save() |
137 |
{ |
138 |
save(false);
|
139 |
} |
140 |
//}}}
|
141 |
|
142 |
|
143 |
//{{{ public void menu_file_save_as()
|
144 |
public void menu_file_save_as() |
145 |
{ |
146 |
save(true);
|
147 |
} |
148 |
//}}}
|
149 |
|
150 |
|
151 |
//{{{ public void menu_file_exit()
|
152 |
public void menu_file_exit() |
153 |
{ |
154 |
if(exit())
|
155 |
System.exit(0); |
156 |
} |
157 |
//}}}
|
158 |
|
159 |
|
160 |
//{{{ public void menu_edit_copy()
|
161 |
public void menu_edit_copy() |
162 |
{ |
163 |
Object component = getSelectedComponent();
|
164 |
if(component != null) |
165 |
{ |
166 |
// serialize selected component tree to Thinlet XML
|
167 |
this.clipboard = serialize(component);
|
168 |
// insert xml string into system clipboard
|
169 |
StringSelection selection = new StringSelection(this.clipboard); |
170 |
thinlet.getToolkit().getSystemClipboard().setContents(selection, selection); |
171 |
updateToolbar(component); |
172 |
setStatusText("" + ThinletWorkarounds.getTotalCount(thinlet, component)
|
173 |
+ " components copied to system clipboard.");
|
174 |
} |
175 |
} |
176 |
//}}}
|
177 |
|
178 |
|
179 |
//{{{ public void menu_edit_cut()
|
180 |
public void menu_edit_cut() |
181 |
{ |
182 |
menu_edit_copy(); |
183 |
menu_edit_delete(); |
184 |
} |
185 |
//}}}
|
186 |
|
187 |
|
188 |
//{{{ public void menu_edit_paste()
|
189 |
public void menu_edit_paste() |
190 |
{ |
191 |
if(this.clipboard != null) |
192 |
{ |
193 |
Object parentTreenode = getSelectedTreeNode(); // note: may be null |
194 |
addComponents(parentTreenode, -1, this.clipboard); |
195 |
setStatusText("Pasted components from internal clipboard.");
|
196 |
} |
197 |
} |
198 |
//}}}
|
199 |
|
200 |
|
201 |
//{{{ public void menu_edit_paste_systemcb()
|
202 |
public void menu_edit_paste_systemcb() |
203 |
{ |
204 |
// get access to system clipboard
|
205 |
Transferable transferable = thinlet.getToolkit().getSystemClipboard().getContents(thinlet);
|
206 |
if(transferable == null) |
207 |
{ |
208 |
if(debug()) log.fine("paste from system clipboard: clipboard is empty"); |
209 |
setStatusText("Note: clipboard is empty");
|
210 |
return;
|
211 |
} |
212 |
if(!transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) |
213 |
{ |
214 |
log.warning("paste from system clipboard: clipboard contains something that is not a string");
|
215 |
messagebox("Clipboard Error", "System clipboard does not contain a string."); |
216 |
return;
|
217 |
} |
218 |
|
219 |
// get string contents of system clipboard
|
220 |
String text = null; |
221 |
try
|
222 |
{ |
223 |
text = (String) transferable.getTransferData(DataFlavor.stringFlavor); |
224 |
} |
225 |
catch(Exception e) |
226 |
{ |
227 |
log.log(Level.SEVERE, "Error getting clipboard contents", e); |
228 |
setStatusText("Error getting clipboard contents: " + e
|
229 |
+ " See log for details.");
|
230 |
} |
231 |
|
232 |
// try to add string, assuming it contains Thinlet XML
|
233 |
if(text != null) |
234 |
{ |
235 |
Object parentTreenode = getSelectedTreeNode(); // note: may be null |
236 |
addComponents(parentTreenode, -1, text);
|
237 |
setStatusText("Pasted components from system clipboard.");
|
238 |
} |
239 |
} |
240 |
//}}}
|
241 |
|
242 |
|
243 |
//{{{ public void menu_edit_delete()
|
244 |
public void menu_edit_delete() |
245 |
{ |
246 |
removeSelectedComponent(); |
247 |
} |
248 |
//}}}
|
249 |
|
250 |
|
251 |
//{{{ public void menu_edit_settings()
|
252 |
public void menu_edit_settings() |
253 |
{ |
254 |
new SettingsDialog(AWTUtils.getFrame(thinlet)).show();
|
255 |
} |
256 |
//}}}
|
257 |
|
258 |
|
259 |
//{{{ public void menu_tools_generate()
|
260 |
public void menu_tools_generate() |
261 |
{ |
262 |
if(isEmpty())
|
263 |
messagebox("Error", "Nothing to generate.\nPlease add components first."); |
264 |
else
|
265 |
new GenerateDialog(AWTUtils.getFrame(thinlet), this).show(); |
266 |
} |
267 |
//}}}
|
268 |
|
269 |
|
270 |
//{{{ public void menu_help_about()
|
271 |
public void menu_help_about() |
272 |
{ |
273 |
MessageDialog dlg = new MessageDialog(AWTUtils.getFrame(thinlet),
|
274 |
"About ThinG",
|
275 |
AWTUtils.getIcon(thinlet, "/thing/icons/thing.gif"),
|
276 |
"ThinG v" + Main.getVersion() + "\n" |
277 |
+ "\n"
|
278 |
+ "Created by Dirk Moebius (dmoebius@gmx.net)\n"
|
279 |
+ "\n"
|
280 |
+ "ThinG is free software, and you are welcome to redistribute it\n"
|
281 |
+ "under the terms of the GNU General Public License, Version 2.\n"
|
282 |
+ "See the license file doc/COPYING.txt for details.\n"
|
283 |
+ "\n"
|
284 |
+ "Many thanks to:\n"
|
285 |
+ " Robert Bajzat (Thinlet inventor)\n"
|
286 |
+ " Wolf Paulus (inventor of the best Thinlet RAD tool available:\n"
|
287 |
+ " Theodore, see http://www.carlsbadcubes.com/theodore/)\n");
|
288 |
dlg.show(); |
289 |
} |
290 |
//}}}
|
291 |
|
292 |
|
293 |
//{{{ public void addComponent(Object toolbarButton)
|
294 |
public void addComponent(Object toolbarButton) |
295 |
{ |
296 |
String classname = getToolbarButtonClassname(toolbarButton);
|
297 |
Object newNode = null; |
298 |
|
299 |
if(isEmpty())
|
300 |
{ |
301 |
// no elements in tree: add component at top/first element
|
302 |
newNode = addComponentImpl(classname, null, -1, null); |
303 |
} |
304 |
else
|
305 |
{ |
306 |
Object parentTreenode = getSelectedTreeNode();
|
307 |
if(parentTreenode != null) |
308 |
{ |
309 |
// create new tree node below currently selected tree node
|
310 |
newNode = addComponentImpl(classname, parentTreenode, -1, null); |
311 |
} |
312 |
} |
313 |
|
314 |
setSelectedTreeNode(newNode); |
315 |
} |
316 |
//}}}
|
317 |
|
318 |
|
319 |
//{{{ public void moveComponentUp()
|
320 |
public void moveComponentUp() |
321 |
{ |
322 |
moveSelectedComponent(-1);
|
323 |
} |
324 |
//}}}
|
325 |
|
326 |
|
327 |
//{{{ public void moveComponentDown()
|
328 |
public void moveComponentDown() |
329 |
{ |
330 |
moveSelectedComponent(1);
|
331 |
} |
332 |
//}}}
|
333 |
|
334 |
|
335 |
//{{{ public void treeSelectionChanged()
|
336 |
public void treeSelectionChanged() |
337 |
{ |
338 |
thinlet.removeAll(props); |
339 |
|
340 |
Object component = getSelectedComponent();
|
341 |
if(component != null) |
342 |
addTableProperties(component); |
343 |
|
344 |
updateToolbar(component); |
345 |
updatePropertyEditor(component, null);
|
346 |
} |
347 |
//}}}
|
348 |
|
349 |
|
350 |
//{{{ public void tableSelectionChanged()
|
351 |
public void tableSelectionChanged() |
352 |
{ |
353 |
Object component = getSelectedComponent();
|
354 |
String propName = getSelectedPropertyName();
|
355 |
if(component != null) |
356 |
updatePropertyEditor(component, propName); |
357 |
} |
358 |
//}}}
|
359 |
|
360 |
|
361 |
//{{{ public void propEditBooleanChanged(String text, boolean isSelected)
|
362 |
public void propEditBooleanChanged(String text, boolean isSelected) |
363 |
{ |
364 |
boolean value = (text.equals("true") && isSelected); |
365 |
Object component = getSelectedComponent();
|
366 |
String propName = getSelectedPropertyName();
|
367 |
if(component != null && propName != null) |
368 |
{ |
369 |
propmgr.setValue(component, propName, Boolean.toString(value));
|
370 |
updateTableProperty(component, propName); |
371 |
if(debug()) log.fine("property " + propName + " for component " |
372 |
+ ThinletWorkarounds.toString(component) + " changed to " + value);
|
373 |
setStatusText("Component " + ThinletWorkarounds.toString(component)
|
374 |
+ ": Property " + propName + " changed to " + value); |
375 |
} |
376 |
} |
377 |
//}}}
|
378 |
|
379 |
|
380 |
//{{{ public void propEditIntegerChanged(String value)
|
381 |
public void propEditIntegerChanged(String value) |
382 |
{ |
383 |
if(value.length() == 0) |
384 |
return;
|
385 |
|
386 |
Object component = getSelectedComponent();
|
387 |
String propName = getSelectedPropertyName();
|
388 |
if(component != null && propName != null) |
389 |
{ |
390 |
propmgr.setValue(component, propName, value); |
391 |
updateTableProperty(component, propName); |
392 |
if(debug()) log.fine("property " + propName + " for component " |
393 |
+ ThinletWorkarounds.toString(component) + " changed to " + value);
|
394 |
setStatusText("Component " + ThinletWorkarounds.toString(component)
|
395 |
+ ": Property " + propName + " changed to " + value); |
396 |
} |
397 |
} |
398 |
//}}}
|
399 |
|
400 |
|
401 |
//{{{ public void propEditChoiceChanged(Object combobox, int selected)
|
402 |
public void propEditChoiceChanged(Object combobox, int selected) |
403 |
{ |
404 |
String choice = thinlet.getString(thinlet.getItem(combobox, selected), "text"); |
405 |
Object component = getSelectedComponent();
|
406 |
String propName = getSelectedPropertyName();
|
407 |
if(component != null && propName != null && choice != null) |
408 |
{ |
409 |
propmgr.setValue(component, propName, choice); |
410 |
updateTableProperty(component, propName); |
411 |
if(debug()) log.fine("property " + propName + " for component " |
412 |
+ ThinletWorkarounds.toString(component) + " changed to " + choice);
|
413 |
setStatusText("Component " + ThinletWorkarounds.toString(component)
|
414 |
+ ": Property " + propName + " changed to " + choice); |
415 |
} |
416 |
} |
417 |
//}}}
|
418 |
|
419 |
|
420 |
//{{{ public void propEditStringChanged(String text)
|
421 |
public void propEditStringChanged(String text) |
422 |
{ |
423 |
Object component = getSelectedComponent();
|
424 |
String propName = getSelectedPropertyName();
|
425 |
|
426 |
if(component != null && propName != null && text != null) |
427 |
{ |
428 |
String classname = Thinlet.getClass(component);
|
429 |
ThinletDTD.Property property = ThinletDTD.getProperty(classname, propName); |
430 |
try
|
431 |
{ |
432 |
switch(property.getType())
|
433 |
{ |
434 |
case ThinletDTD.Property.STRING:
|
435 |
propmgr.setValue(component, property, text); |
436 |
if(propName.equals("name")) |
437 |
{ |
438 |
// update tree node text
|
439 |
String nodeText = classname;
|
440 |
if(text.length() > 0) nodeText += " [" + text + "]"; |
441 |
thinlet.setString(getSelectedTreeNode(), "text", nodeText);
|
442 |
} |
443 |
break;
|
444 |
case ThinletDTD.Property.PROPERTY:
|
445 |
// check input
|
446 |
StringTokenizer st = new StringTokenizer(text, ";"); |
447 |
while (st.hasMoreTokens()) {
|
448 |
String token = st.nextToken();
|
449 |
int equals = token.indexOf('='); |
450 |
if(equals < 0) |
451 |
{ |
452 |
messagebox("Input Error", "Token '" + token + "'\nis missing the '=' character."); |
453 |
return;
|
454 |
} |
455 |
} |
456 |
// remove all existing properties
|
457 |
Enumeration enumx = ThinletWorkarounds.getPropertyKeys(component);
|
458 |
while(enumx.hasMoreElements())
|
459 |
thinlet.putProperty(component, (String)enumx.nextElement(), null); |
460 |
// set new properties
|
461 |
propmgr.setValue(component, property, text); |
462 |
break;
|
463 |
case ThinletDTD.Property.KEYSTROKE:
|
464 |
propmgr.setValue(component, property, text); |
465 |
break;
|
466 |
case ThinletDTD.Property.METHOD:
|
467 |
propmgr.setValue(component, property, text); |
468 |
break;
|
469 |
case ThinletDTD.Property.ICON:
|
470 |
propmgr.setValue(component, property, text); |
471 |
break;
|
472 |
default:
|
473 |
throw new IllegalArgumentException("illegal property type: " + property.getType()); |
474 |
} |
475 |
} |
476 |
catch(IllegalArgumentException e) |
477 |
{ |
478 |
StringBuffer errmsg = new StringBuffer("Illegal argument value"); |
479 |
String exmsg = e.getMessage();
|
480 |
if(exmsg != null && exmsg.length() > 0) |
481 |
errmsg.append(" (").append(exmsg).append(")."); |
482 |
else
|
483 |
errmsg.append(".");
|
484 |
messagebox("Input Error", errmsg.toString());
|
485 |
return;
|
486 |
} |
487 |
|
488 |
updateTableProperty(component, propName); |
489 |
if(debug()) log.fine("property " + propName + " for component " |
490 |
+ ThinletWorkarounds.toString(component) + " changed to "
|
491 |
+ (text.length() > 20 ? (text.substring(0,20) + "...") : text)); |
492 |
setStatusText("Component " + ThinletWorkarounds.toString(component)
|
493 |
+ ": Property " + propName + " changed to " |
494 |
+ (text.length() > 20 ? (text.substring(0,20) + "...") : text)); |
495 |
} |
496 |
} |
497 |
//}}}
|
498 |
|
499 |
|
500 |
//{{{ public void propEditChooseButtonClicked()
|
501 |
public void propEditChooseButtonClicked() |
502 |
{ |
503 |
Object component = getSelectedComponent();
|
504 |
String propName = getSelectedPropertyName();
|
505 |
|
506 |
if(component != null && propName != null) |
507 |
{ |
508 |
String classname = Thinlet.getClass(component);
|
509 |
ThinletDTD.Property property = ThinletDTD.getProperty(classname, propName); |
510 |
switch(property.getType())
|
511 |
{ |
512 |
case ThinletDTD.Property.FONT:
|
513 |
FontChooser fontchooser = new FontChooser(AWTUtils.getFrame(thinlet), "Please choose font:"); |
514 |
fontchooser.setSelectedFont(ThinletWorkarounds.getFont(thinlet, component, propName)); |
515 |
fontchooser.show(); |
516 |
Font font = fontchooser.getSelectedFont();
|
517 |
if(font != null) |
518 |
thinlet.setFont(component, propName, font); |
519 |
break;
|
520 |
case ThinletDTD.Property.COLOR:
|
521 |
ColorChooser colorchooser = new ColorChooser(AWTUtils.getFrame(thinlet), "Please choose color:"); |
522 |
colorchooser.setSelectedColor(ThinletWorkarounds.getColor(component, propName)); |
523 |
colorchooser.show(); |
524 |
Color color = colorchooser.getSelectedColor();
|
525 |
if(color != null) |
526 |
thinlet.setColor(component, propName, color); |
527 |
break;
|
528 |
case ThinletDTD.Property.COMPONENT:
|
529 |
ComponentChooser compchooser = new ComponentChooser(
|
530 |
AWTUtils.getFrame(thinlet), "Please select a named component:",
|
531 |
getTopComponent(), propmgr.getValueString(component, "name"));
|
532 |
compchooser.setSelectedComponent(propmgr.getValueString(component, property)); |
533 |
compchooser.show(); |
534 |
if(!compchooser.isCancelled())
|
535 |
propmgr.setValue(component, property, compchooser.getSelectedComponent()); |
536 |
break;
|
537 |
default:
|
538 |
throw new IllegalArgumentException("unexpected property type: " + property.getType()); |
539 |
} |
540 |
|
541 |
updateTableProperty(component, propName); |
542 |
if(debug()) log.fine("property " + propName + " for component " |
543 |
+ ThinletWorkarounds.toString(component) + " changed to "
|
544 |
+ propmgr.getValueString(component, propName)); |
545 |
setStatusText("Component " + ThinletWorkarounds.toString(component)
|
546 |
+ ": Property " + propName + " changed."); |
547 |
} |
548 |
} |
549 |
//}}}
|
550 |
|
551 |
|
552 |
//{{{ public void propEditReset()
|
553 |
public void propEditReset() |
554 |
{ |
555 |
Object component = getSelectedComponent();
|
556 |
String propName = getSelectedPropertyName();
|
557 |
|
558 |
if(component != null && propName != null) |
559 |
{ |
560 |
ThinletDTD.Property property = PropertyManager.getProperty(component, propName); |
561 |
String defaultValue = PropertyManager.getDefaultValueString(property);
|
562 |
propmgr.setValue(component, property, defaultValue); |
563 |
updateTableProperty(component, propName); |
564 |
updatePropertyEditor(component, propName); |
565 |
setStatusText("Component " + ThinletWorkarounds.toString(component)
|
566 |
+ ": Property " + propName + " reset to default value \"" + defaultValue + "\"."); |
567 |
} |
568 |
} |
569 |
//}}}
|
570 |
|
571 |
|
572 |
//{{{ public void tabSelected(int tabNo)
|
573 |
/**
|
574 |
* serialize the current components tree every time the user switches
|
575 |
* to the XML panel.
|
576 |
*/
|
577 |
public void tabSelected(int tabNo) |
578 |
{ |
579 |
if(debug()) log.fine("tab selected: " + tabNo); |
580 |
if(tabNo == TAB_XML)
|
581 |
{ |
582 |
Object component = getTopComponent();
|
583 |
Object xmltext = thinlet.find("serialize"); |
584 |
if(component != null) |
585 |
thinlet.setString(xmltext, "text", serialize(component));
|
586 |
else
|
587 |
thinlet.setString(xmltext, "text", ""); |
588 |
} |
589 |
} |
590 |
//}}}
|
591 |
|
592 |
|
593 |
//{{{ public void focusgained(Object component)
|
594 |
public void focusgained(Object component) |
595 |
{ |
596 |
Object treenode = getTreeNodeForComponent(component);
|
597 |
setSelectedTreeNode(treenode); |
598 |
} |
599 |
//}}}
|
600 |
|
601 |
//}}} handler actions
|
602 |
|
603 |
|
604 |
//{{{ package protected methods
|
605 |
|
606 |
//{{{ boolean exit()
|
607 |
/**
|
608 |
* Perform the "Exit" action.
|
609 |
*
|
610 |
* @return true if the application should exit.
|
611 |
*/
|
612 |
boolean exit()
|
613 |
{ |
614 |
boolean stateOk = checkForChanges();
|
615 |
if(stateOk)
|
616 |
log.info("ThinG exits.");
|
617 |
return stateOk;
|
618 |
} |
619 |
//}}}
|
620 |
|
621 |
|
622 |
//{{{ Object getTopComponent()
|
623 |
Object getTopComponent()
|
624 |
{ |
625 |
Object treenode = thinlet.getItem(tree, 0); |
626 |
return (treenode != null) ? getComponentForNode(treenode) : null; |
627 |
} |
628 |
//}}}
|
629 |
|
630 |
|
631 |
//{{{ Thinlet getThinlet()
|
632 |
Thinlet getThinlet() |
633 |
{ |
634 |
return thinlet;
|
635 |
} |
636 |
//}}}
|
637 |
|
638 |
|
639 |
//{{{ PropertyManager getPropertyManager()
|
640 |
PropertyManager getPropertyManager() |
641 |
{ |
642 |
return propmgr;
|
643 |
} |
644 |
//}}}
|
645 |
|
646 |
|
647 |
//{{{ File getCurrentFile()
|
648 |
File getCurrentFile()
|
649 |
{ |
650 |
return file;
|
651 |
} |
652 |
//}}}
|
653 |
|
654 |
|
655 |
//{{{ void setStatusText(String text)
|
656 |
void setStatusText(String text) |
657 |
{ |
658 |
thinlet.setString(statusbar, "text", text);
|
659 |
} |
660 |
//}}}
|
661 |
|
662 |
//}}} package protected methods
|
663 |
|
664 |
|
665 |
//{{{ private methods
|
666 |
|
667 |
//{{{ private Object addComponentImpl(String classname, Object parentTreenode, int index, Hashtable attributes)
|
668 |
/**
|
669 |
* Add a component.
|
670 |
*
|
671 |
* @param attributes a key-value list of attributes for the new component.
|
672 |
* Keys and elements should be Strings.
|
673 |
* @return the newly created tree node
|
674 |
*/
|
675 |
private Object addComponentImpl(String classname, Object parentTreenode, int index, Hashtable attributes) |
676 |
{ |
677 |
if(classname.equals("bean")) |
678 |
throw new IllegalArgumentException("Sorry, the <bean> element is not support yet."); |
679 |
|
680 |
Object newNode = Thinlet.create("node"); |
681 |
Object newComponent = Thinlet.create(classname);
|
682 |
|
683 |
if(parentTreenode == null) |
684 |
{ |
685 |
if(!isAddAllowed(classname, null)) |
686 |
throw new IllegalArgumentException("<" + classname |
687 |
+ "> may not be added add top-level.");
|
688 |
// add new component to preview panel
|
689 |
thinlet.add(this.preview, newComponent);
|
690 |
// no elements in tree: add component at top/first element
|
691 |
thinlet.add(this.tree, newNode);
|
692 |
} |
693 |
else
|
694 |
{ |
695 |
// add new component to preview panel
|
696 |
Object containerComponent = getComponentForNode(parentTreenode);
|
697 |
if(!isAddAllowed(classname, containerComponent))
|
698 |
throw new IllegalArgumentException("<" + classname |
699 |
+ "> may not be added to <"
|
700 |
+ Thinlet.getClass(containerComponent) + ">");
|
701 |
if("header".equals(classname)) |
702 |
index = 0; // header is always added as first child |
703 |
else if("popupmenu".equals(classname)) |
704 |
index = -1; // popupmenu is always added as last child |
705 |
else if(index == 0 || index == -1) |
706 |
{ |
707 |
// trying to add normal component as first or last child
|
708 |
int count = thinlet.getCount(parentTreenode);
|
709 |
if(count > 0) |
710 |
{ |
711 |
// check if first child is "header", if yes, add after that
|
712 |
// check if last child is "popupmenu", if yes, add before that
|
713 |
if(index == 0 && Thinlet.getClass(getComponentForNode( |
714 |
thinlet.getItem(parentTreenode, index))).equals("header"))
|
715 |
{ |
716 |
index++; |
717 |
if(index >= count) index = -1; |
718 |
} |
719 |
else if(index == -1 && Thinlet.getClass(getComponentForNode( |
720 |
thinlet.getItem(parentTreenode, count - 1))).equals("popupmenu")) |
721 |
{ |
722 |
index = count - 1;
|
723 |
} |
724 |
} |
725 |
} |
726 |
thinlet.add(containerComponent, newComponent, index); |
727 |
// create new tree node below current tree node
|
728 |
thinlet.add(parentTreenode, newNode, index); |
729 |
} |
730 |
|
731 |
// store tree node/component pair
|
732 |
components.put(newNode, newComponent); |
733 |
|
734 |
// set node text
|
735 |
String nodeText = classname;
|
736 |
if(attributes != null && attributes.get("name") != null) |
737 |
nodeText += " [" + attributes.get("name") + "]"; |
738 |
thinlet.setString(newNode, "text", nodeText);
|
739 |
|
740 |
// mark view as changed
|
741 |
setChanged(true);
|
742 |
|
743 |
// set attributes
|
744 |
if(attributes != null) |
745 |
{ |
746 |
for(Enumeration keys = attributes.keys(); keys.hasMoreElements(); ) |
747 |
{ |
748 |
String key = (String) keys.nextElement(); |
749 |
String value = (String) attributes.get(key); |
750 |
propmgr.setValue(newComponent, key, value); |
751 |
} |
752 |
} |
753 |
|
754 |
ThinletDTD.Widget widget = ThinletDTD.getWidget(classname); |
755 |
if(widget.isInstanceOf("component")) |
756 |
thinlet.setMethod(newComponent, "focusgained", "focusgained(this)", root, this); |
757 |
|
758 |
return newNode;
|
759 |
} |
760 |
//}}}
|
761 |
|
762 |
|
763 |
//{{{ private void updatePropertyEditor(Object component, String propName)
|
764 |
private void updatePropertyEditor(Object component, String propName) |
765 |
{ |
766 |
Object propedit_panel = thinlet.find("propedit_panel"); |
767 |
thinlet.removeAll(propedit_panel); |
768 |
|
769 |
if(propName == null) |
770 |
{ |
771 |
thinlet.setString(thinlet.find("propedit_name"), "text", ""); |
772 |
thinlet.setBoolean(thinlet.find("propedit_reset"), "enabled", false); |
773 |
return;
|
774 |
} |
775 |
|
776 |
String classname = Thinlet.getClass(component);
|
777 |
ThinletDTD.Property property = ThinletDTD.getProperty(classname, propName); |
778 |
Object value = propmgr.getValue(component, property);
|
779 |
if(debug()) log.fine("setup property: " + propName + " for component: " + ThinletWorkarounds.toString(component)); |
780 |
|
781 |
thinlet.setString(thinlet.find("propedit_name"), "text", propName + ":"); |
782 |
thinlet.setBoolean(thinlet.find("propedit_reset"), "enabled", true); |
783 |
|
784 |
switch(property.getType())
|
785 |
{ |
786 |
case ThinletDTD.Property.STRING:
|
787 |
case ThinletDTD.Property.ICON:
|
788 |
case ThinletDTD.Property.KEYSTROKE:
|
789 |
case ThinletDTD.Property.METHOD:
|
790 |
case ThinletDTD.Property.PROPERTY:
|
791 |
Object textfield = Thinlet.create("textfield"); |
792 |
thinlet.add(propedit_panel, textfield); |
793 |
thinlet.setString(textfield, "text", propmgr.getValueString(component, property));
|
794 |
thinlet.setChoice(textfield, "valign", "center"); |
795 |
thinlet.setInteger(textfield, "weightx", 1); |
796 |
thinlet.setInteger(textfield, "weighty", 1); |
797 |
thinlet.setMethod(textfield, "perform", "propEditStringChanged(this.text)", root, this); |
798 |
break;
|
799 |
case ThinletDTD.Property.BOOLEAN:
|
800 |
Object boolTrue = Thinlet.create("checkbox"); |
801 |
Object boolFalse = Thinlet.create("checkbox"); |
802 |
thinlet.add(propedit_panel, boolTrue); |
803 |
thinlet.add(propedit_panel, boolFalse); |
804 |
thinlet.setString(boolTrue, "text", "true"); |
805 |
thinlet.setString(boolTrue, "group", "propedit_panel"); |
806 |
thinlet.setChoice(boolTrue, "valign", "center"); |
807 |
thinlet.setInteger(boolTrue, "weighty", 1); |
808 |
thinlet.setInteger(boolTrue, "mnemonic", 0); |
809 |
thinlet.setMethod(boolTrue, "action", "propEditBooleanChanged(this.text, this.selected)", root, this); |
810 |
thinlet.setString(boolFalse, "text", "false"); |
811 |
thinlet.setString(boolFalse, "group", "propedit_panel"); |
812 |
thinlet.setChoice(boolFalse, "valign", "center"); |
813 |
thinlet.setInteger(boolFalse, "weighty", 1); |
814 |
thinlet.setInteger(boolFalse, "mnemonic", 0); |
815 |
thinlet.setMethod(boolFalse, "action", "propEditBooleanChanged(this.text, this.selected)", root, this); |
816 |
if(((Boolean)value).booleanValue()) |
817 |
thinlet.setBoolean(boolTrue, "selected", true); |
818 |
else
|
819 |
thinlet.setBoolean(boolFalse, "selected", true); |
820 |
break;
|
821 |
case ThinletDTD.Property.INTEGER:
|
822 |
Object spinbox = Thinlet.create("spinbox"); |
823 |
thinlet.add(propedit_panel, spinbox); |
824 |
thinlet.setString(spinbox, "text", ((Integer)value).toString()); |
825 |
thinlet.setChoice(spinbox, "valign", "center"); |
826 |
thinlet.setInteger(spinbox, "weightx", 1); |
827 |
thinlet.setInteger(spinbox, "weighty", 1); |
828 |
thinlet.setMethod(spinbox, "action", "propEditIntegerChanged(this.text)", root, this); |
829 |
break;
|
830 |
case ThinletDTD.Property.CHOICE:
|
831 |
Object combobox = Thinlet.create("combobox"); |
832 |
thinlet.add(propedit_panel, combobox); |
833 |
thinlet.setString(combobox, "text", (String)value); |
834 |
thinlet.setBoolean(combobox, "editable", false); |
835 |
thinlet.setChoice(combobox, "valign", "center"); |
836 |
thinlet.setInteger(combobox, "weightx", 1); |
837 |
thinlet.setInteger(combobox, "weighty", 1); |
838 |
thinlet.setMethod(combobox, "action", "propEditChoiceChanged(this, this.selected)", root, this); |
839 |
String[] choices = property.getChoices(); |
840 |
for(int i = 0; i < choices.length; ++i) |
841 |
{ |
842 |
Object choice = Thinlet.create("choice"); |
843 |
thinlet.add(combobox, choice); |
844 |
thinlet.setString(choice, "text", choices[i]);
|
845 |
if(((String)value).equals(choices[i])) |
846 |
thinlet.setInteger(combobox, "selected", i);
|
847 |
} |
848 |
break;
|
849 |
case ThinletDTD.Property.COLOR:
|
850 |
case ThinletDTD.Property.FONT:
|
851 |
case ThinletDTD.Property.COMPONENT:
|
852 |
Object button = Thinlet.create("button"); |
853 |
thinlet.add(propedit_panel, button); |
854 |
thinlet.setString(button, "text", "Choose..."); |
855 |
thinlet.setInteger(button, "mnemonic", 0); |
856 |
thinlet.setMethod(button, "action", "propEditChooseButtonClicked()", root, this); |
857 |
Font font = ThinletWorkarounds.getFont(thinlet, button, "font"); |
858 |
font = font.deriveFont(10.0f);
|
859 |
thinlet.setFont(button, font); |
860 |
//thinlet.setChoice(button, "valign", "center");
|
861 |
//thinlet.setInteger(button, "weighty", 1);
|
862 |
break;
|
863 |
case ThinletDTD.Property.BEAN:
|
864 |
// only display, not changeable
|
865 |
Object label = Thinlet.create("label"); |
866 |
thinlet.add(propedit_panel, label); |
867 |
thinlet.setString(label, "text", propmgr.getValueString(component, property));
|
868 |
thinlet.setChoice(label, "valign", "center"); |
869 |
thinlet.setInteger(label, "weightx", 1); |
870 |
thinlet.setInteger(label, "weighty", 1); |
871 |
break;
|
872 |
default:
|
873 |
throw new IllegalArgumentException("unexpected property type: " + property.getType()); |
874 |
} |
875 |
} |
876 |
//}}}
|
877 |
|
878 |
|
879 |
//{{{ private void addTableProperties(Object component)
|
880 |
private void addTableProperties(Object component) |
881 |
{ |
882 |
String classname = Thinlet.getClass(component);
|
883 |
ThinletDTD.Property[] properties = ThinletDTD.getProperties(classname);
|
884 |
if(debug()) log.fine("add table properties for component: " + ThinletWorkarounds.toString(component)); |
885 |
for(int i = 0; i < properties.length; i++) |
886 |
if(shouldShowProperty(classname, properties[i].getName()))
|
887 |
addTableProperty(component, properties[i]); |
888 |
} |
889 |
//}}}
|
890 |
|
891 |
|
892 |
//{{{ private void addTableProperty(Object component, ThinletDTD.Property property)
|
893 |
private void addTableProperty(Object component, ThinletDTD.Property property) |
894 |
{ |
895 |
Object row = Thinlet.create("row"); |
896 |
Object cellName = Thinlet.create("cell"); |
897 |
Object cellValue = Thinlet.create("cell"); |
898 |
thinlet.add(props, row); |
899 |
thinlet.add(row, cellName); |
900 |
thinlet.add(row, cellValue); |
901 |
thinlet.setString(cellName, "text", property.getName());
|
902 |
thinlet.setString(cellValue, "text", propmgr.getValueString(component, property));
|
903 |
} |
904 |
//}}}
|
905 |
|
906 |
|
907 |
//{{{ private void removeSelectedComponent()
|
908 |
private void removeSelectedComponent() |
909 |
{ |
910 |
Object treenode = getSelectedTreeNode();
|
911 |
Object component = getSelectedComponent();
|
912 |
if(treenode != null && component != null) |
913 |
{ |
914 |
thinlet.remove(treenode); |
915 |
thinlet.remove(component); |
916 |
propmgr.removeComponent(component); |
917 |
components.remove(treenode); |
918 |
// later FIXME: recursively remove all subcomponents of 'component'
|
919 |
// from 'propmgr' and 'components' hashmap.
|
920 |
treeSelectionChanged(); |
921 |
setChanged(true);
|
922 |
} |
923 |
} |
924 |
//}}}
|
925 |
|
926 |
|
927 |
//{{{ private void updateTableProperty(Object component, String propName)
|
928 |
private void updateTableProperty(Object component, String propName) |
929 |
{ |
930 |
Object cellValue = getSelectedTableCell(1); |
931 |
thinlet.setString(cellValue, "text", propmgr.getValueString(component, propName));
|
932 |
setChanged(true);
|
933 |
} |
934 |
//}}}
|
935 |
|
936 |
|
937 |
//{{{ private static boolean shouldShowProperty(String classname, String prop)
|
938 |
private static boolean shouldShowProperty(String classname, String prop) |
939 |
{ |
940 |
if("for".equals(prop)) |
941 |
{ |
942 |
// show property "for" only for labels, but not it's subclasses
|
943 |
// such as button, checkbox, togglebutton
|
944 |
return classname.equals("label"); |
945 |
} |
946 |
else if("closable".equals(prop) && "dialog".equals(classname)) |
947 |
{ |
948 |
// don't show property "closable" (it has been replaced by "close")
|
949 |
return false; |
950 |
} |
951 |
else
|
952 |
return true; |
953 |
} |
954 |
//}}}
|
955 |
|
956 |
|
957 |
//{{{ private Object getComponentForNode(Object treenode)
|
958 |
private Object getComponentForNode(Object treenode) |
959 |
{ |
960 |
Object component = components.get(treenode);
|
961 |
if(component == null) |
962 |
throw new IllegalArgumentException("something is wrong, no component stored for tree node: " |
963 |
+ ThinletWorkarounds.toString(treenode)); |
964 |
else
|
965 |
return component;
|
966 |
} |
967 |
//}}}
|
968 |
|
969 |
|
970 |
//{{{ private Object getTreeNodeForComponent(Object component)
|
971 |
private Object getTreeNodeForComponent(Object component) |
972 |
{ |
973 |
for(Iterator it = components.entrySet().iterator(); it.hasNext(); ) |
974 |
{ |
975 |
Map.Entry entry = (Map.Entry) it.next(); |
976 |
if(entry.getValue() == component)
|
977 |
return entry.getKey();
|
978 |
} |
979 |
throw new IllegalArgumentException("something is wrong, no tree node stored for component: " |
980 |
+ ThinletWorkarounds.toString(component)); |
981 |
} |
982 |
//}}}
|
983 |
|
984 |
|
985 |
//{{{ private void setSelectedTreeNode(Object treenode)
|
986 |
private void setSelectedTreeNode(Object treenode) |
987 |
{ |
988 |
// selects the treenode, deselects all others
|
989 |
ThinletWorkarounds.setSelectedItem(thinlet, tree, treenode); |
990 |
treeSelectionChanged(); |
991 |
} |
992 |
//}}}
|
993 |
|
994 |
|
995 |
//{{{ private Object getSelectedTreeNode()
|
996 |
private Object getSelectedTreeNode() |
997 |
{ |
998 |
Object treenode = thinlet.getSelectedItem(tree);
|
999 |
return treenode;
|
1000 |
} |
1001 |
//}}}
|
1002 |
|
1003 |
|
1004 |
//{{{ private Object getSelectedComponent()
|
1005 |
private Object getSelectedComponent() |
1006 |
{ |
1007 |
Object treenode = getSelectedTreeNode();
|
1008 |
return (treenode != null) ? getComponentForNode(treenode) : null; |
1009 |
} |
1010 |
//}}}
|
1011 |
|
1012 |
|
1013 |
//{{{ private String getSelectedPropertyName()
|
1014 |
private String getSelectedPropertyName() |
1015 |
{ |
1016 |
Object cell = getSelectedTableCell(0); |
1017 |
if(cell != null) |
1018 |
return thinlet.getString(cell, "text"); |
1019 |
return null; |
1020 |
} |
1021 |
//}}}
|
1022 |
|
1023 |
|
1024 |
//{{{ private String getSelectedPropertyValue()
|
1025 |
private String getSelectedPropertyValue() |
1026 |
{ |
1027 |
Object cell = getSelectedTableCell(1); |
1028 |
if(cell != null) |
1029 |
return thinlet.getString(cell, "text"); |
1030 |
return null; |
1031 |
} |
1032 |
//}}}
|
1033 |
|
1034 |
|
1035 |
//{{{ private Object getSelectedTableCell(int cellNo)
|
1036 |
private Object getSelectedTableCell(int cellNo) |
1037 |
{ |
1038 |
Object treenode = getSelectedTreeNode();
|
1039 |
if(treenode != null) |
1040 |
{ |
1041 |
Object row = thinlet.getSelectedItem(props);
|
1042 |
if(row != null) |
1043 |
return thinlet.getItem(row, cellNo);
|
1044 |
} |
1045 |
return null; |
1046 |
} |
1047 |
//}}}
|
1048 |
|
1049 |
|
1050 |
//{{{ private String getToolbarButtonClassname(Object toolbarButton)
|
1051 |
private String getToolbarButtonClassname(Object toolbarButton) |
1052 |
{ |
1053 |
if("separator".equals(Thinlet.getClass(toolbarButton))) |
1054 |
return null; |
1055 |
|
1056 |
String classname = (String) thinlet.getProperty(toolbarButton, "classname"); |
1057 |
if(classname == null) |
1058 |
{ |
1059 |
String name = thinlet.getString(toolbarButton, "name"); |
1060 |
throw new IllegalArgumentException("toolbar button " + name + ": missing classname property"); |
1061 |
} |
1062 |
return classname;
|
1063 |
} |
1064 |
//}}}
|
1065 |
|
1066 |
|
1067 |
//{{{ private void updateToolbar(Object selectedComponent)
|
1068 |
private void updateToolbar(Object selectedComponent) |
1069 |
{ |
1070 |
ThinletDTD.Widget selectedWidget = null;
|
1071 |
if(selectedComponent != null) |
1072 |
selectedWidget = ThinletDTD.getWidget(Thinlet.getClass(selectedComponent)); |
1073 |
|
1074 |
Object[] items = thinlet.getItems(toolbar); |
1075 |
for(int i = 0; i < items.length; ++i) |
1076 |
{ |
1077 |
String classname = getToolbarButtonClassname(items[i]);
|
1078 |
if(classname != null) |
1079 |
{ |
1080 |
boolean addAllowed = isAddAllowed(classname, selectedComponent, selectedWidget);
|
1081 |
thinlet.setBoolean(items[i], "visible", addAllowed);
|
1082 |
} |
1083 |
} |
1084 |
|
1085 |
// buttons & menus "Cut"/"Copy"/"Delete"/"Paste"
|
1086 |
thinlet.setBoolean(thinlet.find("tb_edit_cut"), "enabled", selectedComponent != null); |
1087 |
thinlet.setBoolean(thinlet.find("tb_edit_copy"), "enabled", selectedComponent != null); |
1088 |
thinlet.setBoolean(thinlet.find("tb_edit_delete"), "enabled", selectedComponent != null); |
1089 |
thinlet.setBoolean(thinlet.find("menu_edit_cut"), "enabled", selectedComponent != null); |
1090 |
thinlet.setBoolean(thinlet.find("menu_edit_copy"), "enabled", selectedComponent != null); |
1091 |
thinlet.setBoolean(thinlet.find("menu_edit_delete"), "enabled", selectedComponent != null); |
1092 |
thinlet.setBoolean(thinlet.find("tb_edit_paste"), "enabled", this.clipboard != null); |
1093 |
thinlet.setBoolean(thinlet.find("menu_edit_paste"), "enabled", this.clipboard != null); |
1094 |
|
1095 |
// buttons "Move Up"/"Move Down"
|
1096 |
thinlet.setBoolean(thinlet.find("tb_edit_moveup"), "enabled", false); |
1097 |
thinlet.setBoolean(thinlet.find("tb_edit_movedown"), "enabled", false); |
1098 |
if(selectedComponent != null) |
1099 |
{ |
1100 |
String classname = Thinlet.getClass(selectedComponent);
|
1101 |
if(!("popupmenu".equals(classname)) && !("header".equals(classname))) |
1102 |
{ |
1103 |
Object treenode = getSelectedTreeNode();
|
1104 |
Object parentTreenode = thinlet.getParent(treenode);
|
1105 |
int count = thinlet.getCount(parentTreenode);
|
1106 |
int index = ThinletWorkarounds.getIndexOfItem(thinlet, parentTreenode, treenode);
|
1107 |
String firstNode = (count > 0) ? Thinlet.getClass(getComponentForNode(thinlet.getItem(parentTreenode, 0))) : ""; |
1108 |
String lastNode = (count > 0) ? Thinlet.getClass(getComponentForNode(thinlet.getItem(parentTreenode, count - 1))) : ""; |
1109 |
// move up is allowed if pos > 0, and item at pos 0 is not a header
|
1110 |
thinlet.setBoolean(thinlet.find("tb_edit_moveup"), "enabled", |
1111 |
index > 0 && !(index == 1 && firstNode.equals("header"))); |
1112 |
// move down is allowed if pos < last pos, and item at last pos is not a popupmenu
|
1113 |
thinlet.setBoolean(thinlet.find("tb_edit_movedown"), "enabled", |
1114 |
index < count-1 && !(index == count - 2 && lastNode.equals("popupmenu"))); |
1115 |
} |
1116 |
} |
1117 |
} |
1118 |
//}}}
|
1119 |
|
1120 |
|
1121 |
//{{{ private void updateTitle()
|
1122 |
private void updateTitle() |
1123 |
{ |
1124 |
Frame frame = AWTUtils.getFrame(thinlet);
|
1125 |
// frame may be null at startup time
|
1126 |
if(frame != null) |
1127 |
{ |
1128 |
StringBuffer title = new StringBuffer("ThinG - "); |
1129 |
if(file == null) |
1130 |
title.append("[untitled]");
|
1131 |
else
|
1132 |
title.append(file.getName()); |
1133 |
if(isChanged)
|
1134 |
title.append(" [modified]");
|
1135 |
frame.setTitle(title.toString()); |
1136 |
} |
1137 |
} |
1138 |
//}}}
|
1139 |
|
1140 |
|
1141 |
//{{{ private boolean isAddAllowed(String classname, Object container)
|
1142 |
/**
|
1143 |
* Check the dtd whether it is allowed to add the widget of the specified
|
1144 |
* classname to the specified container object.
|
1145 |
*
|
1146 |
* @param container the container; may be <code>null</code>, in which
|
1147 |
* case the methods checks allowance for top-level objects such as
|
1148 |
* <code>panel</code> etc.
|
1149 |
*/
|
1150 |
private boolean isAddAllowed(String classname, Object container) |
1151 |
{ |
1152 |
ThinletDTD.Widget containerWidget = null;
|
1153 |
if(container != null) |
1154 |
containerWidget = ThinletDTD.getWidget(Thinlet.getClass(container)); |
1155 |
return isAddAllowed(classname, container, containerWidget);
|
1156 |
} |
1157 |
//}}}
|
1158 |
|
1159 |
|
1160 |
//{{{ private boolean isAddAllowed(String classname, Object container, ThinletDTD.Widget containerWidget)
|
1161 |
/**
|
1162 |
* Check the dtd whether it is allowed to add the widget of the specified
|
1163 |
* classname to the specified container object.
|
1164 |
* Use this method instead of {@link #isAddAllowed(String,Object), if you
|
1165 |
* have already determined the {@link ThinletDTD.Widget widget} for the
|
1166 |
* container object, in order to speed up performance.
|
1167 |
*
|
1168 |
* @param container the container; may be <code>null</code>, in which
|
1169 |
* case the methods checks allowance for top-level objects such as
|
1170 |
* <code>panel</code> etc.
|
1171 |
*/
|
1172 |
private boolean isAddAllowed(String classname, Object container, ThinletDTD.Widget containerWidget) |
1173 |
{ |
1174 |
if(container == null) |
1175 |
{ |
1176 |
// no container selected: only top-level elements allowed
|
1177 |
if(thinlet.getCount(tree) > 0) |
1178 |
return false; |
1179 |
return "panel".equals(classname) |
1180 |
|| "desktop".equals(classname)
|
1181 |
|| "dialog".equals(classname)
|
1182 |
|| "splitpane".equals(classname)
|
1183 |
|| "tabbedpane".equals(classname);
|
1184 |
} |
1185 |
else
|
1186 |
{ |
1187 |
boolean allowed = containerWidget.isSubWidgetAllowed(classname);
|
1188 |
if(!allowed)
|
1189 |
return false; |
1190 |
|
1191 |
// special case for "popupmenu" and "header"
|
1192 |
if("popupmenu".equals(classname) || "header".equals(classname)) |
1193 |
return (thinlet.getWidget(container, classname) == null); |
1194 |
else
|
1195 |
return true; |
1196 |
} |
1197 |
} |
1198 |
//}}}
|
1199 |
|
1200 |
|
1201 |
//{{{ private void init()
|
1202 |
private void init() |
1203 |
{ |
1204 |
propmgr = new PropertyManager(thinlet);
|
1205 |
|
1206 |
try
|
1207 |
{ |
1208 |
if(root != null) |
1209 |
thinlet.remove(root); |
1210 |
root = thinlet.parse("thing.xml", this); |
1211 |
if(debug()) log.fine("Successfully parsed thing.xml"); |
1212 |
} |
1213 |
catch(Exception e) |
1214 |
{ |
1215 |
log.log(Level.SEVERE, "Error parsing thing.xml", e); |
1216 |
} |
1217 |
|
1218 |
components.clear(); |
1219 |
|
1220 |
thinlet.add(root); |
1221 |
thinlet.repaint(root); |
1222 |
|
1223 |
preview = thinlet.find(root, "preview");
|
1224 |
tree = thinlet.find(root, "tree");
|
1225 |
props = thinlet.find(root, "props");
|
1226 |
tabs = thinlet.find(root, "tabs");
|
1227 |
statusbar = thinlet.find(root, "statusbar");
|
1228 |
toolbar = thinlet.find(root, "toolbar");
|
1229 |
|
1230 |
updateToolbar(null);
|
1231 |
updateTitle(); |
1232 |
} |
1233 |
//}}}
|
1234 |
|
1235 |
|
1236 |
//{{{ private void load()
|
1237 |
protected void load() |
1238 |
{ |
1239 |
init(); |
1240 |
|
1241 |
// use a FileReader to read from the file. No need to use a
|
1242 |
// BufferedReader, because Thinlet.parse() uses a buffered stream
|
1243 |
// anyway.
|
1244 |
FileReader reader = null; |
1245 |
try
|
1246 |
{ |
1247 |
reader = new FileReader(this.file); |
1248 |
addComponents(null, -1, reader); |
1249 |
setChanged(false);
|
1250 |
setStatusText(this.file.getAbsolutePath() + " loaded."); |
1251 |
} |
1252 |
catch(ParseException e) |
1253 |
{ |
1254 |
log.log(Level.SEVERE, "Error loading file " + this.file.getAbsolutePath() |
1255 |
+ ", at line: " + e.getErrorOffset(), e);
|
1256 |
messagebox("I/O Error", "Error loading file\n" + this.file.getAbsolutePath() |
1257 |
+ ":\n\n" + e.getMessage() + "\nat line: " + e.getErrorOffset() |
1258 |
+ "\n\nSee log for details.");
|
1259 |
} |
1260 |
catch(Exception e) |
1261 |
{ |
1262 |
log.log(Level.SEVERE, "Error loading file " + this.file.getAbsolutePath(), e); |
1263 |
messagebox("I/O Error", "Error loading file\n" + this.file.getAbsolutePath() |
1264 |
+ ":\n\n" + e.toString() + "\n\nSee log for details."); |
1265 |
} |
1266 |
finally
|
1267 |
{ |
1268 |
if(reader != null) |
1269 |
{ |
1270 |
try { reader.close(); }
|
1271 |
catch(IOException e) { log.log(Level.SEVERE, "Error closing file " + this.file.getAbsolutePath(), e); } |
1272 |
} |
1273 |
} |
1274 |
} |
1275 |
//}}}
|
1276 |
|
1277 |
|
1278 |
//{{{ private void addComponents(Object parentTreenode, int index, String xml)
|
1279 |
private void addComponents(Object parentTreenode, int index, String xml) |
1280 |
{ |
1281 |
try
|
1282 |
{ |
1283 |
addComponents(parentTreenode, index, new StringReader(xml)); |
1284 |
} |
1285 |
catch(ParseException e) |
1286 |
{ |
1287 |
String text = xml.substring(0, Math.min(100, xml.length())) + (xml.length() >= 100 ? "..." : ""); |
1288 |
log.log(Level.SEVERE, "Error adding components from text, at line: " |
1289 |
+ e.getErrorOffset() + "\ntext(0,100):\n" + text, e);
|
1290 |
messagebox("Error", "Error adding components:\n\n" + e.getMessage() |
1291 |
+ "\n\nError is at line " + e.getErrorOffset() + " of:\n" + text |
1292 |
+ "\n\nSee log for details.");
|
1293 |
} |
1294 |
catch(Exception e) |
1295 |
{ |
1296 |
String text = xml.substring(0, Math.min(100, xml.length())) + (xml.length() >= 100 ? "..." : ""); |
1297 |
log.log(Level.SEVERE, "Error adding components from text\ntext(0,100):\n" + text, e); |
1298 |
messagebox("Error", "Error adding components:\n\n" + e.toString() |
1299 |
+ "\n\nXML:\n" + text
|
1300 |
+ "\n\nSee log for details.");
|
1301 |
} |
1302 |
} |
1303 |
//}}}
|
1304 |
|
1305 |
|
1306 |
//{{{ private void addComponents(Object parentTreenode, int index, Reader reader) throws Exception
|
1307 |
private void addComponents(Object parentTreenode, int index, Reader reader) throws Exception |
1308 |
{ |
1309 |
// create a handler that adds the elements
|
1310 |
ThinletReaderHandler handler = new ThinletReaderHandler(parentTreenode, index);
|
1311 |
// parse from reader, using the handler
|
1312 |
ThinletReader thinletreader = new ThinletReader();
|
1313 |
thinletreader.parse(reader, handler); |
1314 |
updateToolbar(getSelectedComponent()); |
1315 |
} |
1316 |
//}}}
|
1317 |
|
1318 |
|
1319 |
//{{{ private void save(boolean askForFile)
|
1320 |
/**
|
1321 |
* Save the current document.
|
1322 |
*
|
1323 |
* @param askForFile if true present a "Save As" dialog.
|
1324 |
* @return true if the file has been saved, false if the save operation
|
1325 |
* has been cancelled because of an error or user interaction.
|
1326 |
*/
|
1327 |
private boolean save(boolean askForFile) |
1328 |
{ |
1329 |
if(isEmpty())
|
1330 |
{ |
1331 |
messagebox("Save Error", "Cannot save empty document.\nPlease add components first."); |
1332 |
return false; |
1333 |
} |
1334 |
|
1335 |
File newFile = this.file; |
1336 |
if(askForFile || this.file == null) |
1337 |
newFile = getFile(FileChooser.MODE_SAVE, "Save As...", thinlet, this.file); |
1338 |
if(newFile == null) |
1339 |
return false; |
1340 |
|
1341 |
if(newFile.exists() && !newFile.equals(this.file)) |
1342 |
{ |
1343 |
int answer = new MessageDialog(AWTUtils.getFrame(thinlet), "File exists", |
1344 |
"File exists.\nOverwrite existing file?", MessageDialog.MODE_YES_NO).show();
|
1345 |
if(answer != MessageDialog.ACTION_YES)
|
1346 |
return false; |
1347 |
} |
1348 |
|
1349 |
setSelectedFile(newFile); |
1350 |
|
1351 |
if(this.file.exists() && !this.file.canWrite()) |
1352 |
{ |
1353 |
log.severe("Cannot write to file: " + this.file.getAbsolutePath()); |
1354 |
messagebox("I/O Error", "Cannot write to file\n" + file.getAbsolutePath()); |
1355 |
return false; |
1356 |
} |
1357 |
|
1358 |
String text = serialize(getTopComponent());
|
1359 |
|
1360 |
BufferedWriter writer = null; |
1361 |
try
|
1362 |
{ |
1363 |
writer = new BufferedWriter(new FileWriter(this.file)); |
1364 |
writer.write(text); |
1365 |
setChanged(false);
|
1366 |
setStatusText(this.file.getAbsolutePath() + " saved."); |
1367 |
return true; |
1368 |
} |
1369 |
catch(Exception e) |
1370 |
{ |
1371 |
log.log(Level.SEVERE, "Error saving file " + this.file.getAbsolutePath(), e); |
1372 |
messagebox("I/O Error", "Error saving file\n" + this.file.getAbsolutePath() |
1373 |
+ ":\n\n" + e.toString() + "\n\nSee log for details."); |
1374 |
} |
1375 |
finally
|
1376 |
{ |
1377 |
if(writer != null) |
1378 |
{ |
1379 |
try { writer.close(); }
|
1380 |
catch(IOException e) { log.log(Level.SEVERE, "Error closing file " + this.file.getAbsolutePath(), e); } |
1381 |
} |
1382 |
} |
1383 |
|
1384 |
// if we come here it's because of an exception
|
1385 |
return false; |
1386 |
} |
1387 |
//}}}
|
1388 |
|
1389 |
|
1390 |
//{{{ private void setSelectedFile(File selectedFile)
|
1391 |
protected void setSelectedFile(File selectedFile) |
1392 |
{ |
1393 |
this.file = selectedFile;
|
1394 |
} |
1395 |
//}}}
|
1396 |
|
1397 |
|
1398 |
//{{{ private void setChanged(boolean isChanged)
|
1399 |
private void setChanged(boolean isChanged) |
1400 |
{ |
1401 |
if(this.isChanged != isChanged) |
1402 |
{ |
1403 |
this.isChanged = isChanged;
|
1404 |
updateTitle(); |
1405 |
} |
1406 |
|
1407 |
// update XML preview, if the tab is currently selected
|
1408 |
int tabNo = thinlet.getInteger(tabs, "selected"); |
1409 |
if(tabNo == TAB_XML)
|
1410 |
{ |
1411 |
Object component = getTopComponent();
|
1412 |
if(component != null) |
1413 |
thinlet.setString(thinlet.find("serialize"), "text", serialize(component)); |
1414 |
} |
1415 |
} |
1416 |
//}}}
|
1417 |
|
1418 |
|
1419 |
//{{{ private boolean checkForChanges()
|
1420 |
/**
|
1421 |
* Check whether the current document is changed. If yes, ask to save the
|
1422 |
* file with a message box saying "Save changes? YES/NO/CANCEL".
|
1423 |
*
|
1424 |
* @return false if the user cancelled the operation, either by clicking
|
1425 |
* on CANCEL, or by cancelling the "Save As" dialog, otherwise true.
|
1426 |
*/
|
1427 |
private boolean checkForChanges() |
1428 |
{ |
1429 |
if(isChanged)
|
1430 |
{ |
1431 |
String msg = (this.file == null) ? "Save changes?" |
1432 |
: "Save changes to " + file.getName() + "?"; |
1433 |
int answer = new MessageDialog(AWTUtils.getFrame(thinlet), |
1434 |
"File not saved", msg, MessageDialog.MODE_YES_NO_CANCEL).show();
|
1435 |
if(answer == MessageDialog.ACTION_CANCEL)
|
1436 |
return false; |
1437 |
else if(answer == MessageDialog.ACTION_YES) |
1438 |
return save(false); |
1439 |
} |
1440 |
return true; |
1441 |
} |
1442 |
//}}}
|
1443 |
|
1444 |
|
1445 |
//{{{ private static File getFile(int mode, String title, Thinlet thinlet, File defaultFile)
|
1446 |
private static File getFile(int mode, String title, Thinlet thinlet, File defaultFile) |
1447 |
{ |
1448 |
FileChooser chooser = new FileChooser(AWTUtils.getFrame(thinlet), title, mode);
|
1449 |
chooser.setFileFilters(new FileFilter[] |
1450 |
{ |
1451 |
new ExtensionFileFilter("xml", "XML files (*.xml)"), |
1452 |
new FileFilter() |
1453 |
{ |
1454 |
public boolean accept(File dir) { return true; } |
1455 |
public String getDescription() { return "All files (*.*)"; } |
1456 |
} |
1457 |
}); |
1458 |
if(defaultFile != null) |
1459 |
chooser.setSelectedFile(defaultFile); |
1460 |
else
|
1461 |
{ |
1462 |
String lastDir = Settings.getLastDir();
|
1463 |
if(lastDir != null) |
1464 |
chooser.setSelectedFile(new File(lastDir)); |
1465 |
} |
1466 |
|
1467 |
// show file dialog
|
1468 |
chooser.show(); |
1469 |
|
1470 |
File selectedFile = chooser.getSelectedFile();
|
1471 |
if(selectedFile == null) |
1472 |
return null; |
1473 |
|
1474 |
File dir = selectedFile.isDirectory() ? selectedFile : selectedFile.getParentFile();
|
1475 |
Settings.setLastDir(dir.getAbsolutePath()); |
1476 |
|
1477 |
return selectedFile;
|
1478 |
} |
1479 |
//}}}
|
1480 |
|
1481 |
|
1482 |
//{{{ private void moveSelectedComponent(int offset)
|
1483 |
private void moveSelectedComponent(int offset) |
1484 |
{ |
1485 |
Object treenode = getSelectedTreeNode();
|
1486 |
if(treenode == null) |
1487 |
{ |
1488 |
setStatusText("No selection.");
|
1489 |
return;
|
1490 |
} |
1491 |
Object parentTreenode = thinlet.getParent(treenode);
|
1492 |
// remember position of selected treenode
|
1493 |
int index = ThinletWorkarounds.getIndexOfItem(thinlet, parentTreenode, treenode);
|
1494 |
// remember selected component tree (as serialized string)
|
1495 |
String xml = serialize(getSelectedComponent());
|
1496 |
// remove selected component tree
|
1497 |
removeSelectedComponent(); |
1498 |
// add remembered component tree one index above
|
1499 |
addComponents(parentTreenode, index + offset, xml); |
1500 |
// select component at new position
|
1501 |
treenode = thinlet.getItem(parentTreenode, index + offset); |
1502 |
setSelectedTreeNode(treenode); |
1503 |
if(offset > 0) |
1504 |
setStatusText("Moved selected component(s) down.");
|
1505 |
else
|
1506 |
setStatusText("Moved selected component(s) up.");
|
1507 |
} |
1508 |
//}}}
|
1509 |
|
1510 |
|
1511 |
//{{{ private boolean isEmpty()
|
1512 |
private boolean isEmpty() |
1513 |
{ |
1514 |
return (thinlet.getCount(tree) == 0); |
1515 |
} |
1516 |
//}}}
|
1517 |
|
1518 |
|
1519 |
//{{{ private String serialize(Object component, boolean addCustomHeader)
|
1520 |
private String serialize(Object component) |
1521 |
{ |
1522 |
if(debug()) log.fine("serializing " + ThinletWorkarounds.toString(component) + " ..."); |
1523 |
long start = System.currentTimeMillis(); |
1524 |
String text = new Serializer(thinlet, propmgr).serialize(component, |
1525 |
Settings.getSerializeIndentSize(), |
1526 |
Settings.getSerializeIndentChar(), |
1527 |
Settings.getSerializeHeader()); |
1528 |
long stop = System.currentTimeMillis(); |
1529 |
if(debug()) log.fine("serialize took " + (stop-start) + "ms."); |
1530 |
return text;
|
1531 |
} |
1532 |
//}}}
|
1533 |
|
1534 |
|
1535 |
//{{{ private void messagebox(String title, String msg)
|
1536 |
private void messagebox(String title, String msg) |
1537 |
{ |
1538 |
setStatusText(title + ": " + msg.replace('\n', ' ').replace('\r', ' ')); |
1539 |
new MessageDialog(AWTUtils.getFrame(thinlet), title, msg).show();
|
1540 |
} |
1541 |
//}}}
|
1542 |
|
1543 |
//}}} private methods
|
1544 |
|
1545 |
|
1546 |
//{{{ inner classes
|
1547 |
|
1548 |
//{{{ inner class ThinletReaderHandler
|
1549 |
/**
|
1550 |
* A parser handler that adds components to the preview.
|
1551 |
*/
|
1552 |
private class ThinletReaderHandler implements ThinletReader.SAXHandler |
1553 |
{ |
1554 |
private Stack stack = new Stack(); |
1555 |
private int index = -1; |
1556 |
|
1557 |
/**
|
1558 |
* Create a new handler that starts adding at top-level.
|
1559 |
*/
|
1560 |
public ThinletReaderHandler()
|
1561 |
{ |
1562 |
} |
1563 |
|
1564 |
/**
|
1565 |
* Create a new handler that starts adding at the specified parent
|
1566 |
* tree node.
|
1567 |
*
|
1568 |
* @param parentTreenode the parent tree node.
|
1569 |
* @param index where to add below the parent tree node.
|
1570 |
*/
|
1571 |
public ThinletReaderHandler(Object parentTreenode, int index) |
1572 |
{ |
1573 |
this.index = index;
|
1574 |
stack.push(parentTreenode); |
1575 |
} |
1576 |
|
1577 |
public void startElement(String name, Hashtable attributelist) |
1578 |
{ |
1579 |
if(debug()) log.fine("add <" + name + "> " + attributelist); |
1580 |
Object parentTreenode = stack.empty() ? null : stack.peek(); |
1581 |
Object newTreeNode = addComponentImpl(name, parentTreenode, index, attributelist);
|
1582 |
stack.push(newTreeNode); |
1583 |
// add at end from now on
|
1584 |
this.index = -1; |
1585 |
} |
1586 |
|
1587 |
public void endElement() |
1588 |
{ |
1589 |
Object treenode = stack.pop();
|
1590 |
} |
1591 |
} |
1592 |
//}}}
|
1593 |
|
1594 |
//}}} inner classes
|
1595 |
|
1596 |
} |
1597 |
|