~ubuntu-branches/ubuntu/precise/weka/precise

« back to all changes in this revision

Viewing changes to weka/gui/arffviewer/ArffTableModel.java

  • Committer: Bazaar Package Importer
  • Author(s): Soeren Sonnenburg
  • Date: 2008-02-24 09:18:45 UTC
  • Revision ID: james.westby@ubuntu.com-20080224091845-1l8zy6fm6xipbzsr
Tags: upstream-3.5.7+tut1
ImportĀ upstreamĀ versionĀ 3.5.7+tut1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *    This program is free software; you can redistribute it and/or modify
 
3
 *    it under the terms of the GNU General Public License as published by
 
4
 *    the Free Software Foundation; either version 2 of the License, or
 
5
 *    (at your option) any later version.
 
6
 *
 
7
 *    This program is distributed in the hope that it will be useful,
 
8
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
9
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
10
 *    GNU General Public License for more details.
 
11
 *
 
12
 *    You should have received a copy of the GNU General Public License
 
13
 *    along with this program; if not, write to the Free Software
 
14
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
15
 */
 
16
 
 
17
/*
 
18
 * ArffTableModel.java
 
19
 * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand
 
20
 *
 
21
 */
 
22
 
 
23
package weka.gui.arffviewer;
 
24
 
 
25
import weka.core.Attribute;
 
26
import weka.core.Instance;
 
27
import weka.core.Instances;
 
28
import weka.core.Undoable;
 
29
import weka.core.converters.AbstractFileLoader;
 
30
import weka.core.converters.ConverterUtils;
 
31
import weka.filters.Filter;
 
32
import weka.filters.unsupervised.attribute.Reorder;
 
33
import weka.gui.ComponentHelper;
 
34
 
 
35
import java.io.BufferedInputStream;
 
36
import java.io.BufferedOutputStream;
 
37
import java.io.File;
 
38
import java.io.FileInputStream;
 
39
import java.io.FileOutputStream;
 
40
import java.io.ObjectInputStream;
 
41
import java.io.ObjectOutputStream;
 
42
import java.util.Arrays;
 
43
import java.util.HashSet;
 
44
import java.util.Iterator;
 
45
import java.util.Vector;
 
46
 
 
47
import javax.swing.JOptionPane;
 
48
import javax.swing.event.TableModelEvent;
 
49
import javax.swing.event.TableModelListener;
 
50
import javax.swing.table.TableModel;
 
51
 
 
52
/**
 
53
 * The model for the Arff-Viewer.
 
54
 *
 
55
 *
 
56
 * @author FracPete (fracpete at waikato dot ac dot nz)
 
57
 * @version $Revision: 1.8 $ 
 
58
 */
 
59
public class ArffTableModel 
 
60
  implements TableModel, Undoable {
 
61
  
 
62
  /** the listeners */
 
63
  private HashSet m_Listeners;
 
64
  /** the data */
 
65
  private Instances m_Data;
 
66
  /** whether notfication is enabled */
 
67
  private boolean m_NotificationEnabled;
 
68
  /** whether undo is active */
 
69
  private boolean m_UndoEnabled;
 
70
  /** whether to ignore changes, i.e. not adding to undo history */
 
71
  private boolean m_IgnoreChanges;
 
72
  /** the undo list (contains temp. filenames) */
 
73
  private Vector m_UndoList;
 
74
  /** whether the table is read-only */
 
75
  private boolean m_ReadOnly;
 
76
  
 
77
  /**
 
78
   * performs some initialization
 
79
   */
 
80
  private ArffTableModel() {
 
81
    super();
 
82
    
 
83
    m_Listeners           = new HashSet();
 
84
    m_Data                = null;
 
85
    m_NotificationEnabled = true;
 
86
    m_UndoList            = new Vector();
 
87
    m_IgnoreChanges       = false;
 
88
    m_UndoEnabled         = true;
 
89
    m_ReadOnly            = false;
 
90
  }
 
91
  
 
92
  /**
 
93
   * initializes the object and loads the given file
 
94
   * 
 
95
   * @param filename    the file to load
 
96
   */
 
97
  public ArffTableModel(String filename) {
 
98
    this();
 
99
    
 
100
    if ( (filename != null) && (!filename.equals("")) )
 
101
      loadFile(filename);
 
102
  }
 
103
  
 
104
  /**
 
105
   * initializes the model with the given data
 
106
   * 
 
107
   * @param data        the data to use
 
108
   */
 
109
  public ArffTableModel(Instances data) {
 
110
    this();
 
111
    
 
112
    this.m_Data = data;
 
113
  }
 
114
 
 
115
  /**
 
116
   * returns whether the notification of changes is enabled
 
117
   * 
 
118
   * @return            true if notification of changes is enabled
 
119
   */
 
120
  public boolean isNotificationEnabled() {
 
121
    return m_NotificationEnabled;
 
122
  }
 
123
  
 
124
  /**
 
125
   * sets whether the notification of changes is enabled
 
126
   * 
 
127
   * @param enabled     enables/disables the notification
 
128
   */
 
129
  public void setNotificationEnabled(boolean enabled) {
 
130
    m_NotificationEnabled = enabled;
 
131
  }
 
132
 
 
133
  /**
 
134
   * returns whether undo support is enabled
 
135
   * 
 
136
   * @return            true if undo support is enabled
 
137
   */
 
138
  public boolean isUndoEnabled() {
 
139
    return m_UndoEnabled;
 
140
  }
 
141
  
 
142
  /**
 
143
   * sets whether undo support is enabled
 
144
   * 
 
145
   * @param enabled     whether to enable/disable undo support
 
146
   */
 
147
  public void setUndoEnabled(boolean enabled) {
 
148
    m_UndoEnabled = enabled;
 
149
  }
 
150
 
 
151
  /**
 
152
   * returns whether the model is read-only
 
153
   * 
 
154
   * @return            true if model is read-only
 
155
   */
 
156
  public boolean isReadOnly() {
 
157
    return m_ReadOnly;
 
158
  }
 
159
  
 
160
  /**
 
161
   * sets whether the model is read-only
 
162
   * 
 
163
   * @param value       if true the model is set to read-only
 
164
   */
 
165
  public void setReadOnly(boolean value) {
 
166
    m_ReadOnly = value;
 
167
  }
 
168
  
 
169
  /**
 
170
   * loads the specified ARFF file
 
171
   * 
 
172
   * @param filename    the file to load
 
173
   */
 
174
  private void loadFile(String filename) {
 
175
    AbstractFileLoader          loader;
 
176
    
 
177
    loader = ConverterUtils.getLoaderForFile(filename);
 
178
    
 
179
    if (loader != null) {
 
180
      try {
 
181
        loader.setFile(new File(filename));
 
182
        m_Data = loader.getDataSet();
 
183
      }
 
184
      catch (Exception e) {
 
185
        ComponentHelper.showMessageBox(
 
186
            null, 
 
187
            "Error loading file...", 
 
188
            e.toString(), 
 
189
            JOptionPane.OK_CANCEL_OPTION,
 
190
            JOptionPane.ERROR_MESSAGE );
 
191
        System.out.println(e);
 
192
        m_Data = null;
 
193
      }
 
194
    }
 
195
  }
 
196
  
 
197
  /**
 
198
   * sets the data
 
199
   * 
 
200
   * @param data        the data to use
 
201
   */
 
202
  public void setInstances(Instances data) {
 
203
    m_Data = data;
 
204
  }
 
205
  
 
206
  /**
 
207
   * returns the data
 
208
   * 
 
209
   * @return            the current data
 
210
   */
 
211
  public Instances getInstances() {
 
212
    return m_Data;
 
213
  }
 
214
  
 
215
  /**
 
216
   * returns the attribute at the given index, can be NULL if not an attribute
 
217
   * column
 
218
   * 
 
219
   * @param columnIndex         the index of the column
 
220
   * @return                    the attribute at the position
 
221
   */
 
222
  public Attribute getAttributeAt(int columnIndex) {
 
223
    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) )
 
224
      return m_Data.attribute(columnIndex - 1);
 
225
    else
 
226
      return null;
 
227
  }
 
228
  
 
229
  /**
 
230
   * returns the TYPE of the attribute at the given position
 
231
   * 
 
232
   * @param columnIndex         the index of the column
 
233
   * @return                    the attribute type
 
234
   */
 
235
  public int getType(int columnIndex) {
 
236
    return getType(0, columnIndex);
 
237
  }
 
238
  
 
239
  /**
 
240
   * returns the TYPE of the attribute at the given position
 
241
   * 
 
242
   * @param rowIndex            the index of the row
 
243
   * @param columnIndex         the index of the column
 
244
   * @return                    the attribute type
 
245
   */
 
246
  public int getType(int rowIndex, int columnIndex) {
 
247
    int            result;
 
248
    
 
249
    result = Attribute.STRING;
 
250
    
 
251
    if (    (rowIndex >= 0) && (rowIndex < getRowCount())
 
252
         && (columnIndex > 0) && (columnIndex < getColumnCount()) )
 
253
      result = m_Data.instance(rowIndex).attribute(columnIndex - 1).type();
 
254
    
 
255
    return result;
 
256
  }
 
257
  
 
258
  /**
 
259
   * deletes the attribute at the given col index. notifies the listeners.
 
260
   * 
 
261
   * @param columnIndex     the index of the attribute to delete
 
262
   */
 
263
  public void deleteAttributeAt(int columnIndex) {
 
264
    deleteAttributeAt(columnIndex, true);
 
265
  }
 
266
  
 
267
  /**
 
268
   * deletes the attribute at the given col index
 
269
   * 
 
270
   * @param columnIndex     the index of the attribute to delete
 
271
   * @param notify          whether to notify the listeners
 
272
   */
 
273
  public void deleteAttributeAt(int columnIndex, boolean notify) {
 
274
    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) {
 
275
      if (!m_IgnoreChanges)
 
276
        addUndoPoint();
 
277
      m_Data.deleteAttributeAt(columnIndex - 1);
 
278
      if (notify) 
 
279
        notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
 
280
    }
 
281
  }
 
282
  
 
283
  /**
 
284
   * deletes the attributes at the given indices
 
285
   * 
 
286
   * @param columnIndices       the column indices
 
287
   */
 
288
  public void deleteAttributes(int[] columnIndices) {
 
289
    int            i;
 
290
    
 
291
    Arrays.sort(columnIndices);
 
292
    
 
293
    addUndoPoint();
 
294
 
 
295
    m_IgnoreChanges = true;
 
296
    for (i = columnIndices.length - 1; i >= 0; i--)
 
297
      deleteAttributeAt(columnIndices[i], false);
 
298
    m_IgnoreChanges = false;
 
299
 
 
300
    notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
 
301
  }
 
302
  
 
303
  /**
 
304
   * renames the attribute at the given col index
 
305
   * 
 
306
   * @param columnIndex         the index of the column
 
307
   * @param newName             the new name of the attribute
 
308
   */
 
309
  public void renameAttributeAt(int columnIndex, String newName) {
 
310
    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) {
 
311
      addUndoPoint();
 
312
      m_Data.renameAttribute(columnIndex - 1, newName);
 
313
      notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
 
314
    }
 
315
  }
 
316
  
 
317
  /**
 
318
   * sets the attribute at the given col index as the new class attribute, i.e.
 
319
   * it moves it to the end of the attributes
 
320
   * 
 
321
   * @param columnIndex         the index of the column
 
322
   */
 
323
  public void attributeAsClassAt(int columnIndex) {
 
324
    Reorder     reorder;
 
325
    String      order;
 
326
    int         i;
 
327
    
 
328
    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) {
 
329
      addUndoPoint();
 
330
      
 
331
      try {
 
332
        // build order string (1-based!)
 
333
        order = "";
 
334
        for (i = 1; i < m_Data.numAttributes() + 1; i++) {
 
335
          // skip new class
 
336
          if (i == columnIndex)
 
337
            continue;
 
338
          
 
339
          if (!order.equals(""))
 
340
            order += ",";
 
341
          order += Integer.toString(i);
 
342
        }
 
343
        if (!order.equals(""))
 
344
          order += ",";
 
345
        order += Integer.toString(columnIndex);
 
346
        
 
347
        // process data
 
348
        reorder = new Reorder();
 
349
        reorder.setAttributeIndices(order);
 
350
        reorder.setInputFormat(m_Data);
 
351
        m_Data = Filter.useFilter(m_Data, reorder);
 
352
        
 
353
        // set class index
 
354
        m_Data.setClassIndex(m_Data.numAttributes() - 1);
 
355
      }
 
356
      catch (Exception e) {
 
357
        e.printStackTrace();
 
358
        undo();
 
359
      }
 
360
      
 
361
      notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
 
362
    }
 
363
  }
 
364
  
 
365
  /**
 
366
   * deletes the instance at the given index
 
367
   * 
 
368
   * @param rowIndex            the index of the row
 
369
   */
 
370
  public void deleteInstanceAt(int rowIndex) {
 
371
    deleteInstanceAt(rowIndex, true);
 
372
  }
 
373
  
 
374
  /**
 
375
   * deletes the instance at the given index
 
376
   * 
 
377
   * @param rowIndex            the index of the row
 
378
   * @param notify              whether to notify the listeners
 
379
   */
 
380
  public void deleteInstanceAt(int rowIndex, boolean notify) {
 
381
    if ( (rowIndex >= 0) && (rowIndex < getRowCount()) ) {
 
382
      if (!m_IgnoreChanges)
 
383
        addUndoPoint();
 
384
      m_Data.delete(rowIndex);
 
385
      if (notify)
 
386
        notifyListener(
 
387
            new TableModelEvent(
 
388
                this, rowIndex, rowIndex, 
 
389
                TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
 
390
    }
 
391
  }
 
392
  
 
393
  /**
 
394
   * deletes the instances at the given positions
 
395
   * 
 
396
   * @param rowIndices          the indices to delete
 
397
   */
 
398
  public void deleteInstances(int[] rowIndices) {
 
399
    int               i;
 
400
    
 
401
    Arrays.sort(rowIndices);
 
402
    
 
403
    addUndoPoint();
 
404
    
 
405
    m_IgnoreChanges = true;
 
406
    for (i = rowIndices.length - 1; i >= 0; i--)
 
407
      deleteInstanceAt(rowIndices[i], false);
 
408
    m_IgnoreChanges = false;
 
409
 
 
410
    notifyListener(
 
411
        new TableModelEvent(
 
412
            this, rowIndices[0], rowIndices[rowIndices.length - 1], 
 
413
            TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE));
 
414
  }
 
415
  
 
416
  /**
 
417
   * sorts the instances via the given attribute
 
418
   * 
 
419
   * @param columnIndex         the index of the column
 
420
   */
 
421
  public void sortInstances(int columnIndex) {
 
422
    if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) {
 
423
      addUndoPoint();
 
424
      m_Data.sort(columnIndex - 1);
 
425
      notifyListener(new TableModelEvent(this));
 
426
    }
 
427
  }
 
428
  
 
429
  /**
 
430
   * returns the column of the given attribute name, -1 if not found
 
431
   * 
 
432
   * @param name                the name of the attribute
 
433
   * @return                    the column index or -1 if not found
 
434
   */
 
435
  public int getAttributeColumn(String name) {
 
436
    int            i;
 
437
    int            result;
 
438
    
 
439
    result = -1;
 
440
    
 
441
    for (i = 0; i < m_Data.numAttributes(); i++) {
 
442
      if (m_Data.attribute(i).name().equals(name)) {
 
443
        result = i + 1;
 
444
        break;
 
445
      }
 
446
    }
 
447
    
 
448
    return result;
 
449
  }
 
450
  
 
451
  /**
 
452
   * returns the most specific superclass for all the cell values in the 
 
453
   * column (always String)
 
454
   * 
 
455
   * @param columnIndex         the column index
 
456
   * @return                    the class of the column
 
457
   */
 
458
  public Class getColumnClass(int columnIndex) {
 
459
    Class       result;
 
460
    
 
461
    result = null;
 
462
    
 
463
    if ( (columnIndex >= 0) && (columnIndex < getColumnCount()) ) {
 
464
      if (columnIndex == 0)
 
465
        result = Integer.class;
 
466
      else if (getType(columnIndex) == Attribute.NUMERIC)
 
467
        result = Double.class;
 
468
      else
 
469
        result = String.class;   // otherwise no input of "?"!!!
 
470
    }
 
471
    
 
472
    return result;
 
473
  }
 
474
  
 
475
  /**
 
476
   * returns the number of columns in the model
 
477
   * 
 
478
   * @return            the number of columns
 
479
   */
 
480
  public int getColumnCount() {
 
481
    int         result;
 
482
    
 
483
    result = 1;
 
484
    if (m_Data != null)
 
485
      result += m_Data.numAttributes();
 
486
    
 
487
    return result;
 
488
  }
 
489
  
 
490
  /**
 
491
   * checks whether the column represents the class or not
 
492
   * 
 
493
   * @param columnIndex         the index of the column
 
494
   * @return                    true if the column is the class attribute
 
495
   */
 
496
  private boolean isClassIndex(int columnIndex) {
 
497
    boolean        result;
 
498
    int            index;
 
499
    
 
500
    index  = m_Data.classIndex();
 
501
    result =    ((index == - 1) && (m_Data.numAttributes() == columnIndex))
 
502
             || (index == columnIndex - 1);
 
503
    
 
504
    return result;
 
505
  }
 
506
  
 
507
  /**
 
508
   * returns the name of the column at columnIndex
 
509
   * 
 
510
   * @param columnIndex         the index of the column
 
511
   * @return                    the name of the column
 
512
   */
 
513
  public String getColumnName(int columnIndex) {
 
514
    String      result;
 
515
    
 
516
    result = "";
 
517
    
 
518
    if ( (columnIndex >= 0) && (columnIndex < getColumnCount()) ) {
 
519
      if (columnIndex == 0) {
 
520
        result = "<html><center>No.<br><font size=\"-2\">&nbsp;</font></center></html>";
 
521
      }
 
522
      else {
 
523
        if (m_Data != null) {
 
524
          if ( (columnIndex - 1 < m_Data.numAttributes()) ) {
 
525
            result = "<html><center>";
 
526
            // name
 
527
            if (isClassIndex(columnIndex))
 
528
              result +=   "<b>" 
 
529
                + m_Data.attribute(columnIndex - 1).name() 
 
530
                + "</b>";
 
531
            else
 
532
              result += m_Data.attribute(columnIndex - 1).name();
 
533
            
 
534
            // attribute type
 
535
            switch (getType(columnIndex)) {
 
536
              case Attribute.DATE: 
 
537
                result += "<br><font size=\"-2\">Date</font>";
 
538
                break;
 
539
              case Attribute.NOMINAL:
 
540
                result += "<br><font size=\"-2\">Nominal</font>";
 
541
                break;
 
542
              case Attribute.STRING:
 
543
                result += "<br><font size=\"-2\">String</font>";
 
544
                break;
 
545
              case Attribute.NUMERIC:
 
546
                result += "<br><font size=\"-2\">Numeric</font>";
 
547
                break;
 
548
              case Attribute.RELATIONAL:
 
549
                result += "<br><font size=\"-2\">Relational</font>";
 
550
                break;
 
551
              default:
 
552
                result += "<br><font size=\"-2\">???</font>";
 
553
            }
 
554
            
 
555
            result += "</center></html>";
 
556
          }
 
557
        }
 
558
      }
 
559
    }
 
560
    
 
561
    return result;
 
562
  }
 
563
  
 
564
  /**
 
565
   * returns the number of rows in the model
 
566
   * 
 
567
   * @return            the number of rows
 
568
   */
 
569
  public int getRowCount() {
 
570
    if (m_Data == null)
 
571
      return 0;
 
572
    else
 
573
      return m_Data.numInstances(); 
 
574
  }
 
575
  
 
576
  /**
 
577
   * checks whether the value at the given position is missing
 
578
   * 
 
579
   * @param rowIndex            the row index
 
580
   * @param columnIndex         the column index
 
581
   * @return                    true if the value at the position is missing
 
582
   */
 
583
  public boolean isMissingAt(int rowIndex, int columnIndex) {
 
584
    boolean           result;
 
585
    
 
586
    result = false;
 
587
    
 
588
    if (    (rowIndex >= 0) && (rowIndex < getRowCount())
 
589
         && (columnIndex > 0) && (columnIndex < getColumnCount()) )
 
590
      result = (m_Data.instance(rowIndex).isMissing(columnIndex - 1));
 
591
    
 
592
    return result;
 
593
  }
 
594
  
 
595
  /**
 
596
   * returns the double value of the underlying Instances object at the
 
597
   * given position, -1 if out of bounds
 
598
   * 
 
599
   * @param rowIndex            the row index
 
600
   * @param columnIndex         the column index
 
601
   * @return                    the underlying value in the Instances object
 
602
   */
 
603
  public double getInstancesValueAt(int rowIndex, int columnIndex) {
 
604
    double      result;
 
605
    
 
606
    result = -1;
 
607
    
 
608
    if (    (rowIndex >= 0) && (rowIndex < getRowCount())
 
609
         && (columnIndex > 0) && (columnIndex < getColumnCount()) )
 
610
      result = m_Data.instance(rowIndex).value(columnIndex - 1);
 
611
    
 
612
    return result;
 
613
  }
 
614
  
 
615
  /**
 
616
   * returns the value for the cell at columnindex and rowIndex
 
617
   * 
 
618
   * @param rowIndex            the row index
 
619
   * @param columnIndex         the column index
 
620
   * @return                    the value at the position
 
621
   */
 
622
  public Object getValueAt(int rowIndex, int columnIndex) {
 
623
    Object            result;
 
624
    String            tmp;
 
625
    
 
626
    result = null;
 
627
    
 
628
    if (    (rowIndex >= 0) && (rowIndex < getRowCount())
 
629
        && (columnIndex >= 0) && (columnIndex < getColumnCount()) ) {
 
630
      if (columnIndex == 0) {
 
631
        result = new Integer(rowIndex + 1);
 
632
      }
 
633
      else {
 
634
        if (isMissingAt(rowIndex, columnIndex)) {
 
635
          result = null;
 
636
        }
 
637
        else {
 
638
          switch (getType(columnIndex)) {
 
639
            case Attribute.DATE: 
 
640
            case Attribute.NOMINAL:
 
641
            case Attribute.STRING:
 
642
            case Attribute.RELATIONAL:
 
643
              result = m_Data.instance(rowIndex).stringValue(columnIndex - 1);
 
644
              break;
 
645
            case Attribute.NUMERIC:
 
646
              result = new Double(m_Data.instance(rowIndex).value(columnIndex - 1));
 
647
              break;
 
648
            default:
 
649
              result = "-can't display-";
 
650
          }
 
651
        }
 
652
      }
 
653
    }
 
654
    
 
655
    if (getType(columnIndex) != Attribute.NUMERIC) {
 
656
      if (result != null) {
 
657
        // does it contain "\n" or "\r"? -> replace with red html tag
 
658
        tmp = result.toString();
 
659
        if ( (tmp.indexOf("\n") > -1) || (tmp.indexOf("\r") > -1) ) {
 
660
          tmp    = tmp.replaceAll("\\r\\n", "<font color=\"red\"><b>\\\\r\\\\n</b></font>");
 
661
          tmp    = tmp.replaceAll("\\r", "<font color=\"red\"><b>\\\\r</b></font>");
 
662
          tmp    = tmp.replaceAll("\\n", "<font color=\"red\"><b>\\\\n</b></font>");
 
663
          result = "<html>" + tmp + "</html>";
 
664
        }
 
665
      }
 
666
    }
 
667
    
 
668
    return result;
 
669
  }
 
670
  
 
671
  /**
 
672
   * returns true if the cell at rowindex and columnindexis editable
 
673
   * 
 
674
   * @param rowIndex            the index of the row
 
675
   * @param columnIndex         the index of the column
 
676
   * @return                    true if the cell is editable
 
677
   */
 
678
  public boolean isCellEditable(int rowIndex, int columnIndex) {
 
679
    return (columnIndex > 0) && !isReadOnly();
 
680
  }
 
681
  
 
682
  /**
 
683
   * sets the value in the cell at columnIndex and rowIndex to aValue.
 
684
   * but only the value and the value can be changed
 
685
   * 
 
686
   * @param aValue              the new value
 
687
   * @param rowIndex            the row index
 
688
   * @param columnIndex         the column index
 
689
   */
 
690
  public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
 
691
    setValueAt(aValue, rowIndex, columnIndex, true);
 
692
  }
 
693
  
 
694
  /**
 
695
   * sets the value in the cell at columnIndex and rowIndex to aValue.
 
696
   * but only the value and the value can be changed
 
697
   * 
 
698
   * @param aValue              the new value
 
699
   * @param rowIndex            the row index
 
700
   * @param columnIndex         the column index
 
701
   * @param notify              whether to notify the listeners
 
702
   */
 
703
  public void setValueAt(Object aValue, int rowIndex, int columnIndex, boolean notify) {
 
704
    int            type;
 
705
    int            index;
 
706
    String         tmp;
 
707
    Instance       inst;
 
708
    Attribute      att;
 
709
    Object         oldValue;
 
710
    
 
711
    if (!m_IgnoreChanges)
 
712
      addUndoPoint();
 
713
    
 
714
    oldValue = getValueAt(rowIndex, columnIndex);
 
715
    type     = getType(rowIndex, columnIndex);
 
716
    index    = columnIndex - 1;
 
717
    inst     = m_Data.instance(rowIndex);
 
718
    att      = inst.attribute(index);
 
719
    
 
720
    // missing?
 
721
    if (aValue == null) {
 
722
      inst.setValue(index, Instance.missingValue());
 
723
    }
 
724
    else {
 
725
      tmp = aValue.toString();
 
726
      
 
727
      switch (type) {
 
728
        case Attribute.DATE:
 
729
          try {
 
730
            att.parseDate(tmp);
 
731
            inst.setValue(index, att.parseDate(tmp));
 
732
          }
 
733
          catch (Exception e) {
 
734
            // ignore
 
735
          }
 
736
          break;
 
737
      
 
738
        case Attribute.NOMINAL:
 
739
          if (att.indexOfValue(tmp) > -1)
 
740
            inst.setValue(index, att.indexOfValue(tmp));
 
741
          break;
 
742
      
 
743
        case Attribute.STRING:
 
744
          inst.setValue(index, tmp);
 
745
          break;
 
746
      
 
747
        case Attribute.NUMERIC:
 
748
          try {
 
749
            Double.parseDouble(tmp);
 
750
            inst.setValue(index, Double.parseDouble(tmp));
 
751
          }
 
752
          catch (Exception e) {
 
753
            // ignore
 
754
          }
 
755
          break;
 
756
          
 
757
        case Attribute.RELATIONAL:
 
758
          try {
 
759
            inst.setValue(index, inst.attribute(index).addRelation((Instances) aValue));
 
760
          }
 
761
          catch (Exception e) {
 
762
            // ignore
 
763
          }
 
764
          break;
 
765
          
 
766
        default:
 
767
          throw new IllegalArgumentException("Unsupported Attribute type: " + type + "!");
 
768
      }
 
769
    }
 
770
    
 
771
    // notify only if the value has changed!
 
772
    if (notify && (!("" + oldValue).equals("" + aValue)) )
 
773
        notifyListener(new TableModelEvent(this, rowIndex, columnIndex));
 
774
  }
 
775
  
 
776
  /**
 
777
   * adds a listener to the list that is notified each time a change to data 
 
778
   * model occurs
 
779
   * 
 
780
   * @param l           the listener to add
 
781
   */
 
782
  public void addTableModelListener(TableModelListener l) {
 
783
    m_Listeners.add(l);
 
784
  }
 
785
  
 
786
  /**
 
787
   * removes a listener from the list that is notified each time a change to
 
788
   * the data model occurs
 
789
   * 
 
790
   * @param l           the listener to remove
 
791
   */
 
792
  public void removeTableModelListener(TableModelListener l) {
 
793
    m_Listeners.remove(l);
 
794
  }
 
795
  
 
796
  /**
 
797
   * notfies all listener of the change of the model
 
798
   * 
 
799
   * @param e           the event to send to the listeners
 
800
   */
 
801
  public void notifyListener(TableModelEvent e) {
 
802
    Iterator                iter;
 
803
    TableModelListener      l;
 
804
 
 
805
    // is notification enabled?
 
806
    if (!isNotificationEnabled())
 
807
      return;
 
808
    
 
809
    iter = m_Listeners.iterator();
 
810
    while (iter.hasNext()) {
 
811
      l = (TableModelListener) iter.next();
 
812
      l.tableChanged(e);
 
813
    }
 
814
  }
 
815
 
 
816
  /**
 
817
   * removes the undo history
 
818
   */
 
819
  public void clearUndo() {
 
820
    m_UndoList = new Vector();
 
821
  }
 
822
  
 
823
  /**
 
824
   * returns whether an undo is possible, i.e. whether there are any undo points
 
825
   * saved so far
 
826
   * 
 
827
   * @return returns TRUE if there is an undo possible 
 
828
   */
 
829
  public boolean canUndo() {
 
830
    return !m_UndoList.isEmpty();
 
831
  }
 
832
  
 
833
  /**
 
834
   * undoes the last action
 
835
   */
 
836
  public void undo() {
 
837
    File                  tempFile;
 
838
    Instances             inst;
 
839
    ObjectInputStream     ooi;
 
840
    
 
841
    if (canUndo()) {
 
842
      // load file
 
843
      tempFile = (File) m_UndoList.get(m_UndoList.size() - 1);
 
844
      try {
 
845
        // read serialized data
 
846
        ooi = new ObjectInputStream(new BufferedInputStream(new FileInputStream(tempFile)));
 
847
        inst = (Instances) ooi.readObject();
 
848
        ooi.close();
 
849
        
 
850
        // set instances
 
851
        setInstances(inst);
 
852
        notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
 
853
        notifyListener(new TableModelEvent(this));
 
854
      }
 
855
      catch (Exception e) {
 
856
        e.printStackTrace();
 
857
      }
 
858
      tempFile.delete();
 
859
      
 
860
      // remove from undo
 
861
      m_UndoList.remove(m_UndoList.size() - 1);
 
862
    }
 
863
  }
 
864
  
 
865
  /**
 
866
   * adds an undo point to the undo history, if the undo support is enabled
 
867
   * @see #isUndoEnabled()
 
868
   * @see #setUndoEnabled(boolean)
 
869
   */
 
870
  public void addUndoPoint() {
 
871
    File                  tempFile;
 
872
    ObjectOutputStream    oos;
 
873
 
 
874
    // undo support currently on?
 
875
    if (!isUndoEnabled())
 
876
      return;
 
877
    
 
878
    if (getInstances() != null) {
 
879
      try {
 
880
        // temp. filename
 
881
        tempFile = File.createTempFile("arffviewer", null);
 
882
        tempFile.deleteOnExit();
 
883
        
 
884
        // serialize instances
 
885
        oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(tempFile)));
 
886
        oos.writeObject(getInstances());
 
887
        oos.flush();
 
888
        oos.close();
 
889
        
 
890
        // add to undo list
 
891
        m_UndoList.add(tempFile);
 
892
      }
 
893
      catch (Exception e) {
 
894
        e.printStackTrace();
 
895
      }
 
896
    }
 
897
  }
 
898
}