Statistics
| Revision:

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