func_widget.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # Copyright (c) 2011, Dorian Scholz, TU Darmstadt
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of the TU Darmstadt nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 from __future__ import division
34 import os
35 import sys
36 
37 from python_qt_binding import loadUi
38 from python_qt_binding.QtCore import Qt, QTimer, Signal, Slot, QSize, QRectF, QSizeF, Property, QSortFilterProxyModel
39 from python_qt_binding.QtGui import QIcon, QFont, QFontMetrics, QTextDocument, QTextOption, QPen, QPainter, QColor, QTextCursor
40 from python_qt_binding.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout, QLabel, QPlainTextEdit, QListWidget, QCompleter
41 from python_qt_binding.QtWidgets import QStyledItemDelegate, QItemDelegate, QPushButton, QLineEdit, QTextEdit
42 from python_qt_binding.QtWidgets import QSlider, QAbstractItemView, QListWidgetItem, QStyleOptionViewItem
43 from python_qt_binding.QtWidgets import QHeaderView, QMenu, QTreeWidgetItem, QWidget, QAbstractItemView
44 
45 
46 import roslib
47 import rospkg
48 import rospy
49 from rospy.exceptions import ROSException
50 
51 from .topic_info import TopicInfo
52 
53 from dyn_tune.srv import ListAvailableFunctions
54 from dyn_tune.msg import Function, Task
55 
56 
57 
58 import topic_widget as topic_widget
59 from .topic_widget import TreeWidgetItem
60 
61 from dyn_tune import function
62 
63 import dyn_tune.function
64 
65 import syntax
66 import re
67 import numpy as np
68 
69 class FuncEditorDelegate(QStyledItemDelegate):
70 
71  def __init__(self, parent = None):
72  super(QStyledItemDelegate, self).__init__(parent)
73  self.block = False
74  self.editor = None
75 
76 
77  def createEditor(self, parent, option, index):
78  editor = QTextEdit(parent)
79  highlight = syntax.PythonHighlighter(editor.document())
80 
81  font = QFont("Courier")
82  font.setFamily("Courier");
83  font.setStyleHint(QFont.Monospace);
84  font.setFixedPitch(True);
85  font.setPointSize(10);
86  editor.setFont(font)
87 
88  tab_stop = 4; # 4 characters
89  metrics = QFontMetrics(font)
90  editor.setTabStopWidth(tab_stop * metrics.width(' '));
91 
92  return editor
93 
94  def setModelData(self, editor, model, index):
95  index.model().setData(index, editor.toPlainText(), Qt.EditRole)
96  self.editor = None
97  self.block = False
98  pass
99 
100  def setEditorData(self, editor, index):
101  if self.block:
102  return
103  self.block = True
104  editor.setPlainText(index.data(Qt.EditRole))
105 
106  def text_changed():
107  editor.blockSignals(True)
108  index.model().setData(index, editor.toPlainText(), Qt.EditRole)
109  print "text changed"
110  print editor.toHtml()
111 
112  editor.blockSignals(False)
113 
114  editor.textChanged.connect(text_changed)
115 
116  self.editor = editor
117  self.last_index = index
118 
119  doc = editor.document()
120  cursor = QTextCursor(doc);
121  cursor.movePosition(QTextCursor.End);
122  editor.setTextCursor(cursor);
123 
124 
125  def sizeHint(self, option, index):
126  item = self.parent().item(index.row())
127  doc = QTextDocument()
128 
129  font = QFont("Courier")
130  font.setFamily("Courier");
131  font.setStyleHint(QFont.Monospace);
132  font.setFixedPitch(True);
133  font.setPointSize(self.parent().font().pointSize());
134  doc.setDefaultFont(font)
135 
136  text = index.data(Qt.EditRole)
137  text = text.replace("\t", ''.join([' '] * 4))
138 
139  doc.setPlainText(text)
140  doc.setDefaultStyleSheet("background-color: red;")
141 
142  return QSize(doc.size().width(), doc.size().height())
143 
144 
145  def paint(self, painter, option, index):
146 
147  is_dark = True
148 
149  item = self.parent().item(index.row())
150 
151  if item.faulty:
152  pen = QPen(QColor(255, 117, 117), 3)
153  painter.setPen(pen)
154  painter.drawRect(option.rect)
155  elif item == self.parent().currentItem():
156  pen = QPen(Qt.white, 3)
157  painter.setPen(pen)
158  painter.drawRect(option.rect)
159 
160 
161  if not item.has_script:
162  painter.fillRect(option.rect, QColor(153, 153, 153))
163  else:
164  painter.fillRect(option.rect, Qt.white)
165  is_dark = False
166 
167  doc = QTextDocument()
168  highlight = syntax.PythonHighlighter(doc, is_dark = is_dark)
169 
170  font = QFont("Courier")
171  font.setFamily("Courier");
172  font.setStyleHint(QFont.Monospace);
173  font.setFixedPitch(True);
174  font.setPointSize(self.parent().font().pointSize())
175  doc.setDefaultFont(font)
176 
177 
178  text = index.data(Qt.EditRole)
179  text = text.replace("\t", ''.join([' '] * 4))
180 
181  doc.setPlainText(text)
182  doc.setDefaultStyleSheet("background-color: red;")
183 
184  painter.translate(option.rect.topLeft())
185  doc.drawContents(painter)
186  painter.resetTransform()
187 
188 
189 
190 class FuncWidget(QWidget):
191  """
192  main class inherits from the ui window class.
193 
194  You can specify the topics that the topic pane.
195 
196  FuncWidget.start must be called in order to update topic pane.
197  """
198 
199  SELECT_BY_NAME = 0
200  SELECT_BY_MSGTYPE = 1
201 
202  _column_names = ['topic', 'type', 'value', 'checkbox']
203 
204  def __init__(self, plugin=None, selected_topics=None, select_topic_type=SELECT_BY_NAME):
205  """
206  @type selected_topics: list of tuples.
207  @param selected_topics: [($NAME_TOPIC$, $TYPE_TOPIC$), ...]
208  @type select_topic_type: int
209  @param select_topic_type: Can specify either the name of topics or by
210  the type of topic, to filter the topics to
211  show. If 'select_topic_type' argument is
212  None, this arg shouldn't be meaningful.
213  """
214  super(FuncWidget, self).__init__()
215 
216  rp = rospkg.RosPack()
217  ui_file = os.path.join(rp.get_path('rqt_dyn_tune'), 'resource', 'FuncWidget.ui')
218  loadUi(ui_file, self)
219 
220  self._plugin = plugin
221  self.list_available_functions = rospy.ServiceProxy('/list_available_functions', ListAvailableFunctions)
222  self.functions = function.load_functions()
223  self._select_topic_type = select_topic_type
224  self.topics_tree_widget = self.widget_topic.topics_tree_widget
225 
226  self.execs = {}
227  self.values = {}
228  self.items = {}
229 
230 
231 
232 
233  self.hLayout_0.setStretchFactor(self.vLayout_0, 5)
234  self.hLayout_0.setStretchFactor(self.gridLayout, 2)
235 
236 
237  @Slot(dict)
238  def selection_changed(data):
239  self.func_list.clear()
240  funcs = self.list_available_functions(self.widget_topic.get_selected_types()).functions
241 
242  for func in funcs:
243  item = QListWidgetItem()
244  item.setData(Qt.UserRole, func)
245  item.setText(func + "(...)")
246  item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
247  self.func_list.addItem(item)
248 
249  self.block_label.setText("")
250 
251 
252  self.add_button.clicked.connect(self.add_button_clicked)
253  self.add_script.clicked.connect(self.add_script_clicked)
254 
255  self.widget_topic.topicsRefreshed.connect(self.topics_refreshed)
256  self.widget_topic.selectionChanged.connect(selection_changed)
257 
258  self.widget_topic.selectionChanged.emit([])
259 
260 
261  delegate = FuncEditorDelegate(self.block_list)
262  font = QFont("Monospace")
263 
264  self.block_list.setFont(font)
265  self.block_list.setItemDelegate(delegate)
266  self.block_list.setContentsMargins(100,10,10,100)
267  self.block_list.setWordWrap(True)
268  self.block_list.setTextElideMode(Qt.ElideNone)
269  self.block_list.setSelectionMode(QAbstractItemView.SingleSelection)
270  self.block_list.setDragEnabled(True)
271  self.block_list.viewport().setAcceptDrops(True)
272  self.block_list.setDropIndicatorShown(True)
273  self.block_list.setDragDropMode(QAbstractItemView.InternalMove)
274  self.block_list.setMouseTracking(True)
275  self.block_list.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
276  self.block_list.setSpacing(3)
277 
278  self.block_list.addItem( ListBlockItem("\"\"\"\n" \
279  + "# Add your own python script here! You could \n" \
280  + "# access the variables defined by the previous \n" \
281  + "# blocks. To access observable values use \n" \
282  + "# vars()['value_name'] or locals()['value_name'] \n" \
283  + "\"\"\"") )
284 
285 
286  self.func_list.setAlternatingRowColors(True)
287  self.func_list.itemSelectionChanged.connect(self.func_list_changed)
288  self.func_ret.setAutoFillBackground(True)
289 
290  completer = QCompleter(self.block_list.model())
291  completer = QCompleter(["bob", "bobby", "bud", "charles", "charlie"], self.func_ret)
292  self.func_ret.setCompleter(completer)
293  self.func_ret.setEditable(True)
294 
295  # add a filter model to filter matching items
296  self.pFilterModel = QSortFilterProxyModel(self.func_ret)
297  self.pFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
298  self.pFilterModel.setSourceModel(self.widget_topic.topics_tree_widget.model())
299 
300  # add a completer, which uses the filter model
301  self.completer = QCompleter(self.pFilterModel, self)
302  # always show all (filtered) completions
303  self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
304 
305  self.func_ret.setCompleter(self.completer)
306 
307  # connect signals
308  def filter(text):
309  print "Edited: ", text, "type: ", type(text)
310  self.pFilterModel.setFilterFixedString(str(text))
311 
312  def on_completer_activated(text):
313  if text:
314  index = self.func_ret.findText(str(text))
315  self.func_ret.setCurrentIndex(index)
316 
317  self.func_ret.lineEdit().textEdited[unicode].connect(filter)
318  self.completer.activated.connect(on_completer_activated)
319 
320 
321  def block_label_clicked(*arg):
322  # print "BLOCK LABEL CLICKED"
323  self.assign_var.setFocus()
324  self.assign_var.selectAll()
325 
326  self.block_label.clicked.connect(block_label_clicked)
327  # self.block_label.setLineWrapMode(QTextEdit.WidgetWidth)
328 
329 
330  def assign_var_text_changed(text = None):
331  if text == None:
332  text = self.assign_var.text()
333  font = self.assign_var.font()
334  fm = QFontMetrics(font);
335  pixelsWide = fm.width(text);
336  # pixelsHigh = fm.height();
337  self.assign_var.setFixedSize(pixelsWide + 10, self.assign_var.height())
338 
339  text = self.assign_var.text()
340  text = text.replace(" ", "")
341  self.assign_var.setText(text)
342 
343  self.assign_var.textChanged.connect(assign_var_text_changed)
344  self.assign_var.returnPressed.connect(self.add_button_clicked)
345 
346 
347 
348  def func_list_changed(self, *_):
349 
350 
351  args = self.widget_topic.get_selected()
352  func = self.func_list.selectedItems()
353 
354  if func and args:
355  func = func[0].data(Qt.UserRole)
356  first = " = {0}( ".format(func)
357  joint = ', '
358  second = "{0} )".format(joint.join(args))
359 
360  self.block_label.setText(first + second)
361  self.assign_var.setFocus()
362  self.assign_var.selectAll()
363 
364  else:
365  self.block_label.setText("")
366 
367 
368 
369 
370  def add_script_clicked(self, checked = False):
371  self.block_list.insertItem(self.block_list.currentRow() + 1, ListBlockItem())
372 
373  def add_button_clicked(self, checked = False):
374 
375  args = self.widget_topic.get_selected()
376  func = self.func_list.selectedItems()
377  retvar = self.assign_var.text()
378 
379  if args and func and retvar != '' and retvar != None:
380  func = func[0].data(Qt.UserRole)
381  print "there is items: ", args, func
382 
383 
384  first = "{0} = {1}( ".format(retvar, func)
385  spcs = len(first)
386  joint = ',\n' + ''.join([' '] * spcs)
387  second = "{0} )".format(joint.join(args))
388 
389  item = ListBlockItem((first + second),
390  func = self.functions[func],
391  args = args,
392  retvar = retvar,
393  has_script = False )
394 
395  font = QFont("Courier")
396  font.setFamily("Courier");
397  font.setStyleHint(QFont.Monospace);
398  font.setFixedPitch(True);
399  font.setPointSize(10);
400  item.setFont(font)
401 
402  self.widget_topic.clear_selection()
403  self.block_label.setText("")
404  self.block_list.addItem(item)
405  self.func_ret.setCurrentText(retvar)
406 
407 
408  def resolve_type(self, msg):
409  if hasattr(msg, '_type') and type(msg) != type:
410  return msg._type
411 
412  if hasattr(msg, 'dtype') and type(msg.dtype.name) == str:
413  return str(msg.dtype.name) + '[]'
414 
415  return type(msg).__name__
416 
417 
418  def create_function_msg(self, name = "objective", desc = 'A new objective function'):
419  func = Function()
420  func.name = name
421 
422  blocks = [ self.block_list.item(index) for index in xrange(self.block_list.count()) ]
423 
424  for block in blocks:
425 
426  task = Task()
427  task.has_script = block.has_script
428  task.script = block.data(Qt.EditRole)
429 
430  if block.func != None:
431  task.function = block.func.__name__
432  task.args = block.args
433  task.return_var = block.retvar
434 
435  func.tasks.append(task)
436 
437  func.return_var = self.func_ret.currentText()
438  func.return_type = "*"
439  func.arg_types = "[\"*\", []]"
440  func.description = desc
441 
442  return func
443 
444 
445  def topics_refreshed(self, values = {}):
446 
447  DUPLICATE_FACTOR = 10
448 
449  values = { key:np.array([value] * DUPLICATE_FACTOR) for key, value in values.items() }
450 
451  items = [ self.block_list.item(index) for index in xrange(self.block_list.count()) ]
452 
453  print self.block_list.count()
454  print "=========== EXECUTING ==========="
455  count = 0
456  for item in items:
457  print ">> block #%d" % count
458  count += 1
459  values.update( item.execute(values) )
460  print "_________________________________"
461  print "================================="
462 
463  _values = values.copy()
464 
465  for key in _values:
466  if re.match("__.*__",key):
467  del values[key]
468 
469  self.values = values
470 
471  # print values
472 
473  topic_widget = self.widget_topic
474 
475  for key, value in self.values.items():
476  if key not in topic_widget._tree_items:
477  topic_widget._tree_items[key] = TreeWidgetItem(topic_widget._toggle_monitoring, key, self.topics_tree_widget, is_topic = False)
478 
479  _item = topic_widget._tree_items[key]
480  _item.setText(topic_widget._column_index['topic'], key)
481  _item.setText(topic_widget._column_index['type'], self.resolve_type(value))
482  _item.setText(topic_widget._column_index['value'], repr(value))
483  _item.setData(topic_widget._column_index['value'], Qt.UserRole, value)
484 
485 
486  for key, item in topic_widget._tree_items.items():
487  if key not in self.values:
488 
489  topic_widget.unselect(key)
490  topic_widget.remove(key)
491 
492  print self.values
493  print "deleting", key
494 
495  def start(self):
496  # self.widget_topic.start()
497  pass
498 
499  def shutdown_plugin(self):
500  self.widget_topic.shutdown_plugin()
501 
502  # TODO(Enhancement) Save/Restore tree expansion state
503  def save_settings(self, plugin_settings, instance_settings):
504  print "do no saving"
505  pass
506  # header_state = self.topics_tree_widget.header().saveState()
507  # instance_settings.set_value('tree_widget_header_state', header_state)
508 
509  def restore_settings(self, pluggin_settings, instance_settings):
510  print "do no restoring"
511  pass
512  # if instance_settings.contains('tree_widget_header_state'):
513  # header_state = instance_settings.value('tree_widget_header_state')
514  # if not self.topics_tree_widget.header().restoreState(header_state):
515  # rospy.logwarn("rqt_dyn_tune: Failed to restore header state.")
516 
517 class ListBlockItem(QListWidgetItem):
518 
519  DEFAULT_TEXT = "\"\"\" Write your own python script here !!! \"\"\"\n"
520 
521  def __init__(self, text = None,
522  parent = None, func = None, args = [],
523  retvar = '', has_script = True):
524 
525  self.darkness = Property(bool, ListBlockItem.isDark, ListBlockItem.setDark)
526 
527  if text == None:
528  text = ListBlockItem.DEFAULT_TEXT
529 
530  super(QListWidgetItem, self).__init__(text, parent)
531 
532  self.func = func
533  self.args = args
534  self.retvar = retvar
535  self.has_script = has_script
536 
537  self.setFlags(
538  Qt.ItemIsDragEnabled |
539  Qt.ItemIsEnabled |
540  Qt.ItemIsSelectable |
541  # Qt.ItemIsUserCheckable |
542  (Qt.ItemIsEditable if has_script else Qt.ItemIsEnabled) )
543 
544 
545  self.faulty = False
546  self.setData(Qt.UserRole, False)
547 
548 
549 
550  def setDark(self, is_dark):
551  self.is_dark = is_dark
552 
553  def isDark(self):
554  return self.is_dark
555 
556  def execute(self, values):
557 
558  try:
559  if self.func != None:
560  values.update({self.retvar:self.func(*self.args, **values)})
561 
562  if self.has_script:
563  script = self.data(Qt.EditRole)
564  exec(script, values, values)
565 
566 
567  print "block executed successfullly"
568  self.faulty = False
569  self.setData(Qt.UserRole, False)
570  except:
571  print "block is faulty"
572 
573  import traceback
574  traceback.print_exc()
575  self.faulty = True
576  self.setData(Qt.UserRole, True)
577 
578  pass
579 
580  return values
581 
582 
583  def setData(self, role, value):
584  super(ListBlockItem, self).setData(role, value)
585 
586 
587 class StyledLabel(QLabel):
588 
589  clicked = Signal(name='clicked')
590 
591  def __init__(self, parent = None, flags = 0):
592  super(QLabel, self).__init__(parent=parent)
593  self.size = None # QSize(0., 0.)
594  self.is_resizable = True
595 
596  def mousePressEvent(self, event):
597  self.clicked.emit()
598 
599  def sizeHint(self):
600  if not self.is_resizable:
601  return super(QLabel, self).sizeHint()
602 
603  doc = QTextDocument()
604 
605  font = self.font()
606  font.setStyleHint(QFont.Monospace);
607  font.setFixedPitch(True);
608  doc.setDefaultFont(font)
609 
610  text = self.text()
611  doc.setPlainText(text)
612 
613  size = QSize(doc.size().width(), doc.size().height())
614 
615  if size != None:
616  return size
617 
618  return super(QLabel, self).sizeHint()
619 
620  def paintEvent(self, event):
621  painter = QPainter(self)
622 
623  doc = QTextDocument()
624  highlight = syntax.PythonHighlighter(doc, is_dark = True, default = ['white'])
625 
626  font = self.font()
627  font.setStyleHint(QFont.Monospace);
628  font.setFixedPitch(True);
629  doc.setDefaultFont(font)
630  self.width = event.rect().width()
631  text = self.text()
632 
633  doc.setPlainText(text)
634 
635  painter.translate(0., event.rect().center().y() - doc.size().height()/2.)
636  doc.drawContents(painter, QRectF(event.rect()))
637  painter.resetTransform()
638 
639  self.size = doc.size()
640 
def paint(self, painter, option, index)
Definition: func_widget.py:145
def __init__(self, parent=None, flags=0)
Definition: func_widget.py:591
def save_settings(self, plugin_settings, instance_settings)
Definition: func_widget.py:503
def __init__(self, plugin=None, selected_topics=None, select_topic_type=SELECT_BY_NAME)
Definition: func_widget.py:204
def setModelData(self, editor, model, index)
Definition: func_widget.py:94
def restore_settings(self, pluggin_settings, instance_settings)
Definition: func_widget.py:509
def create_function_msg(self, name="objective", desc='A new objective function')
Definition: func_widget.py:418
def __init__(self, text=None, parent=None, func=None, args=[], retvar='', has_script=True)
Definition: func_widget.py:523
def add_script_clicked(self, checked=False)
Definition: func_widget.py:370
def format(color, style='')
Definition: syntax.py:8
def setEditorData(self, editor, index)
Definition: func_widget.py:100
def createEditor(self, parent, option, index)
Definition: func_widget.py:77
def topics_refreshed(self, values={})
Definition: func_widget.py:445
def add_button_clicked(self, checked=False)
Definition: func_widget.py:373


rqt_dyn_tune
Author(s):
autogenerated on Mon Jun 10 2019 14:52:08