/
Ear.py
265 lines (233 loc) · 9.52 KB
/
Ear.py
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
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
"""
,----------------------------------------------,
| |
| ,----------, ,------, ,-------, ,--------, |
| | Function | | Case | | Arrow | | Remove | |
| '----------' '------' '-------' '--------' |
| |
| ,------------------------------------------, |
| | Canvas | |
| | start | |
| | | | |
| | v | |
| | ,-----, | |
| | | foo |<------------, | |
| | '-----' | | |
| | | | | |
| | v | | |
| | ^ | | |
| | / \ ,-----, | |
| | < case >-------> | bar | | |
| | \ / '-----' | |
| | v | |
| | | | |
| | v | |
| | ,-----, | |
| | | baz |---> end | |
| | '-----' | |
| | | |
| '------------------------------------------' |
'----------------------------------------------'
* Clicking on Function enables to create a function on the canvas.
Clicking once again disables it.
* Clicking on Case enables to create a function on the canvas.
Clicking once again disables it.
* Clicking on Arrow enables to click on two functions on the canvas
to link them with an arrow.
* Clicking on Remove enables to click on a function or an arrow on the
canvas to remove it.
Removing a function removes the arrows pointing at it.
TODO
* Import/Export diagrams
* Position "Add Function window" next to root window
* <Down> does not work yet
* Enable to remove an argument.
* Editing an arrow and saving with an empty name removes the arrow,
printing a message before doing it.
* Do not draw the arrow inside the text
* Scrollbar in the canvas
* undo/redo
* Curve an arrow when two arrows have exactly the same ends.
o Identify arcs to choose which condition leads to which function
o Draw arrow head
o Start and End blocks.
o Add a circle in the middle of an arrow to allow to select it.
Or: when hoovering, put the line/element in bold to allow to select it.
x Losange/circle for case -> would be too big
x Use arcs instead of lines for arrows to specify loops -> loops are not allowed
Possible improvements
* Work on window size, colors
* Open function parameters in split mode
"""
__author__ = "Rémi Barat"
__version__ = "1.0"
from tkinter import *
from tkinter import filedialog
from tkinter import ttk
from Arrow import Arrow
from Case import Case
from Function import Function
import Properties
STATE_FUNC = "function_state"
STATE_CASE = "case_state"
STATE_ARROW = "arrow_state"
STATE_RM = "remove_state"
class Ear(object):
"""
Attributes
root: The Tkinter root window
mainframe: The main frame of the root window
state: Defines whether we should add an Element on the canvas
and its type, or whether we should remove an Element
buttons: All the buttons of the root window
canvas: Where to draw the algorithm
functions: The Functions created
cases: The Cases created
arrows: The Arrows created
selected: The last element on which the user clicked
moving: Whether the user is moving the selected element
start: In Arrow state, remembers the last clicked element
n_elems: Counter to give ids to elements
"""
def __init__(self):
self.root = Tk()
self.root.title("Ear - Explicit Algorithm Representation")
self.mainframe = ttk.Frame(self.root, padding="3 3 12 12")
self.mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
# Buttons
self.state = None
fbutton = ttk.Button(self.mainframe, text="Function", command=lambda: self.change_state(STATE_FUNC))
cbutton = ttk.Button(self.mainframe, text="Case", command=lambda: self.change_state(STATE_CASE))
abutton = ttk.Button(self.mainframe, text="Arrow", command=lambda: self.change_state(STATE_ARROW))
rbutton = ttk.Button(self.mainframe, text="Remove", command=lambda: self.change_state(STATE_RM))
ibutton = ttk.Button(self.mainframe, text="Import", command=self.import_event)
ebutton = ttk.Button(self.mainframe, text="Export", command=self.export)
fbutton.grid(column=1, row=1)
cbutton.grid(column=2, row=1)
abutton.grid(column=3, row=1)
rbutton.grid(column=4, row=1)
ibutton.grid(column=5, row=1)
ebutton.grid(column=6, row=1)
self.buttons = {
STATE_FUNC: fbutton,
STATE_CASE: cbutton,
STATE_ARROW: abutton,
STATE_RM: rbutton,
"import": ibutton,
"export": ebutton,
}
# Canvas
self.canvas = Canvas(self.mainframe, width=600, height=600, bg="grey") # TODO the mainframe should be grey
self.canvas.grid(column=1, row=2, columnspan=len(self.buttons))
self.canvas.bind("<Button-1>", self.register_position)
self.canvas.bind("<B1-Motion>", self.hold_click)
self.canvas.bind("<ButtonRelease-1>", self.simple_click)
self.functions = []
self.cases = []
self.arrows = []
self.start = None
self.n_elems = 0
# Display
for child in self.mainframe.winfo_children():
child.grid_configure(padx=5, pady=5)
# Create start and end blocks
d = 30
start_f = Function(self, Properties.WINDOW_WIDTH / 2, d, name="start")
end_f = Function(self, Properties.WINDOW_WIDTH / 2, Properties.WINDOW_HEIGHT - d, name="end")
self.functions += [start_f, end_f]
self.root.mainloop()
def change_state(self, new_state):
for button in self.buttons.values():
button.state(["!pressed"])
if self.state == new_state:
self.state = None
self.start = None
else:
self.state = new_state
self.buttons[new_state].state(["pressed"])
print(self.state)
def register_position(self, event):
print("registered position: start is {}".format(self.start))
self.moving = False
self.selected = None
self.selected_l = None
for f in self.functions:
if f.clicked_on(event.x, event.y):
self.select(f, self.functions)
return
for c in self.cases:
if c.clicked_on(event.x, event.y):
self.select(c, self.cases)
return
for a in self.arrows:
if a.clicked_on(event.x, event.y):
self.select(a, self.arrows)
return
def select(self, elem, elem_lst):
if self.start is not None and self.start == self.selected:
self.selected = None
self.selected_l = None
self.start = None
else:
self.selected = elem
self.selected_l = elem_lst
def simple_click(self, event):
"""If clicked on an empty space: add a function/case/arrow,
Else if in state "remove", remove the function/case/arrow,
Else edit the function/case.
"""
print("simple_click: selected is {}, moving is {}".format(self.selected, self.moving))
if self.selected is None:
if self.state == STATE_FUNC:
f = Function(self, event.x, event.y)
if not f.cancelled:
self.functions.append(f)
elif self.state == STATE_CASE:
c = Case(self, event.x, event.y)
if not c.cancelled:
self.cases.append(c)
else:
if self.state == STATE_RM and not self.moving:
self.selected.destroy()
self.start = None
elif self.state == STATE_ARROW:
if self.start is None:
self.start = self.selected
else:
a = Arrow(self, self.start, self.selected)
self.arrows.append(a)
self.start = None
elif not self.moving:
self.selected.edit()
self.start = None
else:
self.start = None
self.moving = False
def hold_click(self, event):
if self.selected is not None and type(self.selected) != Arrow:
self.moving = True
self.selected.move(event)
def import_event(self):
filename = filedialog.askopenfilename()
print("importing " + filename)
def export(self):
filename = filedialog.asksaveasfilename()
with open(filename, "w") as f_out:
f_out.write("Ear version: {}\n".format(__version__))
f_out.write("functions:\n")
for elem in self.functions:
elem.export(f_out)
f_out.write("cases:\n")
for elem in self.cases:
elem.export(f_out)
f_out.write("arrows:\n")
for elem in self.arrows:
elem.export(f_out)
def get_elem_id(self):
i = self.n_elems
self.n_elems += 1
return i
if __name__ == "__main__":
algorator = Ear()