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 from python_qt_binding.QtCore import QObject, Qt, Signal
34 from python_qt_binding.QtGui import QIcon
35 import os
36 import threading
37
38 import rospy
39
40 from node_manager_fkie.common import is_package, utf8
41 from node_manager_fkie.detailed_msg_box import MessageBox
42 from node_manager_fkie.editor.yaml_highlighter import YamlHighlighter
43 import node_manager_fkie as nm
44
45 from .editor import TextEdit
46 try:
47 from python_qt_binding.QtGui import QApplication, QVBoxLayout, QSizePolicy
48 from python_qt_binding.QtGui import QComboBox, QDialog, QDialogButtonBox, QFileDialog, QToolButton
49 except:
50 from python_qt_binding.QtWidgets import QApplication, QVBoxLayout, QSizePolicy
51 from python_qt_binding.QtWidgets import QComboBox, QDialog, QDialogButtonBox, QFileDialog, QToolButton
55 '''
56 Enabled the syntax highlightning for the sync interface.
57 '''
58
60 YamlHighlighter.__init__(self, parent)
61 tagList = ["\\bignore_hosts\\b", "\\bsync_hosts\\b",
62 "\\bignore_nodes\\b", "\\bsync_nodes\\b",
63 "\\bignore_topics\\b", "\\bignore_publishers\\b",
64 "\\bignore_topics\\b", "\\bsync_topics\\b",
65 "\\bignore_subscribers\\b", "\\bsync_services\\b",
66 "\\bsync_topics_on_demand\\b", "\\bsync_remote_nodes\\b",
67 "\\bignore_services\\b", "\\bdo_not_sync\\b",
68 "\\bresync_on_reconnect\\b", "\\bresync_on_reconnect_timeout\\b",
69 ]
70 for tag in tagList:
71 self.rules.append((self._create_regexp(tag), self._create_format(Qt.darkBlue)))
72 self.rules.append((self._create_regexp("\\b\\*|\\*\\B|\\/\\*"), self._create_format(Qt.darkGreen, 'bold')))
73
76 '''
77 A dialog to set the sync options.
78 '''
79
81 QDialog.__init__(self, parent)
82
83 self.setWindowIcon(QIcon(":/icons/irondevil_sync.png"))
84 self.setWindowTitle('Sync')
85 self.verticalLayout = QVBoxLayout(self)
86 self.verticalLayout.setObjectName("verticalLayout")
87 self.resize(350, 190)
88
89 self.toolButton_SyncAll = QToolButton(self)
90 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
91 sizePolicy.setHorizontalStretch(0)
92 sizePolicy.setVerticalStretch(2)
93 sizePolicy.setHeightForWidth(self.toolButton_SyncAll.sizePolicy().hasHeightForWidth())
94 self.toolButton_SyncAll.setSizePolicy(sizePolicy)
95 self.toolButton_SyncAll.setObjectName("toolButton_SyncAll")
96 self.verticalLayout.addWidget(self.toolButton_SyncAll)
97 self.toolButton_SyncAll.setText(self._translate("Sync All"))
98 self.toolButton_SyncAll.clicked.connect(self._on_sync_all_clicked)
99
100
101
102
103
104
105
106
107
108
109
110
111 self.toolButton_SyncTopicOnDemand = QToolButton(self)
112 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
113 sizePolicy.setHorizontalStretch(0)
114 sizePolicy.setVerticalStretch(1)
115 sizePolicy.setHeightForWidth(self.toolButton_SyncTopicOnDemand.sizePolicy().hasHeightForWidth())
116 self.toolButton_SyncTopicOnDemand.setSizePolicy(sizePolicy)
117 self.toolButton_SyncTopicOnDemand.setObjectName("toolButton_SyncTopicOnDemand")
118 self.verticalLayout.addWidget(self.toolButton_SyncTopicOnDemand)
119 self.toolButton_SyncTopicOnDemand.setText(self._translate("Sync only topics on demand"))
120 self.toolButton_SyncTopicOnDemand.clicked.connect(self._on_sync_topics_on_demand_clicked)
121
122 self.toolButton_SelectInterface = QToolButton(self)
123 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
124 sizePolicy.setHorizontalStretch(0)
125 sizePolicy.setVerticalStretch(1)
126 sizePolicy.setHeightForWidth(self.toolButton_SelectInterface.sizePolicy().hasHeightForWidth())
127 self.toolButton_SelectInterface.setSizePolicy(sizePolicy)
128 self.toolButton_SelectInterface.setObjectName("toolButton_SelectInterface")
129 self.verticalLayout.addWidget(self.toolButton_SelectInterface)
130 self.toolButton_SelectInterface.setText(self._translate("Select an interface"))
131 self.toolButton_SelectInterface.clicked.connect(self._on_select_interface_clicked)
132
133 self.interface_field = QComboBox(self)
134 self.interface_field.setInsertPolicy(QComboBox.InsertAlphabetically)
135 self.interface_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
136 self.interface_field.setEditable(True)
137 self.interface_field.setVisible(False)
138 self.interface_field.setObjectName("interface_field")
139 self.verticalLayout.addWidget(self.interface_field)
140 self.interface_field.currentIndexChanged[str].connect(self._on_interface_selected)
141
142 self.toolButton_EditInterface = QToolButton(self)
143 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
144 sizePolicy.setHorizontalStretch(0)
145 sizePolicy.setVerticalStretch(1)
146 sizePolicy.setHeightForWidth(self.toolButton_EditInterface.sizePolicy().hasHeightForWidth())
147 self.toolButton_EditInterface.setSizePolicy(sizePolicy)
148 self.toolButton_EditInterface.setObjectName("toolButton_EditInterface")
149 self.verticalLayout.addWidget(self.toolButton_EditInterface)
150 self.toolButton_EditInterface.setText(self._translate("Edit selected interface"))
151 self.toolButton_EditInterface.clicked.connect(self._on_edit_interface_clicked)
152 self.toolButton_EditInterface.setVisible(False)
153
154 self.toolButton_CreateInterface = QToolButton(self)
155 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
156 sizePolicy.setHorizontalStretch(0)
157 sizePolicy.setVerticalStretch(1)
158 sizePolicy.setHeightForWidth(self.toolButton_CreateInterface.sizePolicy().hasHeightForWidth())
159 self.toolButton_CreateInterface.setSizePolicy(sizePolicy)
160 self.toolButton_CreateInterface.setObjectName("toolButton_CreateInterface")
161 self.verticalLayout.addWidget(self.toolButton_CreateInterface)
162 self.toolButton_CreateInterface.setText(self._translate("Create an interface"))
163 self.toolButton_CreateInterface.clicked.connect(self._on_create_interface_clicked)
164 self.toolButton_CreateInterface.setVisible(False)
165
166 self.textedit = TextEdit('', self)
167 self.hl = SyncHighlighter(self.textedit.document())
168 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
169 self.textedit.setSizePolicy(sizePolicy)
170 self.textedit.setObjectName("syncedit")
171 self.verticalLayout.addWidget(self.textedit)
172 self.textedit.setVisible(False)
173
174 self._fill_interface_thread = None
175 self._interfaces_files = None
176 self._sync_args = []
177 self._interface_filename = None
178
179 self.buttonBox = QDialogButtonBox(self)
180 self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
181 self.buttonBox.setOrientation(Qt.Horizontal)
182 self.buttonBox.setObjectName("buttonBox")
183 self.verticalLayout.addWidget(self.buttonBox)
184 self.buttonBox.accepted.connect(self.accept)
185 self.buttonBox.rejected.connect(self.reject)
186 self._new_iface = True
187
189 if hasattr(QApplication, "UnicodeUTF8"):
190 return QApplication.translate("Form", text, None, QApplication.UnicodeUTF8)
191 else:
192 return QApplication.translate("Form", text, None)
193
194 @property
196 return self._sync_args
197
198 @property
200 return self._interface_filename
201
203 self.setResult(QDialog.Accepted)
204 self._sync_args = []
205 self._sync_args.append(''.join(['_interface_url:=', "'.'"]))
206 self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'False']))
207 self._sync_args.append(''.join(['_ignore_hosts:=', '[]']))
208 self._sync_args.append(''.join(['_sync_hosts:=', '[]']))
209 self._sync_args.append(''.join(['_ignore_nodes:=', '[]']))
210 self._sync_args.append(''.join(['_sync_nodes:=', '[]']))
211 self._sync_args.append(''.join(['_ignore_topics:=', '[]']))
212 self._sync_args.append(''.join(['_ignore_publishers:=', '[]']))
213 self._sync_args.append(''.join(['_ignore_subscribers:=', '[]']))
214 self._sync_args.append(''.join(['_sync_topics:=', '[]']))
215 self._sync_args.append(''.join(['_ignore_services:=', '[]']))
216 self._sync_args.append(''.join(['_sync_services:=', '[]']))
217 self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False']))
218 self._interface_filename = None
219 self.accept()
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
238 self._sync_args = []
239 self._sync_args.append(''.join(['_interface_url:=', "'.'"]))
240 self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True']))
241 self._sync_args.append(''.join(['_ignore_hosts:=', '[]']))
242 self._sync_args.append(''.join(['_sync_hosts:=', '[]']))
243 self._sync_args.append(''.join(['_ignore_nodes:=', '[]']))
244 self._sync_args.append(''.join(['_sync_nodes:=', '[]']))
245 self._sync_args.append(''.join(['_ignore_topics:=', '[]']))
246 self._sync_args.append(''.join(['_ignore_publishers:=', '[]']))
247 self._sync_args.append(''.join(['_ignore_subscribers:=', '[]']))
248 self._sync_args.append(''.join(['_sync_topics:=', '[/only_on_demand]']))
249 self._sync_args.append(''.join(['_ignore_services:=', '[/*]']))
250 self._sync_args.append(''.join(['_sync_services:=', '[]']))
251 self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False']))
252 self._interface_filename = None
253 self.accept()
254
256 self.toolButton_SyncAll.setVisible(False)
257
258 self.toolButton_SyncTopicOnDemand.setVisible(False)
259 self.toolButton_SelectInterface.setVisible(False)
260 self.interface_field.setVisible(True)
261 self.toolButton_CreateInterface.setVisible(True)
262 self.toolButton_EditInterface.setVisible(True)
263 self.toolButton_EditInterface.setEnabled(False)
264 self.textedit.setVisible(False)
265
266 if self._interfaces_files is None:
267 self.interface_field.addItems(['interface searching...'])
268 self.interface_field.setCurrentIndex(0)
269 self._fill_interface_thread = InterfacesThread()
270 self._fill_interface_thread.interfaces.connect(self._fill_interfaces)
271 self._fill_interface_thread.start()
272 else:
273 self.toolButton_EditInterface.setEnabled(self.interface_field.currentText() in self._interfaces_files)
274 self.buttonBox.clear()
275 self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
276 self.interface_field.setFocus(Qt.TabFocusReason)
277 self.resize(350, 80)
278
280 self._interfaces_files = interfaces_files
281 self.interface_field.clear()
282 self.interface_field.clearEditText()
283 self.interface_field.addItems(self._interfaces_files.keys())
284
286 if self._interfaces_files and interface in self._interfaces_files:
287 self._sync_args = []
288 self._sync_args.append(''.join(['_interface_url:=', interface]))
289 self.toolButton_EditInterface.setEnabled(True)
290 else:
291 self.toolButton_EditInterface.setEnabled(False)
292
294 if self.textedit.isVisible():
295 try:
296 tmp_file = os.path.join(nm.screen().LOG_PATH, 'tmp_sync_interface.sync')
297 with open(tmp_file, 'w+') as f:
298 f.write(self.textedit.toPlainText())
299 from master_discovery_fkie.common import read_interface
300 read_interface(tmp_file)
301 if not self._new_iface and self.interface_field.currentText() in self._interfaces_files:
302 fileName = self._interfaces_files[self.interface_field.currentText()]
303 else:
304 fileName, _ = QFileDialog.getSaveFileName(self, 'Save sync interface', '/home', "Sync Files (*.sync)")
305 if fileName:
306 with open(fileName, 'w+') as f:
307 self._interface_filename = fileName
308 f.write(self.textedit.toPlainText())
309 if self._new_iface:
310 self.interface_field.clear()
311 self._interfaces_files = None
312 self._on_select_interface_clicked()
313
314
315 except Exception as e:
316 MessageBox.warning(self, "Create sync interface",
317 "Error while create interface",
318 utf8(e))
319 elif self.interface_field.isVisible():
320 interface = self.interface_field.currentText()
321 if self._interfaces_files and interface in self._interfaces_files:
322 self._interface_filename = self._interfaces_files[interface]
323 self._sync_args = []
324 self._sync_args.append(''.join(['_interface_url:=', interface]))
325 QDialog.accept(self)
326 self.resetView()
327 else:
328 QDialog.accept(self)
329 self.resetView()
330
332 if self.textedit.isVisible():
333 self._on_select_interface_clicked()
334 else:
335 QDialog.reject(self)
336 self.resetView()
337
339 self._new_iface = True
340 self.interface_field.setVisible(False)
341 self.toolButton_CreateInterface.setVisible(False)
342 self.toolButton_EditInterface.setVisible(False)
343 self.textedit.setVisible(True)
344 self.textedit.setText("# The ignore_* lists will be processed first.\n"
345 "# For ignore/sync nodes, topics or services\n"
346 "# use follow declaration:\n"
347 "#{param name}: \n"
348 "# - {ros name}\n"
349 "# or for selected hosts:\n"
350 "# - {host name}:\n"
351 "# - {ros name}\n\n"
352 "# you can use follow wildcard: '*', but not as a first character\n"
353 "ignore_hosts:\n"
354 "sync_hosts:\n\n"
355 "ignore_nodes:\n"
356 "sync_nodes:\n\n"
357 "ignore_topics:\n"
358 "ignore_publishers:\n"
359 "ignore_subscribers:\n"
360 "sync_topics:\n\n"
361 "ignore_services:\n"
362 " - /*get_loggers\n"
363 " - /*set_logger_level\n"
364 "sync_services:\n\n"
365 "# If sync_topics_on_demand is True the local subscribed and published topics\n"
366 "# are synchronized with remote even if they are not in the sync_* list.\n"
367 "sync_topics_on_demand: False\n\n"
368 "# The nodes which are running not at the same host as the ROS master are not\n"
369 "# synchronized by default. Use sync_remote_nodes to sync these nodes also.\n"
370 "sync_remote_nodes: False\n\n"
371 )
372 self.resize(350, 300)
373
375 self._new_iface = False
376 self.interface_field.setVisible(False)
377 self.toolButton_CreateInterface.setVisible(False)
378 self.toolButton_EditInterface.setVisible(False)
379 self.textedit.setVisible(True)
380 if self.interface_field.currentText() in self._interfaces_files:
381 try:
382 with open(self._interfaces_files[self.interface_field.currentText()], 'rw') as f:
383 iface = f.read()
384 self.textedit.setText(iface)
385 except Exception as e:
386 MessageBox.warning(self, "Edit sync interface",
387 "Error while open interface",
388 utf8(e))
389 self.resize(350, 300)
390
392 self.toolButton_SyncAll.setVisible(True)
393
394 self.toolButton_SyncTopicOnDemand.setVisible(True)
395 self.toolButton_SelectInterface.setVisible(True)
396 self.interface_field.setVisible(False)
397 self.toolButton_CreateInterface.setVisible(False)
398 self.toolButton_EditInterface.setVisible(False)
399 self.textedit.setVisible(False)
400 self.buttonBox.clear()
401 self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
402 self.resize(350, 160)
403
406 '''
407 A thread to list all sync interface files and
408 publish there be sending a QT signal.
409 '''
410 interfaces = Signal(dict)
411 '''
412 @ivar: interfaces is a signal, which is emitted, if a list with sync files was created.
413 '''
414
416 '''
417 '''
418 QObject.__init__(self)
419 threading.Thread.__init__(self)
420 self._interfaces_files = None
421 self.setDaemon(True)
422
424 '''
425 '''
426 try:
427
428 self.root_paths = [os.path.normpath(p) for p in os.getenv("ROS_PACKAGE_PATH").split(':')]
429 self._interfaces_files = {}
430 for p in self.root_paths:
431 ret = self._getInterfaces(p)
432 self._interfaces_files = dict(ret.items() + self._interfaces_files.items())
433 self.interfaces.emit(self._interfaces_files)
434 except:
435 import traceback
436 formatted_lines = traceback.format_exc(1).splitlines()
437 rospy.logwarn("Error while list sync interfaces:\n\t%s", formatted_lines[-1])
438
440 result = {}
441 if os.path.isdir(path):
442 fileList = os.listdir(path)
443
444 if not package:
445
446 if is_package(fileList):
447 package = os.path.basename(path)
448 for f in fileList:
449 ret = self._getInterfaces(os.path.join(path, f), package)
450 result = dict(ret.items() + result.items())
451 elif package and os.path.isfile(path) and path.endswith('.sync'):
452
453 return {''.join(['pkg://', package, '///', os.path.basename(path)]): path}
454 return result
455