Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/tools/purgatory/launcher.py
169674 views
1
#!/usr/bin/env python
2
# -*- coding: utf8 -*-
3
# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
4
# Copyright (C) 2010-2025 German Aerospace Center (DLR) and others.
5
# This program and the accompanying materials are made available under the
6
# terms of the Eclipse Public License 2.0 which is available at
7
# https://www.eclipse.org/legal/epl-2.0/
8
# This Source Code may also be made available under the following Secondary
9
# Licenses when the conditions for such availability set forth in the Eclipse
10
# Public License 2.0 are satisfied: GNU General Public License, version 2
11
# or later which is available at
12
# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
13
# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
14
15
# @file launcher.py
16
# @author Jakob Erdmann
17
# @date 2015-01-18
18
19
from __future__ import absolute_import
20
import os
21
import re
22
import subprocess
23
from tkinter import Canvas, Menu, StringVar, Tk, LEFT
24
from tkinter.ttk import Button, Frame, Menubutton, Label, Scrollbar, Entry
25
from tkinter import filedialog
26
from sumolib.options import Option, readOptions
27
28
THISDIR = os.path.dirname(__file__)
29
BINDIR = os.path.join(THISDIR, '..', '..', 'bin')
30
31
APPLICATIONS = ['netconvert', 'netgenerate', 'polyconvert', 'od2trips', 'duarouter', 'jtrrouter',
32
'dfrouter', 'marouter', 'sumo', 'sumo-gui', 'activitygen']
33
34
35
class ResizingCanvas(Canvas):
36
37
""" a subclass of Canvas for dealing with resizing of windows
38
http://stackoverflow.com/questions/22835289/how-to-get-tkinter-canvas-to-dynamically-resize-to-window-width
39
"""
40
41
def __init__(self, parent, **kwargs):
42
Canvas.__init__(self, parent, **kwargs)
43
# self.bind("<Configure>", self.on_resize)
44
# self.height = 400
45
# self.width = 700
46
47
def on_resize(self, event):
48
# determine the ratio of old width/height to new width/height
49
wscale = float(event.width) / self.width
50
hscale = float(event.height) / self.height
51
self.width = event.width - 2
52
self.height = event.height - 2
53
# print "on_resize %s %s %s %s" % (self.width, self.height,
54
# self.winfo_reqwidth(), self.winfo_reqheight())
55
# resize the canvas
56
self.config(width=self.width, height=self.height)
57
# rescale all the objects tagged with the "all" tag
58
self.scale("all", 0, 0, wscale, hscale)
59
60
61
class ScrollableFrame(Frame):
62
63
def __init__(self, root):
64
Frame.__init__(self, root)
65
self.canvas = ResizingCanvas(self, borderwidth=0)
66
self.frame = Frame(self.canvas)
67
self.vsb = Scrollbar(
68
self, orient="vertical", command=self.canvas.yview)
69
self.canvas.configure(yscrollcommand=self.vsb.set)
70
self.vsb.pack(side="right", fill="y")
71
self.canvas.pack(side="left", fill="both", expand=True)
72
self.canvas.create_window(
73
(4, 4), window=self.frame, anchor="nw", tags="self.frame")
74
self.frame.bind("<Configure>", self.OnFrameConfigure)
75
76
def OnFrameConfigure(self, event):
77
'''Reset the scroll region to encompass the inner frame'''
78
# print "OnFrameConfigure"
79
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
80
81
82
def buildValueWidget(frame, optType):
83
if optType == "FOO":
84
pass
85
else:
86
var = StringVar()
87
widget = Entry(frame, textvariable=var)
88
return widget, var
89
90
91
class Launcher:
92
93
def __init__(self, root, app, appOptions):
94
self.title_prefix = "SUMO Application launcher"
95
self.root = root
96
self.appVar = StringVar()
97
self.appVar.set(app)
98
self.appOptions = appOptions
99
self.optionValues = {}
100
101
self.root.title(self.title_prefix)
102
self.root.minsize(700, 200)
103
self.root.geometry("700x400")
104
# self.root.iconphoto(True, PhotoImage(file = os.path.join(THISDIR, "launcher.gif")))
105
106
numButtons = self.mainButtons()
107
for i in range(numButtons):
108
root.columnconfigure(i, weight=1)
109
root.rowconfigure(0, weight=10)
110
root.rowconfigure(1, weight=1)
111
112
sFrame = ScrollableFrame(root)
113
sFrame.grid(row=1, column="0", columnspan=numButtons, sticky="NSEW")
114
self.optFrame = sFrame.frame
115
116
self.buildAppOptions(appOptions)
117
118
# define options for opening or saving a file
119
self.file_opt = options = {}
120
self.filedir = os.getcwd()
121
options['defaultextension'] = 'cfg'
122
options['filetypes'] = [('all files', '.*')]
123
options['initialdir'] = self.filedir
124
options['parent'] = root
125
126
def buildAppOptions(self, appOptions):
127
NAME, VALUE, HELP = range(3)
128
row = 0
129
for o in appOptions:
130
row += 1
131
Label(self.optFrame, text=o.name).grid(
132
row=row, column=NAME, sticky="NW")
133
widget, var = buildValueWidget(self.optFrame, o.type)
134
self.optionValues[o.name] = var
135
widget.grid(row=row, column=VALUE, sticky="NW")
136
Label(self.optFrame, text=o.help, justify=LEFT).grid(
137
row=row, column=HELP, sticky="NW")
138
139
def mainButtons(self):
140
row = 0
141
col = 0
142
self.buttons = []
143
144
mb = Menubutton(self.root, text="Select Application")
145
mb.menu = Menu(mb, tearoff=0)
146
mb["menu"] = mb.menu
147
for app in APPLICATIONS:
148
mb.menu.add_radiobutton(label=app, variable=self.appVar,
149
command=self.onSelectApp)
150
mb.grid(row=row, column=col, sticky="NEW")
151
col += 1
152
153
self.buttons.append(mb)
154
otherButtons = (
155
("Run %12s" % self.appVar.get(), self.runApp),
156
("load Config", self.loadCfg),
157
("Save Config", self.saveCfg),
158
("Save Config as", self.saveCfgAs),
159
("Quit", self.root.quit),
160
)
161
162
for text, command in otherButtons:
163
self.buttons.append(Button(self.root, text=text, command=command))
164
self.buttons[-1].grid(row=row, column=col, sticky="NEW")
165
col += 1
166
return len(self.buttons)
167
168
def onSelectApp(self):
169
self.buttons[1].configure(text="Run %12s" % self.appVar.get())
170
171
def runApp(self):
172
subprocess.call(os.path.join(BINDIR, self.appVar.get()))
173
174
def loadCfg(self):
175
self.file_opt['title'] = 'Load configuration file'
176
filename = filedialog.askopenfilename(**self.file_opt)
177
self.root.title(self.title_prefix + " " + filename)
178
self.loadedOptions = readOptions(filename)
179
for o in self.loadedOptions:
180
self.optionValues[o.name].set(o.value)
181
182
def saveCfg(self):
183
pass
184
185
def saveCfgAs(self):
186
pass
187
188
189
def parse_help(app):
190
binary = os.path.join(BINDIR, app)
191
reOpt = re.compile(r"--([^ ]*) (\w*) (.*$)")
192
helpstring = subprocess.check_output([binary, '--help'])
193
options = []
194
optName = None
195
optHelp = ""
196
optType = ""
197
for line in helpstring.split(os.linesep):
198
if '--' in line:
199
if optName is not None:
200
options.append(Option(optName, None, optType, optHelp))
201
match = reOpt.search(line)
202
if match is not None:
203
optName = match.group(1)
204
optType = match.group(2)
205
optHelp = match.group(3).strip()
206
elif " " in line:
207
optHelp += "\n" + line.strip()
208
if optName is not None:
209
if optType == '':
210
optType = 'BOOL'
211
options.append(Option(optName, None, optType, optHelp))
212
213
return options
214
215
216
def main():
217
app = "netconvert"
218
appOptions = parse_help(app)
219
# appOptions = []
220
root = Tk()
221
app = Launcher(root, app, appOptions)
222
root.mainloop()
223
224
225
if __name__ == "__main__":
226
main()
227
228