-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUndoRedoManager.kt
178 lines (134 loc) · 5.56 KB
/
UndoRedoManager.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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
///////////////////////////////////////////////////////////////////////////
// UndoRedoManager is a small library to implement an undo&redo system
// Copyright (C) 2022 Miguel Alejandro Moreno Barrientos
//
// UndoRedoManager is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// UndoRedoManager is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
///////////////////////////////////////////////////////////////////////////
package undoredomanager
typealias SubscriberAction = (UndoRedoable?) -> Unit
/**
* Base class for any UndoRedoable action
*/
abstract class UndoRedoable
{
val subscribers = mutableListOf<SubscriberAction>()
abstract fun undo()
abstract fun redo()
open fun canUndo() = true
open fun canRedo() = true
open fun undoDescription() = "Undo ${getDescription()}"
open fun redoDescription() = "Redo ${getDescription()}"
abstract fun getDescription(): String
fun notifySubscribers() = subscribers.forEach { it( this ) }
override fun toString() = "UndoRedoable(undoDescription=${undoDescription()}," +
"redoDescription=${redoDescription()}," +
"canUndo=${canUndo()}," +
"canRedo=${canRedo()})"
}
/**
* Transaction for grouping several UndoRedoables.
*/
open class UndoRedoTransaction private constructor( private val list: MutableList<UndoRedoable> )
: MutableList<UndoRedoable> by list, UndoRedoable()
{
constructor(): this( mutableListOf() )
private var undone = false
final override fun undo()
{
if ( canUndo() )
{
for ( undoRedoable in reversed() )
undoRedoable.undo()
undone = true
}
else
throw IllegalStateException( "Can't undo" )
}
final override fun redo()
{
if ( canRedo() )
{
for ( undoRedoable in this )
undoRedoable.redo()
undone = false
}
else
throw IllegalStateException( "Can't redo" )
}
final override fun canUndo() = isNotEmpty() && !undone
final override fun canRedo() = isNotEmpty() && undone
override fun undoDescription() = if ( isNotEmpty() ) last().undoDescription() else "Empty transaction"
override fun redoDescription() = if ( isNotEmpty() ) last().redoDescription() else "Empty transaction"
override fun getDescription() = if ( isNotEmpty() ) last().getDescription() else "Empty transaction"
override fun toString()
= "UndoRedoTransaction(undoDescription=${undoDescription()}, " +
"redoDescription=${redoDescription()}, " +
"list=$list, " +
"canUndo=${canUndo()}, " +
"canRedo=${canRedo()}, " +
"subscribers=$subscribers)"
} // Class UndoRedoTransaction
/**
* Main class to manage the undo-redo system. ***Note: use implemented methods to modify the manager, to use list methods
* will cause unexpected behaviour***
* @param limit maximum number of UndoRedoable actions.
* If this number is exceeded, first actions are removed (limited queue)
*/
open class UndoRedoManager private constructor( private val list: MutableList<UndoRedoable>,
private var limit: Int )
: MutableList<UndoRedoable> by list, UndoRedoable()
{
constructor( limit: Int = Int.MAX_VALUE ): this( mutableListOf(), limit )
private var index = list.size - 1
final override fun undo()
{
if ( canUndo() )
this[index--].undo()
else
throw IllegalStateException( "Can't undo" )
notifySubscribers()
}
final override fun redo()
{
if ( canRedo() )
this[++index].redo()
else
throw IllegalStateException( "Can't redo" )
notifySubscribers()
}
final override fun canUndo() = isNotEmpty() && index >= 0 && this[index].canUndo()
final override fun canRedo() = isNotEmpty() && index < size-1 && this[index+1].canRedo()
override fun undoDescription()
= if ( canUndo() ) this[index].undoDescription() else "Can´t undo"
override fun redoDescription()
= if ( canRedo() ) this[index+1].redoDescription() else "Can't redo"
override fun getDescription() = if ( isNotEmpty() && index >= 0 ) this[index].getDescription()
else "Empty or rewound manager"
fun addItem( undoRedo: UndoRedoable)
{
subList( index + 1, size ).clear()
if ( size >= limit )
subList( 0, size - limit + 1 ).clear()
add( undoRedo )
index = size - 1
notifySubscribers()
}
fun clearAll()
{
clear()
index = size - 1
notifySubscribers()
}
override fun toString() = "UndoRedoManager(list=$list, limit=$limit, canUndo=${canUndo()}, canRedo=${canRedo()})"
} // class UndoRedoManager