Update connect/disconnect action states.
[kaya.git] / lib / interaction / history.rb
blob3da6ab22d5d88538b6e14a9af536a26c343371db
1 # Copyright (c) 2009 Paolo Capriotti <p.capriotti@gmail.com>
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 require 'observer_utils.rb'
9 require 'interaction/operation'
10 require 'interaction/operation_history'
12 class History
13   include Enumerable
14   include Observable
15   include OperationInterface
16   
17   attr_reader :current
18   attr_reader :operations
19   
20   class Item < Struct.new(:state, :move)
21     attr_accessor :text
22   end
23   class UnknownState
24   end
26   OutOfBound = Class.new(Exception)
28   def initialize(state)
29     @history = [Item.new(state.dup, nil)]
30     @current = 0
31     @operations = OperationHistory.new
32   end
33   
34   def each
35     @history.each {|item| yield item.state, item.move }
36   end
37   
38   def add_move(state, move, opts = { })
39     op = operation do |op|
40       op.truncate(@current + 1)
41       item = Item.new(state.dup, move)
42       item.text = opts[:text] if opts[:text]
43       op.move(item)
44     end
45     op.execute :extra => opts
46   end
47   
48   def add_placeholder
49     add_move(UnknownState.new, nil, :quiet => true)
50   end
51   
52   def forward
53     raise OutOfBound if @current >= @history.size - 1
54     @current += 1
55     item = @history[@current]
56     
57     fire :current_changed
58     [item.state, item.move]
59   end
60   
61   def back
62     raise OutOfBound if @current <= 0
63     move = @history[@current].move
64     @current -= 1
65     
66     fire :current_changed
67     [@history[@current].state, move]
68   end
69   
70   def go_to(index)
71     if index != @current
72       item = self[index]
73       @current = index
74       fire :current_changed
75       [item.state, item.move]
76     end
77   end
78   
79   def go_to_last
80     go_to(size - 1)
81   end
82   
83   def go_to_first
84     go_to(0)
85   end
86   
87   def state
88     @history[current].state
89   end
90   
91   def state=(value)
92     @history[current].state = value
93     fire :force_update
94   end
95   
96   def move=(value)
97     @history[current].move = value
98     fire :force_update
99   end
100   
101   def text=(value)
102     @history[current].text = value
103     fire :force_update
104   end
105   
106   def set_item(state, move)
107     @history[current] = Item.new(state, move)
108     fire :force_update
109   end
110   
111   def move
112     @history[current].move
113   end
114   
115   def undo!
116     op = @operations.undo_operation
117     if op
118       op.undo
119     end
120   end
121   
122   def redo!
123     op = @operations.redo_operation
124     if op
125       op.execute
126     end
127   end
128   
129   def size
130     @history.size
131   end
132   
133   def inspect
134     @history.inspect
135   end
136   
137   def [](index = nil)
138     index ||= @current
139     if index >= @history.size || index < 0
140       raise OutOfBound 
141     end
142     @history[index]
143   end
144   
145   def valid_index?(i)
146     not @history[i].state.is_a?(UnknownState)
147   end
148   
149   def path_defined?(from, to)
150     from, to = [from, to].sort
151     (from..to).all? { |i| valid_index?(i) }
152   end
153   
154   # item interface
155   
156   def add_items(opts, *items)
157     @history += items
158     old_current = @current
159     old_state = state
160     @current = @history.size - 1  if opts.fetch(:go_to_end, true)
161     fire :new_move => {
162       :old_current => old_current,
163       :old_state => old_state,
164       :state => state,
165       :opts => opts }
166     @current
167   end
168   
169   def remove_items_at(index, opts = { })
170     @current = index - 1
171     fire :current_changed
172     
173     items = @history[index..-1]
174     @history = @history[0...index]
175     @current = if @current >= index
176       index.pred
177     else
178       @current
179     end
180     fire :truncate => @current
181     items
182   end
183   
184   def current=(value)
185     @current = value
186   end