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