- Timestamp:
- 2003-05-27T15:40:47+12:00 (21 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/gli/src/org/greenstone/gatherer/undo/UndoManager.java
r4293 r4364 63 63 */ 64 64 public class UndoManager 65 66 67 68 69 70 71 72 73 /** In order to make this button a drop target we have to create a DropTarget instance with the button as its target. */74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 65 extends JButton 66 implements ActionListener, DragComponent, DropTargetListener { 67 private ArrayList redo_sources; 68 private ArrayList undo_sources; 69 private boolean ignore = false; 70 private boolean ignore_next = false; 71 /** The group encompasses all of the objects you plan to drag and drop within, and ensures that only one has focus (as clearly identified by the colour of the selection field or, in this particular case, the background) and that actions only occur between components in the same group. */ 72 private DragGroup group; 73 /** In order to make this button a drop target we have to create a DropTarget instance with the button as its target. */ 74 private DropTarget drop_target; 75 private FileQueue file_queue; 76 private FileSystemModel model; 77 private GDMDocument obsolete_metadata; 78 /** What sort of action should a drag resemble. Not really used as we override with custom drag icon. */ 79 private int drag_action = DnDConstants.ACTION_MOVE; 80 /** The last point the mouse was at. Used to repaint 'spoilt' area. */ 81 private Point pt_last = null; 82 /** The area covered by the drag ghost, our custom drag icon. */ 83 private Rectangle ra_ghost = new Rectangle(); 84 private UndoStack redo; 85 private UndoStack undo; 86 static final public int FILE_COPY = 1; 87 static final public int FILE_DELETE = 2; 88 static final public int FILE_MOVE = 3; 89 90 public UndoManager() { 91 super(Utility.getImage("bin.gif")); 92 this.file_queue = Gatherer.f_man.getQueue(); 93 this.drop_target = new DropTarget(this, drag_action, this, true); 94 95 setBackground(Gatherer.config.getColor("coloring.button_background", true)); 96 setForeground(Gatherer.config.getColor("coloring.button_foreground", true)); 97 setOpaque(true); 98 99 // Creation 100 File recycle_directory = new File(Utility.RECYCLE); 101 if(!recycle_directory.exists()) { 102 recycle_directory.mkdirs(); 103 recycle_directory.deleteOnExit(); 104 } 105 this.model = new FileSystemModel(new FileNode(recycle_directory, "Undo")); 106 obsolete_metadata = new GDMDocument(); // This GDM is never saved. 107 redo = new UndoStack(false); 108 redo_sources = new ArrayList(); 109 undo = new UndoStack(true); 110 undo_sources = new ArrayList(); 111 if(Gatherer.debug != null) { 112 showTree(); 113 } 114 } 115 116 public void actionPerformed(ActionEvent event) { 117 // Is this an undo event source... 118 if(undo_sources.contains(event.getSource())) { 119 UndoJob undo_job = undo.pop(); 120 undo_job.action(true, file_queue); 121 121 // Now retrieve all other undo jobs with the same job-number and action them 122 123 124 125 126 127 128 129 122 while((undo_job = undo.getJob(undo_job.ID())) != null) { 123 undo_job.action(true, file_queue); 124 } 125 } 126 // Or a redo one. 127 else if(redo_sources.contains(event.getSource())) { 128 UndoJob redo_job = redo.pop(); 129 redo_job.action(false, file_queue); 130 130 // Now retrieve all other redo jobs with the same job-number and action them 131 132 133 134 135 136 137 138 139 131 while((redo_job = redo.getJob(redo_job.ID())) != null) { 132 redo_job.action(false, file_queue); 133 } 134 } 135 } 136 137 public void addMetadata(File file, ArrayList metadatum) { 138 for(int i = 0; metadatum != null && i < metadatum.size(); i++) { 139 Metadata metadata = (Metadata) metadatum.get(i); 140 140 ///ystem.err.println("UndoMetadata: " + file.getAbsolutePath() + " => " + metadata); 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 141 obsolete_metadata.addMetadata(file.getAbsolutePath(), metadata); 142 } 143 } 144 145 public void addUndo(long id, int type, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode record, boolean undo_event) { 146 if(target_model == null) { 147 target_model = this; 148 } 149 UndoJobAdder job_adder = new UndoJobAdder(id, type, source_model, source_parent, target_model, record, undo_event); 150 SwingUtilities.invokeLater(job_adder); 151 } 152 153 public void clear() { 154 undo.clear(); 155 redo.clear(); 156 } 157 158 /** In order for the appearance to be consistant, given we may be in the situation where the pointer has left our focus but the ghost remains, this method allows other members of the GGroup to tell this component to repair the 'spoilt' region left by its ghost. */ 159 public void clearGhost(){ 160 } 161 162 public void destroy() { 163 // Remove all references of this as a listener 164 for(int i = 0; i < redo_sources.size(); i++) { 165 AbstractButton source = (AbstractButton) redo_sources.get(i); 166 source.removeActionListener(this); 167 } 168 for(int j = 0; j < undo_sources.size(); j++) { 169 AbstractButton source = (AbstractButton) undo_sources.get(j); 170 source.removeActionListener(this); 171 } 172 redo_sources = null; 173 undo_sources = null; 174 redo = null; 175 undo = null; 176 obsolete_metadata = null; 177 file_queue = null; 178 } 179 180 /** Any implementation of DropTargetListener must include this method so we can be notified when the drag focus enters this component. We want to provide some sort of indication whether the current component is an acceptable drop target as well as indicating focus. */ 181 public void dragEnter(DropTargetDragEvent event) { 182 //ystem.err.println("Drag entered"); 183 group.grabFocus(this); 184 setBackground(Gatherer.config.getColor("coloring.button_selected_background", true)); 185 setForeground(Gatherer.config.getColor("coloring.button_selected_foreground", true)); 186 } 187 188 /** Any implementation of DropTargetListener must include this method so we can be notified when the drag focus leaves this component. We need to indicate that we have lost focus. */ 189 public void dragExit(DropTargetEvent event) { 190 //ystem.err.println("Drag exitted"); 191 setBackground(Gatherer.config.getColor("coloring.button_background", true)); 192 setForeground(Gatherer.config.getColor("coloring.button_foreground", true)); 193 } 194 195 /** Any implementation of DropTargetListener must include this method so we can be notified when the drag moves in this component. This is where we repaint our ghost icon at the tip of the mouse pointer. */ 196 public void dragOver(DropTargetDragEvent event) { 197 Graphics2D g2 = (Graphics2D) getGraphics(); 198 Point pt = event.getLocation(); 199 if(pt_last != null && pt.equals(pt_last)) { 200 return; 201 } 202 pt_last = pt; 203 if(!DragSource.isDragImageSupported()) { 204 204 // Erase the last ghost image and or cue line 205 205 paintImmediately(ra_ghost.getBounds()); 206 206 // Remember where you are about to draw the new ghost image 207 207 ra_ghost.setRect(pt.x - group.mouse_offset.x, pt.y - group.mouse_offset.y, group.image_ghost.getWidth(), group.image_ghost.getHeight()); 208 208 // Draw the ghost image 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 209 g2.drawImage(group.image_ghost, AffineTransform.getTranslateInstance(ra_ghost.getX(), ra_ghost.getY()), null); 210 } 211 } 212 213 /** Any implementation of DropTargetListener must include this method so we can be notified when the drag ends, ie the transferable is dropped. This in turn triggers a series of add events preceded by a pre() and followed by a post(). */ 214 public void drop(DropTargetDropEvent event) { 215 ignore = true; 216 group.grabFocus(this); 217 setBackground(Gatherer.config.getColor("coloring.button_background", true)); 218 setForeground(Gatherer.config.getColor("coloring.button_foreground", true)); 219 Transferable transferable = event.getTransferable(); 220 try { 221 DragComponent source = group.getSource(); 222 TreePath[] selection = group.getSelection(); 223 FileNode[] source_nodes = new FileNode[selection.length]; 224 for(int i = 0; i < source_nodes.length; i++) { 225 source_nodes[i] = (FileNode) selection[i].getLastPathComponent(); 226 } 227 227 ///ystem.err.println("Dropped files vector contains " + new_files.size() + " files."); 228 228 event.acceptDrop(drag_action); 229 229 // Action delete 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 230 Gatherer.f_man.action(source, source_nodes, this, null); 231 group.setSource(null); 232 group.setSelection(null); 233 } 234 catch(Exception error) { 235 error.printStackTrace(); 236 event.rejectDrop(); 237 } 238 ignore = false; 239 // Clear up the group.image_ghost 240 paintImmediately(ra_ghost.getBounds()); 241 event.getDropTargetContext().dropComplete(true); 242 } 243 244 /** Any implementation of DropTargetListener must include this method so we can be notified when the action to be taken upon drop changes. We never change so we don't do anything here. */ 245 public void dropActionChanged(DropTargetDragEvent event) { 246 } 247 248 public void fileCopied(long id, DragComponent target_model, FileNode target_parent, FileNode record, boolean undo_event) { 249 ///ystem.err.println("fileCopied(" + id + ", " + target_model + ", " + target_parent + ", " + record + ", " + undo_event + ")"); 250 UndoJob job = new UndoJob(id, null, null, target_model, record, FILE_COPY); 251 if(undo_event) { 252 252 ///ystem.err.println("Add undo job"); 253 254 255 253 undo.push(job); 254 } 255 else { 256 256 ///ystem.err.println("Add redo job"); 257 258 259 260 261 262 263 257 redo.push(job); 258 } 259 } 260 261 public void fileDeleted(long id, DragComponent source_model, FileNode source_parent, FileNode target_parent, FileNode record, boolean undo_event) { 262 UndoJob job = new UndoJob(id, source_model, source_parent, this, record, FILE_DELETE); 263 if(undo_event) { 264 264 ///ystem.err.println("Add undo job"); 265 266 267 265 undo.push(job); 266 } 267 else { 268 268 ///ystem.err.println("Add redo job"); 269 270 271 272 273 274 275 276 269 redo.push(job); 270 } 271 } 272 273 public void fileMoved(long id, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode target_parent, FileNode record, boolean undo_event) { 274 ///ystem.err.println("fileMoved(" + id + ", " + source_model + ", " + source_parent + ", " + target_model + ", " + target_parent + ", " + record + ", " + undo_event + ")"); 275 UndoJob job = new UndoJob(id, source_model, source_parent, target_model, record, FILE_MOVE); 276 if(undo_event) { 277 277 ///ystem.err.println("Add undo job"); 278 279 280 278 undo.push(job); 279 } 280 else { 281 281 ///ystem.err.println("Add redo job"); 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 282 redo.push(job); 283 } 284 } 285 286 /** Used to notify this component that it has gained focus by some method other that mouse focus. */ 287 public void gainFocus(){ 288 } 289 290 public ArrayList getMetadata(File file) { 291 ///ystem.err.println("UndoMetadata: " + file.getAbsolutePath()); 292 return obsolete_metadata.getMetadata(file.getAbsolutePath(), true, new ArrayList(), file); 293 } 294 295 /** Any implementation of DragComponent must include this method so that a outsider can get at the underlying tree model behind the component. */ 296 public FileSystemModel getTreeModel(){ 297 return (FileSystemModel) model; 298 } 299 300 public boolean ignore() { 301 return ignore; 302 } 303 304 /** This method is used to inform this component when it loses focus by means other than a drag mouse event, and should indicate this somehow. */ 305 public void loseFocus(){ 306 } 307 308 public void registerRedoSource(AbstractButton source) { 309 if(!redo_sources.contains(source)) { 310 redo_sources.add(source); 311 source.addActionListener(this); 312 } 313 } 314 315 public void registerUndoSource(AbstractButton source) { 316 if(!undo_sources.contains(source)) { 317 undo_sources.add(source); 318 source.addActionListener(this); 319 } 320 } 321 322 public void setGroup(DragGroup group) { 323 this.group = group; 324 } 325 326 public void undoAll() { 327 FileQueue immediate_queue = new FileQueue(true); 328 UndoJob undo_job = null; 329 while((undo_job = undo.pop()) != null) { 330 undo_job.action(true, immediate_queue); 331 331 // Now retrieve all other undo jobs with the same job-number and action them 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 332 while((undo_job = undo.getJob(undo_job.ID())) != null) { 333 undo_job.action(true, immediate_queue); 334 } 335 } 336 immediate_queue.run(); 337 // Returns only when all undo actions complete. 338 } 339 340 static final public File generateUniqueFile(FileNode record) { 341 String filename_raw = ArrayTools.objectArrayToString(record.getPath()); 342 int hash_code = filename_raw.hashCode(); 343 File file = new File(Utility.RECYCLE, String.valueOf(hash_code)); 344 int offset = 65; 345 while(file.exists() && offset != 90) { 346 file = new File(Utility.RECYCLE, String.valueOf(hash_code) + (char)offset); 347 offset++; 348 } 349 return file; 350 } 351 352 private void showTree() { 353 JDialog dialog = new JDialog(Gatherer.g_man, "Recycle Bin Model"); 354 dialog.setSize(new Dimension(400,300)); 355 JPanel content_pane = (JPanel) dialog.getContentPane(); 356 JTree tree = new JTree(model); 357 content_pane.setLayout(new BorderLayout()); 358 content_pane.add(new JScrollPane(tree), BorderLayout.CENTER); 359 dialog.show(); 360 } 361 362 private class UndoJob { 363 private FileNode record = null; 364 private FileNode source_parent = null; 365 private DragComponent source_model = null; 366 private DragComponent target_model = null; 367 private long id = 0; 368 private int type = -1; 369 private TreePath record_path = null; 370 private TreePath source_parent_path = null; 371 /** Undo file action Constructor. 372 * @param id A unique <i>long</i> id number for all actions associated with a certain gesture. 373 * @param source_model The source <strong>DragComponent</strong> where the new record originally came from. If this is <i>null</i> then it is assumed you are interested in using the 'recycle' bin model. 374 * @param source_parent The previous parent <strong>FileNode</strong> of the new record. 375 * @param target_model The target <strong>DragComponent</strong> where the new record is now. 376 * @param record The new <strong>FileNode</strong> itself. 377 * @param type An <i>int</i> indicating the action that has occured, not what undo action is needed. 378 */ 379 public UndoJob(long id, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode record, int type) { 380 this.id = id; 381 this.record = record; 382 if(record != null) { 383 this.record_path = new TreePath(record.getPath()); 384 } 385 this.source_model = source_model; 386 this.source_parent = source_parent; 387 if(source_parent != null) { 388 this.source_parent_path = new TreePath(source_parent.getPath()); 389 } 390 this.target_model = target_model; 391 this.type = type; 392 } 393 393 394 394 public void action(boolean is_undo, FileQueue file_queue) { 395 395 // Retrieve the lastest version of each file record 396 397 398 399 400 401 402 403 404 405 396 FileNode latest_record = null; 397 FileNode latest_source_parent = null; 398 if(target_model != null && record_path != null) { 399 ///ystem.err.println("Retrieving latest version of record from " + target_model + "."); 400 latest_record = ((FileSystemModel)target_model.getTreeModel()).getNode(record_path); 401 } 402 if(source_model != null && source_parent_path != null) { 403 ///ystem.err.println("Retrieving latest version of source parent."); 404 latest_source_parent = ((FileSystemModel)source_model.getTreeModel()).getNode(source_parent_path); 405 } 406 406 // Of course if there are no newer versions, stick to the ones we've already got. 407 408 409 410 411 412 413 414 407 if(latest_record == null) { 408 ///ystem.err.println("Using original record."); 409 latest_record = record; 410 } 411 if(latest_source_parent == null) { 412 ///ystem.err.println("Using original source parent."); 413 latest_source_parent = source_parent; 414 } 415 415 // Heres the fraction, too much friction. 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 416 switch(type) { 417 case FILE_COPY: 418 // To undo a file copy we issue a delete file action on the destination file record. 419 file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.DELETE, !is_undo, true, false); 420 break; 421 case FILE_DELETE: 422 // To undo a file delete we issue a copy file action from our recycle bin. 423 file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.MOVE, !is_undo, true, false); 424 break; 425 case FILE_MOVE: 426 // This may be a legitimate move, or may be a side effect of an undelete. If the formed source model and parent will be non-null. 427 if(source_model != null && source_parent != null) { 428 // To undo a file move we issue a move file action to return it to where it was. 429 file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.MOVE, !is_undo, true, false); 430 } 431 // Otherwise we perform another delete. 432 else { 433 file_queue.addJob(id, target_model, latest_record, source_model, latest_source_parent, FileJob.DELETE, !is_undo, true, false); 434 } 435 break; 436 default: 437 System.err.println("Unknown code."); 438 } 439 } 440 441 public FileNode getRecord() { 442 return record; 443 } 444 445 public long ID() { 446 return id; 447 } 448 } 449 450 private class UndoJobAdder 451 implements Runnable { 452 private boolean undo_event; 453 private FileNode record; 454 private FileNode source_parent; 455 private DragComponent source_model; 456 private DragComponent target_model; 457 private int type; 458 private long id; 459 public UndoJobAdder(long id, int type, DragComponent source_model, FileNode source_parent, DragComponent target_model, FileNode record, boolean undo_event) { 460 this.id = id; 461 this.record = record; 462 this.source_model = source_model; 463 this.source_parent = source_parent; 464 this.target_model = target_model; 465 this.type = type; 466 this.undo_event = undo_event; 467 } 468 public void run() { 469 UndoJob job = new UndoJob(id, source_model, source_parent, target_model, record, type); 470 if(undo_event) { 471 undo.push(job); 472 } 473 else { 474 redo.push(job); 475 } 476 } 477 } 478 479 private class UndoStack 480 extends LinkedList { 481 private boolean enabled = false; 482 private boolean undo; 483 private int pos = 0; 484 public UndoStack(boolean undo) { 485 this.undo = undo; 486 } 487 public void clear() { 488 super.clear(); 489 pos = 0; 490 if(enabled) { 491 setEnabled(false); 492 } 493 } 494 public UndoJob getJob(long id) { 495 UndoJob job = null; 496 while(job == null && pos < size()) { 497 UndoJob temp = (UndoJob) get(pos); 498 if(temp.ID() == id) { 499 job = temp; 500 remove(temp); 501 } 502 else { 503 pos++; 504 } 505 } 506 if(size() == 0) { 507 setEnabled(false); 508 pos = 0; 509 } 510 return job; 511 } 512 public void push(UndoJob job) { 513 addFirst(job); 514 pos = 0; 515 if(!enabled) { 516 setEnabled(true); 517 } 518 } 519 public UndoJob pop() { 520 UndoJob job = null; 521 if(size() > 0) { 522 job = (UndoJob) removeFirst(); 523 if(size() == 0 && enabled) { 524 setEnabled(false); 525 pos = 0; 526 } 527 } 528 return job; 529 } 530 public void reset() { 531 pos = 0; 532 } 533 private void setEnabled(boolean state) { 534 ArrayList sources; 535 if(undo) { 536 sources = undo_sources; 537 } 538 else { 539 sources = redo_sources; 540 } 541 for(int i = 0; i < sources.size(); i++) { 542 AbstractButton source = (AbstractButton) sources.get(i); 543 source.setEnabled(state); 544 } 545 enabled = state; 546 } 547 } 548 548 }
Note:
See TracChangeset
for help on using the changeset viewer.