Coverage for ase / gui / history.py: 82.81%
64 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 10:20 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-04 10:20 +0000
1# Right now the gui.update_history method is peppered around the code.
2# It should probably work off the observers but right now I am not sure
3# it can. Since draw() is called all over the place and it calls the
4# change_atoms observer, this would lead to a lot of saved history
5# states where nothing actually changed. If the proposed refactoring is
6# done some day, registering update_history to the observers seems like
7# the correct move.
10class History:
11 def __init__(self, images):
12 self.max_entries = 20
13 self.images = images
14 self.initialize_history()
16 def _is_at_end(self, frame):
17 frame_history = self.history_per_image[frame]
18 now = self.now_per_image[frame]
19 return frame_history[now] is frame_history[-1]
21 def _is_at_start(self, frame):
22 frame_history = self.history_per_image[frame]
23 now = self.now_per_image[frame]
24 return frame_history[now] is frame_history[0]
26 def append_image(self, image):
27 self.history_per_image.append([image.copy()])
28 self.now_per_image.append(-1)
30 def cut_tail(self, frame: int, i: int):
31 self.history_per_image[frame] = list(self.history_per_image[frame][:i])
32 # If the 'now' index would point out of range, reset it (this could
33 # be a problem if the index becomes positive somehow):
34 if self.now_per_image[frame] >= len(self.history_per_image[frame]):
35 self.now_per_image[frame] = -1
37 def isolate_history(self, frame):
38 """Takes the history of the given index and makes it the only one"""
39 self.history_per_image = [self.history_per_image[frame]]
40 self.now_per_image = [self.now_per_image[frame]]
42 def initialize_history(self):
43 """Copies the images' current states to the history and creates
44 what's necessary to track changes"""
45 # Will contain a list for each image containing its own linear history
46 # (as copies of the Atoms objects [or anything mutable, really]):
47 self.history_per_image = []
48 # Will contain an index for each image pointing to the historic entry
49 # which is 'active':
50 self.now_per_image = []
51 for img in self.images:
52 self.history_per_image.append([img.copy()])
53 self.now_per_image.append(-1)
55 def insert_image(self, frame, image):
56 self.history_per_image.insert(frame, [image.copy()])
57 self.now_per_image.insert(frame, -1)
59 def pop_image(self, frame):
60 """Removes the entire history of an image"""
61 self.history_per_image.pop(frame)
62 self.now_per_image.pop(frame)
64 def pop_history_item(self, frame, i):
65 """Removes a point in an image's history"""
66 len_history = len(self.history_per_image[frame])
67 # The 'now' may shift when a history item is removed
68 # -> let's take note:
69 i_abs = i % len_history
70 now = self.now_per_image[frame]
71 if now < 0:
72 now_abs = now % len_history
73 if now_abs < i_abs:
74 self.now_per_image[frame] = now + 1
75 elif now > 0 and now > i_abs:
76 self.now_per_image[frame] = now_abs - 1 - len_history
77 else:
78 self.now_per_image[frame] = -1
79 # Finally, pop out the history:
80 self.history_per_image[frame].pop(i)
82 def redo_history(self, frame):
83 if not self._is_at_end(frame):
84 now = self.now_per_image[frame] + 1
85 self.now_per_image[frame] = now
86 self.images._images[frame] = self.history_per_image[frame][
87 now
88 ].copy()
90 def undo_history(self, frame):
91 if not self._is_at_start(frame):
92 now = self.now_per_image[frame] - 1
93 self.now_per_image[frame] = now
94 self.images._images[frame] = self.history_per_image[frame][
95 now
96 ].copy()
98 def update_history(self, frame):
99 if len(self.history_per_image[frame]) >= self.max_entries:
100 self.pop_history_item(frame, 0)
101 if not self._is_at_end(frame):
102 self.cut_tail(frame, self.now_per_image[frame] + 1)
103 self.now_per_image[frame] = -1
104 self.history_per_image[frame].append(self.images[frame].copy())