class FloatingToolBar(QToolBar): """ A floating QToolBar with no border and is offset under its parent """ def __init__(self, name, parent): """ parent: The parent of this toolbar. Should be another toolbar """ QToolBar.__init__(self, name, parent) self.setMovable(False) self.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint | Qt.X11BypassWindowManagerHint) self.setAllowedAreas(Qt.NoToolBarArea) self.actiongroup = QActionGroup(self) def addToActionGroup(self, action): self.actiongroup.addAction(action) def showToolbar(self, parentaction, defaultaction, toggled): if toggled: self.show() if defaultaction: defaultaction.toggle() widget = self.parent().widgetForAction(parentaction) x = self.parent().mapToGlobal(widget.pos()).x() y = self.parent().mapToGlobal(widget.pos()).y() newpoint = QPoint(x, y + self.parent().rect().height()) # if self.orientation() == Qt.Vertical: # newpoint = QPoint(x, y + self.parent().rect().width()) self.move(newpoint) else: action = self.actiongroup.checkedAction() if action: action.toggle() self.hide()
def device_discovery(self, devs): group = QActionGroup(self._menu_devices, exclusive=True) for d in devs: node = QAction(d["name"], self._menu_devices, checkable=True) node.toggled.connect(self._inputdevice_selected) group.addAction(node) self._menu_devices.addAction(node) if (d["name"] == GuiConfig().get("input_device")): self._active_device = d["name"] if (len(self._active_device) == 0): self._active_device = self._menu_devices.actions()[0].text() device_config_mapping = GuiConfig().get("device_config_mapping") if (device_config_mapping): if (self._active_device in device_config_mapping.keys()): self._current_input_config = device_config_mapping[ str(self._active_device)] else: self._current_input_config = self._menu_mappings.actions()[0].text() else: self._current_input_config = self._menu_mappings.actions()[0].text() # Now we know what device to use and what mapping, trigger the events # to change the menus and start the input for c in self._menu_mappings.actions(): c.setEnabled(True) if (c.text() == self._current_input_config): c.setChecked(True) for c in self._menu_devices.actions(): if (c.text() == self._active_device): c.setChecked(True)
def __init__(self, mwHandle): self.mw = mwHandle for x in xrange(2, 20): pin = eval("self.mw.pin%02d" % (x)) menu = QMenu(pin) modeGroup = QActionGroup(self.mw) modeGroup.setExclusive(True) none = menu.addAction("&None") modeGroup.addAction(none) none.triggered.connect(self.clickNone) none.setCheckable(True) none.setChecked(True) input = menu.addAction("&Input") modeGroup.addAction(input) input.triggered.connect(self.clickInput) input.setCheckable(True) output = menu.addAction("&Output") modeGroup.addAction(output) output.triggered.connect(self.clickOutput) output.setCheckable(True) if self.mw.board.pins[x].PWM_CAPABLE: pwm = menu.addAction("&PWM") modeGroup.addAction(pwm) pwm.triggered.connect(self.clickPWM) pwm.setCheckable(True) if self.mw.board.pins[x].type == 2: analogic = menu.addAction(u"&Analógico") modeGroup.addAction(analogic) analogic.triggered.connect(self.clickAnalog) analogic.setCheckable(True) pin.setMenu(menu) pin.setStyleSheet("/* */") # force stylesheet update
def _set_up_tools_connections(self): """ Set up all connections for view menu. """ self.connect(self.actionUnselect_All, SIGNAL("triggered()"), self.canvas.clear_all_selected_cells) self.connect(self.actionCreate_Pattern_Repeat, SIGNAL("triggered()"), self.canvas.add_pattern_repeat) self.connect(self.actionCreate_Row_Repeat, SIGNAL("triggered()"), self.canvas.add_row_repeat) self.connect(self.actionApply_Color_to_Selection, SIGNAL("triggered()"), self.canvas.apply_color_to_selection) self.connect(self.actionAdd_Text, SIGNAL("triggered()"), self.canvas.add_text_item) modeGroup = QActionGroup(self) modeGroup.addAction(self.actionHide_Selected_Cells) modeGroup.addAction(self.actionShow_Selected_Cells) modeGroup.addAction(self.actionCreate_Chart) self.connect(self.actionHide_Selected_Cells, SIGNAL("triggered()"), partial(self.canvas.select_mode, canvas.HIDE_MODE)) self.connect(self.actionShow_Selected_Cells, SIGNAL("triggered()"), partial(self.canvas.select_mode, canvas.UNHIDE_MODE)) self.connect(self.actionCreate_Chart, SIGNAL("triggered()"), partial(self.canvas.select_mode, canvas.SELECTION_MODE))
def device_discovery(self, devs): group = QActionGroup(self._menu_devices, exclusive=True) for d in devs: node = QAction(d["name"], self._menu_devices, checkable=True) node.toggled.connect(self._inputdevice_selected) group.addAction(node) self._menu_devices.addAction(node) if (d["name"] == GuiConfig().get("input_device")): self._active_device = d["name"] if (len(self._active_device) == 0): self._active_device = self._menu_devices.actions()[0].text() device_config_mapping = GuiConfig().get("device_config_mapping") if (device_config_mapping): if (self._active_device in device_config_mapping.keys()): self._current_input_config = device_config_mapping[str( self._active_device)] else: self._current_input_config = self._menu_mappings.actions( )[0].text() else: self._current_input_config = self._menu_mappings.actions()[0].text( ) # Now we know what device to use and what mapping, trigger the events # to change the menus and start the input for c in self._menu_mappings.actions(): c.setEnabled(True) if (c.text() == self._current_input_config): c.setChecked(True) for c in self._menu_devices.actions(): if (c.text() == self._active_device): c.setChecked(True)
def setup_menu(): u""" Add a submenu to a view menu. Add a submenu that lists the available extra classes to the view menu, creating that menu when neccessary """ if extra_classes_list: try: mw.addon_view_menu except AttributeError: mw.addon_view_menu = QMenu(_(u"&View"), mw) mw.form.menubar.insertMenu( mw.form.menuTools.menuAction(), mw.addon_view_menu) mw.extra_class_submenu = QMenu(u"Mode (e&xtra class)", mw) mw.addon_view_menu.addMenu(mw.extra_class_submenu) action_group = QActionGroup(mw, exclusive=True) no_class_action = action_group.addAction( QAction('(none/standard)', mw, checkable=True)) no_class_action.setChecked(True) mw.extra_class_submenu.addAction(no_class_action) mw.connect(no_class_action, SIGNAL("triggered()"), lambda: set_extra_class(None)) for ecd in extra_classes_list: nn_class_action = action_group.addAction( QAction(ecd['display'], mw, checkable=True)) mw.extra_class_submenu.addAction(nn_class_action) mw.connect(nn_class_action, SIGNAL("triggered()"), lambda ec=ecd['class']: set_extra_class(ec))
def init_actions(self): """ here we do extra setup for the window (mainly connect signals and slots) """ # group action2D and action3D to make them mutually exclusive view_group = QActionGroup(self) view_group.addAction(self.action2D) view_group.addAction(self.action3D) # set 2D view by default self.action2D.setChecked(True) # connect view (re)set actions to functions self.action2D.toggled.connect(self.canvas.reset_view_2d) self.action3D.toggled.connect(self.canvas.reset_view_3d) self.actionReset.triggered.connect(self.canvas.reset_view) self.tool2d_explore_radio.toggled.connect(self.canvas.set_mode_explore) for radio2d in self.tab_2d.findChildren(QtGui.QRadioButton, QRegExp("tool2d.*")): self.tool2d_button_group.addButton(radio2d) radio2d.toggled.connect(self.tool2d_radio_toggled) # TODO: do the same for 3dtool buttons self.tool2d_operation_button.clicked.connect(self.perform_operation) self.tool3d_insert_shape_button.clicked.connect( self.open_insert_3d_shape_dialog)
def create_menu(self, menu_name, menu_actions): """ Creates a menu. Groups them so you can only select one at a time. """ menu_action_group = QActionGroup(self) menu_action_group.setExclusive(True) menubar = self.menuBar() menu = menubar.addMenu(menu_name) for action in menu_actions: menu_action_group.addAction(action) menu.addAction(action)
class TrayContextMenu(QtGui.QMenu): instances = set() def __init__(self, trayIcon): """ trayIcon = the object with the methods to call """ QtGui.QMenu.__init__(self) TrayContextMenu.instances.add(self) self.trayIcon = trayIcon self._buildMenu() def _buildMenu(self): self.framelessCheck = QtGui.QAction("Frameless Window", self, checkable=True) self.connect(self.framelessCheck, SIGNAL("triggered()"), self.trayIcon.changeFrameless) self.addAction(self.framelessCheck) self.addSeparator() self.requestCheck = QtGui.QAction("Show status request notifications", self, checkable=True) self.requestCheck.setChecked(True) self.addAction(self.requestCheck) self.connect(self.requestCheck, SIGNAL("triggered()"), self.trayIcon.switchRequest) self.alarmCheck = QtGui.QAction("Show alarm notifications", self, checkable=True) self.alarmCheck.setChecked(True) self.connect(self.alarmCheck, SIGNAL("triggered()"), self.trayIcon.switchAlarm) self.addAction(self.alarmCheck) distanceMenu = self.addMenu("Alarm Distance") self.distanceGroup = QActionGroup(self) for i in range(0, 6): action = QAction("{0} Jumps".format(i), None, checkable=True) if i == 0: action.setChecked(True) action.alarmDistance = i self.connect(action, SIGNAL("triggered()"), self.changeAlarmDistance) self.distanceGroup.addAction(action) distanceMenu.addAction(action) self.addMenu(distanceMenu) self.addSeparator() self.quitAction = QAction("Quit", self) self.connect(self.quitAction, SIGNAL("triggered()"), self.trayIcon.quit) self.addAction(self.quitAction) def changeAlarmDistance(self): for action in self.distanceGroup.actions(): if action.isChecked(): self.trayIcon.alarmDistance = action.alarmDistance self.trayIcon.changeAlarmDistance()
def create_action_group(parent, actions=[], enabled=True, exclusive=True, visible=True, hovered_slot=None, triggered_slot=None): action_group = QActionGroup(parent) for action in actions: action_group.addAction(action) action_group.setEnabled(enabled) action_group.setExclusive(exclusive) action_group.setVisible(visible) if hovered_slot: action_group.connect(action, SIGNAL('hovered(QAction)'), hovered_slot) if triggered_slot: action_group.connect(action, SIGNAL('triggered(QAction)'), triggered_slot) return action_group
def createMenuBar(self): # set menu bar model self.mActionsModel = pActionsModel( self ) self.menuBar().setModel( self.mActionsModel ) # create menus and sub menus self.mActionsModel.addMenu( "mFile", self.tr( "&File" ) ) self.mActionsModel.addMenu( "mEdit", self.tr( "&Edit" ) ) self.mActionsModel.addMenu( "mView", self.tr( "&View" ) ) self.mActionsModel.addMenu( "mView/mStyle", self.tr( "&Style" ) ) self.mActionsModel.addMenu( "mView/mMode", self.tr( "&Mode" ) ) self.mActionsModel.addMenu( "mView/mDockToolBarManager", self.tr( "&Dock ToolBar Manager" ) ) self.mActionsModel.addMenu( "mView/mDockWidgets", self.tr( "Dock &Widgets" ) ) # create actions aQuit = self.mActionsModel.addAction( "mFile/aQuit", self.tr( "&Quit" ) ) aClassic = self.mActionsModel.addAction( "mView/mMode/aShowClassic", self.tr( "Classic" ) ) aClassic.setCheckable( True ) aModern = self.mActionsModel.addAction( "mView/mMode/aShowModern", self.tr( "Modern" ) ) aModern.setCheckable( True ) # style actions self.agStyles = pStylesActionGroup( self ) self.agStyles.installInMenuBar( self.menuBar(), "mView/mStyle" ) # action group agDockToolBarManagerMode = QActionGroup( self ) agDockToolBarManagerMode.addAction( aClassic ) agDockToolBarManagerMode.addAction( aModern ) # add dock toolbar manager actions for dockToolBar in self.dockToolBarManager().dockToolBars(): action = dockToolBar.toggleViewAction() self.mActionsModel.addAction( "mView/mDockToolBarManager/%s" % action.objectName() , action ) action = dockToolBar.toggleExclusiveAction() self.mActionsModel.addAction( "mView/mDockToolBarManager/%s" % action.objectName(), action ) # connections aQuit.triggered.connect(self.close) self.agStyles.styleSelected.connect(self.setCurrentStyle) self.dockToolBarManager().modeChanged.connect( self.dockToolBarManagerModeChanged) aClassic.triggered.connect(self.dockToolBarManagerClassic) aModern.triggered.connect(self.dockToolBarManagerModern)
def _onTvFilesCustomContextMenuRequested(self, pos ): """Connected automatically by uic """ menu = QMenu() menu.addAction( core.actionManager().action( "mFile/mClose/aCurrent" ) ) menu.addAction( core.actionManager().action( "mFile/mSave/aCurrent" ) ) menu.addAction( core.actionManager().action( "mFile/mReload/aCurrent" ) ) menu.addSeparator() # sort menu sortMenu = QMenu( self ) group = QActionGroup( sortMenu ) group.addAction( self.tr( "Opening order" ) ) group.addAction( self.tr( "File name" ) ) group.addAction( self.tr( "URL" ) ) group.addAction( self.tr( "Suffixes" ) ) group.triggered.connect(self._onSortTriggered) sortMenu.addActions( group.actions() ) for i, sortMode in enumerate(["OpeningOrder", "FileName", "URL", "Suffixes"]): action = group.actions()[i] action.setData( sortMode ) action.setCheckable( True ) if sortMode == self.model.sortMode(): action.setChecked( True ) aSortMenu = QAction( self.tr( "Sorting" ), self ) aSortMenu.setMenu( sortMenu ) aSortMenu.setIcon( QIcon( ":/enkiicons/sort.png" )) aSortMenu.setToolTip( aSortMenu.text() ) menu.addAction( sortMenu.menuAction() ) menu.exec_( self.tvFiles.mapToGlobal( pos ) )
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self._create_actions() self._create_toolbar() self._canvas = Canvas() self._canvas.scale(16, 16) self.setCentralWidget(self._canvas) def _create_actions(self): self._delete_action = QAction("Delete", None) self._delete_action.setShortcuts(QKeySequence.Delete) self._delete_action.triggered.connect(self._delete) self._select_action = QAction("Select", None) self._select_action.setCheckable(True) self._select_action.triggered.connect(self._use_select_tool) self._pen_action = QAction("Pen", None) self._pen_action.setCheckable(True) self._pen_action.setChecked(True) self._pen_action.triggered.connect(self._use_pen_tool) self._new_shape_action = QAction("New Shape", None) self._new_shape_action.triggered.connect(self._new_shape) self._tool_group = QActionGroup(None) self._tool_group.addAction(self._select_action) self._tool_group.addAction(self._pen_action) def _create_toolbar(self): toolbar = self.addToolBar("Tools") toolbar.addAction(self._delete_action) toolbar.addAction(self._select_action) toolbar.addAction(self._pen_action) toolbar.addAction(self._new_shape_action) def _use_select_tool(self): self._canvas.use_tool(SelectTool) def _use_pen_tool(self): self._canvas.use_tool(PenTool) def _new_shape(self): self._canvas.new_shape() def _delete(self): self._canvas.delete_selection()
def change_layer_image_kind_menu(self, menu, lbox, selected_uuids, *args): current_kind = self.doc.prez_for_uuid(selected_uuids[0]).kind kind_menu = QMenu("Change Image Kind", menu) action_group = QActionGroup(menu, exclusive=True) actions = {} action_kinds = {} def _change_layers_image_kind(action, action_kinds=action_kinds): if not action.isChecked(): # can't uncheck an image kind LOG.debug("Selected KIND action is not checked") return kind = action_kinds[action] return self.doc.change_layers_image_kind(selected_uuids, kind) for kind in [KIND.IMAGE, KIND.CONTOUR]: action = action_group.addAction( QAction(kind.name, menu, checkable=True)) action_kinds[action] = kind action.setChecked(kind == current_kind) actions[action] = _change_layers_image_kind kind_menu.addAction(action) menu.addMenu(kind_menu) return actions
def createActionGroup(parent): group = QActionGroup(parent) for enc, region in _ENCODINGNAMES: a = group.addAction('%s (%s)' % (region, enc)) a.setCheckable(True) a.setData(enc) return group
def setupToolbar(self): """ Setup the toolbar (adds a drop-down button for program types) """ # create program type group ag = QActionGroup(self) ag.addAction(self.actionProgram) ag.addAction(self.actionSubprogram) ag.triggered.connect(self.on_programType_triggered) self.programActionGroup = ag self.tb = QToolButton() self.tb.setToolTip("Select the compiler program type to make") self.tb.setMenu(self.menuProgramType) self.tb.setPopupMode(QToolButton.InstantPopup) self.tb.setText("Executable") self.toolBarCode.insertWidget(self.actionCompile, self.tb) self.toolBarCode.insertSeparator(self.actionCompile)
def loadPlugins(self): sys.path.append(os.curdir + os.sep + "plugins") plugins = QActionGroup(self) for s in os.listdir("plugins"): # TODO: check all errors if s.endswith(".py"): plugin = __import__(s[:-3]) if plugin == "__init__": continue if "pluginMain" in dir(plugin): pluginClass = plugin.pluginMain() name = pluginClass.GetName() action = QAction(name, self) action.setCheckable(False) QObject.connect(action, SIGNAL("triggered()"), self.GenerateRequests) plugins.addAction(action) self.ui.menuGenerators.addAction(action) self.generators[action] = pluginClass() else: print("pluginMain not found in " + s)
def setActionsExclusive(self): # Build an action list from QGIS navigation toolbar actionList = self.iface.mapNavToolToolBar().actions() # Add actions from QGIS attributes toolbar (handling QWidgetActions) tmpActionList = self.iface.attributesToolBar().actions() for action in tmpActionList: if isinstance(action, QWidgetAction): actionList.extend( action.defaultWidget().actions() ) else: actionList.append( action ) # ... add other toolbars' action lists... # Build a group with actions from actionList and add your own action group = QActionGroup( self.iface.mainWindow() ) group.setExclusive(True) for action in actionList: group.addAction( action ) group.addAction( self.identifyParcelleAction )
class FilterSetWidget(QWidget, Ui_FilterSetWidget): def __init__(self, parent=None): super(FilterSetWidget, self).__init__(parent) self.setupUi(self) self._filterSetActionGroup = QActionGroup(self) self._filterSetActionGroup.addAction(self.saveFilterSetAction) self._filterSetActionGroup.addAction(self.reloadFilterSetAction) self._filterSetActionGroup.addAction(self.deleteFilterSetAction) self._filterSetActionGroup.addAction(self.exportFilterSetAction) self._filterSetMenu = QMenu(self) self._filterSetMenu.addActions(self._filterSetActionGroup.actions()) self.filterSetTool.setMenu(self._filterSetMenu) self.filterSetTool.setDefaultAction(self.saveFilterSetAction) def setFilterSet(self, filterSet): self.setFilterSetKey(filterSet.key) if filterSet.source == 'ark': self.saveFilterSetAction.setEnabled(False) self.deleteFilterSetAction.setEnabled(False) self.filterSetTool.setDefaultAction(self.reloadFilterSetAction) else: self.saveFilterSetAction.setEnabled(True) self.deleteFilterSetAction.setEnabled(True) self.filterSetTool.setDefaultAction(self.saveFilterSetAction) def setFilterSetKey(self, key): self.filterSetCombo.setCurrentIndex(self.filterSetCombo.findData(key)) def currentFilterSetKey(self): return self.filterSetCombo.itemData(self.filterSetCombo.currentIndex()) def currentFilterSetName(self): return self.filterSetCombo.currentText()
def __init__(self, from_table, to_table, condition): QGraphicsLineItem.__init__(self) self.instances.append(self) # from/to table connections self.from_table = from_table self.to_table = to_table self.condition = condition from_table.table_move.connect(self.update_spring) to_table.table_move.connect(self.update_spring) # draw arrow self.arrow = ArrowPolygonItem(self) self.arrow.clicked.connect(self.change_settings) # set attributes pen = QPen() pen.setWidth(2) self.setPen(pen) self.update_spring() self.setZValue(-1) # setup configuration menu menu = QMenu() join_action = QAction('Join', menu) join_action.setCheckable(True) join_action.setChecked(True) outer_join_action = QAction('Outer Join', menu) outer_join_action.setCheckable(True) group = QActionGroup(menu) group.addAction(join_action) group.addAction(outer_join_action) menu.addAction(join_action) menu.addAction(outer_join_action) self.join_action = join_action self.outer_join_action = outer_join_action self.menu = menu
def loadPlugins(self): sys.path.append(os.curdir + os.sep + "plugins") plugins = QActionGroup(self) plugins.setExclusive(True) simple = SimpleInterpreter() action = QAction(simple.GetName(), self) plugins.addAction(action) action.setCheckable(True) action.setChecked(True) self.interpreterPlugins[action] = simple self.ui.menuPlugins.addAction(action) QtCore.QObject.connect(action, SIGNAL("triggered()"), self.ChangeInterpreter) for s in os.listdir("plugins"): # TODO: check all errors if s.endswith(".py"): plugin = __import__(s[:-3]) if "pluginMain" in dir(plugin): pluginClass = plugin.pluginMain() name = pluginClass.GetName() type = pluginClass.GetType() if type != "interpreter": continue action = QAction(name, self) action.setCheckable(True) QtCore.QObject.connect(action, SIGNAL("triggered()"), self.ChangeInterpreter) plugins.addAction(action) self.ui.menuPlugins.addAction(action) self.interpreterPlugins[action] = pluginClass() # TODO: load from .ini file if name == "!!FibreChannel": action.setChecked(True) self.project.method.interpreter = self.interpreterPlugins[ action] else: print("pluginMain not found in " + s)
def _set_up_tools_connections(self): """ Set up all connections for view menu. """ self.connect(self.actionUnselect_All, SIGNAL("triggered()"), self.canvas.clear_all_selected_cells) self.connect(self.actionCreate_Pattern_Repeat, SIGNAL("triggered()"), self.canvas.add_pattern_repeat) self.connect(self.actionCreate_Row_Repeat, SIGNAL("triggered()"), self.canvas.add_row_repeat) self.connect(self.actionApply_Color_to_Selection, SIGNAL("triggered()"), self.canvas.apply_color_to_selection) self.connect(self.actionAdd_Text, SIGNAL("triggered()"), self.canvas.add_text_item) modeGroup = QActionGroup(self) modeGroup.addAction(self.actionHide_Selected_Cells) modeGroup.addAction(self.actionShow_Selected_Cells) modeGroup.addAction(self.actionCreate_Chart) self.connect(self.actionHide_Selected_Cells, SIGNAL("triggered()"), partial(self.canvas.select_mode, canvas.HIDE_MODE)) self.connect(self.actionShow_Selected_Cells, SIGNAL("triggered()"), partial(self.canvas.select_mode, canvas.UNHIDE_MODE)) self.connect(self.actionCreate_Chart, SIGNAL("triggered()"), partial(self.canvas.select_mode, canvas.SELECTION_MODE)) self.connect(self.actionShow_hidden_legend_items, SIGNAL("triggered()"), self.canvas.show_hidden_legend_items)
def setup_menu(): u""" Add a submenu to the edit menu. Add a submenu that lists the available extra classes to the edit menu. """ if extra_classes_list: mw.extra_class_submenu = QMenu(u"Mode (e&xtra class)", mw) mw.form.menuEdit.addSeparator() mw.form.menuEdit.addMenu(mw.extra_class_submenu) action_group = QActionGroup(mw, exclusive=True) no_class_action = action_group.addAction( QAction('(none/standard)', mw, checkable=True)) no_class_action.setChecked(True) mw.extra_class_submenu.addAction(no_class_action) mw.connect(no_class_action, SIGNAL("triggered()"), lambda: set_extra_class(None)) for ecd in extra_classes_list: nn_class_action = action_group.addAction( QAction(ecd['display'], mw, checkable=True)) mw.extra_class_submenu.addAction(nn_class_action) mw.connect(nn_class_action, SIGNAL("triggered()"), lambda ec=ecd['class']: set_extra_class(ec))
def set_menu_items(self, db, tag, names): """Some of the menus change their content dynamically in runtime. This method updates menu specified by 'tag' with the items given in 'names'. """ menu, fb = self.dynamic_menus[tag] group = QActionGroup(self, exclusive=True) menu.clear() for name in names: action = QAction(name, self, checkable=True) action.setStatusTip('Select item') action.triggered.connect(partial(fb, self, db, name)) a = group.addAction(action) menu.addAction(a) if db.selection[tag] == name: a.toggle()
def _combination_actions(self): tree = self.layerTree group = QActionGroup(tree) act = QAction("Union Combine", tree) act.setIcon(QIcon(':icons/glue_or.png')) act.setEnabled(False) act.setToolTip("Define new subset as union of selection") act.setShortcutContext(Qt.WidgetShortcut) act.triggered.connect(self._or_combine) group.addAction(act) tree.addAction(act) self._or_action = act act = QAction("Intersection Combine", tree) act.setIcon(QIcon(':icons/glue_and.png')) act.setEnabled(False) act.setToolTip("Define new subset as intersection of selection") act.triggered.connect(self._and_combine) act.setShortcutContext(Qt.WidgetShortcut) group.addAction(act) tree.addAction(act) self._and_action = act act = QAction("XOR Combine", tree) act.setIcon(QIcon(':icons/glue_xor.png')) act.setEnabled(False) act.setToolTip("Define new subset as non-intersection of selection") act.triggered.connect(self._xor_combine) act.setShortcutContext(Qt.WidgetShortcut) group.addAction(act) tree.addAction(act) self._xor_action = act act = QAction("Invert", tree) act.setIcon(QIcon(':icons/glue_not.png')) act.setEnabled(False) act.setToolTip("Invert current subset") act.triggered.connect(self._invert_subset) act.setShortcutContext(Qt.WidgetShortcut) group.addAction(act) tree.addAction(act) self._invert_action = act
def _onTvFilesCustomContextMenuRequested(self, pos): """Connected automatically by uic """ menu = QMenu() menu.addAction(core.actionManager().action("mFile/mClose/aCurrent")) menu.addAction(core.actionManager().action("mFile/mSave/aCurrent")) menu.addAction(core.actionManager().action("mFile/mReload/aCurrent")) menu.addSeparator() # sort menu sortMenu = QMenu(self) group = QActionGroup(sortMenu) group.addAction(self.tr("Opening order")) group.addAction(self.tr("File name")) group.addAction(self.tr("URL")) group.addAction(self.tr("Suffixes")) group.triggered.connect(self._onSortTriggered) sortMenu.addActions(group.actions()) for i, sortMode in enumerate( ["OpeningOrder", "FileName", "URL", "Suffixes"]): action = group.actions()[i] action.setData(sortMode) action.setCheckable(True) if sortMode == self.model.sortMode(): action.setChecked(True) aSortMenu = QAction(self.tr("Sorting"), self) aSortMenu.setMenu(sortMenu) aSortMenu.setIcon(QIcon(":/enkiicons/sort.png")) aSortMenu.setToolTip(aSortMenu.text()) menu.addAction(sortMenu.menuAction()) menu.exec_(self.tvFiles.mapToGlobal(pos))
def device_discovery(self, devs): """Called when new devices have been added""" for menu in self._all_role_menus: role_menu = menu["rolemenu"] mux_menu = menu["muxmenu"] dev_group = QActionGroup(role_menu, exclusive=True) for d in devs: dev_node = QAction(d.name, role_menu, checkable=True, enabled=True) role_menu.addAction(dev_node) dev_group.addAction(dev_node) dev_node.toggled.connect(self._inputdevice_selected) map_node = None if d.supports_mapping: map_node = QMenu(" Input map", role_menu, enabled=False) map_group = QActionGroup(role_menu, exclusive=True) # Connect device node to map node for easy # enabling/disabling when selection changes and device # to easily enable it dev_node.setData((map_node, d)) for c in ConfigManager().get_list_of_configs(): node = QAction(c, map_node, checkable=True, enabled=True) node.toggled.connect(self._inputconfig_selected) map_node.addAction(node) # Connect all the map nodes back to the device # action node where we can access the raw device node.setData(dev_node) map_group.addAction(node) # If this device hasn't been found before, then # select the default mapping for it. if d not in self._available_devices: last_map = Config().get("device_config_mapping") if d.name in last_map and last_map[d.name] == c: node.setChecked(True) role_menu.addMenu(map_node) dev_node.setData((map_node, d, mux_menu)) # Update the list of what devices we found # to avoid selecting default mapping for all devices when # a new one is inserted self._available_devices = () for d in devs: self._available_devices += (d,) # Only enable MUX nodes if we have enough devies to cover # the roles for mux_node in self._all_mux_nodes: (mux, sub_nodes) = mux_node.data() if len(mux.supported_roles()) <= len(self._available_devices): mux_node.setEnabled(True) # TODO: Currently only supports selecting default mux if self._all_mux_nodes[0].isEnabled(): self._all_mux_nodes[0].setChecked(True) # If the previous length of the available devies was 0, then select # the default on. If that's not available then select the first # on in the list. # TODO: This will only work for the "Normal" mux so this will be # selected by default if Config().get("input_device") in [d.name for d in devs]: for dev_menu in self._all_role_menus[0]["rolemenu"].actions(): if dev_menu.text() == Config().get("input_device"): dev_menu.setChecked(True) else: # Select the first device in the first mux (will always be "Normal" # mux) self._all_role_menus[0]["rolemenu"].actions()[0].setChecked(True) logger.info("Select first device") self._update_input_device_footer()
class MainUI(QtGui.QMainWindow, main_window_class): connectionLostSignal = pyqtSignal(str, str) connectionInitiatedSignal = pyqtSignal(str) batteryUpdatedSignal = pyqtSignal(int, object, object) connectionDoneSignal = pyqtSignal(str) connectionFailedSignal = pyqtSignal(str, str) disconnectedSignal = pyqtSignal(str) linkQualitySignal = pyqtSignal(int) _input_device_error_signal = pyqtSignal(str) _input_discovery_signal = pyqtSignal(object) _log_error_signal = pyqtSignal(object, str) def __init__(self, *args): super(MainUI, self).__init__(*args) self.setupUi(self) ###################################################### # By lxrocks # 'Skinny Progress Bar' tweak for Yosemite # Tweak progress bar - artistic I am not - so pick your own colors !!! # Only apply to Yosemite ###################################################### import platform if platform.system() == 'Darwin': (Version, junk, machine) = platform.mac_ver() logger.info("This is a MAC - checking if we can apply Progress " "Bar Stylesheet for Yosemite Skinny Bars ") yosemite = (10, 10, 0) tVersion = tuple(map(int, (Version.split(".")))) if tVersion >= yosemite: logger.info("Found Yosemite - applying stylesheet") tcss = """ QProgressBar { border: 1px solid grey; border-radius: 5px; text-align: center; } QProgressBar::chunk { background-color: """ + COLOR_BLUE + """; } """ self.setStyleSheet(tcss) else: logger.info("Pre-Yosemite - skinny bar stylesheet not applied") ###################################################### self.cf = Crazyflie(ro_cache=None, rw_cache=cfclient.config_path + "/cache") cflib.crtp.init_drivers(enable_debug_driver=Config() .get("enable_debug_driver")) zmq_params = ZMQParamAccess(self.cf) zmq_params.start() zmq_leds = ZMQLEDDriver(self.cf) zmq_leds.start() self.scanner = ScannerThread() self.scanner.interfaceFoundSignal.connect(self.foundInterfaces) self.scanner.start() # Create and start the Input Reader self._statusbar_label = QLabel("No input-device found, insert one to" " fly.") self.statusBar().addWidget(self._statusbar_label) self.joystickReader = JoystickReader() self._active_device = "" # self.configGroup = QActionGroup(self._menu_mappings, exclusive=True) self._mux_group = QActionGroup(self._menu_inputdevice, exclusive=True) # TODO: Need to reload configs # ConfigManager().conf_needs_reload.add_callback(self._reload_configs) self.cf.connection_failed.add_callback( self.connectionFailedSignal.emit) self.connectionFailedSignal.connect(self._connection_failed) self._input_device_error_signal.connect( self._display_input_device_error) self.joystickReader.device_error.add_callback( self._input_device_error_signal.emit) self._input_discovery_signal.connect(self.device_discovery) self.joystickReader.device_discovery.add_callback( self._input_discovery_signal.emit) # Hide the 'File' menu on OS X, since its only item, 'Exit', gets # merged into the application menu. if sys.platform == 'darwin': self.menuFile.menuAction().setVisible(False) # Connect UI signals self.logConfigAction.triggered.connect(self._show_connect_dialog) self.interfaceCombo.currentIndexChanged['QString'].connect( self.interfaceChanged) self.connectButton.clicked.connect(self._connect) self.scanButton.clicked.connect(self._scan) self.menuItemConnect.triggered.connect(self._connect) self.menuItemConfInputDevice.triggered.connect( self._show_input_device_config_dialog) self.menuItemExit.triggered.connect(self.closeAppRequest) self.batteryUpdatedSignal.connect(self._update_battery) self._menuitem_rescandevices.triggered.connect(self._rescan_devices) self._menuItem_openconfigfolder.triggered.connect( self._open_config_folder) self.address.setValue(0xE7E7E7E7E7) self._auto_reconnect_enabled = Config().get("auto_reconnect") self.autoReconnectCheckBox.toggled.connect( self._auto_reconnect_changed) self.autoReconnectCheckBox.setChecked(Config().get("auto_reconnect")) self.joystickReader.input_updated.add_callback( self.cf.commander.send_setpoint) # Connection callbacks and signal wrappers for UI protection self.cf.connected.add_callback(self.connectionDoneSignal.emit) self.connectionDoneSignal.connect(self._connected) self.cf.disconnected.add_callback(self.disconnectedSignal.emit) self.disconnectedSignal.connect(self._disconnected) self.cf.connection_lost.add_callback(self.connectionLostSignal.emit) self.connectionLostSignal.connect(self._connection_lost) self.cf.connection_requested.add_callback( self.connectionInitiatedSignal.emit) self.connectionInitiatedSignal.connect(self._connection_initiated) self._log_error_signal.connect(self._logging_error) self.batteryBar.setTextVisible(False) self.batteryBar.setStyleSheet(progressbar_stylesheet(COLOR_BLUE)) self.linkQualityBar.setTextVisible(False) self.linkQualityBar.setStyleSheet(progressbar_stylesheet(COLOR_BLUE)) # Connect link quality feedback self.cf.link_quality_updated.add_callback(self.linkQualitySignal.emit) self.linkQualitySignal.connect( lambda percentage: self.linkQualityBar.setValue(percentage)) self._selected_interface = None self._initial_scan = True self._scan() # Parse the log configuration files self.logConfigReader = LogConfigReader(self.cf) self._current_input_config = None self._active_config = None self._active_config = None self.inputConfig = None # Add things to helper so tabs can access it cfclient.ui.pluginhelper.cf = self.cf cfclient.ui.pluginhelper.inputDeviceReader = self.joystickReader cfclient.ui.pluginhelper.logConfigReader = self.logConfigReader self.logConfigDialogue = LogConfigDialogue(cfclient.ui.pluginhelper) self._bootloader_dialog = BootloaderDialog(cfclient.ui.pluginhelper) self._cf2config_dialog = Cf2ConfigDialog(cfclient.ui.pluginhelper) self._cf1config_dialog = Cf1ConfigDialog(cfclient.ui.pluginhelper) self.menuItemBootloader.triggered.connect(self._bootloader_dialog.show) self._about_dialog = AboutDialog(cfclient.ui.pluginhelper) self.menuItemAbout.triggered.connect(self._about_dialog.show) self._menu_cf2_config.triggered.connect(self._cf2config_dialog.show) self._menu_cf1_config.triggered.connect(self._cf1config_dialog.show) # Loading toolboxes (A bit of magic for a lot of automatic) self.toolboxes = [] self.toolboxesMenuItem.setMenu(QtGui.QMenu()) for t_class in cfclient.ui.toolboxes.toolboxes: toolbox = t_class(cfclient.ui.pluginhelper) dockToolbox = MyDockWidget(toolbox.getName()) dockToolbox.setWidget(toolbox) self.toolboxes += [dockToolbox, ] # Add menu item for the toolbox item = QtGui.QAction(toolbox.getName(), self) item.setCheckable(True) item.triggered.connect(self.toggleToolbox) self.toolboxesMenuItem.menu().addAction(item) dockToolbox.closed.connect(lambda: self.toggleToolbox(False)) # Setup some introspection item.dockToolbox = dockToolbox item.menuItem = item dockToolbox.dockToolbox = dockToolbox dockToolbox.menuItem = item # Load and connect tabs self.tabsMenuItem.setMenu(QtGui.QMenu()) tabItems = {} self.loadedTabs = [] for tabClass in cfclient.ui.tabs.available: tab = tabClass(self.tabs, cfclient.ui.pluginhelper) item = QtGui.QAction(tab.getMenuName(), self) item.setCheckable(True) item.toggled.connect(tab.toggleVisibility) self.tabsMenuItem.menu().addAction(item) tabItems[tab.getTabName()] = item self.loadedTabs.append(tab) if not tab.enabled: item.setEnabled(False) # First instantiate all tabs and then open them in the correct order try: for tName in Config().get("open_tabs").split(","): t = tabItems[tName] if (t is not None and t.isEnabled()): # Toggle though menu so it's also marked as open there t.toggle() except Exception as e: logger.warning("Exception while opening tabs [{}]".format(e)) # References to all the device sub-menus in the "Input device" menu self._all_role_menus = () # Used to filter what new devices to add default mapping to self._available_devices = () # Keep track of mux nodes so we can enable according to how many # devices we have self._all_mux_nodes = () # Check which Input muxes are available self._mux_group = QActionGroup(self._menu_inputdevice, exclusive=True) for m in self.joystickReader.available_mux(): node = QAction(m.name, self._menu_inputdevice, checkable=True, enabled=False) node.toggled.connect(self._mux_selected) self._mux_group.addAction(node) self._menu_inputdevice.addAction(node) self._all_mux_nodes += (node,) mux_subnodes = () for name in m.supported_roles(): sub_node = QMenu(" {}".format(name), self._menu_inputdevice, enabled=False) self._menu_inputdevice.addMenu(sub_node) mux_subnodes += (sub_node,) self._all_role_menus += ({"muxmenu": node, "rolemenu": sub_node},) node.setData((m, mux_subnodes)) self._mapping_support = True def interfaceChanged(self, interface): if interface == INTERFACE_PROMPT_TEXT: self._selected_interface = None else: self._selected_interface = interface self._update_ui_state() def foundInterfaces(self, interfaces): selected_interface = self._selected_interface self.interfaceCombo.clear() self.interfaceCombo.addItem(INTERFACE_PROMPT_TEXT) formatted_interfaces = [] for i in interfaces: if len(i[1]) > 0: interface = "%s - %s" % (i[0], i[1]) else: interface = i[0] formatted_interfaces.append(interface) self.interfaceCombo.addItems(formatted_interfaces) if self._initial_scan: self._initial_scan = False try: selected_interface = Config().get("link_uri") except KeyError: pass newIndex = 0 if selected_interface is not None: try: newIndex = formatted_interfaces.index(selected_interface) + 1 except ValueError: pass self.interfaceCombo.setCurrentIndex(newIndex) self.uiState = UIState.DISCONNECTED self._update_ui_state() def _update_ui_state(self): if self.uiState == UIState.DISCONNECTED: self.setWindowTitle("Not connected") canConnect = self._selected_interface is not None self.menuItemConnect.setText("Connect to Crazyflie") self.menuItemConnect.setEnabled(canConnect) self.connectButton.setText("Connect") self.connectButton.setToolTip( "Connect to the Crazyflie on the selected interface") self.connectButton.setEnabled(canConnect) self.scanButton.setText("Scan") self.scanButton.setEnabled(True) self.address.setEnabled(True) self.batteryBar.setValue(3000) self._menu_cf2_config.setEnabled(False) self._menu_cf1_config.setEnabled(True) self.linkQualityBar.setValue(0) self.menuItemBootloader.setEnabled(True) self.logConfigAction.setEnabled(False) self.interfaceCombo.setEnabled(True) elif self.uiState == UIState.CONNECTED: s = "Connected on %s" % self._selected_interface self.setWindowTitle(s) self.menuItemConnect.setText("Disconnect") self.menuItemConnect.setEnabled(True) self.connectButton.setText("Disconnect") self.connectButton.setToolTip("Disconnect from the Crazyflie") self.scanButton.setEnabled(False) self.logConfigAction.setEnabled(True) # Find out if there's an I2C EEPROM, otherwise don't show the # dialog. if len(self.cf.mem.get_mems(MemoryElement.TYPE_I2C)) > 0: self._menu_cf2_config.setEnabled(True) self._menu_cf1_config.setEnabled(False) elif self.uiState == UIState.CONNECTING: s = "Connecting to {} ...".format(self._selected_interface) self.setWindowTitle(s) self.menuItemConnect.setText("Cancel") self.menuItemConnect.setEnabled(True) self.connectButton.setText("Cancel") self.connectButton.setToolTip("Cancel connecting to the Crazyflie") self.scanButton.setEnabled(False) self.address.setEnabled(False) self.menuItemBootloader.setEnabled(False) self.interfaceCombo.setEnabled(False) elif self.uiState == UIState.SCANNING: self.setWindowTitle("Scanning ...") self.connectButton.setText("Connect") self.menuItemConnect.setEnabled(False) self.connectButton.setText("Connect") self.connectButton.setEnabled(False) self.scanButton.setText("Scanning...") self.scanButton.setEnabled(False) self.address.setEnabled(False) self.menuItemBootloader.setEnabled(False) self.interfaceCombo.setEnabled(False) @pyqtSlot(bool) def toggleToolbox(self, display): menuItem = self.sender().menuItem dockToolbox = self.sender().dockToolbox if display and not dockToolbox.isVisible(): dockToolbox.widget().enable() self.addDockWidget(dockToolbox.widget().preferedDockArea(), dockToolbox) dockToolbox.show() elif not display: dockToolbox.widget().disable() self.removeDockWidget(dockToolbox) dockToolbox.hide() menuItem.setChecked(False) def _rescan_devices(self): self._statusbar_label.setText("No inputdevice connected!") self._menu_devices.clear() self._active_device = "" self.joystickReader.stop_input() # for c in self._menu_mappings.actions(): # c.setEnabled(False) # devs = self.joystickReader.available_devices() # if (len(devs) > 0): # self.device_discovery(devs) def _show_input_device_config_dialog(self): self.inputConfig = InputConfigDialogue(self.joystickReader) self.inputConfig.show() def _auto_reconnect_changed(self, checked): self._auto_reconnect_enabled = checked Config().set("auto_reconnect", checked) logger.info("Auto reconnect enabled: {}".format(checked)) def _show_connect_dialog(self): self.logConfigDialogue.show() def _update_battery(self, timestamp, data, logconf): self.batteryBar.setValue(int(data["pm.vbat"] * 1000)) color = COLOR_BLUE # TODO firmware reports fully-charged state as 'Battery', # rather than 'Charged' if data["pm.state"] in [BatteryStates.CHARGING, BatteryStates.CHARGED]: color = COLOR_GREEN elif data["pm.state"] == BatteryStates.LOW_POWER: color = COLOR_RED self.batteryBar.setStyleSheet(progressbar_stylesheet(color)) def _connected(self): self.uiState = UIState.CONNECTED self._update_ui_state() Config().set("link_uri", str(self._selected_interface)) lg = LogConfig("Battery", 1000) lg.add_variable("pm.vbat", "float") lg.add_variable("pm.state", "int8_t") try: self.cf.log.add_config(lg) lg.data_received_cb.add_callback(self.batteryUpdatedSignal.emit) lg.error_cb.add_callback(self._log_error_signal.emit) lg.start() except KeyError as e: logger.warning(str(e)) mem = self.cf.mem.get_mems(MemoryElement.TYPE_DRIVER_LED)[0] mem.write_data(self._led_write_done) # self._led_write_test = 0 # mem.leds[self._led_write_test] = [10, 20, 30] # mem.write_data(self._led_write_done) def _disconnected(self): self.uiState = UIState.DISCONNECTED self._update_ui_state() def _connection_initiated(self): self.uiState = UIState.CONNECTING self._update_ui_state() def _led_write_done(self, mem, addr): logger.info("LED write done callback") # self._led_write_test += 1 # mem.leds[self._led_write_test] = [10, 20, 30] # mem.write_data(self._led_write_done) def _logging_error(self, log_conf, msg): QMessageBox.about(self, "Log error", "Error when starting log config" " [{}]: {}".format(log_conf.name, msg)) def _connection_lost(self, linkURI, msg): if not self._auto_reconnect_enabled: if self.isActiveWindow(): warningCaption = "Communication failure" error = "Connection lost to {}: {}".format(linkURI, msg) QMessageBox.critical(self, warningCaption, error) self.uiState = UIState.DISCONNECTED self._update_ui_state() else: self._connect() def _connection_failed(self, linkURI, error): if not self._auto_reconnect_enabled: msg = "Failed to connect on {}: {}".format(linkURI, error) warningCaption = "Communication failure" QMessageBox.critical(self, warningCaption, msg) self.uiState = UIState.DISCONNECTED self._update_ui_state() else: self._connect() def closeEvent(self, event): self.hide() self.cf.close_link() Config().save_file() def _connect(self): if self.uiState == UIState.CONNECTED: self.cf.close_link() elif self.uiState == UIState.CONNECTING: self.cf.close_link() self.uiState = UIState.DISCONNECTED self._update_ui_state() else: self.cf.open_link(self._selected_interface) def _scan(self): self.uiState = UIState.SCANNING self._update_ui_state() self.scanner.scanSignal.emit(self.address.value()) def _display_input_device_error(self, error): self.cf.close_link() QMessageBox.critical(self, "Input device error", error) def _mux_selected(self, checked): """Called when a new mux is selected. The menu item contains a reference to the raw mux object as well as to the associated device sub-nodes""" if not checked: (mux, sub_nodes) = self.sender().data() for s in sub_nodes: s.setEnabled(False) else: (mux, sub_nodes) = self.sender().data() for s in sub_nodes: s.setEnabled(True) self.joystickReader.set_mux(mux=mux) # Go though the tree and select devices/mapping that was # selected before it was disabled. for role_node in sub_nodes: for dev_node in role_node.children(): if type(dev_node) is QAction and dev_node.isChecked(): dev_node.toggled.emit(True) self._update_input_device_footer() def _get_dev_status(self, device): msg = "{}".format(device.name) if device.supports_mapping: map_name = "N/A" if device.input_map: map_name = device.input_map_name msg += " ({})".format(map_name) return msg def _update_input_device_footer(self): """Update the footer in the bottom of the UI with status for the input device and its mapping""" msg = "" if len(self.joystickReader.available_devices()) > 0: mux = self.joystickReader._selected_mux msg = "Using {} mux with ".format(mux.name) for key in list(mux._devs.keys())[:-1]: if mux._devs[key]: msg += "{}, ".format(self._get_dev_status(mux._devs[key])) else: msg += "N/A, " # Last item key = list(mux._devs.keys())[-1] if mux._devs[key]: msg += "{}".format(self._get_dev_status(mux._devs[key])) else: msg += "N/A" else: msg = "No input device found" self._statusbar_label.setText(msg) def _inputdevice_selected(self, checked): """Called when a new input device has been selected from the menu. The data in the menu object is the associated map menu (directly under the item in the menu) and the raw device""" (map_menu, device, mux_menu) = self.sender().data() if not checked: if map_menu: map_menu.setEnabled(False) # Do not close the device, since we don't know exactly # how many devices the mux can have open. When selecting a # new mux the old one will take care of this. else: if map_menu: map_menu.setEnabled(True) (mux, sub_nodes) = mux_menu.data() for role_node in sub_nodes: for dev_node in role_node.children(): if type(dev_node) is QAction and dev_node.isChecked(): if device.id == dev_node.data()[1].id \ and dev_node is not self.sender(): dev_node.setChecked(False) role_in_mux = str(self.sender().parent().title()).strip() logger.info("Role of {} is {}".format(device.name, role_in_mux)) Config().set("input_device", str(device.name)) self._mapping_support = self.joystickReader.start_input( device.name, role_in_mux) self._update_input_device_footer() def _inputconfig_selected(self, checked): """Called when a new configuration has been selected from the menu. The data in the menu object is a referance to the device QAction in parent menu. This contains a referance to the raw device.""" if not checked: return selected_mapping = str(self.sender().text()) device = self.sender().data().data()[1] self.joystickReader.set_input_map(device.name, selected_mapping) self._update_input_device_footer() def device_discovery(self, devs): """Called when new devices have been added""" for menu in self._all_role_menus: role_menu = menu["rolemenu"] mux_menu = menu["muxmenu"] dev_group = QActionGroup(role_menu, exclusive=True) for d in devs: dev_node = QAction(d.name, role_menu, checkable=True, enabled=True) role_menu.addAction(dev_node) dev_group.addAction(dev_node) dev_node.toggled.connect(self._inputdevice_selected) map_node = None if d.supports_mapping: map_node = QMenu(" Input map", role_menu, enabled=False) map_group = QActionGroup(role_menu, exclusive=True) # Connect device node to map node for easy # enabling/disabling when selection changes and device # to easily enable it dev_node.setData((map_node, d)) for c in ConfigManager().get_list_of_configs(): node = QAction(c, map_node, checkable=True, enabled=True) node.toggled.connect(self._inputconfig_selected) map_node.addAction(node) # Connect all the map nodes back to the device # action node where we can access the raw device node.setData(dev_node) map_group.addAction(node) # If this device hasn't been found before, then # select the default mapping for it. if d not in self._available_devices: last_map = Config().get("device_config_mapping") if d.name in last_map and last_map[d.name] == c: node.setChecked(True) role_menu.addMenu(map_node) dev_node.setData((map_node, d, mux_menu)) # Update the list of what devices we found # to avoid selecting default mapping for all devices when # a new one is inserted self._available_devices = () for d in devs: self._available_devices += (d,) # Only enable MUX nodes if we have enough devies to cover # the roles for mux_node in self._all_mux_nodes: (mux, sub_nodes) = mux_node.data() if len(mux.supported_roles()) <= len(self._available_devices): mux_node.setEnabled(True) # TODO: Currently only supports selecting default mux if self._all_mux_nodes[0].isEnabled(): self._all_mux_nodes[0].setChecked(True) # If the previous length of the available devies was 0, then select # the default on. If that's not available then select the first # on in the list. # TODO: This will only work for the "Normal" mux so this will be # selected by default if Config().get("input_device") in [d.name for d in devs]: for dev_menu in self._all_role_menus[0]["rolemenu"].actions(): if dev_menu.text() == Config().get("input_device"): dev_menu.setChecked(True) else: # Select the first device in the first mux (will always be "Normal" # mux) self._all_role_menus[0]["rolemenu"].actions()[0].setChecked(True) logger.info("Select first device") self._update_input_device_footer() def _open_config_folder(self): QDesktopServices.openUrl( QUrl("file:///" + QDir.toNativeSeparators(cfclient.config_path))) def closeAppRequest(self): self.close() sys.exit(0)
class MainUI(QtGui.QMainWindow, main_window_class): connectionLostSignal = pyqtSignal(str, str) connectionInitiatedSignal = pyqtSignal(str) batteryUpdatedSignal = pyqtSignal(int, object, object) connectionDoneSignal = pyqtSignal(str) connectionFailedSignal = pyqtSignal(str, str) disconnectedSignal = pyqtSignal(str) linkQualitySignal = pyqtSignal(int) _input_device_error_signal = pyqtSignal(str) _input_discovery_signal = pyqtSignal(object) _log_error_signal = pyqtSignal(object, str) def __init__(self, *args): super(MainUI, self).__init__(*args) self.setupUi(self) ###################################################### ### By lxrocks ### 'Skinny Progress Bar' tweak for Yosemite ### Tweak progress bar - artistic I am not - so pick your own colors !!! ### Only apply to Yosemite ###################################################### import platform if platform.system() == 'Darwin': (Version,junk,machine) = platform.mac_ver() logger.info("This is a MAC - checking if we can apply Progress Bar Stylesheet for Yosemite Skinny Bars ") yosemite = (10,10,0) tVersion = tuple(map(int, (Version.split(".")))) if tVersion >= yosemite: logger.info( "Found Yosemite:") tcss = """ QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: center; } QProgressBar::chunk { background-color: #05B8CC; } """ self.setStyleSheet(tcss) else: logger.info( "Pre-Yosemite") ###################################################### self.cf = Crazyflie(ro_cache=sys.path[0] + "/cflib/cache", rw_cache=sys.path[1] + "/cache") cflib.crtp.init_drivers(enable_debug_driver=Config() .get("enable_debug_driver")) # Create the connection dialogue self.connectDialogue = ConnectDialogue() # Create and start the Input Reader self._statusbar_label = QLabel("Loading device and configuration.") self.statusBar().addWidget(self._statusbar_label) self.joystickReader = JoystickReader() self._active_device = "" self.configGroup = QActionGroup(self._menu_mappings, exclusive=True) self._load_input_data() ConfigManager().conf_needs_reload.add_callback(self._reload_configs) # Connections for the Connect Dialogue self.connectDialogue.requestConnectionSignal.connect(self.cf.open_link) self.cf.connection_failed.add_callback(self.connectionFailedSignal.emit) self.connectionFailedSignal.connect(self._connection_failed) self._input_device_error_signal.connect(self._display_input_device_error) self.joystickReader.device_error.add_callback( self._input_device_error_signal.emit) self._input_discovery_signal.connect(self.device_discovery) self.joystickReader.device_discovery.add_callback( self._input_discovery_signal.emit) # Connect UI signals self.menuItemConnect.triggered.connect(self._connect) self.logConfigAction.triggered.connect(self._show_connect_dialog) self.connectButton.clicked.connect(self._connect) self.quickConnectButton.clicked.connect(self._quick_connect) self.menuItemQuickConnect.triggered.connect(self._quick_connect) self.menuItemConfInputDevice.triggered.connect(self._show_input_device_config_dialog) self.menuItemExit.triggered.connect(self.closeAppRequest) self.batteryUpdatedSignal.connect(self._update_vbatt) self._menuitem_rescandevices.triggered.connect(self._rescan_devices) self._menuItem_openconfigfolder.triggered.connect(self._open_config_folder) self._auto_reconnect_enabled = Config().get("auto_reconnect") self.autoReconnectCheckBox.toggled.connect( self._auto_reconnect_changed) self.autoReconnectCheckBox.setChecked(Config().get("auto_reconnect")) self.joystickReader.input_updated.add_callback( self.cf.commander.send_setpoint) # Connection callbacks and signal wrappers for UI protection self.cf.connected.add_callback(self.connectionDoneSignal.emit) self.connectionDoneSignal.connect(self._connected) self.cf.disconnected.add_callback(self.disconnectedSignal.emit) self.disconnectedSignal.connect( lambda linkURI: self._update_ui_state(UIState.DISCONNECTED, linkURI)) self.cf.connection_lost.add_callback(self.connectionLostSignal.emit) self.connectionLostSignal.connect(self._connection_lost) self.cf.connection_requested.add_callback( self.connectionInitiatedSignal.emit) self.connectionInitiatedSignal.connect( lambda linkURI: self._update_ui_state(UIState.CONNECTING, linkURI)) self._log_error_signal.connect(self._logging_error) # Connect link quality feedback self.cf.link_quality_updated.add_callback(self.linkQualitySignal.emit) self.linkQualitySignal.connect( lambda percentage: self.linkQualityBar.setValue(percentage)) # Set UI state in disconnected buy default self._update_ui_state(UIState.DISCONNECTED) # Parse the log configuration files self.logConfigReader = LogConfigReader(self.cf) self._current_input_config = None self._active_config = None self._active_config = None self.inputConfig = None # Add things to helper so tabs can access it cfclient.ui.pluginhelper.cf = self.cf cfclient.ui.pluginhelper.inputDeviceReader = self.joystickReader cfclient.ui.pluginhelper.logConfigReader = self.logConfigReader self.logConfigDialogue = LogConfigDialogue(cfclient.ui.pluginhelper) self._bootloader_dialog = BootloaderDialog(cfclient.ui.pluginhelper) self._cf2config_dialog = Cf2ConfigDialog(cfclient.ui.pluginhelper) self._cf1config_dialog = Cf1ConfigDialog(cfclient.ui.pluginhelper) self.menuItemBootloader.triggered.connect(self._bootloader_dialog.show) self._about_dialog = AboutDialog(cfclient.ui.pluginhelper) self.menuItemAbout.triggered.connect(self._about_dialog.show) self._menu_cf2_config.triggered.connect(self._cf2config_dialog.show) self._menu_cf1_config.triggered.connect(self._cf1config_dialog.show) # Loading toolboxes (A bit of magic for a lot of automatic) self.toolboxes = [] self.toolboxesMenuItem.setMenu(QtGui.QMenu()) for t_class in cfclient.ui.toolboxes.toolboxes: toolbox = t_class(cfclient.ui.pluginhelper) dockToolbox = MyDockWidget(toolbox.getName()) dockToolbox.setWidget(toolbox) self.toolboxes += [dockToolbox, ] # Add menu item for the toolbox item = QtGui.QAction(toolbox.getName(), self) item.setCheckable(True) item.triggered.connect(self.toggleToolbox) self.toolboxesMenuItem.menu().addAction(item) dockToolbox.closed.connect(lambda: self.toggleToolbox(False)) # Setup some introspection item.dockToolbox = dockToolbox item.menuItem = item dockToolbox.dockToolbox = dockToolbox dockToolbox.menuItem = item # Load and connect tabs self.tabsMenuItem.setMenu(QtGui.QMenu()) tabItems = {} self.loadedTabs = [] for tabClass in cfclient.ui.tabs.available: tab = tabClass(self.tabs, cfclient.ui.pluginhelper) item = QtGui.QAction(tab.getMenuName(), self) item.setCheckable(True) item.toggled.connect(tab.toggleVisibility) self.tabsMenuItem.menu().addAction(item) tabItems[tab.getTabName()] = item self.loadedTabs.append(tab) if not tab.enabled: item.setEnabled(False) # First instantiate all tabs and then open them in the correct order try: for tName in Config().get("open_tabs").split(","): t = tabItems[tName] if (t != None and t.isEnabled()): # Toggle though menu so it's also marked as open there t.toggle() except Exception as e: logger.warning("Exception while opening tabs [{}]".format(e)) # Check which Input muxes are available self._mux_group = QActionGroup(self._menu_mux, exclusive=True) for m in self.joystickReader.available_mux(): node = QAction(m, self._menu_mux, checkable=True, enabled=True) node.toggled.connect(self._mux_selected) self._mux_group.addAction(node) self._menu_mux.addAction(node) # TODO: Temporary self._input_dev_stack = [] self._menu_mux.actions()[0].setChecked(True) if Config().get("enable_input_muxing"): self._menu_mux.setEnabled(True) else: logger.info("Input device muxing disabled in config") self._mapping_support = True def _update_ui_state(self, newState, linkURI=""): self.uiState = newState if newState == UIState.DISCONNECTED: self.setWindowTitle("Not connected") self.menuItemConnect.setText("Connect to Crazyflie") self.connectButton.setText("Connect") self.menuItemQuickConnect.setEnabled(True) self.batteryBar.setValue(3000) self._menu_cf2_config.setEnabled(False) self._menu_cf1_config.setEnabled(True) self.linkQualityBar.setValue(0) self.menuItemBootloader.setEnabled(True) self.logConfigAction.setEnabled(False) if len(Config().get("link_uri")) > 0: self.quickConnectButton.setEnabled(True) if newState == UIState.CONNECTED: s = "Connected on %s" % linkURI self.setWindowTitle(s) self.menuItemConnect.setText("Disconnect") self.connectButton.setText("Disconnect") self.logConfigAction.setEnabled(True) # Find out if there's an I2C EEPROM, otherwise don't show the # dialog. if len(self.cf.mem.get_mems(MemoryElement.TYPE_I2C)) > 0: self._menu_cf2_config.setEnabled(True) self._menu_cf1_config.setEnabled(False) if newState == UIState.CONNECTING: s = "Connecting to {} ...".format(linkURI) self.setWindowTitle(s) self.menuItemConnect.setText("Cancel") self.connectButton.setText("Cancel") self.quickConnectButton.setEnabled(False) self.menuItemBootloader.setEnabled(False) self.menuItemQuickConnect.setEnabled(False) @pyqtSlot(bool) def toggleToolbox(self, display): menuItem = self.sender().menuItem dockToolbox = self.sender().dockToolbox if display and not dockToolbox.isVisible(): dockToolbox.widget().enable() self.addDockWidget(dockToolbox.widget().preferedDockArea(), dockToolbox) dockToolbox.show() elif not display: dockToolbox.widget().disable() self.removeDockWidget(dockToolbox) dockToolbox.hide() menuItem.setChecked(False) def _rescan_devices(self): self._statusbar_label.setText("No inputdevice connected!") self._menu_devices.clear() self._active_device = "" self.joystickReader.stop_input() for c in self._menu_mappings.actions(): c.setEnabled(False) devs = self.joystickReader.available_devices() if (len(devs) > 0): self.device_discovery(devs) def _show_input_device_config_dialog(self): self.inputConfig = InputConfigDialogue(self.joystickReader) self.inputConfig.show() def _auto_reconnect_changed(self, checked): self._auto_reconnect_enabled = checked Config().set("auto_reconnect", checked) logger.info("Auto reconnect enabled: {}".format(checked)) def _show_connect_dialog(self): self.logConfigDialogue.show() def _update_vbatt(self, timestamp, data, logconf): self.batteryBar.setValue(int(data["pm.vbat"] * 1000)) def _connected(self, linkURI): self._update_ui_state(UIState.CONNECTED, linkURI) Config().set("link_uri", str(linkURI)) lg = LogConfig("Battery", 1000) lg.add_variable("pm.vbat", "float") try: self.cf.log.add_config(lg) lg.data_received_cb.add_callback(self.batteryUpdatedSignal.emit) lg.error_cb.add_callback(self._log_error_signal.emit) lg.start() except KeyError as e: logger.warning(str(e)) def _logging_error(self, log_conf, msg): QMessageBox.about(self, "Log error", "Error when starting log config" " [{}]: {}".format(log_conf.name, msg)) def _connection_lost(self, linkURI, msg): if not self._auto_reconnect_enabled: if self.isActiveWindow(): warningCaption = "Communication failure" error = "Connection lost to {}: {}".format(linkURI, msg) QMessageBox.critical(self, warningCaption, error) self._update_ui_state(UIState.DISCONNECTED, linkURI) else: self._quick_connect() def _connection_failed(self, linkURI, error): if not self._auto_reconnect_enabled: msg = "Failed to connect on {}: {}".format(linkURI, error) warningCaption = "Communication failure" QMessageBox.critical(self, warningCaption, msg) self._update_ui_state(UIState.DISCONNECTED, linkURI) else: self._quick_connect() def closeEvent(self, event): self.hide() self.cf.close_link() Config().save_file() def _connect(self): if self.uiState == UIState.CONNECTED: self.cf.close_link() elif self.uiState == UIState.CONNECTING: self.cf.close_link() self._update_ui_state(UIState.DISCONNECTED) else: self.connectDialogue.show() def _display_input_device_error(self, error): self.cf.close_link() QMessageBox.critical(self, "Input device error", error) def _load_input_data(self): self.joystickReader.stop_input() # Populate combo box with available input device configurations for c in ConfigManager().get_list_of_configs(): node = QAction(c, self._menu_mappings, checkable=True, enabled=False) node.toggled.connect(self._inputconfig_selected) self.configGroup.addAction(node) self._menu_mappings.addAction(node) def _reload_configs(self, newConfigName): # remove the old actions from the group and the menu for action in self._menu_mappings.actions(): self.configGroup.removeAction(action) self._menu_mappings.clear() # reload the conf files, and populate the menu self._load_input_data() self._update_input(self._active_device, newConfigName) def _update_input(self, device="", config=""): self._active_config = str(config) self._active_device = str(device) Config().set("input_device", str(self._active_device)) # update the checked state of the menu items for c in self._menu_mappings.actions(): c.setEnabled(True) if c.text() == self._active_config: c.setChecked(True) for c in self._menu_devices.actions(): c.setEnabled(True) if c.text() == self._active_device: c.setChecked(True) # update label if device == "" and config == "": self._statusbar_label.setText("No input device selected") elif config == "": self._statusbar_label.setText("Using [{}] - " "No input config selected".format (self._active_device)) else: self._statusbar_label.setText("Using [{}] with config [{}]".format (self._active_device, self._active_config)) def _mux_selected(self, checked): if not checked: return selected_mux_name = str(self.sender().text()) self.joystickReader.set_mux(name=selected_mux_name) logger.debug("Selected mux supports {} devices".format(self.joystickReader.get_mux_supported_dev_count())) self._adjust_nbr_of_selected_devices() def _get_saved_device_mapping(self, device_name): """Return the saved mapping for a given device""" config = None device_config_mapping = Config().get("device_config_mapping") if device_name in device_config_mapping.keys(): config = device_config_mapping[device_name] logging.debug("For [{}] we recommend [{}]".format(device_name, config)) return config def _update_input_device_footer(self, device_name=None, mapping_name=None): """Update the footer in the bottom of the UI with status for the input device and its mapping""" if not device_name and not mapping_name: self._statusbar_label.setText("No input device selected") elif self._mapping_support and not mapping_name: self._statusbar_label.setText("Using [{}] - " "No input config selected".format( device_name)) elif not self._mapping_support: self._statusbar_label.setText("Using [{}]".format(device_name)) else: self._statusbar_label.setText("Using [{}] with config [{}]".format( device_name, mapping_name)) def _adjust_nbr_of_selected_devices(self): nbr_of_selected = len(self._input_dev_stack) nbr_of_supported = self.joystickReader.get_mux_supported_dev_count() while len(self._input_dev_stack) > nbr_of_supported: to_close = self._input_dev_stack.pop(0) # Close and de-select it in the UI self.joystickReader.stop_input(to_close) for c in self._menu_devices.actions(): if c.text() == to_close: c.setChecked(False) def _inputdevice_selected(self, checked): """Called when a new input device has been selected from the menu""" if not checked: return self._input_dev_stack.append(self.sender().text()) selected_device_name = str(self.sender().text()) self._active_device = selected_device_name # Save the device as "last used device" Config().set("input_device", str(selected_device_name)) # Read preferred config used for this controller from config, # if found then select this config in the menu self._mapping_support = self.joystickReader.start_input(selected_device_name) self._adjust_nbr_of_selected_devices() if self.joystickReader.get_mux_supported_dev_count() == 1: preferred_config = self.joystickReader.get_saved_device_mapping(selected_device_name) if preferred_config: for c in self._menu_mappings.actions(): if c.text() == preferred_config: c.setChecked(True) def _inputconfig_selected(self, checked): """Called when a new configuration has been selected from the menu""" if not checked: return selected_mapping = str(self.sender().text()) self.joystickReader.set_input_map(self._active_device, selected_mapping) self._update_input_device_footer(self._active_device, selected_mapping) def device_discovery(self, devs): group = QActionGroup(self._menu_devices, exclusive=False) for d in devs: node = QAction(d.name, self._menu_devices, checkable=True) node.toggled.connect(self._inputdevice_selected) group.addAction(node) self._menu_devices.addAction(node) if d.name == Config().get("input_device"): self._active_device = d.name if len(self._active_device) == 0: self._active_device = str(self._menu_devices.actions()[0].text()) device_config_mapping = Config().get("device_config_mapping") if device_config_mapping: if self._active_device in device_config_mapping.keys(): self._current_input_config = device_config_mapping[ self._active_device] else: self._current_input_config = self._menu_mappings.actions()[0]\ .text() else: self._current_input_config = self._menu_mappings.actions()[0].text() # Now we know what device to use and what mapping, trigger the events # to change the menus and start the input for c in self._menu_devices.actions(): if c.text() == self._active_device: c.setChecked(True) for c in self._menu_mappings.actions(): c.setEnabled(True) if c.text() == self._current_input_config: c.setChecked(True) def _quick_connect(self): try: self.cf.open_link(Config().get("link_uri")) except KeyError: self.cf.open_link("") def _open_config_folder(self): QDesktopServices.openUrl(QUrl("file:///" + QDir.toNativeSeparators(sys.path[1]))) def closeAppRequest(self): self.close() sys.exit(0)
class FreeseerApp(QMainWindow): def __init__(self, config): super(FreeseerApp, self).__init__() self.config = config self.icon = QIcon() self.icon.addPixmap(QPixmap(_fromUtf8(":/freeseer/logo.png")), QIcon.Normal, QIcon.Off) self.setWindowIcon(self.icon) self.aboutDialog = AboutDialog() self.aboutDialog.setModal(True) self.logDialog = LogDialog() # # Translator # self.app = QApplication.instance() self.current_language = None self.uiTranslator = QTranslator() self.uiTranslator.load(":/languages/tr_en_US.qm") self.app.installTranslator(self.uiTranslator) self.langActionGroup = QActionGroup(self) self.langActionGroup.setExclusive(True) QTextCodec.setCodecForTr(QTextCodec.codecForName('utf-8')) self.connect(self.langActionGroup, SIGNAL('triggered(QAction *)'), self.translate) # --- Translator # # Setup Menubar # self.menubar = self.menuBar() self.menubar.setGeometry(QRect(0, 0, 500, 50)) self.menubar.setObjectName(_fromUtf8("menubar")) self.menuFile = QMenu(self.menubar) self.menuFile.setObjectName(_fromUtf8("menuFile")) self.menuLanguage = QMenu(self.menubar) self.menuLanguage.setObjectName(_fromUtf8("menuLanguage")) self.menuHelp = QMenu(self.menubar) self.menuHelp.setObjectName(_fromUtf8("menuHelp")) exitIcon = QIcon.fromTheme("application-exit") self.actionExit = QAction(self) self.actionExit.setShortcut("Ctrl+Q") self.actionExit.setObjectName(_fromUtf8("actionExit")) self.actionExit.setIcon(exitIcon) helpIcon = QIcon.fromTheme("help-contents") self.actionOnlineHelp = QAction(self) self.actionOnlineHelp.setObjectName(_fromUtf8("actionOnlineHelp")) self.actionOnlineHelp.setIcon(helpIcon) self.actionAbout = QAction(self) self.actionAbout.setObjectName(_fromUtf8("actionAbout")) self.actionAbout.setIcon(self.icon) self.actionLog = QAction(self) self.actionLog.setObjectName(_fromUtf8("actionLog")) self.actionLog.setShortcut("Ctrl+L") # Actions self.menuFile.addAction(self.actionExit) self.menuHelp.addAction(self.actionAbout) self.menuHelp.addAction(self.actionLog) self.menuHelp.addAction(self.actionOnlineHelp) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuLanguage.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) self.setupLanguageMenu() # --- End Menubar self.connect(self.actionExit, SIGNAL('triggered()'), self.close) self.connect(self.actionAbout, SIGNAL('triggered()'), self.aboutDialog.show) self.connect(self.actionLog, SIGNAL('triggered()'), self.logDialog.show) self.connect(self.actionOnlineHelp, SIGNAL('triggered()'), self.openOnlineHelp) self.retranslateFreeseerApp() self.aboutDialog.aboutWidget.retranslate("en_US") def openOnlineHelp(self): """Opens a link to the Freeseer Online Help""" url = QUrl("http://freeseer.readthedocs.org") QDesktopServices.openUrl(url) def translate(self, action): """Translates the GUI via menu action. When a language is selected from the language menu, this function is called and the language to be changed to is retrieved. """ self.current_language = str( action.data().toString()).strip("tr_").rstrip(".qm") log.info("Switching language to: %s" % action.text()) self.uiTranslator.load(":/languages/tr_%s.qm" % self.current_language) self.app.installTranslator(self.uiTranslator) self.retranslateFreeseerApp() self.aboutDialog.aboutWidget.retranslate(self.current_language) self.retranslate() self.logDialog.retranslate() def retranslate(self): """ Reimplement this function to provide translations to your app. """ pass def retranslateFreeseerApp(self): # # Menubar # self.menuFile.setTitle(self.app.translate("FreeseerApp", "&File")) self.menuLanguage.setTitle( self.app.translate("FreeseerApp", "&Language")) self.menuHelp.setTitle(self.app.translate("FreeseerApp", "&Help")) self.actionExit.setText(self.app.translate("FreeseerApp", "&Quit")) self.actionAbout.setText(self.app.translate("FreeseerApp", "&About")) self.actionLog.setText(self.app.translate("FreeseerApp", "View &Log")) self.actionOnlineHelp.setText( self.app.translate("FreeseerApp", "Online Documentation")) # --- Menubar def setupLanguageMenu(self): self.languages = QDir(":/languages").entryList() if self.current_language is None: self.current_language = QLocale.system().name( ) # Retrieve Current Locale from the operating system. log.debug("Detected user's locale as %s" % self.current_language) for language in self.languages: translator = QTranslator( ) # Create a translator to translate Language Display Text. translator.load(":/languages/%s" % language) language_display_text = translator.translate( "Translation", "Language Display Text") languageAction = QAction(self) languageAction.setCheckable(True) languageAction.setText(language_display_text) languageAction.setData(language) self.menuLanguage.addAction(languageAction) self.langActionGroup.addAction(languageAction)
class MapWidget(Ui_CanvasWidget, QMainWindow): def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) self.firstshow = True self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) if hasattr(self.canvas, 'setParallelRenderingEnabled'): self.canvas.setParallelRenderingEnabled(True) pal = QgsPalLabeling() self.canvas.mapRenderer().setLabelingEngine(pal) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(":/icons/gps", self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) gpsspacewidget = QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget( self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction( self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.marker = GPSMarker(self.canvas) self.marker.hide() self.currentfeatureband = QgsRubberBand(self.canvas) self.currentfeatureband.setIconSize(20) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(186, 93, 212, 76)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(0, 0, 212, 76)) self.gpsband.setWidth(5) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.featureformloaded.connect(self.feature_form_loaded) self.connectButtons() def init_qgisproject(self, doc): parser = ProjectParser(doc) canvasnode = parser.canvasnode self.canvas.freeze() self.canvas.mapRenderer().readXML(canvasnode) self.canvaslayers = parser.canvaslayers() self.canvas.setLayerSet(self.canvaslayers) #red = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorRedPart", 255 )[0]; #green = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorGreenPart", 255 )[0]; #blue = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorBluePart", 255 )[0]; #color = QColor(red, green, blue); #self.canvas.setCanvasColor(color) self.canvas.updateScale() return self.canvas.mapRenderer().destinationCrs() def showEvent(self, *args, **kwargs): if self.firstshow: self.canvas.refresh() self.canvas.repaint() self.firstshow = False def feature_form_loaded(self, form, feature, project, editmode): self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer) def highlight_selection(self, results): self.clear_selection() for layer, features in results.iteritems(): band = self.selectionbands[layer] band.setColor(QColor(255, 0, 0, 200)) band.setIconSize(20) band.setWidth(2) band.setBrushStyle(Qt.NoBrush) band.reset(layer.geometryType()) for feature in features: band.addGeometry(feature.geometry(), layer) def highlight_active_selection(self, layer, feature, features): self.clear_selection() self.highlight_selection({layer: features}) self.currentfeatureband.setToGeometry(feature.geometry(), layer) def clear_selection(self): # Clear the main selection rubber band self.currentfeatureband.reset() # Clear the rest for band in self.selectionbands.itervalues(): band.reset() self.editfeaturestack = [] def queue_feature_for_edit(self, form, feature): def trigger_default_action(): for action in self.projecttoolbar.actions(): if action.property('dataentry') and action.isdefault: action.trigger() break self.editfeaturestack.append((form, feature)) self.load_form(form) trigger_default_action() def clear_temp_objects(self): def clear_tool_band(): """ Clear the rubber band of the active tool if it has one """ tool = self.canvas.mapTool() try: tool.clearBand() except AttributeError: # No clearBand method found, but that's cool. pass self.currentfeatureband.reset() clear_tool_band() def settings_updated(self, settings): self.actionGPS.updateGPSPort() gpslogging = settings.get('gpslogging', True) if self.gpslogging: self.gpslogging.logging = gpslogging def set_gps(self, gps, logging): self.gps = gps self.gpslogging = logging self.gps.gpsposition.connect(self.gps_update_canvas) self.gps.firstfix.connect(self.gps_first_fix) self.gps.gpsdisconnected.connect(self.gps_disconnected) def gps_update_canvas(self, position, gpsinfo): # Recenter map if we go outside of the 95% of the area if self.gpslogging.logging: self.gpsband.addPoint(position) self.gpsband.show() if roam.config.settings.get('gpscenter', True): if not self.lastgpsposition == position: self.lastposition = position rect = QgsRectangle(position, position) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(position): self.zoom_to_location(position) self.marker.show() self.marker.setCenter(position) def gps_first_fix(self, postion, gpsinfo): zoomtolocation = roam.config.settings.get('gpszoomonfix', True) if zoomtolocation: self.canvas.zoomScale(1000) self.zoom_to_location(postion) def zoom_to_location(self, position): rect = QgsRectangle(position, position) self.canvas.setExtent(rect) self.canvas.refresh() def gps_disconnected(self): self.marker.hide() def select_data_entry(self): def showformerror(form): pass def actions(): for form in self.project.forms: action = form.createuiaction() valid, failreasons = form.valid if not valid: roam.utils.warning("Form {} failed to load".format( form.label)) roam.utils.warning("Reasons {}".format(failreasons)) action.triggered.connect(partial(showformerror, form)) else: action.triggered.connect(partial(self.load_form, form)) yield action formpicker = PickActionDialog(msg="Select data entry form") formpicker.addactions(actions()) formpicker.exec_() def project_loaded(self, project): self.project = project self.actionPan.trigger() try: firstform = project.forms[0] self.load_form(firstform) self.dataentryselection.setVisible(True) except IndexError: self.dataentryselection.setVisible(False) # Enable the raster layers button only if the project contains a raster layer. layers = QgsMapLayerRegistry.instance().mapLayers().values() hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers) self.actionRaster.setEnabled(hasrasters) self.defaultextent = self.canvas.extent() roam.utils.info("Extent: {}".format(self.defaultextent.toString())) self.infoTool.selectionlayers = project.selectlayersmapping() self.canvas.freeze(False) self.canvas.refresh() def setMapTool(self, tool, *args): self.canvas.setMapTool(tool) def connectButtons(self): def connectAction(action, tool): action.toggled.connect(partial(self.setMapTool, tool)) def cursor(name): pix = QPixmap(name) pix = pix.scaled(QSize(24, 24)) return QCursor(pix) self.zoomInTool = QgsMapToolZoom(self.canvas, False) self.zoomOutTool = QgsMapToolZoom(self.canvas, True) self.panTool = PanTool(self.canvas) self.infoTool = InfoTool(self.canvas) connectAction(self.actionZoom_In, self.zoomInTool) connectAction(self.actionZoom_Out, self.zoomOutTool) connectAction(self.actionPan, self.panTool) connectAction(self.actionInfo, self.infoTool) self.zoomInTool.setCursor(cursor(':/icons/in')) self.zoomOutTool.setCursor(cursor(':/icons/out')) self.infoTool.setCursor(cursor(':/icons/info')) self.actionRaster.triggered.connect(self.toggleRasterLayers) self.infoTool.infoResults.connect(RoamEvents.selectionchanged.emit) self.actionHome.triggered.connect(self.homeview) def homeview(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ self.canvas.setExtent(self.defaultextent) self.canvas.refresh() def load_form(self, form): self.clearCapatureTools() self.dataentryselection.setIcon(QIcon(form.icon)) self.dataentryselection.setText(form.icontext) self.create_capture_buttons(form) def create_capture_buttons(self, form): tool = form.getMaptool()(self.canvas) for action in tool.actions: # Create the action here. if action.ismaptool: action.toggled.connect(partial(self.setMapTool, tool)) # Set the action as a data entry button so we can remove it later. action.setProperty("dataentry", True) self.editgroup.addAction(action) self.layerbuttons.append(action) self.projecttoolbar.insertAction(self.topspaceraction, action) action.setChecked(action.isdefault) if hasattr(tool, 'geometryComplete'): add = partial(self.add_new_feature, form) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(partial(self.showUIMessage, form.label)) def add_new_feature(self, form, geometry): """ Add a new new feature to the given layer """ # TODO Extract into function. # NOTE This function is doing too much, acts as add and also edit. layer = form.QGISLayer if layer.geometryType() in [ QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon ]: geometry.convertToMultiType() try: form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass layer = form.QGISLayer fields = layer.pendingFields() feature = QgsFeature(fields) feature.setGeometry(geometry) for index in xrange(fields.count()): pkindexes = layer.dataProvider().pkAttributeIndexes() if index in pkindexes and layer.dataProvider().name( ) == 'spatialite': continue value = layer.dataProvider().defaultValue(index) feature[index] = value RoamEvents.open_feature_form(form, feature, editmode=False) def editfeaturegeometry(self, form, feature, newgeometry): # TODO Extract into function. layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() if not saved: map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() self.currentfeatureband.setToGeometry(feature.geometry(), layer) RoamEvents.editgeometry_complete.emit(form, feature) def clearCapatureTools(self): captureselected = False for action in self.projecttoolbar.actions(): if action.objectName() == "capture" and action.isChecked(): captureselected = True if action.property('dataentry'): self.projecttoolbar.removeAction(action) return captureselected def toggleRasterLayers(self): """ Toggle all raster layers on or off. """ if not self.canvaslayers: return #Freeze the canvas to save on UI refresh self.canvas.freeze() for layer in self.canvaslayers: if layer.layer().type() == QgsMapLayer.RasterLayer: layer.setVisible(not layer.isVisible()) # Really!? We have to reload the whole layer set every time? # WAT? self.canvas.setLayerSet(self.canvaslayers) self.canvas.freeze(False) self.canvas.refresh() def cleanup(self): self.gpsband.reset() self.gpsband.hide() self.clear_selection() self.clear_temp_objects() self.clearCapatureTools() self.canvas.freeze() self.canvas.clear() self.canvas.freeze(False) for action in self.layerbuttons: self.editgroup.removeAction(action)
class MainWindow(QMainWindow, Ui_MainWindow): """docstring for MainWindow.""" def __init__(self, parent=None): super(MainWindow, self).__init__() self._csvFilePath = "" self.serialport = serial.Serial() self.receiver_thread = readerThread(self) self.receiver_thread.setPort(self.serialport) self._localEcho = None self.setupUi(self) self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) font = QtGui.QFont() font.setFamily(EDITOR_FONT) font.setPointSize(10) self.txtEdtOutput.setFont(font) self.txtEdtInput.setFont(font) self.quickSendTable.setFont(font) if UI_FONT is not None: font = QtGui.QFont() font.setFamily(UI_FONT) font.setPointSize(9) self.dockWidget_PortConfig.setFont(font) self.dockWidget_SendHex.setFont(font) self.dockWidget_QuickSend.setFont(font) self.setupFlatUi() self.onEnumPorts() icon = QtGui.QIcon(":/icon.ico") self.setWindowIcon(icon) self.actionAbout.setIcon(icon) icon = QtGui.QIcon(":/qt_logo_16.ico") self.actionAbout_Qt.setIcon(icon) self._viewGroup = QActionGroup(self) self._viewGroup.addAction(self.actionAscii) self._viewGroup.addAction(self.actionHex_lowercase) self._viewGroup.addAction(self.actionHEX_UPPERCASE) self._viewGroup.setExclusive(True) # bind events self.actionOpen_Cmd_File.triggered.connect(self.openCSV) self.actionSave_Log.triggered.connect(self.onSaveLog) self.actionExit.triggered.connect(self.onExit) self.actionOpen.triggered.connect(self.openPort) self.actionClose.triggered.connect(self.closePort) self.actionPort_Config_Panel.triggered.connect(self.onTogglePrtCfgPnl) self.actionQuick_Send_Panel.triggered.connect(self.onToggleQckSndPnl) self.actionSend_Hex_Panel.triggered.connect(self.onToggleHexPnl) self.dockWidget_PortConfig.visibilityChanged.connect( self.onVisiblePrtCfgPnl) self.dockWidget_QuickSend.visibilityChanged.connect( self.onVisibleQckSndPnl) self.dockWidget_SendHex.visibilityChanged.connect(self.onVisibleHexPnl) self.actionLocal_Echo.triggered.connect(self.onLocalEcho) self.actionAlways_On_Top.triggered.connect(self.onAlwaysOnTop) self.actionAscii.triggered.connect(self.onViewChanged) self.actionHex_lowercase.triggered.connect(self.onViewChanged) self.actionHEX_UPPERCASE.triggered.connect(self.onViewChanged) self.actionAbout.triggered.connect(self.onAbout) self.actionAbout_Qt.triggered.connect(self.onAboutQt) self.btnOpen.clicked.connect(self.onOpen) self.btnClear.clicked.connect(self.onClear) self.btnSaveLog.clicked.connect(self.onSaveLog) self.btnEnumPorts.clicked.connect(self.onEnumPorts) self.btnSendHex.clicked.connect(self.sendHex) self.receiver_thread.read.connect(self.receive) self.receiver_thread.exception.connect(self.readerExcept) self._signalMap = QSignalMapper(self) self._signalMap.mapped[int].connect(self.tableClick) # initial action self.actionHEX_UPPERCASE.setChecked(True) self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE) self.initQuickSend() self.restoreLayout() self.moveScreenCenter() self.syncMenu() if self.isMaximized(): self.setMaximizeButton("restore") else: self.setMaximizeButton("maximize") self.LoadSettings() def setupFlatUi(self): self._dragPos = self.pos() self._isDragging = False self.setMouseTracking(True) self.setWindowFlags(Qt.FramelessWindowHint) self.setStyleSheet(""" QWidget { background-color:#99d9ea; /*background-image: url(:/background.png);*/ outline: 1px solid #0057ff; } QLabel { color:#202020; font-size:13px; font-family:Century; } QComboBox { color:#202020; font-size:13px; font-family:Century Schoolbook; } QComboBox { border: none; padding: 1px 18px 1px 3px; } QComboBox:editable { background: white; } QComboBox:!editable, QComboBox::drop-down:editable { background: #62c7e0; } QComboBox:!editable:hover, QComboBox::drop-down:editable:hover { background: #c7eaf3; } QComboBox:!editable:pressed, QComboBox::drop-down:editable:pressed { background: #35b6d7; } QComboBox:on { padding-top: 3px; padding-left: 4px; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 16px; border: none; } QComboBox::down-arrow { image: url(:/downarrow.png); } QComboBox::down-arrow:on { image: url(:/uparrow.png); } QGroupBox { color:#202020; font-size:12px; font-family:Century Schoolbook; border: 1px solid gray; margin-top: 15px; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; left:5px; top:3px; } QCheckBox { color:#202020; spacing: 5px; font-size:12px; font-family:Century Schoolbook; } QScrollBar:horizontal { background-color:#99d9ea; border: none; height: 15px; margin: 0px 20px 0 20px; } QScrollBar::handle:horizontal { background: #61b9e1; min-width: 20px; } QScrollBar::add-line:horizontal { image: url(:/rightarrow.png); border: none; background: #7ecfe4; width: 20px; subcontrol-position: right; subcontrol-origin: margin; } QScrollBar::sub-line:horizontal { image: url(:/leftarrow.png); border: none; background: #7ecfe4; width: 20px; subcontrol-position: left; subcontrol-origin: margin; } QScrollBar:vertical { background-color:#99d9ea; border: none; width: 15px; margin: 20px 0px 20px 0px; } QScrollBar::handle::vertical { background: #61b9e1; min-height: 20px; } QScrollBar::add-line::vertical { image: url(:/downarrow.png); border: none; background: #7ecfe4; height: 20px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line::vertical { image: url(:/uparrow.png); border: none; background: #7ecfe4; height: 20px; subcontrol-position: top; subcontrol-origin: margin; } QTableView { background-color: white; /*selection-background-color: #FF92BB;*/ border: 1px solid #eeeeee; color: #2f2f2f; } QTableView::focus { /*border: 1px solid #2a7fff;*/ } QTableView QTableCornerButton::section { border: none; border-right: 1px solid #eeeeee; border-bottom: 1px solid #eeeeee; background-color: #8ae6d2; } QTableView QWidget { background-color: white; } QTableView::item:focus { border: 1px red; background-color: transparent; color: #2f2f2f; } QHeaderView::section { border: none; border-right: 1px solid #eeeeee; border-bottom: 1px solid #eeeeee; padding-left: 2px; padding-right: 2px; color: #444444; background-color: #8ae6d2; } QTextEdit { background-color:white; color:#2f2f2f; border: 1px solid white; } QTextEdit::focus { border: 1px solid #2a7fff; } QPushButton { background-color:#30a7b8; border:none; color:#ffffff; font-size:14px; font-family:Century Schoolbook; } QPushButton:hover { background-color:#51c0d1; } QPushButton:pressed { background-color:#3a9ecc; } QMenuBar { color: #2f2f2f; } QMenuBar::item { background-color: transparent; margin: 8px 0px 0px 0px; padding: 1px 8px 1px 8px; height: 15px; } QMenuBar::item:selected { background: #51c0d1; } QMenuBar::item:pressed { } QMenu { color: #2f2f2f; } QMenu { margin: 2px; } QMenu::item { padding: 2px 25px 2px 21px; border: 1px solid transparent; } QMenu::item:selected { background: #51c0d1; } QMenu::icon { background: transparent; border: 2px inset transparent; } QDockWidget { font-size:13px; font-family:Century; color: #202020; titlebar-close-icon: none; titlebar-normal-icon: none; } QDockWidget::title { margin: 0; padding: 2px; subcontrol-origin: content; subcontrol-position: right top; text-align: left; background: #67baed; } QDockWidget::float-button { max-width: 12px; max-height: 12px; background-color:transparent; border:none; image: url(:/restore_inactive.png); } QDockWidget::float-button:hover { background-color:#227582; image: url(:/restore_active.png); } QDockWidget::float-button:pressed { padding: 0; background-color:#14464e; image: url(:/restore_active.png); } QDockWidget::close-button { max-width: 12px; max-height: 12px; background-color:transparent; border:none; image: url(:/close_inactive.png); } QDockWidget::close-button:hover { background-color:#ea5e00; image: url(:/close_active.png); } QDockWidget::close-button:pressed { background-color:#994005; image: url(:/close_active.png); padding: 0; } """) self.dockWidgetContents.setStyleSheet(""" QPushButton { min-height:23px; } """) self.dockWidget_QuickSend.setStyleSheet(""" QPushButton { background-color:#27b798; font-family:Consolas; font-size:12px; min-width:46px; } QPushButton:hover { background-color:#3bd5b4; } QPushButton:pressed { background-color:#1d8770; } """) self.dockWidgetContents_2.setStyleSheet(""" QPushButton { min-height:23px; min-width:50px; } """) w = self.frameGeometry().width() self._minBtn = QPushButton(self) self._minBtn.setGeometry(w - 103, 0, 28, 24) self._minBtn.clicked.connect(self.onMinimize) self._minBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/minimize_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/minimize_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/minimize_active.png); } """) self._maxBtn = QPushButton(self) self._maxBtn.setGeometry(w - 74, 0, 28, 24) self._maxBtn.clicked.connect(self.onMaximize) self.setMaximizeButton("maximize") self._closeBtn = QPushButton(self) self._closeBtn.setGeometry(w - 45, 0, 36, 24) self._closeBtn.clicked.connect(self.onExit) self._closeBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/close_inactive.png); } QPushButton:hover { background-color:#ea5e00; image: url(:/close_active.png); } QPushButton:pressed { background-color:#994005; image: url(:/close_active.png); } """) def resizeEvent(self, event): w = event.size().width() self._minBtn.move(w - 103, 0) self._maxBtn.move(w - 74, 0) self._closeBtn.move(w - 45, 0) def onMinimize(self): self.showMinimized() def isMaximized(self): return ((self.windowState() == Qt.WindowMaximized)) def onMaximize(self): if self.isMaximized(): self.showNormal() self.setMaximizeButton("maximize") else: self.showMaximized() self.setMaximizeButton("restore") def setMaximizeButton(self, style): if "maximize" == style: self._maxBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/maximize_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/maximize_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/maximize_active.png); } """) elif "restore" == style: self._maxBtn.setStyleSheet(""" QPushButton { background-color:transparent; border:none; outline: none; image: url(:/restore_inactive.png); } QPushButton:hover { background-color:#227582; image: url(:/restore_active.png); } QPushButton:pressed { background-color:#14464e; image: url(:/restore_active.png); } """) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._isDragging = True self._dragPos = event.globalPos() - self.pos() event.accept() def mouseMoveEvent(self, event): if event.buttons( ) and Qt.LeftButton and self._isDragging and not self.isMaximized(): self.move(event.globalPos() - self._dragPos) event.accept() def mouseReleaseEvent(self, event): self._isDragging = False event.accept() def SaveSettings(self): root = ET.Element("MyTerm") GUISettings = ET.SubElement(root, "GUISettings") PortCfg = ET.SubElement(GUISettings, "PortConfig") ET.SubElement(PortCfg, "port").text = self.cmbPort.currentText() ET.SubElement(PortCfg, "baudrate").text = self.cmbBaudRate.currentText() ET.SubElement(PortCfg, "databits").text = self.cmbDataBits.currentText() ET.SubElement(PortCfg, "parity").text = self.cmbParity.currentText() ET.SubElement(PortCfg, "stopbits").text = self.cmbStopBits.currentText() ET.SubElement( PortCfg, "rtscts").text = self.chkRTSCTS.isChecked() and "on" or "off" ET.SubElement( PortCfg, "xonxoff").text = self.chkXonXoff.isChecked() and "on" or "off" View = ET.SubElement(GUISettings, "View") ET.SubElement( View, "LocalEcho" ).text = self.actionLocal_Echo.isChecked() and "on" or "off" ET.SubElement( View, "ReceiveView").text = self._viewGroup.checkedAction().text() with open(get_config_path('settings.xml'), 'w') as f: f.write('<?xml version="1.0" encoding="UTF-8"?>\n') f.write( ET.tostring(root, encoding='utf-8', pretty_print=True).decode("utf-8")) def LoadSettings(self): if os.path.isfile(get_config_path("settings.xml")): with open(get_config_path("settings.xml"), 'r') as f: tree = safeET.parse(f) port = tree.findtext('GUISettings/PortConfig/port', default='') if port != '': self.cmbPort.setCurrentText(port) baudrate = tree.findtext('GUISettings/PortConfig/baudrate', default='38400') if baudrate != '': self.cmbBaudRate.setCurrentText(baudrate) databits = tree.findtext('GUISettings/PortConfig/databits', default='8') id = self.cmbDataBits.findText(databits) if id >= 0: self.cmbDataBits.setCurrentIndex(id) parity = tree.findtext('GUISettings/PortConfig/parity', default='None') id = self.cmbParity.findText(parity) if id >= 0: self.cmbParity.setCurrentIndex(id) stopbits = tree.findtext('GUISettings/PortConfig/stopbits', default='1') id = self.cmbStopBits.findText(stopbits) if id >= 0: self.cmbStopBits.setCurrentIndex(id) rtscts = tree.findtext('GUISettings/PortConfig/rtscts', default='off') if 'on' == rtscts: self.chkRTSCTS.setChecked(True) else: self.chkRTSCTS.setChecked(False) xonxoff = tree.findtext('GUISettings/PortConfig/xonxoff', default='off') if 'on' == xonxoff: self.chkXonXoff.setChecked(True) else: self.chkXonXoff.setChecked(False) LocalEcho = tree.findtext('GUISettings/View/LocalEcho', default='off') if 'on' == LocalEcho: self.actionLocal_Echo.setChecked(True) self._localEcho = True else: self.actionLocal_Echo.setChecked(False) self._localEcho = False ReceiveView = tree.findtext('GUISettings/View/ReceiveView', default='HEX(UPPERCASE)') if 'Ascii' in ReceiveView: self.actionAscii.setChecked(True) elif 'lowercase' in ReceiveView: self.actionHex_lowercase.setChecked(True) elif 'UPPERCASE' in ReceiveView: self.actionHEX_UPPERCASE.setChecked(True) def closeEvent(self, event): self.saveLayout() self.saveCSV() self.SaveSettings() event.accept() def tableClick(self, row): self.sendTableRow(row) def initQuickSend(self): #self.quickSendTable.horizontalHeader().setDefaultSectionSize(40) #self.quickSendTable.horizontalHeader().setMinimumSectionSize(25) self.quickSendTable.setRowCount(50) self.quickSendTable.setColumnCount(20) for row in range(50): item = QPushButton(str("Send")) item.clicked.connect(self._signalMap.map) self._signalMap.setMapping(item, row) self.quickSendTable.setCellWidget(row, 0, item) self.quickSendTable.setRowHeight(row, 20) if os.path.isfile(get_config_path('QckSndBckup.csv')): self.loadCSV(get_config_path('QckSndBckup.csv')) self.quickSendTable.resizeColumnsToContents() def openCSV(self): fileName = QFileDialog.getOpenFileName(self, "Select a file", os.getcwd(), "CSV Files (*.csv)") if fileName: self.loadCSV(fileName, notifyExcept=True) def saveCSV(self): # scan table rows = self.quickSendTable.rowCount() cols = self.quickSendTable.columnCount() tmp_data = [[ self.quickSendTable.item(row, col) is not None and self.quickSendTable.item(row, col).text() or '' for col in range(1, cols) ] for row in range(rows)] data = [] # delete trailing blanks for row in tmp_data: for idx, d in enumerate(row[::-1]): if '' != d: break new_row = row[:len(row) - idx] data.append(new_row) #import pprint #pprint.pprint(data, width=120, compact=True) # write to file with open(get_config_path('QckSndBckup.csv'), 'w') as csvfile: csvwriter = csv.writer(csvfile, delimiter=',', lineterminator='\n') csvwriter.writerows(data) def loadCSV(self, path, notifyExcept=False): data = [] set_rows = 0 set_cols = 0 try: with open(path) as csvfile: csvData = csv.reader(csvfile) for row in csvData: data.append(row) set_rows = set_rows + 1 if len(row) > set_cols: set_cols = len(row) except IOError as e: print("({})".format(e)) if notifyExcept: QMessageBox.critical(self, "Open failed", str(e), QMessageBox.Close) return rows = self.quickSendTable.rowCount() cols = self.quickSendTable.columnCount() # clear table for col in range(cols): for row in range(rows): self.quickSendTable.setItem(row, col, QTableWidgetItem("")) self._csvFilePath = path if (cols - 1) < set_cols: # first colume is used by the "send" buttons. cols = set_cols + 10 self.quickSendTable.setColumnCount(cols) if rows < set_rows: rows = set_rows + 20 self.quickSendTable.setRowCount(rows) for row, rowdat in enumerate(data): if len(rowdat) > 0: for col, cell in enumerate(rowdat, 1): self.quickSendTable.setItem(row, col, QTableWidgetItem(str(cell))) self.quickSendTable.resizeColumnsToContents() #self.quickSendTable.resizeRowsToContents() def sendTableRow(self, row): cols = self.quickSendTable.columnCount() try: data = [ '0' + self.quickSendTable.item(row, col).text() for col in range(1, cols) if self.quickSendTable.item(row, col) is not None and self.quickSendTable.item(row, col).text() is not '' ] except: print("Exception in get table data(row = %d)" % (row + 1)) else: tmp = [d[-2] + d[-1] for d in data if len(d) >= 2] for t in tmp: if not is_hex(t): QMessageBox.critical(self, "Error", "'%s' is not hexadecimal." % (t), QMessageBox.Close) return h = [int(t, 16) for t in tmp] self.transmitHex(h) def sendHex(self): hexStr = self.txtEdtInput.toPlainText() hexStr = ''.join(hexStr.split(" ")) hexarray = [] for i in range(0, len(hexStr), 2): hexarray.append(int(hexStr[i:i + 2], 16)) self.transmitHex(hexarray) def readerExcept(self, e): self.closePort() QMessageBox.critical(self, "Read failed", str(e), QMessageBox.Close) def timestamp(self): return datetime.datetime.now().time().isoformat()[:-3] def receive(self, data): self.appendOutputText("\n%s R<-:%s" % (self.timestamp(), data)) def appendOutputText(self, data, color=Qt.black): # the qEditText's "append" methon will add a unnecessary newline. # self.txtEdtOutput.append(data.decode('utf-8')) tc = self.txtEdtOutput.textColor() self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End) self.txtEdtOutput.setTextColor(QtGui.QColor(color)) self.txtEdtOutput.insertPlainText(data) self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End) self.txtEdtOutput.setTextColor(tc) def transmitHex(self, hexarray): if len(hexarray) > 0: byteArray = bytearray(hexarray) if self.serialport.isOpen(): try: self.serialport.write(byteArray) except serial.SerialException as e: print("Exception in transmitHex(%s)" % repr(hexarray)) QMessageBox.critical(self, "Exception in transmitHex", str(e), QMessageBox.Close) else: # self.txCount += len( b ) # self.frame.statusbar.SetStatusText('Tx:%d' % self.txCount, 2) text = ''.join(['%02X ' % i for i in hexarray]) self.appendOutputText( "\n%s T->:%s" % (self.timestamp(), text), Qt.blue) def GetPort(self): return self.cmbPort.currentText() def GetDataBits(self): s = self.cmbDataBits.currentText() if s == '5': return serial.FIVEBITS elif s == '6': return serial.SIXBITS elif s == '7': return serial.SEVENBITS elif s == '8': return serial.EIGHTBITS def GetParity(self): s = self.cmbParity.currentText() if s == 'None': return serial.PARITY_NONE elif s == 'Even': return serial.PARITY_EVEN elif s == 'Odd': return serial.PARITY_ODD elif s == 'Mark': return serial.PARITY_MARK elif s == 'Space': return serial.PARITY_SPACE def GetStopBits(self): s = self.cmbStopBits.currentText() if s == '1': return serial.STOPBITS_ONE elif s == '1.5': return serial.STOPBITS_ONE_POINT_FIVE elif s == '2': return serial.STOPBITS_TWO def openPort(self): if self.serialport.isOpen(): return _port = self.GetPort() if '' == _port: QMessageBox.information(self, "Invalid parameters", "Port is empty.") return _baudrate = self.cmbBaudRate.currentText() if '' == _baudrate: QMessageBox.information(self, "Invalid parameters", "Baudrate is empty.") return self.serialport.port = _port self.serialport.baudrate = _baudrate self.serialport.bytesize = self.GetDataBits() self.serialport.stopbits = self.GetStopBits() self.serialport.parity = self.GetParity() self.serialport.rtscts = self.chkRTSCTS.isChecked() self.serialport.xonxoff = self.chkXonXoff.isChecked() # self.serialport.timeout = THREAD_TIMEOUT # self.serialport.writeTimeout = SERIAL_WRITE_TIMEOUT try: self.serialport.open() except serial.SerialException as e: QMessageBox.critical(self, "Could not open serial port", str(e), QMessageBox.Close) else: self._start_reader() self.setWindowTitle("%s on %s [%s, %s%s%s%s%s]" % ( appInfo.title, self.serialport.portstr, self.serialport.baudrate, self.serialport.bytesize, self.serialport.parity, self.serialport.stopbits, self.serialport.rtscts and ' RTS/CTS' or '', self.serialport.xonxoff and ' Xon/Xoff' or '', )) pal = self.btnOpen.palette() pal.setColor(QtGui.QPalette.Button, QtGui.QColor(0, 0xff, 0x7f)) self.btnOpen.setAutoFillBackground(True) self.btnOpen.setPalette(pal) self.btnOpen.setText('Close') self.btnOpen.update() def closePort(self): if self.serialport.isOpen(): self._stop_reader() self.serialport.close() self.setWindowTitle(appInfo.title) pal = self.btnOpen.style().standardPalette() self.btnOpen.setAutoFillBackground(True) self.btnOpen.setPalette(pal) self.btnOpen.setText('Open') self.btnOpen.update() def _start_reader(self): """Start reader thread""" self.receiver_thread.start() def _stop_reader(self): """Stop reader thread only, wait for clean exit of thread""" self.receiver_thread.join() def onTogglePrtCfgPnl(self): if self.actionPort_Config_Panel.isChecked(): self.dockWidget_PortConfig.show() else: self.dockWidget_PortConfig.hide() def onToggleQckSndPnl(self): if self.actionQuick_Send_Panel.isChecked(): self.dockWidget_QuickSend.show() else: self.dockWidget_QuickSend.hide() def onToggleHexPnl(self): if self.actionSend_Hex_Panel.isChecked(): self.dockWidget_SendHex.show() else: self.dockWidget_SendHex.hide() def onVisiblePrtCfgPnl(self, visible): self.actionPort_Config_Panel.setChecked(visible) def onVisibleQckSndPnl(self, visible): self.actionQuick_Send_Panel.setChecked(visible) def onVisibleHexPnl(self, visible): self.actionSend_Hex_Panel.setChecked(visible) def onLocalEcho(self): self._localEcho = self.actionLocal_Echo.isChecked() def onAlwaysOnTop(self): if self.actionAlways_On_Top.isChecked(): style = self.windowFlags() self.setWindowFlags(style | Qt.WindowStaysOnTopHint) self.show() else: style = self.windowFlags() self.setWindowFlags(style & ~Qt.WindowStaysOnTopHint) self.show() def onOpen(self): if self.serialport.isOpen(): self.closePort() else: self.openPort() def onClear(self): self.txtEdtOutput.clear() def onSaveLog(self): fileName = QFileDialog.getSaveFileName( self, "Save as", os.getcwd(), "Log files (*.log);;Text files (*.txt);;All files (*.*)") if fileName: import codecs f = codecs.open(fileName, 'w', 'utf-8') f.write(self.txtEdtOutput.toPlainText()) f.close() def moveScreenCenter(self): w = self.frameGeometry().width() h = self.frameGeometry().height() desktop = QDesktopWidget() screenW = desktop.screen().width() screenH = desktop.screen().height() self.setGeometry((screenW - w) / 2, (screenH - h) / 2, w, h) def onEnumPorts(self): for p in enum_ports(): self.cmbPort.addItem(p) # self.cmbPort.update() def onAbout(self): q = QWidget() icon = QtGui.QIcon(":/icon.ico") q.setWindowIcon(icon) QMessageBox.about(q, "About MyTerm", appInfo.aboutme) def onAboutQt(self): QMessageBox.aboutQt(None) def onExit(self): if self.serialport.isOpen(): self.closePort() self.close() def restoreLayout(self): if os.path.isfile(get_config_path("layout.dat")): try: f = open(get_config_path("layout.dat"), 'rb') geometry, state = pickle.load(f) self.restoreGeometry(geometry) self.restoreState(state) except Exception as e: print("Exception on restoreLayout, {}".format(e)) else: try: f = QFile(':/default_layout.dat') f.open(QIODevice.ReadOnly) geometry, state = pickle.loads(f.readAll()) self.restoreGeometry(geometry) self.restoreState(state) except Exception as e: print("Exception on restoreLayout, {}".format(e)) def saveLayout(self): with open(get_config_path("layout.dat"), 'wb') as f: pickle.dump((self.saveGeometry(), self.saveState()), f) def syncMenu(self): self.actionPort_Config_Panel.setChecked( not self.dockWidget_PortConfig.isHidden()) self.actionQuick_Send_Panel.setChecked( not self.dockWidget_QuickSend.isHidden()) self.actionSend_Hex_Panel.setChecked( not self.dockWidget_SendHex.isHidden()) def onViewChanged(self): checked = self._viewGroup.checkedAction() if checked is None: self.actionHEX_UPPERCASE.setChecked(True) self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE) else: if 'Ascii' in checked.text(): self.receiver_thread.setViewMode(VIEWMODE_ASCII) elif 'lowercase' in checked.text(): self.receiver_thread.setViewMode(VIEWMODE_HEX_LOWERCASE) elif 'UPPERCASE' in checked.text(): self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE)
class MapWidget(Ui_CanvasWidget, QMainWindow): def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) self.snapping = True icon = roam_style.iconsize() self.projecttoolbar.setIconSize(QSize(icon, icon)) self.current_form = None self.last_form = None self.firstshow = True self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.bridge = QgsLayerTreeMapCanvasBridge( QgsProject.instance().layerTreeRoot(), self.canvas) self.bridge.setAutoSetupOnFirstLayer(False) QgsProject.instance().writeProject.connect(self.bridge.writeProject) QgsProject.instance().readProject.connect(self.bridge.readProject) # self.canvas.setInteractive(False) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) self.snappingutils = SnappingUtils(self.canvas, self) self.canvas.setSnappingUtils(self.snappingutils) QgsProject.instance().readProject.connect( self.snappingutils.readConfigFromProject) if hasattr(self.canvas, 'setParallelRenderingEnabled'): threadcount = QThread.idealThreadCount() threadcount = 2 if threadcount > 2 else 1 QgsApplication.setMaxThreads(threadcount) self.canvas.setParallelRenderingEnabled(True) pal = QgsPalLabeling() self.canvas.mapRenderer().setLabelingEngine(pal) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(":/icons/gps", self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) if roam.config.settings.get('north_arrow', False): self.northarrow = NorthArrow(":/icons/north", self.canvas) self.northarrow.setPos(10, 10) self.canvas.scene().addItem(self.northarrow) smallmode = roam.config.settings.get("smallmode", False) self.projecttoolbar.setSmallMode(smallmode) self.scalebar_enabled = roam.config.settings.get('scale_bar', False) if self.scalebar_enabled: self.scalebar = ScaleBarItem(self.canvas) self.canvas.scene().addItem(self.scalebar) self.projecttoolbar.setContextMenuPolicy(Qt.CustomContextMenu) gpsspacewidget = QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget( self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction( self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.marker = GPSMarker(self.canvas) self.marker.hide() self.currentfeatureband = CurrentSelection(self.canvas) self.currentfeatureband.setIconSize(30) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(186, 93, 212, 50)) self.currentfeatureband.setOutlineColour(QColor(186, 93, 212)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(165, 111, 212, 75)) self.gpsband.setWidth(5) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.openfeatureform.connect(self.feature_form_loaded) RoamEvents.sync_complete.connect(self.refresh_map) RoamEvents.snappingChanged.connect(self.snapping_changed) self.snappingbutton = QToolButton() self.snappingbutton.setText("Snapping: On") self.snappingbutton.setAutoRaise(True) self.snappingbutton.pressed.connect(self.toggle_snapping) spacer = QWidget() spacer2 = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.scalewidget = QgsScaleComboBox() self.scalebutton = QToolButton() self.scalebutton.setAutoRaise(True) self.scalebutton.setMaximumHeight(self.statusbar.height()) self.scalebutton.pressed.connect(self.selectscale) self.scalebutton.setText("Scale") self.scalelist = BigList(parent=self.canvas, centeronparent=True, showsave=False) self.scalelist.hide() self.scalelist.setlabel("Map Scale") self.scalelist.setmodel(self.scalewidget.model()) self.scalelist.closewidget.connect(self.scalelist.close) self.scalelist.itemselected.connect(self.update_scale_from_item) self.scalelist.itemselected.connect(self.scalelist.close) self.positionlabel = QLabel('') self.gpslabel = QLabel("GPS: Not active") self.gpslabelposition = QLabel("") self.statusbar.addWidget(self.snappingbutton) self.statusbar.addWidget(spacer2) self.statusbar.addWidget(self.gpslabel) self.statusbar.addWidget(self.gpslabelposition) self.statusbar.addPermanentWidget(self.scalebutton) self.canvas.extentsChanged.connect(self.updatestatuslabel) self.canvas.scaleChanged.connect(self.updatestatuslabel) GPS.gpsposition.connect(self.update_gps_label) GPS.gpsdisconnected.connect(self.gps_disconnected) self.connectButtons() def clear_plugins(self): toolbars = self.findChildren(QToolBar) for toolbar in toolbars: if toolbar.property("plugin_toolbar"): toolbar.unload() self.removeToolBar(toolbar) toolbar.deleteLater() def add_plugins(self, pluginnames): for name in pluginnames: # Get the plugin try: plugin_mod = plugins.loaded_plugins[name] except KeyError: continue if not hasattr(plugin_mod, 'toolbars'): roam.utils.warning( "No toolbars() function found in {}".format(name)) continue toolbars = plugin_mod.toolbars() self.load_plugin_toolbars(toolbars) def load_plugin_toolbars(self, toolbars): for ToolBarClass in toolbars: toolbar = ToolBarClass(plugins.api, self) self.addToolBar(Qt.BottomToolBarArea, toolbar) toolbar.setProperty("plugin_toolbar", True) def snapping_changed(self, snapping): """ Called when the snapping settings have changed. Updates the label in the status bar. :param snapping: """ if snapping: self.snappingbutton.setText("Snapping: On") else: self.snappingbutton.setText("Snapping: Off") def toggle_snapping(self): """ Toggle snapping on or off. """ self.snapping = not self.snapping try: self.canvas.mapTool().toggle_snapping() except AttributeError: pass RoamEvents.snappingChanged.emit(self.snapping) def selectscale(self): """ Show the select scale widget. :return: """ self.scalelist.show() def update_scale_from_item(self, index): """ Update the canvas scale from the selected scale item. :param index: The index of the selected item. """ scale, _ = self.scalewidget.toDouble(index.data(Qt.DisplayRole)) self.canvas.zoomScale(1.0 / scale) def update_gps_label(self, position, gpsinfo): """ Update the GPS label in the status bar with the GPS status. :param position: The current GPS position. :param gpsinfo: The current extra GPS information. """ self.gpslabel.setText( "GPS: PDOP <b>{0:.2f}</b> HDOP <b>{1:.2f}</b> VDOP <b>{2:.2f}</b>" .format(gpsinfo.pdop, gpsinfo.hdop, gpsinfo.vdop)) places = roam.config.settings.get("gpsplaces", 8) self.gpslabelposition.setText( "X <b>{x:.{places}f}</b> Y <b>{y:.{places}f}</b>".format( x=position.x(), y=position.y(), places=places)) def gps_disconnected(self): """ Called when the GPS is disconnected. Updates the label in the status bar with the message. :return: """ self.gpslabel.setText("GPS Not Active") self.gpslabelposition.setText("") def zoom_to_feature(self, feature): box = feature.geometry().boundingBox() xmin, xmax, ymin, ymax = box.xMinimum(), box.xMaximum(), box.yMinimum( ), box.yMaximum() xmin -= 5 xmax += 5 ymin -= 5 ymax += 5 box = QgsRectangle(xmin, ymin, xmax, ymax) self.canvas.setExtent(box) self.canvas.refresh() def updatestatuslabel(self, *args): """ Update the status bar labels when the information has changed. """ extent = self.canvas.extent() self.positionlabel.setText("Map Center: {}".format( extent.center().toString())) scale = 1.0 / self.canvas.scale() scale = self.scalewidget.toString(scale) self.scalebutton.setText(scale) def refresh_map(self): """ Refresh the map """ self.canvas.refresh() def updatescale(self): """ Update the scale of the map with the current scale from the scale widget :return: """ self.canvas.zoomScale(1.0 / self.scalewidget.scale()) def init_qgisproject(self, doc): """ Called when the project file is read for the firs time. :param doc: The XML doc. :return: The current canvas CRS :note: This method is old and needs to be refactored into something else. """ return self.canvas.mapSettings().destinationCrs() def showEvent(self, *args, **kwargs): """ Handle the show event of the of the map widget. We have to do a little hack here to make the QGIS map refresh. """ if QGis.QGIS_VERSION_INT == 20200 and self.firstshow: self.canvas.refresh() self.canvas.repaint() self.firstshow = False def feature_form_loaded(self, form, feature, *args): """ Called when the feature form is loaded. :param form: The Form object. Holds a reference to the forms layer. :param feature: The current capture feature """ self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer) def highlight_selection(self, results): """ Highlight the selection on the canvas. This updates all selected objects based on the result set. :param results: A dict-of-list of layer-features. """ self.clear_selection() for layer, features in results.iteritems(): band = self.selectionbands[layer] band.setColor(QColor(255, 0, 0)) band.setIconSize(25) band.setWidth(5) band.setBrushStyle(Qt.NoBrush) band.reset(layer.geometryType()) band.setZValue(self.currentfeatureband.zValue() - 1) for feature in features: band.addGeometry(feature.geometry(), layer) self.canvas.update() def highlight_active_selection(self, layer, feature, features): """ Update the current active selected feature. :param layer: The layer of the active feature. :param feature: The active feature. :param features: The other features in the set to show as non active selection. :return: """ self.clear_selection() self.highlight_selection({layer: features}) self.currentfeatureband.setToGeometry(feature.geometry(), layer) self.canvas.update() def clear_selection(self): """ Clear the selection from the canvas. Resets all selection rubbber bands. :return: """ # Clear the main selection rubber band self.canvas.scene().update() self.currentfeatureband.reset() # Clear the rest for band in self.selectionbands.itervalues(): band.reset() self.canvas.update() self.editfeaturestack = [] def queue_feature_for_edit(self, form, feature): """ Push a feature on the edit stack so the feature can have the geometry edited. :note: This is a big hack and I don't like it! :param form: The form for the current feature :param feature: The active feature. """ def trigger_default_action(): for action in self.projecttoolbar.actions(): if action.property('dataentry') and action.isdefault: action.trigger() self.canvas.mapTool().setEditMode(True, feature.geometry()) break self.editfeaturestack.append((form, feature)) self.save_current_form() self.load_form(form) trigger_default_action() def save_current_form(self): self.last_form = self.current_form def restore_last_form(self): self.load_form(self.last_form) def clear_temp_objects(self): """ Clear all temp objects from the canvas. :return: """ def clear_tool_band(): """ Clear the rubber band of the active tool if it has one """ tool = self.canvas.mapTool() try: tool.clearBand() except AttributeError: # No clearBand method found, but that's cool. pass self.currentfeatureband.reset() clear_tool_band() def settings_updated(self, settings): """ Called when the settings have been updated in the Roam config. :param settings: A dict of the settings. """ self.actionGPS.updateGPSPort() gpslogging = settings.get('gpslogging', True) if self.gpslogging: self.gpslogging.logging = gpslogging def set_gps(self, gps, logging): """ Set the GPS for the map widget. Connects GPS signals """ self.gps = gps self.gpslogging = logging self.gps.gpsposition.connect(self.gps_update_canvas) self.gps.firstfix.connect(self.gps_first_fix) self.gps.gpsdisconnected.connect(self.gps_disconnected) def gps_update_canvas(self, position, gpsinfo): """ Updates the map canvas based on the GPS position. By default if the GPS is outside the canvas extent the canvas will move to center on the GPS. Can be turned off in settings. :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ # Recenter map if we go outside of the 95% of the area if self.gpslogging.logging: self.gpsband.addPoint(position) self.gpsband.show() if roam.config.settings.get('gpscenter', True): if not self.lastgpsposition == position: self.lastposition = position rect = QgsRectangle(position, position) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(position): self.zoom_to_location(position) self.marker.show() self.marker.setCenter(position, gpsinfo) def gps_first_fix(self, postion, gpsinfo): """ Called the first time the GPS gets a fix. If set this will zoom to the GPS after the first fix :param postion: The current GPS position. :param gpsinfo: The extra GPS information """ zoomtolocation = roam.config.settings.get('gpszoomonfix', True) if zoomtolocation: self.canvas.zoomScale(1000) self.zoom_to_location(postion) def zoom_to_location(self, position): """ Zoom to ta given position on the map.. """ rect = QgsRectangle(position, position) self.canvas.setExtent(rect) self.canvas.refresh() def gps_disconnected(self): """ Called when the GPS is disconnected """ self.marker.hide() def select_data_entry(self): """ Open the form selection widget to allow the user to pick the active capture form. """ def showformerror(form): pass def actions(): for form in self.project.forms: if not self.form_valid_for_capture(form): continue action = form.createuiaction() valid, failreasons = form.valid if not valid: roam.utils.warning("Form {} failed to load".format( form.label)) roam.utils.warning("Reasons {}".format(failreasons)) action.triggered.connect(partial(showformerror, form)) else: action.triggered.connect(partial(self.load_form, form)) yield action formpicker = PickActionDialog(msg="Select data entry form", wrap=5) formpicker.addactions(actions()) formpicker.exec_() def project_loaded(self, project): """ Called when the project is loaded. Main entry point for a loade project. :param project: The Roam project that has been loaded. """ self.project = project self.actionPan.trigger() firstform = self.first_capture_form() if firstform: self.load_form(firstform) self.dataentryselection.setVisible(True) else: self.dataentryselection.setVisible(False) # Enable the raster layers button only if the project contains a raster layer. layers = QgsMapLayerRegistry.instance().mapLayers().values() hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers) self.actionRaster.setEnabled(hasrasters) self.defaultextent = self.canvas.extent() roam.utils.info("Extent: {}".format(self.defaultextent.toString())) self.infoTool.selectionlayers = project.selectlayersmapping() self.canvas.refresh() projectscales, _ = QgsProject.instance().readBoolEntry( "Scales", "/useProjectScales") if projectscales: projectscales, _ = QgsProject.instance().readListEntry( "Scales", "/ScalesList") self.scalewidget.updateScales(projectscales) else: scales = [ "1:50000", "1:25000", "1:10000", "1:5000", "1:2500", "1:1000", "1:500", "1:250", "1:200", "1:100" ] scales = roam.config.settings.get('scales', scales) self.scalewidget.updateScales(scales) if self.scalebar_enabled: self.scalebar.update() self.actionPan.toggle() self.clear_plugins() self.add_plugins(project.enabled_plugins) def setMapTool(self, tool, *args): """ Set the active map tool in the canvas. :param tool: The QgsMapTool to set. """ if tool == self.canvas.mapTool(): return if hasattr(tool, "setSnapping"): tool.setSnapping(self.snapping) self.canvas.setMapTool(tool) def connectButtons(self): """ Connect the default buttons in the interface. Zoom, pan, etc """ def connectAction(action, tool): action.toggled.connect(partial(self.setMapTool, tool)) def cursor(name): pix = QPixmap(name) pix = pix.scaled(QSize(24, 24)) return QCursor(pix) self.zoomInTool = QgsMapToolZoom(self.canvas, False) self.zoomOutTool = QgsMapToolZoom(self.canvas, True) self.panTool = PanTool(self.canvas) self.infoTool = InfoTool(self.canvas) self.infoTool.setAction(self.actionInfo) self.zoomInTool.setAction(self.actionZoom_In) self.zoomOutTool.setAction(self.actionZoom_Out) self.panTool.setAction(self.actionPan) connectAction(self.actionZoom_In, self.zoomInTool) connectAction(self.actionZoom_Out, self.zoomOutTool) connectAction(self.actionPan, self.panTool) connectAction(self.actionInfo, self.infoTool) self.zoomInTool.setCursor(cursor(':/icons/in')) self.zoomOutTool.setCursor(cursor(':/icons/out')) self.infoTool.setCursor(cursor(':/icons/select')) self.actionRaster.triggered.connect(self.toggleRasterLayers) self.actionHome.triggered.connect(self.homeview) def homeview(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ self.canvas.setExtent(self.defaultextent) self.canvas.refresh() def form_valid_for_capture(self, form): """ Check if the given form is valid for capture. :param form: The form to check. :return: True if valid form for capture """ return form.has_geometry and self.project.layer_can_capture( form.QGISLayer) def first_capture_form(self): """ Return the first valid form for capture. """ for form in self.project.forms: if self.form_valid_for_capture(form): return form def load_form(self, form): """ Load the given form so it's the active one for capture :param form: The form to load """ self.clearCaptureTools() self.dataentryselection.setIcon(QIcon(form.icon)) self.dataentryselection.setText(form.icontext) self.create_capture_buttons(form) self.current_form = form def create_capture_buttons(self, form): """ Create the capture buttons in the toolbar for the given form. :param form: The active form. """ layer = form.QGISLayer tool = form.getMaptool()(self.canvas, form.settings) for action in tool.actions: # Create the action here. if action.ismaptool: action.toggled.connect(partial(self.setMapTool, tool)) # Set the action as a data entry button so we can remove it later. action.setProperty("dataentry", True) self.editgroup.addAction(action) self.layerbuttons.append(action) self.projecttoolbar.insertAction(self.topspaceraction, action) action.setChecked(action.isdefault) if hasattr(tool, 'geometryComplete'): add = partial(self.add_new_feature, form) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(self.show_invalid_geometry_message) def show_invalid_geometry_message(self, message): RoamEvents.raisemessage("Invalid geometry capture", message, level=RoamEvents.CRITICAL) def add_new_feature(self, form, geometry): """ Add a new new feature to the given layer """ # TODO Extract into function. # NOTE This function is doing too much, acts as add and also edit. layer = form.QGISLayer if layer.geometryType() in [ QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon ]: geometry.convertToMultiType() try: form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass feature = form.new_feature(geometry=geometry) RoamEvents.load_feature_form(form, feature, editmode=False) def editfeaturegeometry(self, form, feature, newgeometry): # TODO Extract into function. layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() if not saved: map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() self.currentfeatureband.setToGeometry(feature.geometry(), layer) RoamEvents.editgeometry_complete.emit(form, feature) self.canvas.mapTool().setEditMode(False, None) self.restore_last_form() def clearCaptureTools(self): """ Clear the capture tools from the toolbar. :return: True if the capture button was active at the time of clearing. """ captureselected = False for action in self.projecttoolbar.actions(): if action.objectName() == "capture" and action.isChecked(): captureselected = True if action.property('dataentry'): self.projecttoolbar.removeAction(action) return captureselected def toggleRasterLayers(self): """ Toggle all raster layers on or off. """ # Freeze the canvas to save on UI refresh self.canvas.freeze() tree = QgsProject.instance().layerTreeRoot() for node in tree.findLayers(): if node.layer().type() == QgsMapLayer.RasterLayer: if node.isVisible() == Qt.Checked: state = Qt.Unchecked else: state = Qt.Checked node.setVisible(state) self.canvas.freeze(False) self.canvas.refresh() def cleanup(self): """ Clean up when the project has changed. :return: """ self.bridge.clear() self.gpsband.reset() self.gpsband.hide() self.clear_selection() self.clear_temp_objects() self.clearCaptureTools() self.canvas.freeze() self.canvas.clear() self.canvas.freeze(False) for action in self.layerbuttons: self.editgroup.removeAction(action)
class MainUI(QtGui.QMainWindow, main_window_class): connectionLostSignal = pyqtSignal(str, str) connectionInitiatedSignal = pyqtSignal(str) batteryUpdatedSignal = pyqtSignal(int, object, object) connectionDoneSignal = pyqtSignal(str) connectionFailedSignal = pyqtSignal(str, str) disconnectedSignal = pyqtSignal(str) linkQualitySignal = pyqtSignal(int) _input_device_error_signal = pyqtSignal(str) _input_discovery_signal = pyqtSignal(object) _log_error_signal = pyqtSignal(object, str) def __init__(self, *args): super(MainUI, self).__init__(*args) self.setupUi(self) ###################################################### ### By lxrocks ### 'Skinny Progress Bar' tweak for Yosemite ### Tweak progress bar - artistic I am not - so pick your own colors !!! ### Only apply to Yosemite ###################################################### import platform if platform.system() == 'Darwin': (Version, junk, machine) = platform.mac_ver() logger.info( "This is a MAC - checking if we can apply Progress Bar Stylesheet for Yosemite Skinny Bars " ) yosemite = (10, 10, 0) tVersion = tuple(map(int, (Version.split(".")))) if tVersion >= yosemite: logger.info("Found Yosemite:") tcss = """ QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: center; } QProgressBar::chunk { background-color: #05B8CC; } """ self.setStyleSheet(tcss) else: logger.info("Pre-Yosemite") ###################################################### self.cf = Crazyflie(ro_cache=sys.path[0] + "/cflib/cache", rw_cache=sys.path[1] + "/cache") cflib.crtp.init_drivers( enable_debug_driver=Config().get("enable_debug_driver")) zmq_params = ZMQParamAccess(self.cf) zmq_params.start() zmq_leds = ZMQLEDDriver(self.cf) zmq_leds.start() # Create the connection dialogue self.connectDialogue = ConnectDialogue() # Create and start the Input Reader self._statusbar_label = QLabel("No input-device found, insert one to" " fly.") self.statusBar().addWidget(self._statusbar_label) self.joystickReader = JoystickReader() self._active_device = "" #self.configGroup = QActionGroup(self._menu_mappings, exclusive=True) self._mux_group = QActionGroup(self._menu_inputdevice, exclusive=True) # TODO: Need to reload configs #ConfigManager().conf_needs_reload.add_callback(self._reload_configs) # Connections for the Connect Dialogue self.connectDialogue.requestConnectionSignal.connect(self.cf.open_link) self.cf.connection_failed.add_callback( self.connectionFailedSignal.emit) self.connectionFailedSignal.connect(self._connection_failed) self._input_device_error_signal.connect( self._display_input_device_error) self.joystickReader.device_error.add_callback( self._input_device_error_signal.emit) self._input_discovery_signal.connect(self.device_discovery) self.joystickReader.device_discovery.add_callback( self._input_discovery_signal.emit) # Connect UI signals self.menuItemConnect.triggered.connect(self._connect) self.logConfigAction.triggered.connect(self._show_connect_dialog) self.connectButton.clicked.connect(self._connect) self.quickConnectButton.clicked.connect(self._quick_connect) self.menuItemQuickConnect.triggered.connect(self._quick_connect) self.menuItemConfInputDevice.triggered.connect( self._show_input_device_config_dialog) self.menuItemExit.triggered.connect(self.closeAppRequest) self.batteryUpdatedSignal.connect(self._update_vbatt) self._menuitem_rescandevices.triggered.connect(self._rescan_devices) self._menuItem_openconfigfolder.triggered.connect( self._open_config_folder) self._auto_reconnect_enabled = Config().get("auto_reconnect") self.autoReconnectCheckBox.toggled.connect( self._auto_reconnect_changed) self.autoReconnectCheckBox.setChecked(Config().get("auto_reconnect")) self.joystickReader.input_updated.add_callback( self.cf.commander.send_setpoint) # Connection callbacks and signal wrappers for UI protection self.cf.connected.add_callback(self.connectionDoneSignal.emit) self.connectionDoneSignal.connect(self._connected) self.cf.disconnected.add_callback(self.disconnectedSignal.emit) self.disconnectedSignal.connect(lambda linkURI: self._update_ui_state( UIState.DISCONNECTED, linkURI)) self.cf.connection_lost.add_callback(self.connectionLostSignal.emit) self.connectionLostSignal.connect(self._connection_lost) self.cf.connection_requested.add_callback( self.connectionInitiatedSignal.emit) self.connectionInitiatedSignal.connect( lambda linkURI: self._update_ui_state(UIState.CONNECTING, linkURI)) self._log_error_signal.connect(self._logging_error) # Connect link quality feedback self.cf.link_quality_updated.add_callback(self.linkQualitySignal.emit) self.linkQualitySignal.connect( lambda percentage: self.linkQualityBar.setValue(percentage)) # Set UI state in disconnected buy default self._update_ui_state(UIState.DISCONNECTED) # Parse the log configuration files self.logConfigReader = LogConfigReader(self.cf) self._current_input_config = None self._active_config = None self._active_config = None self.inputConfig = None # Add things to helper so tabs can access it cfclient.ui.pluginhelper.cf = self.cf cfclient.ui.pluginhelper.inputDeviceReader = self.joystickReader cfclient.ui.pluginhelper.logConfigReader = self.logConfigReader self.logConfigDialogue = LogConfigDialogue(cfclient.ui.pluginhelper) self._bootloader_dialog = BootloaderDialog(cfclient.ui.pluginhelper) self._cf2config_dialog = Cf2ConfigDialog(cfclient.ui.pluginhelper) self._cf1config_dialog = Cf1ConfigDialog(cfclient.ui.pluginhelper) self.menuItemBootloader.triggered.connect(self._bootloader_dialog.show) self._about_dialog = AboutDialog(cfclient.ui.pluginhelper) self.menuItemAbout.triggered.connect(self._about_dialog.show) self._menu_cf2_config.triggered.connect(self._cf2config_dialog.show) self._menu_cf1_config.triggered.connect(self._cf1config_dialog.show) # Loading toolboxes (A bit of magic for a lot of automatic) self.toolboxes = [] self.toolboxesMenuItem.setMenu(QtGui.QMenu()) for t_class in cfclient.ui.toolboxes.toolboxes: toolbox = t_class(cfclient.ui.pluginhelper) dockToolbox = MyDockWidget(toolbox.getName()) dockToolbox.setWidget(toolbox) self.toolboxes += [ dockToolbox, ] # Add menu item for the toolbox item = QtGui.QAction(toolbox.getName(), self) item.setCheckable(True) item.triggered.connect(self.toggleToolbox) self.toolboxesMenuItem.menu().addAction(item) dockToolbox.closed.connect(lambda: self.toggleToolbox(False)) # Setup some introspection item.dockToolbox = dockToolbox item.menuItem = item dockToolbox.dockToolbox = dockToolbox dockToolbox.menuItem = item # Load and connect tabs self.tabsMenuItem.setMenu(QtGui.QMenu()) tabItems = {} self.loadedTabs = [] for tabClass in cfclient.ui.tabs.available: tab = tabClass(self.tabs, cfclient.ui.pluginhelper) item = QtGui.QAction(tab.getMenuName(), self) item.setCheckable(True) item.toggled.connect(tab.toggleVisibility) self.tabsMenuItem.menu().addAction(item) tabItems[tab.getTabName()] = item self.loadedTabs.append(tab) if not tab.enabled: item.setEnabled(False) # First instantiate all tabs and then open them in the correct order try: for tName in Config().get("open_tabs").split(","): t = tabItems[tName] if (t != None and t.isEnabled()): # Toggle though menu so it's also marked as open there t.toggle() except Exception as e: logger.warning("Exception while opening tabs [{}]".format(e)) # References to all the device sub-menus in the "Input device" menu self._all_role_menus = () # Used to filter what new devices to add default mapping to self._available_devices = () # Keep track of mux nodes so we can enable according to how many # devices we have self._all_mux_nodes = () # Check which Input muxes are available self._mux_group = QActionGroup(self._menu_inputdevice, exclusive=True) for m in self.joystickReader.available_mux(): node = QAction(m.name, self._menu_inputdevice, checkable=True, enabled=False) node.toggled.connect(self._mux_selected) self._mux_group.addAction(node) self._menu_inputdevice.addAction(node) self._all_mux_nodes += (node, ) mux_subnodes = () for name in m.supported_roles(): sub_node = QMenu(" {}".format(name), self._menu_inputdevice, enabled=False) self._menu_inputdevice.addMenu(sub_node) mux_subnodes += (sub_node, ) self._all_role_menus += ({ "muxmenu": node, "rolemenu": sub_node }, ) node.setData((m, mux_subnodes)) self._mapping_support = True def _update_ui_state(self, newState, linkURI=""): self.uiState = newState if newState == UIState.DISCONNECTED: self.setWindowTitle("Not connected") self.menuItemConnect.setText("Connect to Crazyflie") self.connectButton.setText("Connect") self.menuItemQuickConnect.setEnabled(True) self.batteryBar.setValue(3000) self._menu_cf2_config.setEnabled(False) self._menu_cf1_config.setEnabled(True) self.linkQualityBar.setValue(0) self.menuItemBootloader.setEnabled(True) self.logConfigAction.setEnabled(False) if len(Config().get("link_uri")) > 0: self.quickConnectButton.setEnabled(True) if newState == UIState.CONNECTED: s = "Connected on %s" % linkURI self.setWindowTitle(s) self.menuItemConnect.setText("Disconnect") self.connectButton.setText("Disconnect") self.logConfigAction.setEnabled(True) # Find out if there's an I2C EEPROM, otherwise don't show the # dialog. if len(self.cf.mem.get_mems(MemoryElement.TYPE_I2C)) > 0: self._menu_cf2_config.setEnabled(True) self._menu_cf1_config.setEnabled(False) if newState == UIState.CONNECTING: s = "Connecting to {} ...".format(linkURI) self.setWindowTitle(s) self.menuItemConnect.setText("Cancel") self.connectButton.setText("Cancel") self.quickConnectButton.setEnabled(False) self.menuItemBootloader.setEnabled(False) self.menuItemQuickConnect.setEnabled(False) @pyqtSlot(bool) def toggleToolbox(self, display): menuItem = self.sender().menuItem dockToolbox = self.sender().dockToolbox if display and not dockToolbox.isVisible(): dockToolbox.widget().enable() self.addDockWidget(dockToolbox.widget().preferedDockArea(), dockToolbox) dockToolbox.show() elif not display: dockToolbox.widget().disable() self.removeDockWidget(dockToolbox) dockToolbox.hide() menuItem.setChecked(False) def _rescan_devices(self): self._statusbar_label.setText("No inputdevice connected!") self._menu_devices.clear() self._active_device = "" self.joystickReader.stop_input() #for c in self._menu_mappings.actions(): # c.setEnabled(False) #devs = self.joystickReader.available_devices() #if (len(devs) > 0): # self.device_discovery(devs) def _show_input_device_config_dialog(self): self.inputConfig = InputConfigDialogue(self.joystickReader) self.inputConfig.show() def _auto_reconnect_changed(self, checked): self._auto_reconnect_enabled = checked Config().set("auto_reconnect", checked) logger.info("Auto reconnect enabled: {}".format(checked)) def _show_connect_dialog(self): self.logConfigDialogue.show() def _update_vbatt(self, timestamp, data, logconf): self.batteryBar.setValue(int(data["pm.vbat"] * 1000)) def _connected(self, linkURI): self._update_ui_state(UIState.CONNECTED, linkURI) Config().set("link_uri", str(linkURI)) lg = LogConfig("Battery", 1000) lg.add_variable("pm.vbat", "float") try: self.cf.log.add_config(lg) lg.data_received_cb.add_callback(self.batteryUpdatedSignal.emit) lg.error_cb.add_callback(self._log_error_signal.emit) lg.start() except KeyError as e: logger.warning(str(e)) mem = self.cf.mem.get_mems(MemoryElement.TYPE_DRIVER_LED)[0] mem.write_data(self._led_write_done) #self._led_write_test = 0 #mem.leds[self._led_write_test] = [10, 20, 30] #mem.write_data(self._led_write_done) def _led_write_done(self, mem, addr): logger.info("LED write done callback") #self._led_write_test += 1 #mem.leds[self._led_write_test] = [10, 20, 30] #mem.write_data(self._led_write_done) def _logging_error(self, log_conf, msg): QMessageBox.about( self, "Log error", "Error when starting log config" " [{}]: {}".format(log_conf.name, msg)) def _connection_lost(self, linkURI, msg): if not self._auto_reconnect_enabled: if self.isActiveWindow(): warningCaption = "Communication failure" error = "Connection lost to {}: {}".format(linkURI, msg) QMessageBox.critical(self, warningCaption, error) self._update_ui_state(UIState.DISCONNECTED, linkURI) else: self._quick_connect() def _connection_failed(self, linkURI, error): if not self._auto_reconnect_enabled: msg = "Failed to connect on {}: {}".format(linkURI, error) warningCaption = "Communication failure" QMessageBox.critical(self, warningCaption, msg) self._update_ui_state(UIState.DISCONNECTED, linkURI) else: self._quick_connect() def closeEvent(self, event): self.hide() self.cf.close_link() Config().save_file() def _connect(self): if self.uiState == UIState.CONNECTED: self.cf.close_link() elif self.uiState == UIState.CONNECTING: self.cf.close_link() self._update_ui_state(UIState.DISCONNECTED) else: self.connectDialogue.show() def _display_input_device_error(self, error): self.cf.close_link() QMessageBox.critical(self, "Input device error", error) def _mux_selected(self, checked): """Called when a new mux is selected. The menu item contains a reference to the raw mux object as well as to the associated device sub-nodes""" if not checked: (mux, sub_nodes) = self.sender().data().toPyObject() for s in sub_nodes: s.setEnabled(False) else: (mux, sub_nodes) = self.sender().data().toPyObject() for s in sub_nodes: s.setEnabled(True) self.joystickReader.set_mux(mux=mux) # Go though the tree and select devices/mapping that was # selected before it was disabled. for role_node in sub_nodes: for dev_node in role_node.children(): if type(dev_node) is QAction and dev_node.isChecked(): dev_node.toggled.emit(True) self._update_input_device_footer() def _get_dev_status(self, device): msg = "{}".format(device.name) if device.supports_mapping: map_name = "N/A" if device.input_map: map_name = device.input_map_name msg += " ({})".format(map_name) return msg def _update_input_device_footer(self): """Update the footer in the bottom of the UI with status for the input device and its mapping""" msg = "" if len(self.joystickReader.available_devices()) > 0: mux = self.joystickReader._selected_mux msg = "Using {} mux with ".format(mux.name) for key in mux._devs.keys()[:-1]: if mux._devs[key]: msg += "{}, ".format(self._get_dev_status(mux._devs[key])) else: msg += "N/A, " # Last item key = mux._devs.keys()[-1] if mux._devs[key]: msg += "{}".format(self._get_dev_status(mux._devs[key])) else: msg += "N/A" else: msg = "No input device found" self._statusbar_label.setText(msg) def _inputdevice_selected(self, checked): """Called when a new input device has been selected from the menu. The data in the menu object is the associated map menu (directly under the item in the menu) and the raw device""" (map_menu, device, mux_menu) = self.sender().data().toPyObject() if not checked: if map_menu: map_menu.setEnabled(False) # Do not close the device, since we don't know exactly # how many devices the mux can have open. When selecting a # new mux the old one will take care of this. else: if map_menu: map_menu.setEnabled(True) (mux, sub_nodes) = mux_menu.data().toPyObject() for role_node in sub_nodes: for dev_node in role_node.children(): if type(dev_node) is QAction and dev_node.isChecked(): if device.id == dev_node.data().toPyObject()[1].id \ and dev_node is not self.sender(): dev_node.setChecked(False) role_in_mux = str(self.sender().parent().title()).strip() logger.info("Role of {} is {}".format(device.name, role_in_mux)) Config().set("input_device", str(device.name)) self._mapping_support = self.joystickReader.start_input( device.name, role_in_mux) self._update_input_device_footer() def _inputconfig_selected(self, checked): """Called when a new configuration has been selected from the menu. The data in the menu object is a referance to the device QAction in parent menu. This contains a referance to the raw device.""" if not checked: return selected_mapping = str(self.sender().text()) device = self.sender().data().toPyObject().data().toPyObject()[1] self.joystickReader.set_input_map(device.name, selected_mapping) self._update_input_device_footer() def device_discovery(self, devs): """Called when new devices have been added""" for menu in self._all_role_menus: role_menu = menu["rolemenu"] mux_menu = menu["muxmenu"] dev_group = QActionGroup(role_menu, exclusive=True) for d in devs: dev_node = QAction(d.name, role_menu, checkable=True, enabled=True) role_menu.addAction(dev_node) dev_group.addAction(dev_node) dev_node.toggled.connect(self._inputdevice_selected) map_node = None if d.supports_mapping: map_node = QMenu(" Input map", role_menu, enabled=False) map_group = QActionGroup(role_menu, exclusive=True) # Connect device node to map node for easy # enabling/disabling when selection changes and device # to easily enable it dev_node.setData((map_node, d)) for c in ConfigManager().get_list_of_configs(): node = QAction(c, map_node, checkable=True, enabled=True) node.toggled.connect(self._inputconfig_selected) map_node.addAction(node) # Connect all the map nodes back to the device # action node where we can access the raw device node.setData(dev_node) map_group.addAction(node) # If this device hasn't been found before, then # select the default mapping for it. if d not in self._available_devices: last_map = Config().get("device_config_mapping") if last_map.has_key( d.name) and last_map[d.name] == c: node.setChecked(True) role_menu.addMenu(map_node) dev_node.setData((map_node, d, mux_menu)) # Update the list of what devices we found # to avoid selecting default mapping for all devices when # a new one is inserted self._available_devices = () for d in devs: self._available_devices += (d, ) # Only enable MUX nodes if we have enough devies to cover # the roles for mux_node in self._all_mux_nodes: (mux, sub_nodes) = mux_node.data().toPyObject() if len(mux.supported_roles()) <= len(self._available_devices): mux_node.setEnabled(True) # TODO: Currently only supports selecting default mux if self._all_mux_nodes[0].isEnabled(): self._all_mux_nodes[0].setChecked(True) # If the previous length of the available devies was 0, then select # the default on. If that's not available then select the first # on in the list. # TODO: This will only work for the "Normal" mux so this will be # selected by default if Config().get("input_device") in [d.name for d in devs]: for dev_menu in self._all_role_menus[0]["rolemenu"].actions(): if dev_menu.text() == Config().get("input_device"): dev_menu.setChecked(True) else: # Select the first device in the first mux (will always be "Normal" # mux) self._all_role_menus[0]["rolemenu"].actions()[0].setChecked(True) logger.info("Select first device") self._update_input_device_footer() def _quick_connect(self): try: self.cf.open_link(Config().get("link_uri")) except KeyError: self.cf.open_link("") def _open_config_folder(self): QDesktopServices.openUrl( QUrl("file:///" + QDir.toNativeSeparators(sys.path[1]))) def closeAppRequest(self): self.close() sys.exit(0)
class MainWindow(ui_mainwindow.Ui_MainWindow, QMainWindow): """ Main application window """ def __init__(self, roamapp): super(MainWindow, self).__init__() self.setupUi(self) import roam self.projectwidget.project_base = roamapp.projectsroot QgsApplication.instance().setStyleSheet(roam.roam_style.appstyle()) self.menutoolbar.setStyleSheet(roam.roam_style.menubarstyle()) icon = roam.roam_style.iconsize() self.menutoolbar.setIconSize(QSize(icon, icon)) self.projectupdater = ProjectUpdater(projects_base=roamapp.projectsroot) self.projectupdater.foundProjects.connect(self.projectwidget.show_new_updateable) self.projectupdater.projectUpdateStatus.connect(self.projectwidget.update_project_status) self.projectupdater.projectInstalled.connect(self.projectwidget.project_installed) self.projectwidget.search_for_updates.connect(self.search_for_projects) self.projectwidget.projectUpdate.connect(self.projectupdater.update_project) self.projectwidget.projectInstall.connect(self.projectupdater.install_project) self.project = None self.tracking = GPSLogging(GPS) self.canvas_page.set_gps(GPS, self.tracking) self.canvas = self.canvas_page.canvas roam.defaults.canvas = self.canvas self.bar = roam.messagebaritems.MessageBar(self.centralwidget) self.actionMap.setVisible(False) self.actionLegend.setVisible(False) self.menuGroup = QActionGroup(self) self.menuGroup.setExclusive(True) self.menuGroup.addAction(self.actionMap) self.menuGroup.addAction(self.actionDataEntry) self.menuGroup.addAction(self.actionLegend) self.menuGroup.addAction(self.actionProject) self.menuGroup.addAction(self.actionSync) self.menuGroup.addAction(self.actionSettings) self.menuGroup.addAction(self.actionGPS) self.menuGroup.triggered.connect(self.updatePage) self.projectbuttons = [] self.pluginactions = [] self.actionQuit.triggered.connect(self.exit) self.actionLegend.triggered.connect(self.updatelegend) self.projectwidget.requestOpenProject.connect(self.loadProject) QgsProject.instance().readProject.connect(self._readProject) self.gpswidget.setgps(GPS) self.gpswidget.settracking(self.tracking) self.settings = {} self.actionSettings.toggled.connect(self.settingswidget.populateControls) self.actionSettings.toggled.connect(self.settingswidget.readSettings) self.settingswidget.settingsupdated.connect(self.settingsupdated) self.dataentrywidget = DataEntryWidget(self.canvas, self.bar) self.dataentrywidget.lastwidgetremoved.connect(self.dataentryfinished) self.widgetpage.layout().addWidget(self.dataentrywidget) self.dataentrywidget.rejected.connect(self.formrejected) RoamEvents.featuresaved.connect(self.featureSaved) RoamEvents.helprequest.connect(self.showhelp) RoamEvents.deletefeature.connect(self.delete_feature) def createSpacer(width=0, height=0): widget = QWidget() widget.setMinimumWidth(width) widget.setMinimumHeight(height) return widget gpsspacewidget = createSpacer(30) sidespacewidget = createSpacer(30) sidespacewidget2 = createSpacer(height=20) sidespacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sidespacewidget2.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.projectlabel = QLabel("Project: {project}") self.userlabel = QLabel("User: {user}".format(user=getpass.getuser())) self.positionlabel = QLabel('') self.gpslabel = QLabel("GPS: Not active") self.statusbar.addWidget(self.projectlabel) self.statusbar.addWidget(self.userlabel) spacer = createSpacer() spacer2 = createSpacer() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.statusbar.addWidget(spacer) self.statusbar.addWidget(self.positionlabel) self.statusbar.addWidget(spacer2) self.statusbar.addWidget(self.gpslabel) self.scalewidget = QgsScaleComboBox() self.scalebutton = QToolButton() self.scalebutton.setAutoRaise(True) self.scalebutton.setMaximumHeight(self.statusbar.height()) self.scalebutton.pressed.connect(self.selectscale) self.scalebutton.setText("Scale") self.statusbar.addPermanentWidget(self.scalebutton) self.scalelist = BigList(parent=self.canvas, centeronparent=True, showsave=False) self.scalelist.hide() self.scalelist.setlabel("Map Scale") self.scalelist.setmodel(self.scalewidget.model()) self.scalelist.closewidget.connect(self.scalelist.close) self.scalelist.itemselected.connect(self.update_scale_from_item) self.scalelist.itemselected.connect(self.scalelist.close) self.menutoolbar.insertWidget(self.actionQuit, sidespacewidget2) self.spaceraction = self.menutoolbar.insertWidget(self.actionProject, sidespacewidget) self.panels = [] self.centralwidget.layout().addWidget(self.statusbar) self.actionGPSFeature.setProperty('dataentry', True) self.infodock = InfoDock(self.canvas) self.infodock.featureupdated.connect(self.highlightfeature) self.infodock.hide() self.hidedataentry() self.canvas.extentsChanged.connect(self.updatestatuslabel) self.canvas.scaleChanged.connect(self.updatestatuslabel) RoamEvents.openimage.connect(self.openimage) RoamEvents.openurl.connect(self.viewurl) RoamEvents.openfeatureform.connect(self.openForm) RoamEvents.openkeyboard.connect(self.openkeyboard) RoamEvents.editgeometry_complete.connect(self.on_geometryedit) RoamEvents.onShowMessage.connect(self.showUIMessage) RoamEvents.selectionchanged.connect(self.showInfoResults) RoamEvents.show_widget.connect(self.dataentrywidget.add_widget) RoamEvents.closeProject.connect(self.close_project) GPS.gpsposition.connect(self.update_gps_label) GPS.gpsdisconnected.connect(self.gps_disconnected) self.legendpage.showmap.connect(self.showmap) self.currentselection = {} def update_scale_from_item(self, index): scale, _ = self.scalewidget.toDouble(index.data(Qt.DisplayRole)) self.canvas.zoomScale(1.0 / scale) def selectscale(self): self.scalelist.show() def set_projectbuttons(self, visible): for action in self.projectbuttons: action.setVisible(visible) def loadpages(self, pages): def safe_connect(method, to): try: method.connect(to) except AttributeError: pass for PageClass in pages: action = QAction(self.menutoolbar) text = PageClass.title.ljust(13) action.setIconText(text) action.setIcon(QIcon(PageClass.icon)) action.setCheckable(True) if PageClass.projectpage: action.setVisible(False) self.projectbuttons.append(action) self.menutoolbar.insertAction(self.spaceraction, action) else: self.menutoolbar.insertAction(self.actionProject, action) iface = RoamInterface(RoamEvents, GPS, self) pagewidget = PageClass(iface, self) safe_connect(RoamEvents.selectionchanged, pagewidget.selection_changed) safe_connect(RoamEvents.projectloaded, pagewidget.project_loaded) pageindex = self.stackedWidget.insertWidget(-1, pagewidget) action.setProperty('page', pageindex) self.pluginactions.append(action) self.menuGroup.addAction(action) def showUIMessage(self, label, message, level=QgsMessageBar.INFO, time=0, extra=''): self.bar.pushMessage(label, message, level, duration=time, extrainfo=extra) def updatelegend(self): self.legendpage.updatecanvas(self.canvas) def update_gps_label(self, position, gpsinfo): # Recenter map if we go outside of the 95% of the area self.gpslabel.setText("GPS: PDOP {} HDOP {} VDOP {}".format(gpsinfo.pdop, gpsinfo.hdop, gpsinfo.vdop)) def gps_disconnected(self): self.gpslabel.setText("GPS Not Active") def openkeyboard(self): if not roam.config.settings.get('keyboard', True): return roam.api.utils.open_keyboard() def viewurl(self, url): """ Open a URL in Roam :param url: :return: """ key = url.toString().lstrip('file://') try: # Hack. Eww fix me. data, imagetype = roam.htmlviewer.images[os.path.basename(key)] pix = QPixmap() if imagetype == 'base64': pix.loadFromData(data) else: pix.load(data) self.openimage(pix) except KeyError: pix = QPixmap() pix.load(key) if pix.isNull(): QDesktopServices.openUrl(url) return self.openimage(pix) def openimage(self, pixmap): viewer = ImageViewer(self.stackedWidget) viewer.resize(self.stackedWidget.size()) viewer.openimage(pixmap) def delete_feature(self, form, feature): """ Delete the selected feature """ # We have to make the feature form because the user might have setup logic # to handle the delete case featureform = form.create_featureform(feature) try: msg = featureform.deletemessage except AttributeError: msg = 'Do you really want to delete this feature?' if not DeleteFeatureDialog(msg).exec_(): return try: featureform.delete() except DeleteFeatureException as ex: RoamEvents.raisemessage(*ex.error) featureform.featuredeleted(feature) def search_for_projects(self): server = roam.config.settings.get('updateserver', '') print server self.projectupdater.update_server(server, self.projects) def settingsupdated(self, settings): self.settings = settings self.show() self.canvas_page.settings_updated(settings) def updatestatuslabel(self, *args): extent = self.canvas.extent() self.positionlabel.setText("Map Center: {}".format(extent.center().toString())) scale = 1.0 / self.canvas.scale() scale = self.scalewidget.toString(scale) self.scalebutton.setText(scale) def on_geometryedit(self, form, feature): layer = form.QGISLayer self.reloadselection(layer, updated=[feature]) def handle_removed_features(self, layer, layerid, deleted_feature_ids): self.canvas.refresh() self.reloadselection(layer, deleted=deleted_feature_ids) def reloadselection(self, layer, deleted=[], updated=[]): """ Reload the selection after features have been updated or deleted. :param layer: :param deleted: :param updated: :return: """ selectedfeatures = [] for selection_layer, features in self.currentselection.iteritems(): if layer.name() == selection_layer.name(): selectedfeatures = features layer = selection_layer break if not selectedfeatures: return # Update any features that have changed. for updatedfeature in updated: oldfeatures = [f for f in selectedfeatures if f.id() == updatedfeature.id()] for feature in oldfeatures: self.currentselection[layer].remove(feature) self.currentselection[layer].append(updatedfeature) # Delete any old ones for deletedid in deleted: oldfeatures = [f for f in selectedfeatures if f.id() == deletedid] for feature in oldfeatures: self.currentselection[layer].remove(feature) RoamEvents.selectionchanged.emit(self.currentselection) def highlightfeature(self, layer, feature, features): self.canvas_page.highlight_active_selection(layer, feature, features) def showmap(self): self.actionMap.setVisible(True) self.actionLegend.setVisible(True) self.actionMap.trigger() def hidedataentry(self): self.actionDataEntry.setVisible(False) def showdataentry(self): self.actionDataEntry.setVisible(True) self.actionDataEntry.trigger() def raiseerror(self, *exinfo): info = traceback.format_exception(*exinfo) item = self.bar.pushError(QApplication.translate('MainWindowPy','Seems something has gone wrong. Press for more details', None, QApplication.UnicodeUTF8), info) def showhelp(self, parent, url): help = HelpPage(parent) help.setHelpPage(url) help.show() def dataentryfinished(self): self.hidedataentry() self.showmap() self.cleartempobjects() self.infodock.refreshcurrent() def featuresdeleted(self, layerid, featureids): layer = QgsMapLayerRegistry.instance().mapLayer(layerid) self.reloadselection(layer, deleted=featureids) self.canvas.refresh() def featureSaved(self, *args): #self.reloadselection(layer, deleted=[featureid]) self.canvas.refresh() def cleartempobjects(self): self.canvas_page.clear_temp_objects() def formrejected(self, message, level): if message: RoamEvents.raisemessage("Form Message", message, level, duration=2) def openForm(self, form, feature, editmode, *args): """ Open the form that is assigned to the layer """ self.showdataentry() self.dataentrywidget.load_feature_form(feature, form, editmode, *args) def editfeaturegeometry(self, form, feature, newgeometry): layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() RoamEvents.editgeometry_complete.emit(form, feature) def addNewFeature(self, form, geometry): """ Add a new new feature to the given layer """ layer = form.QGISLayer if layer.geometryType() in [QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon]: geometry.convertToMultiType() try: # TODO: This is a gross hack. We need to move this out into a edit tool with better control. form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass feature = form.new_feature(set_defaults=True) feature.setGeometry(geometry) self.openForm(form, feature, editmode=False) def exit(self): """ Exit the application. """ self.close() def showInfoResults(self, results): forms = {} for layer in results.keys(): layername = layer.name() if not layername in forms: forms[layername] = list(self.project.formsforlayer(layername)) self.currentselection = results self.infodock.setResults(results, forms, self.project) self.infodock.show() def missingLayers(self, layers): """ Called when layers have failed to load from the current project """ roam.utils.warning("Missing layers") map(roam.utils.warning, layers) missinglayers = roam.messagebaritems.MissingLayerItem(layers, parent=self.bar) self.bar.pushItem(missinglayers) def loadprojects(self, projects): """ Load the given projects into the project list """ projects = list(projects) self.projects = projects self.projectwidget.loadProjectList(projects) self.syncwidget.loadprojects(projects) self.search_for_projects() def updatePage(self, action): """ Update the current stack page based on the current selected action """ page = action.property("page") self.stackedWidget.setCurrentIndex(page) def show(self): """ Override show method. Handles showing the app in fullscreen mode or just maximized """ fullscreen = roam.config.settings.get("fullscreen", False) if fullscreen: self.showFullScreen() else: self.showMaximized() def viewprojects(self): self.stackedWidget.setCurrentIndex(1) @roam.utils.timeit def _readProject(self, doc): """ readProject is called by QgsProject once the map layer has been populated with all the layers """ crs = self.canvas_page.init_qgisproject(doc) self.projectOpened() GPS.crs = crs @roam.utils.timeit def projectOpened(self): """ Called when a new project is opened in QGIS. """ projectpath = QgsProject.instance().fileName() self.project = Project.from_folder(os.path.dirname(projectpath)) self.projectlabel.setText("Project: {}".format(self.project.name)) # Show panels for panel in self.project.getPanels(): self.mainwindow.addDockWidget(Qt.BottomDockWidgetArea, panel) self.panels.append(panel) self.clear_plugins() self.add_plugins(self.project.enabled_plugins) layers = self.project.legendlayersmapping().values() self.legendpage.updateitems(layers) gps_loglayer = self.project.gpslog_layer() if gps_loglayer: self.tracking.enable_logging_on(gps_loglayer) else: roam.utils.info("No gps_log found for GPS logging") self.tracking.clear_logging() for layer in roam.api.utils.layers(): if not layer.type() == QgsMapLayer.VectorLayer: continue layer.committedFeaturesRemoved.connect(partial(self.handle_removed_features, layer)) self.canvas_page.project_loaded(self.project) self.showmap() self.set_projectbuttons(True) self.dataentrywidget.project = self.project projectscales, _ = QgsProject.instance().readBoolEntry("Scales", "/useProjectScales") if projectscales: projectscales, _ = QgsProject.instance().readListEntry("Scales", "/ScalesList") self.scalewidget.updateScales(projectscales) else: scales = ["1:50000", "1:25000", "1:10000", "1:5000", "1:2500", "1:1000", "1:500", "1:250", "1:200", "1:100"] scales = roam.config.settings.get('scales', scales) self.scalewidget.updateScales(scales) RoamEvents.projectloaded.emit(self.project) def clear_plugins(self): self.projectbuttons = [] self.projectbuttons.append(self.actionMap) self.projectbuttons.append(self.actionLegend) for action in self.pluginactions: # Remove the page widget, because we make it on each load widget = self.stackedWidget.widget(action.property("page")) self.stackedWidget.removeWidget(widget) widget.deleteLater() self.menutoolbar.removeAction(action) self.pluginactions = [] def add_plugins(self, pluginnames): for name in pluginnames: # Get the plugin try: plugin_mod = plugins.loaded_plugins[name] except KeyError: continue if not hasattr(plugin_mod, 'pages'): roam.utils.warning("No pages() function found in {}".format(name)) continue pages = plugin_mod.pages() self.loadpages(pages) @roam.utils.timeit def loadProject(self, project): """ Load a project into the application . """ roam.utils.log(project) roam.utils.log(project.name) roam.utils.log(project.projectfile) roam.utils.log(project.valid) (passed, message) = project.onProjectLoad() if not passed: self.bar.pushMessage("Project load rejected", "Sorry this project couldn't" "be loaded. Click for me details.", QgsMessageBar.WARNING, extrainfo=message) return self.actionMap.trigger() self.close_project() # No idea why we have to set this each time. Maybe QGIS deletes it for # some reason. self.badLayerHandler = BadLayerHandler(callback=self.missingLayers) QgsProject.instance().setBadLayerHandler(self.badLayerHandler) # Project loading screen self.stackedWidget.setCurrentIndex(3) self.projectloading_label.setText("Project {} Loading".format(project.name)) pixmap = QPixmap(project.splash) w = self.projectimage.width() h = self.projectimage.height() self.projectimage.setPixmap(pixmap.scaled(w,h, Qt.KeepAspectRatio)) QApplication.processEvents() QDir.setCurrent(os.path.dirname(project.projectfile)) fileinfo = QFileInfo(project.projectfile) QgsProject.instance().read(fileinfo) def close_project(self, project=None): """ Close the current open project """ if not project is None and not project == self.project: return self.tracking.clear_logging() self.dataentrywidget.clear() self.canvas_page.cleanup() QgsMapLayerRegistry.instance().removeAllMapLayers() for panel in self.panels: self.removeDockWidget(panel) del panel # Remove all the old buttons self.panels = [] oldproject = self.project self.project = None self.set_projectbuttons(False) self.hidedataentry() self.infodock.close() RoamEvents.selectioncleared.emit() RoamEvents.projectClosed.emit(oldproject) self.projectwidget.set_open_project(None)
def __createToolbarsAndConnect(self): actionGroup = QActionGroup(self) actionGroup.addAction(self.actionImport) actionGroup.addAction(self.actionVyhledavani) actionGroup.addAction(self.actionZpracujZmeny) # QSignalMapper self.signalMapper = QSignalMapper(self) # connect to 'clicked' on all buttons self.connect(self.actionImport, SIGNAL( "triggered()"), self.signalMapper, SLOT("map()")) self.connect(self.actionVyhledavani, SIGNAL( "triggered()"), self.signalMapper, SLOT("map()")) self.connect(self.actionZpracujZmeny, SIGNAL( "triggered()"), self.signalMapper, SLOT("map()")) # setMapping on each button to the QStackedWidget index we'd like to # switch to self.signalMapper.setMapping(self.actionImport, 0) self.signalMapper.setMapping(self.actionVyhledavani, 2) self.signalMapper.setMapping(self.actionZpracujZmeny, 1) # connect mapper to stackedWidget self.connect(self.signalMapper, SIGNAL("mapped(int)"), self.stackedWidget, SLOT("setCurrentIndex(int)")) self.actionImport.trigger() self.connect(self.vfkBrowser, SIGNAL( "switchToPanelImport"), self.switchToImport) self.connect(self.vfkBrowser, SIGNAL( "switchToPanelSearch"), self.switchToSearch) self.connect(self.vfkBrowser, SIGNAL( "switchToPanelChanges"), self.switchToChanges) # Browser toolbar # --------------- self.__mBrowserToolbar = QToolBar(self) self.connect(self.actionBack, SIGNAL( "triggered()"), self.vfkBrowser.goBack) self.connect(self.actionForward, SIGNAL( "triggered()"), self.vfkBrowser.goForth) self.connect(self.actionSelectBudInMap, SIGNAL("triggered()"), self.selectBudInMap) self.connect(self.actionSelectParInMap, SIGNAL("triggered()"), self.selectParInMap) self.connect(self.actionCuzkPage, SIGNAL("triggered()"), self.showOnCuzk) self.connect(self.actionExportLatex, SIGNAL("triggered()"), self.latexExport) self.connect(self.actionExportHtml, SIGNAL("triggered()"), self.htmlExport) self.connect(self.actionShowInfoaboutSelection, SIGNAL( "toggled(bool)"), self.setSelectionChangedConnected) self.connect(self.actionShowHelpPage, SIGNAL( "triggered()"), self.vfkBrowser.showHelpPage) self.loadVfkButton.clicked.connect(self.loadVfkButton_clicked) self.__browseButtons['browseButton_1'] = self.browseButton self.__browseButtons['browseButton_1'].clicked.connect( lambda: self.browseButton_clicked(int('{}'.format(len(self.__vfkLineEdits))))) self.__vfkLineEdits['vfkLineEdit_1'] = self.vfkFileLineEdit bt = QToolButton(self.__mBrowserToolbar) bt.setPopupMode(QToolButton.InstantPopup) bt.setText("Export ") menu = QMenu(bt) menu.addAction(self.actionExportLatex) menu.addAction(self.actionExportHtml) bt.setMenu(menu) # add actions to toolbar icons self.__mBrowserToolbar.addAction(self.actionImport) self.__mBrowserToolbar.addAction(self.actionVyhledavani) self.__mBrowserToolbar.addAction(self.actionZpracujZmeny) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addAction(self.actionBack) self.__mBrowserToolbar.addAction(self.actionForward) self.__mBrowserToolbar.addAction(self.actionSelectParInMap) self.__mBrowserToolbar.addAction(self.actionSelectBudInMap) self.__mBrowserToolbar.addAction(self.actionCuzkPage) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addAction(self.actionShowInfoaboutSelection) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addWidget(bt) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addAction(self.actionShowHelpPage) self.rightWidgetLayout.insertWidget(0, self.__mBrowserToolbar) # connect signals from vfkbrowser when changing history self.connect(self.vfkBrowser, SIGNAL( "currentParIdsChanged"), self.actionSelectParInMap.setEnabled) self.connect(self.vfkBrowser, SIGNAL("currentBudIdsChanged"), self.actionSelectBudInMap.setEnabled) self.connect(self.vfkBrowser, SIGNAL( "historyBefore"), self.actionBack.setEnabled) self.connect(self.vfkBrowser, SIGNAL( "historyAfter"), self.actionForward.setEnabled) self.connect(self.vfkBrowser, SIGNAL( "definitionPointAvailable"), self.actionCuzkPage.setEnabled) # add toolTips self.pb_nextFile.setToolTip(u'Přidej další soubor VFK') self.parCheckBox.setToolTip(u'Načti vrstvu parcel') self.budCheckBox.setToolTip(u'Načti vrstvu budov') # add new VFK file self.pb_nextFile.clicked.connect(self.__addRowToGridLayout) # widget apply changes self.pb_mainDb.clicked.connect( lambda: self.browseDb_clicked('mainDb')) self.pb_amendmentDb.clicked.connect( lambda: self.browseDb_clicked('amendmentDb')) self.pb_exportDb.clicked.connect( lambda: self.browseDb_clicked('exportDb')) self.pb_applyChanges.clicked.connect(self.applyChanges) self.pb_applyChanges.setEnabled(False) self.connect(self.changes_instance, SIGNAL("maxRangeProgressBar"), self.__setRangeProgressBarChanges) self.connect(self.changes_instance, SIGNAL("updateStatus"), self.__updateProgressBarChanges) self.connect(self.changes_instance, SIGNAL("finishedStatus"), self.__changesApplied) self.connect(self.changes_instance, SIGNAL("preprocessingDatabase"), self.__changesPreprocessingDatabase) # connect radio boxes self.rb_file.clicked.connect(self.radioButtonValue) self.rb_directory.clicked.connect(self.radioButtonValue)
class XSplitButton(QWidget): """ ~~>[img:widgets/xsplitbutton.png] The XSplitButton class provides a simple class for creating a multi-checkable tool button based on QActions and QActionGroups. === Example Usage === |>>> from projexui.widgets.xsplitbutton import XSplitButton |>>> import projexui | |>>> # create the widget |>>> widget = projexui.testWidget(XSplitButton) | |>>> # add some actions (can be text or a QAction) |>>> widget.addAction('Day') |>>> widget.addAction('Month') |>>> widget.addAction('Year') | |>>> # create connections |>>> def printAction(act): print act.text() |>>> widget.actionGroup().triggered.connect(printAction) """ __designer_icon__ = projexui.resources.find('img/ui/multicheckbox.png') clicked = Signal() currentActionChanged = Signal(object) hovered = Signal(object) triggered = Signal(object) def __init__( self, parent = None ): super(XSplitButton, self).__init__( parent ) # define custom properties self._actionGroup = QActionGroup(self) self._padding = 5 self._cornerRadius = 10 #self._currentAction = None self._checkable = True # set default properties layout = QBoxLayout(QBoxLayout.LeftToRight) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setLayout(layout) self.clear() # create connections self._actionGroup.hovered.connect(self.emitHovered) self._actionGroup.triggered.connect(self.emitTriggered) def actions(self): """ Returns a list of the actions linked with this widget. :return [<QAction>, ..] """ return self._actionGroup.actions() def actionTexts(self): """ Returns a list of the action texts for this widget. :return [<str>, ..] """ return map(lambda x: x.text(), self._actionGroup.actions()) def actionGroup( self ): """ Returns the action group linked with this widget. :return <QActionGroup> """ return self._actionGroup def addAction(self, action, checked=None, autoBuild=True): """ Adds the inputed action to this widget's action group. This will auto-\ create a new group if no group is already defined. :param action | <QAction> || <str> :return <QAction> """ # clear the holder actions = self._actionGroup.actions() if actions and actions[0].objectName() == 'place_holder': self._actionGroup.removeAction(actions[0]) actions[0].deleteLater() # create an action from the name if not isinstance(action, QAction): action_name = str(action) action = QAction(action_name, self) action.setObjectName(action_name) action.setCheckable(self.isCheckable()) # auto-check the first option if checked or (not self._actionGroup.actions() and checked is None): action.setChecked(True) self._actionGroup.addAction(action) if autoBuild: self.rebuild() return action def clear(self, autoBuild=True): """ Clears the actions for this widget. """ for action in self._actionGroup.actions(): self._actionGroup.removeAction(action) action = QAction('', self) action.setObjectName('place_holder') # self._currentAction = None self._actionGroup.addAction(action) if autoBuild: self.rebuild() def cornerRadius( self ): """ Returns the corner radius for this widget. :return <int> """ return self._cornerRadius def count(self): """ Returns the number of actions associated with this button. :return <int> """ actions = self._actionGroup.actions() if len(actions) == 1 and actions[0].objectName() == 'place_holder': return 0 return len(actions) def currentAction( self ): """ Returns the action that is currently checked in the system. :return <QAction> || None """ return self._actionGroup.checkedAction() def direction( self ): """ Returns the direction for this widget. :return <QBoxLayout::Direction> """ return self.layout().direction() def emitClicked(self): """ Emits the clicked signal whenever any of the actions are clicked. """ if not self.signalsBlocked(): self.clicked.emit() def emitHovered(self, action): """ Emits the hovered action for this widget. :param action | <QAction> """ if not self.signalsBlocked(): self.hovered.emit(action) def emitTriggered(self, action): """ Emits the triggered action for this widget. :param action | <QAction> """ # if action != self._currentAction: # self._currentAction = action # self.currentActionChanged.emit(action) # self._currentAction = action if not self.signalsBlocked(): self.triggered.emit(action) def findAction( self, text ): """ Looks up the action based on the inputed text. :return <QAction> || None """ for action in self.actionGroup().actions(): if ( text in (action.objectName(), action.text()) ): return action return None def isCheckable(self): """ Returns whether or not the actions within this button should be checkable. :return <bool> """ return self._checkable def padding( self ): """ Returns the button padding amount for this widget. :return <int> """ return self._padding def rebuild( self ): """ Rebuilds the user interface buttons for this widget. """ self.setUpdatesEnabled(False) # sync up the toolbuttons with our actions actions = self._actionGroup.actions() btns = self.findChildren(QToolButton) horiz = self.direction() in (QBoxLayout.LeftToRight, QBoxLayout.RightToLeft) # remove unnecessary buttons if len(actions) < len(btns): rem_btns = btns[len(actions)-1:] btns = btns[:len(actions)] for btn in rem_btns: btn.close() btn.setParent(None) btn.deleteLater() # create new buttons elif len(btns) < len(actions): for i in range(len(btns), len(actions)): btn = QToolButton(self) btn.setAutoFillBackground(True) btns.append(btn) self.layout().addWidget(btn) btn.clicked.connect(self.emitClicked) # determine coloring options palette = self.palette() checked = palette.color(palette.Highlight) checked_fg = palette.color(palette.HighlightedText) unchecked = palette.color(palette.Button) unchecked_fg = palette.color(palette.ButtonText) border = palette.color(palette.Mid) # define the stylesheet options options = {} options['top_left_radius'] = 0 options['top_right_radius'] = 0 options['bot_left_radius'] = 0 options['bot_right_radius'] = 0 options['border_color'] = border.name() options['checked_fg'] = checked_fg.name() options['checked_bg'] = checked.name() options['checked_bg_alt'] = checked.darker(120).name() options['unchecked_fg'] = unchecked_fg.name() options['unchecked_bg'] = unchecked.name() options['unchecked_bg_alt'] = unchecked.darker(120).name() options['padding_top'] = 1 options['padding_bottom'] = 1 options['padding_left'] = 1 options['padding_right'] = 1 if horiz: options['x1'] = 0 options['y1'] = 0 options['x2'] = 0 options['y2'] = 1 else: options['x1'] = 0 options['y1'] = 0 options['x2'] = 1 options['y2'] = 1 # sync up the actions and buttons count = len(actions) palette = self.palette() font = self.font() for i, action in enumerate(actions): btn = btns[i] # assign the action for this button if btn.defaultAction() != action: # clear out any existing actions for act in btn.actions(): btn.removeAction(act) # assign the given action btn.setDefaultAction(action) options['top_left_radius'] = 1 options['bot_left_radius'] = 1 options['top_right_radius'] = 1 options['bot_right_radius'] = 1 if horiz: options['padding_left'] = self._padding options['padding_right'] = self._padding else: options['padding_top'] = self._padding options['padding_bottom'] = self._padding if not i: if horiz: options['top_left_radius'] = self.cornerRadius() options['bot_left_radius'] = self.cornerRadius() options['padding_left'] += self.cornerRadius() / 3.0 else: options['top_left_radius'] = self.cornerRadius() options['top_right_radius'] = self.cornerRadius() options['padding_top'] += self.cornerRadius() / 3.0 if i == count - 1: if horiz: options['top_right_radius'] = self.cornerRadius() options['bot_right_radius'] = self.cornerRadius() options['padding_right'] += self.cornerRadius() / 3.0 else: options['bot_left_radius'] = self.cornerRadius() options['bot_right_radius'] = self.cornerRadius() options['padding_bottom'] += self.cornerRadius() / 3.0 btn.setFont(font) btn.setPalette(palette) btn.setStyleSheet(TOOLBUTTON_STYLE % options) if horiz: btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) else: btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.setUpdatesEnabled(True) def setActions(self, actions): """ Sets the actions for this widget to th inputed list of actions. :param [<QAction>, ..] """ self.clear(autoBuild=False) for action in actions: self.addAction(action, autoBuild=False) self.rebuild() def setActionTexts(self, names): """ Convenience method for auto-generating actions based on text names, sets the list of actions for this widget to the inputed list of names. :param names | [<str>, ..] """ self.setActions(names) def setActionGroup( self, actionGroup ): """ Sets the action group for this widget to the inputed action group. :param actionGroup | <QActionGroup> """ self._actionGroup = actionGroup self.rebuild() def setCheckable(self, state): """ Sets whether or not the actions within this button should be checkable. :param state | <bool> """ self._checkable = state for act in self._actionGroup.actions(): act.setCheckable(state) def setCornerRadius( self, radius ): """ Sets the corner radius value for this widget to the inputed radius. :param radius | <int> """ self._cornerRadius = radius def setCurrentAction(self, action): """ Sets the current action for this button to the inputed action. :param action | <QAction> || <str> """ self._actionGroup.blockSignals(True) for act in self._actionGroup.actions(): act.setChecked(act == action or act.text() == action) self._actionGroup.blockSignals(False) def setDirection( self, direction ): """ Sets the direction that this group widget will face. :param direction | <QBoxLayout::Direction> """ self.layout().setDirection(direction) self.rebuild() def setFont(self, font): """ Sets the font for this widget and propogates down to the buttons. :param font | <QFont> """ super(XSplitButton, self).setFont(font) self.rebuild() def setPadding( self, padding ): """ Sets the padding amount for this widget's button set. :param padding | <int> """ self._padding = padding self.rebuild() def setPalette(self, palette): """ Rebuilds the buttons for this widget since they use specific palette options. :param palette | <QPalette> """ super(XSplitButton, self).setPalette(palette) self.rebuild() def sizeHint(self): """ Returns the base size hint for this widget. :return <QSize> """ return QSize(35, 22) x_actionTexts = Property(QStringList, actionTexts, setActionTexts) x_checkable = Property(bool, isCheckable, setCheckable)
class TrackingWindow(QMainWindow): """ Main window of the application. This class is responsible for the global data structures too. :IVariables: undo_stack : `QUndoStack` Undo stack. All actions that can be undone should be pushed on the stack. _project : `project.Project` Project object managing the loaded data _data : `tracking_data.TrackingData` Data object keeping track of points and cells toolGroup : `QActionGroup` Group of actions to be enabled only when actions can be taken on images previousSelAct : `QActionGroup` Actions enabled when points are selected in the previous pane currentSelAct : `QActionGroup` Actions enabled when points are selected in the current pane projectAct : `QActionGroup` Actions to enable once a project is loaded _previousScene : `tracking_scene.TrackingScene` Object managing the previous pane _currentScene : `tracking_scene.LinkedTrackingScene` Object managing the current pane """ def __init__(self, *args, **kwords): QMainWindow.__init__(self, *args) self.undo_stack = QUndoStack(self) self.ui = Ui_TrackingWindow() self.ui.setupUi(self) self._project = None self._data = None self.toolGroup = QActionGroup(self) self.toolGroup.addAction(self.ui.actionAdd_point) self.toolGroup.addAction(self.ui.action_Move_point) self.toolGroup.addAction(self.ui.actionAdd_cell) self.toolGroup.addAction(self.ui.actionRemove_cell) self.toolGroup.addAction(self.ui.action_Pan) self.toolGroup.addAction(self.ui.actionZoom_out) self.toolGroup.addAction(self.ui.actionZoom_in) self.previousSelAct = QActionGroup(self) self.previousSelAct.addAction(self.ui.actionCopy_selection_from_Previous) self.previousSelAct.addAction(self.ui.actionDelete_Previous) self.previousSelAct.setEnabled(False) self.currentSelAct = QActionGroup(self) self.currentSelAct.addAction(self.ui.actionCopy_selection_from_Current) self.currentSelAct.addAction(self.ui.actionDelete_Current) self.currentSelAct.setEnabled(False) self.projectAct = QActionGroup(self) self.projectAct.addAction(self.ui.action_Next_image) self.projectAct.addAction(self.ui.action_Previous_image) self.projectAct.addAction(self.ui.actionAdd_point) self.projectAct.addAction(self.ui.action_Move_point) self.projectAct.addAction(self.ui.action_Pan) self.projectAct.addAction(self.ui.actionAdd_cell) self.projectAct.addAction(self.ui.actionRemove_cell) self.projectAct.addAction(self.ui.action_Change_data_file) self.projectAct.addAction(self.ui.actionNew_data_file) self.projectAct.addAction(self.ui.actionZoom_out) self.projectAct.addAction(self.ui.actionZoom_in) self.projectAct.addAction(self.ui.actionSave_as) self.projectAct.addAction(self.ui.action_Fit) self.projectAct.addAction(self.ui.actionZoom_100) self.projectAct.addAction(self.ui.actionMerge_points) self.projectAct.addAction(self.ui.actionCopy_from_previous) self.projectAct.addAction(self.ui.actionCopy_from_current) self.projectAct.addAction(self.ui.actionReset_alignment) self.projectAct.addAction(self.ui.actionAlign_images) self.projectAct.addAction(self.ui.actionSelectPreviousAll) self.projectAct.addAction(self.ui.actionSelectPreviousNew) self.projectAct.addAction(self.ui.actionSelectPreviousNone) self.projectAct.addAction(self.ui.actionSelectPreviousNon_associated) self.projectAct.addAction(self.ui.actionSelectPreviousAssociated) self.projectAct.addAction(self.ui.actionSelectPreviousInvert) self.projectAct.addAction(self.ui.actionSelectCurrentAll) self.projectAct.addAction(self.ui.actionSelectCurrentNew) self.projectAct.addAction(self.ui.actionSelectCurrentNone) self.projectAct.addAction(self.ui.actionSelectCurrentNon_associated) self.projectAct.addAction(self.ui.actionSelectCurrentAssociated) self.projectAct.addAction(self.ui.actionSelectCurrentInvert) self.projectAct.addAction(self.ui.actionEdit_timing) self.projectAct.addAction(self.ui.actionEdit_scales) self.projectAct.addAction(self.ui.actionCompute_growth) self.projectAct.addAction(self.ui.actionClean_cells) self.projectAct.addAction(self.ui.actionGotoCell) self.projectAct.setEnabled(False) current_sel_actions = [self.ui.actionSelectCurrentAll, self.ui.actionSelectCurrentNew, self.ui.actionSelectCurrentNone, self.ui.actionSelectCurrentInvert, '-', self.ui.actionSelectCurrentNon_associated, self.ui.actionSelectCurrentAssociated, self.ui.actionCopy_selection_from_Previous ] previous_sel_actions = [self.ui.actionSelectPreviousAll, self.ui.actionSelectPreviousNew, self.ui.actionSelectPreviousNone, self.ui.actionSelectPreviousInvert, '-', self.ui.actionSelectPreviousNon_associated, self.ui.actionSelectPreviousAssociated, self.ui.actionCopy_selection_from_Current ] self._previousScene = TrackingScene(self.undo_stack, self.ui.actionDelete_Previous, previous_sel_actions, self) self._currentScene = LinkedTrackingScene(self._previousScene, self.undo_stack, self.ui.actionDelete_Current, current_sel_actions, self) self._previousScene.hasSelectionChanged.connect(self.previousSelAct.setEnabled) self._currentScene.hasSelectionChanged.connect(self.currentSelAct.setEnabled) self._previousScene.realSceneSizeChanged.connect(self.sceneSizeChanged) self._currentScene.realSceneSizeChanged.connect(self.sceneSizeChanged) self._previousScene.zoomIn[QPointF].connect(self.zoomIn) self._currentScene.zoomIn.connect(self.zoomIn) self._previousScene.zoomOut[QPointF].connect(self.zoomOut) self._currentScene.zoomOut.connect(self.zoomOut) self.ui.previousData.setScene(self._previousScene) self.ui.currentData.setScene(self._currentScene) self.ui.previousData.setDragMode(QGraphicsView.ScrollHandDrag) self.ui.currentData.setDragMode(QGraphicsView.ScrollHandDrag) #self.ui.previousData.setCacheMode(QGraphicsView.CacheBackground) #self.ui.currentData.setCacheMode(QGraphicsView.CacheBackground) # Redefine shortcuts to standard key sequences self.ui.action_Save.setShortcut(QKeySequence.Save) self.ui.actionSave_as.setShortcut(QKeySequence.SaveAs) self.ui.action_Open_project.setShortcut(QKeySequence.Open) self.ui.action_Undo.setShortcut(QKeySequence.Undo) self.ui.action_Redo.setShortcut(QKeySequence.Redo) self.ui.action_Next_image.setShortcut(QKeySequence.Forward) self.ui.action_Previous_image.setShortcut(QKeySequence.Back) # Connecting undo stack signals self.ui.action_Undo.triggered.connect(self.undo) self.ui.action_Redo.triggered.connect(self.redo) self.undo_stack.canRedoChanged[bool].connect(self.ui.action_Redo.setEnabled) self.undo_stack.canUndoChanged[bool].connect(self.ui.action_Undo.setEnabled) self.undo_stack.redoTextChanged["const QString&"].connect(self.changeRedoText) self.undo_stack.undoTextChanged["const QString&"].connect(self.changeUndoText) self.undo_stack.cleanChanged[bool].connect(self.ui.action_Save.setDisabled) # link_icon = QIcon() # pix = QPixmap(":/icons/link.png") # link_icon.addPixmap(pix, QIcon.Normal, QIcon.On) # pix = QPixmap(":/icons/link_broken.png") # link_icon.addPixmap(pix, QIcon.Normal, QIcon.Off) # self.link_icon = link_icon # #self.ui.linkViews.setIconSize(QSize(64,32)) # self.ui.linkViews.setIcon(link_icon) self._recent_projects_menu = QMenu(self) self.ui.actionRecent_projects.setMenu(self._recent_projects_menu) self._recent_projects_act = [] self._projects_mapper = QSignalMapper(self) self._projects_mapper.mapped[int].connect(self.loadRecentProject) self.param_dlg = None # Setting up the status bar bar = self.statusBar() # Adding current directory cur_dir = QLabel("") bar.addPermanentWidget(cur_dir) self._current_dir_label = cur_dir # Adding up zoom zoom = QLabel("") bar.addPermanentWidget(zoom) self._zoom_label = zoom self.changeZoom(1) self.loadConfig() parameters.instance.renderingChanged.connect(self.changeRendering) self.changeRendering() def changeRendering(self): if parameters.instance.use_OpenGL: self.ui.previousData.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) self.ui.currentData.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) else: self.ui.previousData.setViewport(QWidget()) self.ui.currentData.setViewport(QWidget()) def undo(self): self.undo_stack.undo() def redo(self): self.undo_stack.redo() def changeRedoText(self, text): self.ui.action_Redo.setText(text) self.ui.action_Redo.setToolTip(text) self.ui.action_Redo.setStatusTip(text) def changeUndoText(self, text): self.ui.action_Undo.setText(text) self.ui.action_Undo.setToolTip(text) self.ui.action_Undo.setStatusTip(text) def closeEvent(self, event): self.saveConfig() if not self.ensure_save_data("Exiting whith unsaved data", "The last modifications you made were not saved." " Are you sure you want to exit?"): event.ignore() return QMainWindow.closeEvent(self, event) #sys.exit(0) def loadConfig(self): params = parameters.instance self.ui.action_Show_vector.setChecked(params.show_vectors) self.ui.linkViews.setChecked(params.link_views) self.ui.action_Show_template.setChecked(parameters.instance.show_template) self.ui.actionShow_id.setChecked(parameters.instance.show_id) self.ui.action_Estimate_position.setChecked(parameters.instance.estimate) self.updateRecentFiles() params.recentProjectsChange.connect(self.updateRecentFiles) def updateRecentFiles(self): for a in self._recent_projects_act: self._projects_mapper.removeMappings(a) del self._recent_projects_act[:] menu = self._recent_projects_menu menu.clear() recent_projects = parameters.instance.recent_projects for i, p in enumerate(recent_projects): act = QAction(self) act.setText("&{0:d} {1}".format(i + 1, p)) self._recent_projects_act.append(act) act.triggered.connect(self._projects_mapper.map) self._projects_mapper.setMapping(act, i) menu.addAction(act) def saveConfig(self): parameters.instance.save() def check_for_data(self): if self._project is None: QMessageBox.critical(self, "No project loaded", "You have to load a project before performing this operation") return False return True def loadRecentProject(self, i): if self.ensure_save_data("Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change project?"): self.loadProject(parameters.instance.recent_projects[i]) @pyqtSignature("") def on_action_Open_project_triggered(self): if self.ensure_save_data("Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change project?"): dir_ = QFileDialog.getExistingDirectory(self, "Select a project directory", parameters.instance._last_dir) if dir_: self.loadProject(dir_) def loadProject(self, dir_): dir_ = path(dir_) project = Project(dir_) if project.valid: self._project = project else: create = QMessageBox.question(self, "Invalid project directory", "This directory does not contain a valid project. Turn into a directory?", QMessageBox.No, QMessageBox.Yes) if create == QMessageBox.No: return project.create() self._project = project self._project.use() parameters.instance.add_recent_project(dir_) parameters.instance._last_dir = dir_ if self._data is not None: _data = self._data _data.saved.disconnect(self.undo_stack.setClean) try: #self._project.load() self.load_data() _data = self._project.data _data.saved.connect(self.undo_stack.setClean) self._project.changedDataFile.connect(self.dataFileChanged) self._data = _data self._previousScene.changeDataManager(self._data) self._currentScene.changeDataManager(self._data) self.initFromData() self.projectAct.setEnabled(True) except TrackingDataException as ex: showException(self, "Error while loaded data", ex) def dataFileChanged(self, new_file): if new_file is None: self._current_dir_label.setText("") else: self._current_dir_label.setText(new_file) def initFromData(self): """ Initialize the interface using the current data """ self.ui.previousState.clear() self.ui.currentState.clear() for name in self._data.images_name: self.ui.previousState.addItem(name) self.ui.currentState.addItem(name) self.ui.previousState.setCurrentIndex(0) self.ui.currentState.setCurrentIndex(1) self._previousScene.changeImage(self._data.image_path(self._data.images_name[0])) self._currentScene.changeImage(self._data.image_path(self._data.images_name[1])) self.dataFileChanged(self._project.data_file) @pyqtSignature("int") def on_previousState_currentIndexChanged(self, index): #print "Previous image loaded: %s" % self._data.images[index] self.changeScene(self._previousScene, index) self._currentScene.changeImage(None) @pyqtSignature("int") def on_currentState_currentIndexChanged(self, index): #print "Current image loaded: %s" % self._data.images[index] self.changeScene(self._currentScene, index) def changeScene(self, scene, index): """ Set the scene to use the image number index. """ scene.changeImage(self._data.image_path(self._data.images_name[index])) @pyqtSignature("") def on_action_Save_triggered(self): self.save_data() @pyqtSignature("") def on_actionSave_as_triggered(self): fn = QFileDialog.getSaveFileName(self, "Select a data file to save in", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: self.save_data(path(fn)) def save_data(self, data_file=None): if self._data is None: raise TrackingDataException("Trying to save data when none have been loaded") try: self._project.save(data_file) return True except TrackingDataException as ex: showException(self, "Error while saving data", ex) return False def load_data(self, **opts): if self._project is None: raise TrackingDataException("Trying to load data when no project have been loaded") try: if self._project.load(**opts): log_debug("Data file was corrected. Need saving.") self.ui.action_Save.setEnabled(True) else: log_debug("Data file is clean.") self.ui.action_Save.setEnabled(False) return True except TrackingDataException as ex: showException(self, "Error while loading data", ex) return False except RetryTrackingDataException as ex: if retryException(self, "Problem while loading data", ex): new_opts = dict(opts) new_opts.update(ex.method_args) return self.load_data(**new_opts) return False def ensure_save_data(self, title, reason): if self._data is not None and not self.undo_stack.isClean(): button = QMessageBox.warning(self, title, reason, QMessageBox.Yes | QMessageBox.Save | QMessageBox.Cancel) if button == QMessageBox.Save: return self.save_data() elif button == QMessageBox.Cancel: return False self.undo_stack.clear() return True @pyqtSignature("") def on_action_Change_data_file_triggered(self): if self.ensure_save_data("Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change the current data file?"): fn = QFileDialog.getOpenFileName(self, "Select a data file to load", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: self._project.data_file = str(fn) if self.load_data(): self._previousScene.resetNewPoints() self._currentScene.resetNewPoints() @pyqtSignature("bool") def on_action_Show_vector_toggled(self, value): parameters.instance.show_vector = value self._currentScene.showVector(value) @pyqtSignature("bool") def on_action_Show_template_toggled(self, value): parameters.instance.show_template = value @pyqtSignature("bool") def on_actionShow_id_toggled(self, value): parameters.instance.show_id = value @pyqtSignature("") def on_action_Next_image_triggered(self): cur = self.ui.currentState.currentIndex() pre = self.ui.previousState.currentIndex() l = len(self._data.images_name) if cur < l-1 and pre < l-1: self.ui.previousState.setCurrentIndex(pre+1) self.ui.currentState.setCurrentIndex(cur+1) @pyqtSignature("") def on_action_Previous_image_triggered(self): cur = self.ui.currentState.currentIndex() pre = self.ui.previousState.currentIndex() if cur > 0 and pre > 0: self.ui.previousState.setCurrentIndex(pre-1) self.ui.currentState.setCurrentIndex(cur-1) @pyqtSignature("") def on_copyToPrevious_clicked(self): self._currentScene.copyFromLinked(self._previousScene) @pyqtSignature("") def on_copyToCurrent_clicked(self): self._previousScene.copyToLinked(self._currentScene) @pyqtSignature("bool") def on_action_Estimate_position_toggled(self, value): parameters.instance.estimate = value # @pyqtSignature("") # def on_action_Undo_triggered(self): # print "Undo" # @pyqtSignature("") # def on_action_Redo_triggered(self): # print "Redo" @pyqtSignature("bool") def on_action_Parameters_toggled(self, value): if value: from .parametersdlg import ParametersDlg self._previousScene.showTemplates() self._currentScene.showTemplates() #tracking_scene.saveParameters() parameters.instance.save() max_size = max(self._currentScene.width(), self._currentScene.height(), self._previousScene.width(), self._previousScene.height(), 400) self.param_dlg = ParametersDlg(max_size, self) self.param_dlg.setModal(False) self.ui.action_Pan.setChecked(True) self.ui.actionAdd_point.setEnabled(False) self.ui.action_Move_point.setEnabled(False) self.ui.actionAdd_cell.setEnabled(False) self.ui.actionRemove_cell.setEnabled(False) self.ui.action_Undo.setEnabled(False) self.ui.action_Redo.setEnabled(False) self.ui.action_Open_project.setEnabled(False) self.ui.actionRecent_projects.setEnabled(False) self.ui.action_Change_data_file.setEnabled(False) self.ui.copyToCurrent.setEnabled(False) self.ui.copyToPrevious.setEnabled(False) self.param_dlg.finished[int].connect(self.closeParam) self.param_dlg.show() elif self.param_dlg: self.param_dlg.accept() def closeParam(self, value): if value == QDialog.Rejected: parameters.instance.load() self.ui.actionAdd_point.setEnabled(True) self.ui.action_Move_point.setEnabled(True) self.ui.actionAdd_cell.setEnabled(True) self.ui.actionRemove_cell.setEnabled(True) self.ui.action_Undo.setEnabled(True) self.ui.action_Redo.setEnabled(True) self.ui.action_Open_project.setEnabled(True) self.ui.actionRecent_projects.setEnabled(True) self.ui.action_Change_data_file.setEnabled(True) self.ui.copyToCurrent.setEnabled(True) self.ui.copyToPrevious.setEnabled(True) self._previousScene.showTemplates(False) self._currentScene.showTemplates(False) self._previousScene.update() self._currentScene.update() self.param_dlg = None self.ui.action_Parameters.setChecked(False) @pyqtSignature("bool") def on_actionZoom_in_toggled(self, value): if value: self._previousScene.mode = TrackingScene.ZoomIn self._currentScene.mode = TrackingScene.ZoomIn @pyqtSignature("bool") def on_actionZoom_out_toggled(self, value): if value: self._previousScene.mode = TrackingScene.ZoomOut self._currentScene.mode = TrackingScene.ZoomOut #def resizeEvent(self, event): # self.ensureZoomFit() def ensureZoomFit(self): if self._data: prev_rect = self._previousScene.sceneRect() cur_rect = self._currentScene.sceneRect() prev_wnd = QRectF(self.ui.previousData.childrenRect()) cur_wnd = QRectF(self.ui.currentData.childrenRect()) prev_matrix = self.ui.previousData.matrix() cur_matrix = self.ui.currentData.matrix() prev_mapped_rect = prev_matrix.mapRect(prev_rect) cur_mapped_rect = cur_matrix.mapRect(cur_rect) if (prev_mapped_rect.width() < prev_wnd.width() or prev_mapped_rect.height() < prev_wnd.height() or cur_mapped_rect.width() < cur_wnd.width() or cur_mapped_rect.height() < cur_wnd.height()): self.on_action_Fit_triggered() @pyqtSignature("") def on_action_Fit_triggered(self): prev_rect = self._previousScene.sceneRect() cur_rect = self._currentScene.sceneRect() prev_wnd = self.ui.previousData.childrenRect() cur_wnd = self.ui.currentData.childrenRect() prev_sw = prev_wnd.width() / prev_rect.width() prev_sh = prev_wnd.height() / prev_rect.height() cur_sw = cur_wnd.width() / cur_rect.width() cur_sh = cur_wnd.height() / cur_rect.height() s = max(prev_sw, prev_sh, cur_sw, cur_sh) self.ui.previousData.resetMatrix() self.ui.previousData.scale(s, s) self.ui.currentData.resetMatrix() self.ui.currentData.scale(s, s) self.changeZoom(s) def zoomOut(self, point=None): self.ui.currentData.scale(0.5, 0.5) self.ui.previousData.scale(0.5, 0.5) self.changeZoom(self.ui.previousData.matrix().m11()) if point is not None: self.ui.previousData.centerOn(point) self.ui.currentData.centerOn(point) #self.ensureZoomFit() def zoomIn(self, point=None): self.ui.currentData.scale(2, 2) self.ui.previousData.scale(2, 2) self.changeZoom(self.ui.previousData.matrix().m11()) if point is not None: self.ui.previousData.centerOn(point) self.ui.currentData.centerOn(point) def changeZoom(self, zoom): self._zoom_label.setText("Zoom: %.5g%%" % (100*zoom)) @pyqtSignature("") def on_actionZoom_100_triggered(self): self.ui.previousData.resetMatrix() self.ui.currentData.resetMatrix() self.changeZoom(1) @pyqtSignature("bool") def on_actionAdd_point_toggled(self, value): if value: self._previousScene.mode = TrackingScene.Add self._currentScene.mode = TrackingScene.Add @pyqtSignature("bool") def on_actionAdd_cell_toggled(self, value): if value: self._previousScene.mode = TrackingScene.AddCell self._currentScene.mode = TrackingScene.AddCell @pyqtSignature("bool") def on_actionRemove_cell_toggled(self, value): if value: self._previousScene.mode = TrackingScene.RemoveCell self._currentScene.mode = TrackingScene.RemoveCell @pyqtSignature("bool") def on_action_Move_point_toggled(self, value): if value: self._previousScene.mode = TrackingScene.Move self._currentScene.mode = TrackingScene.Move @pyqtSignature("bool") def on_action_Pan_toggled(self, value): if value: self._previousScene.mode = TrackingScene.Pan self._currentScene.mode = TrackingScene.Pan @pyqtSignature("bool") def on_linkViews_toggled(self, value): parameters.instance.link_views = value phor = self.ui.previousData.horizontalScrollBar() pver = self.ui.previousData.verticalScrollBar() chor = self.ui.currentData.horizontalScrollBar() cver = self.ui.currentData.verticalScrollBar() if value: phor.valueChanged[int].connect(chor.setValue) pver.valueChanged[int].connect(cver.setValue) chor.valueChanged[int].connect(phor.setValue) cver.valueChanged[int].connect(pver.setValue) self._previousScene.templatePosChange.connect(self._currentScene.setTemplatePos) self._currentScene.templatePosChange.connect(self._previousScene.setTemplatePos) phor.setValue(chor.value()) pver.setValue(cver.value()) else: phor.valueChanged[int].disconnect(chor.setValue) pver.valueChanged[int].disconnect(cver.setValue) chor.valueChanged[int].disconnect(phor.setValue) cver.valueChanged[int].disconnect(pver.setValue) self._previousScene.templatePosChange.disconnect(self._currentScene.setTemplatePos) self._currentScene.templatePosChange.disconnect(self._previousScene.setTemplatePos) def copyFrom(self, start, items): if parameters.instance.estimate: dlg = createForm('copy_progress.ui', None) dlg.buttonBox.clicked["QAbstractButton*"].connect(self.cancelCopy) params = parameters.instance ts = params.template_size ss = params.search_size fs = params.filter_size self.copy_thread = algo.FindInAll(self._data, start, items, ts, ss, fs, self) dlg.imageProgress.setMaximum(self.copy_thread.num_images) self.copy_thread.start() self.copy_dlg = dlg dlg.exec_() else: algo.copyFromImage(self._data, start, items, self.undo_stack) def cancelCopy(self, *args): self.copy_thread.stop = True dlg = self.copy_dlg dlg.buttonBox.clicked['QAbstractButton*)'].disconnect(self.cancelCopy) self._previousScene.changeImage(None) self._currentScene.changeImage(None) def event(self, event): if isinstance(event, algo.NextImage): dlg = self.copy_dlg if dlg is not None: dlg.imageProgress.setValue(event.currentImage) dlg.pointProgress.setMaximum(event.nbPoints) dlg.pointProgress.setValue(0) return True elif isinstance(event, algo.NextPoint): dlg = self.copy_dlg if dlg is not None: dlg.pointProgress.setValue(event.currentPoint) return True elif isinstance(event, algo.FoundAll): dlg = self.copy_dlg if dlg is not None: self.cancelCopy() dlg.accept() return True elif isinstance(event, algo.Aborted): dlg = self.copy_dlg if dlg is not None: self.cancelCopy() dlg.accept() return True return QMainWindow.event(self, event) def itemsToCopy(self, scene): items = scene.getSelectedIds() if items: answer = QMessageBox.question(self, "Copy of points", "Some points were selected in the previous data window." " Do you want to copy only these point on the successive images?", QMessageBox.Yes, QMessageBox.No) if answer == QMessageBox.Yes: return items return scene.getAllIds() @pyqtSignature("") def on_actionCopy_from_previous_triggered(self): items = self.itemsToCopy(self._previousScene) if items: self.copyFrom(self.ui.previousState.currentIndex(), items) @pyqtSignature("") def on_actionCopy_from_current_triggered(self): items = self.itemsToCopy(self._currentScene) if items: self.copyFrom(self.ui.currentState.currentIndex(), items) @pyqtSignature("") def on_actionSelectPreviousAll_triggered(self): self._previousScene.selectAll() @pyqtSignature("") def on_actionSelectPreviousNew_triggered(self): self._previousScene.selectNew() @pyqtSignature("") def on_actionSelectPreviousNone_triggered(self): self._previousScene.selectNone() @pyqtSignature("") def on_actionSelectPreviousNon_associated_triggered(self): self._previousScene.selectNonAssociated() @pyqtSignature("") def on_actionSelectPreviousAssociated_triggered(self): self._previousScene.selectAssociated() @pyqtSignature("") def on_actionSelectPreviousInvert_triggered(self): self._previousScene.selectInvert() @pyqtSignature("") def on_actionSelectCurrentAll_triggered(self): self._currentScene.selectAll() @pyqtSignature("") def on_actionSelectCurrentNew_triggered(self): self._currentScene.selectNew() @pyqtSignature("") def on_actionSelectCurrentNone_triggered(self): self._currentScene.selectNone() @pyqtSignature("") def on_actionSelectCurrentNon_associated_triggered(self): self._currentScene.selectNonAssociated() @pyqtSignature("") def on_actionSelectCurrentAssociated_triggered(self): self._currentScene.selectAssociated() @pyqtSignature("") def on_actionSelectCurrentInvert_triggered(self): self._currentScene.selectInvert() def whichDelete(self): """ Returns a function deleting what the user wants """ dlg = createForm("deletedlg.ui", None) ret = dlg.exec_() if ret: if dlg.inAllImages.isChecked(): return TrackingScene.deleteInAllImages if dlg.toImage.isChecked(): return TrackingScene.deleteToImage if dlg.fromImage.isChecked(): return TrackingScene.deleteFromImage return lambda x: None @pyqtSignature("") def on_actionDelete_Previous_triggered(self): del_fct = self.whichDelete() del_fct(self._previousScene) @pyqtSignature("") def on_actionDelete_Current_triggered(self): del_fct = self.whichDelete() del_fct(self._currentScene) @pyqtSignature("") def on_actionMerge_points_triggered(self): if self._previousScene.mode == TrackingScene.AddCell: old_cell = self._previousScene.selected_cell new_cell = self._currentScene.selected_cell if old_cell is None or new_cell is None: QMessageBox.critical(self, "Cannot merge cells", "You have to select exactly one cell in the old state " "and one in the new state to merge them.") return try: if old_cell != new_cell: self.undo_stack.push(MergeCells(self._data, self._previousScene.image_name, old_cell, new_cell)) else: self.undo_stack.push(SplitCells(self._data, self._previousScene.image_name, old_cell, new_cell)) except AssertionError as error: QMessageBox.critical(self, "Cannot merge the cells", str(error)) else: old_pts = self._previousScene.getSelectedIds() new_pts = self._currentScene.getSelectedIds() if len(old_pts) != 1 or len(new_pts) != 1: QMessageBox.critical(self, "Cannot merge points", "You have to select exactly one point in the old state " "and one in the new state to link them.") return try: if old_pts != new_pts: self.undo_stack.push(ChangePointsId(self._data, self._previousScene.image_name, old_pts, new_pts)) else: log_debug("Splitting point of id %d" % old_pts[0]) self.undo_stack.push(SplitPointsId(self._data, self._previousScene.image_name, old_pts)) except AssertionError as error: QMessageBox.critical(self, "Cannot merge the points", str(error)) @pyqtSignature("") def on_actionCopy_selection_from_Current_triggered(self): cur_sel = self._currentScene.getSelectedIds() self._previousScene.setSelectedIds(cur_sel) @pyqtSignature("") def on_actionCopy_selection_from_Previous_triggered(self): cur_sel = self._previousScene.getSelectedIds() self._currentScene.setSelectedIds(cur_sel) @pyqtSignature("") def on_actionNew_data_file_triggered(self): if self.ensure_save_data("Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change the current data file?"): fn = QFileDialog.getSaveFileName(self, "Select a new data file to create", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: fn = path(fn) if fn.exists(): button = QMessageBox.question(self, "Erasing existing file", "Are you sure yo want to empty the file '%s' ?" % fn, QMessageBox.Yes, QMessageBox.No) if button == QMessageBox.No: return fn.remove() self._data.clear() self._previousScene.resetNewPoints() self._currentScene.resetNewPoints() self._project.data_file = fn self.initFromData() log_debug("Data file = %s" % (self._project.data_file,)) @pyqtSignature("") def on_actionAbout_triggered(self): dlg = QMessageBox(self) dlg.setWindowTitle("About Point Tracker") dlg.setIconPixmap(self.windowIcon().pixmap(64, 64)) #dlg.setTextFormat(Qt.RichText) dlg.setText("""Point Tracker Tool version %s rev %s Developper: Pierre Barbier de Reuille <*****@*****.**> Copyright 2008 """ % (__version__, __revision__)) img_read = ", ".join(str(s) for s in QImageReader.supportedImageFormats()) img_write = ", ".join(str(s) for s in QImageWriter.supportedImageFormats()) dlg.setDetailedText("""Supported image formats: - For reading: %s - For writing: %s """ % (img_read, img_write)) dlg.exec_() @pyqtSignature("") def on_actionAbout_Qt_triggered(self): QMessageBox.aboutQt(self, "About Qt") @pyqtSignature("") def on_actionReset_alignment_triggered(self): self.undo_stack.push(ResetAlignment(self._data)) @pyqtSignature("") def on_actionAlign_images_triggered(self): fn = QFileDialog.getOpenFileName(self, "Select a data file for alignment", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: d = self._data.copy() fn = path(fn) try: d.load(fn) except TrackingDataException as ex: showException(self, "Error while loading data file", ex) return if d._last_pt_id > 0: dlg = AlignmentDlg(d._last_pt_id+1, self) if dlg.exec_(): ref = dlg.ui.referencePoint.currentText() try: ref = int(ref) except ValueError: ref = str(ref) if dlg.ui.twoPointsRotation.isChecked(): r1 = int(dlg.ui.rotationPt1.currentText()) r2 = int(dlg.ui.rotationPt2.currentText()) rotation = ("TwoPoint", r1, r2) else: rotation = None else: return else: ref = 0 rotation = None try: shifts, angles = algo.alignImages(self._data, d, ref, rotation) self.undo_stack.push(AlignImages(self._data, shifts, angles)) except algo.AlgoException as ex: showException(self, "Error while aligning images", ex) def sceneSizeChanged(self): previous_rect = self._previousScene.real_scene_rect current_rect = self._currentScene.real_scene_rect rect = previous_rect | current_rect self._previousScene.setSceneRect(rect) self._currentScene.setSceneRect(rect) @pyqtSignature("") def on_actionEdit_timing_triggered(self): data = self._data dlg = TimeEditDlg(data.images_name, data.images_time, [data.image_path(n) for n in data.images_name], self) self.current_dlg = dlg if dlg.exec_() == QDialog.Accepted: self.undo_stack.push(ChangeTiming(data, [t for n, t in dlg.model])) del self.current_dlg @pyqtSignature("") def on_actionEdit_scales_triggered(self): data = self._data dlg = EditResDlg(data.images_name, data.images_scale, [data.image_path(n) for n in data.images_name], self) self.current_dlg = dlg if dlg.exec_() == QDialog.Accepted: self.undo_stack.push(ChangeScales(data, [sc for n, sc in dlg.model])) del self.current_dlg @pyqtSignature("") def on_actionCompute_growth_triggered(self): data = self._data dlg = GrowthComputationDlg(data, self) self.current_dlg = dlg dlg.exec_() del self.current_dlg @pyqtSignature("") def on_actionPlot_growth_triggered(self): data = self._data dlg = PlottingDlg(data, self) self.current_dlg = dlg dlg.exec_() del self.current_dlg @pyqtSignature("") def on_actionClean_cells_triggered(self): self.undo_stack.push(CleanCells(self._data)) @pyqtSignature("") def on_actionGotoCell_triggered(self): cells = [str(cid) for cid in self._data.cells] selected, ok = QInputDialog.getItem(self, "Goto cell", "Select the cell to go to", cells, 0) if ok: cid = int(selected) self.ui.actionAdd_cell.setChecked(True) data = self._data if cid not in data.cells: return ls = data.cells_lifespan[cid] prev_pos = self._previousScene.current_data._current_index cur_pos = self._currentScene.current_data._current_index full_poly = data.cells[cid] poly = [pid for pid in full_poly if pid in data[prev_pos]] #log_debug("Cell %d on time %d: %s" % (cid, prev_pos, poly)) if prev_pos < ls.start or prev_pos >= ls.end or not poly: for i in range(*ls.slice().indices(len(data))): poly = [pid for pid in full_poly if pid in data[i]] if poly: log_debug("Found cell %d on image %d with polygon %s" % (cid, i, poly)) new_prev_pos = i break else: log_debug("Cell %d found nowhere in range %s!!!" % (cid, ls.slice())) else: new_prev_pos = prev_pos new_cur_pos = min(max(cur_pos + new_prev_pos - prev_pos, 0), len(data)) self.ui.previousState.setCurrentIndex(new_prev_pos) self.ui.currentState.setCurrentIndex(new_cur_pos) self._previousScene.current_cell = cid self._currentScene.current_cell = cid prev_data = self._previousScene.current_data poly = data.cells[cid] prev_poly = QPolygonF([prev_data[ptid] for ptid in poly if ptid in prev_data]) prev_bbox = prev_poly.boundingRect() log_debug("Previous bounding box = %dx%d+%d+%d" % (prev_bbox.width(), prev_bbox.height(), prev_bbox.left(), prev_bbox.top())) self.ui.previousData.ensureVisible(prev_bbox) @pyqtSignature("") def on_actionGotoPoint_triggered(self): data = self._data points = [str(pid) for pid in data.cell_points] selected, ok = QInputDialog.getItem(self, "Goto point", "Select the point to go to", points, 0) if ok: pid = int(selected) self.ui.action_Move_point.setChecked(True) if pid not in data.cell_points: return prev_pos = self._previousScene.current_data._current_index cur_pos = self._currentScene.current_data._current_index prev_data = self._previousScene.current_data if not pid in prev_data: closest = -1 best_dist = len(data)+1 for img_data in data: if pid in img_data: dist = abs(img_data._current_index - prev_pos) if dist < best_dist: best_dist = dist closest = img_data._current_index new_prev_pos = closest else: new_prev_pos = prev_pos new_cur_pos = min(max(cur_pos + new_prev_pos - prev_pos, 0), len(data)) self.ui.previousState.setCurrentIndex(new_prev_pos) self.ui.currentState.setCurrentIndex(new_cur_pos) self._previousScene.setSelectedIds([pid]) self._currentScene.setSelectedIds([pid]) self.ui.previousData.centerOn(self._previousScene.current_data[pid])
class MainUI(QtGui.QMainWindow, main_window_class): connectionLostSignal = pyqtSignal(str, str) connectionInitiatedSignal = pyqtSignal(str) batteryUpdatedSignal = pyqtSignal(int, object, object) connectionDoneSignal = pyqtSignal(str) connectionFailedSignal = pyqtSignal(str, str) disconnectedSignal = pyqtSignal(str) linkQualitySignal = pyqtSignal(int) _input_device_error_signal = pyqtSignal(str) _input_discovery_signal = pyqtSignal(object) _log_error_signal = pyqtSignal(object, str) def __init__(self, *args): super(MainUI, self).__init__(*args) self.setupUi(self) self.cf = Crazyflie(ro_cache=sys.path[0] + "/cflib/cache", rw_cache=sys.path[1] + "/cache") cflib.crtp.init_drivers(enable_debug_driver=GuiConfig() .get("enable_debug_driver")) # Create the connection dialogue self.connectDialogue = ConnectDialogue() # Create and start the Input Reader self._statusbar_label = QLabel("Loading device and configuration.") self.statusBar().addWidget(self._statusbar_label) self.joystickReader = JoystickReader(cf=self.cf) self._active_device = "" self.configGroup = QActionGroup(self._menu_mappings, exclusive=True) self._load_input_data() self._update_input ConfigManager().conf_needs_reload.add_callback(self._reload_configs) # Connections for the Connect Dialogue self.connectDialogue.requestConnectionSignal.connect(self.cf.open_link) self.connectionDoneSignal.connect(self.connectionDone) self.cf.connection_failed.add_callback(self.connectionFailedSignal.emit) self.connectionFailedSignal.connect(self.connectionFailed) self._input_device_error_signal.connect(self.inputDeviceError) self.joystickReader.device_error.add_callback( self._input_device_error_signal.emit) self._input_discovery_signal.connect(self.device_discovery) self.joystickReader.device_discovery.add_callback( self._input_discovery_signal.emit) # Connect UI signals self.menuItemConnect.triggered.connect(self.connectButtonClicked) self.logConfigAction.triggered.connect(self.doLogConfigDialogue) self.connectButton.clicked.connect(self.connectButtonClicked) self.quickConnectButton.clicked.connect(self.quickConnect) self.menuItemQuickConnect.triggered.connect(self.quickConnect) self.menuItemConfInputDevice.triggered.connect(self.configInputDevice) self.menuItemExit.triggered.connect(self.closeAppRequest) self.batteryUpdatedSignal.connect(self.updateBatteryVoltage) self._menuitem_rescandevices.triggered.connect(self._rescan_devices) self._menuItem_openconfigfolder.triggered.connect(self._open_config_folder) self._auto_reconnect_enabled = GuiConfig().get("auto_reconnect") self.autoReconnectCheckBox.toggled.connect( self._auto_reconnect_changed) self.autoReconnectCheckBox.setChecked(GuiConfig().get("auto_reconnect")) # Do not queue data from the controller output to the Crazyflie wrapper # to avoid latency #self.joystickReader.sendControlSetpointSignal.connect( # self.cf.commander.send_setpoint, # Qt.DirectConnection) self.joystickReader.input_updated.add_callback( self.cf.commander.send_setpoint) # Connection callbacks and signal wrappers for UI protection self.cf.connected.add_callback( self.connectionDoneSignal.emit) self.connectionDoneSignal.connect(self.connectionDone) self.cf.disconnected.add_callback(self.disconnectedSignal.emit) self.disconnectedSignal.connect( lambda linkURI: self.setUIState(UIState.DISCONNECTED, linkURI)) self.cf.connection_lost.add_callback(self.connectionLostSignal.emit) self.connectionLostSignal.connect(self.connectionLost) self.cf.connection_requested.add_callback( self.connectionInitiatedSignal.emit) self.connectionInitiatedSignal.connect( lambda linkURI: self.setUIState(UIState.CONNECTING, linkURI)) self._log_error_signal.connect(self._logging_error) # Connect link quality feedback self.cf.link_quality_updated.add_callback(self.linkQualitySignal.emit) self.linkQualitySignal.connect( lambda percentage: self.linkQualityBar.setValue(percentage)) # Set UI state in disconnected buy default self.setUIState(UIState.DISCONNECTED) # Parse the log configuration files self.logConfigReader = LogConfigReader(self.cf) # Add things to helper so tabs can access it cfclient.ui.pluginhelper.cf = self.cf cfclient.ui.pluginhelper.inputDeviceReader = self.joystickReader cfclient.ui.pluginhelper.logConfigReader = self.logConfigReader self.logConfigDialogue = LogConfigDialogue(cfclient.ui.pluginhelper) self._bootloader_dialog = BootloaderDialog(cfclient.ui.pluginhelper) self.menuItemBootloader.triggered.connect(self._bootloader_dialog.show) self._about_dialog = AboutDialog(cfclient.ui.pluginhelper) self.menuItemAbout.triggered.connect(self._about_dialog.show) # Loading toolboxes (A bit of magic for a lot of automatic) self.toolboxes = [] self.toolboxesMenuItem.setMenu(QtGui.QMenu()) for t_class in cfclient.ui.toolboxes.toolboxes: toolbox = t_class(cfclient.ui.pluginhelper) dockToolbox = MyDockWidget(toolbox.getName()) dockToolbox.setWidget(toolbox) self.toolboxes += [dockToolbox, ] # Add menu item for the toolbox item = QtGui.QAction(toolbox.getName(), self) item.setCheckable(True) item.triggered.connect(self.toggleToolbox) self.toolboxesMenuItem.menu().addAction(item) dockToolbox.closed.connect(lambda: self.toggleToolbox(False)) # Setup some introspection item.dockToolbox = dockToolbox item.menuItem = item dockToolbox.dockToolbox = dockToolbox dockToolbox.menuItem = item # Load and connect tabs self.tabsMenuItem.setMenu(QtGui.QMenu()) tabItems = {} self.loadedTabs = [] for tabClass in cfclient.ui.tabs.available: tab = tabClass(self.tabs, cfclient.ui.pluginhelper) item = QtGui.QAction(tab.getMenuName(), self) item.setCheckable(True) item.toggled.connect(tab.toggleVisibility) self.tabsMenuItem.menu().addAction(item) tabItems[tab.getTabName()] = item self.loadedTabs.append(tab) if not tab.enabled: item.setEnabled(False) # First instantiate all tabs and then open them in the correct order try: for tName in GuiConfig().get("open_tabs").split(","): t = tabItems[tName] if (t != None and t.isEnabled()): # Toggle though menu so it's also marked as open there t.toggle() except Exception as e: logger.warning("Exception while opening tabs [%s]", e) def setUIState(self, newState, linkURI=""): self.uiState = newState if (newState == UIState.DISCONNECTED): self.setWindowTitle("Not connected") self.menuItemConnect.setText("Connect to Crazyflie") self.connectButton.setText("Connect") self.menuItemQuickConnect.setEnabled(True) self.batteryBar.setValue(3000) self.linkQualityBar.setValue(0) self.menuItemBootloader.setEnabled(True) self.logConfigAction.setEnabled(False) if (len(GuiConfig().get("link_uri")) > 0): self.quickConnectButton.setEnabled(True) if (newState == UIState.CONNECTED): s = "Connected on %s" % linkURI self.setWindowTitle(s) self.menuItemConnect.setText("Disconnect") self.connectButton.setText("Disconnect") self.logConfigAction.setEnabled(True) if (newState == UIState.CONNECTING): s = "Connecting to %s ..." % linkURI self.setWindowTitle(s) self.menuItemConnect.setText("Cancel") self.connectButton.setText("Cancel") self.quickConnectButton.setEnabled(False) self.menuItemBootloader.setEnabled(False) self.menuItemQuickConnect.setEnabled(False) @pyqtSlot(bool) def toggleToolbox(self, display): menuItem = self.sender().menuItem dockToolbox = self.sender().dockToolbox if display and not dockToolbox.isVisible(): dockToolbox.widget().enable() self.addDockWidget(dockToolbox.widget().preferedDockArea(), dockToolbox) dockToolbox.show() elif not display: dockToolbox.widget().disable() self.removeDockWidget(dockToolbox) dockToolbox.hide() menuItem.setChecked(False) def _rescan_devices(self): self._statusbar_label.setText("No inputdevice connected!") self._menu_devices.clear() self._active_device = "" self.joystickReader.stop_input() for c in self._menu_mappings.actions(): c.setEnabled(False) devs = self.joystickReader.getAvailableDevices() if (len(devs) > 0): self.device_discovery(devs) def configInputDevice(self): self.inputConfig = InputConfigDialogue(self.joystickReader) self.inputConfig.show() def _auto_reconnect_changed(self, checked): self._auto_reconnect_enabled = checked GuiConfig().set("auto_reconnect", checked) logger.info("Auto reconnect enabled: %s", checked) def doLogConfigDialogue(self): self.logConfigDialogue.show() def updateBatteryVoltage(self, timestamp, data, logconf): self.batteryBar.setValue(int(data["pm.vbat"] * 1000)) cfclient.ui.pluginhelper.inputDeviceReader.inputdevice.setBatteryData(int(data["pm.vbat"] * 1000)) def connectionDone(self, linkURI): self.setUIState(UIState.CONNECTED, linkURI) GuiConfig().set("link_uri", linkURI) lg = LogConfig("Battery", 1000) lg.add_variable("pm.vbat", "float") self.cf.log.add_config(lg) if lg.valid: lg.data_received_cb.add_callback(self.batteryUpdatedSignal.emit) lg.error_cb.add_callback(self._log_error_signal.emit) lg.start() else: logger.warning("Could not setup loggingblock!") def _logging_error(self, log_conf, msg): QMessageBox.about(self, "Log error", "Error when starting log config" " [%s]: %s" % (log_conf.name, msg)) def connectionLost(self, linkURI, msg): if not self._auto_reconnect_enabled: if (self.isActiveWindow()): warningCaption = "Communication failure" error = "Connection lost to %s: %s" % (linkURI, msg) QMessageBox.critical(self, warningCaption, error) self.setUIState(UIState.DISCONNECTED, linkURI) else: self.quickConnect() def connectionFailed(self, linkURI, error): if not self._auto_reconnect_enabled: msg = "Failed to connect on %s: %s" % (linkURI, error) warningCaption = "Communication failure" QMessageBox.critical(self, warningCaption, msg) self.setUIState(UIState.DISCONNECTED, linkURI) else: self.quickConnect() def closeEvent(self, event): self.hide() self.cf.close_link() GuiConfig().save_file() def connectButtonClicked(self): if (self.uiState == UIState.CONNECTED): self.cf.close_link() elif (self.uiState == UIState.CONNECTING): self.cf.close_link() self.setUIState(UIState.DISCONNECTED) else: self.connectDialogue.show() def inputDeviceError(self, error): self.cf.close_link() QMessageBox.critical(self, "Input device error", error) def _load_input_data(self): self.joystickReader.stop_input() # Populate combo box with available input device configurations for c in ConfigManager().get_list_of_configs(): node = QAction(c, self._menu_mappings, checkable=True, enabled=False) node.toggled.connect(self._inputconfig_selected) self.configGroup.addAction(node) self._menu_mappings.addAction(node) def _reload_configs(self, newConfigName): # remove the old actions from the group and the menu for action in self._menu_mappings.actions(): self.configGroup.removeAction(action) self._menu_mappings.clear() # reload the conf files, and populate the menu self._load_input_data() self._update_input(self._active_device, newConfigName) def _update_input(self, device="", config=""): self.joystickReader.stop_input() self._active_config = str(config) self._active_device = str(device) GuiConfig().set("input_device", self._active_device) GuiConfig().get( "device_config_mapping" )[self._active_device] = self._active_config self.joystickReader.start_input(self._active_device, self._active_config) # update the checked state of the menu items for c in self._menu_mappings.actions(): c.setEnabled(True) if c.text() == self._active_config: c.setChecked(True) for c in self._menu_devices.actions(): c.setEnabled(True) if c.text() == self._active_device: c.setChecked(True) # update label if device == "" and config == "": self._statusbar_label.setText("No input device selected") elif config == "": self._statusbar_label.setText("Using [%s] - " "No input config selected" % (self._active_device)) else: self._statusbar_label.setText("Using [%s] with config [%s]" % (self._active_device, self._active_config)) def _inputdevice_selected(self, checked): if (not checked): return self.joystickReader.stop_input() sender = self.sender() self._active_device = sender.text() device_config_mapping = GuiConfig().get("device_config_mapping") if (self._active_device in device_config_mapping.keys()): self._current_input_config = device_config_mapping[ str(self._active_device)] else: self._current_input_config = self._menu_mappings.actions()[0].text() GuiConfig().set("input_device", str(self._active_device)) for c in self._menu_mappings.actions(): if (c.text() == self._current_input_config): c.setChecked(True) self.joystickReader.start_input(str(sender.text()), self._current_input_config) self._statusbar_label.setText("Using [%s] with config [%s]" % ( self._active_device, self._current_input_config)) def _inputconfig_selected(self, checked): if (not checked): return self._update_input(self._active_device, self.sender().text()) def device_discovery(self, devs): group = QActionGroup(self._menu_devices, exclusive=True) for d in devs: node = QAction(d["name"], self._menu_devices, checkable=True) node.toggled.connect(self._inputdevice_selected) group.addAction(node) self._menu_devices.addAction(node) if (d["name"] == GuiConfig().get("input_device")): self._active_device = d["name"] if (len(self._active_device) == 0): self._active_device = self._menu_devices.actions()[0].text() device_config_mapping = GuiConfig().get("device_config_mapping") if (device_config_mapping): if (self._active_device in device_config_mapping.keys()): self._current_input_config = device_config_mapping[ str(self._active_device)] else: self._current_input_config = self._menu_mappings.actions()[0].text() else: self._current_input_config = self._menu_mappings.actions()[0].text() # Now we know what device to use and what mapping, trigger the events # to change the menus and start the input for c in self._menu_mappings.actions(): c.setEnabled(True) if (c.text() == self._current_input_config): c.setChecked(True) for c in self._menu_devices.actions(): if (c.text() == self._active_device): c.setChecked(True) def quickConnect(self): try: self.cf.open_link(GuiConfig().get("link_uri")) except KeyError: self.cf.open_link("") def _open_config_folder(self): QDesktopServices.openUrl(QUrl("file:///" + QDir.toNativeSeparators(sys.path[1]))) def closeAppRequest(self): self.close() sys.exit(0)
class Viewer(ViewerBase, ViewerClass): trackingChanged = pyqtSignal(bool) setLocationTriggered = pyqtSignal() updateFeatures = pyqtSignal(bool) layerChanged = pyqtSignal(QgsMapLayer) clearLine = pyqtSignal() closed = pyqtSignal() def __init__(self, callbackobject, parent=None): """Constructor.""" super(Viewer, self).__init__(parent) self.setupUi(self) self.callbackobject = callbackobject self.frame = self.webview.page().mainFrame() self.actiongroup = QActionGroup(self) self.actiongroup.setExclusive(True) self.actiongroup.triggered.connect(self.action_triggered) self.measuredialog = MeasureDialog(self) self.toolbar = QToolBar() self.qgisTrackButton = self.toolbar.addAction("QGIS Track") self.qgisTrackButton.setIcon(QIcon(":/icons/track")) self.qgisTrackButton.setCheckable(True) self.qgisTrackButton.setChecked(True) self.qgisTrackButton.toggled.connect(self.trackingChanged.emit) self.setlocationaction = self.toolbar.addAction("Set location") self.setlocationaction.setIcon(QIcon(":/icons/location")) self.setlocationaction.triggered.connect( self.setLocationTriggered.emit) self.setlocationaction.setCheckable(True) self.viewfeatures = self.toolbar.addAction("Load QGIS Features") self.viewfeatures.setIcon(QIcon(":/icons/features")) self.viewfeatures.setCheckable(True) self.viewfeatures.setChecked(True) self.viewfeatures.toggled.connect(self.updateFeatures.emit) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.toolbar.addWidget(spacer) self.measureaction = self.toolbar.addAction("measure") self.measureaction.setObjectName("Measure") self.measureaction.setIcon(QIcon(":/icons/measure")) self.measureaction.setCheckable(True) self.infoaction = self.toolbar.addAction("Info") self.infoaction.setObjectName("Info") self.infoaction.setIcon(QIcon(":/icons/info")) self.infoaction.setCheckable(True) self.selectaction = self.toolbar.addAction("Select") self.selectaction.setObjectName("Select") self.selectaction.setIcon(QIcon(":/icons/select")) self.selectaction.setCheckable(True) self.toolbar.addSeparator() self.deleteaction = self.toolbar.addAction("Delete") self.deleteaction.setIcon(QIcon(":/icons/delete")) self.deleteaction.triggered.connect(self.delete_selected) self.deleteaction.setEnabled(False) self.addaction = self.toolbar.addAction("Add") self.addaction.setObjectName("Add") self.addaction.setIcon(QIcon(":/icons/add")) self.addaction.setCheckable(True) self.moveaction = self.toolbar.addAction("Move") self.moveaction.setObjectName("Move") self.moveaction.setIcon(QIcon(":/icons/move")) self.moveaction.setCheckable(True) self.actiongroup.addAction(self.moveaction) self.actiongroup.addAction(self.addaction) self.actiongroup.addAction(self.infoaction) self.actiongroup.addAction(self.measureaction) self.actiongroup.addAction(self.selectaction) self.activelayercombo = QgsMapLayerComboBox() self.activelayercombo.layerChanged.connect(self.layer_changed) self.activelayeraction = self.toolbar.addWidget(self.activelayercombo) self.activelayercombo.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.activelayercombo.currentIndexChanged.connect(self.index_changed) self.zvaluecheck = QCheckBox() self.zvaluecheck.setChecked(True) self.zvaluecheck.setText("Copy Z value") self.zvaluecheck.setToolTip( "Copy Z value from viewer to new features in QGIS. Must have a field named Z to enable" ) self.zvalueaction = self.toolbar.addWidget(self.zvaluecheck) self.dockWidgetContents.layout().insertWidget(0, self.toolbar) self.webview.settings().setAttribute(QWebSettings.PluginsEnabled, True) self.webview.settings().setAttribute(QWebSettings.JavascriptEnabled, True) self.webview.settings().setAttribute( QWebSettings.DeveloperExtrasEnabled, True) self.frame.setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff) self.frame.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff) self.frame.javaScriptWindowObjectCleared.connect( self.addcallbackobject) self.measuredialog.modeCombo.currentIndexChanged.connect( self.action_triggered) self.measuredialog.clearButton.clicked.connect(self.clear_line) self.earthmine = EarthmineAPI(self.frame) def closeEvent(self, event): self.closed.emit() super(Viewer, self).closeEvent(event) def index_changed(self, index): if index == -1: self.set_button_states(False, False, False, False) def clear_line(self): self.clearLine.emit() self.earthmine.clearLine() @property def copyZvalue(self): layer = self.active_layer if not layer: return False if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType( ) == QGis.Point: return self.zvaluecheck.isChecked() else: return False @property def geom(self): return self.measuredialog.geom @geom.setter def geom(self, value): self.measuredialog.geom = value self.measuredialog.update_geom_labels() @property def tracking(self): return self.qgisTrackButton.isChecked() @property def mode(self): return self.measuredialog.mode @property def active_layer(self): return self.activelayercombo.currentLayer() def layer_changed(self, layer): if not layer: self.set_button_states(False, False, False, False) return if layer.type() == QgsMapLayer.VectorLayer: enabledselecttools = layer.geometryType() in [ QGis.Line, QGis.Point ] enableedittools = layer.isEditable() enabledelete = layer.isEditable() and layer.selectedFeatureCount() enablemove = layer.geometryType( ) == QGis.Point and layer.isEditable() else: enabledselecttools = False enableedittools = False enabledelete = False enablemove = False self.set_button_states(enabledselecttools, enableedittools, enabledelete, enablemove) self.action_triggered() self.layerChanged.emit(layer) def selection_changed(self, layer): if layer == self.active_layer: enabledelete = layer.isEditable() and layer.selectedFeatureCount() self.deleteaction.setEnabled(enabledelete) def set_button_states(self, selecttools, edittools, deleteenabled, moveenabled): actions = [self.selectaction, self.infoaction] for action in actions: action.setEnabled(selecttools) editactions = [self.deleteaction, self.moveaction, self.addaction] for action in editactions: action.setEnabled(edittools) if edittools: self.deleteaction.setEnabled(deleteenabled) self.moveaction.setEnabled(moveenabled) for action in editactions: if action is self.actiongroup.checkedAction( ) and not action.isEnabled(): self.infoaction.toggle() break layer = self.active_layer if not layer: enablez = False else: enablez = layer.type( ) == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Point self.zvalueaction.setEnabled(enablez) @property def current_action_color(self): action = self.actiongroup.checkedAction() color = int("0x00ff00", 16) if action == self.measureaction: if self.mode == "Vertical": color = int("0x0000ff", 16) return color def action_triggered(self, *args): action = self.actiongroup.checkedAction() layer = self.activelayercombo.currentLayer() self.clear_line() if not action: return if not action == self.measureaction and ( not layer or not layer.type() == QgsMapLayer.VectorLayer): return color = self.current_action_color actiondata = {} if action == self.measureaction: self.measuredialog.show() actiondata['mode'] = self.mode geomtype = None layerid = None else: self.measuredialog.hide() geomtype = QGis.vectorGeometryType(layer.geometryType()) layerid = layer.id() data = dict(action=action.objectName(), layer=layerid, geom=geomtype, actiondata=actiondata, color=color) self.earthmine.updateAction(data) def active_tool(self): action = self.actiongroup.checkedAction() if not action: return None return action.objectName() def update_current_layer(self, layer): self.activelayercombo.setLayer(layer) def addcallbackobject(self): self.frame.addToJavaScriptWindowObject("qgis", self.callbackobject) def loadviewer(self, url): self.webview.load(url) self.frame.addToJavaScriptWindowObject("qgis", self.callbackobject) def startViewer(self, settings): self.earthmine.startViewer(settings) def set_location(self, point): # # NOTE Set location takes WGS84 make sure you have transformed it first before sending self.earthmine.setLocation(point.x(), point.y()) def clear_features(self): self.earthmine.clearFeatures() def clear_layer_features(self, layerid): self.earthmine.clearLayerObjects(layerid) def remove_feature(self, layerid, featureid): """ :param features: A dict of layerid, id, lat, lng :return: """ self.earthmine.removeFeature(layerid, featureid) def load_features(self, layerdata, features): """ :param features: A dict of layerid, id, lat, lng :return: """ self.earthmine.loadFeatures(layerdata, features) def layer_loaded(self, layerid): return self.earthmine.layerLoaded(layerid) def clear_selection(self, layerid): self.earthmine.clearSelection(layerid) def set_selection(self, layerid, featureids, clearlast=True): self.earthmine.setSelection(layerid, featureids, clearlast) def edit_feature(self, layerid, featureid, nodes): self.earthmine.editFeature(layerid, featureid, nodes) def delete_selected(self): layer = self.active_layer layer.deleteSelectedFeatures()
class ToolBox(QFrame): """ A tool box widget. """ # Emitted when a tab is toggled. tabToogled = Signal(int, bool) def setExclusive(self, exclusive): """ Set exclusive tabs (only one tab can be open at a time). """ if self.__exclusive != exclusive: self.__exclusive = exclusive self.__tabActionGroup.setExclusive(exclusive) checked = self.__tabActionGroup.checkedAction() if checked is None: # The action group can be out of sync with the actions state # when switching between exclusive states. actions_checked = [ page.action for page in self.__pages if page.action.isChecked() ] if actions_checked: checked = actions_checked[0] # Trigger/toggle remaining open pages if exclusive and checked is not None: for page in self.__pages: if checked != page.action and page.action.isChecked(): page.action.trigger() def exclusive(self): """ Are the tabs in the toolbox exclusive. """ return self.__exclusive exclusive_ = Property(bool, fget=exclusive, fset=setExclusive, designable=True, doc="Exclusive tabs") def __init__(self, parent=None, **kwargs): QFrame.__init__(self, parent, **kwargs) self.__pages = [] self.__tabButtonHeight = -1 self.__tabIconSize = QSize() self.__exclusive = False self.__setupUi() def __setupUi(self): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) # Scroll area for the contents. self.__scrollArea = \ _ToolBoxScrollArea(self, objectName="toolbox-scroll-area") self.__scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.__scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.__scrollArea.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.__scrollArea.setFrameStyle(QScrollArea.NoFrame) self.__scrollArea.setWidgetResizable(True) # A widget with all of the contents. # The tabs/contents are placed in the layout inside this widget self.__contents = QWidget(self.__scrollArea, objectName="toolbox-contents") # The layout where all the tab/pages are placed self.__contentsLayout = QVBoxLayout() self.__contentsLayout.setContentsMargins(0, 0, 0, 0) self.__contentsLayout.setSizeConstraint(QVBoxLayout.SetMinAndMaxSize) self.__contentsLayout.setSpacing(0) self.__contents.setLayout(self.__contentsLayout) self.__scrollArea.setWidget(self.__contents) layout.addWidget(self.__scrollArea) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) self.__tabActionGroup = \ QActionGroup(self, objectName="toolbox-tab-action-group") self.__tabActionGroup.setExclusive(self.__exclusive) self.__actionMapper = QSignalMapper(self) self.__actionMapper.mapped[QObject].connect(self.__onTabActionToogled) def setTabButtonHeight(self, height): """ Set the tab button height. """ if self.__tabButtonHeight != height: self.__tabButtonHeight = height for page in self.__pages: page.button.setFixedHeight(height) def tabButtonHeight(self): """ Return the tab button height. """ return self.__tabButtonHeight def setTabIconSize(self, size): """ Set the tab button icon size. """ if self.__tabIconSize != size: self.__tabIconSize = size for page in self.__pages: page.button.setIconSize(size) def tabIconSize(self): """ Return the tab icon size. """ return self.__tabIconSize def tabButton(self, index): """ Return the tab button at `index` """ return self.__pages[index].button def tabAction(self, index): """ Return open/close action for the tab at `index`. """ return self.__pages[index].action def addItem(self, widget, text, icon=None, toolTip=None): """ Append the `widget` in a new tab and return its index. Parameters ---------- widget : :class:`QWidget` A widget to be inserted. The toolbox takes ownership of the widget. text : str Name/title of the new tab. icon : :class:`QIcon`, optional An icon for the tab button. toolTip : str, optional Tool tip for the tab button. """ return self.insertItem(self.count(), widget, text, icon, toolTip) def insertItem(self, index, widget, text, icon=None, toolTip=None): """ Insert the `widget` in a new tab at position `index`. See also -------- ToolBox.addItem """ button = self.createTabButton(widget, text, icon, toolTip) self.__contentsLayout.insertWidget(index * 2, button) self.__contentsLayout.insertWidget(index * 2 + 1, widget) widget.hide() page = _ToolBoxPage(index, widget, button.defaultAction(), button) self.__pages.insert(index, page) for i in range(index + 1, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) self.__updatePositions() # Show (open) the first tab. if self.count() == 1 and index == 0: page.action.trigger() self.__updateSelected() self.updateGeometry() return index def removeItem(self, index): """ Remove the widget at `index`. .. note:: The widget hidden but is is not deleted. """ self.__contentsLayout.takeAt(2 * index + 1) self.__contentsLayout.takeAt(2 * index) page = self.__pages.pop(index) # Update the page indexes for i in range(index, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) page.button.deleteLater() # Hide the widget and reparent to self # This follows QToolBox.removeItem page.widget.hide() page.widget.setParent(self) self.__updatePositions() self.__updateSelected() self.updateGeometry() def count(self): """ Return the number of widgets inserted in the toolbox. """ return len(self.__pages) def widget(self, index): """ Return the widget at `index`. """ return self.__pages[index].widget def createTabButton(self, widget, text, icon=None, toolTip=None): """ Create the tab button for `widget`. """ action = QAction(text, self) action.setCheckable(True) if icon: action.setIcon(icon) if toolTip: action.setToolTip(toolTip) self.__tabActionGroup.addAction(action) self.__actionMapper.setMapping(action, action) action.toggled.connect(self.__actionMapper.map) button = ToolBoxTabButton(self, objectName="toolbox-tab-button") button.setDefaultAction(action) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) if self.__tabIconSize.isValid(): button.setIconSize(self.__tabIconSize) if self.__tabButtonHeight > 0: button.setFixedHeight(self.__tabButtonHeight) return button def ensureWidgetVisible(self, child, xmargin=50, ymargin=50): """ Scroll the contents so child widget instance is visible inside the viewport. """ self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin) def sizeHint(self): hint = self.__contentsLayout.sizeHint() if self.count(): # Compute max width of hidden widgets also. scroll = self.__scrollArea scroll_w = scroll.verticalScrollBar().sizeHint().width() frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2 max_w = max([p.widget.sizeHint().width() for p in self.__pages]) hint = QSize( max(max_w, hint.width()) + scroll_w + frame_w, hint.height()) return QSize(200, 200).expandedTo(hint) def __onTabActionToogled(self, action): page = find(self.__pages, action, key=attrgetter("action")) on = action.isChecked() page.widget.setVisible(on) index = page.index if index > 0: # Update the `previous` tab buttons style hints previous = self.__pages[index - 1].button flag = QStyleOptionToolBoxV2.NextIsSelected if on: previous.selected |= flag else: previous.selected &= ~flag previous.update() if index < self.count() - 1: next = self.__pages[index + 1].button flag = QStyleOptionToolBoxV2.PreviousIsSelected if on: next.selected |= flag else: next.selected &= ~flag next.update() self.tabToogled.emit(index, on) self.__contentsLayout.invalidate() def __updateSelected(self): """Update the tab buttons selected style flags. """ if self.count() == 0: return opt = QStyleOptionToolBoxV2 def update(button, next_sel, prev_sel): if next_sel: button.selected |= opt.NextIsSelected else: button.selected &= ~opt.NextIsSelected if prev_sel: button.selected |= opt.PreviousIsSelected else: button.selected &= ~opt.PreviousIsSelected button.update() if self.count() == 1: update(self.__pages[0].button, False, False) elif self.count() >= 2: pages = self.__pages for i in range(1, self.count() - 1): update(pages[i].button, pages[i + 1].action.isChecked(), pages[i - 1].action.isChecked()) def __updatePositions(self): """Update the tab buttons position style flags. """ if self.count() == 0: return elif self.count() == 1: self.__pages[0].button.position = QStyleOptionToolBoxV2.OnlyOneTab else: self.__pages[0].button.position = QStyleOptionToolBoxV2.Beginning self.__pages[-1].button.position = QStyleOptionToolBoxV2.End for p in self.__pages[1:-1]: p.button.position = QStyleOptionToolBoxV2.Middle for p in self.__pages: p.button.update()
class ProfilesPlugin: def __init__(self, iface): self.iface = iface QSettings().setValue('/UI/Customization/enabled', False) try: from profiles.tests import testerplugin from qgistester.tests import addTestModule addTestModule(testerplugin, 'Profiles plugin') except: pass self.userProfileAction = None self.profilesMenu = None iface.initializationCompleted.connect(self.initProfile) def unload(self): if self.profilesMenu is not None: self.profilesMenu.deleteLater() self.iface.removePluginMenu(self.tr('Profiles'), self.autoloadAction) self.iface.removePluginMenu(self.tr('Profiles'), self.saveProfileAction) try: from profiles.tests import testerplugin from qgistester.tests import removeTestModule removeTestModule(testerplugin, 'Profiles plugin') except: pass saveCurrentPluginState() def initGui(self): self.addMenus() def addMenus(self): if self.profilesMenu is not None: self.profilesMenu.clear() self.actions = defaultdict(list) settings = QSettings() defaultProfile = settings.value('profilesplugin/LastProfile', 'Default', unicode) autoLoad = settings.value('profilesplugin/AutoLoad', False, bool) for k, v in profiles.iteritems(): action = QAction(k, self.iface.mainWindow()) action.setCheckable(True) if k == defaultProfile and autoLoad: action.setChecked(True) action.triggered.connect(lambda _, menuName=k: self.applyProfile(menuName)) action.setObjectName('mProfilesPlugin_' + k) self.actions[v.group].append(action) actions = self.iface.mainWindow().menuBar().actions() settingsMenu = None self.profilesGroup = QActionGroup(self.iface.mainWindow()) if self.profilesMenu is None: for action in actions: if action.menu().objectName() == 'mSettingsMenu': settingsMenu = action.menu() self.profilesMenu = QMenu(settingsMenu) self.profilesMenu.setObjectName('mProfilesPlugin') self.profilesMenu.setTitle(self.tr('Profiles')) settingsMenu.addMenu(self.profilesMenu) break if self.profilesMenu is not None: for k,v in self.actions.iteritems(): submenu = QMenu(self.profilesMenu) submenu.setObjectName('mProfilesPlugin_submenu_' + k) submenu.setTitle(k) for action in v: self.profilesGroup.addAction(action) submenu.addAction(action) self.profilesMenu.addMenu(submenu) self.profilesMenu.addSeparator() settings = QSettings() def _setAutoLoad(): settings.setValue('profilesplugin/AutoLoad', self.autoloadAction.isChecked()) self.autoloadAction = QAction(self.tr('Auto-load last profile on QGIS start'), iface.mainWindow()) self.autoloadAction.setCheckable(True) autoLoad = settings.value('profilesplugin/AutoLoad', False, bool) self.autoloadAction.setChecked(autoLoad) self.autoloadAction.setObjectName('mProfilesPluginAutoLoad') self.autoloadAction.triggered.connect(_setAutoLoad) self.profilesMenu.addAction(self.autoloadAction) self.saveProfileAction = QAction(self.tr('Profiles manager...'), self.iface.mainWindow()) self.saveProfileAction.setObjectName('mProfilesPluginProfilesManager') self.saveProfileAction.triggered.connect(self.saveProfile) self.profilesMenu.addAction(self.saveProfileAction) self.showHelpAction = QAction(self.tr('Help'), self.iface.mainWindow()) self.showHelpAction.setIcon(QgsApplication.getThemeIcon('/mActionHelpContents.svg')) self.showHelpAction.setObjectName('mProfilesPluginShowHelp') self.showHelpAction.triggered.connect(self.showHelp) self.profilesMenu.addAction(self.showHelpAction) def applyProfile(self, name): profile = profiles.get(name) applyProfile(profile) def initProfile(self): settings = QSettings() autoLoad = settings.value('profilesplugin/AutoLoad', False, bool) if autoLoad: profileName = settings.value('profilesplugin/LastProfile', '', unicode) if profileName in profiles: profile = profiles[profileName] if not profile.hasToInstallPlugins(): applyProfile(profile, False) def saveProfile(self): dlg = ProfileManager(iface.mainWindow()) dlg.exec_() def showHelp(self): if not QDesktopServices.openUrl( QUrl('file://{}'.format(os.path.join(pluginPath, 'docs', 'html', 'index.html')))): QMessageBox.warning(None, self.tr('Error'), self.tr('Can not open help URL in browser')) def tr(self, text): return QCoreApplication.translate('Profiles', text)
def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.dirty = False self.isLoaded = False self.calibration_enabled = False self.aggregate_enabled = False self.setObjectName("MainWindow") self.resize(800, 600) self.setWindowTitle("OpenFisca") app_icon = get_icon('OpenFisca22.png') self.setWindowIcon(app_icon) self.setLocale(QLocale(QLocale.French, QLocale.France)) self.setDockOptions(QMainWindow.AllowNestedDocks | QMainWindow.AllowTabbedDocks | QMainWindow.AnimatedDocks) self.centralwidget = QWidget(self) self.gridLayout = QGridLayout(self.centralwidget) self.setCentralWidget(self.centralwidget) self.centralwidget.hide() self.statusbar = QStatusBar(self) self.statusbar.setObjectName("statusbar") self.setStatusBar(self.statusbar) # Showing splash screen pixmap = QPixmap(':/images/splash.png', 'png') self.splash = QSplashScreen(pixmap) font = self.splash.font() font.setPixelSize(10) self.splash.setFont(font) self.splash.show() self.splash.showMessage( "Initialisation...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) # if CONF.get('main', 'current_version', '') != __version__: # CONF.set('main', 'current_version', __version__) # Execute here the actions to be performed only once after # each update (there is nothing there for now, but it could # be useful some day... self.scenario = Scenario() # Preferences self.general_prefs = [SimConfigPage, PathConfigPage, CalConfigPage] self.oldXAXIS = 'sal' self.reforme = False self.apply_settings() # Dockwidgets creation self.splash.showMessage( "Creating widgets...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) self.create_dockwidgets() self.populate_mainwidow() ################################################################# ## Menu initialization ################################################################# self.splash.showMessage( "Creating menubar...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) # Menu Fichier self.file_menu = self.menuBar().addMenu("Fichier") action_export_png = create_action(self, 'Exporter le graphique', icon='document-save png.png', triggered=self._graph.save_figure) action_export_csv = create_action(self, 'Exporter la table', icon='document-save csv.png', triggered=self._table.saveCsv) action_pref = create_action(self, u'Préférences', QKeySequence.Preferences, icon='preferences-desktop.png', triggered=self.edit_preferences) action_quit = create_action(self, 'Quitter', QKeySequence.Quit, icon='process-stop.png', triggered=SLOT('close()')) file_actions = [ action_export_png, action_export_csv, None, action_pref, None, action_quit ] add_actions(self.file_menu, file_actions) # Menu Edit self.edit_menu = self.menuBar().addMenu(u"Édition") action_copy = create_action(self, 'Copier', QKeySequence.Copy, triggered=self.global_callback, data='copy') edit_actions = [None, action_copy] add_actions(self.edit_menu, edit_actions) # Menu Simulation self.simulation_menu = self.menuBar().addMenu(u"Simulation") self.action_refresh_bareme = create_action( self, u'Calculer barèmes', shortcut='F9', icon='calculator_green.png', triggered=self.refresh_bareme) self.action_refresh_aggregate = create_action( self, u'Calculer aggrégats', shortcut='F10', icon='calculator_blue.png', triggered=self.refresh_aggregate) self.action_calibrate = create_action(self, u'Caler les poids', shortcut='CTRL+K', icon='scale22.png', triggered=self.calibrate) action_bareme = create_action(self, u'Barème', icon='bareme22.png', toggled=self.modeBareme) action_cas_type = create_action(self, u'Cas type', icon='castype22.png', toggled=self.modeCasType) action_mode_reforme = create_action( self, u'Réforme', icon='comparison22.png', toggled=self.modeReforme, tip= u"Différence entre la situation simulée et la situation actuelle") mode_group = QActionGroup(self) mode_group.addAction(action_bareme) mode_group.addAction(action_cas_type) self.mode = 'bareme' action_bareme.trigger() simulation_actions = [ self.action_refresh_bareme, self.action_refresh_aggregate, None, self.action_calibrate, None, action_bareme, action_cas_type, None, action_mode_reforme ] add_actions(self.simulation_menu, simulation_actions) # Menu Help help_menu = self.menuBar().addMenu("&Aide") action_about = create_action(self, u"&About OpenFisca", triggered=self.helpAbout) action_help = create_action(self, "&Aide", QKeySequence.HelpContents, triggered=self.helpHelp) help_actions = [action_about, action_help] add_actions(help_menu, help_actions) # Display Menu view_menu = self.createPopupMenu() view_menu.setTitle("&Affichage") self.menuBar().insertMenu(help_menu.menuAction(), view_menu) # Toolbar self.main_toolbar = self.create_toolbar(u"Barre d'outil", 'main_toolbar') toolbar_actions = [ action_export_png, action_export_csv, None, self.action_refresh_bareme, self.action_refresh_aggregate, None, self.action_calibrate, None, action_bareme, action_cas_type, None, action_mode_reforme ] add_actions(self.main_toolbar, toolbar_actions) self.connect(self._menage, SIGNAL('changed()'), self.changed_bareme) self.connect(self._parametres, SIGNAL('changed()'), self.changed_param) self.connect(self._aggregate_output, SIGNAL('calculated()'), self.calculated) self.connect(self, SIGNAL('weights_changed()'), self.refresh_aggregate) self.connect(self, SIGNAL('bareme_only()'), self.switch_bareme_only) # Window settings self.splash.showMessage( "Restoring settings...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) settings = QSettings() size = settings.value('MainWindow/Size', QVariant(QSize(800, 600))).toSize() self.resize(size) position = settings.value('MainWindow/Position', QVariant(QPoint(0, 0))).toPoint() self.move(position) self.restoreState(settings.value("MainWindow/State").toByteArray()) self.splash.showMessage( "Loading survey data...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) self.enable_aggregate(True) self.refresh_bareme() self.isLoaded = True self.splash.hide()
class ContextMenu(QObject): def __init__(self, quarterwidget): QObject.__init__(self, quarterwidget) #QObject.__init__(quarterwidget) self._quarterwidget = quarterwidget self._rendermanager = self._quarterwidget.getSoRenderManager() self.contextmenu = QMenu() self.functionsmenu = QMenu("Functions") self.rendermenu = QMenu("Render Mode") self.stereomenu = QMenu("Stereo Mode") self.transparencymenu = QMenu("Transparency Type") self.functionsgroup = QActionGroup(self) self.stereomodegroup = QActionGroup(self) self.rendermodegroup = QActionGroup(self) self.transparencytypegroup = QActionGroup(self) self.rendermodes = [] self.rendermodes.append((SoRenderManager.AS_IS, "as is")) self.rendermodes.append((SoRenderManager.WIREFRAME, "wireframe")) self.rendermodes.append( (SoRenderManager.WIREFRAME_OVERLAY, "wireframe overlay")) self.rendermodes.append((SoRenderManager.POINTS, "points")) self.rendermodes.append((SoRenderManager.HIDDEN_LINE, "hidden line")) self.rendermodes.append((SoRenderManager.BOUNDING_BOX, "bounding box")) self.stereomodes = [] self.stereomodes.append((SoRenderManager.MONO, "mono")) self.stereomodes.append((SoRenderManager.ANAGLYPH, "anaglyph")) self.stereomodes.append((SoRenderManager.QUAD_BUFFER, "quad buffer")) self.stereomodes.append( (SoRenderManager.INTERLEAVED_ROWS, "interleaved rows")) self.stereomodes.append( (SoRenderManager.INTERLEAVED_COLUMNS, "interleaved columns")) self.transparencytypes = [] self.transparencytypes.append((SoGLRenderAction.NONE, "none")) self.transparencytypes.append( (SoGLRenderAction.SCREEN_DOOR, "screen door")) self.transparencytypes.append((SoGLRenderAction.ADD, "add")) self.transparencytypes.append( (SoGLRenderAction.DELAYED_ADD, "delayed add")) self.transparencytypes.append( (SoGLRenderAction.SORTED_OBJECT_ADD, "sorted object add")) self.transparencytypes.append((SoGLRenderAction.BLEND, "blend")) self.transparencytypes.append( (SoGLRenderAction.DELAYED_BLEND, "delayed blend")) self.transparencytypes.append( (SoGLRenderAction.SORTED_OBJECT_BLEND, "sorted object blend")) self.transparencytypes.append( (SoGLRenderAction.SORTED_OBJECT_SORTED_TRIANGLE_ADD, "sorted object sorted triangle add")) self.transparencytypes.append( (SoGLRenderAction.SORTED_OBJECT_SORTED_TRIANGLE_BLEND, "sorted object sorted triangle blend")) self.transparencytypes.append( (SoGLRenderAction.SORTED_LAYERS_BLEND, "sorted layers blend")) self.rendermodeactions = [] for first, second in self.rendermodes: action = QAction(second, self) action.setCheckable(True) action.setChecked(self._rendermanager.getRenderMode() == first) action.setData(QVariant(first)) self.rendermodeactions.append(action) self.rendermodegroup.addAction(action) self.rendermenu.addAction(action) self.stereomodeactions = [] for first, second in self.stereomodes: action = QAction(second, self) action.setCheckable(True) action.setChecked(self._rendermanager.getStereoMode() == first) action.setData(QVariant(first)) self.stereomodeactions.append(action) self.stereomodegroup.addAction(action) self.stereomenu.addAction(action) self.transparencytypeactions = [] for first, second in self.transparencytypes: action = QAction(second, self) action.setCheckable(True) action.setChecked(self._rendermanager.getGLRenderAction(). getTransparencyType() == first) action.setData(QVariant(first)) self.transparencytypeactions.append(action) self.transparencytypegroup.addAction(action) self.transparencymenu.addAction(action) viewall = QAction("View All", self) seek = QAction("Seek", self) self.functionsmenu.addAction(viewall) self.functionsmenu.addAction(seek) self.connect(seek, QtCore.SIGNAL("triggered(bool)"), self.seek) self.connect(viewall, QtCore.SIGNAL("triggered(bool)"), self.viewAll) self.connect(self.rendermodegroup, QtCore.SIGNAL("triggered(QAction *)"), self.changeRenderMode) self.connect(self.stereomodegroup, QtCore.SIGNAL("triggered(QAction *)"), self.changeStereoMode) self.connect(self.transparencytypegroup, QtCore.SIGNAL("triggered(QAction *)"), self.changeTransparencyType) self.contextmenu.addMenu(self.functionsmenu) self.contextmenu.addMenu(self.rendermenu) self.contextmenu.addMenu(self.stereomenu) self.contextmenu.addMenu(self.transparencymenu) def __del__(self): del self.functionsmenu del self.rendermenu del self.stereomenu del self.transparencymenu del self.contextmenu def getMenu(self): return self.contextmenu def exec_(self, pos): self._processEvent("sim.coin3d.coin.PopupMenuOpen") self.contextmenu.exec_(pos) def seek(self, checked): self._processEvent("sim.coin3d.coin.navigation.Seek") def viewAll(self, checked): self._processEvent("sim.coin3d.coin.navigation.ViewAll") def _processEvent(self, eventname): eventmanager = self._quarterwidget.getSoEventManager() for c in range(eventmanager.getNumSoScXMLStateMachines()): sostatemachine = eventmanager.getSoScXMLStateMachine(c) sostatemachine.queueEvent(coin.SbName(eventname)) sostatemachine.processEventQueue() def changeRenderMode(self, action): self._rendermanager.setRenderMode(action.data().toInt()[0]) def changeStereoMode(self, action): self._rendermanager.setStereoMode(action.data().toInt()[0]) def changeTransparencyType(self, action): self._quarterwidget.setTransparencyType(action.data().toInt()[0])
def __init__(self, parent = None): super(MainWindow, self).__init__(parent) self.dirty = False self.isLoaded = False self.calibration_enabled = False self.aggregate_enabled = False self.setObjectName("MainWindow") self.resize(800, 600) self.setWindowTitle("OpenFisca") app_icon = get_icon('OpenFisca22.png') self.setWindowIcon(app_icon) self.setLocale(QLocale(QLocale.French, QLocale.France)) self.setDockOptions(QMainWindow.AllowNestedDocks|QMainWindow.AllowTabbedDocks|QMainWindow.AnimatedDocks) self.centralwidget = QWidget(self) self.gridLayout = QGridLayout(self.centralwidget) self.setCentralWidget(self.centralwidget) self.centralwidget.hide() self.statusbar = QStatusBar(self) self.statusbar.setObjectName("statusbar") self.setStatusBar(self.statusbar) # Showing splash screen pixmap = QPixmap(':/images/splash.png', 'png') self.splash = QSplashScreen(pixmap) font = self.splash.font() font.setPixelSize(10) self.splash.setFont(font) self.splash.show() self.splash.showMessage("Initialisation...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) # if CONF.get('main', 'current_version', '') != __version__: # CONF.set('main', 'current_version', __version__) # Execute here the actions to be performed only once after # each update (there is nothing there for now, but it could # be useful some day... self.scenario = Scenario() # Preferences self.general_prefs = [SimConfigPage, PathConfigPage, CalConfigPage] self.oldXAXIS = 'sal' self.reforme = False self.apply_settings() # Dockwidgets creation self.splash.showMessage("Creating widgets...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) self.create_dockwidgets() self.populate_mainwidow() ################################################################# ## Menu initialization ################################################################# self.splash.showMessage("Creating menubar...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) # Menu Fichier self.file_menu = self.menuBar().addMenu("Fichier") action_export_png = create_action(self, 'Exporter le graphique', icon = 'document-save png.png', triggered = self._graph.save_figure) action_export_csv = create_action(self, 'Exporter la table', icon = 'document-save csv.png', triggered = self._table.saveCsv) action_pref = create_action(self, u'Préférences', QKeySequence.Preferences, icon = 'preferences-desktop.png', triggered = self.edit_preferences) action_quit = create_action(self, 'Quitter', QKeySequence.Quit, icon = 'process-stop.png', triggered = SLOT('close()')) file_actions = [action_export_png, action_export_csv,None, action_pref, None, action_quit] add_actions(self.file_menu, file_actions) # Menu Edit self.edit_menu = self.menuBar().addMenu(u"Édition") action_copy = create_action(self, 'Copier', QKeySequence.Copy, triggered = self.global_callback, data = 'copy') edit_actions = [None, action_copy] add_actions(self.edit_menu, edit_actions) # Menu Simulation self.simulation_menu = self.menuBar().addMenu(u"Simulation") self.action_refresh_bareme = create_action(self, u'Calculer barèmes', shortcut = 'F8', icon = 'view-refresh.png', triggered = self.refresh_bareme) self.action_refresh_calibration = create_action(self, u'Calibrer', shortcut = 'F9', icon = 'view-refresh.png', triggered = self.refresh_calibration) self.action_refresh_aggregate = create_action(self, u'Calculer aggrégats', shortcut = 'F10', icon = 'view-refresh.png', triggered = self.refresh_aggregate) action_bareme = create_action(self, u'Barème', icon = 'bareme22.png', toggled = self.modeBareme) action_cas_type = create_action(self, u'Cas type', icon = 'castype22.png', toggled = self.modeCasType) action_mode_reforme = create_action(self, u'Réforme', icon = 'comparison22.png', toggled = self.modeReforme, tip = u"Différence entre la situation simulée et la situation actuelle") mode_group = QActionGroup(self) mode_group.addAction(action_bareme) mode_group.addAction(action_cas_type) self.mode = 'bareme' action_bareme.trigger() simulation_actions = [self.action_refresh_bareme, self.action_refresh_calibration, self.action_refresh_aggregate , None, action_bareme, action_cas_type, None, action_mode_reforme] add_actions(self.simulation_menu, simulation_actions) # Menu Help help_menu = self.menuBar().addMenu("&Aide") action_about = create_action(self, u"&About OpenFisca", triggered = self.helpAbout) action_help = create_action(self, "&Aide", QKeySequence.HelpContents, triggered = self.helpHelp) help_actions = [action_about, action_help] add_actions(help_menu, help_actions) # Display Menu view_menu = self.createPopupMenu() view_menu.setTitle("&Affichage") self.menuBar().insertMenu(help_menu.menuAction(), view_menu) # Toolbar self.main_toolbar = self.create_toolbar(u"Barre d'outil", 'main_toolbar') toolbar_actions = [action_export_png, action_export_csv, None, self.action_refresh_bareme, self.action_refresh_calibration, self.action_refresh_aggregate, None, action_bareme, action_cas_type, None, action_mode_reforme] add_actions(self.main_toolbar, toolbar_actions) self.connect(self._menage, SIGNAL('changed()'), self.changed_bareme) self.connect(self._parametres, SIGNAL('changed()'), self.changed_param) self.connect(self._aggregate_output, SIGNAL('calculated()'), self.calculated) self.connect(self._calibration, SIGNAL('param_or_margins_changed()'), self.param_or_margins_changed) self.connect(self._calibration, SIGNAL('calibrated()'), self.calibrated) # Window settings self.splash.showMessage("Restoring settings...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) settings = QSettings() size = settings.value('MainWindow/Size', QVariant(QSize(800,600))).toSize() self.resize(size) position = settings.value('MainWindow/Position', QVariant(QPoint(0,0))).toPoint() self.move(position) self.restoreState(settings.value("MainWindow/State").toByteArray()) self.splash.showMessage("Loading survey data...", Qt.AlignBottom | Qt.AlignCenter | Qt.AlignAbsolute, QColor(Qt.black)) self.enable_aggregate(True) self.enable_calibration(True) self.refresh_bareme() self.isLoaded = True self.splash.hide()
class MainUI(QtGui.QMainWindow, main_window_class): connectionLostSignal = pyqtSignal(str, str) connectionInitiatedSignal = pyqtSignal(str) batteryUpdatedSignal = pyqtSignal(int, object, object) connectionDoneSignal = pyqtSignal(str) connectionFailedSignal = pyqtSignal(str, str) disconnectedSignal = pyqtSignal(str) linkQualitySignal = pyqtSignal(int) _input_device_error_signal = pyqtSignal(str) _input_discovery_signal = pyqtSignal(object) _log_error_signal = pyqtSignal(object, str) def __init__(self, *args): super(MainUI, self).__init__(*args) self.setupUi(self) self.cf = Crazyflie(ro_cache=sys.path[0] + "/cflib/cache", rw_cache=sys.path[1] + "/cache") cflib.crtp.init_drivers( enable_debug_driver=GuiConfig().get("enable_debug_driver")) # Create the connection dialogue self.connectDialogue = ConnectDialogue() # Create and start the Input Reader self._statusbar_label = QLabel("Loading device and configuration.") self.statusBar().addWidget(self._statusbar_label) self.joystickReader = JoystickReader(cf=self.cf) self._active_device = "" self.configGroup = QActionGroup(self._menu_mappings, exclusive=True) self._load_input_data() self._update_input ConfigManager().conf_needs_reload.add_callback(self._reload_configs) # Connections for the Connect Dialogue self.connectDialogue.requestConnectionSignal.connect(self.cf.open_link) self.connectionDoneSignal.connect(self.connectionDone) self.cf.connection_failed.add_callback( self.connectionFailedSignal.emit) self.connectionFailedSignal.connect(self.connectionFailed) self._input_device_error_signal.connect(self.inputDeviceError) self.joystickReader.device_error.add_callback( self._input_device_error_signal.emit) self._input_discovery_signal.connect(self.device_discovery) self.joystickReader.device_discovery.add_callback( self._input_discovery_signal.emit) # Connect UI signals self.menuItemConnect.triggered.connect(self.connectButtonClicked) self.logConfigAction.triggered.connect(self.doLogConfigDialogue) self.connectButton.clicked.connect(self.connectButtonClicked) self.quickConnectButton.clicked.connect(self.quickConnect) self.menuItemQuickConnect.triggered.connect(self.quickConnect) self.menuItemConfInputDevice.triggered.connect(self.configInputDevice) self.menuItemExit.triggered.connect(self.closeAppRequest) self.batteryUpdatedSignal.connect(self.updateBatteryVoltage) self._menuitem_rescandevices.triggered.connect(self._rescan_devices) self._menuItem_openconfigfolder.triggered.connect( self._open_config_folder) self._auto_reconnect_enabled = GuiConfig().get("auto_reconnect") self.autoReconnectCheckBox.toggled.connect( self._auto_reconnect_changed) self.autoReconnectCheckBox.setChecked( GuiConfig().get("auto_reconnect")) # Do not queue data from the controller output to the Crazyflie wrapper # to avoid latency #self.joystickReader.sendControlSetpointSignal.connect( # self.cf.commander.send_setpoint, # Qt.DirectConnection) self.joystickReader.input_updated.add_callback( self.cf.commander.send_setpoint) # Connection callbacks and signal wrappers for UI protection self.cf.connected.add_callback(self.connectionDoneSignal.emit) self.connectionDoneSignal.connect(self.connectionDone) self.cf.disconnected.add_callback(self.disconnectedSignal.emit) self.disconnectedSignal.connect( lambda linkURI: self.setUIState(UIState.DISCONNECTED, linkURI)) self.cf.connection_lost.add_callback(self.connectionLostSignal.emit) self.connectionLostSignal.connect(self.connectionLost) self.cf.connection_requested.add_callback( self.connectionInitiatedSignal.emit) self.connectionInitiatedSignal.connect( lambda linkURI: self.setUIState(UIState.CONNECTING, linkURI)) self._log_error_signal.connect(self._logging_error) # Connect link quality feedback self.cf.link_quality_updated.add_callback(self.linkQualitySignal.emit) self.linkQualitySignal.connect( lambda percentage: self.linkQualityBar.setValue(percentage)) # Set UI state in disconnected buy default self.setUIState(UIState.DISCONNECTED) # Parse the log configuration files self.logConfigReader = LogConfigReader(self.cf) # Add things to helper so tabs can access it cfclient.ui.pluginhelper.cf = self.cf cfclient.ui.pluginhelper.inputDeviceReader = self.joystickReader cfclient.ui.pluginhelper.logConfigReader = self.logConfigReader self.logConfigDialogue = LogConfigDialogue(cfclient.ui.pluginhelper) self._bootloader_dialog = BootloaderDialog(cfclient.ui.pluginhelper) self.menuItemBootloader.triggered.connect(self._bootloader_dialog.show) self._about_dialog = AboutDialog(cfclient.ui.pluginhelper) self.menuItemAbout.triggered.connect(self._about_dialog.show) # Loading toolboxes (A bit of magic for a lot of automatic) self.toolboxes = [] self.toolboxesMenuItem.setMenu(QtGui.QMenu()) for t_class in cfclient.ui.toolboxes.toolboxes: toolbox = t_class(cfclient.ui.pluginhelper) dockToolbox = MyDockWidget(toolbox.getName()) dockToolbox.setWidget(toolbox) self.toolboxes += [ dockToolbox, ] # Add menu item for the toolbox item = QtGui.QAction(toolbox.getName(), self) item.setCheckable(True) item.triggered.connect(self.toggleToolbox) self.toolboxesMenuItem.menu().addAction(item) dockToolbox.closed.connect(lambda: self.toggleToolbox(False)) # Setup some introspection item.dockToolbox = dockToolbox item.menuItem = item dockToolbox.dockToolbox = dockToolbox dockToolbox.menuItem = item # Load and connect tabs self.tabsMenuItem.setMenu(QtGui.QMenu()) tabItems = {} self.loadedTabs = [] for tabClass in cfclient.ui.tabs.available: tab = tabClass(self.tabs, cfclient.ui.pluginhelper) item = QtGui.QAction(tab.getMenuName(), self) item.setCheckable(True) item.toggled.connect(tab.toggleVisibility) self.tabsMenuItem.menu().addAction(item) tabItems[tab.getTabName()] = item self.loadedTabs.append(tab) if not tab.enabled: item.setEnabled(False) # First instantiate all tabs and then open them in the correct order try: for tName in GuiConfig().get("open_tabs").split(","): t = tabItems[tName] if (t != None and t.isEnabled()): # Toggle though menu so it's also marked as open there t.toggle() except Exception as e: logger.warning("Exception while opening tabs [%s]", e) def setUIState(self, newState, linkURI=""): self.uiState = newState if (newState == UIState.DISCONNECTED): self.setWindowTitle("Not connected") self.menuItemConnect.setText("Connect to Crazyflie") self.connectButton.setText("Connect") self.menuItemQuickConnect.setEnabled(True) self.batteryBar.setValue(3000) self.linkQualityBar.setValue(0) self.menuItemBootloader.setEnabled(True) self.logConfigAction.setEnabled(False) if (len(GuiConfig().get("link_uri")) > 0): self.quickConnectButton.setEnabled(True) if (newState == UIState.CONNECTED): s = "Connected on %s" % linkURI self.setWindowTitle(s) self.menuItemConnect.setText("Disconnect") self.connectButton.setText("Disconnect") self.logConfigAction.setEnabled(True) if (newState == UIState.CONNECTING): s = "Connecting to %s ..." % linkURI self.setWindowTitle(s) self.menuItemConnect.setText("Cancel") self.connectButton.setText("Cancel") self.quickConnectButton.setEnabled(False) self.menuItemBootloader.setEnabled(False) self.menuItemQuickConnect.setEnabled(False) @pyqtSlot(bool) def toggleToolbox(self, display): menuItem = self.sender().menuItem dockToolbox = self.sender().dockToolbox if display and not dockToolbox.isVisible(): dockToolbox.widget().enable() self.addDockWidget(dockToolbox.widget().preferedDockArea(), dockToolbox) dockToolbox.show() elif not display: dockToolbox.widget().disable() self.removeDockWidget(dockToolbox) dockToolbox.hide() menuItem.setChecked(False) def _rescan_devices(self): self._statusbar_label.setText("No inputdevice connected!") self._menu_devices.clear() self._active_device = "" self.joystickReader.stop_input() for c in self._menu_mappings.actions(): c.setEnabled(False) devs = self.joystickReader.getAvailableDevices() if (len(devs) > 0): self.device_discovery(devs) def configInputDevice(self): self.inputConfig = InputConfigDialogue(self.joystickReader) self.inputConfig.show() def _auto_reconnect_changed(self, checked): self._auto_reconnect_enabled = checked GuiConfig().set("auto_reconnect", checked) logger.info("Auto reconnect enabled: %s", checked) def doLogConfigDialogue(self): self.logConfigDialogue.show() def updateBatteryVoltage(self, timestamp, data, logconf): self.batteryBar.setValue(int(data["pm.vbat"] * 1000)) def connectionDone(self, linkURI): self.setUIState(UIState.CONNECTED, linkURI) GuiConfig().set("link_uri", linkURI) lg = LogConfig("Battery", 1000) lg.add_variable("pm.vbat", "float") self.cf.log.add_config(lg) if lg.valid: lg.data_received_cb.add_callback(self.batteryUpdatedSignal.emit) lg.error_cb.add_callback(self._log_error_signal.emit) lg.start() else: logger.warning("Could not setup loggingblock!") def _logging_error(self, log_conf, msg): QMessageBox.about( self, "Log error", "Error when starting log config" " [%s]: %s" % (log_conf.name, msg)) def connectionLost(self, linkURI, msg): if not self._auto_reconnect_enabled: if (self.isActiveWindow()): warningCaption = "Communication failure" error = "Connection lost to %s: %s" % (linkURI, msg) QMessageBox.critical(self, warningCaption, error) self.setUIState(UIState.DISCONNECTED, linkURI) else: self.quickConnect() def connectionFailed(self, linkURI, error): if not self._auto_reconnect_enabled: msg = "Failed to connect on %s: %s" % (linkURI, error) warningCaption = "Communication failure" QMessageBox.critical(self, warningCaption, msg) self.setUIState(UIState.DISCONNECTED, linkURI) else: self.quickConnect() def closeEvent(self, event): self.hide() self.cf.close_link() GuiConfig().save_file() def connectButtonClicked(self): if (self.uiState == UIState.CONNECTED): self.cf.close_link() elif (self.uiState == UIState.CONNECTING): self.cf.close_link() self.setUIState(UIState.DISCONNECTED) else: self.connectDialogue.show() def inputDeviceError(self, error): self.cf.close_link() QMessageBox.critical(self, "Input device error", error) def _load_input_data(self): self.joystickReader.stop_input() # Populate combo box with available input device configurations for c in ConfigManager().get_list_of_configs(): node = QAction(c, self._menu_mappings, checkable=True, enabled=False) node.toggled.connect(self._inputconfig_selected) self.configGroup.addAction(node) self._menu_mappings.addAction(node) def _reload_configs(self, newConfigName): # remove the old actions from the group and the menu for action in self._menu_mappings.actions(): self.configGroup.removeAction(action) self._menu_mappings.clear() # reload the conf files, and populate the menu self._load_input_data() self._update_input(self._active_device, newConfigName) def _update_input(self, device="", config=""): self.joystickReader.stop_input() self._active_config = str(config) self._active_device = str(device) GuiConfig().set("input_device", self._active_device) GuiConfig().get("device_config_mapping")[ self._active_device] = self._active_config self.joystickReader.start_input(self._active_device, self._active_config) # update the checked state of the menu items for c in self._menu_mappings.actions(): c.setEnabled(True) if c.text() == self._active_config: c.setChecked(True) for c in self._menu_devices.actions(): c.setEnabled(True) if c.text() == self._active_device: c.setChecked(True) # update label if device == "" and config == "": self._statusbar_label.setText("No input device selected") elif config == "": self._statusbar_label.setText("Using [%s] - " "No input config selected" % (self._active_device)) else: self._statusbar_label.setText( "Using [%s] with config [%s]" % (self._active_device, self._active_config)) def _inputdevice_selected(self, checked): if (not checked): return self.joystickReader.stop_input() sender = self.sender() self._active_device = sender.text() device_config_mapping = GuiConfig().get("device_config_mapping") if (self._active_device in device_config_mapping.keys()): self._current_input_config = device_config_mapping[str( self._active_device)] else: self._current_input_config = self._menu_mappings.actions()[0].text( ) GuiConfig().set("input_device", str(self._active_device)) for c in self._menu_mappings.actions(): if (c.text() == self._current_input_config): c.setChecked(True) self.joystickReader.start_input(str(sender.text()), self._current_input_config) self._statusbar_label.setText( "Using [%s] with config [%s]" % (self._active_device, self._current_input_config)) def _inputconfig_selected(self, checked): if (not checked): return self._update_input(self._active_device, self.sender().text()) def device_discovery(self, devs): group = QActionGroup(self._menu_devices, exclusive=True) for d in devs: node = QAction(d["name"], self._menu_devices, checkable=True) node.toggled.connect(self._inputdevice_selected) group.addAction(node) self._menu_devices.addAction(node) if (d["name"] == GuiConfig().get("input_device")): self._active_device = d["name"] if (len(self._active_device) == 0): self._active_device = self._menu_devices.actions()[0].text() device_config_mapping = GuiConfig().get("device_config_mapping") if (device_config_mapping): if (self._active_device in device_config_mapping.keys()): self._current_input_config = device_config_mapping[str( self._active_device)] else: self._current_input_config = self._menu_mappings.actions( )[0].text() else: self._current_input_config = self._menu_mappings.actions()[0].text( ) # Now we know what device to use and what mapping, trigger the events # to change the menus and start the input for c in self._menu_mappings.actions(): c.setEnabled(True) if (c.text() == self._current_input_config): c.setChecked(True) for c in self._menu_devices.actions(): if (c.text() == self._active_device): c.setChecked(True) def quickConnect(self): try: self.cf.open_link(GuiConfig().get("link_uri")) except KeyError: self.cf.open_link("") def _open_config_folder(self): QDesktopServices.openUrl( QUrl("file:///" + QDir.toNativeSeparators(sys.path[1]))) def closeAppRequest(self): self.close() sys.exit(0)
class FilterClauseWidget(QWidget, Ui_FilterClauseWidget): clauseAdded = pyqtSignal() clauseRemoved = pyqtSignal(int) clauseChanged = pyqtSignal(int) def __init__(self, parent=None): super(FilterClauseWidget, self).__init__(parent) self.setupUi(self) self._filterIndex = -1 self._filterType = FilterType.Include self._filterActionStatus = -1 self._siteCode = '' self._addIcon = QIcon(':/plugins/ark/filter/addFilter.svg') self._addAction = QAction(self._addIcon, 'Add filter', self) self._addAction.setStatusTip('Add filter') self._addAction.triggered.connect(self._addFilterClicked) self._removeIcon = QIcon(':/plugins/ark/filter/removeFilter.svg') self._removeAction = QAction(self._removeIcon, 'Remove filter', self) self._removeAction.setStatusTip('Remove filter') self._removeAction.triggered.connect(self._removeFilterClicked) self.setFilterAction(FilterWidgetAction.AddFilter) self._includeIcon = QIcon(':/plugins/ark/filter/includeFilter.png') self._includeAction = QAction(self._includeIcon, 'Include', self) self._includeAction.setStatusTip('Include items in selection') self._includeAction.setCheckable(True) self._includeAction.triggered.connect(self._includeFilterChecked) self._excludeIcon = QIcon(':/plugins/ark/filter/excludeFilter.png') self._excludeAction = QAction(self._excludeIcon, 'Exclude', self) self._excludeAction.setStatusTip('Exclude items from selection') self._excludeAction.setCheckable(True) self._excludeAction.triggered.connect(self._excludeFilterChecked) self._selectIcon = QIcon(':/plugins/ark/filter/selectFilter.svg') self._selectAction = QAction(self._selectIcon, 'Select', self) self._selectAction.setStatusTip('Select items') self._selectAction.setCheckable(True) self._selectAction.triggered.connect(self._selectFilterChecked) self._highlightIcon = QIcon(':/plugins/ark/filter/highlightFilter.svg') self._highlightAction = QAction(self._highlightIcon, 'Highlight', self) self._highlightAction.setStatusTip('Highlight items') self._highlightAction.setCheckable(True) self._highlightAction.triggered.connect(self._highlightFilterChecked) self._typeActionGroup = QActionGroup(self) self._typeActionGroup.addAction(self._includeAction) self._typeActionGroup.addAction(self._excludeAction) self._typeActionGroup.addAction(self._selectAction) self._typeActionGroup.addAction(self._highlightAction) self._colorTool = QgsColorButtonV2(self) self._colorTool.setAllowAlpha(True) self._colorTool.setColorDialogTitle('Choose Highlight Color') self._colorTool.setContext('Choose Highlight Color') self._colorTool.setDefaultColor(Application.highlightFillColor()) self._colorTool.setToDefaultColor() self._colorTool.colorChanged.connect(self._colorChanged) self._colorAction = QWidgetAction(self) self._colorAction.setDefaultWidget(self._colorTool) self._typeMenu = QMenu(self) self._typeMenu.addActions(self._typeActionGroup.actions()) self._typeMenu.addSeparator() self._typeMenu.addAction(self._colorAction) self.filterTypeTool.setMenu(self._typeMenu) self._setFilterType(FilterType.Include) def index(self): return self._filterIndex def setIndex(self, index): self._filterIndex = index def clause(self): cl = FilterClause() cl.item = Item(self.siteCode(), self.classCode(), self.filterRange()) cl.action = self.filterType() cl.color = self.color() return cl def setClause(self, clause): self.blockSignals(True) self._colorTool.blockSignals(True) self._siteCode = clause.item.siteCode() self.filterClassCombo.setCurrentIndex(self.filterClassCombo.findData(clause.item.classCode())) self.filterRangeCombo.setEditText(clause.item.itemId()) self._setFilterType(clause.action) # self._colorTool.setColor(clause.color) self._colorTool.blockSignals(False) self.blockSignals(False) def _setFilterType(self, filterType): self._filterType = filterType if filterType == FilterType.Exclude: self._excludeAction.setChecked(True) self.filterTypeTool.setDefaultAction(self._excludeAction) elif filterType == FilterType.Select: self._selectAction.setChecked(True) self.filterTypeTool.setDefaultAction(self._selectAction) elif filterType == FilterType.Highlight: self._highlightAction.setChecked(True) self.filterTypeTool.setDefaultAction(self._highlightAction) else: self._includeAction.setChecked(True) self.filterTypeTool.setDefaultAction(self._includeAction) def filterType(self): return self._filterType def siteCode(self): return self._siteCode def setSiteCode(self, siteCode): self._siteCode = siteCode def classCode(self): return self.filterClassCombo.itemData(self.filterClassCombo.currentIndex()) def filterRange(self): return self._normaliseRange(self.filterRangeCombo.currentText()) def color(self): return self._colorTool.color() def setClassCodes(self, codes): self.filterClassCombo.clear() keys = codes.keys() keys.sort() for key in keys: self.filterClassCombo.addItem(codes[key], key) def setHistory(self, history): self.filterRangeCombo.addItems(history) self.filterRangeCombo.clearEditText() def history(self): history = [] for idx in range(0, self.filterRangeCombo.count()): history.append(self.filterRangeCombo.itemText(idx)) return history def clearFilterRange(self): self.filterRangeCombo.clearEditText() def setFilterAction(self, action): if self._filterActionStatus == FilterWidgetAction.AddFilter: self.filterRangeCombo.lineEdit().returnPressed.disconnect(self._addFilterClicked) elif self._filterActionStatus == FilterWidgetAction.RemoveFilter: self.filterRangeCombo.lineEdit().editingFinished.disconnect(self._filterRangeChanged) self._filterActionStatus = action if action == FilterWidgetAction.LockFilter: self.filterActionTool.removeAction(self._addAction) self.filterActionTool.setDefaultAction(self._removeAction) self.setEnabled(False) elif action == FilterWidgetAction.RemoveFilter: self.filterActionTool.removeAction(self._addAction) self.filterActionTool.setDefaultAction(self._removeAction) self.filterRangeCombo.lineEdit().editingFinished.connect(self._filterRangeChanged) self.setEnabled(True) else: self.filterActionTool.removeAction(self._removeAction) self.filterActionTool.setDefaultAction(self._addAction) self.filterRangeCombo.lineEdit().returnPressed.connect(self._addFilterClicked) self.setEnabled(True) def _normaliseRange(self, text): return text.replace(' - ', '-').replace(',', ' ').strip() def _addFilterClicked(self): self.setFilterAction(FilterWidgetAction.RemoveFilter) self.clauseAdded.emit() def _removeFilterClicked(self): self.clauseRemoved.emit(self._filterIndex) def _includeFilterChecked(self): self._setFilterType(FilterType.Include) self.clauseChanged.emit(self._filterIndex) def _excludeFilterChecked(self): self._setFilterType(FilterType.Exclude) self.clauseChanged.emit(self._filterIndex) def _highlightFilterChecked(self): self._setFilterType(FilterType.Highlight) self.clauseChanged.emit(self._filterIndex) def _selectFilterChecked(self): self._setFilterType(FilterType.Select) self.clauseChanged.emit(self._filterIndex) def _filterRangeChanged(self): self.clauseChanged.emit(self._filterIndex) def _colorChanged(self, color): pix = QPixmap(22, 22) pix.fill(color) self._highlightIcon = QIcon(pix) self._highlightAction.setIcon(self._highlightIcon) self._setFilterType(FilterType.Highlight) self.clauseChanged.emit(self._filterIndex)
class MapWidget(Ui_CanvasWidget, QMainWindow): def __init__(self, parent=None): super(MapWidget, self).__init__(parent) self.setupUi(self) self.firstshow = True self.layerbuttons = [] self.editfeaturestack = [] self.lastgpsposition = None self.project = None self.gps = None self.gpslogging = None self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas)) self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) if hasattr(self.canvas, 'setParallelRenderingEnabled'): self.canvas.setParallelRenderingEnabled(True) pal = QgsPalLabeling() self.canvas.mapRenderer().setLabelingEngine(pal) self.canvas.setFrameStyle(QFrame.NoFrame) self.editgroup = QActionGroup(self) self.editgroup.setExclusive(True) self.editgroup.addAction(self.actionPan) self.editgroup.addAction(self.actionZoom_In) self.editgroup.addAction(self.actionZoom_Out) self.editgroup.addAction(self.actionInfo) self.actionGPS = GPSAction(":/icons/gps", self.canvas, self) self.projecttoolbar.addAction(self.actionGPS) gpsspacewidget= QWidget() gpsspacewidget.setMinimumWidth(30) gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.topspaceraction = self.projecttoolbar.insertWidget(self.actionGPS, gpsspacewidget) self.dataentryselection = QAction(self.projecttoolbar) self.dataentryaction = self.projecttoolbar.insertAction(self.topspaceraction, self.dataentryselection) self.dataentryselection.triggered.connect(self.select_data_entry) self.marker = GPSMarker(self.canvas) self.marker.hide() self.currentfeatureband = QgsRubberBand(self.canvas) self.currentfeatureband.setIconSize(20) self.currentfeatureband.setWidth(10) self.currentfeatureband.setColor(QColor(186, 93, 212, 76)) self.gpsband = QgsRubberBand(self.canvas) self.gpsband.setColor(QColor(0, 0, 212, 76)) self.gpsband.setWidth(5) RoamEvents.editgeometry.connect(self.queue_feature_for_edit) RoamEvents.selectioncleared.connect(self.clear_selection) RoamEvents.selectionchanged.connect(self.highlight_selection) RoamEvents.featureformloaded.connect(self.feature_form_loaded) self.connectButtons() def init_qgisproject(self, doc): parser = ProjectParser(doc) canvasnode = parser.canvasnode self.canvas.freeze() self.canvas.mapRenderer().readXML(canvasnode) self.canvaslayers = parser.canvaslayers() self.canvas.setLayerSet(self.canvaslayers) #red = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorRedPart", 255 )[0]; #green = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorGreenPart", 255 )[0]; #blue = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorBluePart", 255 )[0]; #color = QColor(red, green, blue); #self.canvas.setCanvasColor(color) self.canvas.updateScale() return self.canvas.mapRenderer().destinationCrs() def showEvent(self, *args, **kwargs): if self.firstshow: self.canvas.refresh() self.canvas.repaint() self.firstshow = False def feature_form_loaded(self, form, feature, project, editmode): self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer) def highlight_selection(self, results): self.clear_selection() for layer, features in results.iteritems(): band = self.selectionbands[layer] band.setColor(QColor(255, 0, 0, 200)) band.setIconSize(20) band.setWidth(2) band.setBrushStyle(Qt.NoBrush) band.reset(layer.geometryType()) for feature in features: band.addGeometry(feature.geometry(), layer) def highlight_active_selection(self, layer, feature, features): self.clear_selection() self.highlight_selection({layer: features}) self.currentfeatureband.setToGeometry(feature.geometry(), layer) def clear_selection(self): # Clear the main selection rubber band self.currentfeatureband.reset() # Clear the rest for band in self.selectionbands.itervalues(): band.reset() self.editfeaturestack = [] def queue_feature_for_edit(self, form, feature): def trigger_default_action(): for action in self.projecttoolbar.actions(): if action.property('dataentry') and action.isdefault: action.trigger() break self.editfeaturestack.append((form, feature)) self.load_form(form) trigger_default_action() def clear_temp_objects(self): def clear_tool_band(): """ Clear the rubber band of the active tool if it has one """ tool = self.canvas.mapTool() try: tool.clearBand() except AttributeError: # No clearBand method found, but that's cool. pass self.currentfeatureband.reset() clear_tool_band() def settings_updated(self, settings): self.actionGPS.updateGPSPort() gpslogging = settings.get('gpslogging', True) if self.gpslogging: self.gpslogging.logging = gpslogging def set_gps(self, gps, logging): self.gps = gps self.gpslogging = logging self.gps.gpsposition.connect(self.gps_update_canvas) self.gps.firstfix.connect(self.gps_first_fix) self.gps.gpsdisconnected.connect(self.gps_disconnected) def gps_update_canvas(self, position, gpsinfo): # Recenter map if we go outside of the 95% of the area if self.gpslogging.logging: self.gpsband.addPoint(position) self.gpsband.show() if roam.config.settings.get('gpscenter', True): if not self.lastgpsposition == position: self.lastposition = position rect = QgsRectangle(position, position) extentlimt = QgsRectangle(self.canvas.extent()) extentlimt.scale(0.95) if not extentlimt.contains(position): self.zoom_to_location(position) self.marker.show() self.marker.setCenter(position) def gps_first_fix(self, postion, gpsinfo): zoomtolocation = roam.config.settings.get('gpszoomonfix', True) if zoomtolocation: self.canvas.zoomScale(1000) self.zoom_to_location(postion) def zoom_to_location(self, position): rect = QgsRectangle(position, position) self.canvas.setExtent(rect) self.canvas.refresh() def gps_disconnected(self): self.marker.hide() def select_data_entry(self): def showformerror(form): pass def actions(): for form in self.project.forms: action = form.createuiaction() valid, failreasons = form.valid if not valid: roam.utils.warning("Form {} failed to load".format(form.label)) roam.utils.warning("Reasons {}".format(failreasons)) action.triggered.connect(partial(showformerror, form)) else: action.triggered.connect(partial(self.load_form, form)) yield action formpicker = PickActionDialog(msg="Select data entry form") formpicker.addactions(actions()) formpicker.exec_() def project_loaded(self, project): self.project = project self.actionPan.trigger() try: firstform = project.forms[0] self.load_form(firstform) self.dataentryselection.setVisible(True) except IndexError: self.dataentryselection.setVisible(False) # Enable the raster layers button only if the project contains a raster layer. layers = QgsMapLayerRegistry.instance().mapLayers().values() hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers) self.actionRaster.setEnabled(hasrasters) self.defaultextent = self.canvas.extent() roam.utils.info("Extent: {}".format(self.defaultextent.toString())) self.infoTool.selectionlayers = project.selectlayersmapping() self.canvas.freeze(False) self.canvas.refresh() def setMapTool(self, tool, *args): self.canvas.setMapTool(tool) def connectButtons(self): def connectAction(action, tool): action.toggled.connect(partial(self.setMapTool, tool)) def cursor(name): pix = QPixmap(name) pix = pix.scaled(QSize(24,24)) return QCursor(pix) self.zoomInTool = QgsMapToolZoom(self.canvas, False) self.zoomOutTool = QgsMapToolZoom(self.canvas, True) self.panTool = PanTool(self.canvas) self.infoTool = InfoTool(self.canvas) connectAction(self.actionZoom_In, self.zoomInTool) connectAction(self.actionZoom_Out, self.zoomOutTool) connectAction(self.actionPan, self.panTool) connectAction(self.actionInfo, self.infoTool) self.zoomInTool.setCursor(cursor(':/icons/in')) self.zoomOutTool.setCursor(cursor(':/icons/out')) self.infoTool.setCursor(cursor(':/icons/info')) self.actionRaster.triggered.connect(self.toggleRasterLayers) self.infoTool.infoResults.connect(RoamEvents.selectionchanged.emit) self.actionHome.triggered.connect(self.homeview) def homeview(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ self.canvas.setExtent(self.defaultextent) self.canvas.refresh() def load_form(self, form): self.clearCapatureTools() self.dataentryselection.setIcon(QIcon(form.icon)) self.dataentryselection.setText(form.icontext) self.create_capture_buttons(form) def create_capture_buttons(self, form): tool = form.getMaptool()(self.canvas) for action in tool.actions: # Create the action here. if action.ismaptool: action.toggled.connect(partial(self.setMapTool, tool)) # Set the action as a data entry button so we can remove it later. action.setProperty("dataentry", True) self.editgroup.addAction(action) self.layerbuttons.append(action) self.projecttoolbar.insertAction(self.topspaceraction, action) action.setChecked(action.isdefault) if hasattr(tool, 'geometryComplete'): add = partial(self.add_new_feature, form) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(partial(self.showUIMessage, form.label)) def add_new_feature(self, form, geometry): """ Add a new new feature to the given layer """ # TODO Extract into function. # NOTE This function is doing too much, acts as add and also edit. layer = form.QGISLayer if layer.geometryType() in [QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon]: geometry.convertToMultiType() try: form, feature = self.editfeaturestack.pop() self.editfeaturegeometry(form, feature, newgeometry=geometry) return except IndexError: pass layer = form.QGISLayer fields = layer.pendingFields() feature = QgsFeature(fields) feature.setGeometry(geometry) for index in xrange(fields.count()): pkindexes = layer.dataProvider().pkAttributeIndexes() if index in pkindexes and layer.dataProvider().name() == 'spatialite': continue value = layer.dataProvider().defaultValue(index) feature[index] = value RoamEvents.open_feature_form(form, feature, editmode=False) def editfeaturegeometry(self, form, feature, newgeometry): # TODO Extract into function. layer = form.QGISLayer layer.startEditing() feature.setGeometry(newgeometry) layer.updateFeature(feature) saved = layer.commitChanges() if not saved: map(roam.utils.error, layer.commitErrors()) self.canvas.refresh() self.currentfeatureband.setToGeometry(feature.geometry(), layer) RoamEvents.editgeometry_complete.emit(form, feature) def clearCapatureTools(self): captureselected = False for action in self.projecttoolbar.actions(): if action.objectName() == "capture" and action.isChecked(): captureselected = True if action.property('dataentry'): self.projecttoolbar.removeAction(action) return captureselected def toggleRasterLayers(self): """ Toggle all raster layers on or off. """ if not self.canvaslayers: return #Freeze the canvas to save on UI refresh self.canvas.freeze() for layer in self.canvaslayers: if layer.layer().type() == QgsMapLayer.RasterLayer: layer.setVisible(not layer.isVisible()) # Really!? We have to reload the whole layer set every time? # WAT? self.canvas.setLayerSet(self.canvaslayers) self.canvas.freeze(False) self.canvas.refresh() def cleanup(self): self.gpsband.reset() self.gpsband.hide() self.clear_selection() self.clear_temp_objects() self.clearCapatureTools() self.canvas.freeze() self.canvas.clear() self.canvas.freeze(False) for action in self.layerbuttons: self.editgroup.removeAction(action)
class EjdexplInt: def __init__(self, iface): self.iface = iface self.plugin_dir = os.path.dirname(__file__) self.searchobj = None self.readconfig() self.updateconfig() self.srsitem = CanvasItems(self.iface.mapCanvas(),self.config['search_color'],self.config['search_style'],self.config['search_width'],self.config['search_icon'],self.config['search_size']) self.db = QSqlDatabase.addDatabase('QODBC') self.db.setDatabaseName(self.config['connection']) locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join(self.plugin_dir,'i18n','EjdexplInt_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) def tr(self, message): return QCoreApplication.translate('EjdexplInt', message) def initGui(self): self.action = QAction(QIcon(":/plugins/EjdexplInt/icons/icon.png"),self.tr(u'Activate EjdExplorer tool'),self.iface.mainWindow()) self.action.setWhatsThis(u"Activate EjdExplorer tool") self.action.triggered.connect(self.run) self.tbmenu = QMenu() self.ag1 = QActionGroup(self.tbmenu,exclusive=True) self.acPol = self.ag1.addAction(QAction(QIcon(':/plugins/EjdexplInt/icons/Icons8-Ios7-Maps-Polygon.ico'),self.tr(u'Draw polygon'),self.tbmenu,checkable=True)) self.acLin = self.ag1.addAction(QAction(QIcon(':/plugins/EjdexplInt/icons/Icons8-Ios7-Maps-Polyline.ico'),self.tr(u'Draw line'),self.tbmenu,checkable=True)) self.acPnt = self.ag1.addAction(QAction(QIcon(':/plugins/EjdexplInt/icons/Icons8-Ios7-Maps-Geo-Fence.ico'),self.tr(u'Draw point'),self.tbmenu,checkable=True)) self.acAlay = self.ag1.addAction(QAction(QIcon(':/plugins/EjdexplInt/icons/Icons8-Ios7-Maps-Layers.ico'),self.tr(u'Active selection'),self.tbmenu,checkable=True)) self.acPobj = self.ag1.addAction(QAction(QIcon(':/plugins/EjdexplInt/icons/Icons8-Ios7-Maps-Quest.ico'),self.tr(u'Previous object'),self.tbmenu,checkable=True)) self.tbmenu.addActions(self.ag1.actions()); self.acPol.setChecked(self.config['searchtool']==u'polygon') self.acLin.setChecked(self.config['searchtool']==u'line') self.acPnt.setChecked(self.config['searchtool']==u'point') self.acAlay.setChecked(self.config['searchtool']==u'selection') self.acPobj.setChecked(self.config['searchtool']==u'search') self.tbmenu.addSeparator() self.ag2 = QActionGroup(self.tbmenu,exclusive=True) self.acSingle = self.ag2.addAction(QAction(self.tr(u'Use single mode'),self.tbmenu,checkable=True)) self.acBulk = self.ag2.addAction(QAction(self.tr(u'Use bulk mode'),self.tbmenu,checkable=True)) self.acMerge = self.ag2.addAction(QAction(self.tr(u'Use merge mode'),self.tbmenu,checkable=True)) self.tbmenu.addActions(self.ag2.actions()); self.acSingle.setChecked(self.config['tabchoice']==u'single') self.acBulk.setChecked(self.config['tabchoice']==u'bulk') self.acMerge.setChecked(self.config['tabchoice']==u'merge') if self.config['old_behavior'] == 0: self.tbmenu.addSeparator() self.acClear = QAction(self.tr(u'Clear'),self.tbmenu,checkable=False) self.acClear.triggered.connect(self.clearSearch) self.tbmenu.addAction(self.acClear) self.toolButton = QToolButton() self.toolButton.addAction(self.action) self.toolButton.setDefaultAction(self.action) self.toolButton.setMenu(self.tbmenu) self.toolButton.setPopupMode(QToolButton.MenuButtonPopup) self.iface.addToolBarWidget(self.toolButton) self.iface.addPluginToMenu(self.tr(u'Activate EjdExplorer tool'), self.action) self.ag1.triggered.connect(self.drawChanged) def drawChanged(self, action): if action.isChecked(): self.run() def clearSearch(self): if self.config['old_behavior'] == 0: self.srsitem.clearMarkerGeom() def unload(self): self.iface.removePluginMenu(self.tr(u'Activate EjdExplorer tool'), self.action) self.iface.removeToolBarIcon(self.action) del self.tbmenu # No parent del self.toolButton # No parent def run(self): geoms = None canvas = self.iface.mapCanvas() if self.config['old_behavior'] == 0: self.srsitem.clearMarkerGeom() if self.acPol.isChecked(): # polygon tool = CaptureTool(canvas, self.geometryAdded, CaptureTool.CAPTURE_POLYGON) canvas.setMapTool(tool) elif self.acLin.isChecked(): # line tool = CaptureTool(canvas, self.geometryAdded, CaptureTool.CAPTURE_LINE) canvas.setMapTool(tool) elif self.acPnt.isChecked(): # point tool = AddPointTool(canvas, self.geometryAdded) canvas.setMapTool(tool) elif self.acAlay.isChecked(): # active layer selection layer = self.iface.activeLayer() if (layer) and (layer.type() == QgsMapLayer.VectorLayer): selection = layer.selectedFeatures() if (selection): for f in selection: if geoms == None: geoms = f.geometry() else: geoms = geoms.combine( f.geometry() ) if (geoms != None): self.geometryAdded(geoms) else: self.iface.messageBar().pushMessage(self.tr(u'EjdExplorer - Object definition'), self.tr(u'No object found'), QgsMessageBar.CRITICAL, 6) elif self.acPobj.isChecked(): # existing object geoms = self.searchobj if (geoms != None): self.geometryAdded(geoms) else: self.iface.messageBar().pushMessage(self.tr(u'EjdExplorer - Object definition'), self.tr(u'No existing object found'), QgsMessageBar.CRITICAL, 6) self.acPol.setChecked(True) else: self.iface.messageBar().pushMessage(self.tr(u'EjdExplorer - Object definition'), self.tr(u'Uknown search tool'), QgsMessageBar.CRITICAL, 6) def cnvobj2wkt (self,gobj,epsg_in,epsg_out, buffer_pol, buffer_lin): # Buffer around search object; negative for polygon or positive for points and lines if gobj.type() == 2: # Polygon gobj = gobj.buffer(buffer_pol,8) else: # Line or Point gobj = gobj.buffer(buffer_lin,8) crsSrc = QgsCoordinateReferenceSystem(int(epsg_in)) crsDest = QgsCoordinateReferenceSystem(int(epsg_out)) xform = QgsCoordinateTransform(crsSrc, crsDest) i = gobj.transform(xform) return gobj.exportToWkt() def geometryAdded(self, geom): self.iface.messageBar().pushMessage(self.tr(u'EjdExplorer - Start EjdExplorer'), self.tr(u'Starting EjdExplorer program, takes a few seconds..'), QgsMessageBar.INFO, 6) self.searchobj = geom if self.config['old_behavior'] == 0: self.srsitem.setMarkerGeom(geom) epsg_in = self.iface.mapCanvas().mapRenderer().destinationCrs().authid().replace('EPSG:','') geom_txt = self.cnvobj2wkt (geom,epsg_in,self.config['epsg'],self.config['buffer_pol'],self.config['buffer_lin']) txt1, txt2 = self.getlists (geom_txt) mode = u'single' if self.acBulk.isChecked(): mode = u'bulk' if self.acMerge.isChecked(): mode = u'merge' # NB! parameters dosn't work as expected, LIFA contacted (and admit to an error in their program) txt = u'start ' + self.config['command'] + u' "' + self.config['parameter'].format(mode, txt1,txt2) + u'"' txt = txt.encode('latin-1') if len(txt) <= 8190: # max length of command-line parameter os.system ( txt) else: self.iface.messageBar().pushMessage(self.tr(u'EjdExplorer - Start EjdExplorer'), self.tr(u'To many entities selected; try with a smaller search object'), QgsMessageBar.CRITICAL, 6) self.iface.actionPan().trigger() def msgbox (self,txt1): cb = QApplication.clipboard() cb.setText(txt1) msgBox = QMessageBox() msgBox.setText(txt1) msgBox.exec_() def getlists(self, wkt): ejrlrv = [] matrnr = [] if (self.db.open()==True): query = QSqlQuery (self.config['sqlquery'].format(wkt,self.config['epsg'])) while (query.next()): ejrlrv.append(query.value(0)) matrnr.append(query.value(1)) else: self.iface.messageBar().pushMessage(self.tr(u'EjdExplorer - Database Error'), db.lastError().text(), QgsMessageBar.INFO, 6) return u','.join(ejrlrv), u','.join(matrnr) def readconfig(self): s = QSettings() k = __package__ self.config = { 'epsg': unicode(s.value(k + "/epsg" , "25832", type=str)), 'buffer_pol': s.value(k + "/buffer_pol" , -0.1, type=float), 'buffer_lin': s.value(k + "/buffer_lin" , 0.1, type=float), 'searchtool': unicode(s.value(k + "/searchtool" , "point", type=str)).lower(), 'tabchoice': unicode(s.value(k + "/tabchoice" , "single", type=str)).lower(), 'sqlquery': unicode(s.value(k + "/sqlquery" , "select LTRIM(RTRIM(CONVERT(varchar(10), [landsejerlavskode]))) as ejrlvnr, LTRIM(RTRIM([matrikelnummer])) as matrnr from [dbo].[Jordstykke] where geometry::STGeomFromText('{0}',{1}).STIntersects([geometri])=1", type=str)), 'connection': unicode(s.value(k + "/connection" , "Driver={SQL Server};Server=f-sql12;Database=LOIS;Trusted_Connection=Yes;", type=str)), 'command': unicode(s.value(k + "/command" , 'C:/"Program Files (x86)"/LIFA/EjdExplorer/LIFA.EjdExplorer.GUI.exe', type=str)), 'parameter': unicode(s.value(k + "/parameter" , 'ejdexpl://?mode={0}&CadastralDistrictIdentifier={1}&RealPropertyKey={2}', type=str)), 'search_color': str(s.value(k + "/search_color", "#FF0000", type=str)), 'search_width': s.value(k + "/search_width", 4, type=int), 'search_style': s.value(k + "/search_style", 1, type=int), 'search_icon': s.value(k + "/search_icon", QgsVertexMarker.ICON_CROSS, type=int), 'search_size': s.value(k + "/search_size", 30, type=int), 'old_behavior': s.value(k + "/old_behavior", 0, type=int) } self.srsitem = CanvasItems(self.iface.mapCanvas(),self.config['search_color'],self.config['search_style'],self.config['search_width'],self.config['search_icon'],self.config['search_size']) def updateconfig(self): s = QSettings() k = __package__ s.setValue(k + "/epsg", self.config['epsg']) s.setValue(k + "/buffer_pol", self.config['buffer_pol']) s.setValue(k + "/buffer_lin", self.config['buffer_lin']) s.setValue(k + "/searchtool", self.config['searchtool']) s.setValue(k + "/tabchoice", self.config['tabchoice']) s.setValue(k + "/sqlquery", self.config['sqlquery']) s.setValue(k + "/connection", self.config['connection']) s.setValue(k + "/command", self.config['command']) s.setValue(k + "/parameter", self.config['parameter']) s.setValue(k + "/search_color", self.config['search_color']) s.setValue(k + "/search_style", self.config['search_style']) s.setValue(k + "/search_width", self.config['search_width']) s.setValue(k + "/search_icon", self.config['search_icon']) s.setValue(k + "/search_size", self.config['search_size']) s.setValue(k + "/old_behavior", self.config['old_behavior']) s.sync
class FreeseerApp(QMainWindowWithDpi): def __init__(self, config): super(FreeseerApp, self).__init__() self.config = config self.icon = QIcon() self.icon.addPixmap(QPixmap(_fromUtf8(":/freeseer/logo.png")), QIcon.Normal, QIcon.Off) self.setWindowIcon(self.icon) self.aboutDialog = AboutDialog() self.aboutDialog.setModal(True) self.logDialog = LogDialog() # # Translator # self.app = QApplication.instance() self.current_language = None self.uiTranslator = QTranslator() self.uiTranslator.load(":/languages/tr_en_US.qm") self.app.installTranslator(self.uiTranslator) self.langActionGroup = QActionGroup(self) self.langActionGroup.setExclusive(True) QTextCodec.setCodecForTr(QTextCodec.codecForName('utf-8')) self.connect(self.langActionGroup, SIGNAL('triggered(QAction *)'), self.translate) # --- Translator # # Setup Menubar # self.menubar = self.menuBar() self.menubar.setGeometry(self.qrect_with_dpi(0, 0, 500, 50)) self.menubar.setObjectName(_fromUtf8("menubar")) self.menuFile = QMenu(self.menubar) self.menuFile.setObjectName(_fromUtf8("menuFile")) self.menuLanguage = QMenu(self.menubar) self.menuLanguage.setObjectName(_fromUtf8("menuLanguage")) self.menuHelp = QMenu(self.menubar) self.menuHelp.setObjectName(_fromUtf8("menuHelp")) exitIcon = QIcon.fromTheme("application-exit") self.actionExit = QAction(self) self.actionExit.setShortcut("Ctrl+Q") self.actionExit.setObjectName(_fromUtf8("actionExit")) self.actionExit.setIcon(exitIcon) helpIcon = QIcon.fromTheme("help-contents") self.actionOnlineHelp = QAction(self) self.actionOnlineHelp.setObjectName(_fromUtf8("actionOnlineHelp")) self.actionOnlineHelp.setIcon(helpIcon) self.actionAbout = QAction(self) self.actionAbout.setObjectName(_fromUtf8("actionAbout")) self.actionAbout.setIcon(self.icon) self.actionLog = QAction(self) self.actionLog.setObjectName(_fromUtf8("actionLog")) self.actionLog.setShortcut("Ctrl+L") # Actions self.menuFile.addAction(self.actionExit) self.menuHelp.addAction(self.actionAbout) self.menuHelp.addAction(self.actionLog) self.menuHelp.addAction(self.actionOnlineHelp) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuLanguage.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) self.setupLanguageMenu() # --- End Menubar self.connect(self.actionExit, SIGNAL('triggered()'), self.close) self.connect(self.actionAbout, SIGNAL('triggered()'), self.aboutDialog.show) self.connect(self.actionLog, SIGNAL('triggered()'), self.logDialog.show) self.connect(self.actionOnlineHelp, SIGNAL('triggered()'), self.openOnlineHelp) self.retranslateFreeseerApp() self.aboutDialog.aboutWidget.retranslate("en_US") def openOnlineHelp(self): """Opens a link to the Freeseer Online Help""" url = QUrl("http://freeseer.readthedocs.org") QDesktopServices.openUrl(url) def translate(self, action): """Translates the GUI via menu action. When a language is selected from the language menu, this function is called and the language to be changed to is retrieved. """ self.current_language = str(action.data().toString()).strip("tr_").rstrip(".qm") log.info("Switching language to: %s" % action.text()) self.uiTranslator.load(":/languages/tr_%s.qm" % self.current_language) self.app.installTranslator(self.uiTranslator) self.retranslateFreeseerApp() self.aboutDialog.aboutWidget.retranslate(self.current_language) self.retranslate() self.logDialog.retranslate() def retranslate(self): """ Reimplement this function to provide translations to your app. """ pass def retranslateFreeseerApp(self): # # Menubar # self.menuFile.setTitle(self.app.translate("FreeseerApp", "&File")) self.menuLanguage.setTitle(self.app.translate("FreeseerApp", "&Language")) self.menuHelp.setTitle(self.app.translate("FreeseerApp", "&Help")) self.actionExit.setText(self.app.translate("FreeseerApp", "&Quit")) self.actionAbout.setText(self.app.translate("FreeseerApp", "&About")) self.actionLog.setText(self.app.translate("FreeseerApp", "View &Log")) self.actionOnlineHelp.setText(self.app.translate("FreeseerApp", "Online Documentation")) # --- Menubar def setupLanguageMenu(self): self.languages = QDir(":/languages").entryList() if self.current_language is None: self.current_language = QLocale.system().name() # Retrieve Current Locale from the operating system. log.debug("Detected user's locale as %s" % self.current_language) for language in self.languages: translator = QTranslator() # Create a translator to translate Language Display Text. translator.load(":/languages/%s" % language) language_display_text = translator.translate("Translation", "Language Display Text") languageAction = QAction(self) languageAction.setCheckable(True) languageAction.setText(language_display_text) languageAction.setData(language) self.menuLanguage.addAction(languageAction) self.langActionGroup.addAction(languageAction)
class ViewerWnd(QMainWindow): def __init__(self, app, dictOpts): QMainWindow.__init__(self) self.setWindowTitle("PostGIS Layer Viewer - v.1.6.1") self.setTabPosition(Qt.BottomDockWidgetArea, QTabWidget.North) self.canvas = QgsMapCanvas() self.canvas.setCanvasColor(Qt.white) self.canvas.useImageToRender(True) self.canvas.enableAntiAliasing(True) self.setCentralWidget(self.canvas) actionZoomIn = QAction(QIcon(imgs_dir + "mActionZoomIn.png"), QString("Zoom in"), self) actionZoomOut = QAction(QIcon(imgs_dir + "mActionZoomOut.png"), QString("Zoom out"), self) actionPan = QAction(QIcon(imgs_dir + "mActionPan.png"), QString("Pan"), self) actionZoomFullExtent = QAction( QIcon(imgs_dir + "mActionZoomFullExtent.png"), QString("Zoom full"), self) actionZoomIn.setCheckable(True) actionZoomOut.setCheckable(True) actionPan.setCheckable(True) self.connect(actionZoomIn, SIGNAL("triggered()"), self.zoomIn) self.connect(actionZoomOut, SIGNAL("triggered()"), self.zoomOut) self.connect(actionPan, SIGNAL("triggered()"), self.pan) self.connect(actionZoomFullExtent, SIGNAL("triggered()"), self.zoomFullExtent) self.actionGroup = QActionGroup(self) self.actionGroup.addAction(actionPan) self.actionGroup.addAction(actionZoomIn) self.actionGroup.addAction(actionZoomOut) # Create the toolbar self.toolbar = self.addToolBar("Map tools") self.toolbar.addAction(actionPan) self.toolbar.addAction(actionZoomIn) self.toolbar.addAction(actionZoomOut) self.toolbar.addAction(actionZoomFullExtent) # Create the map tools self.toolPan = QgsMapToolPan(self.canvas) self.toolPan.setAction(actionPan) self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in self.toolZoomIn.setAction(actionZoomIn) self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out self.toolZoomOut.setAction(actionZoomOut) # Create the statusbar self.statusbar = QStatusBar(self) self.statusbar.setObjectName("statusbar") self.setStatusBar(self.statusbar) self.lblXY = QLabel() self.lblXY.setFrameStyle(QFrame.Box) self.lblXY.setMinimumWidth(170) self.lblXY.setAlignment(Qt.AlignCenter) self.statusbar.setSizeGripEnabled(False) self.statusbar.addPermanentWidget(self.lblXY, 0) self.lblScale = QLabel() self.lblScale.setFrameStyle(QFrame.StyledPanel) self.lblScale.setMinimumWidth(140) self.statusbar.addPermanentWidget(self.lblScale, 0) self.createLegendWidget() # Create the legend widget self.connect(app, SIGNAL("loadPgLayer"), self.loadLayer) self.connect(self.canvas, SIGNAL("scaleChanged(double)"), self.changeScale) self.connect(self.canvas, SIGNAL("xyCoordinates(const QgsPoint&)"), self.updateXY) self.pan() # Default self.plugins = Plugins(self, self.canvas, dictOpts['-h'], dictOpts['-p'], dictOpts['-d'], dictOpts['-U'], dictOpts['-W']) self.createAboutWidget() self.layerSRID = '-1' self.loadLayer(dictOpts) def zoomIn(self): self.canvas.setMapTool(self.toolZoomIn) def zoomOut(self): self.canvas.setMapTool(self.toolZoomOut) def pan(self): self.canvas.setMapTool(self.toolPan) def zoomFullExtent(self): self.canvas.zoomToFullExtent() def about(self): pass def createLegendWidget(self): """ Create the map legend widget and associate it to the canvas """ self.legend = Legend(self) self.legend.setCanvas(self.canvas) self.legend.setObjectName("theMapLegend") self.LegendDock = QDockWidget("Layers", self) self.LegendDock.setObjectName("legend") self.LegendDock.setTitleBarWidget(QWidget()) self.LegendDock.setWidget(self.legend) self.LegendDock.setContentsMargins(0, 0, 0, 0) self.addDockWidget(Qt.BottomDockWidgetArea, self.LegendDock) def createAboutWidget(self): self.AboutDock = QDockWidget("About", self) self.AboutDock.setObjectName("about") self.AboutDock.setTitleBarWidget(QWidget()) self.AboutDock.setContentsMargins(0, 0, 0, 0) self.tabifyDockWidget(self.LegendDock, self.AboutDock) self.LegendDock.raise_() # legendDock at the top from PyQt4.QtCore import QRect from PyQt4.QtGui import QSizePolicy, QGridLayout, QFont font = QFont() font.setFamily("Sans Serif") font.setPointSize(8.7) self.AboutWidget = QWidget() self.AboutWidget.setFont(font) self.AboutWidget.setObjectName("AboutWidget") self.AboutDock.setWidget(self.AboutWidget) self.labelAbout = QLabel(self.AboutWidget) self.labelAbout.setAlignment(Qt.AlignCenter) self.labelAbout.setWordWrap(True) self.gridLayout = QGridLayout(self.AboutWidget) self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setObjectName("gridLayout") self.gridLayout.addWidget(self.labelAbout, 0, 1, 1, 1) self.labelAbout.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard | Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse) self.labelAbout.setOpenExternalLinks(True) self.labelAbout.setText("<html><head/><body><a href=\"http://geotux.tuxfamily.org/index.php/en/geo-blogs/item/293-consola-sql-para-plugin-pgadmin-postgis-viewer\">PostGIS Layer Viewer</a> v.1.6.1 (2015.02.24)<br \><br \>" \ "Copyright (c) 2010 Ivan Mincik,<br \>[email protected]<br \>" \ u"Copyright (c) 2011-2015 Germán Carrillo,<br \>[email protected]<br \><br \>" \ "<i>Licensed under the terms of GNU GPL v.2.0</i><br \><br \>" \ "Based on PyQGIS. Plugin Fast SQL Layer by Pablo T. Carreira.</body></html>" ) def loadLayer(self, dictOpts): print 'I: Loading the layer...' self.layerSRID = dictOpts[ 'srid'] # To access the SRID when querying layer properties if not self.isActiveWindow(): self.activateWindow() self.raise_() if dictOpts['type'] == 'vector': # QGIS connection uri = QgsDataSourceURI() uri.setConnection(dictOpts['-h'], dictOpts['-p'], dictOpts['-d'], dictOpts['-U'], dictOpts['-W']) uri.setDataSource(dictOpts['-s'], dictOpts['-t'], dictOpts['-g']) layer = QgsVectorLayer(uri.uri(), dictOpts['-s'] + '.' + dictOpts['-t'], "postgres") elif dictOpts['type'] == 'raster': connString = "PG: dbname=%s host=%s user=%s password=%s port=%s mode=2 " \ "schema=%s column=%s table=%s" % ( dictOpts['-d'], dictOpts['-h'], dictOpts['-U'], dictOpts['-W'], dictOpts['-p'], dictOpts['-s'], dictOpts['col'], dictOpts['-t'] ) layer = QgsRasterLayer(connString, dictOpts['-s'] + '.' + dictOpts['-t']) if layer.isValid(): layer.setContrastEnhancement( QgsContrastEnhancement.StretchToMinimumMaximum) self.addLayer(layer, self.layerSRID) def addLayer(self, layer, srid='-1'): if layer.isValid(): # Only in case that srid != -1, read the layer SRS properties, otherwise don't since it will return 4326 if srid != '-1': self.layerSRID = layer.crs().description() + ' (' + str( layer.crs().postgisSrid()) + ')' else: self.layerSRID = 'Unknown SRS (-1)' if self.canvas.layerCount() == 0: self.canvas.setExtent(layer.extent()) if srid != '-1': print 'I: Map SRS (EPSG): %s' % self.layerSRID self.canvas.setMapUnits(layer.crs().mapUnits()) else: print 'I: Unknown Reference System' self.canvas.setMapUnits(0) # 0: QGis.Meters return QgsMapLayerRegistry.instance().addMapLayer(layer) return False def activeLayer(self): """ Returns the active layer in the layer list widget """ return self.legend.activeLayer() def getLayerProperties(self, l): """ Create a layer-properties string (l:layer)""" print 'I: Generating layer properties...' if l.type() == 0: # Vector wkbType = [ "WKBUnknown", "WKBPoint", "WKBLineString", "WKBPolygon", "WKBMultiPoint", "WKBMultiLineString", "WKBMultiPolygon", "WKBNoGeometry", "WKBPoint25D", "WKBLineString25D", "WKBPolygon25D", "WKBMultiPoint25D", "WKBMultiLineString25D", "WKBMultiPolygon25D" ] properties = "Source: %s\n" \ "Geometry type: %s\n" \ "Number of features: %s\n" \ "Number of fields: %s\n" \ "SRS (EPSG): %s\n" \ "Extent: %s " \ % ( l.source(), wkbType[l.wkbType()], l.featureCount(), l.dataProvider().fields().count(), self.layerSRID, l.extent().toString() ) elif l.type() == 1: # Raster rType = [ "GrayOrUndefined (single band)", "Palette (single band)", "Multiband", "ColorLayer" ] properties = "Source: %s\n" \ "Raster type: %s\n" \ "Width-Height (pixels): %sx%s\n" \ "Bands: %s\n" \ "SRS (EPSG): %s\n" \ "Extent: %s" \ % ( l.source(), rType[l.rasterType()], l.width(), l.height(), l.bandCount(), self.layerSRID, l.extent().toString() ) self.layerSRID = '-1' # Initialize the srid return properties def changeScale(self, scale): self.lblScale.setText("Scale 1:" + formatNumber(scale)) def updateXY(self, p): if self.canvas.mapUnits() == 2: # Degrees self.lblXY.setText( formatToDegrees( p.x() ) + " | " \ + formatToDegrees( p.y() ) ) else: # Unidad lineal self.lblXY.setText( formatNumber( p.x() ) + " | " \ + formatNumber( p.y() ) + "" )
def create_actions(self): fileNewAction = self.createAction("&New...", self.fileNew, QKeySequence.New, "filenew", "Create an image file") fileOpenAction = self.createAction("&Open...", self.fileOpen, QKeySequence.Open, "fileopen", "Open an existing image file") fileSaveAction = self.createAction("&Save", self.fileSave, QKeySequence.Save, "filesave", "Save the image") fileSaveAsAction = self.createAction( "Save &As...", self.fileSaveAs, icon="filesaveas", tip="Save the image using a new name") filePrintAction = self.createAction("&Print", self.filePrint, QKeySequence.Print, "fileprint", "Print the image") fileQuitAction = self.createAction("&Quit", self.close, "Ctrl+Q", "filequit", "Close the application") editInvertAction = self.createAction("&Invert", None, "Ctrl+I", "editinvert", "Invert the image's colors", True) editInvertAction.toggled.connect(self.editInvert) editSwapRedAndBlueAction = self.createAction( "Sw&ap Red and Blue", None, "Ctrl+A", "editswap", "Swap the image's red and blue color components", True) editSwapRedAndBlueAction.toggled.connect(self.editSwapRedAndBlue) editZoomAction = self.createAction("&Zoom...", self.editZoom, "Alt+Z", "editzoom", "Zoom the image") mirrorGroup = QActionGroup(self) editUnMirrorAction = self.createAction("&Unmirror", None, "Ctrl+U", "editunmirror", "Unmirror the image", True) editUnMirrorAction.toggled.connect(self.editUnMirror) mirrorGroup.addAction(editUnMirrorAction) editMirrorHorizontalAction = self.createAction( "Mirror &Horizontally", None, "Ctrl+H", "editmirrorhoriz", "Horizontally mirror the image", True) editMirrorHorizontalAction.toggled.connect(self.editMirrorHorizontal) mirrorGroup.addAction(editMirrorHorizontalAction) editMirrorVerticalAction = self.createAction( "Mirror &Vertically", None, "Ctrl+V", "editmirrorvert", "Vertically mirror the image", True) editMirrorVerticalAction.toggled.connect(self.editMirrorVertical) mirrorGroup.addAction(editMirrorVerticalAction) editUnMirrorAction.setChecked(True) helpAboutAction = self.createAction("&About Image Changer", self.helpAbout) helpHelpAction = self.createAction("&Help", self.helpHelp, QKeySequence.HelpContents) self.fileMenu = self.menuBar().addMenu("&File") self.fileMenuActions = (fileNewAction, fileOpenAction, fileSaveAction, fileSaveAsAction, None, filePrintAction, fileQuitAction) self.fileMenu.aboutToShow.connect(self.updateFileMenu) editMenu = self.menuBar().addMenu("&Edit") self.addActions( editMenu, (editInvertAction, editSwapRedAndBlueAction, editZoomAction)) mirrorMenu = editMenu.addMenu(QIcon(":/editmirror.png"), "&Mirror") self.addActions(mirrorMenu, (editUnMirrorAction, editMirrorHorizontalAction, editMirrorVerticalAction)) helpMenu = self.menuBar().addMenu("&Help") self.addActions(helpMenu, (helpAboutAction, helpHelpAction)) fileToolbar = self.addToolBar("File") fileToolbar.setObjectName("FileToolBar") self.addActions(fileToolbar, (fileNewAction, fileOpenAction, fileSaveAsAction)) editToolbar = self.addToolBar("Edit") editToolbar.setObjectName("EditToolBar") self.addActions( editToolbar, (editInvertAction, editSwapRedAndBlueAction, editUnMirrorAction, editMirrorVerticalAction, editMirrorHorizontalAction)) self.zoomSpinBox = QSpinBox() self.zoomSpinBox.setRange(1, 400) self.zoomSpinBox.setSuffix(" %") self.zoomSpinBox.setValue(100) self.zoomSpinBox.setToolTip("Zoom the image") self.zoomSpinBox.setStatusTip(self.zoomSpinBox.toolTip()) self.zoomSpinBox.setFocusPolicy(Qt.NoFocus) self.zoomSpinBox.valueChanged.connect(self.showImage) editToolbar.addWidget(self.zoomSpinBox) self.addActions( self.imageLabel, (editInvertAction, editSwapRedAndBlueAction, editUnMirrorAction, editMirrorVerticalAction, editMirrorHorizontalAction)) self.resetableActions = ((editInvertAction, False), (editSwapRedAndBlueAction, False), (editUnMirrorAction, True))
class LayerSnappingAction(LayerSnappingEnabledAction): """Action to change snapping settings for a QGIS vector layer.""" snapSettingsChanged = pyqtSignal(str) def __init__(self, snapLayer, parent=None): super(LayerSnappingAction, self).__init__(snapLayer, parent) self._toleranceAction = None # LayerSnappingToleranceAction() self._avoidAction = None # LayerSnappingAvoidIntersectionsAction() self._vertexAction = LayerSnappingTypeAction(snapLayer, Snapping.Vertex, self) self._segmentAction = LayerSnappingTypeAction(snapLayer, Snapping.Segment, self) self._vertexSegmentAction = LayerSnappingTypeAction(snapLayer, Snapping.VertexAndSegment, self) self._snappingTypeActionGroup = QActionGroup(self) self._snappingTypeActionGroup.addAction(self._vertexAction) self._snappingTypeActionGroup.addAction(self._segmentAction) self._snappingTypeActionGroup.addAction(self._vertexSegmentAction) self._toleranceAction = LayerSnappingToleranceAction(snapLayer, parent) self._pixelUnitsAction = LayerSnappingUnitAction(snapLayer, Snapping.Pixels, self) self._layerUnitsAction = LayerSnappingUnitAction(snapLayer, Snapping.LayerUnits, self) self._projectUnitsAction = LayerSnappingUnitAction(snapLayer, Snapping.ProjectUnits, self) self._unitTypeActionGroup = QActionGroup(self) self._unitTypeActionGroup.addAction(self._pixelUnitsAction) self._unitTypeActionGroup.addAction(self._layerUnitsAction) self._unitTypeActionGroup.addAction(self._projectUnitsAction) menu = ControlMenu(parent) menu.addActions(self._snappingTypeActionGroup.actions()) menu.addSeparator() menu.addAction(self._toleranceAction) menu.addActions(self._unitTypeActionGroup.actions()) if (isinstance(snapLayer, QgisInterface) or (isinstance(snapLayer, QgsVectorLayer) and snapLayer.geometryType() == QGis.Polygon)): self._avoidAction = LayerSnappingAvoidIntersectionsAction(snapLayer, self) menu.addSeparator() menu.addAction(self._avoidAction) self.setMenu(menu) self._refreshAction() # Make sure we catch changes in the main snapping dialog QgsProject.instance().snapSettingsChanged.connect(self._refreshAction) # If using current layer, make sure we update when it changes if self._iface: self._iface.legendInterface().currentLayerChanged.connect(self._refreshAction) # If any of the settings change then signal, but don't tell project as actions already have self.snappingEnabledChanged.connect(self.snapSettingsChanged) self._vertexAction.snappingTypeChanged.connect(self.snapSettingsChanged) self._segmentAction.snappingTypeChanged.connect(self.snapSettingsChanged) self._vertexSegmentAction.snappingTypeChanged.connect(self.snapSettingsChanged) self._toleranceAction.snappingToleranceChanged.connect(self.snapSettingsChanged) self._pixelUnitsAction.snappingUnitChanged.connect(self.snapSettingsChanged) self._layerUnitsAction.snappingUnitChanged.connect(self.snapSettingsChanged) self._projectUnitsAction.snappingUnitChanged.connect(self.snapSettingsChanged) if self._avoidAction: self._avoidAction.avoidIntersectionsChanged.connect(self.snapSettingsChanged) def setInterface(self, iface): self._toleranceAction.setInterface(iface) def unload(self): if not self._layerId: return super(LayerSnappingAction, self).unload() QgsProject.instance().snapSettingsChanged.disconnect(self._refreshAction) self.snappingEnabledChanged.disconnect(self.snapSettingsChanged) self._vertexAction.snappingTypeChanged.disconnect(self.snapSettingsChanged) self._segmentAction.snappingTypeChanged.disconnect(self.snapSettingsChanged) self._vertexSegmentAction.snappingTypeChanged.disconnect(self.snapSettingsChanged) self._toleranceAction.snappingToleranceChanged.disconnect(self.snapSettingsChanged) self._pixelUnitsAction.snappingUnitChanged.disconnect(self.snapSettingsChanged) self._layerUnitsAction.snappingUnitChanged.disconnect(self.snapSettingsChanged) self._projectUnitsAction.snappingUnitChanged.disconnect(self.snapSettingsChanged) if self._avoidAction: self._avoidAction.avoidIntersectionsChanged.disconnect(self.snapSettingsChanged) self._vertexAction.unload() self._segmentAction.unload() self._vertexSegmentAction.unload() self._toleranceAction.unload() self._pixelUnitsAction.unload() self._layerUnitsAction.unload() self._projectUnitsAction.unload() # Private API def _refreshAction(self): if (self._segmentAction.isChecked()): self.setIcon(self._segmentAction.icon()) elif (self._vertexSegmentAction.isChecked()): self.setIcon(self._vertexSegmentAction.icon()) else: # Snapping.Vertex or undefined self.setIcon(self._vertexAction.icon()) if self._iface and self._avoidAction: layer = QgsMapLayerRegistry.instance().mapLayer(self.layerId()) isPolygon = (layer is not None and layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Polygon) self._avoidAction.setEnabled(isPolygon)