001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins.streetside.history; 003 004import java.util.ArrayList; 005import java.util.List; 006import java.util.Objects; 007 008import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage; 009import org.openstreetmap.josm.plugins.streetside.history.commands.StreetsideCommand; 010import org.openstreetmap.josm.plugins.streetside.history.commands.StreetsideExecutableCommand; 011 012/** 013* History record system in order to let the user undo and redo commands. 014* 015* @author nokutu 016* 017*/ 018public class StreetsideRecord { 019/** The unique instance of the class. */ 020private static StreetsideRecord instance; 021 022private final List<StreetsideRecordListener> listeners = new ArrayList<>(); 023 024/** The set of commands that have taken place or that have been undone. */ 025public List<StreetsideCommand> commandList; 026/** Last written command. */ 027public int position; 028 029/** 030* Main constructor. 031*/ 032public StreetsideRecord() { 033 this.commandList = new ArrayList<>(); 034 this.position = -1; 035} 036 037/** 038* Returns the unique instance of the class. 039* 040* @return The unique instance of the class. 041*/ 042public static synchronized StreetsideRecord getInstance() { 043 if (StreetsideRecord.instance == null) 044 StreetsideRecord.instance = new StreetsideRecord(); 045 return StreetsideRecord.instance; 046} 047 048/** 049* Adds a listener. 050* 051* @param lis 052* The listener to be added. 053*/ 054public void addListener(StreetsideRecordListener lis) { 055 this.listeners.add(lis); 056} 057 058/** 059* Removes the given listener. 060* 061* @param lis 062* The listener to be removed. 063*/ 064public void removeListener(StreetsideRecordListener lis) { 065 this.listeners.remove(lis); 066} 067 068/** 069* Adds a new command to the list. 070* 071* @param command 072* The command to be added. 073*/ 074public void addCommand(final StreetsideCommand command) { 075 076 if (command instanceof StreetsideExecutableCommand) 077 ((StreetsideExecutableCommand) command).execute(); 078 // Checks if it is a continuation of last command 079 if (this.position != -1) { 080 boolean equalSets = true; 081 for (StreetsideAbstractImage img : this.commandList.get(this.position).images) { 082 equalSets = command.images.contains(img) && equalSets; 083 } 084 for (StreetsideAbstractImage img : command.images) { 085 equalSets = this.commandList.get(this.position).images.contains(img) && equalSets; 086 } 087 if (equalSets && this.commandList.get(this.position).getClass() == command.getClass()) { 088 this.commandList.get(this.position).sum(command); 089 fireRecordChanged(); 090 return; 091 } 092 } 093 // Adds the command to the last position of the list. 094 this.commandList.add(this.position + 1, command); 095 this.position++; 096 while (this.commandList.size() > this.position + 1) { 097 this.commandList.remove(this.position + 1); 098 } 099 fireRecordChanged(); 100} 101 102/** 103* Undo latest command. 104*/ 105public void undo() { 106 if (this.position <= -1) { 107 return; 108 } 109 this.commandList.get(this.position).undo(); 110 this.position--; 111 fireRecordChanged(); 112} 113 114/** 115* Redoes latest undone action. 116*/ 117public void redo() { 118 if (position + 1 >= commandList.size()) { 119 return; 120 } 121 this.position++; 122 this.commandList.get(this.position).redo(); 123 fireRecordChanged(); 124} 125 126private void fireRecordChanged() { 127 this.listeners.stream().filter(Objects::nonNull).forEach(StreetsideRecordListener::recordChanged); 128} 129 130/** 131* Resets the object to its start state. 132*/ 133public void reset() { 134 this.commandList.clear(); 135 this.position = -1; 136} 137}