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()
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()
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 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 Gban: def __init__(self, iface): locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join( os.path.dirname(__file__), 'i18n', 'gban_{}.qm'.format(locale)) self.translator = None if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) if qVersion() > '4.3.3': QCoreApplication.installTranslator(self.translator) self.iface = iface self.canvas = self.iface.mapCanvas() self.exclusive = QActionGroup( self.iface.mainWindow() ) self.actions = [] self.menu = '&Gban' self.toolbar = self.iface.addToolBar('Gban') self.toolbar.setObjectName('Gban') #Select tool initialization self.tool = QgsMapToolEmitPoint(self.canvas) self.tool.canvasClicked.connect(self.doReverseGeocoding) self.tool.deactivated.connect(self.uncheckReverseGeocoding) self.rb = QgsRubberBand(self.iface.mapCanvas(), QGis.Point) self.rb.setColor( QColor(255, 0, 0) ) self.rb.setWidth( 5 ) # Network configuration self.manager = QgsNetworkAccessManager.instance() def unload(self): for action in self.actions: self.iface.removePluginMenu('&Gban', action) self.iface.removeToolBarIcon(action) del self.toolbar def tr(self, message): return QCoreApplication.translate('Gban', message) def add_action( self, icon_path, text, callback, enabled_flag=True, checkable=False, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) action.setCheckable(checkable) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: self.toolbar.addAction(action) if add_to_menu: self.iface.addPluginToMenu( self.menu, action) if checkable: self.exclusive.addAction( action ) self.actions.append(action) return action def initGui(self): icon_path = ":/plugins/qgeric/resources/icon_geocode.png" self.add_action( icon_path, text=self.tr("Geocoding"), callback=self.geocoding, parent=self.iface.mainWindow() ) icon_path = ":/plugins/qgeric/resources/icon_reversegeocode.png" self.add_action( icon_path, checkable = True, text=self.tr("Reverse geocoding"), callback=self.reverseGeocoding, parent=self.iface.mainWindow() ) def geocoding(self): self.rb.reset( QGis.Point ) address, ok = QInputDialog.getText(self.iface.mainWindow(), self.tr("Address"), self.tr("Input address to geocode:")) if ok and address: self.doGeocoding(address) def doGeocoding(self, address): address = unicodedata.normalize('NFKD', unicode(address)).encode('ASCII', 'ignore') url = "http://api-adresse.data.gouv.fr/search/?q="+address.replace(" ", "%20") result = self.request(url) try: data = json.loads(result) features = data["features"] if len(features) > 0: feature_list = [] for feature in features: feature_list.append(feature["properties"]["label"]+" - "+str(round(feature["properties"]["score"]*100))+"%") feature, ok = QInputDialog.getItem(self.iface.mainWindow(), self.tr("Result"), "", feature_list) if ok: index = feature_list.index(feature) x = features[index]["geometry"]["coordinates"][0] y = features[index]["geometry"]["coordinates"][1] point_4326 = QgsPoint(x, y) transform = QgsCoordinateTransform(QgsCoordinateReferenceSystem(4326), self.canvas.mapSettings().destinationCrs()) point_2154 = transform.transform(point_4326) self.rb.addPoint(point_2154) self.iface.mapCanvas().setCenter(point_2154) self.iface.mapCanvas().refresh() else: QMessageBox.information(self.iface.mainWindow(), self.tr("Result"), self.tr("No result.")) except ValueError: QMessageBox.critical(self.iface.mainWindow(), self.tr("Error"), self.tr("An error occured. Check your network settings (proxy).")) def reverseGeocoding(self): self.canvas.setMapTool(self.tool) def doReverseGeocoding(self, point_orig): transform = QgsCoordinateTransform(self.canvas.mapSettings().destinationCrs(), QgsCoordinateReferenceSystem(4326)) point_4326 = transform.transform(point_orig) url = "http://api-adresse.data.gouv.fr/reverse/?lon="+str(point_4326.x())+"&lat="+str(point_4326.y()) result = self.request(url) try: data = json.loads(result) if len(data["features"]) > 0: address = data["features"][0]["properties"]["label"] clicked = QMessageBox.information(self.iface.mainWindow(), self.tr("Result"), address, QDialogButtonBox.Ok, QDialogButtonBox.Save) if clicked == QDialogButtonBox.Save: QApplication.clipboard().setText(address) else: QMessageBox.information(self.iface.mainWindow(), self.tr("Result"), self.tr("No result.")) except ValueError: QMessageBox.critical(self.iface.mainWindow(), self.tr("Error"), self.tr("An error occured. Check your network settings (proxy).")) def uncheckReverseGeocoding(self): self.exclusive.checkedAction().setChecked(False) def request(self, url): ''' prepare the request and return the result of the reply ''' request = QNetworkRequest(QUrl(url)) reply = self.manager.get(request) reply.deleteLater() evloop = QEventLoop() reply.finished.connect(evloop.quit) evloop.exec_(QEventLoop.ExcludeUserInputEvents) return unicode(reply.readAll())
class QMap(): def __init__(self, iface): self.iface = iface self.actions = [] self.panels= [] self.navtoolbar = self.iface.mapNavToolToolBar() self.mainwindow = self.iface.mainWindow() self.iface.projectRead.connect(self.projectOpened) self.iface.initializationCompleted.connect(self.setupUI) self.actionGroup = QActionGroup(self.mainwindow) self.actionGroup.setExclusive(True) self.menuGroup = QActionGroup(self.mainwindow) self.menuGroup.setExclusive(True) self.movetool = MoveTool(self.iface.mapCanvas(), []) self.infotool = InfoTool(self.iface.mapCanvas()) self.infotool.infoResults.connect(self.showInfoResults) self.report = PopDownReport(self.iface.messageBar()) self.dialogprovider = DialogProvider(iface.mapCanvas(), iface) self.dialogprovider.accepted.connect(self.clearToolRubberBand) self.dialogprovider.rejected.connect(self.clearToolRubberBand) self.edittool = EditTool(self.iface.mapCanvas(),[]) self.edittool.finished.connect(self.openForm) self.edittool.featuresfound.connect(self.showFeatureSelection) self.infodock = InfoDock(self.iface.mainWindow()) self.iface.addDockWidget(Qt.RightDockWidgetArea, self.infodock) self.infodock.hide() self.band = QgsRubberBand(self.iface.mapCanvas()) self.band.setIconSize(20) self.band.setWidth(10) self.band.setColor(QColor(186, 93, 212, 76)) def showFeatureSelection(self, features): listUi = ListFeaturesForm(self.mainwindow) listUi.loadFeatureList(features) listUi.openFeatureForm.connect(self.openForm) listUi.exec_() def showInfoResults(self, results): self.infodock.clearResults() self.infodock.setResults(results) self.infodock.show() self.infodock.repaint() @property def _mapLayers(self): return QgsMapLayerRegistry.instance().mapLayers() def clearToolRubberBand(self): tool = self.iface.mapCanvas().mapTool() try: tool.clearBand() except AttributeError: # No clearBand method found, but that's cool. pass def missingLayers(self, layers): def showError(): html = ["<h1>Missing Layers</h1>", "<ul>"] for layer in layers: html.append("<li>{}</li>".format(layer)) html.append("</ul>") self.errorreport.updateHTML("".join(html)) message = "Seems like {} didn't load correctly".format(utils._pluralstring('layer', len(layers))) utils.warning("Missing layers") map(utils.warning, layers) self.widget = self.iface.messageBar().createMessage("Missing Layers", message, QIcon(":/icons/sad")) button = QPushButton(self.widget) button.setCheckable(True) button.setChecked(self.errorreport.isVisible()) button.setText("Show missing layers") button.toggled.connect(showError) button.toggled.connect(functools.partial(self.errorreport.setVisible)) self.widget.destroyed.connect(self.hideReports) self.widget.layout().addWidget(button) self.iface.messageBar().pushWidget(self.widget, QgsMessageBar.WARNING) def excepthook(self, ex_type, value, tb): """ Custom exception hook so that we can handle errors in a nicer way """ where = ''.join(traceback.format_tb(tb)) msg = '{}'.format(value) utils.critical(msg) def showError(): html = """ <html> <body bgcolor="#FFEDED"> <p><b>{}</b></p> <p align="left"><small>{}</small></p> </body> </html> """.format(msg, where) self.errorreport.updateHTML(html) self.widget = self.iface.messageBar().createMessage("oops", "Looks like an error occurred", QIcon(":/icons/sad")) button = QPushButton(self.widget) button.setCheckable(True) button.setChecked(self.errorreport.isVisible()) button.setText("Show error") button.toggled.connect(showError) button.toggled.connect(functools.partial(self.errorreport.setVisible)) self.widget.destroyed.connect(self.hideReports) self.widget.layout().addWidget(button) self.messageBar.pushWidget(self.widget, QgsMessageBar.CRITICAL) def hideReports(self): self.errorreport.setVisible(False) self.report.setVisible(False) def setupUI(self): """ Set up the main QGIS interface items. Called after QGIS has loaded the plugin. """ self.updateAppSize() utils.settings_notify.settings_changed.connect(self.updateAppSize) self.navtoolbar.setMovable(False) self.navtoolbar.setAllowedAreas(Qt.TopToolBarArea) self.mainwindow.insertToolBar(self.toolbar, self.navtoolbar) self.openProjectAction.trigger() def updateAppSize(self): fullscreen = utils.settings.get("fullscreen", False) if fullscreen: self.mainwindow.showFullScreen() else: self.mainwindow.showMaximized() def setMapTool(self, tool): """ Set the current mapview canvas tool tool -- The QgsMapTool to set """ self.iface.mapCanvas().setMapTool(tool) def createToolBars(self): """ Create all the needed toolbars """ self.menutoolbar = QToolBar("Menu", self.mainwindow) self.menutoolbar.setMovable(False) self.menutoolbar.setAllowedAreas(Qt.LeftToolBarArea) self.mainwindow.addToolBar(Qt.LeftToolBarArea, self.menutoolbar) self.toolbar = QToolBar("QMap", self.mainwindow) self.mainwindow.addToolBar(Qt.TopToolBarArea, self.toolbar) self.toolbar.setMovable(False) self.editingtoolbar = FloatingToolBar("Editing", self.toolbar) self.extraaddtoolbar = FloatingToolBar("Extra Add Tools", self.toolbar) self.syncactionstoolbar = FloatingToolBar("Syncing", self.toolbar) self.syncactionstoolbar.setOrientation(Qt.Vertical) def createActions(self): """ Create all the actions """ self.homeAction = (QAction(QIcon(":/icons/zoomfull"), "Default View", self.mainwindow)) self.gpsAction = (GPSAction(QIcon(":/icons/gps"), self.iface.mapCanvas(), self.mainwindow)) self.openProjectAction = (QAction(QIcon(":/icons/open"), "Projects", self.mainwindow)) self.openProjectAction.setCheckable(True) self.configAction = (QAction(QIcon(":/icons/config"), "Settings", self.mainwindow)) self.configAction.setCheckable(True) self.toggleRasterAction = (QAction(QIcon(":/icons/photo"), "Aerial Photos", self.mainwindow)) self.syncAction = QAction(QIcon(":/icons/sync"), "Sync", self.mainwindow) self.syncAction.setVisible(False) self.editattributesaction = QAction(QIcon(":/icons/edit"), "Edit Attributes", self.mainwindow) self.editattributesaction.setCheckable(True) self.editattributesaction.toggled.connect(functools.partial(self.setMapTool, self.edittool)) self.moveaction = QAction(QIcon(":/icons/move"), "Move Feature", self.mainwindow) self.moveaction.setCheckable(True) self.editingmodeaction = QAction(QIcon(":/icons/edittools"), "Edit Tools", self.mainwindow) self.editingmodeaction.setCheckable(True) self.infoaction = QAction(QIcon(":/icons/info"), "Info", self.mainwindow) self.infoaction.setCheckable(True) self.addatgpsaction = QAction(QIcon(":/icons/gpsadd"), "Add at GPS", self.mainwindow) self.edittool.layersupdated.connect(self.editattributesaction.setVisible) self.movetool.layersupdated.connect(self.moveaction.setVisible) self.movetool.layersupdated.connect(self.editingmodeaction.setVisible) def initGui(self): """ Create all the icons and setup the tool bars. Called by QGIS when loading. This is called before setupUI. """ QApplication.setWindowIcon(QIcon(":/branding/logo")) self.mainwindow.findChildren(QMenuBar)[0].setVisible(False) self.mainwindow.setContextMenuPolicy(Qt.PreventContextMenu) self.mainwindow.setWindowTitle("IntraMaps Roam: Mobile Data Collection") # Disable QGIS logging window popups. We do our own logging QgsMessageLog.instance().messageReceived.disconnect() s = """ QToolButton { padding: 6px; color: #4f4f4f; } QToolButton:hover { padding: 6px; background-color: rgb(211, 228, 255); } QToolBar { background: white; } QCheckBox::indicator { width: 40px; height: 40px; } QLabel { color: #4f4f4f; } QDialog { background-color: rgb(255, 255, 255); } QPushButton { border: 1px solid #e1e1e1; padding: 6px; color: #4f4f4f; } QPushButton:hover { border: 1px solid #e1e1e1; padding: 6px; background-color: rgb(211, 228, 255); } QCheckBox { color: #4f4f4f; } QComboBox::drop-down { width: 30px; } QComboBox { border: 1px solid #d3d3d3; } QStackedWidget { background-color: rgb(255, 255, 255); } """ self.mainwindow.setStyleSheet(s) mainwidget = self.mainwindow.centralWidget() mainwidget.setLayout(QGridLayout()) mainwidget.layout().setContentsMargins(0,0,0,0) newlayout = QGridLayout() newlayout.setContentsMargins(0,0,0,0) newlayout.addWidget(self.iface.mapCanvas(), 0,0,2,1) newlayout.addWidget(self.iface.messageBar(), 0,0,1,1) wid = QWidget() wid.setLayout(newlayout) self.stack = QStackedWidget(self.mainwindow) self.messageBar = QgsMessageBar(wid) self.messageBar.setSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed ) self.errorreport = PopDownReport(self.messageBar) mainwidget.layout().addWidget(self.stack, 0,0,2,1) mainwidget.layout().addWidget(self.messageBar, 0,0,1,1) self.helppage = HelpPage() helppath = os.path.join(os.path.dirname(__file__) , 'help',"help.html") self.helppage.setHelpPage(helppath) self.settingswidget = SettingsWidget(self.stack) self.projectwidget = ProjectsWidget() self.projectwidget.requestOpenProject.connect(self.loadProject) self.stack.addWidget(wid) self.stack.addWidget(self.projectwidget) self.stack.addWidget(self.helppage) self.stack.addWidget(self.settingswidget) sys.excepthook = self.excepthook def createSpacer(width=30): widget = QWidget() widget.setMinimumWidth(width) return widget self.createToolBars() self.createActions() spacewidget = createSpacer(60) gpsspacewidget = createSpacer() gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.moveaction.toggled.connect(functools.partial(self.setMapTool, self.movetool)) self.infoaction.toggled.connect(functools.partial(self.setMapTool, self.infotool)) showediting = (functools.partial(self.editingtoolbar.showToolbar, self.editingmodeaction, self.moveaction)) self.editingmodeaction.toggled.connect(showediting) self.addatgpsaction.triggered.connect(self.addAtGPS) self.addatgpsaction.setEnabled(self.gpsAction.isConnected) self.gpsAction.gpsfixed.connect(self.addatgpsaction.setEnabled) self.editingtoolbar.addToActionGroup(self.moveaction) self.actionGroup.addAction(self.editingmodeaction) self.actionGroup.addAction(self.editattributesaction) self.actionGroup.addAction(self.infoaction) self.homeAction.triggered.connect(self.zoomToDefaultView) self.openProjectAction.triggered.connect(self.showOpenProjectDialog) self.openProjectAction.triggered.connect(functools.partial(self.stack.setCurrentIndex, 1)) self.configAction.triggered.connect(functools.partial(self.stack.setCurrentIndex, 3)) self.configAction.triggered.connect(self.settingswidget.populateControls) self.configAction.triggered.connect(self.settingswidget.readSettings) self.toggleRasterAction.triggered.connect(self.toggleRasterLayers) self.navtoolbar.insertAction(self.iface.actionZoomIn(), self.iface.actionTouch()) self.navtoolbar.insertAction(self.iface.actionTouch(), self.homeAction) self.navtoolbar.insertAction(self.iface.actionTouch(), self.iface.actionZoomFullExtent()) self.navtoolbar.insertAction(self.homeAction, self.iface.actionZoomFullExtent()) self.navtoolbar.addAction(self.toggleRasterAction) self.navtoolbar.insertWidget(self.iface.actionZoomFullExtent(), spacewidget) self.toolbar.addAction(self.infoaction) self.toolbar.addAction(self.editingmodeaction) self.toolbar.addAction(self.editattributesaction) self.toolbar.addAction(self.syncAction) self.toolbar.addAction(self.gpsAction) self.toolbar.insertWidget(self.syncAction, gpsspacewidget) self.toolbar.insertSeparator(self.gpsAction) self.extraaddtoolbar.addAction(self.addatgpsaction) self.editingtoolbar.addAction(self.moveaction) self.mapview = QAction(QIcon(":/icons/map"), "Map", self.menutoolbar) self.mapview.setCheckable(True) self.mapview.triggered.connect(functools.partial(self.stack.setCurrentIndex, 0)) self.help = QAction(QIcon(":/icons/help"), "Help", self.menutoolbar) self.help.setCheckable(True) self.help.triggered.connect(functools.partial(self.stack.setCurrentIndex, 2)) self.help.setVisible(False) self.projectlabel = QLabel("Project: <br> None") self.projectlabel.setAlignment(Qt.AlignCenter) self.projectlabel.setStyleSheet(""" QLabel { color: #8c8c8c; font: 10px "Calibri" ; }""") self.userlabel = QLabel("User: <br> {user}".format(user=getpass.getuser())) self.userlabel.setAlignment(Qt.AlignCenter) self.userlabel.setStyleSheet(""" QLabel { color: #8c8c8c; font: 10px "Calibri" ; }""") self.quit = QAction(QIcon(":/icons/quit"), "Quit", self.menutoolbar) self.quit.triggered.connect(self.iface.actionExit().trigger) self.menuGroup.addAction(self.mapview) self.menuGroup.addAction(self.openProjectAction) self.menuGroup.addAction(self.help) self.menuGroup.addAction(self.configAction) self.menutoolbar.addAction(self.mapview) self.menutoolbar.addAction(self.openProjectAction) self.menutoolbar.addAction(self.help) self.menutoolbar.addAction(self.configAction) self.menutoolbar.addAction(self.quit) quitspacewidget = createSpacer() quitspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) labelaction = self.menutoolbar.insertWidget(self.configAction, self.userlabel) self.menutoolbar.insertWidget(labelaction, quitspacewidget) self.menutoolbar.insertWidget(labelaction, self.projectlabel) self.setupIcons() self.stack.currentChanged.connect(self.updateUIState) def updateUIState(self, page): """ Update the UI state to reflect the currently selected page in the stacked widget """ def setToolbarsActive(enabled): toolbars = self.mainwindow.findChildren(QToolBar) for toolbar in toolbars: if toolbar == self.menutoolbar: continue toolbar.setEnabled(enabled) def setPanelsVisible(visible): for panel in self.panels: panel.setVisible(visible) ismapview = page == 0 setToolbarsActive(ismapview) setPanelsVisible(ismapview) self.infodock.hide() def addAtGPS(self): """ Add a record at the current GPS location. """ action = self.actionGroup.checkedAction() if not action: return layer = action.data() if not layer: return point = self.gpsAction.position self.addNewFeature(layer=layer, geometry=point) def zoomToDefaultView(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ self.iface.mapCanvas().setExtent(self.defaultextent) self.iface.mapCanvas().refresh() def toggleRasterLayers(self): """ Toggle all raster layers on or off. """ legend = self.iface.legendInterface() #Freeze the canvas to save on UI refresh self.iface.mapCanvas().freeze() for layer in self._mapLayers.values(): if layer.type() == QgsMapLayer.RasterLayer: isvisible = legend.isLayerVisible(layer) legend.setLayerVisible(layer, not isvisible) self.iface.mapCanvas().freeze(False) self.iface.mapCanvas().refresh() def setupIcons(self): """ Update toolbars to have text and icons, change normal QGIS icons to new style """ toolbars = self.mainwindow.findChildren(QToolBar) for toolbar in toolbars: toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QSize(32, 32)) self.iface.actionTouch().setIconText("Pan") self.iface.actionTouch().setIcon(QIcon(":/icons/pan")) self.iface.actionZoomIn().setIcon(QIcon(":/icons/in")) self.iface.actionZoomOut().setIcon(QIcon(":/icons/out")) self.iface.actionPan().setIcon(QIcon(":/icons/pan")) self.iface.actionZoomFullExtent().setIcon(QIcon(":/icons/home")) self.iface.actionZoomFullExtent().setIconText("Home View") self.actionGroup.addAction(self.iface.actionZoomIn()) self.actionGroup.addAction(self.iface.actionZoomOut()) self.actionGroup.addAction(self.iface.actionTouch()) def projectOpened(self): """ Called when a new project is opened in QGIS. """ for panel in self.panels: self.mainwindow.removeDockWidget(panel) del panel projectpath = QgsProject.instance().fileName() project = QMapProject(os.path.dirname(projectpath), self.iface) self.projectlabel.setText("Project: <br> {}".format(project.name)) self.createFormButtons(projectlayers = project.getConfiguredLayers()) # Enable the raster layers button only if the project contains a raster layer. hasrasters = any(layer.type() for layer in self._mapLayers.values()) self.toggleRasterAction.setEnabled(hasrasters) self.defaultextent = self.iface.mapCanvas().extent() self.connectSyncProviders(project) # Show panels self.panels = list(project.getPanels()) for panel in self.panels: self.mainwindow.addDockWidget(Qt.BottomDockWidgetArea , panel) self.iface.messageBar().popWidget() def captureLayer(self, layer): text = layer.icontext tool = layer.getMaptool(self.iface.mapCanvas()) # Hack until I fix it later if isinstance(tool, PointTool): add = functools.partial(self.addNewFeature, qgslayer) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) tool.error.connect(functools.partial(self.showToolError, text)) action = QAction(QIcon(layer.icon), text, self.mainwindow) action.setData(layer) action.setCheckable(True) action.toggled.connect(functools.partial(self.setMapTool, tool)) self.toolbar.insertAction(self.editingmodeaction, action) if not tool.isEditTool(): # Connect the GPS tools strip to the action pressed event. showgpstools = (functools.partial(self.extraaddtoolbar.showToolbar, action, None)) action.toggled.connect(showgpstools) self.actionGroup.addAction(action) self.actions.append(action) def editLayer(self, layer): self.edittool.addLayer(layer.QGISLayer) self.edittool.searchRadius = 10 def moveLayer(self, layer): self.movetool.addLayer(layer.QGISLayer) def createFormButtons(self, projectlayers): """ Create buttons for each form that is definded """ # Remove all the old buttons for action in self.actions: self.actionGroup.removeAction(action) self.toolbar.removeAction(action) self.edittool.layers = [] self.movetool.layers = [] capabilitityhandlers = { "capture" : self.captureLayer, "edit" : self.editLayer, "move" : self.moveLayer} for layer in projectlayers: try: qgslayer = QgsMapLayerRegistry.instance().mapLayersByName(layer.name)[0] if qgslayer.type() == QgsMapLayer.RasterLayer: utils.log("We can't support raster layers for data entry") continue layer.QGISLayer = qgslayer except IndexError: utils.log("Layer {} not found in project".format(layer.name)) continue for capability in layer.capabilities: try: capabilitityhandlers[capability](layer) except NoMapToolConfigured: utils.log("No map tool configured") continue except ErrorInMapTool as error: self.iface.messageBar().pushMessage("Error configuring map tool", error.message, level=QgsMessageBar.WARNING) continue def showToolError(self, label, message): self.iface.messageBar().pushMessage(label, message, QgsMessageBar.WARNING) def openForm(self, layer, feature): if not layer.isEditable(): layer.startEditing() self.band.setToGeometry(feature.geometry(), layer) self.dialogprovider.openDialog(feature=feature, layer=layer) self.band.reset() def addNewFeature(self, layer, geometry): fields = layer.pendingFields() feature = QgsFeature() feature.setGeometry( geometry ) feature.initAttributes(fields.count()) feature.setFields(fields) for indx in xrange(fields.count()): feature[indx] = layer.dataProvider().defaultValue(indx) self.openForm(layer, feature) def showOpenProjectDialog(self): """ Show the project selection dialog. """ self.stack.setCurrentIndex(1) self.infodock.hide() path = os.path.join(os.path.dirname(__file__), '..' , 'projects/') projects = getProjects(path, self.iface) self.projectwidget.loadProjectList(projects) def loadProject(self, project): """ Load a project into QGIS. """ utils.log(project) utils.log(project.name) utils.log(project.projectfile) utils.log(project.vaild) (passed, message) = project.onProjectLoad() if not passed: QMessageBox.warning(self.mainwindow, "Project Load Rejected", "Project couldn't be loaded because {}".format(message)) return self.mapview.trigger() self.iface.newProject(False) self.iface.mapCanvas().freeze() self.infodock.clearResults() # 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 ) self.iface.messageBar().pushMessage("Project Loading","", QgsMessageBar.INFO) QApplication.processEvents() fileinfo = QFileInfo(project.projectfile) QgsProject.instance().read(fileinfo) self.iface.mapCanvas().updateScale() self.iface.mapCanvas().freeze(False) self.iface.mapCanvas().refresh() self.mainwindow.setWindowTitle("IntraMaps Roam: Mobile Data Collection") self.iface.projectRead.emit() def unload(self): del self.toolbar def connectSyncProviders(self, project): self.syncactionstoolbar.clear() syncactions = list(project.syncprovders()) # Don't show the sync button if there is no sync providers if not syncactions: self.syncAction.setVisible(False) return self.syncAction.setVisible(True) for provider in syncactions: action = QAction(QIcon(":/icons/sync"), "Sync {}".format(provider.name), self.mainwindow) action.triggered.connect(functools.partial(self.syncProvider, provider)) self.syncactionstoolbar.addAction(action) try: self.syncAction.toggled.disconnect() except TypeError: pass try: self.syncAction.triggered.disconnect() except TypeError: pass if len(syncactions) == 1: # If one provider is set then we just connect the main button. self.syncAction.setCheckable(False) self.syncAction.setText("Sync") self.syncAction.triggered.connect(functools.partial(self.syncProvider, syncactions[0])) else: # the sync button because a sync menu self.syncAction.setCheckable(True) self.syncAction.setText("Sync Menu") showsyncoptions = (functools.partial(self.syncactionstoolbar.showToolbar, self.syncAction, None)) self.syncAction.toggled.connect(showsyncoptions) def syncstarted(self): # Remove the old widget if it's still there. # I don't really like this. Seems hacky. try: self.iface.messageBar().popWidget(self.syncwidget) except RuntimeError: pass except AttributeError: pass self.iface.messageBar().findChildren(QToolButton)[0].setVisible(False) self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync in progress", QIcon(":/icons/syncing")) button = QPushButton(self.syncwidget) button.setCheckable(True) button.setText("Status") button.setIcon(QIcon(":/icons/syncinfo")) button.toggled.connect(functools.partial(self.report.setVisible)) pro = QProgressBar() pro.setMaximum(0) pro.setMinimum(0) self.syncwidget.layout().addWidget(pro) self.syncwidget.layout().addWidget(button) self.iface.messageBar().pushWidget(self.syncwidget, QgsMessageBar.INFO) def synccomplete(self): try: self.iface.messageBar().popWidget(self.syncwidget) except RuntimeError: pass stylesheet = ("QgsMessageBar { background-color: rgba(239, 255, 233); border: 0px solid #b9cfe4; } " "QLabel,QTextEdit { color: #057f35; } ") closebutton = self.iface.messageBar().findChildren(QToolButton)[0] closebutton.setVisible(True) closebutton.clicked.connect(functools.partial(self.report.setVisible, False)) self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync Complete", QIcon(":/icons/syncdone")) button = QPushButton(self.syncwidget) button.setCheckable(True) button.setChecked(self.report.isVisible()) button.setText("Sync Report") button.setIcon(QIcon(":/icons/syncinfo")) button.toggled.connect(functools.partial(self.report.setVisible)) pro = QProgressBar() pro.setMaximum(100) pro.setValue(100) self.syncwidget.layout().addWidget(pro) self.syncwidget.layout().addWidget(button) self.iface.messageBar().pushWidget(self.syncwidget) self.iface.messageBar().setStyleSheet(stylesheet) self.iface.mapCanvas().refresh() def syncerror(self): try: self.iface.messageBar().popWidget(self.syncwidget) except RuntimeError: pass closebutton = self.iface.messageBar().findChildren(QToolButton)[0] closebutton.setVisible(True) closebutton.clicked.connect(functools.partial(self.report.setVisible, False)) self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync Error", QIcon(":/icons/syncfail")) button = QPushButton(self.syncwidget) button.setCheckable(True) button.setChecked(self.report.isVisible()) button.setText("Sync Report") button.setIcon(QIcon(":/icons/syncinfo")) button.toggled.connect(functools.partial(self.report.setVisible)) self.syncwidget.layout().addWidget(button) self.iface.messageBar().pushWidget(self.syncwidget, QgsMessageBar.CRITICAL) self.iface.mapCanvas().refresh() def syncProvider(self, provider): self.syncAction.toggle() provider.syncStarted.connect(functools.partial(self.syncAction.setEnabled, False)) provider.syncStarted.connect(self.syncstarted) provider.syncComplete.connect(self.synccomplete) provider.syncComplete.connect(functools.partial(self.syncAction.setEnabled, True)) provider.syncComplete.connect(functools.partial(self.report.updateHTML)) provider.syncMessage.connect(self.report.updateHTML) provider.syncError.connect(self.report.updateHTML) provider.syncError.connect(self.syncerror) provider.syncError.connect(functools.partial(self.syncAction.setEnabled, True)) provider.startSync()
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 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 ActionSettingsTool(QToolButton): settingsChanged = pyqtSignal() mapActionChanged = pyqtSignal(int) filterActionChanged = pyqtSignal(int) drawingActionChanged = pyqtSignal(int) def __init__(self, parent=None): super(ActionSettingsTool, self).__init__(parent) self._mapActionGroup = QActionGroup(self) self._noMapAction = self._addMapAction(MapAction.NoMapAction, 'No map action') self._zoomMapAction = self._addMapAction(MapAction.ZoomMap, 'Zoom map view') self._panMapAction = self._addMapAction(MapAction.PanMap, 'Pan map view') self._moveMapAction = self._addMapAction(MapAction.MoveMap, 'Move map view') self._moveMapAction.setChecked(True) self._filterActionGroup = QActionGroup(self) self._noFilterAction = self._addFilterAction(FilterAction.NoFilterAction, 'No filter action') self._includeFilterAction = self._addFilterAction(FilterAction.IncludeFilter, 'Add to filter') self._exclusiveFilterAction = self._addFilterAction(FilterAction.ExclusiveFilter, 'Exclusive filter') self._selectFilterAction = self._addFilterAction(FilterAction.SelectFilter, 'Add to selection') self._exclusiveSelectFilterAction = self._addFilterAction( FilterAction.ExclusiveSelectFilter, 'Exclusive selection') self._highlightFilterAction = self._addFilterAction(FilterAction.HighlightFilter, 'Add to highlight') self._exclusiveHighlightFilterAction = self._addFilterAction( FilterAction.ExclusiveHighlightFilter, 'Exclusive highlight') self._exclusiveHighlightFilterAction.setChecked(True) self._drawingActionGroup = QActionGroup(self) self._noDrawingAction = self._addDrawingAction(DrawingAction.NoDrawingAction, 'No drawing action') self._noDrawingAction.setChecked(True) self._loadDrawingsAction = self._addDrawingAction(DrawingAction.LoadDrawings, 'Load drawings') self._addDrawingsAction = self._addDrawingAction(DrawingAction.AddDrawings, 'Add drawings') self._settingsMenu = QMenu(self) self._settingsMenu.addActions(self._mapActionGroup.actions()) self._settingsMenu.addSeparator() self._settingsMenu.addActions(self._filterActionGroup.actions()) self._settingsMenu.addSeparator() self._settingsMenu.addActions(self._drawingActionGroup.actions()) self._settingsAction = QAction(QIcon(':/plugins/ark/settings.svg'), "Action Settings", self) self._settingsAction.setMenu(self._settingsMenu) self.setDefaultAction(self._settingsAction) self.setPopupMode(QToolButton.InstantPopup) def setMapAction(self, mapAction): if mapAction == MapAction.NoMapAction: self._noMapAction.setChecked(True) elif mapAction == MapAction.ZoomMap: self._zoomMapAction.setChecked(True) elif mapAction == MapAction.PanMap: self._panMapAction.setChecked(True) elif mapAction == MapAction.MoveMap: self._moveMapAction.setChecked(True) def setFilterAction(self, filterAction): if filterAction == FilterAction.NoFilterAction: self._noFilterAction.setChecked(True) elif filterAction == FilterAction.IncludeFilter: self._includeFilterAction.setChecked(True) elif filterAction == FilterAction.ExclusiveFilter: self._exclusiveFilterAction.setChecked(True) elif filterAction == FilterAction.SelectFilter: self._selectFilterAction.setChecked(True) elif filterAction == FilterAction.ExclusiveSelectFilter: self._exclusiveSelectFilterAction.setChecked(True) elif filterAction == FilterAction.HighlightFilter: self._highlightFilterAction.setChecked(True) elif filterAction == FilterAction.ExclusiveHighlightFilter: self._exclusiveHighlightFilterAction.setChecked(True) def setDrawingAction(self, drawingAction): if drawingAction == DrawingAction.NoDrawingAction: self._noDrawingAction.setChecked(True) elif drawingAction == DrawingAction.LoadDrawings: self._loadDrawingsAction.setChecked(True) elif drawingAction == DrawingAction.AddDrawings: self._addDrawingsAction.setChecked(True) def _addMapAction(self, mapAction, text): action = QAction(text, self) action.setCheckable(True) action.setData(mapAction) action.triggered.connect(self._mapActionSelected) self._mapActionGroup.addAction(action) return action def _mapActionSelected(self): self.mapActionChanged.emit(self._mapActionGroup.checkedAction().data()) self.settingsChanged.emit() def _addFilterAction(self, filterAction, text): action = QAction(text, self) action.setCheckable(True) action.setData(filterAction) action.triggered.connect(self._filterActionSelected) self._filterActionGroup.addAction(action) return action def _filterActionSelected(self): self.filterActionChanged.emit(self._filterActionGroup.checkedAction().data()) self.settingsChanged.emit() def _addDrawingAction(self, drawingAction, text): action = QAction(text, self) action.setCheckable(True) action.setData(drawingAction) action.triggered.connect(self._drawingActionSelected) self._drawingActionGroup.addAction(action) return action def _drawingActionSelected(self): self.drawingActionChanged.emit(self._drawingActionGroup.checkedAction().data()) self.settingsChanged.emit()
class XViewProfileToolBar(XToolBar): profileCreated = qt.Signal(qt.PyObject) profileChanged = qt.Signal(qt.PyObject) profileRemoved = qt.Signal(qt.PyObject) currentProfileChanged = qt.Signal(qt.PyObject) def __init__(self, parent): super(XViewProfileToolBar, self).__init__(parent) # create custom properties self._editingEnabled = True self._viewWidget = None self._profileGroup = QActionGroup(self) # set the default options self.setIconSize(QSize(48, 48)) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.setContextMenuPolicy(Qt.CustomContextMenu) # create connections self.actionTriggered.connect(self.handleActionTrigger) self.customContextMenuRequested.connect(self.showProfileMenu) def addProfile(self, profile): """ Adds the inputed profile as an action to the toolbar. :param profile | <projexui.widgets.xviewwidget.XViewProfile> """ act = XViewProfileAction(profile, self) self._profileGroup.addAction(act) self.addAction(act) return act def currentProfile(self): """ Returns the current profile for this toolbar. :return <projexui.widgets.xviewwidget.XViewProfile> || None """ act = self._profileGroup.checkedAction() if (act): return act.profile() return None def createProfile(self, profile=None, clearLayout=True): """ Prompts the user to create a new profile. """ if (profile): prof = profile elif (not self.viewWidget() or clearLayout): prof = XViewProfile() else: prof = self.viewWidget().saveProfile() blocked = self.signalsBlocked() self.blockSignals(False) changed = self.editProfile(prof) self.blockSignals(blocked) if (not changed): return act = self.addProfile(prof) act.setChecked(True) # update the interface if (self.viewWidget() and (profile or clearLayout)): self.viewWidget().restoreProfile(prof) if (not self.signalsBlocked()): self.profileCreated.emit(prof) @qt.Slot(qt.PyObject) def editProfile(self, profile): """ Prompts the user to edit the given profile. :param profile | <projexui.widgets.xviewwidget.XViewProfile> """ mod = XViewProfileDialog.edit(self, profile) if (not mod): return False # update the action interface for act in self._profileGroup.actions(): if (act.profile() == profile): act.setProfile(profile) break # signal the change if (not self.signalsBlocked()): self.profileChanged.emit(profile) return True def exportProfiles(self, filename=None): """ Exports this toolbar to the given filename. :param filename | <str> || None """ if (not filename): filename = QFileDialog.getSaveFileName(self, 'Export Toolbar', '', 'Toolbar Files (*.xtool)') if (not filename): return False profile_xml = self.toXml() projex.text.xmlindent(profile_xml) profile_string = ElementTree.tostring(profile_xml) f = open(str(filename), 'w') f.write(profile_string) f.close() return True def handleActionTrigger(self, action): """ Handles when an action has been triggered. If the inputed action is a XViewProfileAction, then the currentProfileChanged signal will emit. :param action | <QAction> """ # trigger a particular profile if (isinstance(action, XViewProfileAction)): if (not self.signalsBlocked()): self.currentProfileChanged.emit(action.profile()) if (self._viewWidget): self._viewWidget.restoreProfile(action.profile()) def importProfiles(self, filename=None): """ Imports the profiles from the given filename. :param filename | <str> || None """ if (not filename): filename = QFileDialog.getOpenFileName(self, 'Import Toolbar', '', 'Toolbar Files (*.xtool)') if type(filename) == tuple: filename = str(filename[0]) if (not (filename and os.path.exists(filename))): return False f = open(str(filename), 'r') profile_string = f.read() f.close() self.loadString(profile_string) # load the default toolbar action = self._profileGroup.checkedAction() if (action): self.handleActionTrigger(action) def isEditingEnabled(self): """ Sets whether or not the create is enabled for this toolbar. :return <bool> """ return self._editingEnabled def isEmpty(self): """ Returns whether or not this toolbar is empty. :return <bool> """ return len(self._profileGroup.actions()) == 0 def loadString(self, profilestr): """ Loads the information for this toolbar from the inputed string. :param profilestr | <str> """ try: xtoolbar = ElementTree.fromstring(str(profilestr)) except ExpatError, e: return self.clear() curr = xtoolbar.get('current') for xprofile in xtoolbar: prof = XViewProfile.fromXml(xprofile) act = self.addProfile(prof) if (prof.name() == curr): act.setChecked(True)
class QMap(): def __init__(self, iface): self.iface = iface self.actions = [] self.panels= [] self.navtoolbar = self.iface.mapNavToolToolBar() self.mainwindow = self.iface.mainWindow() self.iface.projectRead.connect(self.projectOpened) self.iface.initializationCompleted.connect(self.setupUI) self.actionGroup = QActionGroup(self.mainwindow) self.actionGroup.setExclusive(True) self.menuGroup = QActionGroup(self.mainwindow) self.menuGroup.setExclusive(True) self.movetool = MoveTool(self.iface.mapCanvas(), []) self.report = PopDownReport(self.iface.messageBar()) self.dialogprovider = DialogProvider(iface.mapCanvas(), iface) self.dialogprovider.accepted.connect(self.clearToolRubberBand) self.dialogprovider.rejected.connect(self.clearToolRubberBand) self.edittool = EditTool(self.iface.mapCanvas(),[]) self.edittool.finished.connect(self.openForm) @property def _mapLayers(self): return QgsMapLayerRegistry.instance().mapLayers() def clearToolRubberBand(self): tool = self.iface.mapCanvas().mapTool() try: tool.clearBand() except AttributeError: # No clearBand method found, but that's cool. pass def missingLayers(self, layers): def showError(): html = ["<h1>Missing Layers</h1>", "<ul>"] for layer in layers: html.append("<li>{}</li>".format(layer)) html.append("</ul>") self.errorreport.updateHTML("".join(html)) message = "Seems like {} didn't load correctly".format(utils._pluralstring('layer', len(layers))) utils.warning("Missing layers") map(utils.warning, layers) self.widget = self.messageBar.createMessage("Missing Layers", message, QIcon(":/icons/sad")) button = QPushButton(self.widget) button.setCheckable(True) button.setChecked(self.errorreport.isVisible()) button.setText("Show missing layers") button.toggled.connect(showError) button.toggled.connect(functools.partial(self.errorreport.setVisible)) self.widget.destroyed.connect(self.hideReports) self.widget.layout().addWidget(button) self.messageBar.pushWidget(self.widget, QgsMessageBar.WARNING) def excepthook(self, ex_type, value, tb): """ Custom exception hook so that we can handle errors in a nicer way """ where = ''.join(traceback.format_tb(tb)) msg = '{}'.format(value) utils.critical(msg) def showError(): html = """ <html> <body bgcolor="#FFEDED"> <p><b>{}</b></p> <p align="left"><small>{}</small></p> </body> </html> """.format(msg, where) self.errorreport.updateHTML(html) self.widget = self.messageBar.createMessage("oops", "Looks like an error occurred", QIcon(":/icons/sad")) button = QPushButton(self.widget) button.setCheckable(True) button.setChecked(self.errorreport.isVisible()) button.setText("Show error") button.toggled.connect(showError) button.toggled.connect(functools.partial(self.errorreport.setVisible)) self.widget.destroyed.connect(self.hideReports) self.widget.layout().addWidget(button) self.messageBar.pushWidget(self.widget, QgsMessageBar.CRITICAL) def hideReports(self): self.errorreport.setVisible(False) self.report.setVisible(False) def setupUI(self): """ Set up the main QGIS interface items. Called after QGIS has loaded the plugin. """ fullscreen = utils.settings["fullscreen"] if fullscreen: self.mainwindow.showFullScreen() else: self.mainwindow.showMaximized() self.navtoolbar.setMovable(False) self.navtoolbar.setAllowedAreas(Qt.TopToolBarArea) self.mainwindow.insertToolBar(self.toolbar, self.navtoolbar) self.openProjectAction.trigger() def setMapTool(self, tool): """ Set the current mapview canvas tool tool -- The QgsMapTool to set """ self.iface.mapCanvas().setMapTool(tool) def createToolBars(self): """ Create all the needed toolbars """ self.menutoolbar = QToolBar("Menu", self.mainwindow) self.menutoolbar.setMovable(False) self.menutoolbar.setAllowedAreas(Qt.LeftToolBarArea) self.mainwindow.addToolBar(Qt.LeftToolBarArea, self.menutoolbar) self.toolbar = QToolBar("QMap", self.mainwindow) self.mainwindow.addToolBar(Qt.TopToolBarArea, self.toolbar) self.toolbar.setMovable(False) self.editingtoolbar = FloatingToolBar("Editing", self.toolbar) self.extraaddtoolbar = FloatingToolBar("Extra Add Tools", self.toolbar) self.syncactionstoolbar = FloatingToolBar("Syncing", self.toolbar) self.syncactionstoolbar.setOrientation(Qt.Vertical) def createActions(self): """ Create all the actions """ self.homeAction = (QAction(QIcon(":/icons/zoomfull"), "Default View", self.mainwindow)) self.gpsAction = (GPSAction(QIcon(":/icons/gps"), self.iface.mapCanvas(), self.mainwindow)) self.openProjectAction = (QAction(QIcon(":/icons/open"), "Projects", self.mainwindow)) self.openProjectAction.setCheckable(True) self.toggleRasterAction = (QAction(QIcon(":/icons/photo"), "Aerial Photos", self.mainwindow)) self.syncAction = QAction(QIcon(":/icons/sync"), "Sync", self.mainwindow) self.syncAction.setVisible(False) self.editattributesaction = QAction(QIcon(":/icons/edit"), "Edit Attributes", self.mainwindow) self.editattributesaction.setCheckable(True) self.editattributesaction.toggled.connect(functools.partial(self.setMapTool, self.edittool)) self.moveaction = QAction(QIcon(":/icons/move"), "Move Feature", self.mainwindow) self.moveaction.setCheckable(True) self.editingmodeaction = QAction(QIcon(":/icons/edittools"), "Editing Tools", self.mainwindow) self.editingmodeaction.setCheckable(True) self.addatgpsaction = QAction(QIcon(":/icons/gpsadd"), "Add at GPS", self.mainwindow) self.edittool.layersupdated.connect(self.editattributesaction.setVisible) self.movetool.layersupdated.connect(self.moveaction.setVisible) self.edittool.layersupdated.connect(self.updateEditTools) self.movetool.layersupdated.connect(self.updateEditTools) def updateEditTools(self, *args): """ Show or hide the Editing Tools button based on the sub tools. """ if self.edittool.layers and self.movetool.layers: self.editingmodeaction.setVisible(True) else: self.editingmodeaction.setVisible(False) def initGui(self): """ Create all the icons and setup the tool bars. Called by QGIS when loading. This is called before setupUI. """ QApplication.setWindowIcon(QIcon(":/branding/logo")) self.mainwindow.findChildren(QMenuBar)[0].setVisible(False) self.mainwindow.setContextMenuPolicy(Qt.PreventContextMenu) self.mainwindow.setWindowTitle("IntraMaps Roam: Mobile Data Collection") # Disable QGIS logging window popups. We do our own logging QgsMessageLog.instance().messageReceived.disconnect() s = """ QToolButton { padding: 6px; color: #4f4f4f; } QToolButton:hover { padding: 6px; background-color: rgb(211, 228, 255); } QToolBar { background: white; } QCheckBox::indicator { width: 40px; height: 40px; } QLabel { color: #4f4f4f; } QDialog { background-color: rgb(255, 255, 255); } QPushButton { border: 1px solid #e1e1e1; padding: 6px; color: #4f4f4f; } QPushButton:hover { border: 1px solid #e1e1e1; padding: 6px; background-color: rgb(211, 228, 255); } QCheckBox { color: #4f4f4f; } QComboBox::drop-down { width: 30px; } """ self.mainwindow.setStyleSheet(s) mainwidget = self.mainwindow.centralWidget() mainwidget.setLayout(QGridLayout()) mainwidget.layout().setContentsMargins(0,0,0,0) newlayout = QGridLayout() newlayout.setContentsMargins(0,0,0,0) newlayout.addWidget(self.iface.mapCanvas(), 0,0,2,1) newlayout.addWidget(self.iface.messageBar(), 0,0,1,1) wid = QWidget() wid.setLayout(newlayout) self.stack = QStackedWidget() self.messageBar = QgsMessageBar(wid) self.messageBar.setSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed ) self.errorreport = PopDownReport(self.messageBar) mainwidget.layout().addWidget(self.stack, 0,0,2,1) mainwidget.layout().addWidget(self.messageBar, 0,0,1,1) self.helppage = HelpPage() helppath = os.path.join(os.path.dirname(__file__) , 'help',"help.html") self.helppage.setHelpPage(helppath) self.projectwidget = ProjectsWidget() self.projectwidget.requestOpenProject.connect(self.loadProject) self.stack.addWidget(wid) self.stack.addWidget(self.projectwidget) self.stack.addWidget(self.helppage) sys.excepthook = self.excepthook def createSpacer(width=30): widget = QWidget() widget.setMinimumWidth(width) return widget self.createToolBars() self.createActions() spacewidget = createSpacer(60) gpsspacewidget = createSpacer() gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.moveaction.toggled.connect(functools.partial(self.setMapTool, self.movetool)) showediting = (functools.partial(self.editingtoolbar.showToolbar, self.editingmodeaction, self.editattributesaction)) self.editingmodeaction.toggled.connect(showediting) self.addatgpsaction.triggered.connect(self.addAtGPS) self.addatgpsaction.setEnabled(self.gpsAction.isConnected) self.gpsAction.gpsfixed.connect(self.addatgpsaction.setEnabled) self.editingtoolbar.addToActionGroup(self.editattributesaction) self.editingtoolbar.addToActionGroup(self.moveaction) self.actionGroup.addAction(self.editingmodeaction) self.homeAction.triggered.connect(self.zoomToDefaultView) self.openProjectAction.triggered.connect(self.showOpenProjectDialog) self.openProjectAction.triggered.connect(functools.partial(self.stack.setCurrentIndex, 1)) self.toggleRasterAction.triggered.connect(self.toggleRasterLayers) self.navtoolbar.insertAction(self.iface.actionZoomIn(), self.iface.actionTouch()) self.navtoolbar.insertAction(self.iface.actionTouch(), self.homeAction) self.navtoolbar.insertAction(self.iface.actionTouch(), self.iface.actionZoomFullExtent()) self.navtoolbar.insertAction(self.homeAction, self.iface.actionZoomFullExtent()) self.navtoolbar.addAction(self.toggleRasterAction) self.navtoolbar.insertWidget(self.iface.actionZoomFullExtent(), spacewidget) self.toolbar.addAction(self.editingmodeaction) self.toolbar.addAction(self.syncAction) self.toolbar.addAction(self.gpsAction) self.toolbar.insertWidget(self.syncAction, gpsspacewidget) self.toolbar.insertSeparator(self.gpsAction) self.extraaddtoolbar.addAction(self.addatgpsaction) self.editingtoolbar.addAction(self.editattributesaction) self.editingtoolbar.addAction(self.moveaction) self.mapview = QAction(QIcon(":/icons/map"), "Map", self.menutoolbar) self.mapview.setCheckable(True) self.mapview.triggered.connect(functools.partial(self.stack.setCurrentIndex, 0)) self.help = QAction(QIcon(":/icons/help"), "Help", self.menutoolbar) self.help.setCheckable(True) self.help.triggered.connect(functools.partial(self.stack.setCurrentIndex, 2)) self.help.setVisible(False) self.userlabel = QLabel("Current User <br> {user}".format(user=getpass.getuser())) self.userlabel.setAlignment(Qt.AlignCenter) self.userlabel.setStyleSheet(""" QLabel { color: #8c8c8c; font: 10px "Calibri" ; }""") self.quit = QAction(QIcon(":/icons/quit"), "Quit", self.menutoolbar) self.quit.triggered.connect(self.iface.actionExit().trigger) self.menuGroup.addAction(self.mapview) self.menuGroup.addAction(self.openProjectAction) self.menuGroup.addAction(self.help) self.menutoolbar.addAction(self.mapview) self.menutoolbar.addAction(self.openProjectAction) self.menutoolbar.addAction(self.help) self.menutoolbar.addAction(self.quit) quitspacewidget = createSpacer() quitspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) labelaction = self.menutoolbar.insertWidget(self.quit, self.userlabel) self.menutoolbar.insertWidget(labelaction, quitspacewidget) self.setupIcons() self.stack.currentChanged.connect(self.updateUIState) def updateUIState(self, page): """ Update the UI state to reflect the currently selected page in the stacked widget """ def setToolbarsActive(enabled): toolbars = self.mainwindow.findChildren(QToolBar) for toolbar in toolbars: if toolbar == self.menutoolbar: continue toolbar.setEnabled(enabled) def setPanelsVisible(visible): for panel in self.panels: panel.setVisible(visible) ismapview = page == 0 setToolbarsActive(ismapview) setPanelsVisible(ismapview) def addAtGPS(self): """ Add a record at the current GPS location. """ action = self.actionGroup.checkedAction() if not action: return layer = action.data() if not layer: return point = self.gpsAction.position self.addNewFeature(layer=layer, geometry=point) def zoomToDefaultView(self): """ Zoom the mapview canvas to the extents the project was opened at i.e. the default extent. """ self.iface.mapCanvas().setExtent(self.defaultextent) self.iface.mapCanvas().refresh() def toggleRasterLayers(self): """ Toggle all raster layers on or off. """ legend = self.iface.legendInterface() #Freeze the canvas to save on UI refresh self.iface.mapCanvas().freeze() for layer in self._mapLayers.values(): if layer.type() == QgsMapLayer.RasterLayer: isvisible = legend.isLayerVisible(layer) legend.setLayerVisible(layer, not isvisible) self.iface.mapCanvas().freeze(False) self.iface.mapCanvas().refresh() def setupIcons(self): """ Update toolbars to have text and icons, change normal QGIS icons to new style """ toolbars = self.mainwindow.findChildren(QToolBar) for toolbar in toolbars: toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QSize(32, 32)) self.iface.actionTouch().setIconText("Pan") self.iface.actionTouch().setIcon(QIcon(":/icons/pan")) self.iface.actionZoomIn().setIcon(QIcon(":/icons/in")) self.iface.actionZoomOut().setIcon(QIcon(":/icons/out")) self.iface.actionPan().setIcon(QIcon(":/icons/pan")) self.iface.actionZoomFullExtent().setIcon(QIcon(":/icons/home")) self.iface.actionZoomFullExtent().setIconText("Home View") self.actionGroup.addAction(self.iface.actionZoomIn()) self.actionGroup.addAction(self.iface.actionZoomOut()) self.actionGroup.addAction(self.iface.actionTouch()) def projectOpened(self): """ Called when a new project is opened in QGIS. """ for panel in self.panels: self.mainwindow.removeDockWidget(panel) del panel projectpath = QgsProject.instance().fileName() project = QMapProject(os.path.dirname(projectpath), self.iface) self.createFormButtons(projectlayers = project.getConfiguredLayers()) # Enable the raster layers button only if the project contains a raster layer. hasrasters = any(layer.type() for layer in self._mapLayers.values()) self.toggleRasterAction.setEnabled(hasrasters) self.defaultextent = self.iface.mapCanvas().extent() self.connectSyncProviders(project) # Show panels self.panels = list(project.getPanels()) for panel in self.panels: self.mainwindow.addDockWidget(Qt.BottomDockWidgetArea , panel) self.iface.messageBar().popWidget() def createFormButtons(self, projectlayers): """ Create buttons for each form that is definded """ # Remove all the old buttons for action in self.actions: self.actionGroup.removeAction(action) self.toolbar.removeAction(action) self.edittool.layers = [] self.movetool.layers = [] for layer in projectlayers: try: qgslayer = QgsMapLayerRegistry.instance().mapLayersByName(layer.name)[0] if qgslayer.type() == QgsMapLayer.RasterLayer: utils.log("We can't support raster layers for data entry") continue layer.QGISLayer = qgslayer except KeyError: utils.log("Layer not found in project") continue if 'capture' in layer.capabilities: text = layer.icontext try: tool = layer.getMaptool(self.iface.mapCanvas()) except NoMapToolConfigured: utils.log("No map tool configured") continue except ErrorInMapTool as error: self.messageBar.pushMessage("Error configuring map tool", error.message, level=QgsMessageBar.WARNING) continue # Hack until I fix it later if isinstance(tool, PointTool): add = functools.partial(self.addNewFeature, qgslayer) tool.geometryComplete.connect(add) else: tool.finished.connect(self.openForm) action = QAction(QIcon(layer.icon), text, self.mainwindow) action.setData(layer) action.setCheckable(True) action.toggled.connect(functools.partial(self.setMapTool, tool)) self.toolbar.insertAction(self.editingmodeaction, action) if not tool.isEditTool(): # Connect the GPS tools strip to the action pressed event. showgpstools = (functools.partial(self.extraaddtoolbar.showToolbar, action, None)) action.toggled.connect(showgpstools) self.actionGroup.addAction(action) self.actions.append(action) if 'edit' in layer.capabilities: # TODO Use snapping options from project radius = (QgsTolerance.toleranceInMapUnits( 10, qgslayer, self.iface.mapCanvas().mapRenderer(), QgsTolerance.Pixels)) self.edittool.addLayer(qgslayer) self.edittool.searchRadius = radius if 'move' in layer.capabilities: self.movetool.addLayer(qgslayer) def openForm(self, layer, feature): if not layer.isEditable(): layer.startEditing() self.dialogprovider.openDialog(feature=feature, layer=layer) def addNewFeature(self, layer, geometry): fields = layer.pendingFields() if not layer.isEditable(): layer.startEditing() feature = QgsFeature() feature.setGeometry( geometry ) feature.initAttributes(fields.count()) feature.setFields(fields) for indx in xrange(fields.count()): feature[indx] = layer.dataProvider().defaultValue(indx) self.dialogprovider.openDialog(feature=feature, layer=layer) def showOpenProjectDialog(self): """ Show the project selection dialog. """ self.stack.setCurrentIndex(1) path = os.path.join(os.path.dirname(__file__), '..' , 'projects/') projects = getProjects(path, self.iface) self.projectwidget.loadProjectList(projects) def loadProject(self, project): """ Load a project into QGIS. """ utils.log(project) utils.log(project.name) utils.log(project.projectfile) utils.log(project.vaild) (passed, message) = project.onProjectLoad() if not passed: QMessageBox.warning(self.mainwindow, "Project Load Rejected", "Project couldn't be loaded because {}".format(message)) return self.mapview.trigger() self.iface.newProject(False) self.iface.mapCanvas().freeze() fileinfo = QFileInfo(project.projectfile) self.badLayerHandler = BadLayerHandler(callback=self.missingLayers) QgsProject.instance().setBadLayerHandler( self.badLayerHandler ) self.iface.messageBar().pushMessage("Project Loading","", QgsMessageBar.INFO) QgsProject.instance().read(fileinfo) self.iface.mapCanvas().updateScale() self.iface.mapCanvas().freeze(False) self.iface.mapCanvas().refresh() self.mainwindow.setWindowTitle("IntraMaps Roam: Mobile Data Collection") self.iface.projectRead.emit() def unload(self): del self.toolbar def connectSyncProviders(self, project): self.syncactionstoolbar.clear() syncactions = list(project.getSyncProviders()) # Don't show the sync button if there is no sync providers if not syncactions: self.syncAction.setVisible(False) return self.syncAction.setVisible(True) for provider in syncactions: action = QAction(QIcon(":/icons/sync"), "Sync {}".format(provider.name), self.mainwindow) action.triggered.connect(functools.partial(self.syncProvider, provider)) self.syncactionstoolbar.addAction(action) try: self.syncAction.toggled.disconnect() except TypeError: pass try: self.syncAction.triggered.disconnect() except TypeError: pass if len(syncactions) == 1: # If one provider is set then we just connect the main button. self.syncAction.setCheckable(False) self.syncAction.setText("Sync") self.syncAction.triggered.connect(functools.partial(self.syncProvider, syncactions[0])) else: # the sync button because a sync menu self.syncAction.setCheckable(True) self.syncAction.setText("Sync Menu") showsyncoptions = (functools.partial(self.syncactionstoolbar.showToolbar, self.syncAction, None)) self.syncAction.toggled.connect(showsyncoptions) def syncstarted(self): # Remove the old widget if it's still there. # I don't really like this. Seems hacky. try: self.iface.messageBar().popWidget(self.syncwidget) except RuntimeError: pass except AttributeError: pass self.iface.messageBar().findChildren(QToolButton)[0].setVisible(False) self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync in progress", QIcon(":/icons/syncing")) button = QPushButton(self.syncwidget) button.setCheckable(True) button.setText("Status") button.setIcon(QIcon(":/icons/syncinfo")) button.toggled.connect(functools.partial(self.report.setVisible)) pro = QProgressBar() pro.setMaximum(0) pro.setMinimum(0) self.syncwidget.layout().addWidget(pro) self.syncwidget.layout().addWidget(button) self.iface.messageBar().pushWidget(self.syncwidget, QgsMessageBar.INFO) def synccomplete(self): try: self.iface.messageBar().popWidget(self.syncwidget) except RuntimeError: pass stylesheet = ("QgsMessageBar { background-color: rgba(239, 255, 233); border: 0px solid #b9cfe4; } " "QLabel,QTextEdit { color: #057f35; } ") closebutton = self.iface.messageBar().findChildren(QToolButton)[0] closebutton.setVisible(True) closebutton.clicked.connect(functools.partial(self.report.setVisible, False)) self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync Complete", QIcon(":/icons/syncdone")) button = QPushButton(self.syncwidget) button.setCheckable(True) button.setChecked(self.report.isVisible()) button.setText("Sync Report") button.setIcon(QIcon(":/icons/syncinfo")) button.toggled.connect(functools.partial(self.report.setVisible)) pro = QProgressBar() pro.setMaximum(100) pro.setValue(100) self.syncwidget.layout().addWidget(pro) self.syncwidget.layout().addWidget(button) self.iface.messageBar().pushWidget(self.syncwidget) self.iface.messageBar().setStyleSheet(stylesheet) self.iface.mapCanvas().refresh() def syncerror(self): try: self.iface.messageBar().popWidget(self.syncwidget) except RuntimeError: pass closebutton = self.iface.messageBar().findChildren(QToolButton)[0] closebutton.setVisible(True) closebutton.clicked.connect(functools.partial(self.report.setVisible, False)) self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync Error", QIcon(":/icons/syncfail")) button = QPushButton(self.syncwidget) button.setCheckable(True) button.setChecked(self.report.isVisible()) button.setText("Sync Report") button.setIcon(QIcon(":/icons/syncinfo")) button.toggled.connect(functools.partial(self.report.setVisible)) self.syncwidget.layout().addWidget(button) self.iface.messageBar().pushWidget(self.syncwidget, QgsMessageBar.CRITICAL) self.iface.mapCanvas().refresh() def syncProvider(self, provider): self.syncAction.toggle() provider.syncStarted.connect(functools.partial(self.syncAction.setEnabled, False)) provider.syncStarted.connect(self.syncstarted) provider.syncComplete.connect(self.synccomplete) provider.syncComplete.connect(functools.partial(self.syncAction.setEnabled, True)) provider.syncComplete.connect(functools.partial(self.report.updateHTML)) provider.syncMessage.connect(self.report.updateHTML) provider.syncError.connect(self.report.updateHTML) provider.syncError.connect(self.syncerror) provider.syncComplete.connect(functools.partial(self.syncAction.setEnabled, True)) provider.startSync()
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; } QCheckBox::indicator:unchecked { image: url(:/checkbox_unchecked.png); } QCheckBox::indicator:unchecked:hover { image: url(:/checkbox_unchecked_hover.png); } QCheckBox::indicator:unchecked:pressed { image: url(:/checkbox_unchecked_pressed.png); } QCheckBox::indicator:checked { image: url(:/checkbox_checked.png); } QCheckBox::indicator:checked:hover { image: url(:/checkbox_checked_hover.png); } QCheckBox::indicator:checked:pressed { image: url(:/checkbox_checked_pressed.png); } 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() == 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.setProperty("currentText", port) baudrate = tree.findtext('GUISettings/PortConfig/baudrate', default='38400') if baudrate != '': self.cmbBaudRate.setProperty("currentText", 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 StockMain(QMainWindow, Ui_MainWindow): """ Class documentation goes here. """ def __init__(self, parent=None): """ Constructor """ QMainWindow.__init__(self, parent) self.setupUi(self) self.initTypeCombo() self.ids = [] self.tableSetting = None self.selectedGroup = None self.classifyMenuGroup = None regx = QRegExp("[0-9]*[\.]{0,1}[0-9]*$") validator = QRegExpValidator(regx, self) self.smallValueEdit.setValidator(validator) self.bigValueEdit.setValidator(validator) self.weightEdit.setValidator(validator) self.on_daySumRadio_clicked() self.initCmpMethCombo() self.initCmpTypeCombo() self.initCrossTypeCombo() self.filter = '' # myGlobal.init() # myGlobal.initDealDays() self.dayInfoModel = CustomModel(self) self.dayInfoModel.setRestApi('liststockdayinfo') self.dayInfoModel.setPageSize(10000) self.calcModel = CustomModel(self) self.calcModel.setRestApi('listmonthsum') self.calcModel.setPageSize(20000) self.calcModel2 = CustomModel(self) self.calcModel2.setRestApi('liststockdaysdiff') self.calcModel2.setPageSize(10000) self.crossModel = CustomModel(self) self.crossModel.setRestApi('listcrossinfo') self.crossModel.setPageSize(10000) self.combineModel = CombineModel(self) self.combineModel.setPageSize(10000) self.classifyMenu = None self.endDate = QDate.currentDate() self.startDate = self.endDate.addDays(-1) self.calcTableWidget.setButtonsVisible(False) self.combineWidget.setButtonsVisible(False) self.combineWidget.clearBtn.setVisible(True) self.combineWidget.undoBtn.setVisible(True) #self.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu); savedSetting = config.readSetting() if 'groups' in savedSetting: self.groups = savedSetting['groups'] else: self.groups = {} self.updateFilter() self.listWidget.setVisible(False) self.listWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.modifyDateEdit() self.showGroupBtn.setChecked(True) self.on_showGroupBtn_clicked() self.showMaximized() def modifyDateEdit(self): curIndex = self.tabWidget.currentIndex() if curIndex == 0: self.startDateEdit = self.startDateEdit_1 self.endDateEdit = self.endDateEdit_1 elif curIndex == 1: self.startDateEdit = self.startDateEdit_2 self.endDateEdit = self.endDateEdit_2 elif curIndex == 2: self.startDateEdit = self.startDateEdit_3 self.endDateEdit = self.endDateEdit_3 elif curIndex == 3: self.startDateEdit = self.startDateEdit_4 self.endDateEdit = self.endDateEdit_4 self.startDateEdit.setDate(self.startDate) self.endDateEdit.setDate(self.endDate) def updateFilter(self): config.writeSetting('groups', self.groups) self.listWidget.clear() for key in self.groups: self.listWidget.addItem(key) def initTypeCombo(self): self.customName = u'均价' self.customType = 'D' self.customNum = 3 self.typeNames = { u'均价':'avg_price', \ u'涨幅':'growth', \ u'换手':'turn',\ u'振幅':'amp',\ u'总金额':'total',\ u'量比':'vol'} for key in self.typeNames: self.typeCombo.addItem(key) self.sumTypeNames = { u'正和':'positive', \ u'负和':'negative', \ u'所有和':'all' } for key in self.sumTypeNames: self.sumTypeCombo.addItem(key) def initCmpMethCombo(self): self.cmpMethNames = { u'指定两天加':'plus', \ u'指定两天减':'minus', \ u'指定两天比值':'divide', \ u'指定时间段内最大值减最小值':'maxmin', \ u'指定时间段内最大值比最小值':'maxmindivide', \ u'指定时间段内的和':'sum', \ u'两个时间内涨幅,振幅数据分段':"seperate", } for key in self.cmpMethNames: self.cmpMethCombo.addItem(key) def initCmpTypeCombo(self): #avg_price,growth_ratio,current_price,total_stock,total_value,avg_circulation_value,cir_of_cap_stock self.cmpTypeCombo.clear() self.cmpTypeNames = { u'均价':'avg_price', \ u'涨幅':'growth_ratio', \ u'总股本':'total_stock', \ u'总市值':'total_value', \ u'均价流通市值':'avg_circulation_value', \ u'流通股本':'cir_of_cap_stock', \ u'现价':'current_price',\ u'换手':'turnover_ratio',\ u'总金额':'total_money',\ u'振幅':'amplitude_ratio',\ u'量比':'volume_ratio'} for key in self.cmpTypeNames: self.cmpTypeCombo.addItem(key) def initCrossTypeCombo(self): #avg_price,growth_ratio,current_price,total_stock,total_value,avg_circulation_value,cir_of_cap_stock self.crossTypeCombo.clear() self.crossTypeNames = { u'昨收': 'ytd_end_price', u'均价': 'avg_price', u'均价流通市值': 'avg_circulation_value', u'总市值': 'total_value', u'总股本': 'total_stock', u'流通股本': 'cir_of_cap_stock', } for key in self.crossTypeNames: self.crossTypeCombo.addItem(key) @pyqtSignature("") def on_daySumRadio_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet self.customType = 'D' self.numCombo.clear() items = [str(i) for i in range(3, 31)] for item in items: self.numCombo.addItem(item) #raise NotImplementedError @pyqtSignature("") def on_weekSumRadio_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet self.customType = 'W' self.numCombo.clear() items = [str(i) for i in range(1, 7)] for item in items: self.numCombo.addItem(item) #raise NotImplementedError @pyqtSignature("") def on_monthSumRadio_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet self.customType = 'M' self.numCombo.clear() items = [str(i) for i in range(1, 13)] for item in items: self.numCombo.addItem(item) def chooseNearDate( self, d, ): lastDate = '' for tmpDate in myGlobal.dealDays: if tmpDate > d: if lastDate != '': return lastDate.strftime( "%Y-%m-%d") + ', ' + tmpDate.strftime("%Y-%m-%d") else: return u'无, ' + tmpDate.strftime("%Y-%m-%d") lastDate = tmpDate return lastDate.strftime("%Y-%m-%d") + u', 无' def testDate(self, startD, endD): if startD >= endD: QMessageBox.warning(self, 'warning', u'开始时间大于或等于结束时间') return False if startD not in myGlobal.dealDays: QMessageBox.warning( self, 'warning', u'开始时间非交易日或无数据,请重新选择,前后的交易日期分别为: ' + self.chooseNearDate(startD)) return False if endD not in myGlobal.dealDays: QMessageBox.warning( self, 'warning', u'结束时间非交易日或无数据,请重新选择,前后的交易日期分别为: ' + self.chooseNearDate(endD)) return False return True @pyqtSignature("") def on_queryBtn_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet # from loading import Loading # import http # http.callRestAsync(self, "xxx", {"sss":'s'}, self.callBack) # if True: # return if len(self.ids) == 0: QMessageBox.warning(self, 'warning', u'所选代码为空,请选择代码') return startD = self.startDateEdit.date().toPyDate() endD = self.endDateEdit.date().toPyDate() self.startDate = self.startDateEdit.date() self.endDate = self.endDateEdit.date() if not self.testDate(startD, endD): return #response=json&page=2&pagesize=20&stockid=000001,000002,000003,000004,000005&starttime=2008-09-24&sortname=turnover_ratio config.collect( 'info', u'原始数据查询, 起始时间:%s, 结束时间:%s, 代码: %s' % (startD, endD, self.ids)) args = {} if len(self.ids) == len(myGlobal.id2name.keys()): args = {'starttime': startD, 'endtime': endD} else: args = { 'stockid': ','.join(self.ids), 'starttime': startD, 'endtime': endD } self.dayInfoModel.setRestArgs(args) self.srcTableWidget.init(self.dayInfoModel, 2, self.tableSetting, self) @pyqtSignature("") def on_queryBtn_2_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet if len(self.ids) == 0: QMessageBox.warning(self, 'warning', u'所选代码为空,请选择代码') return startD = self.startDateEdit_2.date().toPyDate() endD = self.endDateEdit_2.date().toPyDate() self.startDate = self.startDateEdit.date() self.endDate = self.endDateEdit.date() #response=json&page=2&pagesize=20&stockid=000001,000002,000003,000004,000005&starttime=2008-09-24&sortname=turnover_ratio if not self.testDate(startD, endD): return customNum = self.numCombo.currentText().toInt()[0] calcName = self.typeNames[str( self.typeCombo.currentText().toUtf8()).decode('utf-8')] sumType = self.sumTypeNames[str( self.sumTypeCombo.currentText().toUtf8()).decode('utf-8')] log.log(calcName) config.collect( 'info', u'X日和查询, 起始时间:%s, 结束时间:%s, 查询指标:%s, 查询类型:%s 代码: %s,' % (startD, endD, str( self.typeCombo.currentText().toUtf8()).decode('utf-8'), str(customNum) + self.customType.replace('D', u'日').replace( 'W', u'周').replace('M', u'月'), self.ids)) if len(self.ids) == len(myGlobal.id2name.keys()): args = { 'starttime': startD, 'endtime': endD, 'sumtype': sumType, 'sumname': calcName } else: args = { 'stockid': ','.join(self.ids), 'starttime': startD, 'endtime': endD, 'sumType': sumType, 'sumname': calcName } if self.customType == 'D': self.calcModel.setRestApi('listdaysum') args['days'] = customNum elif self.customType == 'W': self.calcModel.setRestApi('listweeksum') args['weeks'] = customNum elif self.customType == 'M': self.calcModel.setRestApi('listmonthsum') args['months'] = customNum self.calcModel.setRestArgs(args) self.sumTableWidget.init(self.calcModel, parent=self) #raise NotImplementedError @pyqtSignature("") def on_action_triggered(self): """ Slot documentation goes here. 设置dayinfo表中的显示列 """ # TODO: not implemented yet if not self.srcTableWidget.inited: QMessageBox.warning(self, 'warning', u'列表中尚无内容,请先查询') return tableSetting = TableSetting.getSetting(self.srcTableWidget.getView(), self) log.log(tableSetting) if tableSetting is not None: self.tableSetting = tableSetting self.srcTableWidget.setSetting(self.tableSetting) @pyqtSignature("QPoint") def on_listWidget_customContextMenuRequested(self, pos): """ Slot documentation goes here. 代码筛选框中的右键 """ # TODO: not implemented yet cur = self.cursor() curPos = cur.pos() log.log(curPos.x(), curPos.y()) menu = QMenu(self) menu.addAction(self.action_addGroup) if self.listWidget.itemAt(pos): menu.addAction(self.action_editGroup) menu.addAction(self.action_deleteGroup) menu.exec_(curPos) # #raise NotImplementedError def getTextFromItem(self, item): selectItem = None if type(item) == type([]): if len(item) <= 0: QMessageBox.warning(self, 'warning', u'没有选中任何组') return None selectItem = item[0] else: selectItem = item selectedItemText = str(selectItem.text().toUtf8()).decode('utf-8') return selectedItemText @pyqtSignature("") def on_action_addGroup_triggered(self): """ Slot documentation goes here. """ # TODO: not implemented yet ret = ChooseId.getIds(self, self.groups) if ret is None: return self.groups[ret[0]] = ret[1] # self.listWidget.addItem(ret[0]) config.collect( 'info', u'添加分组, 分组名称: %s, 分组详情: %s' % (ret[0], ','.join(ret[1]))) log.log(self.groups) self.updateFilter() #raise NotImplementedError @pyqtSignature("") def on_action_editGroup_triggered(self): """ Slot documentation goes here. """ # TODO: not implemented yet #print 'delete' selectedItemText = self.getTextFromItem(self.listWidget.selectedItems( )) #str(selectedItems[0].text().toUtf8()).decode('utf-8') if selectedItemText not in self.groups: QMessageBox.warning(self, 'warning', u'未知分组') return ret = ChooseId.getIds(self, self.groups, selectedItemText) if ret is not None: if selectedItemText != ret[0]: self.groups.pop(selectedItemText) self.groups[ret[0]] = ret[1] else: return config.collect( 'info', u'编辑分组, 分组名称: %s, 新分组详情: %s' % (ret[0], ','.join(ret[1]))) self.updateFilter() # config.writeSetting('groups', self.groups) if self.selectedGroup == selectedItemText: self.ids = self.groups[self.selectedGroup] self.clearClassify() self.changeIds(self.groups[self.selectedGroup]) #raise NotImplementedError @pyqtSignature("") def on_action_deleteGroup_triggered(self): """ Slot documentation goes here. """ # TODO: not implemented yet #raise NotImplementedError selectedItemText = self.getTextFromItem( self.listWidget.selectedItems()) if selectedItemText not in self.groups: QMessageBox.warning(self, 'warning', u'未知分组') return config.collect( 'info', u'删除分组, 分组名称: %s, 分组详情: %s' % (selectedItemText, ','.join(self.groups[selectedItemText]))) self.groups.pop(selectedItemText) self.updateFilter() # config.writeSetting('groups', self.groups) # @pyqtSignature("bool") # def on_action_showGroupView_triggered(self, checked): # """ # Slot documentation goes here. # """ # # TODO: not implemented yet # if checked: # self.groupView.show() # else: # self.groupView.hide() # #raise NotImplementedError # @pyqtSignature("bool") # def on_groupView_visibilityChanged(self, visible): # """ # Slot documentation goes here. # """ # # TODO: not implemented yet # if visible: # self.action_showGroupView.setChecked(True) # else: # self.action_showGroupView.setChecked(False) # #raise NotImplementedError @pyqtSignature("QListWidgetItem*") def on_listWidget_itemDoubleClicked(self, item): """ Slot documentation goes here. """ # TODO: not implemented yet self.selectedGroup = self.getTextFromItem( self.listWidget.selectedItems()) if self.selectedGroup not in self.groups: QMessageBox.warning(self, 'warning', u'未知分组') return # log.log('self.ids:', self.ids) self.clearClassify() self.changeIds(self.groups[self.selectedGroup]) # log('self.ids:', self.ids) # curIndex = self.tabWidget.currentIndex() # if curIndex == 0: # self.on_queryBtn_clicked() # elif curIndex == 1: # self.on_queryBtn_2_clicked() # elif curIndex == 2: # self.on_calculateBtn_clicked() #raise NotImplementedError @pyqtSignature("") def on_calculateBtn_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet #raise NotImplementedError if len(self.ids) == 0: QMessageBox.warning(self, 'warning', u'所选代码为空,请选择代码') return startD = self.startDateEdit_3.date().toPyDate() endD = self.endDateEdit_3.date().toPyDate() self.startDate = self.startDateEdit.date() self.endDate = self.endDateEdit.date() if not self.testDate(startD, endD): return #response=json&page=2&pagesize=20&stockid=000001,000002,000003,000004,000005&starttime=2008-09-24&sortname=turnover_ratio #optname=avg_price,growth_ratio,current_price&opt=-&starttime=2008-03-25&endtime=2008-03-28&page=4&pagesize=20 optname = self.cmpTypeNames[str( self.cmpTypeCombo.currentText().toUtf8()).decode( 'utf-8')] #self.cmpTypeCombo.currentText().toInt()[0] opt = self.cmpMethNames[str( self.cmpMethCombo.currentText().toUtf8()).decode('utf-8')] log.log(opt) config.collect( 'info', u'计算查询, 起始时间:%s, 结束时间:%s, 计算指标:%s, 计算类型:%s, 代码: %s' % (startD, endD, str( self.cmpTypeCombo.currentText().toUtf8()).decode('utf-8'), str(self.cmpMethCombo.currentText().toUtf8()).decode('utf-8'), self.ids)) if opt == 'seperate': args = { 'stockid': ','.join(self.ids), 'starttime': startD, 'endtime': endD } self.calcModel2.setRestApi('listgrowthampdis') elif opt == 'sum': args = { 'stockid': ','.join(self.ids), 'starttime': startD, 'endtime': endD, 'sumname': optname } self.calcModel2.setRestApi('listndayssum') else: args = { 'stockid': ','.join(self.ids), 'starttime': startD, 'endtime': endD, 'optname': optname, 'opt': opt } self.calcModel2.setRestApi('liststockdaysdiff') log.log("ids", len(self.ids), len(myGlobal.id2name.keys())) if len(self.ids) == len(myGlobal.id2name.keys()): args.pop('stockid') self.calcModel2.setRestArgs(args) if self.calcModel2.restApi == 'listgrowthampdis': self.calcTableWidget.init(self.calcModel2, 6, parent=self) else: self.calcTableWidget.init(self.calcModel2, 2, parent=self) # smallLimit = self.smallValueEdit.text().toFloat() # bigLimit = self.bigValueEdit.text().toFloat() # smallValue = None # bigValue = None # if smallLimit[1]: # smallValue = smallLimit[0] # if bigLimit[1]: # bigValue = bigLimit[0] # hideRows = self.calcModel2.calcRowsInLimit(4, smallValue, bigValue) # log(hideRows) # log(self.smallValueEdit.text().toFloat()). # log(self.bigValueEdit.text().toFloat()) # self.calcTableWidget.init(self.calcModel2, 0, {'hideRows': hideRows}) @pyqtSignature("int") def on_tabWidget_currentChanged(self, index): """ Slot documentation goes here. """ # TODO: not implemented yet # raise NotImplementedError self.modifyDateEdit() self.clearClassify() if index == 0: self.dayInfoModel.removeFilter() elif index == 1: self.calcModel.removeFilter() elif index == 2: self.calcModel2.removeFilter() def changeIds(self, ids): log.log('ChangeIds') log.log('self.ids:', self.ids) self.ids = ids curIndex = self.tabWidget.currentIndex() if curIndex == 0: self.dayInfoModel.removeFilter() self.on_queryBtn_clicked() elif curIndex == 1: self.calcModel.removeFilter() self.on_queryBtn_2_clicked() elif curIndex == 2: self.calcModel2.removeFilter() self.on_calculateBtn_clicked() @pyqtSignature("") def on_showGroupBtn_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet #raise NotImplementedError if self.classifyBtn.isChecked(): self.classifyBtn.setChecked(False) if self.showGroupBtn.isChecked(): self.listWidget.setVisible(True) else: self.listWidget.setVisible(False) @pyqtSignature("") def on_classifyBtn_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet #raise NotImplementedError if self.showGroupBtn.isChecked(): self.showGroupBtn.setChecked(False) self.on_showGroupBtn_clicked() if self.classifyMenu is None: self.classifyMenu = QMenu(self) subMenu = QMenu(self) subMenu.setTitle(u'地区板块') self.classifyMenuGroup = QActionGroup(self) for key in myGlobal.area2ids: if len(key) != 0: action = MyAction(key, myGlobal.area2ids[key], self.changeIds, self) subMenu.addAction(action) self.classifyMenuGroup.addAction(action) self.classifyMenu.addMenu(subMenu) subMenu = QMenu(self) subMenu.setTitle(u'行业板块') for key in myGlobal.industry2ids: if len(key) != 0: action = MyAction(key, myGlobal.industry2ids[key], self.changeIds, self) subMenu.addAction(action) self.classifyMenuGroup.addAction(action) self.classifyMenu.addMenu(subMenu) subMenu = MyMenu(u'向上版块', 'FLAG_UP', self, self.classifyMenuGroup) self.classifyMenu.addMenu(subMenu) subMenu = MyMenu(u'向下版块', 'FLAG_DOWN', self, self.classifyMenuGroup) self.classifyMenu.addMenu(subMenu) self.classifyBtn.setChecked(True) pos = QPoint() pos.setX(0) pos.setY(-self.classifyMenu.sizeHint().height()) self.classifyMenu.exec_(self.classifyBtn.mapToGlobal(pos)) def clearClassify(self): if self.classifyMenuGroup is not None: checkedAction = self.classifyMenuGroup.checkedAction() if checkedAction is not None: checkedAction.setChecked(False) @pyqtSignature("QString") def on_cmpMethCombo_currentIndexChanged(self, p0): """ Slot documentation goes here. """ # TODO: not implemented yet #raise NotImplementedError if str(p0.toUtf8()).decode('utf-8') == u'两个时间内涨幅,振幅数据分段': self.cmpTypeCombo.setEnabled(False) else: self.cmpTypeCombo.setEnabled(True) @pyqtSignature("QListWidgetItem*") def on_listWidget_itemClicked(self, item): """ Slot documentation goes here. """ # TODO: not implemented yet self.selectedGroup = self.getTextFromItem( self.listWidget.selectedItems()) if self.selectedGroup not in self.groups: QMessageBox.warning(self, 'warning', u'未知分组') return self.ids = self.groups[self.selectedGroup] # log.log('self.ids:', self.ids) self.clearClassify() #raise NotImplementedError def detailClassifyDate(self, arg): clz, switchType, subType = arg.split('_') if switchType != '5': return subTypeL = subType.split('.') subType = subTypeL[0] year = int(subTypeL[1]) self.startDate, self.endDate = { '0': (QDate(year, 1, 1), QDate(year, 3, 31)), #u'第 1 季度' '1': (QDate(year, 4, 1), QDate(year, 6, 30)), #u'第 2 季度' '2': (QDate(year, 7, 1), QDate(year, 9, 30)), #u'第 3 季度' '3': (QDate(year, 10, 1), QDate(year, 12, 31)), #u'第 4 季度' '4': (QDate(year, 1, 1), QDate(year, 2, 1).addDays(-1)), #u'1 月' '5': (QDate(year, 2, 1), QDate(year, 3, 1).addDays(-1)), #u'2 月' '6': (QDate(year, 3, 1), QDate(year, 4, 1).addDays(-1)), #u'3 月' '7': (QDate(year, 4, 1), QDate(year, 5, 1).addDays(-1)), #u'4 月' '8': (QDate(year, 5, 1), QDate(year, 6, 1).addDays(-1)), #u'5 月' '9': (QDate(year, 6, 1), QDate(year, 7, 1).addDays(-1)), #u'6 月' '10': (QDate(year, 7, 1), QDate(year, 8, 1).addDays(-1)), #u'7 月' '11': (QDate(year, 8, 1), QDate(year, 9, 1).addDays(-1)), #u'8 月' '12': (QDate(year, 9, 1), QDate(year, 10, 1).addDays(-1)), #u'9 月' '13': (QDate(year, 10, 1), QDate(year, 11, 1).addDays(-1)), #u'10 月' '14': (QDate(year, 11, 1), QDate(year, 12, 1).addDays(-1)), #u'11 月' '14': (QDate(year, 12, 1), QDate(year, 12, 31)), #u'12 月' }[subType] self.modifyDateEdit() def detailClassify(self, type, arg): curIndex = self.tabWidget.currentIndex() self.detailClassifyDate(arg) if curIndex == 0: self.dayInfoModel.setFilter(type + '__' + arg) self.on_queryBtn_clicked() elif curIndex == 1: self.calcModel.setFilter(type + '__' + arg) self.on_queryBtn_2_clicked() elif curIndex == 2: self.calcModel2.setFilter(type + '__' + arg) self.on_calculateBtn_clicked() @pyqtSignature("") def on_crossBtn_clicked(self): """ Slot documentation goes here. """ # TODO: not implemented yet # raise NotImplementedError startD = self.startDateEdit.date().toPyDate() endD = self.endDateEdit.date().toPyDate() self.startDate = self.startDateEdit.date() self.endDate = self.endDateEdit.date() if not self.testDate(startD, endD): return #response=json&page=2&pagesize=20&stockid=000001,000002,000003,000004,000005&starttime=2008-09-24&sortname=turnover_ratio #optname=avg_price,growth_ratio,current_price&opt=-&starttime=2008-03-25&endtime=2008-03-28&page=4&pagesize=20 optname = self.crossTypeNames[str( self.crossTypeCombo.currentText().toUtf8()).decode( 'utf-8')] #self.cmpTypeCombo.currentText().toInt()[0] weight = str(self.weightEdit.text().toUtf8()) try: weight = float(weight) except: QMessageBox.warning(self, 'warning', u'权重输入有误') return args = { 'starttime': startD, 'endtime': endD, 'optname': optname, 'weight': weight } log.log(args) self.crossModel.setRestArgs(args) self.crossTableWidget.init(self.crossModel, parent=self)
class XViewProfileToolBar(XToolBar): profileCreated = qt.Signal(qt.PyObject) profileChanged = qt.Signal(qt.PyObject) profileRemoved = qt.Signal(qt.PyObject) currentProfileChanged = qt.Signal(qt.PyObject) def __init__( self, parent ): super(XViewProfileToolBar, self).__init__(parent) # create custom properties self._editingEnabled = True self._viewWidget = None self._profileGroup = QActionGroup(self) # set the default options self.setIconSize(QSize(48, 48)) self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.setContextMenuPolicy(Qt.CustomContextMenu) # create connections self.actionTriggered.connect(self.handleActionTrigger) self.customContextMenuRequested.connect(self.showProfileMenu) def addProfile(self, profile): """ Adds the inputed profile as an action to the toolbar. :param profile | <projexui.widgets.xviewwidget.XViewProfile> """ act = XViewProfileAction(profile, self) self._profileGroup.addAction(act) self.addAction(act) return act def currentProfile( self ): """ Returns the current profile for this toolbar. :return <projexui.widgets.xviewwidget.XViewProfile> || None """ act = self._profileGroup.checkedAction() if ( act ): return act.profile() return None def createProfile( self, profile = None, clearLayout = True ): """ Prompts the user to create a new profile. """ if ( profile ): prof = profile elif ( not self.viewWidget() or clearLayout ): prof = XViewProfile() else: prof = self.viewWidget().saveProfile() blocked = self.signalsBlocked() self.blockSignals(False) changed = self.editProfile(prof) self.blockSignals(blocked) if ( not changed ): return act = self.addProfile(prof) act.setChecked(True) # update the interface if ( self.viewWidget() and (profile or clearLayout) ): self.viewWidget().restoreProfile(prof) if ( not self.signalsBlocked() ): self.profileCreated.emit(prof) @qt.Slot(qt.PyObject) def editProfile( self, profile ): """ Prompts the user to edit the given profile. :param profile | <projexui.widgets.xviewwidget.XViewProfile> """ mod = XViewProfileDialog.edit(self, profile) if ( not mod ): return False # update the action interface for act in self._profileGroup.actions(): if ( act.profile() == profile ): act.setProfile(profile) break # signal the change if ( not self.signalsBlocked() ): self.profileChanged.emit(profile) return True def exportProfiles( self, filename = None ): """ Exports this toolbar to the given filename. :param filename | <str> || None """ if ( not filename ): filename = QFileDialog.getSaveFileName(self, 'Export Toolbar', '', 'Toolbar Files (*.xtool)') if ( not filename ): return False profile_xml = self.toXml() projex.text.xmlindent(profile_xml) profile_string = ElementTree.tostring(profile_xml) f = open(str(filename), 'w') f.write(profile_string) f.close() return True def handleActionTrigger(self, action): """ Handles when an action has been triggered. If the inputed action is a XViewProfileAction, then the currentProfileChanged signal will emit. :param action | <QAction> """ # trigger a particular profile if ( isinstance(action, XViewProfileAction) ): if ( not self.signalsBlocked() ): self.currentProfileChanged.emit(action.profile()) if ( self._viewWidget ): self._viewWidget.restoreProfile(action.profile()) def importProfiles( self, filename = None ): """ Imports the profiles from the given filename. :param filename | <str> || None """ if ( not filename ): filename = QFileDialog.getOpenFileName( self, 'Import Toolbar', '', 'Toolbar Files (*.xtool)') if type(filename) == tuple: filename = str(filename[0]) if ( not (filename and os.path.exists(filename)) ): return False f = open(str(filename), 'r') profile_string = f.read() f.close() self.loadString(profile_string) # load the default toolbar action = self._profileGroup.checkedAction() if ( action ): self.handleActionTrigger(action) def isEditingEnabled( self ): """ Sets whether or not the create is enabled for this toolbar. :return <bool> """ return self._editingEnabled def isEmpty( self ): """ Returns whether or not this toolbar is empty. :return <bool> """ return len(self._profileGroup.actions()) == 0 def loadString( self, profilestr ): """ Loads the information for this toolbar from the inputed string. :param profilestr | <str> """ try: xtoolbar = ElementTree.fromstring(str(profilestr)) except ExpatError, e: return self.clear() curr = xtoolbar.get('current') for xprofile in xtoolbar: prof = XViewProfile.fromXml(xprofile) act = self.addProfile(prof) if ( prof.name() == curr ): act.setChecked(True)