def _load_parameter(self,param_index,param_name, param_type,param_value, param_min, param_max, param_desc): x_check = 160 x ,y ,w ,h = 4,4 + 40 * param_index,150,20 label = QLabel(param_name,self.parameters) label.setGeometry(x,y,w,h) label.setToolTip(param_desc) label.show() if param_type == 'Boolean': checkbox = QCheckBox('',self.parameters) checkbox.setGeometry(x_check,y,w,h) checkbox.setChecked(param_value == '1') checkbox.setToolTip(param_desc) checkbox.stateChanged.connect(partial(self._handle_bool_param_changed,param_name)) checkbox.show() elif param_type == 'Integer': if len(param_min) > 0 and len(param_max) > 0 and int(param_max) < 5: slider = QSlider(Qt.Horizontal,self.parameters) slider.setMinimum(int(param_min)) slider.setMaximum(int(param_max)) slider.setValue(int(param_value)) slider.setTickPosition(QSlider.TicksBelow) slider.setGeometry(x_check,y,w,h) slider.setToolTip(param_desc) slider.setTickInterval(1) slider.setSingleStep(1) slider.setPageStep(1) slider.valueChanged.connect(partial(self._handle_param_changed,param_name)) slider.show() else: spin = QSpinBox(self.parameters) if len(param_min) > 0: spin.setMinimum(int(param_min)) if len(param_max) > 0: spin.setMaximum(int(param_max)) spin.setValue(int(param_value)) spin.setToolTip(param_desc) spin.setGeometry(x_check,y,w,h) spin.valueChanged.connect(partial(self._handle_param_changed,param_name)) spin.show() elif param_type == 'Double': spin = QDoubleSpinBox(self.parameters) if len(param_min) > 0: spin.setMinimum(float(param_min)) if len(param_max) > 0: spin.setMaximum(float(param_max)) spin.setValue(float(param_value)) spin.valueChanged.connect(partial(self._handle_param_changed,param_name)) spin.setGeometry(x_check,y,w,h) spin.show() elif param_type == 'String': lineEdit = QLineEdit(self.parameters) lineEdit.setText(param_value) lineEdit.setToolTip(param_desc) lineEdit.setGeometry(x_check,y,w,h) lineEdit.textChanged.connect(partial(self._handle_param_changed,param_name)) lineEdit.show() self.parameters.update()
class PathEditor(QWidget): ''' This is a path editor used as ItemDeligate in settings view. This editor provides an additional button for directory selection dialog. ''' editing_finished_signal = Signal() def __init__(self, path, parent=None): QWidget.__init__(self, parent) self.path = path self._layout = QHBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._button = QPushButton('...') self._button.setMaximumSize(QSize(24, 20)) self._button.clicked.connect(self._on_path_select_clicked) self._layout.addWidget(self._button) self._lineedit = QLineEdit(path) self._lineedit.returnPressed.connect(self._on_editing_finished) self._layout.addWidget(self._lineedit) self.setLayout(self._layout) self.setFocusProxy(self._button) self.setAutoFillBackground(True) def _on_path_select_clicked(self): # Workaround for QFileDialog.getExistingDirectory because it do not # select the configuration folder in the dialog self.dialog = QFileDialog(self, caption='Select a new settings folder') self.dialog.setOption(QFileDialog.HideNameFilterDetails, True) self.dialog.setFileMode(QFileDialog.Directory) self.dialog.setDirectory(self.path) if self.dialog.exec_(): fileNames = self.dialog.selectedFiles() path = fileNames[0] if os.path.isfile(path): path = os.path.basename(path) self._lineedit.setText(path) self.path = dir self.editing_finished_signal.emit() def _on_editing_finished(self): if self._lineedit.text(): self.path = self._lineedit.text() self.editing_finished_signal.emit()
class PathEditor(QWidget): ''' This is a path editor used as ItemDeligate in settings view. This editor provides an additional button for directory selection dialog. ''' editing_finished_signal = Signal() def __init__(self, path, parent=None): QWidget.__init__(self, parent) self.path = path self._layout = QHBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._button = QPushButton('...') self._button.setMaximumSize(QSize(24, 20)) self._button.clicked.connect(self._on_path_select_clicked) self._layout.addWidget(self._button) self._lineedit = QLineEdit(path) self._lineedit.returnPressed.connect(self._on_editing_finished) self._layout.addWidget(self._lineedit) self.setLayout(self._layout) self.setFocusProxy(self._button) self.setAutoFillBackground(True) def _on_path_select_clicked(self): # Workaround for QFileDialog.getExistingDirectory because it do not # select the configuration folder in the dialog self.dialog = QFileDialog(self, caption='Select a new settings folder') self.dialog.setOption(QFileDialog.HideNameFilterDetails, True) self.dialog.setFileMode(QFileDialog.Directory) self.dialog.setDirectory(self.path) if self.dialog.exec_(): fileNames = self.dialog.selectedFiles() path = fileNames[0] if os.path.isfile(path): path = os.path.basename(path) self._lineedit.setText(path) self.path = dir self.editing_finished_signal.emit() def _on_editing_finished(self): if self._lineedit.text(): self.path = self._lineedit.text() self.editing_finished_signal.emit()
class Top(Plugin): NODE_FIELDS = [ 'pid', 'get_cpu_percent', 'get_memory_percent', 'get_num_threads'] OUT_FIELDS = ['node_name', 'pid', 'cpu_percent', 'memory_percent', 'num_threads' ] FORMAT_STRS = ['%s', '%s', '%0.2f', '%0.2f', '%s' ] NODE_LABELS = ['Node', 'PID', 'CPU %', 'Mem %', 'Num Threads' ] SORT_TYPE = [str, str, float, float, float ] TOOLTIPS = { 0: ('cmdline', lambda x: '\n'.join(textwrap.wrap(' '.join(x)))), 3: ('memory_info', lambda x: ('Resident: %0.2f MiB, Virtual: %0.2f MiB' % (x[0]/2**20, x[1]/2**20))) } _node_info = NodeInfo() name_filter = re.compile('') def __init__(self, context): super(Top, self).__init__(context) # Give QObjects reasonable names self.setObjectName('Top') # Process standalone plugin command-line arguments from argparse import ArgumentParser parser = ArgumentParser() # Add argument(s) to the parser. parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", help="Put plugin in silent mode") args, unknowns = parser.parse_known_args(context.argv()) # if not args.quiet: # print 'arguments: ', args # print 'unknowns: ', unknowns self._selected_node = '' self._selected_node_lock = RLock() # Setup the toolbar self._toolbar = QToolBar() self._filter_box = QLineEdit() self._regex_box = QCheckBox() self._regex_box.setText('regex') self._toolbar.addWidget(QLabel('Filter')) self._toolbar.addWidget(self._filter_box) self._toolbar.addWidget(self._regex_box) self._filter_box.returnPressed.connect(self.update_filter) self._regex_box.stateChanged.connect(self.update_filter) # Create a container widget and give it a layout self._container = QWidget() self._container.setWindowTitle('Process Monitor') self._layout = QVBoxLayout() self._container.setLayout(self._layout) self._layout.addWidget(self._toolbar) # Create the table widget self._table_widget = QTreeWidget() self._table_widget.setObjectName('TopTable') self._table_widget.setColumnCount(len(self.NODE_LABELS)) self._table_widget.setHeaderLabels(self.NODE_LABELS) self._table_widget.itemClicked.connect(self._tableItemClicked) self._table_widget.setSortingEnabled(True) self._table_widget.setAlternatingRowColors(True) self._layout.addWidget(self._table_widget) context.add_widget(self._container) # Add a button for killing nodes self._kill_button = QPushButton('Kill Node') self._layout.addWidget(self._kill_button) self._kill_button.clicked.connect(self._kill_node) # Update twice since the first cpu% lookup will always return 0 self.update_table() self.update_table() self._table_widget.resizeColumnToContents(0) # Start a timer to trigger updates self._update_timer = QTimer() self._update_timer.setInterval(1000) self._update_timer.timeout.connect(self.update_table) self._update_timer.start() def _tableItemClicked(self, item, column): with self._selected_node_lock: self._selected_node = item.text(0) def update_filter(self, *args): if self._regex_box.isChecked(): expr = self._filter_box.text() else: expr = re.escape(self._filter_box.text()) self.name_filter = re.compile(expr) self.update_table() def _kill_node(self): self._node_info.kill_node(self._selected_node) def update_one_item(self, row, info): twi = TopWidgetItem() for col, field in enumerate(self.OUT_FIELDS): val = info[field] twi.setText(col, self.FORMAT_STRS[col] % val) self._table_widget.insertTopLevelItem(row, twi) for col, (key, func) in self.TOOLTIPS.iteritems(): twi.setToolTip(col, func(info[key])) with self._selected_node_lock: if twi.text(0) == self._selected_node: twi.setSelected(True) self._table_widget.setItemHidden(twi, len(self.name_filter.findall(info['node_name'])) == 0) def update_table(self): self._table_widget.clear() infos = self._node_info.get_all_node_fields(self.NODE_FIELDS) for nx, info in enumerate(infos): self.update_one_item(nx, info) def shutdown_plugin(self): self._update_timer.stop() def save_settings(self, plugin_settings, instance_settings): instance_settings.set_value('filter_text', self._filter_box.text()) instance_settings.set_value('is_regex', int(self._regex_box.checkState())) def restore_settings(self, plugin_settings, instance_settings): self._filter_box.setText(instance_settings.value('filter_text')) is_regex_int = instance_settings.value('is_regex') if is_regex_int: self._regex_box.setCheckState(Qt.CheckState(is_regex_int)) else: self._regex_box.setCheckState(Qt.CheckState(0)) self.update_filter()
class LocationFunction(object): EDIT_LOCATION_PROPERITIES = 'Edit Location Properties' ADD_LOCATION_AREA = 'Add Location' EDIT_EXISTING_AREA = 'Edit Location' def __init__(self, location_file, map, widget, subfunction_layout, configuration_layout, image): self.edit_area_button = None self.edit_area_selection_color = Qt.black # Dictionary of polygons self.locations = {} # Dictionary that maps location names to their colors self.location_colors = {} self.draw_location = {} self.unique_loc_counter = 1 self.editing_area = False self.edit_existing_location = None self.editing_properties = False self.edit_properties_location = None # Use this to initialize variables. self.clearAreaSelection() self.is_modified = False self.widget = widget self.subfunction_layout = subfunction_layout self.image = image self.image_size = image.overlay_image.size() self.configuration_layout = configuration_layout self.location_file = location_file self.map_size = QSize(map.map.info.width, map.map.info.height) self.readLocationsFromFile() self.edit_area_button = {} def readLocationsFromFile(self): if os.path.isfile(self.location_file): stream = open(self.location_file, 'r') try: contents = yaml.load(stream) if "polygons" not in contents or "locations" not in contents: rospy.logerr( "YAML file found at " + self.location_file + ", but does not seem to have been written by this tool. I'm starting locations from scratch." ) else: location_keys = contents["locations"] location_polygons = contents["polygons"] for index, location in enumerate(location_keys): self.locations[location] = QPolygon() self.locations[location].setPoints( location_polygons[index]) self.locations[location] = scalePolygon( self.locations[location], self.map_size, self.image_size) (_, self.location_colors[location] ) = self.getUniqueNameAndColor() self.draw_location[location] = True except yaml.YAMLError: rospy.logerr( "File found at " + self.location_file + ", but cannot be parsed by YAML parser. I'm starting locations from scratch." ) stream.close() else: rospy.logwarn( "Location file not found at " + self.location_file + ". I'm starting locations from scratch and will attempt to write to this location before exiting." ) def saveConfiguration(self): self.writeLocationsToFile() def writeLocationsToFile(self): out_dict = {} out_dict["locations"] = self.locations.keys() out_dict["polygons"] = [] for index, location in enumerate(self.locations): out_dict["polygons"].append([]) for i in range(self.locations[location].size()): pt = self.locations[location].point(i) scaled_pt = scalePoint(pt, self.image_size, self.map_size) out_dict["polygons"][index].append(scaled_pt.x()) out_dict["polygons"][index].append(scaled_pt.y()) data_directory = os.path.dirname(os.path.realpath(self.location_file)) image_file = getLocationsImageFileLocationFromDataDirectory( data_directory) # Create an image with the location data, so that C++ programs don't need to rely on determining regions using polygons. out_dict["data"] = 'locations.pgm' location_image = QImage(self.map_size, QImage.Format_RGB32) location_image.fill(Qt.white) painter = QPainter(location_image) for index, location in enumerate(self.locations): if index > 254: rospy.logerr( "You have more than 254 locations, which is unsupported by the bwi_planning_common C++ code!" ) painter.setPen(Qt.NoPen) painter.setBrush(QColor(index, index, index)) scaled_polygon = scalePolygon(self.locations[location], self.image_size, self.map_size) painter.drawPolygon(scaled_polygon) painter.end() location_image.save(image_file) stream = open(self.location_file, 'w') yaml.dump(out_dict, stream) stream.close() self.is_modified = False def deactivateFunction(self): if self.editing_area: self.endAreaEdit("Cancel") elif self.editing_properties: self.endPropertyEdit() clearLayoutAndFixHeight(self.subfunction_layout) self.edit_area_button.clear() self.image.enableDefaultMouseHooks() # Just in case we were editing a location, that location was not being drawn. for location in self.draw_location: self.draw_location[location] = True def activateFunction(self): # Add all the necessary buttons to the subfunction layout. clearLayoutAndFixHeight(self.subfunction_layout) for button_text in [ LocationFunction.ADD_LOCATION_AREA, LocationFunction.EDIT_EXISTING_AREA ]: button = QPushButton(button_text, self.widget) button.clicked[bool].connect( partial(self.startAreaEdit, button_text)) button.setCheckable(True) self.subfunction_layout.addWidget(button) self.edit_area_button[button_text] = button self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setEnabled( False) self.subfunction_layout.addStretch(1) # ActivateMouseHooks. self.image.mousePressEvent = self.mousePressEvent self.image.mouseMoveEvent = self.mouseMoveEvent self.image.mouseReleaseEvent = self.mouseReleaseEvent self.updateOverlay() def getLocationNameFromPoint(self, point): for location in self.locations: if self.locations[location].containsPoint(point, Qt.OddEvenFill): return location return None def startAreaEdit(self, edit_type): if self.editing_properties: self.endPropertyEdit() self.editing_area = True if edit_type == LocationFunction.ADD_LOCATION_AREA: self.edit_existing_location = None # else edit_existing_location was set to the correct location by startPropertyEdit() # Make sure all active selections have been cleared. self.clearAreaSelection() # If we're going to edit an existing area, stop drawing it and copy it to the active selection. if self.edit_existing_location is not None: self.draw_location[self.edit_existing_location] = False self.current_selection = QPolygon( self.locations[self.edit_existing_location]) self.edit_existing_location = self.edit_existing_location # Setup the buttons in the configuration toolbar, and disable the original buttons to edit an area. clearLayoutAndFixHeight(self.configuration_layout) for button_text in ["Done", "Cancel"]: button = QPushButton(button_text, self.widget) button.clicked[bool].connect(partial(self.endAreaEdit, button_text)) self.configuration_layout.addWidget(button) self.configuration_layout.addStretch(1) self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setEnabled( False) self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setEnabled( False) self.updateOverlay() def clearAreaSelection(self): # Make sure all selections are clear. self.new_selection_start_point = None self.new_selection_end_point = None # QPolygons to track current location. self.current_selection = None self.new_selection = None self.subtract_new_selection = None def endAreaEdit(self, button_text): edit_properties_location = None if (button_text == "Done") and (self.current_selection is not None) and ( not self.current_selection.isEmpty()): # If the current location being added completely wipes out an old location, make sure you remove it. for location in self.locations.keys(): if location != self.edit_existing_location: self.locations[location] = self.locations[ location].subtracted(self.current_selection) if self.locations[location].isEmpty(): self.removeLocation(location) if self.edit_existing_location == None: # We're adding a new location. Generate a new location name and color. (self.edit_existing_location, new_location_color) = self.getUniqueNameAndColor() self.location_colors[ self.edit_existing_location] = new_location_color self.locations[ self.edit_existing_location] = self.current_selection self.draw_location[self.edit_existing_location] = True edit_properties_location = self.edit_existing_location # Since a location was added or edited, set file modification to true. self.is_modified = True else: # Cancel was pressed, draw the original location if we were editing as before. if self.edit_existing_location is not None: self.draw_location[self.edit_existing_location] = True self.editing_area = False self.edit_existing_location = None self.clearAreaSelection() # Update the entire image overlay. self.updateOverlay() self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setEnabled( True) self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setChecked( False) self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setChecked( False) clearLayoutAndFixHeight(self.configuration_layout) if edit_properties_location is not None: self.edit_properties_location = edit_properties_location self.startPropertyEdit() def startPropertyEdit(self): self.editing_properties = True self.edit_existing_location = self.edit_properties_location self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setEnabled( True) self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setEnabled( True) # Construct the configuration layout. clearLayoutAndFixHeight(self.configuration_layout) self.update_name_label = QLabel( "Location (" + self.edit_properties_location + ") New Name: ", self.widget) self.configuration_layout.addWidget(self.update_name_label) self.update_name_textedit = QLineEdit(self.widget) self.update_name_textedit.setText(self.edit_properties_location) self.update_name_textedit.textEdited.connect( self.locationNameTextEdited) self.configuration_layout.addWidget(self.update_name_textedit) self.update_name_button = QPushButton("Update location Name", self.widget) self.update_name_button.clicked[bool].connect(self.updateLocationName) self.update_name_button.setEnabled(False) self.configuration_layout.addWidget(self.update_name_button) self.remove_location_button = QPushButton("Remove Location", self.widget) self.remove_location_button.clicked[bool].connect( self.removeCurrentLocation) self.configuration_layout.addWidget(self.remove_location_button) self.configuration_layout.addStretch(1) self.updateOverlay() def endPropertyEdit(self): self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setEnabled( True) self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setEnabled( False) clearLayoutAndFixHeight(self.configuration_layout) self.update_name_label = None self.update_name_textedit = None self.update_name_button = None self.editing_properties = False self.edit_properties_location = None self.updateOverlay() def locationNameTextEdited(self, text): if str(text) != self.edit_properties_location: self.update_name_button.setEnabled(True) else: self.update_name_button.setEnabled(False) def updateLocationName(self): old_loc_name = self.edit_properties_location new_loc_name = str(self.update_name_textedit.text()) if new_loc_name in self.locations: # This means that two locations need to be merged self.locations[new_loc_name] = self.locations[new_loc_name].united( self.locations.pop(old_loc_name)) else: # This is a simple rename task. self.locations[new_loc_name] = self.locations.pop(old_loc_name) self.location_colors[new_loc_name] = self.location_colors.pop( old_loc_name) self.draw_location[new_loc_name] = self.draw_location.pop( old_loc_name) # Since a location name was modified, set file modification to true. self.is_modified = True # Restart property edit with the updated name. self.endPropertyEdit() self.edit_properties_location = new_loc_name self.startPropertyEdit() def removeCurrentLocation(self): old_loc_name = self.edit_properties_location self.removeLocation(old_loc_name) self.endPropertyEdit() self.updateOverlay() # Since a location was removed, set file modification to true. self.is_modified = True def removeLocation(self, loc_name): if loc_name in self.locations: self.locations.pop(loc_name) if loc_name in self.location_colors: self.location_colors.pop(loc_name) if loc_name in self.draw_location: self.draw_location.pop(loc_name) def isModified(self): return self.is_modified def mousePressEvent(self, event): if self.editing_area: self.subtract_new_selection = event.button() == Qt.RightButton self.new_selection_start_point = event.pos() self.new_selection_end_point = event.pos() self.new_selection = None else: loc = self.getLocationNameFromPoint(event.pos()) if loc is not None: self.edit_properties_location = loc self.startPropertyEdit() else: self.endPropertyEdit() def mouseReleaseEvent(self, event): if self.editing_area: self.mouseMoveEvent(event) if self.new_selection is not None: if self.current_selection is None and self.subtract_new_selection == False: self.current_selection = self.new_selection if self.subtract_new_selection: self.current_selection = self.current_selection.subtracted( self.new_selection) else: self.current_selection = self.current_selection.united( self.new_selection) self.new_selection = None self.subtract_new_selection = None def mouseMoveEvent(self, event): if self.editing_area: # First make sure we update the region corresponding to the old mark. old_overlay_update_rect = self.get_rectangular_polygon( self.new_selection_start_point, self.new_selection_end_point) # Draw new mark, taking some care to reduce the size of the polygon's bottom right corner by (1,1). self.new_selection_end_point = event.pos() self.new_selection = self.get_rectangular_polygon( self.new_selection_start_point, self.new_selection_end_point) self.new_selection = self.new_selection.boundingRect() self.new_selection.setHeight(self.new_selection.height() - 1) self.new_selection.setWidth(self.new_selection.width() - 1) self.new_selection = QPolygon(self.new_selection, True) # Next determine the region that needs to be update because of the new mark. new_overlay_update_rect = self.get_rectangular_polygon( self.new_selection_start_point, self.new_selection_end_point) overlay_update_region = (old_overlay_update_rect + new_overlay_update_rect).boundingRect() self.updateOverlay(overlay_update_region) def updateOverlay(self, rect=None): # Redraw the overlay image from scratch using the location image and current location. self.image.overlay_image.fill(Qt.transparent) painter = QPainter(self.image.overlay_image) painter.setBackgroundMode(Qt.TransparentMode) painter.setCompositionMode(QPainter.CompositionMode_Source) for location in self.locations: if self.draw_location[location]: color = self.location_colors[location] if self.edit_properties_location == location and self.editing_properties: color = self.edit_area_selection_color lineColor = QColor(color) lineColor.setAlpha(255) brushColor = QColor(color) brushColor.setAlpha(128) painter.setPen(lineColor) painter.setBrush(brushColor) painter.drawPolygon(self.locations[location]) if (self.current_selection is not None) or (self.new_selection is not None): lineColor = QColor(self.edit_area_selection_color) lineColor.setAlpha(255) brushColor = QColor(self.edit_area_selection_color) brushColor.setAlpha(128) painter.setPen(lineColor) painter.setBrush(brushColor) if self.new_selection is not None: # Create a temporary polygon as the new selection is being drawn. if self.current_selection is not None: current_selection = QPolygon(self.current_selection) if self.subtract_new_selection: current_selection = current_selection.subtracted( self.new_selection) else: current_selection = current_selection.united( self.new_selection) painter.drawPolygon(current_selection) elif self.subtract_new_selection == False: painter.drawPolygon(self.new_selection) else: painter.drawPolygon(self.current_selection) painter.end() if rect is None: self.image.update() else: self.image.update(rect) def getUniqueNameAndColor(self): """ Use golden ratio to generate unique colors. http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ """ name = "new_loc" + str(self.unique_loc_counter) h = int(359 * (self.unique_loc_counter * 0.618033988749895)) h = h % 359 self.unique_loc_counter += 1 return name, QColor.fromHsv(h, 255, 255) def get_rectangular_polygon(self, pt1, pt2): return QPolygon( [pt1, QPoint(pt1.x(), pt2.y()), pt2, QPoint(pt2.x(), pt1.y())])
class Top(Plugin): NODE_FIELDS = [ 'pid', 'get_cpu_percent', 'get_memory_percent', 'get_num_threads' ] OUT_FIELDS = [ 'node_name', 'pid', 'cpu_percent', 'memory_percent', 'num_threads' ] FORMAT_STRS = ['%s', '%s', '%0.2f', '%0.2f', '%s'] NODE_LABELS = ['Node', 'PID', 'CPU %', 'Mem %', 'Num Threads'] SORT_TYPE = [str, str, float, float, float] TOOLTIPS = { 0: ('cmdline', lambda x: '\n'.join(textwrap.wrap(' '.join(x)))), 3: ('memory_info', lambda x: ('Resident: %0.2f MiB, Virtual: %0.2f MiB' % (x[0] / 2**20, x[1] / 2**20))) } _node_info = NodeInfo() name_filter = re.compile('') def __init__(self, context): super(Top, self).__init__(context) # Give QObjects reasonable names self.setObjectName('Top') # Process standalone plugin command-line arguments from argparse import ArgumentParser parser = ArgumentParser() # Add argument(s) to the parser. parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", help="Put plugin in silent mode") args, unknowns = parser.parse_known_args(context.argv()) # if not args.quiet: # print 'arguments: ', args # print 'unknowns: ', unknowns self._selected_node = '' self._selected_node_lock = RLock() # Setup the toolbar self._toolbar = QToolBar() self._filter_box = QLineEdit() self._regex_box = QCheckBox() self._regex_box.setText('regex') self._toolbar.addWidget(QLabel('Filter')) self._toolbar.addWidget(self._filter_box) self._toolbar.addWidget(self._regex_box) self._filter_box.returnPressed.connect(self.update_filter) self._regex_box.stateChanged.connect(self.update_filter) # Create a container widget and give it a layout self._container = QWidget() self._container.setWindowTitle('Process Monitor') self._layout = QVBoxLayout() self._container.setLayout(self._layout) self._layout.addWidget(self._toolbar) # Create the table widget self._table_widget = QTreeWidget() self._table_widget.setObjectName('TopTable') self._table_widget.setColumnCount(len(self.NODE_LABELS)) self._table_widget.setHeaderLabels(self.NODE_LABELS) self._table_widget.itemClicked.connect(self._tableItemClicked) self._table_widget.setSortingEnabled(True) self._table_widget.setAlternatingRowColors(True) self._layout.addWidget(self._table_widget) context.add_widget(self._container) # Add a button for killing nodes self._kill_button = QPushButton('Kill Node') self._layout.addWidget(self._kill_button) self._kill_button.clicked.connect(self._kill_node) # Update twice since the first cpu% lookup will always return 0 self.update_table() self.update_table() self._table_widget.resizeColumnToContents(0) # Start a timer to trigger updates self._update_timer = QTimer() self._update_timer.setInterval(1000) self._update_timer.timeout.connect(self.update_table) self._update_timer.start() def _tableItemClicked(self, item, column): with self._selected_node_lock: self._selected_node = item.text(0) def update_filter(self, *args): if self._regex_box.isChecked(): expr = self._filter_box.text() else: expr = re.escape(self._filter_box.text()) self.name_filter = re.compile(expr) self.update_table() def _kill_node(self): self._node_info.kill_node(self._selected_node) def update_one_item(self, row, info): twi = TopWidgetItem() for col, field in enumerate(self.OUT_FIELDS): val = info[field] twi.setText(col, self.FORMAT_STRS[col] % val) self._table_widget.insertTopLevelItem(row, twi) for col, (key, func) in self.TOOLTIPS.iteritems(): twi.setToolTip(col, func(info[key])) with self._selected_node_lock: if twi.text(0) == self._selected_node: twi.setSelected(True) self._table_widget.setItemHidden( twi, len(self.name_filter.findall(info['node_name'])) == 0) def update_table(self): self._table_widget.clear() infos = self._node_info.get_all_node_fields(self.NODE_FIELDS) for nx, info in enumerate(infos): self.update_one_item(nx, info) def shutdown_plugin(self): self._update_timer.stop() def save_settings(self, plugin_settings, instance_settings): instance_settings.set_value('filter_text', self._filter_box.text()) instance_settings.set_value('is_regex', int(self._regex_box.checkState())) def restore_settings(self, plugin_settings, instance_settings): self._filter_box.setText(instance_settings.value('filter_text')) is_regex_int = instance_settings.value('is_regex') if is_regex_int: self._regex_box.setCheckState(Qt.CheckState(is_regex_int)) else: self._regex_box.setCheckState(Qt.CheckState(0)) self.update_filter()
class RunDialog(PackageDialog): ''' A dialog to run a ROS node without configuration ''' def __init__(self, host, masteruri=None, parent=None): PackageDialog.__init__(self, masteruri, parent) self.host = host self.setWindowTitle('Run') ns_name_label = QLabel("NS/Name:", self.content) self.ns_field = QComboBox(self.content) self.ns_field.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.ns_field.setEditable(True) ns_history = nm.history().cachedParamValues('run_dialog/NS') ns_history.insert(0, '/') self.ns_field.addItems(ns_history) self.name_field = QLineEdit(self.content) self.name_field.setEnabled(False) horizontalLayout = QHBoxLayout() horizontalLayout.addWidget(self.ns_field) horizontalLayout.addWidget(self.name_field) self.contentLayout.addRow(ns_name_label, horizontalLayout) args_label = QLabel("Args:", self.content) self.args_field = QComboBox(self.content) self.args_field.setSizeAdjustPolicy( QComboBox.AdjustToMinimumContentsLength) self.args_field.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.args_field.setEditable(True) self.contentLayout.addRow(args_label, self.args_field) args_history = nm.history().cachedParamValues('run_dialog/Args') args_history.insert(0, '') self.args_field.addItems(args_history) host_label = QLabel("Host:", self.content) self.host_field = QComboBox(self.content) self.host_field.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.host_field.setEditable(True) host_label.setBuddy(self.host_field) self.contentLayout.addRow(host_label, self.host_field) self.host_history = host_history = nm.history().cachedParamValues( '/Host') if self.host in host_history: host_history.remove(self.host) host_history.insert(0, self.host) self.host_field.addItems(host_history) master_label = QLabel("ROS Master URI:", self.content) self.master_field = QComboBox(self.content) self.master_field.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.master_field.setEditable(True) master_label.setBuddy(self.host_field) self.contentLayout.addRow(master_label, self.master_field) self.master_history = master_history = nm.history().cachedParamValues( '/Optional Parameter/ROS Master URI') if self.masteruri in master_history: master_history.remove(self.masteruri) master_history.insert(0, self.masteruri) self.master_field.addItems(master_history) self.binary_field.activated[str].connect(self.on_binary_selected) def run_params(self): ''' Runs the selected node, or do nothing. :return: a tuple with host, package, binary, name, args, maseruri or empty tuple on errors ''' self.binary = self.binary_field.currentText() self.host = self.host_field.currentText( ) if self.host_field.currentText() else self.host self.masteruri = self.master_field.currentText( ) if self.master_field.currentText() else self.masteruri if self.host not in self.host_history and self.host != 'localhost' and self.host != '127.0.0.1': nm.history().add2HostHistory(self.host) ns = self.ns_field.currentText() if ns and ns != '/': nm.history().addParamCache('run_dialog/NS', ns) args = self.args_field.currentText() if args: nm.history().addParamCache('run_dialog/Args', args) if self.package and self.binary: nm.history().addParamCache('/Host', self.host) return (self.host, self.package, self.binary, self.name_field.text(), ('__ns:=%s %s' % (ns, args)).split(' '), None if self.masteruri == 'ROS_MASTER_URI' else self.masteruri) return () def on_package_selected(self, package): PackageDialog.on_package_selected(self, package) if self.packages and package in self.packages: self.args_field.setEnabled(True) self.ns_field.setEnabled(True) self.name_field.setEnabled(True) root, _ext = os.path.splitext( os.path.basename(self.binary_field.currentText())) self.name_field.setText(root) def on_binary_selected(self, binary): root, _ext = os.path.splitext(os.path.basename(binary)) self.name_field.setText(root)
class SpectrogramPlugin(Plugin): update_signal = QtCore.pyqtSignal() subscriber_signal = QtCore.pyqtSignal(str) def __init__(self, context): super(SpectrogramPlugin, self).__init__(context) self.setObjectName('Spectrogram') self._widget = QWidget() layout = QVBoxLayout() self._widget.setLayout(layout) layout_ = QHBoxLayout() self.line_edit = QLineEdit() layout_.addWidget(self.line_edit) self.apply_btn = QPushButton("Apply") self.apply_btn.clicked.connect(self.apply_clicked) layout_.addWidget(self.apply_btn) layout.addLayout(layout_) self.fig = Figure((5, 4), dpi=100) self.canvas = FigureCanvas(self.fig) self.axes = self.fig.add_subplot(111) self.fig.tight_layout() layout.addWidget(self.canvas) context.add_widget(self._widget) self.update_signal.connect(self.update_spectrogram) self.subscriber_signal.connect(self.update_subscriber) self.subscriber_signal.emit('spec') def spectrum_callback(self, data): nch = data.nch len = data.nfreq if self.spectrogram is None: self.spectrogram = zeros([len, 1000]) s = array(data.data).reshape([nch, len, 2])[-1] s = linalg.norm(s, axis=1) s += 1e-8 log(s, s) self.spectrogram = roll(self.spectrogram, -1, 1) self.spectrogram[:, -1] = s if data.header.seq % 100 == 0: self.update_signal.emit() def apply_clicked(self): self.update_subscriber(self.line_edit.displayText()) def update_spectrogram(self): if self.spectrogram is not None: self.axes.clear() self.axes.imshow(self.spectrogram, aspect="auto", origin="lower", cmap="hot") self.axes.grid(None) self.axes.set_ylabel("Freq. [bin]") self.axes.set_xlabel("Time [frame]") self.fig.tight_layout() self.canvas.draw() QApplication.processEvents() def update_subscriber(self, topic_name): self.topic_name = topic_name self.line_edit.setText(self.topic_name) if hasattr(self, 'sub'): self.sub.unregister() self.spectrogram = None self.sub = rospy.Subscriber(topic_name, Spectrum, self.spectrum_callback, queue_size=500) def restore_settings(self, plugin_settings, instance_settings): topic_name = instance_settings.value('topic_name') self.subscriber_signal.emit(topic_name) def save_settings(self, plugin_settings, instance_settings): instance_settings.set_value('topic_name', self.topic_name) def shutdown_plugin(self): pass
class RunDialog(PackageDialog): ''' A dialog to run a ROS node without configuration ''' def __init__(self, host, masteruri=None, parent=None): PackageDialog.__init__(self, parent) self.host = host self.setWindowTitle('Run') ns_name_label = QLabel("NS/Name:", self.content) self.ns_field = QComboBox(self.content) self.ns_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.ns_field.setEditable(True) ns_history = nm.history().cachedParamValues('run_dialog/NS') ns_history.insert(0, '/') self.ns_field.addItems(ns_history) self.name_field = QLineEdit(self.content) self.name_field.setEnabled(False) horizontalLayout = QHBoxLayout() horizontalLayout.addWidget(self.ns_field) horizontalLayout.addWidget(self.name_field) self.contentLayout.addRow(ns_name_label, horizontalLayout) args_label = QLabel("Args:", self.content) self.args_field = QComboBox(self.content) self.args_field.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength) self.args_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.args_field.setEditable(True) self.contentLayout.addRow(args_label, self.args_field) args_history = nm.history().cachedParamValues('run_dialog/Args') args_history.insert(0, '') self.args_field.addItems(args_history) host_label = QLabel("Host:", self.content) self.host_field = QComboBox(self.content) # self.host_field.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.host_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.host_field.setEditable(True) host_label.setBuddy(self.host_field) self.contentLayout.addRow(host_label, self.host_field) self.host_history = host_history = nm.history().cachedParamValues('/Host') if self.host in host_history: host_history.remove(self.host) host_history.insert(0, self.host) self.host_field.addItems(host_history) master_label = QLabel("ROS Master URI:", self.content) self.master_field = QComboBox(self.content) self.master_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.master_field.setEditable(True) master_label.setBuddy(self.host_field) self.contentLayout.addRow(master_label, self.master_field) self.master_history = master_history = nm.history().cachedParamValues('/Optional Parameter/ROS Master URI') self.masteruri = "ROS_MASTER_URI" if masteruri is None else masteruri if self.masteruri in master_history: master_history.remove(self.masteruri) master_history.insert(0, self.masteruri) self.master_field.addItems(master_history) # self.package_field.setFocus(QtCore.Qt.TabFocusReason) if hasattr(self.package_field, "textChanged"): # qt compatibility self.package_field.textChanged.connect(self.on_package_selected) else: self.package_field.editTextChanged.connect(self.on_package_selected) self.binary_field.activated[str].connect(self.on_binary_selected) def run_params(self): ''' Runs the selected node, or do nothing. :return: a tuple with host, package, binary, name, args, maseruri or empty tuple on errors ''' self.binary = self.binary_field.currentText() self.host = self.host_field.currentText() if self.host_field.currentText() else self.host self.masteruri = self.master_field.currentText() if self.master_field.currentText() else self.masteruri if self.host not in self.host_history and self.host != 'localhost' and self.host != '127.0.0.1': nm.history().add2HostHistory(self.host) ns = self.ns_field.currentText() if ns and ns != '/': nm.history().addParamCache('run_dialog/NS', ns) args = self.args_field.currentText() if args: nm.history().addParamCache('run_dialog/Args', args) if self.package and self.binary: nm.history().addParamCache('/Host', self.host) return (self.host, self.package, self.binary, self.name_field.text(), ('__ns:=%s %s' % (ns, args)).split(' '), None if self.masteruri == 'ROS_MASTER_URI' else self.masteruri) return () def on_package_selected(self, package): PackageDialog.on_package_selected(self, package) if self.packages and package in self.packages: self.args_field.setEnabled(True) self.ns_field.setEnabled(True) self.name_field.setEnabled(True) root, _ext = os.path.splitext(os.path.basename(self.binary_field.currentText())) self.name_field.setText(root) def on_binary_selected(self, binary): root, _ext = os.path.splitext(os.path.basename(binary)) self.name_field.setText(root)
class LocationFunction(object): EDIT_LOCATION_PROPERITIES = 'Edit Location Properties' ADD_LOCATION_AREA = 'Add Location' EDIT_EXISTING_AREA = 'Edit Location' def __init__(self, location_file, map, widget, subfunction_layout, configuration_layout, image): self.edit_area_button = None self.edit_area_selection_color = Qt.black # Dictionary of polygons self.locations = {} # Dictionary that maps location names to their colors self.location_colors = {} self.draw_location = {} self.unique_loc_counter = 1 self.editing_area = False self.edit_existing_location = None self.editing_properties = False self.edit_properties_location = None # Use this to initialize variables. self.clearAreaSelection() self.is_modified = False self.widget = widget self.subfunction_layout = subfunction_layout self.image = image self.image_size = image.overlay_image.size() self.configuration_layout = configuration_layout self.location_file = location_file self.map_size = QSize(map.map.info.width, map.map.info.height) self.readLocationsFromFile() self.edit_area_button = {} def readLocationsFromFile(self): if os.path.isfile(self.location_file): stream = open(self.location_file, 'r') try: contents = yaml.load(stream) if "polygons" not in contents or "locations" not in contents: rospy.logerr("YAML file found at " + self.location_file + ", but does not seem to have been written by this tool. I'm starting locations from scratch.") else: location_keys = contents["locations"] location_polygons = contents["polygons"] for index, location in enumerate(location_keys): self.locations[location] = QPolygon() self.locations[location].setPoints(location_polygons[index]) self.locations[location] = scalePolygon(self.locations[location], self.map_size, self.image_size) (_,self.location_colors[location]) = self.getUniqueNameAndColor() self.draw_location[location] = True except yaml.YAMLError: rospy.logerr("File found at " + self.location_file + ", but cannot be parsed by YAML parser. I'm starting locations from scratch.") stream.close() else: rospy.logwarn("Location file not found at " + self.location_file + ". I'm starting locations from scratch and will attempt to write to this location before exiting.") def saveConfiguration(self): self.writeLocationsToFile() def writeLocationsToFile(self): out_dict = {} out_dict["locations"] = self.locations.keys() out_dict["polygons"] = [] for index, location in enumerate(self.locations): out_dict["polygons"].append([]) for i in range(self.locations[location].size()): pt = self.locations[location].point(i) scaled_pt = scalePoint(pt, self.image_size, self.map_size) out_dict["polygons"][index].append(scaled_pt.x()) out_dict["polygons"][index].append(scaled_pt.y()) data_directory = os.path.dirname(os.path.realpath(self.location_file)) image_file = getLocationsImageFileLocationFromDataDirectory(data_directory) # Create an image with the location data, so that C++ programs don't need to rely on determining regions using polygons. out_dict["data"] = 'locations.pgm' location_image = QImage(self.map_size, QImage.Format_RGB32) location_image.fill(Qt.white) painter = QPainter(location_image) for index, location in enumerate(self.locations): if index > 254: rospy.logerr("You have more than 254 locations, which is unsupported by the bwi_planning_common C++ code!") painter.setPen(Qt.NoPen) painter.setBrush(QColor(index, index, index)) scaled_polygon = scalePolygon(self.locations[location], self.image_size, self.map_size) painter.drawPolygon(scaled_polygon) painter.end() location_image.save(image_file) stream = open(self.location_file, 'w') yaml.dump(out_dict, stream) stream.close() self.is_modified = False def deactivateFunction(self): if self.editing_area: self.endAreaEdit("Cancel") elif self.editing_properties: self.endPropertyEdit() clearLayoutAndFixHeight(self.subfunction_layout) self.edit_area_button.clear() self.image.enableDefaultMouseHooks() # Just in case we were editing a location, that location was not being drawn. for location in self.draw_location: self.draw_location[location] = True def activateFunction(self): # Add all the necessary buttons to the subfunction layout. clearLayoutAndFixHeight(self.subfunction_layout) for button_text in [LocationFunction.ADD_LOCATION_AREA, LocationFunction.EDIT_EXISTING_AREA]: button = QPushButton(button_text, self.widget) button.clicked[bool].connect(partial(self.startAreaEdit, button_text)) button.setCheckable(True) self.subfunction_layout.addWidget(button) self.edit_area_button[button_text] = button self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setEnabled(False) self.subfunction_layout.addStretch(1) # ActivateMouseHooks. self.image.mousePressEvent = self.mousePressEvent self.image.mouseMoveEvent = self.mouseMoveEvent self.image.mouseReleaseEvent = self.mouseReleaseEvent self.updateOverlay() def getLocationNameFromPoint(self, point): for location in self.locations: if self.locations[location].containsPoint(point, Qt.OddEvenFill): return location return None def startAreaEdit(self, edit_type): if self.editing_properties: self.endPropertyEdit() self.editing_area = True if edit_type == LocationFunction.ADD_LOCATION_AREA: self.edit_existing_location = None # else edit_existing_location was set to the correct location by startPropertyEdit() # Make sure all active selections have been cleared. self.clearAreaSelection() # If we're going to edit an existing area, stop drawing it and copy it to the active selection. if self.edit_existing_location is not None: self.draw_location[self.edit_existing_location] = False self.current_selection = QPolygon(self.locations[self.edit_existing_location]) self.edit_existing_location = self.edit_existing_location # Setup the buttons in the configuration toolbar, and disable the original buttons to edit an area. clearLayoutAndFixHeight(self.configuration_layout) for button_text in ["Done", "Cancel"]: button = QPushButton(button_text, self.widget) button.clicked[bool].connect(partial(self.endAreaEdit, button_text)) self.configuration_layout.addWidget(button) self.configuration_layout.addStretch(1) self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setEnabled(False) self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setEnabled(False) self.updateOverlay() def clearAreaSelection(self): # Make sure all selections are clear. self.new_selection_start_point = None self.new_selection_end_point = None # QPolygons to track current location. self.current_selection = None self.new_selection = None self.subtract_new_selection = None def endAreaEdit(self, button_text): edit_properties_location = None if (button_text == "Done") and (self.current_selection is not None) and (not self.current_selection.isEmpty()): # If the current location being added completely wipes out an old location, make sure you remove it. for location in self.locations.keys(): if location != self.edit_existing_location: self.locations[location] = self.locations[location].subtracted(self.current_selection) if self.locations[location].isEmpty(): self.removeLocation(location) if self.edit_existing_location == None: # We're adding a new location. Generate a new location name and color. (self.edit_existing_location, new_location_color) = self.getUniqueNameAndColor() self.location_colors[self.edit_existing_location] = new_location_color self.locations[self.edit_existing_location] = self.current_selection self.draw_location[self.edit_existing_location] = True edit_properties_location = self.edit_existing_location # Since a location was added or edited, set file modification to true. self.is_modified = True else: # Cancel was pressed, draw the original location if we were editing as before. if self.edit_existing_location is not None: self.draw_location[self.edit_existing_location] = True self.editing_area = False self.edit_existing_location = None self.clearAreaSelection() # Update the entire image overlay. self.updateOverlay() self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setEnabled(True) self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setChecked(False) self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setChecked(False) clearLayoutAndFixHeight(self.configuration_layout) if edit_properties_location is not None: self.edit_properties_location = edit_properties_location self.startPropertyEdit() def startPropertyEdit(self): self.editing_properties = True self.edit_existing_location = self.edit_properties_location self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setEnabled(True) self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setEnabled(True) # Construct the configuration layout. clearLayoutAndFixHeight(self.configuration_layout) self.update_name_label = QLabel("Location (" + self.edit_properties_location + ") New Name: ", self.widget) self.configuration_layout.addWidget(self.update_name_label) self.update_name_textedit = QLineEdit(self.widget) self.update_name_textedit.setText(self.edit_properties_location) self.update_name_textedit.textEdited.connect(self.locationNameTextEdited) self.configuration_layout.addWidget(self.update_name_textedit) self.update_name_button = QPushButton("Update location Name", self.widget) self.update_name_button.clicked[bool].connect(self.updateLocationName) self.update_name_button.setEnabled(False) self.configuration_layout.addWidget(self.update_name_button) self.remove_location_button = QPushButton("Remove Location", self.widget) self.remove_location_button.clicked[bool].connect(self.removeCurrentLocation) self.configuration_layout.addWidget(self.remove_location_button) self.configuration_layout.addStretch(1) self.updateOverlay() def endPropertyEdit(self): self.edit_area_button[LocationFunction.ADD_LOCATION_AREA].setEnabled(True) self.edit_area_button[LocationFunction.EDIT_EXISTING_AREA].setEnabled(False) clearLayoutAndFixHeight(self.configuration_layout) self.update_name_label = None self.update_name_textedit = None self.update_name_button = None self.editing_properties = False self.edit_properties_location = None self.updateOverlay() def locationNameTextEdited(self, text): if str(text) != self.edit_properties_location: self.update_name_button.setEnabled(True) else: self.update_name_button.setEnabled(False) def updateLocationName(self): old_loc_name = self.edit_properties_location new_loc_name = str(self.update_name_textedit.text()) if new_loc_name in self.locations: # This means that two locations need to be merged self.locations[new_loc_name] = self.locations[new_loc_name].united(self.locations.pop(old_loc_name)) else: # This is a simple rename task. self.locations[new_loc_name] = self.locations.pop(old_loc_name) self.location_colors[new_loc_name] = self.location_colors.pop(old_loc_name) self.draw_location[new_loc_name] = self.draw_location.pop(old_loc_name) # Since a location name was modified, set file modification to true. self.is_modified = True # Restart property edit with the updated name. self.endPropertyEdit() self.edit_properties_location = new_loc_name self.startPropertyEdit() def removeCurrentLocation(self): old_loc_name = self.edit_properties_location self.removeLocation(old_loc_name) self.endPropertyEdit() self.updateOverlay() # Since a location was removed, set file modification to true. self.is_modified = True def removeLocation(self, loc_name): if loc_name in self.locations: self.locations.pop(loc_name) if loc_name in self.location_colors: self.location_colors.pop(loc_name) if loc_name in self.draw_location: self.draw_location.pop(loc_name) def isModified(self): return self.is_modified def mousePressEvent(self, event): if self.editing_area: self.subtract_new_selection = event.button() == Qt.RightButton self.new_selection_start_point = event.pos() self.new_selection_end_point = event.pos() self.new_selection = None else: loc = self.getLocationNameFromPoint(event.pos()) if loc is not None: self.edit_properties_location = loc self.startPropertyEdit() else: self.endPropertyEdit() def mouseReleaseEvent(self, event): if self.editing_area: self.mouseMoveEvent(event) if self.new_selection is not None: if self.current_selection is None and self.subtract_new_selection == False: self.current_selection = self.new_selection if self.subtract_new_selection: self.current_selection = self.current_selection.subtracted(self.new_selection) else: self.current_selection = self.current_selection.united(self.new_selection) self.new_selection = None self.subtract_new_selection = None def mouseMoveEvent(self, event): if self.editing_area: # First make sure we update the region corresponding to the old mark. old_overlay_update_rect = self.get_rectangular_polygon(self.new_selection_start_point, self.new_selection_end_point) # Draw new mark, taking some care to reduce the size of the polygon's bottom right corner by (1,1). self.new_selection_end_point = event.pos() self.new_selection = self.get_rectangular_polygon(self.new_selection_start_point, self.new_selection_end_point) self.new_selection = self.new_selection.boundingRect() self.new_selection.setHeight(self.new_selection.height() - 1) self.new_selection.setWidth(self.new_selection.width() - 1) self.new_selection = QPolygon(self.new_selection, True) # Next determine the region that needs to be update because of the new mark. new_overlay_update_rect = self.get_rectangular_polygon(self.new_selection_start_point, self.new_selection_end_point) overlay_update_region = (old_overlay_update_rect + new_overlay_update_rect).boundingRect() self.updateOverlay(overlay_update_region) def updateOverlay(self, rect = None): # Redraw the overlay image from scratch using the location image and current location. self.image.overlay_image.fill(Qt.transparent) painter = QPainter(self.image.overlay_image) painter.setBackgroundMode(Qt.TransparentMode) painter.setCompositionMode(QPainter.CompositionMode_Source) for location in self.locations: if self.draw_location[location]: color = self.location_colors[location] if self.edit_properties_location == location and self.editing_properties: color = self.edit_area_selection_color lineColor = QColor(color) lineColor.setAlpha(255) brushColor = QColor(color) brushColor.setAlpha(128) painter.setPen(lineColor) painter.setBrush(brushColor) painter.drawPolygon(self.locations[location]) if (self.current_selection is not None) or (self.new_selection is not None): lineColor = QColor(self.edit_area_selection_color) lineColor.setAlpha(255) brushColor = QColor(self.edit_area_selection_color) brushColor.setAlpha(128) painter.setPen(lineColor) painter.setBrush(brushColor) if self.new_selection is not None: # Create a temporary polygon as the new selection is being drawn. if self.current_selection is not None: current_selection = QPolygon(self.current_selection) if self.subtract_new_selection: current_selection = current_selection.subtracted(self.new_selection) else: current_selection = current_selection.united(self.new_selection) painter.drawPolygon(current_selection) elif self.subtract_new_selection == False: painter.drawPolygon(self.new_selection) else: painter.drawPolygon(self.current_selection) painter.end() if rect is None: self.image.update() else: self.image.update(rect) def getUniqueNameAndColor(self): """ Use golden ratio to generate unique colors. http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/ """ name = "new_loc" + str(self.unique_loc_counter) h = int(359 * (self.unique_loc_counter * 0.618033988749895)) h = h % 359 self.unique_loc_counter += 1 return name, QColor.fromHsv(h, 255, 255) def get_rectangular_polygon(self, pt1, pt2): return QPolygon([pt1, QPoint(pt1.x(), pt2.y()), pt2, QPoint(pt2.x(), pt1.y())])