class LEDDialog(QDialog): rowwidth = 20 def __init__(self, strip, parent=None): super(LEDDialog,self).__init__(parent) self.strip = strip self.buttons = [] layout = QVBoxLayout() btnlayout = QGridLayout() self.btnmapper = QSignalMapper() for i in xrange(int(self.strip.config['nleds'])): p = QPushButton() p.setFixedWidth(40) p.setFlat(True) p.setAutoFillBackground(True) self.btnmapper.setMapping( p, i) p.clicked.connect( self.btnmapper.map) self.buttons += [[p,QColor()]] btnlayout.addWidget(p, i/self.rowwidth, i%self.rowwidth) self.btnmapper.mapped['int'].connect(self.chooseColor) layout.addLayout(btnlayout) ctrllayout = QHBoxLayout() p = QPushButton("Refresh") p.clicked.connect(self.refresh) ctrllayout.addWidget(p) p = QPushButton("Set") p.clicked.connect(self.set) ctrllayout.addWidget(p) p = QPushButton("Close") p.clicked.connect(self.close) ctrllayout.addWidget(p) layout.addLayout(ctrllayout) self.setLayout( layout) self.refresh() def refresh(self): tmp = self.strip.state() for i in xrange(len(self.buttons)): c = QColor(int(tmp[i*3+0]),int(tmp[i*3+1]),int(tmp[i*3+2])) pal = self.buttons[i][0].palette() pal.setBrush( QPalette.Button, QColor(c.red(),c.green(),c.blue())) self.buttons[i][0].setPalette(pal) self.buttons[i][1] = c def set(self): leds = [] for b,c in self.buttons: leds += [ c.red(), c.green(), c.blue() ] self.strip.setState(leds) time.sleep(0.1) self.refresh() def chooseColor(self, i): if not i < len(self.buttons): return initial = self.buttons[i][1] c = QColorDialog.getColor(initial,self) if initial == c: return pal = self.buttons[i][0].palette() pal.setBrush( QPalette.Button, QColor(c.red(),c.green(),c.blue())) self.buttons[i][0].setPalette(pal) self.buttons[i][1] = c
def contextMenuEvent(self, event): menu = QtGui.QMenu() last_pos = event.pos() cursor = self.cursorForPosition(last_pos) pos = cursor.positionInBlock() line = cursor.blockNumber() keywords = self.words_at_pos(line, pos) if len(keywords) > 0: keyword_mapper = QSignalMapper(self) actions = [] for keyword in keywords: action_text = "Copy \"%s\"" % keyword[0].meaning actions.append(QtGui.QAction(action_text, None)) # We can only send strings with the signal mapper, so pickle our data. data = pickle.dumps(keyword[0]) data = QtCore.QString.fromAscii(data) self.connect(actions[-1], QtCore.SIGNAL("triggered()"), keyword_mapper, QtCore.SLOT("map()")) keyword_mapper.setMapping(actions[-1], data) self.connect(keyword_mapper, QtCore.SIGNAL("mapped(QString)"), self.copy_keyword) menu.addActions(actions) menu.addSeparator() default_menu = self.createStandardContextMenu() menu.addActions(default_menu.actions()) menu.exec_(event.globalPos())
class PeriodicInstrumentController(NonBlockingInstrumentController): def __init__(self, instrument_config): NonBlockingInstrumentController.__init__(self, instrument_config) self.mapper = QSignalMapper() self.timers = [] def run(self): # create and start periodic timers for each command for i, command in enumerate(self.instr_cfg.operation_commands): self.log.debug("Creating timer for '{0}' command".format( command.name)) timer = QTimer() timer.timeout.connect(self.mapper.map) self.mapper.setMapping(timer, i) timer.start(command.param) self.timers.append(timer) self.mapper.mapped.connect(self.send_command) NonBlockingInstrumentController.run(self) def quit(self): for timer in self.timers: timer.stop() NonBlockingInstrumentController.quit(self) def send_command(self, num): self.new_command.emit(self.instr_cfg.operation_commands[num])
class ProviderToolBar(QToolBar): ''' Widget to display the vehicles/objects status and position ''' triggered = pyqtSignal(str) def __init__(self, parent=None): super(ProviderToolBar, self).__init__(parent) self.signalMapper = QSignalMapper(self) self.setMovable(True) self.setFloatable(True) self.upToDate = False self.actions = [] self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.signalMapper.mapped[str].connect(self.triggered) def createAction(self, provider): icon = QIcon(':/plugins/PosiView/ledgreen.png') icon.addFile(':/plugins/PosiView/ledgrey.png', QSize(), QIcon.Disabled, QIcon.Off) action = QAction(icon, provider.name, None) button = QToolButton() button.setDefaultAction(action) action.setEnabled(False) provider.deviceConnected.connect(action.setEnabled) provider.deviceDisconnected.connect(action.setDisabled) self.signalMapper.setMapping(action, provider.name) action.triggered.connect(self.signalMapper.map) self.addAction(action) self.actions.append(action)
class ProviderToolBar(QToolBar): ''' Widget to display the vehicles/objects status and position ''' triggered = pyqtSignal(str) def __init__(self, parent=None): super(ProviderToolBar, self).__init__(parent) self.signalMapper = QSignalMapper(self) self.setMovable(True) self.setFloatable(True) self.upToDate = False self.actions = [] self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.signalMapper.mapped[str].connect(self.triggered) def createAction(self, provider): icon = QIcon(':/plugins/PosiView/ledgreen.png') icon.addFile(':/plugins/PosiView/ledgrey.png', QSize(), QIcon.Disabled, QIcon.Off) action = QAction(icon, provider.name, None) button = QToolButton() button.setDefaultAction(action) action.setEnabled(False) provider.deviceConnected.connect(action.setEnabled) provider.deviceDisconnected.connect(action.setDisabled) self.signalMapper.setMapping(action, provider.name) action.triggered.connect(self.signalMapper.map) self.addAction(action) self.actions.append(action)
def __init__(self, parent, tester): """Construct a new dockwindow following the tester """ self.tester = tester QtGui.QDockWidget.__init__(self, tester.testname, parent) self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) if not self.widget(): self.setWidget(QtGui.QWidget(self)) vl = QtGui.QVBoxLayout(self.widget()) #self.widget().setLayout(vl) panel = QtGui.QFrame(self) vl.addWidget(panel) self.login = QtGui.QLineEdit(self) self.login.textEdited.connect(self.check_logpass) self.password = QtGui.QLineEdit(self) self.password.setEchoMode(QtGui.QLineEdit.Password) self.password.textEdited.connect(self.check_logpass) self.tests = [] fl = QtGui.QFormLayout(panel) fl.addRow('&Login:'******'&Password:',self.password) panel.setLayout(fl) panel = QtGui.QFrame(self) panel.setFrameShadow(QtGui.QFrame.Sunken) panel.setFrameShape(QtGui.QFrame.Panel) vl.addWidget(panel) vl2 = QtGui.QVBoxLayout(panel) signalmapper = QSignalMapper(self) signalmapper.mapped[int].connect(self.test) for i,test in enumerate(self.tester.tests): btn = QtGui.QPushButton("Test {}: {}".format(i+1,test.name),panel) btn.setStyleSheet(self.btn_default_stylesheet) btn.setEnabled(False) vl2.addWidget(btn) self.tests.append(btn) signalmapper.setMapping(btn,i) btn.clicked.connect(signalmapper.map) panel.setLayout(vl2) self.text = QtGui.QLabel("Enter your Coursera login and assignments password and push one of the test buttons above to run the test and submit the results to Coursera.") self.text.setWordWrap(True) vl.addWidget(self.text) self.text.setFrameShadow(QtGui.QFrame.Sunken) self.text.setFrameShape(QtGui.QFrame.Panel) self.text.setMargin(5) #vl.setStretch(2,1) vl.addStretch(1)
def setupUi(self, nx, ny, keys): mapper = QSignalMapper() layout = QGridLayout(self) for ix in range(nx): for iy in range(ny): key = keys[iy * nx + ix] keyPb = QPushButton(self) keyPb.setFixedHeight(40) keyPb.setText(key[0]) keyPb.clicked.connect(mapper.map) layout.addWidget(keyPb, iy, ix) mapper.setMapping(keyPb, key[1]) mapper.mapped.connect(self.keyClicked) self.mapper = mapper
def setup_file_locs(self): # Because the default margins are ugly as h*ck. self.ui.tabLocs.layout().setContentsMargins(0, 0, 0, 0) # Map our buttons to functions that retrieve the necessary data. cfg_mapper = QSignalMapper(self) for i, item in enumerate(FILE_LOCATIONS): self.connect(self.ui.__dict__[item[BTN]], QtCore.SIGNAL("clicked()"), cfg_mapper, QtCore.SLOT("map()")) cfg_mapper.setMapping(self.ui.__dict__[item[BTN]], i) self.connect(cfg_mapper, QtCore.SIGNAL("mapped(int)"), self.__get_cfg_item) # Load in all our info from the config file. for item in FILE_LOCATIONS: self.ui.__dict__[item[TEXT]].setText(common.editor_config.get_pref(item[CFG]))
class SelectMapper: def setupSelectMapper(self, selectMapper, selectAllButton=None): """selectMapper should include keys 'button'(QPushButton) and 'widget'(QWidget) """ self._select = None self.selectMapper = selectMapper self.selectAllButton = selectAllButton self.signalMapper = QSignalMapper(self) for key, value in self.selectMapper.iteritems(): # config signal mapper value['button'].clicked.connect(self.signalMapper.map) self.signalMapper.setMapping(value['button'], key) # connect signal mapper to slot 'select' QObject.connect(self.signalMapper, SIGNAL("mapped(QString)"), self.select) if selectAllButton: selectAllButton.clicked.connect(self.selectAll) def select(self, select): select = unicode(select) self.selectMapper[select]['button'].setChecked(True) if self._select == select: return if self._select: self.selectMapper[self._select]['button'].setChecked(False) self.selectMapper[self._select]['widget'].hide() else: if self.selectAllButton: self.selectAllButton.setChecked(False) for key, value in self.selectMapper.iteritems(): value['widget'].hide() self.selectMapper[select]['widget'].show() self._select = select def selectAll(self): self.selectAllButton.setChecked(True) if self._select == None: return for key, value in self.selectMapper.iteritems(): value['button'].setChecked(False) value['widget'].show() self._select = None
def __init__(self, items, colCount, parent=None): """ Create Grid and setup QSignalMapper. Connect each item's 'clicked' signal and use the sequence index as map value which will be passed as argument when the Grid's clicked signal is emitted. items: sequence with widgets having a 'void clicked()' signal colCount: column count for each row parent: parent widget, default None """ super(Grid, self).__init__(parent) # Create a grid layout. layout = QGridLayout() self.setLayout(layout) # Create the signal mapper. signalMapper = QSignalMapper(self) for cnt, item in enumerate(items): # Setup mapping for the item. In this case, the # mapping is the sequence index of the item. signalMapper.setMapping(item, cnt) # Connect the item's 'clicked' signal to the signal # mapper's 'map' slot. # The 'map' slot will emit the 'mapped' signal # when invoked and the mapping set previously will be # passed as an argument to the slot/signal that is # connected to the signal mapper's 'mapped' signal. item.clicked.connect(signalMapper.map) # Add the widget to the grid layout layout.addWidget(item, cnt / colCount, cnt % colCount) # Forward the signal mapper's 'mapped' signal via the Grid's # 'clicked' signal. This will handle all widgets' 'clicked' # ssignals. signalMapper.mapped.connect(self.clicked)
def setup_file_locs(self): # Because the default margins are ugly as h*ck. self.ui.tabLocs.layout().setContentsMargins(0, 0, 0, 0) # Map our buttons to functions that retrieve the necessary data. cfg_mapper = QSignalMapper(self) for i, item in enumerate(FILE_LOCATIONS): self.connect(self.ui.__dict__[item[BTN]], QtCore.SIGNAL("clicked()"), cfg_mapper, QtCore.SLOT("map()")) cfg_mapper.setMapping(self.ui.__dict__[item[BTN]], i) self.connect(cfg_mapper, QtCore.SIGNAL("mapped(int)"), self.__get_cfg_item) # Load in all our info from the config file. for item in FILE_LOCATIONS: self.ui.__dict__[item[TEXT]].setText( common.editor_config.get_pref(item[CFG]))
def contextMenuEvent(self, event): menu = QtGui.QMenu() last_pos = event.pos() cursor = self.cursorForPosition(last_pos) pos = cursor.positionInBlock() line = cursor.blockNumber() keywords = self.words_at_pos(line, pos) if len(keywords) > 0: keyword_mapper = QSignalMapper(self) actions = [] for keyword in keywords: action_text = "Copy \"%s\"" % keyword[0].meaning actions.append(QtGui.QAction(action_text, None)) # We can only send strings with the signal mapper, so pickle our data. data = pickle.dumps(keyword[0]) data = QtCore.QString.fromAscii(data) self.connect(actions[-1], QtCore.SIGNAL("triggered()"), keyword_mapper, QtCore.SLOT("map()")) keyword_mapper.setMapping(actions[-1], data) self.connect(keyword_mapper, QtCore.SIGNAL("mapped(QString)"), self.copy_keyword) menu.addActions(actions) menu.addSeparator() default_menu = self.createStandardContextMenu() menu.addActions(default_menu.actions()) menu.exec_(event.globalPos())
class StackedWidget(QStackedWidget): """A stacked widget.""" def __init__( self, parentWidget, dockWidgetName, iface, pluginDir): """Constructor. Args: parentWidget (QToolBar): A reference to the parent widget. dockWidgetName (str): A name of the dock widget. iface (QgisInterface): A reference to the QgisInterface. pluginDir (QDir): A plugin directory. """ self.dW = parentWidget self.dWName = dockWidgetName self.iface = iface self.pluginDir = pluginDir super(StackedWidget, self).__init__(self.dW) self._setup_self() def _setup_self(self): """Sets up self.""" self.setObjectName(u'stackedWidget') self.openTabSignalMapper = QSignalMapper(self) self._build_widgets() def _build_widgets(self): """Builds own widgets.""" self.loadVfkPuWidget = loadvfk_puwidget.LoadVfkPuWidget( self, self.dWName, self.iface, self.dW, self.pluginDir) self.dW.toolBar.loadVfkAction.triggered.connect( self.openTabSignalMapper.map) self.openTabSignalMapper.setMapping(self.dW.toolBar.loadVfkAction, 0) self.addWidget(self.loadVfkPuWidget) self.editPuWidget = edit_puwidget.EditPuWidget( self, self.dWName, self.iface, self.dW, self.pluginDir) self.dW.toolBar.editAction.triggered.connect( self.openTabSignalMapper.map) self.openTabSignalMapper.setMapping(self.dW.toolBar.editAction, 1) self.addWidget(self.editPuWidget) self.checkAnalysisPuWidget = \ checkanalysis_puwidget.CheckAnalysisPuWidget( self, self.dWName, self.iface, self.dW, self.pluginDir) self.dW.toolBar.checkAnalysisAction.triggered.connect( self.openTabSignalMapper.map) self.openTabSignalMapper.setMapping( self.dW.toolBar.checkAnalysisAction, 2) self.addWidget(self.checkAnalysisPuWidget) self.openTabSignalMapper.mapped.connect(self.setCurrentIndex) self.currentChanged.connect( self.dW.statusBar.change_text)
class ImportData(QWizard, Ui_frmImport): def __init__(self,parent=None): QWizard.__init__(self,parent) self.setupUi(self) self.curr_profile = current_profile() #Connect signals self.btnBrowseSource.clicked.connect(self.setSourceFile) self.lstDestTables.itemClicked.connect(self.destSelectChanged) self.btnSrcUp.clicked.connect(self.srcItemUp) self.btnSrcDown.clicked.connect(self.srcItemDown) self.btnSrcAll.clicked.connect(self.checkSrcItems) self.btnSrcNone.clicked.connect(self.uncheckSrcItems) self.btnDestUp.clicked.connect(self.targetItemUp) self.btnDestDown.clicked.connect(self.targetItemDown) self.lstSrcFields.currentRowChanged[int].connect(self.sourceRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self.destRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self._enable_disable_trans_tools) self.chk_virtual.toggled.connect(self._on_load_virtual_columns) #Data Reader self.dataReader = None #Init self.registerFields() #Geometry columns self.geomcols = [] #Initialize value translators from definitions self._init_translators() #self._set_target_fields_stylesheet() def _init_translators(self): translator_menu = QMenu(self) self._trans_widget_mgr = TranslatorWidgetManager(self) self._trans_signal_mapper = QSignalMapper(self) for trans_name, config in ValueTranslatorConfig.translators.iteritems(): trans_action = QAction( u'{}...'.format(trans_name), translator_menu ) self._trans_signal_mapper.setMapping(trans_action, trans_name) trans_action.triggered.connect(self._trans_signal_mapper.map) translator_menu.addAction(trans_action) if len(translator_menu.actions()) == 0: self.btn_add_translator.setEnabled(False) else: self.btn_add_translator.setMenu(translator_menu) self._trans_signal_mapper.mapped[str].connect(self._load_translator_dialog) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) self.btn_edit_translator.clicked.connect(self._on_edit_translator) self.btn_delete_translator.clicked.connect(self._on_delete_translator) def _load_translator_dialog(self, config_key): """ Load translator dialog. """ dest_column = self._selected_destination_column() src_column = self._selected_source_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: trans_config = ValueTranslatorConfig.translators.get(config_key, None) #Safety precaution if trans_config is None: return try: trans_dlg = trans_config.create( self, self._source_columns(), self.targetTab, dest_column, src_column ) except RuntimeError as re: QMessageBox.critical( self, QApplication.translate( 'ImportData', 'Value Translator' ), unicode(re) ) return self._handle_translator_dlg(dest_column, trans_dlg) def _handle_translator_dlg(self, key, dlg): if dlg.exec_() == QDialog.Accepted: self._trans_widget_mgr.add_widget(key, dlg) self._enable_disable_trans_tools() def _on_edit_translator(self): """ Slot to load the translator widget specific for the selected column for editing. """ dest_column = self._selected_destination_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) self._handle_translator_dlg(dest_column, trans_dlg) def _on_delete_translator(self): """ Slot for deleting the translator widget for the selected column. """ dest_column = self._selected_destination_column() self._delete_translator(dest_column) def _delete_translator(self, destination_column): if not destination_column: return res = self._trans_widget_mgr.remove_translator_widget(destination_column) self._enable_disable_trans_tools() def _enable_disable_trans_tools(self, index=-1): """ Enable/disable appropriate value translator tools based on the selected column. """ dest_column = self._selected_destination_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: self.btn_add_translator.setEnabled(True) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(True) self.btn_delete_translator.setEnabled(True) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) def _selected_destination_column(self): dest_field_item = self.lstTargetFields.currentItem() if dest_field_item is None: return "" else: return dest_field_item.text() def _selected_source_column(self): src_field_item = self.lstSrcFields.currentItem() if src_field_item is None: return "" else: return src_field_item.text() def _set_target_fields_stylesheet(self): self.lstTargetFields.setStyleSheet("QListWidget#lstTargetFields::item:selected" " { selection-background-color: darkblue }") def registerFields(self): #Register wizard fields pgSource = self.page(0) pgSource.registerField("srcFile*",self.txtDataSource) pgSource.registerField("typeText",self.rbTextType) pgSource.registerField("typeSpatial",self.rbSpType) #Destination table configuration destConf = self.page(1) destConf.registerField("optAppend",self.rbAppend) destConf.registerField("optOverwrite",self.rbOverwrite) destConf.registerField("tabIndex*",self.lstDestTables) destConf.registerField("geomCol",self.geomClm,"currentText",SIGNAL("currentIndexChanged(int)")) def initializePage(self,pageid): #Re-implementation of wizard page initialization if pageid == 1: #Reference to checked listwidget item representing table name self.destCheckedItem=None self.geomClm.clear() if self.field("typeText"): self.loadTables("textual") self.geomClm.setEnabled(False) elif self.field("typeSpatial"): self.loadTables("spatial") self.geomClm.setEnabled(True) if pageid == 2: self.lstSrcFields.clear() self.lstTargetFields.clear() self.assignCols() self._enable_disable_trans_tools() def _source_columns(self): return self.dataReader.getFields() def assignCols(self): #Load source and target columns respectively srcCols = self._source_columns() for c in srcCols: srcItem = QListWidgetItem(c,self.lstSrcFields) srcItem.setCheckState(Qt.Unchecked) srcItem.setIcon(QIcon(":/plugins/stdm/images/icons/column.png")) self.lstSrcFields.addItem(srcItem) #Destination Columns tabIndex = int(self.field("tabIndex")) self.targetTab = self.destCheckedItem.text() targetCols = table_column_names(self.targetTab, False, True) #Remove geometry columns in the target columns list for gc in self.geomcols: colIndex = getIndex(targetCols,gc) if colIndex != -1: targetCols.remove(gc) #Remove 'id' column if there id_idx = getIndex(targetCols, 'id') if id_idx != -1: targetCols.remove('id') self._add_target_table_columns(targetCols) def _add_target_table_columns(self, items, style=False): for item in items: list_item = QListWidgetItem(item) if style: color = QColor(0, 128, 255) list_item.setTextColor(color) self.lstTargetFields.addItem(list_item) def _on_load_virtual_columns(self, state): """ Load/unload relationships in the list of destination table columns. """ virtual_columns = self.dataReader.entity_virtual_columns(self.targetTab) if state: if len(virtual_columns) == 0: msg = QApplication.translate("ImportData", "There are no virtual columns for the specified table.") QMessageBox.warning( self, QApplication.translate( 'ImportData', 'Import Data' ), msg ) self.chk_virtual.setChecked(False) return self._add_target_table_columns(virtual_columns, True) else: self._remove_destination_table_fields(virtual_columns) def _remove_destination_table_fields(self, fields): """Remove the specified columns from the destination view.""" for f in fields: list_items = self.lstTargetFields.findItems(f, Qt.MatchFixedString) if len(list_items) > 0: list_item = list_items[0] row = self.lstTargetFields.row(list_item) rem_item = self.lstTargetFields.takeItem(row) del rem_item #Delete translator if already defined for the given column self._delete_translator(f) def loadGeomCols(self, table): #Load geometry columns based on the selected table self.geomcols = table_column_names(table, True, True) self.geomClm.clear() self.geomClm.addItems(self.geomcols) def loadTables(self, type): #Load textual or spatial tables self.lstDestTables.clear() tables = None if type == "textual": tables = profile_user_tables(self.curr_profile, False, True) elif type == "spatial": tables = profile_spatial_tables(self.curr_profile) if tables is not None: for t in tables: tabItem = QListWidgetItem(t,self.lstDestTables) tabItem.setCheckState(Qt.Unchecked) tabItem.setIcon(QIcon(":/plugins/stdm/images/icons/table.png")) self.lstDestTables.addItem(tabItem) def validateCurrentPage(self): #Validate the current page before proceeding to the next one validPage=True if not QFile.exists(unicode(self.field("srcFile"))): self.ErrorInfoMessage("The specified source file does not exist.") validPage = False else: if self.dataReader: self.dataReader.reset() self.dataReader = OGRReader(unicode(self.field("srcFile"))) if not self.dataReader.isValid(): self.ErrorInfoMessage("The source file could not be opened." "\nPlease check is the given file type " "is supported") validPage = False if self.currentId()==1: if self.destCheckedItem == None: self.ErrorInfoMessage("Please select the destination table.") validPage = False if self.currentId()==2: validPage = self.execImport() return validPage def setSourceFile(self): #Set the file path to the source file imageFilters = "Comma Separated Value (*.csv);;ESRI Shapefile (*.shp);;AutoCAD DXF (*.dxf)" sourceFile = QFileDialog.getOpenFileName(self,"Select Source File",vectorFileDir(),imageFilters) if sourceFile != "": self.txtDataSource.setText(sourceFile) def getSrcDestPairs(self): #Return the matched source and destination columns srcDest = {} for l in range(self.lstTargetFields.count()): if l < self.lstSrcFields.count(): srcItem = self.lstSrcFields.item(l) if srcItem.checkState() == Qt.Checked: destItem = self.lstTargetFields.item(l) srcDest[srcItem.text()] = destItem.text() return srcDest def execImport(self): #Initiate the import process success = False matchCols = self.getSrcDestPairs() #Specify geometry column geom_column=None if self.field("typeSpatial"): geom_column = self.field("geomCol") # Ensure that user has selected at least one column if it is a # non-spatial table if len(matchCols) == 0: self.ErrorInfoMessage("Please select at least one source column.") return success value_translator_manager = self._trans_widget_mgr.translator_manager() # try: if self.field("optOverwrite"): entity = self.curr_profile.entity_by_name(self.targetTab) dependencies = entity.dependencies() view_dep = dependencies['views'] entity_dep = [e.name for e in entity.children()] entities_dep_str = ', '.join(entity_dep) views_dep_str = ', '.join(view_dep) if len(entity_dep) > 0 or len(view_dep) > 0: del_msg = QApplication.translate( 'ImportData', "Overwriting existing records will permanently \n" "remove records from other tables linked to the \n" "records. The following tables will be affected." "\n{}\n{}" "\nClick Yes to proceed importing or No to cancel.". format(entities_dep_str, views_dep_str) ) del_result = QMessageBox.critical( self, QApplication.translate( "ImportData", "Overwrite Import Data Warning" ), del_msg, QMessageBox.Yes | QMessageBox.No ) if del_result == QMessageBox.Yes: self.dataReader.featToDb( self.targetTab, matchCols, False, self, geom_column, translator_manager=value_translator_manager ) # Update directory info in the registry setVectorFileDir(self.field("srcFile")) self.InfoMessage( "All features have been imported successfully!" ) success = True else: success = False else: self.dataReader.featToDb( self.targetTab, matchCols, True, self, geom_column, translator_manager=value_translator_manager ) self.InfoMessage( "All features have been imported successfully!" ) #Update directory info in the registry setVectorFileDir(self.field("srcFile")) success = True # except: # self.ErrorInfoMessage(unicode(sys.exc_info()[1])) return success def _clear_dest_table_selections(self, exclude=None): #Clears checked items in destination table list view if exclude is None: exclude = [] for i in range(self.lstDestTables.count()): item = self.lstDestTables.item(i) if item.checkState() == Qt.Checked and not item.text() in exclude: item.setCheckState(Qt.Unchecked) def destSelectChanged(self, item): """ Handler when a list widget item is clicked, clears previous selections """ if not self.destCheckedItem is None: if item.checkState() == Qt.Checked: self.destCheckedItem.setCheckState(Qt.Unchecked) else: self.destCheckedItem = None if item.checkState() == Qt.Checked: self.destCheckedItem = item #Ensure other selected items have been cleared self._clear_dest_table_selections(exclude=[item.text()]) #Load geometry columns if selection is a spatial table if self.field("typeSpatial"): self.loadGeomCols(item.text()) def syncRowSelection(self, srcList, destList): """ Sync the selection of an srcList item to the corresponding one in the destination column list. """ if (srcList.currentRow() + 1) <= destList.count(): destList.setCurrentRow(srcList.currentRow()) def sourceRowChanged(self): #Slot when the source list's current row changes self.syncRowSelection(self.lstSrcFields,self.lstTargetFields) def destRowChanged(self): #Slot when the destination list's current row changes self.syncRowSelection(self.lstTargetFields, self.lstSrcFields) def itemUp(self, listWidget): #Moves the selected item in the list widget one level up curIndex = listWidget.currentRow() curItem = listWidget.takeItem(curIndex) listWidget.insertItem(curIndex - 1, curItem) listWidget.setCurrentRow(curIndex - 1) def itemDown(self, listWidget): #Moves the selected item in the list widget one level down curIndex=listWidget.currentRow() curItem=listWidget.takeItem(curIndex) listWidget.insertItem(curIndex + 1,curItem) listWidget.setCurrentRow(curIndex + 1) def checkAllItems(self, listWidget, state): #Checks all items in the list widget for l in range(listWidget.count()): item=listWidget.item(l) if state: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) def checkSrcItems(self): #Slot for checking all source table columns self.checkAllItems(self.lstSrcFields, True) def uncheckSrcItems(self): #Slot for unchecking all source table columns self.checkAllItems(self.lstSrcFields, False) def srcItemUp(self): #Slot for moving source list item up self.itemUp(self.lstSrcFields) def srcItemDown(self): #Slot for moving source list item down self.itemDown(self.lstSrcFields) def targetItemUp(self): #Slot for moving target item up self.itemUp(self.lstTargetFields) def targetItemDown(self): #Slot for moving target item down self.itemDown(self.lstTargetFields) def keyPressEvent(self,e): """ Override method for preventing the dialog from closing itself when the escape key is hit """ if e.key() == Qt.Key_Escape: pass def InfoMessage(self, message): #Information message box msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(message) msg.exec_() def ErrorInfoMessage(self, message): #Error Message Box msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(message) msg.exec_()
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)
def main(icon_spec): app = QApplication(sys.argv) main_window = QMainWindow() def sigint_handler(*args): main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler signal_timer = QTimer() signal_timer.start(100) signal_timer.timeout.connect(lambda: None) tool_bar = QToolBar() main_window.addToolBar(Qt.TopToolBarArea, tool_bar) table_view = QTableView() table_view.setSelectionBehavior(QAbstractItemView.SelectRows) table_view.setSelectionMode(QAbstractItemView.SingleSelection) table_view.setSortingEnabled(True) main_window.setCentralWidget(table_view) proxy_model = QSortFilterProxyModel() proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive) proxy_model.setFilterKeyColumn(1) table_view.setModel(proxy_model) proxy_model.layoutChanged.connect(table_view.resizeRowsToContents) item_model = QStandardItemModel() proxy_model.setSourceModel(item_model) # get all icons and their available sizes QIcon.setThemeName("gnome") icons = [] all_sizes = set([]) for context, icon_names in icon_spec: for icon_name in icon_names: icon = QIcon.fromTheme(icon_name) sizes = [] for size in icon.availableSizes(): size = (size.width(), size.height()) sizes.append(size) all_sizes.add(size) sizes.sort() icons.append({ 'context': context, 'icon_name': icon_name, 'icon': icon, 'sizes': sizes, }) all_sizes = list(all_sizes) all_sizes.sort() # input field for filter def filter_changed(value): proxy_model.setFilterRegExp(value) table_view.resizeRowsToContents() filter_line_edit = QLineEdit() filter_line_edit.setMaximumWidth(200) filter_line_edit.setPlaceholderText('Filter name') filter_line_edit.setToolTip( 'Filter name optionally using regular expressions (' + QKeySequence(QKeySequence.Find).toString() + ')') filter_line_edit.textChanged.connect(filter_changed) tool_bar.addWidget(filter_line_edit) # actions to toggle visibility of available sizes/columns def action_toggled(index): column = 2 + index table_view.setColumnHidden(column, not table_view.isColumnHidden(column)) table_view.resizeColumnsToContents() table_view.resizeRowsToContents() signal_mapper = QSignalMapper() for i, size in enumerate(all_sizes): action = QAction('%dx%d' % size, tool_bar) action.setCheckable(True) action.setChecked(True) tool_bar.addAction(action) action.toggled.connect(signal_mapper.map) signal_mapper.setMapping(action, i) # set tool tip and handle key sequence tool_tip = 'Toggle visibility of column' if i < 10: digit = ('%d' % (i + 1))[-1] tool_tip += ' (%s)' % QKeySequence('Ctrl+%s' % digit).toString() action.setToolTip(tool_tip) signal_mapper.mapped.connect(action_toggled) # label columns header_labels = ['context', 'name'] for width, height in all_sizes: header_labels.append('%dx%d' % (width, height)) item_model.setColumnCount(len(header_labels)) item_model.setHorizontalHeaderLabels(header_labels) # fill rows item_model.setRowCount(len(icons)) for row, icon_data in enumerate(icons): # context item = QStandardItem(icon_data['context']) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, 0, item) # icon name item = QStandardItem(icon_data['icon_name']) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, 1, item) for index_in_all_sizes, size in enumerate(all_sizes): column = 2 + index_in_all_sizes if size in icon_data['sizes']: # icon as pixmap to keep specific size item = QStandardItem('') pixmap = icon_data['icon'].pixmap(size[0], size[1]) item.setData(pixmap, Qt.DecorationRole) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, column, item) else: # single space to be sortable against icons item = QStandardItem(' ') item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, column, item) table_view.resizeColumnsToContents() # manually set row heights because resizeRowsToContents is not working properly for row, icon_data in enumerate(icons): if len(icon_data['sizes']) > 0: max_size = icon_data['sizes'][-1] table_view.setRowHeight(row, max_size[1]) # enable focus find (ctrl+f) and toggle columns (ctrl+NUM) def main_window_keyPressEvent(self, event, old_keyPressEvent=QMainWindow.keyPressEvent): if event.matches(QKeySequence.Find): filter_line_edit.setFocus() return if event.modifiers() == Qt.ControlModifier and event.key( ) >= Qt.Key_0 and event.key() <= Qt.Key_9: index = event.key() - Qt.Key_1 if event.key() == Qt.Key_0: index += 10 action = signal_mapper.mapping(index) if action: action.toggle() return old_keyPressEvent(self, event) main_window.keyPressEvent = new.instancemethod(main_window_keyPressEvent, table_view, None) # enable copy (ctrl+c) name of icon to clipboard def table_view_keyPressEvent(self, event, old_keyPressEvent=QTableView.keyPressEvent): if event.matches(QKeySequence.Copy): selection_model = self.selectionModel() if selection_model.hasSelection(): index = selection_model.selectedRows()[0] source_index = self.model().mapToSource(index) item = self.model().sourceModel().item(source_index.row(), 1) icon_name = item.data(Qt.EditRole) app.clipboard().setText(icon_name.toString()) return old_keyPressEvent(self, event) table_view.keyPressEvent = new.instancemethod(table_view_keyPressEvent, table_view, None) print 'Icon Theme: ', QIcon.themeName() print 'Theme Search Paths:' for item in QIcon.themeSearchPaths(): print item main_window.showMaximized() return app.exec_()
class ImportData(QWizard, Ui_frmImport): def __init__(self,parent=None): QWizard.__init__(self,parent) self.setupUi(self) self.curr_profile = current_profile() #Connect signals self.btnBrowseSource.clicked.connect(self.setSourceFile) self.lstDestTables.itemClicked.connect(self.destSelectChanged) self.btnSrcUp.clicked.connect(self.srcItemUp) self.btnSrcDown.clicked.connect(self.srcItemDown) self.btnSrcAll.clicked.connect(self.checkSrcItems) self.btnSrcNone.clicked.connect(self.uncheckSrcItems) self.btnDestUp.clicked.connect(self.targetItemUp) self.btnDestDown.clicked.connect(self.targetItemDown) self.lstSrcFields.currentRowChanged[int].connect(self.sourceRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self.destRowChanged) self.lstTargetFields.currentRowChanged[int].connect(self._enable_disable_trans_tools) self.chk_virtual.toggled.connect(self._on_load_virtual_columns) #Data Reader self.dataReader = None #Init self.registerFields() #Geometry columns self.geomcols = [] #Initialize value translators from definitions self._init_translators() #self._set_target_fields_stylesheet() def _init_translators(self): translator_menu = QMenu(self) self._trans_widget_mgr = TranslatorWidgetManager(self) self._trans_signal_mapper = QSignalMapper(self) for trans_name, config in ValueTranslatorConfig.translators.iteritems(): trans_action = QAction( u'{}...'.format(trans_name), translator_menu ) self._trans_signal_mapper.setMapping(trans_action, trans_name) trans_action.triggered.connect(self._trans_signal_mapper.map) translator_menu.addAction(trans_action) if len(translator_menu.actions()) == 0: self.btn_add_translator.setEnabled(False) else: self.btn_add_translator.setMenu(translator_menu) self._trans_signal_mapper.mapped[str].connect(self._load_translator_dialog) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) self.btn_edit_translator.clicked.connect(self._on_edit_translator) self.btn_delete_translator.clicked.connect(self._on_delete_translator) def _load_translator_dialog(self, config_key): """ Load translator dialog. """ dest_column = self._selected_destination_column() src_column = self._selected_source_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: trans_config = ValueTranslatorConfig.translators.get(config_key, None) #Safety precaution if trans_config is None: return try: trans_dlg = trans_config.create( self, self._source_columns(), self.targetTab, dest_column, src_column ) except RuntimeError as re: QMessageBox.critical( self, QApplication.translate( 'ImportData', 'Value Translator' ), unicode(re) ) return self._handle_translator_dlg(dest_column, trans_dlg) def _handle_translator_dlg(self, key, dlg): if dlg.exec_() == QDialog.Accepted: self._trans_widget_mgr.add_widget(key, dlg) self._enable_disable_trans_tools() def _on_edit_translator(self): """ Slot to load the translator widget specific for the selected column for editing. """ dest_column = self._selected_destination_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) self._handle_translator_dlg(dest_column, trans_dlg) def _on_delete_translator(self): """ Slot for deleting the translator widget for the selected column. """ dest_column = self._selected_destination_column() self._delete_translator(dest_column) def _delete_translator(self, destination_column): if not destination_column: return res = self._trans_widget_mgr.remove_translator_widget(destination_column) self._enable_disable_trans_tools() def _enable_disable_trans_tools(self, index=-1): """ Enable/disable appropriate value translator tools based on the selected column. """ dest_column = self._selected_destination_column() if dest_column: #Check if there is an existing dialog in the manager trans_dlg = self._trans_widget_mgr.translator_widget(dest_column) if trans_dlg is None: self.btn_add_translator.setEnabled(True) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(True) self.btn_delete_translator.setEnabled(True) else: self.btn_add_translator.setEnabled(False) self.btn_edit_translator.setEnabled(False) self.btn_delete_translator.setEnabled(False) def _selected_destination_column(self): dest_field_item = self.lstTargetFields.currentItem() if dest_field_item is None: return "" else: return dest_field_item.text() def _selected_source_column(self): src_field_item = self.lstSrcFields.currentItem() if src_field_item is None: return "" else: return src_field_item.text() def _set_target_fields_stylesheet(self): self.lstTargetFields.setStyleSheet("QListWidget#lstTargetFields::item:selected" " { selection-background-color: darkblue }") def registerFields(self): #Register wizard fields pgSource = self.page(0) pgSource.registerField("srcFile*",self.txtDataSource) pgSource.registerField("typeText",self.rbTextType) pgSource.registerField("typeSpatial",self.rbSpType) #Destination table configuration destConf = self.page(1) destConf.registerField("optAppend",self.rbAppend) destConf.registerField("optOverwrite",self.rbOverwrite) destConf.registerField("tabIndex*",self.lstDestTables) destConf.registerField("geomCol",self.geomClm,"currentText",SIGNAL("currentIndexChanged(int)")) def initializePage(self,pageid): #Re-implementation of wizard page initialization if pageid == 1: #Reference to checked listwidget item representing table name self.destCheckedItem=None self.geomClm.clear() if self.field("typeText"): self.loadTables("textual") self.geomClm.setEnabled(False) elif self.field("typeSpatial"): self.loadTables("spatial") self.geomClm.setEnabled(True) if pageid == 2: self.lstSrcFields.clear() self.lstTargetFields.clear() self.assignCols() self._enable_disable_trans_tools() def _source_columns(self): return self.dataReader.getFields() def assignCols(self): #Load source and target columns respectively srcCols = self._source_columns() for c in srcCols: srcItem = QListWidgetItem(c,self.lstSrcFields) srcItem.setCheckState(Qt.Unchecked) srcItem.setIcon(QIcon(":/plugins/stdm/images/icons/column.png")) self.lstSrcFields.addItem(srcItem) #Destination Columns tabIndex = int(self.field("tabIndex")) self.targetTab = self.destCheckedItem.text() targetCols = table_column_names(self.targetTab, False, True) #Remove geometry columns in the target columns list for gc in self.geomcols: colIndex = getIndex(targetCols,gc) if colIndex != -1: targetCols.remove(gc) #Remove 'id' column if there id_idx = getIndex(targetCols, 'id') if id_idx != -1: targetCols.remove('id') self._add_target_table_columns(targetCols) def _add_target_table_columns(self, items, style=False): for item in items: list_item = QListWidgetItem(item) if style: color = QColor(0, 128, 255) list_item.setTextColor(color) self.lstTargetFields.addItem(list_item) def _on_load_virtual_columns(self, state): """ Load/unload relationships in the list of destination table columns. """ virtual_columns = self.dataReader.entity_virtual_columns(self.targetTab) if state: if len(virtual_columns) == 0: msg = QApplication.translate("ImportData", "There are no virtual columns for the specified table.") QMessageBox.warning( self, QApplication.translate( 'ImportData', 'Import Data' ), msg ) self.chk_virtual.setChecked(False) return self._add_target_table_columns(virtual_columns, True) else: self._remove_destination_table_fields(virtual_columns) def _remove_destination_table_fields(self, fields): """Remove the specified columns from the destination view.""" for f in fields: list_items = self.lstTargetFields.findItems(f, Qt.MatchFixedString) if len(list_items) > 0: list_item = list_items[0] row = self.lstTargetFields.row(list_item) rem_item = self.lstTargetFields.takeItem(row) del rem_item #Delete translator if already defined for the given column self._delete_translator(f) def loadGeomCols(self, table): #Load geometry columns based on the selected table self.geomcols = table_column_names(table, True, True) self.geomClm.clear() self.geomClm.addItems(self.geomcols) def loadTables(self, type): #Load textual or spatial tables self.lstDestTables.clear() tables = None if type == "textual": tables = profile_user_tables(self.curr_profile, False, True) elif type == "spatial": tables = profile_spatial_tables(self.curr_profile) if tables is not None: for t in tables: tabItem = QListWidgetItem(t,self.lstDestTables) tabItem.setCheckState(Qt.Unchecked) tabItem.setIcon(QIcon(":/plugins/stdm/images/icons/table.png")) self.lstDestTables.addItem(tabItem) def validateCurrentPage(self): #Validate the current page before proceeding to the next one validPage=True if not QFile.exists(unicode(self.field("srcFile"))): self.ErrorInfoMessage("The specified source file does not exist.") validPage = False else: if self.dataReader: self.dataReader.reset() self.dataReader = OGRReader(unicode(self.field("srcFile"))) if not self.dataReader.isValid(): self.ErrorInfoMessage("The source file could not be opened." "\nPlease check is the given file type " "is supported") validPage = False if self.currentId()==1: if self.destCheckedItem == None: self.ErrorInfoMessage("Please select the destination table.") validPage = False if self.currentId()==2: validPage = self.execImport() return validPage def setSourceFile(self): #Set the file path to the source file imageFilters = "Comma Separated Value (*.csv);;ESRI Shapefile (*.shp);;AutoCAD DXF (*.dxf)" sourceFile = QFileDialog.getOpenFileName(self,"Select Source File",vectorFileDir(),imageFilters) if sourceFile != "": self.txtDataSource.setText(sourceFile) def getSrcDestPairs(self): #Return the matched source and destination columns srcDest = {} for l in range(self.lstTargetFields.count()): if l < self.lstSrcFields.count(): srcItem = self.lstSrcFields.item(l) if srcItem.checkState() == Qt.Checked: destItem = self.lstTargetFields.item(l) srcDest[srcItem.text()] = destItem.text() return srcDest def execImport(self): #Initiate the import process success = False matchCols = self.getSrcDestPairs() #Specify geometry column geom_column=None if self.field("typeSpatial"): geom_column = self.field("geomCol") # Ensure that user has selected at least one column if it is a # non-spatial table if len(matchCols) == 0: self.ErrorInfoMessage("Please select at least one source column.") return success value_translator_manager = self._trans_widget_mgr.translator_manager() # try: if self.field("optOverwrite"): entity = self.curr_profile.entity_by_name(self.targetTab) dependencies = entity.dependencies() view_dep = dependencies['views'] entity_dep = [e.name for e in entity.children()] entities_dep_str = ', '.join(entity_dep) views_dep_str = ', '.join(view_dep) if len(entity_dep) > 0 or len(view_dep) > 0: del_msg = QApplication.translate( 'ImportData', "Overwriting existing records will permanently \n" "remove records from other tables linked to the \n" "records. The following tables will be affected." "\n{}\n{}" "\nClick Yes to proceed importing or No to cancel.". format(entities_dep_str, views_dep_str) ) del_result = QMessageBox.critical( self, QApplication.translate( "ImportData", "Overwrite Import Data Warning" ), del_msg, QMessageBox.Yes | QMessageBox.No ) if del_result == QMessageBox.Yes: self.dataReader.featToDb( self.targetTab, matchCols, False, self, geom_column, translator_manager=value_translator_manager ) # Update directory info in the registry setVectorFileDir(self.field("srcFile")) self.InfoMessage( "All features have been imported successfully!") else: success = False else: self.dataReader.featToDb( self.targetTab, matchCols, True, self, geom_column, translator_manager=value_translator_manager ) self.InfoMessage( "All features have been imported successfully!" ) #Update directory info in the registry setVectorFileDir(self.field("srcFile")) success = True # except: # self.ErrorInfoMessage(unicode(sys.exc_info()[1])) return success def _clear_dest_table_selections(self, exclude=None): #Clears checked items in destination table list view if exclude is None: exclude = [] for i in range(self.lstDestTables.count()): item = self.lstDestTables.item(i) if item.checkState() == Qt.Checked and not item.text() in exclude: item.setCheckState(Qt.Unchecked) def destSelectChanged(self, item): """ Handler when a list widget item is clicked, clears previous selections """ if not self.destCheckedItem is None: if item.checkState() == Qt.Checked: self.destCheckedItem.setCheckState(Qt.Unchecked) else: self.destCheckedItem = None if item.checkState() == Qt.Checked: self.destCheckedItem = item #Ensure other selected items have been cleared self._clear_dest_table_selections(exclude=[item.text()]) #Load geometry columns if selection is a spatial table if self.field("typeSpatial"): self.loadGeomCols(item.text()) def syncRowSelection(self, srcList, destList): """ Sync the selection of an srcList item to the corresponding one in the destination column list. """ if (srcList.currentRow() + 1) <= destList.count(): destList.setCurrentRow(srcList.currentRow()) def sourceRowChanged(self): #Slot when the source list's current row changes self.syncRowSelection(self.lstSrcFields,self.lstTargetFields) def destRowChanged(self): #Slot when the destination list's current row changes self.syncRowSelection(self.lstTargetFields, self.lstSrcFields) def itemUp(self, listWidget): #Moves the selected item in the list widget one level up curIndex = listWidget.currentRow() curItem = listWidget.takeItem(curIndex) listWidget.insertItem(curIndex - 1, curItem) listWidget.setCurrentRow(curIndex - 1) def itemDown(self, listWidget): #Moves the selected item in the list widget one level down curIndex=listWidget.currentRow() curItem=listWidget.takeItem(curIndex) listWidget.insertItem(curIndex + 1,curItem) listWidget.setCurrentRow(curIndex + 1) def checkAllItems(self, listWidget, state): #Checks all items in the list widget for l in range(listWidget.count()): item=listWidget.item(l) if state: item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) def checkSrcItems(self): #Slot for checking all source table columns self.checkAllItems(self.lstSrcFields, True) def uncheckSrcItems(self): #Slot for unchecking all source table columns self.checkAllItems(self.lstSrcFields, False) def srcItemUp(self): #Slot for moving source list item up self.itemUp(self.lstSrcFields) def srcItemDown(self): #Slot for moving source list item down self.itemDown(self.lstSrcFields) def targetItemUp(self): #Slot for moving target item up self.itemUp(self.lstTargetFields) def targetItemDown(self): #Slot for moving target item down self.itemDown(self.lstTargetFields) def keyPressEvent(self,e): """ Override method for preventing the dialog from closing itself when the escape key is hit """ if e.key() == Qt.Key_Escape: pass def InfoMessage(self, message): #Information message box msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(message) msg.exec_() def ErrorInfoMessage(self, message): #Error Message Box msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText(message) msg.exec_() # # def drag_drop_handler(self, event): # if event.source() == self: # rows = set([mi.row() for mi in self.selectedIndexes()]) # targetRow = self.indexAt(event.pos()).row() # rows.discard(targetRow) # rows = sorted(rows) # if not rows: # return # if targetRow == -1: # targetRow = self.rowCount() # for _ in range(len(rows)): # self.insertRow(targetRow) # rowMapping = dict() # Src row to target row. # for idx, row in enumerate(rows): # if row < targetRow: # rowMapping[row] = targetRow + idx # else: # rowMapping[row + len(rows)] = targetRow + idx # colCount = self.columnCount() # for srcRow, tgtRow in sorted(rowMapping.iteritems()): # for col in range(0, colCount): # self.setItem(tgtRow, col, self.takeItem(srcRow, col)) # for row in reversed(sorted(rowMapping.iterkeys())): # self.removeRow(row) # event.accept() # return def drag_move(self, e): e.accept()
class TrackingWindow(QMainWindow): """ Main window of the application. This class is responsible for the global data structures too. :IVariables: undo_stack : `QUndoStack` Undo stack. All actions that can be undone should be pushed on the stack. _project : `project.Project` Project object managing the loaded data _data : `tracking_data.TrackingData` Data object keeping track of points and cells toolGroup : `QActionGroup` Group of actions to be enabled only when actions can be taken on images previousSelAct : `QActionGroup` Actions enabled when points are selected in the previous pane currentSelAct : `QActionGroup` Actions enabled when points are selected in the current pane projectAct : `QActionGroup` Actions to enable once a project is loaded _previousScene : `tracking_scene.TrackingScene` Object managing the previous pane _currentScene : `tracking_scene.LinkedTrackingScene` Object managing the current pane """ def __init__(self, *args, **kwords): QMainWindow.__init__(self, *args) self.undo_stack = QUndoStack(self) self.ui = Ui_TrackingWindow() self.ui.setupUi(self) self._project = None self._data = None self.toolGroup = QActionGroup(self) self.toolGroup.addAction(self.ui.actionAdd_point) self.toolGroup.addAction(self.ui.action_Move_point) self.toolGroup.addAction(self.ui.actionAdd_cell) self.toolGroup.addAction(self.ui.actionRemove_cell) self.toolGroup.addAction(self.ui.action_Pan) self.toolGroup.addAction(self.ui.actionZoom_out) self.toolGroup.addAction(self.ui.actionZoom_in) self.previousSelAct = QActionGroup(self) self.previousSelAct.addAction( self.ui.actionCopy_selection_from_Previous) self.previousSelAct.addAction(self.ui.actionDelete_Previous) self.previousSelAct.setEnabled(False) self.currentSelAct = QActionGroup(self) self.currentSelAct.addAction(self.ui.actionCopy_selection_from_Current) self.currentSelAct.addAction(self.ui.actionDelete_Current) self.currentSelAct.setEnabled(False) self.projectAct = QActionGroup(self) self.projectAct.addAction(self.ui.action_Next_image) self.projectAct.addAction(self.ui.action_Previous_image) self.projectAct.addAction(self.ui.actionAdd_point) self.projectAct.addAction(self.ui.action_Move_point) self.projectAct.addAction(self.ui.action_Pan) self.projectAct.addAction(self.ui.actionAdd_cell) self.projectAct.addAction(self.ui.actionRemove_cell) self.projectAct.addAction(self.ui.action_Change_data_file) self.projectAct.addAction(self.ui.actionNew_data_file) self.projectAct.addAction(self.ui.actionZoom_out) self.projectAct.addAction(self.ui.actionZoom_in) self.projectAct.addAction(self.ui.actionSave_as) self.projectAct.addAction(self.ui.action_Fit) self.projectAct.addAction(self.ui.actionZoom_100) self.projectAct.addAction(self.ui.actionMerge_points) self.projectAct.addAction(self.ui.actionCopy_from_previous) self.projectAct.addAction(self.ui.actionCopy_from_current) self.projectAct.addAction(self.ui.actionReset_alignment) self.projectAct.addAction(self.ui.actionAlign_images) self.projectAct.addAction(self.ui.actionSelectPreviousAll) self.projectAct.addAction(self.ui.actionSelectPreviousNew) self.projectAct.addAction(self.ui.actionSelectPreviousNone) self.projectAct.addAction(self.ui.actionSelectPreviousNon_associated) self.projectAct.addAction(self.ui.actionSelectPreviousAssociated) self.projectAct.addAction(self.ui.actionSelectPreviousInvert) self.projectAct.addAction(self.ui.actionSelectCurrentAll) self.projectAct.addAction(self.ui.actionSelectCurrentNew) self.projectAct.addAction(self.ui.actionSelectCurrentNone) self.projectAct.addAction(self.ui.actionSelectCurrentNon_associated) self.projectAct.addAction(self.ui.actionSelectCurrentAssociated) self.projectAct.addAction(self.ui.actionSelectCurrentInvert) self.projectAct.addAction(self.ui.actionEdit_timing) self.projectAct.addAction(self.ui.actionEdit_scales) self.projectAct.addAction(self.ui.actionCompute_growth) self.projectAct.addAction(self.ui.actionClean_cells) self.projectAct.addAction(self.ui.actionGotoCell) self.projectAct.setEnabled(False) current_sel_actions = [ self.ui.actionSelectCurrentAll, self.ui.actionSelectCurrentNew, self.ui.actionSelectCurrentNone, self.ui.actionSelectCurrentInvert, '-', self.ui.actionSelectCurrentNon_associated, self.ui.actionSelectCurrentAssociated, self.ui.actionCopy_selection_from_Previous ] previous_sel_actions = [ self.ui.actionSelectPreviousAll, self.ui.actionSelectPreviousNew, self.ui.actionSelectPreviousNone, self.ui.actionSelectPreviousInvert, '-', self.ui.actionSelectPreviousNon_associated, self.ui.actionSelectPreviousAssociated, self.ui.actionCopy_selection_from_Current ] self._previousScene = TrackingScene(self.undo_stack, self.ui.actionDelete_Previous, previous_sel_actions, self) self._currentScene = LinkedTrackingScene(self._previousScene, self.undo_stack, self.ui.actionDelete_Current, current_sel_actions, self) self._previousScene.hasSelectionChanged.connect( self.previousSelAct.setEnabled) self._currentScene.hasSelectionChanged.connect( self.currentSelAct.setEnabled) self._previousScene.realSceneSizeChanged.connect(self.sceneSizeChanged) self._currentScene.realSceneSizeChanged.connect(self.sceneSizeChanged) self._previousScene.zoomIn[QPointF].connect(self.zoomIn) self._currentScene.zoomIn.connect(self.zoomIn) self._previousScene.zoomOut[QPointF].connect(self.zoomOut) self._currentScene.zoomOut.connect(self.zoomOut) self.ui.previousData.setScene(self._previousScene) self.ui.currentData.setScene(self._currentScene) self.ui.previousData.setDragMode(QGraphicsView.ScrollHandDrag) self.ui.currentData.setDragMode(QGraphicsView.ScrollHandDrag) #self.ui.previousData.setCacheMode(QGraphicsView.CacheBackground) #self.ui.currentData.setCacheMode(QGraphicsView.CacheBackground) # Redefine shortcuts to standard key sequences self.ui.action_Save.setShortcut(QKeySequence.Save) self.ui.actionSave_as.setShortcut(QKeySequence.SaveAs) self.ui.action_Open_project.setShortcut(QKeySequence.Open) self.ui.action_Undo.setShortcut(QKeySequence.Undo) self.ui.action_Redo.setShortcut(QKeySequence.Redo) self.ui.action_Next_image.setShortcut(QKeySequence.Forward) self.ui.action_Previous_image.setShortcut(QKeySequence.Back) # Connecting undo stack signals self.ui.action_Undo.triggered.connect(self.undo) self.ui.action_Redo.triggered.connect(self.redo) self.undo_stack.canRedoChanged[bool].connect( self.ui.action_Redo.setEnabled) self.undo_stack.canUndoChanged[bool].connect( self.ui.action_Undo.setEnabled) self.undo_stack.redoTextChanged["const QString&"].connect( self.changeRedoText) self.undo_stack.undoTextChanged["const QString&"].connect( self.changeUndoText) self.undo_stack.cleanChanged[bool].connect( self.ui.action_Save.setDisabled) # link_icon = QIcon() # pix = QPixmap(":/icons/link.png") # link_icon.addPixmap(pix, QIcon.Normal, QIcon.On) # pix = QPixmap(":/icons/link_broken.png") # link_icon.addPixmap(pix, QIcon.Normal, QIcon.Off) # self.link_icon = link_icon # #self.ui.linkViews.setIconSize(QSize(64,32)) # self.ui.linkViews.setIcon(link_icon) self._recent_projects_menu = QMenu(self) self.ui.actionRecent_projects.setMenu(self._recent_projects_menu) self._recent_projects_act = [] self._projects_mapper = QSignalMapper(self) self._projects_mapper.mapped[int].connect(self.loadRecentProject) self.param_dlg = None # Setting up the status bar bar = self.statusBar() # Adding current directory cur_dir = QLabel("") bar.addPermanentWidget(cur_dir) self._current_dir_label = cur_dir # Adding up zoom zoom = QLabel("") bar.addPermanentWidget(zoom) self._zoom_label = zoom self.changeZoom(1) self.loadConfig() parameters.instance.renderingChanged.connect(self.changeRendering) self.changeRendering() def changeRendering(self): if parameters.instance.use_OpenGL: self.ui.previousData.setViewport( QGLWidget(QGLFormat(QGL.SampleBuffers))) self.ui.currentData.setViewport( QGLWidget(QGLFormat(QGL.SampleBuffers))) else: self.ui.previousData.setViewport(QWidget()) self.ui.currentData.setViewport(QWidget()) def undo(self): self.undo_stack.undo() def redo(self): self.undo_stack.redo() def changeRedoText(self, text): self.ui.action_Redo.setText(text) self.ui.action_Redo.setToolTip(text) self.ui.action_Redo.setStatusTip(text) def changeUndoText(self, text): self.ui.action_Undo.setText(text) self.ui.action_Undo.setToolTip(text) self.ui.action_Undo.setStatusTip(text) def closeEvent(self, event): self.saveConfig() if not self.ensure_save_data( "Exiting whith unsaved data", "The last modifications you made were not saved." " Are you sure you want to exit?"): event.ignore() return QMainWindow.closeEvent(self, event) #sys.exit(0) def loadConfig(self): params = parameters.instance self.ui.action_Show_vector.setChecked(params.show_vectors) self.ui.linkViews.setChecked(params.link_views) self.ui.action_Show_template.setChecked( parameters.instance.show_template) self.ui.actionShow_id.setChecked(parameters.instance.show_id) self.ui.action_Estimate_position.setChecked( parameters.instance.estimate) self.updateRecentFiles() params.recentProjectsChange.connect(self.updateRecentFiles) def updateRecentFiles(self): for a in self._recent_projects_act: self._projects_mapper.removeMappings(a) del self._recent_projects_act[:] menu = self._recent_projects_menu menu.clear() recent_projects = parameters.instance.recent_projects for i, p in enumerate(recent_projects): act = QAction(self) act.setText("&{0:d} {1}".format(i + 1, p)) self._recent_projects_act.append(act) act.triggered.connect(self._projects_mapper.map) self._projects_mapper.setMapping(act, i) menu.addAction(act) def saveConfig(self): parameters.instance.save() def check_for_data(self): if self._project is None: QMessageBox.critical( self, "No project loaded", "You have to load a project before performing this operation") return False return True def loadRecentProject(self, i): if self.ensure_save_data( "Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change project?"): self.loadProject(parameters.instance.recent_projects[i]) @pyqtSignature("") def on_action_Open_project_triggered(self): if self.ensure_save_data( "Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change project?"): dir_ = QFileDialog.getExistingDirectory( self, "Select a project directory", parameters.instance._last_dir) if dir_: self.loadProject(dir_) def loadProject(self, dir_): dir_ = path(dir_) project = Project(dir_) if project.valid: self._project = project else: create = QMessageBox.question( self, "Invalid project directory", "This directory does not contain a valid project. Turn into a directory?", QMessageBox.No, QMessageBox.Yes) if create == QMessageBox.No: return project.create() self._project = project self._project.use() parameters.instance.add_recent_project(dir_) parameters.instance._last_dir = dir_ if self._data is not None: _data = self._data _data.saved.disconnect(self.undo_stack.setClean) try: #self._project.load() self.load_data() _data = self._project.data _data.saved.connect(self.undo_stack.setClean) self._project.changedDataFile.connect(self.dataFileChanged) self._data = _data self._previousScene.changeDataManager(self._data) self._currentScene.changeDataManager(self._data) self.initFromData() self.projectAct.setEnabled(True) except TrackingDataException as ex: showException(self, "Error while loaded data", ex) def dataFileChanged(self, new_file): if new_file is None: self._current_dir_label.setText("") else: self._current_dir_label.setText(new_file) def initFromData(self): """ Initialize the interface using the current data """ self.ui.previousState.clear() self.ui.currentState.clear() for name in self._data.images_name: self.ui.previousState.addItem(name) self.ui.currentState.addItem(name) self.ui.previousState.setCurrentIndex(0) self.ui.currentState.setCurrentIndex(1) self._previousScene.changeImage( self._data.image_path(self._data.images_name[0])) self._currentScene.changeImage( self._data.image_path(self._data.images_name[1])) self.dataFileChanged(self._project.data_file) @pyqtSignature("int") def on_previousState_currentIndexChanged(self, index): #print "Previous image loaded: %s" % self._data.images[index] self.changeScene(self._previousScene, index) self._currentScene.changeImage(None) @pyqtSignature("int") def on_currentState_currentIndexChanged(self, index): #print "Current image loaded: %s" % self._data.images[index] self.changeScene(self._currentScene, index) def changeScene(self, scene, index): """ Set the scene to use the image number index. """ scene.changeImage(self._data.image_path(self._data.images_name[index])) @pyqtSignature("") def on_action_Save_triggered(self): self.save_data() @pyqtSignature("") def on_actionSave_as_triggered(self): fn = QFileDialog.getSaveFileName(self, "Select a data file to save in", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: self.save_data(path(fn)) def save_data(self, data_file=None): if self._data is None: raise TrackingDataException( "Trying to save data when none have been loaded") try: self._project.save(data_file) return True except TrackingDataException as ex: showException(self, "Error while saving data", ex) return False def load_data(self, **opts): if self._project is None: raise TrackingDataException( "Trying to load data when no project have been loaded") try: if self._project.load(**opts): log_debug("Data file was corrected. Need saving.") self.ui.action_Save.setEnabled(True) else: log_debug("Data file is clean.") self.ui.action_Save.setEnabled(False) return True except TrackingDataException as ex: showException(self, "Error while loading data", ex) return False except RetryTrackingDataException as ex: if retryException(self, "Problem while loading data", ex): new_opts = dict(opts) new_opts.update(ex.method_args) return self.load_data(**new_opts) return False def ensure_save_data(self, title, reason): if self._data is not None and not self.undo_stack.isClean(): button = QMessageBox.warning( self, title, reason, QMessageBox.Yes | QMessageBox.Save | QMessageBox.Cancel) if button == QMessageBox.Save: return self.save_data() elif button == QMessageBox.Cancel: return False self.undo_stack.clear() return True @pyqtSignature("") def on_action_Change_data_file_triggered(self): if self.ensure_save_data( "Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change the current data file?"): fn = QFileDialog.getOpenFileName( self, "Select a data file to load", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: self._project.data_file = str(fn) if self.load_data(): self._previousScene.resetNewPoints() self._currentScene.resetNewPoints() @pyqtSignature("bool") def on_action_Show_vector_toggled(self, value): parameters.instance.show_vector = value self._currentScene.showVector(value) @pyqtSignature("bool") def on_action_Show_template_toggled(self, value): parameters.instance.show_template = value @pyqtSignature("bool") def on_actionShow_id_toggled(self, value): parameters.instance.show_id = value @pyqtSignature("") def on_action_Next_image_triggered(self): cur = self.ui.currentState.currentIndex() pre = self.ui.previousState.currentIndex() l = len(self._data.images_name) if cur < l - 1 and pre < l - 1: self.ui.previousState.setCurrentIndex(pre + 1) self.ui.currentState.setCurrentIndex(cur + 1) @pyqtSignature("") def on_action_Previous_image_triggered(self): cur = self.ui.currentState.currentIndex() pre = self.ui.previousState.currentIndex() if cur > 0 and pre > 0: self.ui.previousState.setCurrentIndex(pre - 1) self.ui.currentState.setCurrentIndex(cur - 1) @pyqtSignature("") def on_copyToPrevious_clicked(self): self._currentScene.copyFromLinked(self._previousScene) @pyqtSignature("") def on_copyToCurrent_clicked(self): self._previousScene.copyToLinked(self._currentScene) @pyqtSignature("bool") def on_action_Estimate_position_toggled(self, value): parameters.instance.estimate = value # @pyqtSignature("") # def on_action_Undo_triggered(self): # print "Undo" # @pyqtSignature("") # def on_action_Redo_triggered(self): # print "Redo" @pyqtSignature("bool") def on_action_Parameters_toggled(self, value): if value: from .parametersdlg import ParametersDlg self._previousScene.showTemplates() self._currentScene.showTemplates() #tracking_scene.saveParameters() parameters.instance.save() max_size = max(self._currentScene.width(), self._currentScene.height(), self._previousScene.width(), self._previousScene.height(), 400) self.param_dlg = ParametersDlg(max_size, self) self.param_dlg.setModal(False) self.ui.action_Pan.setChecked(True) self.ui.actionAdd_point.setEnabled(False) self.ui.action_Move_point.setEnabled(False) self.ui.actionAdd_cell.setEnabled(False) self.ui.actionRemove_cell.setEnabled(False) self.ui.action_Undo.setEnabled(False) self.ui.action_Redo.setEnabled(False) self.ui.action_Open_project.setEnabled(False) self.ui.actionRecent_projects.setEnabled(False) self.ui.action_Change_data_file.setEnabled(False) self.ui.copyToCurrent.setEnabled(False) self.ui.copyToPrevious.setEnabled(False) self.param_dlg.finished[int].connect(self.closeParam) self.param_dlg.show() elif self.param_dlg: self.param_dlg.accept() def closeParam(self, value): if value == QDialog.Rejected: parameters.instance.load() self.ui.actionAdd_point.setEnabled(True) self.ui.action_Move_point.setEnabled(True) self.ui.actionAdd_cell.setEnabled(True) self.ui.actionRemove_cell.setEnabled(True) self.ui.action_Undo.setEnabled(True) self.ui.action_Redo.setEnabled(True) self.ui.action_Open_project.setEnabled(True) self.ui.actionRecent_projects.setEnabled(True) self.ui.action_Change_data_file.setEnabled(True) self.ui.copyToCurrent.setEnabled(True) self.ui.copyToPrevious.setEnabled(True) self._previousScene.showTemplates(False) self._currentScene.showTemplates(False) self._previousScene.update() self._currentScene.update() self.param_dlg = None self.ui.action_Parameters.setChecked(False) @pyqtSignature("bool") def on_actionZoom_in_toggled(self, value): if value: self._previousScene.mode = TrackingScene.ZoomIn self._currentScene.mode = TrackingScene.ZoomIn @pyqtSignature("bool") def on_actionZoom_out_toggled(self, value): if value: self._previousScene.mode = TrackingScene.ZoomOut self._currentScene.mode = TrackingScene.ZoomOut #def resizeEvent(self, event): # self.ensureZoomFit() def ensureZoomFit(self): if self._data: prev_rect = self._previousScene.sceneRect() cur_rect = self._currentScene.sceneRect() prev_wnd = QRectF(self.ui.previousData.childrenRect()) cur_wnd = QRectF(self.ui.currentData.childrenRect()) prev_matrix = self.ui.previousData.matrix() cur_matrix = self.ui.currentData.matrix() prev_mapped_rect = prev_matrix.mapRect(prev_rect) cur_mapped_rect = cur_matrix.mapRect(cur_rect) if (prev_mapped_rect.width() < prev_wnd.width() or prev_mapped_rect.height() < prev_wnd.height() or cur_mapped_rect.width() < cur_wnd.width() or cur_mapped_rect.height() < cur_wnd.height()): self.on_action_Fit_triggered() @pyqtSignature("") def on_action_Fit_triggered(self): prev_rect = self._previousScene.sceneRect() cur_rect = self._currentScene.sceneRect() prev_wnd = self.ui.previousData.childrenRect() cur_wnd = self.ui.currentData.childrenRect() prev_sw = prev_wnd.width() / prev_rect.width() prev_sh = prev_wnd.height() / prev_rect.height() cur_sw = cur_wnd.width() / cur_rect.width() cur_sh = cur_wnd.height() / cur_rect.height() s = max(prev_sw, prev_sh, cur_sw, cur_sh) self.ui.previousData.resetMatrix() self.ui.previousData.scale(s, s) self.ui.currentData.resetMatrix() self.ui.currentData.scale(s, s) self.changeZoom(s) def zoomOut(self, point=None): self.ui.currentData.scale(0.5, 0.5) self.ui.previousData.scale(0.5, 0.5) self.changeZoom(self.ui.previousData.matrix().m11()) if point is not None: self.ui.previousData.centerOn(point) self.ui.currentData.centerOn(point) #self.ensureZoomFit() def zoomIn(self, point=None): self.ui.currentData.scale(2, 2) self.ui.previousData.scale(2, 2) self.changeZoom(self.ui.previousData.matrix().m11()) if point is not None: self.ui.previousData.centerOn(point) self.ui.currentData.centerOn(point) def changeZoom(self, zoom): self._zoom_label.setText("Zoom: %.5g%%" % (100 * zoom)) @pyqtSignature("") def on_actionZoom_100_triggered(self): self.ui.previousData.resetMatrix() self.ui.currentData.resetMatrix() self.changeZoom(1) @pyqtSignature("bool") def on_actionAdd_point_toggled(self, value): if value: self._previousScene.mode = TrackingScene.Add self._currentScene.mode = TrackingScene.Add @pyqtSignature("bool") def on_actionAdd_cell_toggled(self, value): if value: self._previousScene.mode = TrackingScene.AddCell self._currentScene.mode = TrackingScene.AddCell @pyqtSignature("bool") def on_actionRemove_cell_toggled(self, value): if value: self._previousScene.mode = TrackingScene.RemoveCell self._currentScene.mode = TrackingScene.RemoveCell @pyqtSignature("bool") def on_action_Move_point_toggled(self, value): if value: self._previousScene.mode = TrackingScene.Move self._currentScene.mode = TrackingScene.Move @pyqtSignature("bool") def on_action_Pan_toggled(self, value): if value: self._previousScene.mode = TrackingScene.Pan self._currentScene.mode = TrackingScene.Pan @pyqtSignature("bool") def on_linkViews_toggled(self, value): parameters.instance.link_views = value phor = self.ui.previousData.horizontalScrollBar() pver = self.ui.previousData.verticalScrollBar() chor = self.ui.currentData.horizontalScrollBar() cver = self.ui.currentData.verticalScrollBar() if value: phor.valueChanged[int].connect(chor.setValue) pver.valueChanged[int].connect(cver.setValue) chor.valueChanged[int].connect(phor.setValue) cver.valueChanged[int].connect(pver.setValue) self._previousScene.templatePosChange.connect( self._currentScene.setTemplatePos) self._currentScene.templatePosChange.connect( self._previousScene.setTemplatePos) phor.setValue(chor.value()) pver.setValue(cver.value()) else: phor.valueChanged[int].disconnect(chor.setValue) pver.valueChanged[int].disconnect(cver.setValue) chor.valueChanged[int].disconnect(phor.setValue) cver.valueChanged[int].disconnect(pver.setValue) self._previousScene.templatePosChange.disconnect( self._currentScene.setTemplatePos) self._currentScene.templatePosChange.disconnect( self._previousScene.setTemplatePos) def copyFrom(self, start, items): if parameters.instance.estimate: dlg = createForm('copy_progress.ui', None) dlg.buttonBox.clicked["QAbstractButton*"].connect(self.cancelCopy) params = parameters.instance ts = params.template_size ss = params.search_size fs = params.filter_size self.copy_thread = algo.FindInAll(self._data, start, items, ts, ss, fs, self) dlg.imageProgress.setMaximum(self.copy_thread.num_images) self.copy_thread.start() self.copy_dlg = dlg dlg.exec_() else: algo.copyFromImage(self._data, start, items, self.undo_stack) def cancelCopy(self, *args): self.copy_thread.stop = True dlg = self.copy_dlg dlg.buttonBox.clicked['QAbstractButton*)'].disconnect(self.cancelCopy) self._previousScene.changeImage(None) self._currentScene.changeImage(None) def event(self, event): if isinstance(event, algo.NextImage): dlg = self.copy_dlg if dlg is not None: dlg.imageProgress.setValue(event.currentImage) dlg.pointProgress.setMaximum(event.nbPoints) dlg.pointProgress.setValue(0) return True elif isinstance(event, algo.NextPoint): dlg = self.copy_dlg if dlg is not None: dlg.pointProgress.setValue(event.currentPoint) return True elif isinstance(event, algo.FoundAll): dlg = self.copy_dlg if dlg is not None: self.cancelCopy() dlg.accept() return True elif isinstance(event, algo.Aborted): dlg = self.copy_dlg if dlg is not None: self.cancelCopy() dlg.accept() return True return QMainWindow.event(self, event) def itemsToCopy(self, scene): items = scene.getSelectedIds() if items: answer = QMessageBox.question( self, "Copy of points", "Some points were selected in the previous data window." " Do you want to copy only these point on the successive images?", QMessageBox.Yes, QMessageBox.No) if answer == QMessageBox.Yes: return items return scene.getAllIds() @pyqtSignature("") def on_actionCopy_from_previous_triggered(self): items = self.itemsToCopy(self._previousScene) if items: self.copyFrom(self.ui.previousState.currentIndex(), items) @pyqtSignature("") def on_actionCopy_from_current_triggered(self): items = self.itemsToCopy(self._currentScene) if items: self.copyFrom(self.ui.currentState.currentIndex(), items) @pyqtSignature("") def on_actionSelectPreviousAll_triggered(self): self._previousScene.selectAll() @pyqtSignature("") def on_actionSelectPreviousNew_triggered(self): self._previousScene.selectNew() @pyqtSignature("") def on_actionSelectPreviousNone_triggered(self): self._previousScene.selectNone() @pyqtSignature("") def on_actionSelectPreviousNon_associated_triggered(self): self._previousScene.selectNonAssociated() @pyqtSignature("") def on_actionSelectPreviousAssociated_triggered(self): self._previousScene.selectAssociated() @pyqtSignature("") def on_actionSelectPreviousInvert_triggered(self): self._previousScene.selectInvert() @pyqtSignature("") def on_actionSelectCurrentAll_triggered(self): self._currentScene.selectAll() @pyqtSignature("") def on_actionSelectCurrentNew_triggered(self): self._currentScene.selectNew() @pyqtSignature("") def on_actionSelectCurrentNone_triggered(self): self._currentScene.selectNone() @pyqtSignature("") def on_actionSelectCurrentNon_associated_triggered(self): self._currentScene.selectNonAssociated() @pyqtSignature("") def on_actionSelectCurrentAssociated_triggered(self): self._currentScene.selectAssociated() @pyqtSignature("") def on_actionSelectCurrentInvert_triggered(self): self._currentScene.selectInvert() def whichDelete(self): """ Returns a function deleting what the user wants """ dlg = createForm("deletedlg.ui", None) ret = dlg.exec_() if ret: if dlg.inAllImages.isChecked(): return TrackingScene.deleteInAllImages if dlg.toImage.isChecked(): return TrackingScene.deleteToImage if dlg.fromImage.isChecked(): return TrackingScene.deleteFromImage return lambda x: None @pyqtSignature("") def on_actionDelete_Previous_triggered(self): del_fct = self.whichDelete() del_fct(self._previousScene) @pyqtSignature("") def on_actionDelete_Current_triggered(self): del_fct = self.whichDelete() del_fct(self._currentScene) @pyqtSignature("") def on_actionMerge_points_triggered(self): if self._previousScene.mode == TrackingScene.AddCell: old_cell = self._previousScene.selected_cell new_cell = self._currentScene.selected_cell if old_cell is None or new_cell is None: QMessageBox.critical( self, "Cannot merge cells", "You have to select exactly one cell in the old state " "and one in the new state to merge them.") return try: if old_cell != new_cell: self.undo_stack.push( MergeCells(self._data, self._previousScene.image_name, old_cell, new_cell)) else: self.undo_stack.push( SplitCells(self._data, self._previousScene.image_name, old_cell, new_cell)) except AssertionError as error: QMessageBox.critical(self, "Cannot merge the cells", str(error)) else: old_pts = self._previousScene.getSelectedIds() new_pts = self._currentScene.getSelectedIds() if len(old_pts) != 1 or len(new_pts) != 1: QMessageBox.critical( self, "Cannot merge points", "You have to select exactly one point in the old state " "and one in the new state to link them.") return try: if old_pts != new_pts: self.undo_stack.push( ChangePointsId(self._data, self._previousScene.image_name, old_pts, new_pts)) else: log_debug("Splitting point of id %d" % old_pts[0]) self.undo_stack.push( SplitPointsId(self._data, self._previousScene.image_name, old_pts)) except AssertionError as error: QMessageBox.critical(self, "Cannot merge the points", str(error)) @pyqtSignature("") def on_actionCopy_selection_from_Current_triggered(self): cur_sel = self._currentScene.getSelectedIds() self._previousScene.setSelectedIds(cur_sel) @pyqtSignature("") def on_actionCopy_selection_from_Previous_triggered(self): cur_sel = self._previousScene.getSelectedIds() self._currentScene.setSelectedIds(cur_sel) @pyqtSignature("") def on_actionNew_data_file_triggered(self): if self.ensure_save_data( "Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change the current data file?"): fn = QFileDialog.getSaveFileName( self, "Select a new data file to create", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: fn = path(fn) if fn.exists(): button = QMessageBox.question( self, "Erasing existing file", "Are you sure yo want to empty the file '%s' ?" % fn, QMessageBox.Yes, QMessageBox.No) if button == QMessageBox.No: return fn.remove() self._data.clear() self._previousScene.resetNewPoints() self._currentScene.resetNewPoints() self._project.data_file = fn self.initFromData() log_debug("Data file = %s" % (self._project.data_file, )) @pyqtSignature("") def on_actionAbout_triggered(self): dlg = QMessageBox(self) dlg.setWindowTitle("About Point Tracker") dlg.setIconPixmap(self.windowIcon().pixmap(64, 64)) #dlg.setTextFormat(Qt.RichText) dlg.setText("""Point Tracker Tool version %s rev %s Developper: Pierre Barbier de Reuille <*****@*****.**> Copyright 2008 """ % (__version__, __revision__)) img_read = ", ".join( str(s) for s in QImageReader.supportedImageFormats()) img_write = ", ".join( str(s) for s in QImageWriter.supportedImageFormats()) dlg.setDetailedText("""Supported image formats: - For reading: %s - For writing: %s """ % (img_read, img_write)) dlg.exec_() @pyqtSignature("") def on_actionAbout_Qt_triggered(self): QMessageBox.aboutQt(self, "About Qt") @pyqtSignature("") def on_actionReset_alignment_triggered(self): self.undo_stack.push(ResetAlignment(self._data)) @pyqtSignature("") def on_actionAlign_images_triggered(self): fn = QFileDialog.getOpenFileName(self, "Select a data file for alignment", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: d = self._data.copy() fn = path(fn) try: d.load(fn) except TrackingDataException as ex: showException(self, "Error while loading data file", ex) return if d._last_pt_id > 0: dlg = AlignmentDlg(d._last_pt_id + 1, self) if dlg.exec_(): ref = dlg.ui.referencePoint.currentText() try: ref = int(ref) except ValueError: ref = str(ref) if dlg.ui.twoPointsRotation.isChecked(): r1 = int(dlg.ui.rotationPt1.currentText()) r2 = int(dlg.ui.rotationPt2.currentText()) rotation = ("TwoPoint", r1, r2) else: rotation = None else: return else: ref = 0 rotation = None try: shifts, angles = algo.alignImages(self._data, d, ref, rotation) self.undo_stack.push(AlignImages(self._data, shifts, angles)) except algo.AlgoException as ex: showException(self, "Error while aligning images", ex) def sceneSizeChanged(self): previous_rect = self._previousScene.real_scene_rect current_rect = self._currentScene.real_scene_rect rect = previous_rect | current_rect self._previousScene.setSceneRect(rect) self._currentScene.setSceneRect(rect) @pyqtSignature("") def on_actionEdit_timing_triggered(self): data = self._data dlg = TimeEditDlg(data.images_name, data.images_time, [data.image_path(n) for n in data.images_name], self) self.current_dlg = dlg if dlg.exec_() == QDialog.Accepted: self.undo_stack.push(ChangeTiming(data, [t for n, t in dlg.model])) del self.current_dlg @pyqtSignature("") def on_actionEdit_scales_triggered(self): data = self._data dlg = EditResDlg(data.images_name, data.images_scale, [data.image_path(n) for n in data.images_name], self) self.current_dlg = dlg if dlg.exec_() == QDialog.Accepted: self.undo_stack.push( ChangeScales(data, [sc for n, sc in dlg.model])) del self.current_dlg @pyqtSignature("") def on_actionCompute_growth_triggered(self): data = self._data dlg = GrowthComputationDlg(data, self) self.current_dlg = dlg dlg.exec_() del self.current_dlg @pyqtSignature("") def on_actionPlot_growth_triggered(self): data = self._data dlg = PlottingDlg(data, self) self.current_dlg = dlg dlg.exec_() del self.current_dlg @pyqtSignature("") def on_actionClean_cells_triggered(self): self.undo_stack.push(CleanCells(self._data)) @pyqtSignature("") def on_actionGotoCell_triggered(self): cells = [str(cid) for cid in self._data.cells] selected, ok = QInputDialog.getItem(self, "Goto cell", "Select the cell to go to", cells, 0) if ok: cid = int(selected) self.ui.actionAdd_cell.setChecked(True) data = self._data if cid not in data.cells: return ls = data.cells_lifespan[cid] prev_pos = self._previousScene.current_data._current_index cur_pos = self._currentScene.current_data._current_index full_poly = data.cells[cid] poly = [pid for pid in full_poly if pid in data[prev_pos]] #log_debug("Cell %d on time %d: %s" % (cid, prev_pos, poly)) if prev_pos < ls.start or prev_pos >= ls.end or not poly: for i in range(*ls.slice().indices(len(data))): poly = [pid for pid in full_poly if pid in data[i]] if poly: log_debug("Found cell %d on image %d with polygon %s" % (cid, i, poly)) new_prev_pos = i break else: log_debug("Cell %d found nowhere in range %s!!!" % (cid, ls.slice())) else: new_prev_pos = prev_pos new_cur_pos = min(max(cur_pos + new_prev_pos - prev_pos, 0), len(data)) self.ui.previousState.setCurrentIndex(new_prev_pos) self.ui.currentState.setCurrentIndex(new_cur_pos) self._previousScene.current_cell = cid self._currentScene.current_cell = cid prev_data = self._previousScene.current_data poly = data.cells[cid] prev_poly = QPolygonF( [prev_data[ptid] for ptid in poly if ptid in prev_data]) prev_bbox = prev_poly.boundingRect() log_debug("Previous bounding box = %dx%d+%d+%d" % (prev_bbox.width(), prev_bbox.height(), prev_bbox.left(), prev_bbox.top())) self.ui.previousData.ensureVisible(prev_bbox) @pyqtSignature("") def on_actionGotoPoint_triggered(self): data = self._data points = [str(pid) for pid in data.cell_points] selected, ok = QInputDialog.getItem(self, "Goto point", "Select the point to go to", points, 0) if ok: pid = int(selected) self.ui.action_Move_point.setChecked(True) if pid not in data.cell_points: return prev_pos = self._previousScene.current_data._current_index cur_pos = self._currentScene.current_data._current_index prev_data = self._previousScene.current_data if not pid in prev_data: closest = -1 best_dist = len(data) + 1 for img_data in data: if pid in img_data: dist = abs(img_data._current_index - prev_pos) if dist < best_dist: best_dist = dist closest = img_data._current_index new_prev_pos = closest else: new_prev_pos = prev_pos new_cur_pos = min(max(cur_pos + new_prev_pos - prev_pos, 0), len(data)) self.ui.previousState.setCurrentIndex(new_prev_pos) self.ui.currentState.setCurrentIndex(new_cur_pos) self._previousScene.setSelectedIds([pid]) self._currentScene.setSelectedIds([pid]) self.ui.previousData.centerOn( self._previousScene.current_data[pid])
class CanvasScene(QGraphicsScene): """ A Graphics Scene for displaying an :class:`~.scheme.Scheme` instance. """ #: Signal emitted when a :class:`NodeItem` has been added to the scene. node_item_added = Signal(object) #: Signal emitted when a :class:`NodeItem` has been removed from the #: scene. node_item_removed = Signal(object) #: Signal emitted when a new :class:`LinkItem` has been added to the #: scene. link_item_added = Signal(object) #: Signal emitted when a :class:`LinkItem` has been removed. link_item_removed = Signal(object) #: Signal emitted when a :class:`Annotation` item has been added. annotation_added = Signal(object) #: Signal emitted when a :class:`Annotation` item has been removed. annotation_removed = Signal(object) #: Signal emitted when the position of a :class:`NodeItem` has changed. node_item_position_changed = Signal(object, QPointF) #: Signal emitted when an :class:`NodeItem` has been double clicked. node_item_double_clicked = Signal(object) #: An node item has been activated (clicked) node_item_activated = Signal(object) #: An node item has been hovered node_item_hovered = Signal(object) #: Link item has been hovered link_item_hovered = Signal(object) def __init__(self, *args, **kwargs): QGraphicsScene.__init__(self, *args, **kwargs) self.scheme = None self.registry = None # All node items self.__node_items = [] # Mapping from SchemeNodes to canvas items self.__item_for_node = {} # All link items self.__link_items = [] # Mapping from SchemeLinks to canvas items. self.__item_for_link = {} # All annotation items self.__annotation_items = [] # Mapping from SchemeAnnotations to canvas items. self.__item_for_annotation = {} # Is the scene editable self.editable = True # Anchor Layout self.__anchor_layout = AnchorLayout() self.addItem(self.__anchor_layout) self.__channel_names_visible = True self.__node_animation_enabled = True self.user_interaction_handler = None self.activated_mapper = QSignalMapper(self) self.activated_mapper.mapped[QObject].connect( lambda node: self.node_item_activated.emit(node) ) self.hovered_mapper = QSignalMapper(self) self.hovered_mapper.mapped[QObject].connect( lambda node: self.node_item_hovered.emit(node) ) self.position_change_mapper = QSignalMapper(self) self.position_change_mapper.mapped[QObject].connect( self._on_position_change ) log.info("'%s' intitialized." % self) def clear_scene(self): """ Clear (reset) the scene. """ if self.scheme is not None: self.scheme.node_added.disconnect(self.add_node) self.scheme.node_removed.disconnect(self.remove_node) self.scheme.link_added.disconnect(self.add_link) self.scheme.link_removed.disconnect(self.remove_link) self.scheme.annotation_added.disconnect(self.add_annotation) self.scheme.annotation_removed.disconnect(self.remove_annotation) self.scheme.node_state_changed.disconnect( self.on_widget_state_change ) self.scheme.channel_state_changed.disconnect( self.on_link_state_change ) # Remove all items to make sure all signals from scheme items # to canvas items are disconnected. for annot in self.scheme.annotations: if annot in self.__item_for_annotation: self.remove_annotation(annot) for link in self.scheme.links: if link in self.__item_for_link: self.remove_link(link) for node in self.scheme.nodes: if node in self.__item_for_node: self.remove_node(node) self.scheme = None self.__node_items = [] self.__item_for_node = {} self.__link_items = [] self.__item_for_link = {} self.__annotation_items = [] self.__item_for_annotation = {} self.__anchor_layout.deleteLater() self.user_interaction_handler = None self.clear() log.info("'%s' cleared." % self) def set_scheme(self, scheme): """ Set the scheme to display. Populates the scene with nodes and links already in the scheme. Any further change to the scheme will be reflected in the scene. Parameters ---------- scheme : :class:`~.scheme.Scheme` """ if self.scheme is not None: # Clear the old scheme self.clear_scene() log.info("Setting scheme '%s' on '%s'" % (scheme, self)) self.scheme = scheme if self.scheme is not None: self.scheme.node_added.connect(self.add_node) self.scheme.node_removed.connect(self.remove_node) self.scheme.link_added.connect(self.add_link) self.scheme.link_removed.connect(self.remove_link) self.scheme.annotation_added.connect(self.add_annotation) self.scheme.annotation_removed.connect(self.remove_annotation) self.scheme.node_state_changed.connect( self.on_widget_state_change ) self.scheme.channel_state_changed.connect( self.on_link_state_change ) self.scheme.topology_changed.connect(self.on_scheme_change) for node in scheme.nodes: self.add_node(node) for link in scheme.links: self.add_link(link) for annot in scheme.annotations: self.add_annotation(annot) def set_registry(self, registry): """ Set the widget registry. """ # TODO: Remove/Deprecate. Is used only to get the category/background # color. That should be part of the SchemeNode/WidgetDescription. log.info("Setting registry '%s on '%s'." % (registry, self)) self.registry = registry def set_anchor_layout(self, layout): """ Set an :class:`~.layout.AnchorLayout` """ if self.__anchor_layout != layout: if self.__anchor_layout: self.__anchor_layout.deleteLater() self.__anchor_layout = None self.__anchor_layout = layout def anchor_layout(self): """ Return the anchor layout instance. """ return self.__anchor_layout def set_channel_names_visible(self, visible): """ Set the channel names visibility. """ self.__channel_names_visible = visible for link in self.__link_items: link.setChannelNamesVisible(visible) def channel_names_visible(self): """ Return the channel names visibility state. """ return self.__channel_names_visible def set_node_animation_enabled(self, enabled): """ Set node animation enabled state. """ if self.__node_animation_enabled != enabled: self.__node_animation_enabled = enabled for node in self.__node_items: node.setAnimationEnabled(enabled) def add_node_item(self, item): """ Add a :class:`.NodeItem` instance to the scene. """ if item in self.__node_items: raise ValueError("%r is already in the scene." % item) if item.pos().isNull(): if self.__node_items: pos = self.__node_items[-1].pos() + QPointF(150, 0) else: pos = QPointF(150, 150) item.setPos(pos) item.setFont(self.font()) # Set signal mappings self.activated_mapper.setMapping(item, item) item.activated.connect(self.activated_mapper.map) self.hovered_mapper.setMapping(item, item) item.hovered.connect(self.hovered_mapper.map) self.position_change_mapper.setMapping(item, item) item.positionChanged.connect(self.position_change_mapper.map) self.addItem(item) self.__node_items.append(item) self.node_item_added.emit(item) log.info("Added item '%s' to '%s'" % (item, self)) return item def add_node(self, node): """ Add and return a default constructed :class:`.NodeItem` for a :class:`SchemeNode` instance `node`. If the `node` is already in the scene do nothing and just return its item. """ if node in self.__item_for_node: # Already added return self.__item_for_node[node] item = self.new_node_item(node.description) if node.position: pos = QPointF(*node.position) item.setPos(pos) item.setTitle(node.title) item.setProcessingState(node.processing_state) item.setProgress(node.progress) for message in node.state_messages(): item.setStateMessage(message) item.setStatusMessage(node.status_message()) self.__item_for_node[node] = item node.position_changed.connect(self.__on_node_pos_changed) node.title_changed.connect(item.setTitle) node.progress_changed.connect(item.setProgress) node.processing_state_changed.connect(item.setProcessingState) node.state_message_changed.connect(item.setStateMessage) node.status_message_changed.connect(item.setStatusMessage) return self.add_node_item(item) def new_node_item(self, widget_desc, category_desc=None): """ Construct an new :class:`.NodeItem` from a `WidgetDescription`. Optionally also set `CategoryDescription`. """ item = items.NodeItem() item.setWidgetDescription(widget_desc) if category_desc is None and self.registry and widget_desc.category: category_desc = self.registry.category(widget_desc.category) if category_desc is None and self.registry is not None: try: category_desc = self.registry.category(widget_desc.category) except KeyError: pass if category_desc is not None: item.setWidgetCategory(category_desc) item.setAnimationEnabled(self.__node_animation_enabled) return item def remove_node_item(self, item): """ Remove `item` (:class:`.NodeItem`) from the scene. """ self.activated_mapper.removeMappings(item) self.hovered_mapper.removeMappings(item) self.position_change_mapper.removeMappings(item) item.hide() self.removeItem(item) self.__node_items.remove(item) self.node_item_removed.emit(item) log.info("Removed item '%s' from '%s'" % (item, self)) def remove_node(self, node): """ Remove the :class:`.NodeItem` instance that was previously constructed for a :class:`SchemeNode` `node` using the `add_node` method. """ item = self.__item_for_node.pop(node) node.position_changed.disconnect(self.__on_node_pos_changed) node.title_changed.disconnect(item.setTitle) node.progress_changed.disconnect(item.setProgress) node.processing_state_changed.disconnect(item.setProcessingState) node.state_message_changed.disconnect(item.setStateMessage) self.remove_node_item(item) def node_items(self): """ Return all :class:`.NodeItem` instances in the scene. """ return list(self.__node_items) def add_link_item(self, item): """ Add a link (:class:`.LinkItem`) to the scene. """ if item.scene() is not self: self.addItem(item) item.setFont(self.font()) self.__link_items.append(item) self.link_item_added.emit(item) log.info("Added link %r -> %r to '%s'" % \ (item.sourceItem.title(), item.sinkItem.title(), self)) self.__anchor_layout.invalidateLink(item) return item def add_link(self, scheme_link): """ Create and add a :class:`.LinkItem` instance for a :class:`SchemeLink` instance. If the link is already in the scene do nothing and just return its :class:`.LinkItem`. """ if scheme_link in self.__item_for_link: return self.__item_for_link[scheme_link] source = self.__item_for_node[scheme_link.source_node] sink = self.__item_for_node[scheme_link.sink_node] item = self.new_link_item(source, scheme_link.source_channel, sink, scheme_link.sink_channel) item.setEnabled(scheme_link.enabled) scheme_link.enabled_changed.connect(item.setEnabled) if scheme_link.is_dynamic(): item.setDynamic(True) item.setDynamicEnabled(scheme_link.dynamic_enabled) scheme_link.dynamic_enabled_changed.connect(item.setDynamicEnabled) self.add_link_item(item) self.__item_for_link[scheme_link] = item return item def new_link_item(self, source_item, source_channel, sink_item, sink_channel): """ Construct and return a new :class:`.LinkItem` """ item = items.LinkItem() item.setSourceItem(source_item) item.setSinkItem(sink_item) def channel_name(channel): if isinstance(channel, str): return channel else: return channel.name source_name = channel_name(source_channel) sink_name = channel_name(sink_channel) fmt = "<b>{0}</b> \u2192 <b>{1}</b>" item.setToolTip( fmt.format(escape(source_name), escape(sink_name)) ) item.setSourceName(source_name) item.setSinkName(sink_name) item.setChannelNamesVisible(self.__channel_names_visible) return item def remove_link_item(self, item): """ Remove a link (:class:`.LinkItem`) from the scene. """ # Invalidate the anchor layout. self.__anchor_layout.invalidateAnchorItem( item.sourceItem.outputAnchorItem ) self.__anchor_layout.invalidateAnchorItem( item.sinkItem.inputAnchorItem ) self.__link_items.remove(item) # Remove the anchor points. item.removeLink() self.removeItem(item) self.link_item_removed.emit(item) log.info("Removed link '%s' from '%s'" % (item, self)) return item def remove_link(self, scheme_link): """ Remove a :class:`.LinkItem` instance that was previously constructed for a :class:`SchemeLink` instance `link` using the `add_link` method. """ item = self.__item_for_link.pop(scheme_link) scheme_link.enabled_changed.disconnect(item.setEnabled) if scheme_link.is_dynamic(): scheme_link.dynamic_enabled_changed.disconnect( item.setDynamicEnabled ) self.remove_link_item(item) def link_items(self): """ Return all :class:`.LinkItem`\s in the scene. """ return list(self.__link_items) def add_annotation_item(self, annotation): """ Add an :class:`.Annotation` item to the scene. """ self.__annotation_items.append(annotation) self.addItem(annotation) self.annotation_added.emit(annotation) return annotation def add_annotation(self, scheme_annot): """ Create a new item for :class:`SchemeAnnotation` and add it to the scene. If the `scheme_annot` is already in the scene do nothing and just return its item. """ if scheme_annot in self.__item_for_annotation: # Already added return self.__item_for_annotation[scheme_annot] if isinstance(scheme_annot, scheme.SchemeTextAnnotation): item = items.TextAnnotation() item.setPlainText(scheme_annot.text) x, y, w, h = scheme_annot.rect item.setPos(x, y) item.resize(w, h) item.setTextInteractionFlags(Qt.TextEditorInteraction) font = font_from_dict(scheme_annot.font, item.font()) item.setFont(font) scheme_annot.text_changed.connect(item.setPlainText) elif isinstance(scheme_annot, scheme.SchemeArrowAnnotation): item = items.ArrowAnnotation() start, end = scheme_annot.start_pos, scheme_annot.end_pos item.setLine(QLineF(QPointF(*start), QPointF(*end))) item.setColor(QColor(scheme_annot.color)) scheme_annot.geometry_changed.connect( self.__on_scheme_annot_geometry_change ) self.add_annotation_item(item) self.__item_for_annotation[scheme_annot] = item return item def remove_annotation_item(self, annotation): """ Remove an :class:`.Annotation` instance from the scene. """ self.__annotation_items.remove(annotation) self.removeItem(annotation) self.annotation_removed.emit(annotation) def remove_annotation(self, scheme_annotation): """ Remove an :class:`.Annotation` instance that was previously added using :func:`add_anotation`. """ item = self.__item_for_annotation.pop(scheme_annotation) scheme_annotation.geometry_changed.disconnect( self.__on_scheme_annot_geometry_change ) if isinstance(scheme_annotation, scheme.SchemeTextAnnotation): scheme_annotation.text_changed.disconnect( item.setPlainText ) self.remove_annotation_item(item) def annotation_items(self): """ Return all :class:`.Annotation` items in the scene. """ return self.__annotation_items def item_for_annotation(self, scheme_annotation): return self.__item_for_annotation[scheme_annotation] def annotation_for_item(self, item): rev = dict(reversed(item) \ for item in self.__item_for_annotation.items()) return rev[item] def commit_scheme_node(self, node): """ Commit the `node` into the scheme. """ if not self.editable: raise Exception("Scheme not editable.") if node not in self.__item_for_node: raise ValueError("No 'NodeItem' for node.") item = self.__item_for_node[node] try: self.scheme.add_node(node) except Exception: log.error("An error occurred while committing node '%s'", node, exc_info=True) # Cleanup (remove the node item) self.remove_node_item(item) raise log.info("Commited node '%s' from '%s' to '%s'" % \ (node, self, self.scheme)) def commit_scheme_link(self, link): """ Commit a scheme link. """ if not self.editable: raise Exception("Scheme not editable") if link not in self.__item_for_link: raise ValueError("No 'LinkItem' for link.") self.scheme.add_link(link) log.info("Commited link '%s' from '%s' to '%s'" % \ (link, self, self.scheme)) def node_for_item(self, item): """ Return the `SchemeNode` for the `item`. """ rev = dict([(v, k) for k, v in self.__item_for_node.items()]) return rev[item] def item_for_node(self, node): """ Return the :class:`NodeItem` instance for a :class:`SchemeNode`. """ return self.__item_for_node[node] def link_for_item(self, item): """ Return the `SchemeLink for `item` (:class:`LinkItem`). """ rev = dict([(v, k) for k, v in self.__item_for_link.items()]) return rev[item] def item_for_link(self, link): """ Return the :class:`LinkItem` for a :class:`SchemeLink` """ return self.__item_for_link[link] def selected_node_items(self): """ Return the selected :class:`NodeItem`'s. """ return [item for item in self.__node_items if item.isSelected()] def selected_annotation_items(self): """ Return the selected :class:`Annotation`'s """ return [item for item in self.__annotation_items if item.isSelected()] def node_links(self, node_item): """ Return all links from the `node_item` (:class:`NodeItem`). """ return self.node_output_links(node_item) + \ self.node_input_links(node_item) def node_output_links(self, node_item): """ Return a list of all output links from `node_item`. """ return [link for link in self.__link_items if link.sourceItem == node_item] def node_input_links(self, node_item): """ Return a list of all input links for `node_item`. """ return [link for link in self.__link_items if link.sinkItem == node_item] def neighbor_nodes(self, node_item): """ Return a list of `node_item`'s (class:`NodeItem`) neighbor nodes. """ neighbors = list(map(attrgetter("sourceItem"), self.node_input_links(node_item))) neighbors.extend(map(attrgetter("sinkItem"), self.node_output_links(node_item))) return neighbors def on_widget_state_change(self, widget, state): pass def on_link_state_change(self, link, state): pass def on_scheme_change(self, ): pass def _on_position_change(self, item): # Invalidate the anchor point layout and schedule a layout. self.__anchor_layout.invalidateNode(item) self.node_item_position_changed.emit(item, item.pos()) def __on_node_pos_changed(self, pos): node = self.sender() item = self.__item_for_node[node] item.setPos(*pos) def __on_scheme_annot_geometry_change(self): annot = self.sender() item = self.__item_for_annotation[annot] if isinstance(annot, scheme.SchemeTextAnnotation): item.setGeometry(QRectF(*annot.rect)) elif isinstance(annot, scheme.SchemeArrowAnnotation): p1 = item.mapFromScene(QPointF(*annot.start_pos)) p2 = item.mapFromScene(QPointF(*annot.end_pos)) item.setLine(QLineF(p1, p2)) else: pass def item_at(self, pos, type_or_tuple=None, buttons=0): """Return the item at `pos` that is an instance of the specified type (`type_or_tuple`). If `buttons` (`Qt.MouseButtons`) is given only return the item if it is the top level item that would accept any of the buttons (`QGraphicsItem.acceptedMouseButtons`). """ rect = QRectF(pos, QSizeF(1, 1)) items = self.items(rect) if buttons: items = itertools.dropwhile( lambda item: not item.acceptedMouseButtons() & buttons, items ) items = list(items)[:1] if type_or_tuple: items = [i for i in items if isinstance(i, type_or_tuple)] return items[0] if items else None if PYQT_VERSION < 0x40900: # For QGraphicsObject subclasses items, itemAt ... return a # QGraphicsItem wrapper instance and not the actual class instance. def itemAt(self, *args, **kwargs): item = QGraphicsScene.itemAt(self, *args, **kwargs) return toGraphicsObjectIfPossible(item) def items(self, *args, **kwargs): items = QGraphicsScene.items(self, *args, **kwargs) return list(map(toGraphicsObjectIfPossible, items)) def selectedItems(self, *args, **kwargs): return list(map(toGraphicsObjectIfPossible, QGraphicsScene.selectedItems(self, *args, **kwargs))) def collidingItems(self, *args, **kwargs): return list(map(toGraphicsObjectIfPossible, QGraphicsScene.collidingItems(self, *args, **kwargs))) def focusItem(self, *args, **kwargs): item = QGraphicsScene.focusItem(self, *args, **kwargs) return toGraphicsObjectIfPossible(item) def mouseGrabberItem(self, *args, **kwargs): item = QGraphicsScene.mouseGrabberItem(self, *args, **kwargs) return toGraphicsObjectIfPossible(item) def mousePressEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.mousePressEvent(event): return # Right (context) click on the node item. If the widget is not # in the current selection then select the widget (only the widget). # Else simply return and let customContextMenuReqested signal # handle it shape_item = self.item_at(event.scenePos(), items.NodeItem) if shape_item and event.button() == Qt.RightButton and \ shape_item.flags() & QGraphicsItem.ItemIsSelectable: if not shape_item.isSelected(): self.clearSelection() shape_item.setSelected(True) return QGraphicsScene.mousePressEvent(self, event) def mouseMoveEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.mouseMoveEvent(event): return return QGraphicsScene.mouseMoveEvent(self, event) def mouseReleaseEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.mouseReleaseEvent(event): return return QGraphicsScene.mouseReleaseEvent(self, event) def mouseDoubleClickEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.mouseDoubleClickEvent(event): return return QGraphicsScene.mouseDoubleClickEvent(self, event) def keyPressEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.keyPressEvent(event): return return QGraphicsScene.keyPressEvent(self, event) def keyReleaseEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.keyReleaseEvent(event): return return QGraphicsScene.keyReleaseEvent(self, event) def set_user_interaction_handler(self, handler): if self.user_interaction_handler and \ not self.user_interaction_handler.isFinished(): self.user_interaction_handler.cancel() log.info("Setting interaction '%s' to '%s'" % (handler, self)) self.user_interaction_handler = handler if handler: handler.start() def event(self, event): # TODO: change the base class of Node/LinkItem to QGraphicsWidget. # It already handles font changes. if event.type() == QEvent.FontChange: self.__update_font() return QGraphicsScene.event(self, event) def __update_font(self): font = self.font() for item in self.__node_items + self.__link_items: item.setFont(font) def __str__(self): return "%s(objectName=%r, ...)" % \ (type(self).__name__, str(self.objectName()))
class MainApp(QDockWidget, QMainWindow, Ui_MainApp): # signals goBack = pyqtSignal() searchOpsubByName = pyqtSignal(str) enableSearch = pyqtSignal(bool) refreshLegend = pyqtSignal(QgsMapLayer) ogrDatasourceLoaded = pyqtSignal(bool) class VfkLayer(object): Par = 0 Bud = 1 def __init__(self, iface): QDockWidget.__init__(self, iface.mainWindow()) self.setupUi(self) self.iface = iface # variables self.__mLastVfkFile = [] self.__mOgrDataSource = None self.__mDataSourceName = '' self.__fileName = [] self.__mLoadedLayers = {} self.__mDefaultPalette = self.vfkFileLineEdit.palette() # new lineEdits variables self.lineEditsCount = 1 self.__browseButtons = {} self.__vfkLineEdits = {} # data will be load from source according to checked radiobox self.__source_for_data = 'file' # apply changes into main database self.__databases = {} # self.pb_applyChanges.setEnabled(False) self.changes_instance = ApplyChanges() # Connect ui with functions self.__createToolbarsAndConnect() # check GDAL version self.__gdal_version = int(gdal.VersionInfo()) if self.__gdal_version < 2020000: self.actionZpracujZmeny.setEnabled(False) self.pb_nextFile.setEnabled(False) self.pb_nextFile.setToolTip( u'Není možné načíst více souborů, verze GDAL je nižší než 2.2.0.') self.actionZpracujZmeny.setToolTip(u'Zpracování změn není povoleno, verze GDAL je nižší než 2.2.0.') self.groupBox.setEnabled(False) self.loadVfkButton.setDisabled(True) self.searchFormMainControls = SearchFormController.MainControls() self.searchFormMainControls.formCombobox = self.searchCombo self.searchFormMainControls.searchForms = self.searchForms self.searchFormMainControls.searchButton = self.searchButton self.searchForms = SearchFormController.SearchForms() self.searchForms.vlastnici = self.vlastniciSearchForm self.searchForms.parcely = self.parcelySearchForm self.searchForms.budovy = self.budovySearchForm self.searchForms.jednotky = self.jednotkySearchForm # search form controller self.__mSearchController = SearchFormController( self.searchFormMainControls, self.searchForms, self) self.connect(self.__mSearchController, SIGNAL( "actionTriggered(QUrl)"), self.vfkBrowser.processAction) self.connect( self, SIGNAL("enableSearch"), self.searchButton.setEnabled) self.connect(self.vfkBrowser, SIGNAL("showParcely"), self.showParInMap) self.connect(self.vfkBrowser, SIGNAL("showBudovy"), self.showBudInMap) # connect lineEdits and returnPressed action self.connect(self.vfkFileLineEdit, SIGNAL( "returnPressed()"), self.loadVfkButton_clicked) self.connect(self.vlastniciSearchForm.ui.jmenoLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.vlastniciSearchForm.ui.rcIcoLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.vlastniciSearchForm.ui.lvVlastniciLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.parcelySearchForm.ui.parCisloLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.parcelySearchForm.ui.lvParcelyLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.budovySearchForm.ui.cisloDomovniLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.budovySearchForm.ui.naParceleLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.budovySearchForm.ui.lvBudovyLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.jednotkySearchForm.ui.mCisloJednotkyLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.jednotkySearchForm.ui.mCisloDomovniLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.jednotkySearchForm.ui.mNaParceleLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.jednotkySearchForm.ui.mLvJednotkyLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.vfkBrowser.showHelpPage() self.settings = QSettings("CTU", "VFK plugin") self.full_data = True def browseButton_clicked(self, browseButton_id=1): """ :param browseButton_id: ID of clicked browse button. :return: """ title = u'Načti soubor VFK' sender = u'{}-lastUserFilePath'.format(self.sender().objectName()) lastUsedDir = self.settings.value(sender, '') if self.__source_for_data == 'file': ext = '*.vfk' if self.__gdal_version >= 2020000: ext += ' *.db' loaded_file = QFileDialog.getOpenFileName( self, title, lastUsedDir, u'Soubory podporované ovladačem VFK GDAL ({})'.format(ext)) if not loaded_file: return else: self.__fileName.append(loaded_file) if browseButton_id == 1: self.vfkFileLineEdit.setText(self.__fileName[0]) #TODO:vyresit nacitani vice souboru else: self.__vfkLineEdits['vfkLineEdit_{}'.format(len(self.__vfkLineEdits))].setText( self.__fileName[browseButton_id - 1]) self.settings.setValue(sender, os.path.dirname(loaded_file)) elif self.__source_for_data == 'directory': loaded_file = QFileDialog.getExistingDirectory(self, u"Vyberte adresář s daty VFK") if not loaded_file: return else: self.__fileName = [] self.__fileName.append(loaded_file) self.vfkFileLineEdit.setText(self.__fileName[0]) else: qDebug('(VFK) Not valid source') self.loadVfkButton.setEnabled(True) def browserGoBack(self): self.vfkBrowser.goBack() def browserGoForward(self): self.vfkBrowser.goForth() def selectParInMap(self): self.showInMap(self.vfkBrowser.currentParIds(), "PAR") def selectBudInMap(self): self.showInMap(self.vfkBrowser.currentBudIds(), "BUD") def latexExport(self): fileName = QFileDialog.getSaveFileName( self, u"Jméno exportovaného souboru", ".tex", "LaTeX (*.tex)") if fileName: export_succesfull = self.vfkBrowser.exportDocument( self.vfkBrowser.currentUrl(), fileName, self.vfkBrowser.ExportFormat.Latex) if export_succesfull: self.succesfullExport("LaTeX") def htmlExport(self): fileName = QFileDialog.getSaveFileName( self, u"Jméno exportovaného souboru", ".html", "HTML (*.html)") if fileName: export_succesfull = self.vfkBrowser.exportDocument( self.vfkBrowser.currentUrl(), fileName, self.vfkBrowser.ExportFormat.Html) if export_succesfull: self.succesfullExport("HTML") def setSelectionChangedConnected(self, connected): """ :type connected: bool :return: """ for layer in self.__mLoadedLayers: id = self.__mLoadedLayers[layer] vectorLayer = QgsMapLayerRegistry.instance().mapLayer(id) if connected: self.connect( vectorLayer, SIGNAL("selectionChanged()"), self.showInfoAboutSelection) else: self.disconnect( vectorLayer, SIGNAL("selectionChanged()"), self.showInfoAboutSelection) def showInMap(self, ids, layerName): """ :type ids: list :type layerName: str :return: """ if layerName in self.__mLoadedLayers: id = self.__mLoadedLayers[layerName] vectorLayer = QgsMapLayerRegistry.instance().mapLayer(id) searchString = "ID IN ({})".format(", ".join(ids)) error = '' fIds = self.__search(vectorLayer, searchString, error) if error: qDebug('\n (VFK) ERROR in showInMap: {}'.format(error)) return else: vectorLayer.setSelectedFeatures(fIds) def __search(self, layer, searchString, error): """ :type layer: QgsVectorLayer :type searchString: str :type error: str :return: """ # parse search string and build parsed tree search = QgsExpression(searchString) rect = QgsRectangle() fIds = [] if search.hasParserError(): error += "Parsing error:" + search.parserErrorString() return fIds if not search.prepare(layer.pendingFields()): error + "Evaluation error:" + search.evalErrorString() layer.select(rect, False) fit = QgsFeatureIterator(layer.getFeatures()) f = QgsFeature() while fit.nextFeature(f): if search.evaluate(f): fIds.append(f.id()) # check if there were errors during evaluating if search.hasEvalError(): qDebug('\n (VFK) Evaluate error: {}'.format(error)) break return fIds def loadVfkButton_clicked(self): """ After click method starts loading all inserted files """ # check the source of data if self.__source_for_data == 'directory': dir_path = self.__fileName[0] self.__fileName = self.__findVFKFilesInDirectory(dir_path) # check if first file is amendment amendment_file = self.__checkIfAmendmentFile(self.__fileName[0]) # prepare name for database if amendment_file: new_database_name = '{}_zmeny.db'.format(os.path.basename(self.__fileName[0]).split('.')[0]) else: new_database_name = '{}_stav.db'.format(os.path.basename(self.__fileName[0]).split('.')[0]) os.environ['OGR_VFK_DB_NAME'] = os.path.normpath(os.path.join( os.path.dirname(os.path.dirname(self.__fileName[0])), new_database_name)) gdal.SetConfigOption('OGR_VFK_DB_NAME', str(os.environ['OGR_VFK_DB_NAME'])) self.__mDataSourceName = self.__fileName[0] # os.environ['OGR_VFK_DB_NAME'] QgsApplication.processEvents() self.importThread = OpenThread(self.__fileName) self.importThread.working.connect(self.runLoadingLayer) if not self.importThread.isRunning(): self.importThread.start() def runLoadingLayer(self, fileName): """ :return: """ if fileName not in self.__mLastVfkFile: self.labelLoading.setText( u'Načítám data do SQLite databáze (může nějaký čas trvat...)') try: self.loadVfkFile(fileName) except VFKError as e: QMessageBox.critical( self, u'Chyba', u'{}'.format(e), QMessageBox.Ok) self.emit(SIGNAL("enableSearch"), False) return self.__mLastVfkFile.append(fileName) self.importThread.nextLayer = False if fileName == self.__fileName[-1]: self.loadingLayersFinished() def loadingLayersFinished(self): """ :return: """ try: self.__openDatabase( os.environ['OGR_VFK_DB_NAME']) # self.__mDataSourceName) except VFKError as e: QMessageBox.critical( self, u'Chyba', u'{}'.format(e), QMessageBox.Ok) self.emit(SIGNAL("enableSearch"), False) return self.vfkBrowser.setConnectionName(self.property("connectionName")) self.__mSearchController.setConnectionName( self.property("connectionName")) self.emit(SIGNAL("enableSearch"), True) self.__mLoadedLayers.clear() if self.parCheckBox.isChecked(): self.__loadVfkLayer('PAR') else: self.__unLoadVfkLayer('PAR') if self.budCheckBox.isChecked(): self.__loadVfkLayer('BUD') else: self.__unLoadVfkLayer('BUD') self.labelLoading.setText(u'Načítání souborů VFK bylo dokončeno.') def vfkFileLineEdit_textChanged(self, arg1): """ :type arg1: str :return: """ info = QFileInfo(arg1) if info.isFile(): self.loadVfkButton.setEnabled(True) self.vfkFileLineEdit.setPalette(self.__mDefaultPalette) else: self.loadVfkButton.setEnabled(False) pal = QPalette(self.vfkFileLineEdit.palette()) pal.setColor(QPalette.text(), Qt.red) self.vfkFileLineEdit.setPalette(pal) def __loadVfkLayer(self, vfkLayerName): """ :type vfkLayerName: str :return: """ qDebug("\n(VFK) Loading vfk layer {}".format(vfkLayerName)) if vfkLayerName in self.__mLoadedLayers: qDebug( "\n(VFK) Vfk layer {} is already loaded".format(vfkLayerName)) return composedURI = self.__mDataSourceName + "|layername=" + vfkLayerName layer = QgsVectorLayer(composedURI, vfkLayerName, "ogr") if not layer.isValid(): qDebug("\n(VFK) Layer failed to load!") self.__mLoadedLayers[vfkLayerName] = layer.id() try: self.__setSymbology(layer) except VFKWarning as e: QMessageBox.information(self, 'Load Style', '{}'.format(e), QMessageBox.Ok) QgsMapLayerRegistry.instance().addMapLayer(layer) def __unLoadVfkLayer(self, vfkLayerName): """ :type vfkLayerName: str :return: """ qDebug("\n(VFK) Unloading vfk layer {}".format(vfkLayerName)) if vfkLayerName not in self.__mLoadedLayers: qDebug( "\n(VFK) Vfk layer {} is already unloaded".format(vfkLayerName)) return QgsMapLayerRegistry.instance().removeMapLayer( self.__mLoadedLayers[vfkLayerName]) del self.__mLoadedLayers[vfkLayerName] def __setSymbology(self, layer): """ :type layer: QgsVectorLayer :return: """ name = layer.name() symbologyFile = '' if name == 'PAR': symbologyFile = ':/parStyle.qml' elif name == 'BUD': symbologyFile = ':/budStyle.qml' errorMsg, resultFlag = layer.loadNamedStyle(symbologyFile) if not resultFlag: raise VFKWarning(u'Load style: {}'.format(errorMsg)) layer.triggerRepaint() self.emit(SIGNAL("refreshLegend"), layer) def __openDatabase(self, dbPath): """ :type dbPath: str :return: """ qDebug("\n(VFK) Open DB: {}".format(dbPath)) if not QSqlDatabase.isDriverAvailable('QSQLITE'): raise VFKError(u'Databázový ovladač QSQLITE není dostupný.') connectionName = QUuid.createUuid().toString() db = QSqlDatabase.addDatabase("QSQLITE", connectionName) db.setDatabaseName(dbPath) if not db.open(): raise VFKError(u'Nepodařilo se otevřít databázi. ') self.setProperty("connectionName", connectionName) def loadVfkFile(self, fileName): """ :type fileName: str :return: """ label_text = fileName.split('/') label_text = '...' + label_text[-2] + '/' + label_text[-1] # overwrite database if fileName == self.__fileName[0]: if self.overwriteCheckBox.isChecked(): qDebug('\n (VFK) Database will be overwritten') os.environ['OGR_VFK_DB_OVERWRITE'] = '1' if self.__mOgrDataSource: self.__mOgrDataSource.Destroy() self.__mOgrDataSource = None QgsApplication.registerOgrDrivers() self.progressBar.setRange(0, 1) self.progressBar.setValue(0) QgsApplication.processEvents() #os.environ['OGR_VFK_DB_READ_ALL_BLOCKS'] = 'NO' self.labelLoading.setText( u'Načítám soubor {} (může nějaký čas trvat...)'.format(label_text)) QgsApplication.processEvents() self.__mOgrDataSource = ogr.Open( fileName, 0) # 0 - datasource is open in read-only mode if not self.__mOgrDataSource: raise VFKError( u"Nelze otevřít VFK soubor '{}' jako platný OGR datasource.".format(fileName)) t_par = self.__mOgrDataSource.GetLayerByName('PAR') if t_par == None: self.__mOgrDataSource = None # Build Parcels parcels = VFKParBuilder(fileName) parcels.build_all_par() # Build Buildings buildings = VFKBudBuilder(fileName) buildings.build_all_bud() self.labelLoading.setText( u'Data byla načtena pomocí rozšíření, které neobsažené bloky PAR a BUD sestavilo z neúplného VFK souboru.') self.__mOgrDataSource = ogr.Open(os.environ['OGR_VFK_DB_NAME'], 0) self.__mDataSourceName = os.environ['OGR_VFK_DB_NAME'] #What kind of data were loaded? full/incomplete if not self.__mOgrDataSource.GetLayerByName('JED'): self.full_data = False if self.full_data is False: self.actionBack.setEnabled(False) self.actionBack.setToolTip(u'Zpět-nefunkční pro neúplná data') self.actionZpracujZmeny.setEnabled(False) self.actionZpracujZmeny.setToolTip(u'Zpracování změn pro neúplná data není možné') self.actionForward.setEnabled(False) self.actionForward.setToolTip(u'Vpřed-nefunkční pro neúplná data') self.actionSelectBudInMap.setEnabled(False) self.actionSelectBudInMap.setToolTip(u'Výběr budov v mapě není možný u neúplných dat') self.actionCuzkPage.setEnabled(False) self.actionCuzkPage.setToolTip(u'V neúplných datech nelze nahlížet do katastru nemovitostí') self.actionShowInfoaboutSelection.setEnabled(False) self.actionShowInfoaboutSelection.setToolTip(u'Informace o výběru pro neúplná data nefungují') self.actionShowHelpPage.setEnabled(False) self.actionShowHelpPage.setToolTip(u'Nápověda není k dispozici pro neúplná data') self.actionExportLatex.setEnabled(False) self.actionExportHtml.setEnabled(False) layers_names = [] layerCount = self.__mOgrDataSource.GetLayerCount() for i in xrange(layerCount): layers_names.append( self.__mOgrDataSource.GetLayer(i).GetLayerDefn().GetName()) if ('PAR' not in layers_names or 'BUD' not in layers_names) and len(self.__vfkLineEdits) == 1: self.__dataWithoutParBud() self.labelLoading.setText( u'Data nemohla být načtena. Vstupní soubor neobsahuje bloky PAR a BUD.') QgsApplication.processEvents() return # load all layers self.progressBar.setRange(0, layerCount - 1) for i in xrange(layerCount): self.progressBar.setValue(i) theLayerName = self.__mOgrDataSource.GetLayer( i).GetLayerDefn().GetName() self.labelLoading.setText( u"VFK data {}/{}: {}".format(i + 1, layerCount, theLayerName)) QgsApplication.processEvents() self.__mOgrDataSource.GetLayer(i).GetFeatureCount(True) time.sleep(0.02) self.labelLoading.setText( u'Soubor {} úspěšně načten.'.format(label_text)) os.environ['OGR_VFK_DB_OVERWRITE'] = '0' self.__mOgrDataSource.Destroy() self.__mOgrDataSource = None def __selectedIds(self, layer): """ :type layer: QgsVectorLayer :return: """ ids = [] flist = layer.selectedFeatures() for it in flist: f = QgsFeature(it) ids.append(str(f.attribute("ID"))) return ids def showInfoAboutSelection(self): layers = ["PAR", "BUD"] layerIds = {} for layer in layers: if layer in self.__mLoadedLayers: id = str(self.__mLoadedLayers[layer]) vectorLayer = QgsMapLayerRegistry.instance().mapLayer(id) layerIds[layer] = self.__selectedIds(vectorLayer) self.vfkBrowser.showInfoAboutSelection( layerIds["PAR"], layerIds["BUD"]) def showParInMap(self, ids): """ :type ids: list :return: """ if self.actionShowInfoaboutSelection.isChecked(): self.setSelectionChangedConnected(False) self.showInMap(ids, "PAR") self.setSelectionChangedConnected(True) else: self.showInMap(ids, "PAR") def showBudInMap(self, ids): """ :type ids: list :return: """ if self.actionShowInfoaboutSelection.isChecked(): self.setSelectionChangedConnected(False) self.showInMap(ids, "BUD") self.setSelectionChangedConnected(True) else: self.showInMap(ids, "BUD") def showOnCuzk(self): x = self.vfkBrowser.currentDefinitionPoint().first.split(".")[0] y = self.vfkBrowser.currentDefinitionPoint().second.split(".")[0] url = "http://nahlizenidokn.cuzk.cz/MapaIdentifikace.aspx?&x=-{}&y=-{}".format( y, x) QDesktopServices.openUrl(QUrl(url, QUrl.TolerantMode)) def switchToImport(self): self.actionImport.trigger() def switchToSearch(self, searchType): """ :type searchType: int """ self.actionVyhledavani.trigger() self.searchCombo.setCurrentIndex(searchType) self.searchFormMainControls.searchForms.setCurrentIndex(searchType) def switchToChanges(self): """ """ self.actionZpracujZmeny.trigger() def succesfullExport(self, export_format): """ :type export_format: str :return: """ QMessageBox.information( self, u'Export', u"Export do formátu {} proběhl úspěšně.".format( export_format), QMessageBox.Ok) def __dataWithoutParBud(self): """ :type export_format: str :return: """ QMessageBox.warning(self, u'Upozornění', u"Zvolený VFK soubor neobsahuje vrstvy s geometrií (PAR, BUD), proto nemohou " u"být pomocí VFK Pluginu načtena. Data je možné načíst v QGIS pomocí volby " u"'Načíst vektorovou vrstvu.'", QMessageBox.Ok) def __addRowToGridLayout(self): if len(self.__vfkLineEdits) >= 5: self.__maximumLineEditsReached() return # update label self.label.setText('VFK soubory:') # new layout horizontalLayout = QtGui.QHBoxLayout() # create new objects self.__browseButtons['browseButton_{}'.format( len(self.__vfkLineEdits) + 1)] = QtGui.QPushButton(u"Procházet") self.__vfkLineEdits['vfkLineEdit_{}'.format( len(self.__vfkLineEdits) + 1)] = QtGui.QLineEdit() horizontalLayout.addWidget(self.__vfkLineEdits[ 'vfkLineEdit_{}'.format(len(self.__vfkLineEdits))]) horizontalLayout.addWidget(self.__browseButtons[ 'browseButton_{}'.format(len(self.__vfkLineEdits))]) # number of lines in gridLayout rows_count = self.gridLayout_12.rowCount( ) # count of rows in gridLayout # export objects from gridLayout item_label = self.gridLayout_12.itemAtPosition(rows_count - 3, 0) item_par = self.gridLayout_12.itemAtPosition(rows_count - 3, 1) item_bud = self.gridLayout_12.itemAtPosition(rows_count - 2, 1) item_settings = self.gridLayout_12.itemAtPosition(rows_count - 1, 0) item_rewrite_db = self.gridLayout_12.itemAtPosition(rows_count - 1, 1) # remove objects from gridLayout self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 3, 0)) self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 3, 1)) self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 2, 1)) self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 1, 0)) self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 1, 1)) # re-build gridLayout self.gridLayout_12.addLayout(horizontalLayout, rows_count - 3, 1) self.gridLayout_12.addItem(item_label, rows_count - 2, 0) self.gridLayout_12.addItem(item_par, rows_count - 2, 1) self.gridLayout_12.addItem(item_bud, rows_count - 1, 1) self.gridLayout_12.addItem(item_settings, rows_count, 0) self.gridLayout_12.addItem(item_rewrite_db, rows_count, 1) self.__browseButtons['browseButton_{}'.format(len(self.__vfkLineEdits))].clicked.\ connect(lambda: self.browseButton_clicked( int('{}'.format(len(self.__vfkLineEdits))))) def __maximumLineEditsReached(self): QMessageBox.information(self, u'Upozornění', u"Byl dosažen maximální počet ({}) VFK souboru pro zpracování." u"\nNačítání dalších souborů není povoleno!". format(self.lineEditsCount), QMessageBox.Ok) def browseDb_clicked(self, database_type): """ Method run dialog for select database in widget with changes. According to pushButton name will fill in relevant lineEdit. :type database_type: str """ title = u'Vyber databázi' settings = QSettings() lastUsedDir = str(settings.value('/UI/' + "lastVectorFileFilter" + "Dir", ".")) if database_type == 'mainDb': self.__databases[database_type] = QFileDialog.getOpenFileName(self, title, lastUsedDir, u'Datábaze (*.db)') if not self.__databases[database_type]: return self.le_mainDb.setText(self.__databases[database_type]) elif database_type == 'amendmentDb': self.__databases[database_type] = QFileDialog.getOpenFileName(self, title, lastUsedDir, u'Datábaze (*.db)') if not self.__databases[database_type]: return self.le_amendmentDb.setText(self.__databases[database_type]) elif database_type == 'exportDb': title = u'Zadej jméno výstupní databáze' self.__databases[database_type] = QFileDialog.getSaveFileName(self, u"Jméno výstupní databáze", ".db", u"Databáze (*.db)") if not self.__databases[database_type]: return self.le_exportDb.setText(self.__databases[database_type]) if len(self.__databases) == 3: self.pb_applyChanges.setEnabled(True) def applyChanges(self): """ Method :return: """ self.changes_instance.run(self.__databases['mainDb'], self.__databases['amendmentDb'], self.__databases['exportDb']) def __updateProgressBarChanges(self, iteration, table_name): """ :type iteration: int :type table_name: str """ self.progressBar_changes.setValue(iteration) self.l_status.setText(u'Aplikuji změny na tabulku {}...'.format(table_name)) QgsApplication.processEvents() def __setRangeProgressBarChanges(self, max_range): """ :type max_range: int """ self.progressBar_changes.setRange(0, max_range) self.progressBar_changes.setValue(0) def __changesApplied(self): """ """ time.sleep(1) self.l_status.setText(u'Změny byly úspěšně aplikovány.') QgsApplication.processEvents() def __changesPreprocessingDatabase(self): """ """ self.l_status.setText(u'Připravuji výstupní databázi...') QgsApplication.processEvents() def __checkIfAmendmentFile(self, file_name): """ :param file_name: Name of the input file :type file_name: str :return: bool """ if file_name.endswith(".vfk"): with open(file_name, 'r') as f: for line in f: line_splited = str(line).split(';') if line_splited[0] == '&HZMENY': if line_splited[1] == '1': return True else: return False else: print 'database' # TODO: dopsat kontrolu, zda se jedna o stavovou, nebo zmenovou databazi def radioButtonValue(self): """ Check which radio button is checked """ self.vfkFileLineEdit.setText('') self.__fileName = [] self.loadVfkButton.setEnabled(False) if self.rb_file.isChecked(): self.__source_for_data = 'file' self.pb_nextFile.show() self.label.setText('VFK soubor:') elif self.rb_directory.isChecked(): self.__source_for_data = 'directory' self.pb_nextFile.hide() self.label.setText(u'Adresář:') # delete if len(self.__browseButtons) > 1: for i, button in enumerate(self.__browseButtons): if i > 0: self.__browseButtons[button].hide() if len(self.__vfkLineEdits) > 1: for i, le in enumerate(self.__vfkLineEdits): if i > 0: self.__vfkLineEdits[le].hide() def __findVFKFilesInDirectory(self, dir_path): """ Finds all files with extension '.vfk' in given directory including subdirectories :param dir_path: Path to directory. :type dir_path: str :return: List of VFK files """ file_paths = [] for root, dirs, files in os.walk(dir_path): for file in files: if file.endswith(".db"): #if file.endswith(".vfk"): file_paths.append(os.path.join(root, file)) return file_paths def __createToolbarsAndConnect(self): actionGroup = QActionGroup(self) actionGroup.addAction(self.actionImport) actionGroup.addAction(self.actionVyhledavani) actionGroup.addAction(self.actionZpracujZmeny) # QSignalMapper self.signalMapper = QSignalMapper(self) # connect to 'clicked' on all buttons self.connect(self.actionImport, SIGNAL( "triggered()"), self.signalMapper, SLOT("map()")) self.connect(self.actionVyhledavani, SIGNAL( "triggered()"), self.signalMapper, SLOT("map()")) self.connect(self.actionZpracujZmeny, SIGNAL( "triggered()"), self.signalMapper, SLOT("map()")) # setMapping on each button to the QStackedWidget index we'd like to # switch to self.signalMapper.setMapping(self.actionImport, 0) self.signalMapper.setMapping(self.actionVyhledavani, 2) self.signalMapper.setMapping(self.actionZpracujZmeny, 1) # connect mapper to stackedWidget self.connect(self.signalMapper, SIGNAL("mapped(int)"), self.stackedWidget, SLOT("setCurrentIndex(int)")) self.actionImport.trigger() self.connect(self.vfkBrowser, SIGNAL( "switchToPanelImport"), self.switchToImport) self.connect(self.vfkBrowser, SIGNAL( "switchToPanelSearch"), self.switchToSearch) self.connect(self.vfkBrowser, SIGNAL( "switchToPanelChanges"), self.switchToChanges) # Browser toolbar # --------------- self.__mBrowserToolbar = QToolBar(self) self.connect(self.actionBack, SIGNAL( "triggered()"), self.vfkBrowser.goBack) self.connect(self.actionForward, SIGNAL( "triggered()"), self.vfkBrowser.goForth) self.connect(self.actionSelectBudInMap, SIGNAL("triggered()"), self.selectBudInMap) self.connect(self.actionSelectParInMap, SIGNAL("triggered()"), self.selectParInMap) self.connect(self.actionCuzkPage, SIGNAL("triggered()"), self.showOnCuzk) self.connect(self.actionExportLatex, SIGNAL("triggered()"), self.latexExport) self.connect(self.actionExportHtml, SIGNAL("triggered()"), self.htmlExport) self.connect(self.actionShowInfoaboutSelection, SIGNAL( "toggled(bool)"), self.setSelectionChangedConnected) self.connect(self.actionShowHelpPage, SIGNAL( "triggered()"), self.vfkBrowser.showHelpPage) self.loadVfkButton.clicked.connect(self.loadVfkButton_clicked) self.__browseButtons['browseButton_1'] = self.browseButton self.__browseButtons['browseButton_1'].clicked.connect( lambda: self.browseButton_clicked(int('{}'.format(len(self.__vfkLineEdits))))) self.__vfkLineEdits['vfkLineEdit_1'] = self.vfkFileLineEdit bt = QToolButton(self.__mBrowserToolbar) bt.setPopupMode(QToolButton.InstantPopup) bt.setText("Export ") menu = QMenu(bt) menu.addAction(self.actionExportLatex) menu.addAction(self.actionExportHtml) bt.setMenu(menu) # add actions to toolbar icons self.__mBrowserToolbar.addAction(self.actionImport) self.__mBrowserToolbar.addAction(self.actionVyhledavani) self.__mBrowserToolbar.addAction(self.actionZpracujZmeny) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addAction(self.actionBack) self.__mBrowserToolbar.addAction(self.actionForward) self.__mBrowserToolbar.addAction(self.actionSelectParInMap) self.__mBrowserToolbar.addAction(self.actionSelectBudInMap) self.__mBrowserToolbar.addAction(self.actionCuzkPage) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addAction(self.actionShowInfoaboutSelection) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addWidget(bt) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addAction(self.actionShowHelpPage) self.rightWidgetLayout.insertWidget(0, self.__mBrowserToolbar) # connect signals from vfkbrowser when changing history self.connect(self.vfkBrowser, SIGNAL( "currentParIdsChanged"), self.actionSelectParInMap.setEnabled) self.connect(self.vfkBrowser, SIGNAL("currentBudIdsChanged"), self.actionSelectBudInMap.setEnabled) self.connect(self.vfkBrowser, SIGNAL( "historyBefore"), self.actionBack.setEnabled) self.connect(self.vfkBrowser, SIGNAL( "historyAfter"), self.actionForward.setEnabled) self.connect(self.vfkBrowser, SIGNAL( "definitionPointAvailable"), self.actionCuzkPage.setEnabled) # add toolTips self.pb_nextFile.setToolTip(u'Přidej další soubor VFK') self.parCheckBox.setToolTip(u'Načti vrstvu parcel') self.budCheckBox.setToolTip(u'Načti vrstvu budov') # add new VFK file self.pb_nextFile.clicked.connect(self.__addRowToGridLayout) # widget apply changes self.pb_mainDb.clicked.connect( lambda: self.browseDb_clicked('mainDb')) self.pb_amendmentDb.clicked.connect( lambda: self.browseDb_clicked('amendmentDb')) self.pb_exportDb.clicked.connect( lambda: self.browseDb_clicked('exportDb')) self.pb_applyChanges.clicked.connect(self.applyChanges) self.pb_applyChanges.setEnabled(False) self.connect(self.changes_instance, SIGNAL("maxRangeProgressBar"), self.__setRangeProgressBarChanges) self.connect(self.changes_instance, SIGNAL("updateStatus"), self.__updateProgressBarChanges) self.connect(self.changes_instance, SIGNAL("finishedStatus"), self.__changesApplied) self.connect(self.changes_instance, SIGNAL("preprocessingDatabase"), self.__changesPreprocessingDatabase) # connect radio boxes self.rb_file.clicked.connect(self.radioButtonValue) self.rb_directory.clicked.connect(self.radioButtonValue)
class MainWindow(QMainWindow, Ui_MainWindow): """Where all the action happens.""" def __init__(self, settings): super(MainWindow, self).__init__() self.settings = settings self.setupUi(self) # MainWindow is a collection of widgets in their respective docks. # We make DockArea our central widget self.dock_area = DockArea() self.setCentralWidget(self.dock_area) self.createDocks() self.initAfterCreatingDockWidgets() self.loadSettings() self.connectSignalsToSlots() # all signals in place, send out the first image # self.image_browser.populateAndEmitImageInfo() self.image_browser.initialEmit() self.roi_editor_h.initialEmit() self.roi_editor_v.initialEmit() self.roi_editor_int.initialEmit() self.roi_editor_err.initialEmit() self.loadPlugins() def loadPlugins(self): """Looks for all plugins and creates menu entries, signals and slots for them.""" self.pluginSignalMapper = QSignalMapper(self) for p in plugin_list: click_action = QAction(p.name, self) self.menuPlugins.addAction(click_action) self.connect(click_action, SIGNAL("triggered()"), self.pluginSignalMapper, SLOT("map()")) self.pluginSignalMapper.setMapping(click_action, QString(p.name)) self.connect(self.pluginSignalMapper, SIGNAL("mapped(const QString &)"), self.image_browser.handlePluginClicked) def createDocks(self): """Create all dock widgets and add them to DockArea.""" self.image_view = ImageView(self.settings, self) self.image_browser = ImageBrowser(self.settings, self) self.fitter = Fitter(self.settings, self) self.roi_editor_h = RoiEditor(self.settings, self.image_view, self, name='ROIH', pen=(1, 9), axis=1) self.roi_editor_v = RoiEditor(self.settings, self.image_view, self, name='ROIV', pen=(1, 1), axis=0) self.roi_editor_int = RoiEditor(self.settings, self.image_view, self, name='ROI Int', pen=(1, 2), axis=1) self.roi_editor_err = RoiEditor(self.settings, self.image_view, self, name='ROI Err', pen=(1, 3), axis=1) self.roi_plot_h = Plot1d(parent=self, title='ROI H') self.roi_plot_v = Plot1d(parent=self, title='ROI V') self.analyzer = Analyzer(self.settings, parent=self) # Create docks for all widgets self.dock_image_view = Dock('Image View', widget=self.image_view) self.dock_image_browser = Dock('Image Browser', widget=self.image_browser) self.dock_fitter = Dock('Fitter', widget=self.fitter) self.dock_roi_h = Dock('ROIH', widget=self.roi_editor_h) self.dock_roi_v = Dock('ROIV', widget=self.roi_editor_v) self.dock_roi_int = Dock('ROI Int', widget=self.roi_editor_int) self.dock_roi_err = Dock('ROI Err', widget=self.roi_editor_err) self.dock_roi_plot_h = Dock('ROIH Plot', widget=self.roi_plot_h) self.dock_roi_plot_v = Dock('ROIV Plot', widget=self.roi_plot_v) self.dock_analyzer = Dock('Analyze', widget=self.analyzer) self.dock_area.addDock(self.dock_image_view, position='top') self.dock_area.addDock(self.dock_image_browser, position='right', relativeTo=self.dock_image_view) self.dock_area.addDock(self.dock_fitter, position='left', relativeTo=self.dock_image_view) self.dock_area.addDock(self.dock_roi_h, position='bottom', relativeTo=self.dock_fitter) self.dock_area.addDock(self.dock_roi_v, position='below', relativeTo=self.dock_roi_h) self.dock_area.addDock(self.dock_roi_int, position='below', relativeTo=self.dock_roi_v) self.dock_area.addDock(self.dock_roi_err, position='below', relativeTo=self.dock_roi_int) self.dock_area.addDock(self.dock_roi_plot_h, position='below', relativeTo=self.dock_image_view) self.dock_area.addDock(self.dock_roi_plot_v, position='right', relativeTo=self.dock_roi_plot_h) self.dock_area.addDock(self.dock_analyzer, position='top', relativeTo=self.dock_image_browser) def initAfterCreatingDockWidgets(self): self.setWindowTitle(self.image_browser.current_directory) def connectSignalsToSlots(self): self.actionOpen_Directory.triggered.connect(self.image_browser.handleOpenDirectoryAction) self.actionDark_File.triggered.connect(self.image_browser.handleDarkFileAction) self.actionRefresh.triggered.connect(self.image_browser.handleRefreshAction) self.actionSave.triggered.connect(self.image_browser.handleSaveAnalysis) self.image_browser.windowTitleChanged.connect(self.setWindowTitle) # self.image_browser.imageChanged.connect(self.image_view.handleImageChanged) self.image_browser.imageChanged.connect(self.fitter.handleImageChanged) self.image_browser.imageChanged.connect(self.analyzer.handleImageChanged) self.roi_editor_int.roiChanged.connect(self.image_browser.handleRoiChanged) self.roi_editor_err.roiChanged.connect(self.image_browser.handleROIErrChanged) self.roi_editor_h.roiChanged.connect(self.image_browser.handleROIHChanged) self.roi_editor_v.roiChanged.connect(self.image_browser.handleROIVChanged) self.roi_editor_h.roiChanged.connect(self.fitter.handleROIHChanged) self.roi_editor_h.roiChanged.connect(self.analyzer.handleROIHChanged) self.roi_editor_v.roiChanged.connect(self.fitter.handleROIVChanged) self.roi_editor_v.roiChanged.connect(self.analyzer.handleROIVChanged) self.roi_editor_int.roiChanged.connect(self.fitter.handleROIIntChanged) self.roi_editor_int.roiChanged.connect(self.analyzer.handleROIIntChanged) self.roi_editor_err.roiChanged.connect(self.fitter.handleROIErrChanged) self.roi_editor_err.roiChanged.connect(self.analyzer.handleROIErrChanged) self.image_view.doubleClicked.connect(self.roi_editor_h.centerROI) self.image_view.doubleClicked.connect(self.roi_editor_v.centerROI) self.fitter.imageChanged.connect(self.image_view.handleImageChanged) self.fitter.horDataChanged.connect(self.roi_plot_h.handleDataChanged) self.fitter.verDataChanged.connect(self.roi_plot_v.handleDataChanged) self.fitter.doneFitting.connect(self.analyzer.handleDoneFitting) def loadSettings(self): """Load window state from self.settings""" self.settings.beginGroup('mainwindow') geometry = self.settings.value('geometry').toByteArray() state = self.settings.value('windowstate').toByteArray() dock_string = str(self.settings.value('dockstate').toString()) if dock_string is not "": dock_state = eval(dock_string) self.dock_area.restoreState(dock_state) self.settings.endGroup() self.restoreGeometry(geometry) self.restoreState(state) def saveSettings(self): """Save window state to self.settings.""" self.settings.beginGroup('mainwindow') self.settings.setValue('geometry', self.saveGeometry()) self.settings.setValue('windowstate', self.saveState()) dock_state = self.dock_area.saveState() # dock_state returned here is a python dictionary. Coundn't find a good # way to save dicts in QSettings, hence just using representation # of it. self.settings.setValue('dockstate', repr(dock_state)) self.settings.endGroup() def closeEvent(self, event): self.saveSettings() self.image_browser.saveSettings() self.roi_editor_int.saveSettings() self.roi_editor_err.saveSettings() self.roi_editor_v.saveSettings() self.roi_editor_h.saveSettings() self.analyzer.saveSettings() super(MainWindow, self).closeEvent(event) def setWindowTitle(self, newTitle=''): """Prepend IP-BEC to all window titles.""" title = 'IP-BEC: ' + newTitle super(MainWindow, self).setWindowTitle(title)
class CanvasScene(QGraphicsScene): """ A Graphics Scene for displaying an :class:`~.scheme.Scheme` instance. """ #: Signal emitted when a :class:`NodeItem` has been added to the scene. node_item_added = Signal(object) #: Signal emitted when a :class:`NodeItem` has been removed from the #: scene. node_item_removed = Signal(object) #: Signal emitted when a new :class:`LinkItem` has been added to the #: scene. link_item_added = Signal(object) #: Signal emitted when a :class:`LinkItem` has been removed. link_item_removed = Signal(object) #: Signal emitted when a :class:`Annotation` item has been added. annotation_added = Signal(object) #: Signal emitted when a :class:`Annotation` item has been removed. annotation_removed = Signal(object) #: Signal emitted when the position of a :class:`NodeItem` has changed. node_item_position_changed = Signal(object, QPointF) #: Signal emitted when an :class:`NodeItem` has been double clicked. node_item_double_clicked = Signal(object) #: An node item has been activated (clicked) node_item_activated = Signal(object) #: An node item has been hovered node_item_hovered = Signal(object) #: Link item has been hovered link_item_hovered = Signal(object) def __init__(self, *args, **kwargs): QGraphicsScene.__init__(self, *args, **kwargs) self.scheme = None self.registry = None # All node items self.__node_items = [] # Mapping from SchemeNodes to canvas items self.__item_for_node = {} # All link items self.__link_items = [] # Mapping from SchemeLinks to canvas items. self.__item_for_link = {} # All annotation items self.__annotation_items = [] # Mapping from SchemeAnnotations to canvas items. self.__item_for_annotation = {} # Is the scene editable self.editable = True # Anchor Layout self.__anchor_layout = AnchorLayout() self.addItem(self.__anchor_layout) self.__channel_names_visible = True self.__node_animation_enabled = True self.user_interaction_handler = None self.activated_mapper = QSignalMapper(self) self.activated_mapper.mapped[QObject].connect( lambda node: self.node_item_activated.emit(node) ) self.hovered_mapper = QSignalMapper(self) self.hovered_mapper.mapped[QObject].connect( lambda node: self.node_item_hovered.emit(node) ) self.position_change_mapper = QSignalMapper(self) self.position_change_mapper.mapped[QObject].connect( self._on_position_change ) log.info("'%s' intitialized." % self) def clear_scene(self): """ Clear (reset) the scene. """ if self.scheme is not None: self.scheme.node_added.disconnect(self.add_node) self.scheme.node_removed.disconnect(self.remove_node) self.scheme.link_added.disconnect(self.add_link) self.scheme.link_removed.disconnect(self.remove_link) self.scheme.annotation_added.disconnect(self.add_annotation) self.scheme.annotation_removed.disconnect(self.remove_annotation) self.scheme.node_state_changed.disconnect( self.on_widget_state_change ) self.scheme.channel_state_changed.disconnect( self.on_link_state_change ) # Remove all items to make sure all signals from scheme items # to canvas items are disconnected. for annot in self.scheme.annotations: if annot in self.__item_for_annotation: self.remove_annotation(annot) for link in self.scheme.links: if link in self.__item_for_link: self.remove_link(link) for node in self.scheme.nodes: if node in self.__item_for_node: self.remove_node(node) self.scheme = None self.__node_items = [] self.__item_for_node = {} self.__link_items = [] self.__item_for_link = {} self.__annotation_items = [] self.__item_for_annotation = {} self.__anchor_layout.deleteLater() self.user_interaction_handler = None self.clear() log.info("'%s' cleared." % self) def set_scheme(self, scheme): """ Set the scheme to display. Populates the scene with nodes and links already in the scheme. Any further change to the scheme will be reflected in the scene. Parameters ---------- scheme : :class:`~.scheme.Scheme` """ if self.scheme is not None: # Clear the old scheme self.clear_scene() log.info("Setting scheme '%s' on '%s'" % (scheme, self)) self.scheme = scheme if self.scheme is not None: self.scheme.node_added.connect(self.add_node) self.scheme.node_removed.connect(self.remove_node) self.scheme.link_added.connect(self.add_link) self.scheme.link_removed.connect(self.remove_link) self.scheme.annotation_added.connect(self.add_annotation) self.scheme.annotation_removed.connect(self.remove_annotation) self.scheme.node_state_changed.connect( self.on_widget_state_change ) self.scheme.channel_state_changed.connect( self.on_link_state_change ) self.scheme.topology_changed.connect(self.on_scheme_change) for node in scheme.nodes: self.add_node(node) for link in scheme.links: self.add_link(link) for annot in scheme.annotations: self.add_annotation(annot) def set_registry(self, registry): """ Set the widget registry. """ # TODO: Remove/Deprecate. Is used only to get the category/background # color. That should be part of the SchemeNode/WidgetDescription. log.info("Setting registry '%s on '%s'." % (registry, self)) self.registry = registry def set_anchor_layout(self, layout): """ Set an :class:`~.layout.AnchorLayout` """ if self.__anchor_layout != layout: if self.__anchor_layout: self.__anchor_layout.deleteLater() self.__anchor_layout = None self.__anchor_layout = layout def anchor_layout(self): """ Return the anchor layout instance. """ return self.__anchor_layout def set_channel_names_visible(self, visible): """ Set the channel names visibility. """ self.__channel_names_visible = visible for link in self.__link_items: link.setChannelNamesVisible(visible) def channel_names_visible(self): """ Return the channel names visibility state. """ return self.__channel_names_visible def set_node_animation_enabled(self, enabled): """ Set node animation enabled state. """ if self.__node_animation_enabled != enabled: self.__node_animation_enabled = enabled for node in self.__node_items: node.setAnimationEnabled(enabled) def add_node_item(self, item): """ Add a :class:`.NodeItem` instance to the scene. """ if item in self.__node_items: raise ValueError("%r is already in the scene." % item) if item.pos().isNull(): if self.__node_items: pos = self.__node_items[-1].pos() + QPointF(150, 0) else: pos = QPointF(150, 150) item.setPos(pos) item.setFont(self.font()) # Set signal mappings self.activated_mapper.setMapping(item, item) item.activated.connect(self.activated_mapper.map) self.hovered_mapper.setMapping(item, item) item.hovered.connect(self.hovered_mapper.map) self.position_change_mapper.setMapping(item, item) item.positionChanged.connect(self.position_change_mapper.map) self.addItem(item) self.__node_items.append(item) self.node_item_added.emit(item) log.info("Added item '%s' to '%s'" % (item, self)) return item def add_node(self, node): """ Add and return a default constructed :class:`.NodeItem` for a :class:`SchemeNode` instance `node`. If the `node` is already in the scene do nothing and just return its item. """ if node in self.__item_for_node: # Already added return self.__item_for_node[node] item = self.new_node_item(node.description) if node.position: pos = QPointF(*node.position) item.setPos(pos) item.setTitle(node.title) item.setProcessingState(node.processing_state) item.setProgress(node.progress) for message in node.state_messages(): item.setStateMessage(message) item.setStatusMessage(node.status_message()) self.__item_for_node[node] = item node.position_changed.connect(self.__on_node_pos_changed) node.title_changed.connect(item.setTitle) node.progress_changed.connect(item.setProgress) node.processing_state_changed.connect(item.setProcessingState) node.state_message_changed.connect(item.setStateMessage) node.status_message_changed.connect(item.setStatusMessage) return self.add_node_item(item) def new_node_item(self, widget_desc, category_desc=None): """ Construct an new :class:`.NodeItem` from a `WidgetDescription`. Optionally also set `CategoryDescription`. """ item = items.NodeItem() item.setWidgetDescription(widget_desc) if category_desc is None and self.registry and widget_desc.category: category_desc = self.registry.category(widget_desc.category) if category_desc is None and self.registry is not None: try: category_desc = self.registry.category(widget_desc.category) except KeyError: pass if category_desc is not None: item.setWidgetCategory(category_desc) item.setAnimationEnabled(self.__node_animation_enabled) return item def remove_node_item(self, item): """ Remove `item` (:class:`.NodeItem`) from the scene. """ self.activated_mapper.removeMappings(item) self.hovered_mapper.removeMappings(item) self.position_change_mapper.removeMappings(item) item.hide() self.removeItem(item) self.__node_items.remove(item) self.node_item_removed.emit(item) log.info("Removed item '%s' from '%s'" % (item, self)) def remove_node(self, node): """ Remove the :class:`.NodeItem` instance that was previously constructed for a :class:`SchemeNode` `node` using the `add_node` method. """ item = self.__item_for_node.pop(node) node.position_changed.disconnect(self.__on_node_pos_changed) node.title_changed.disconnect(item.setTitle) node.progress_changed.disconnect(item.setProgress) node.processing_state_changed.disconnect(item.setProcessingState) node.state_message_changed.disconnect(item.setStateMessage) self.remove_node_item(item) def node_items(self): """ Return all :class:`.NodeItem` instances in the scene. """ return list(self.__node_items) def add_link_item(self, item): """ Add a link (:class:`.LinkItem`) to the scene. """ if item.scene() is not self: self.addItem(item) item.setFont(self.font()) self.__link_items.append(item) self.link_item_added.emit(item) log.info("Added link %r -> %r to '%s'" % \ (item.sourceItem.title(), item.sinkItem.title(), self)) self.__anchor_layout.invalidateLink(item) return item def add_link(self, scheme_link): """ Create and add a :class:`.LinkItem` instance for a :class:`SchemeLink` instance. If the link is already in the scene do nothing and just return its :class:`.LinkItem`. """ if scheme_link in self.__item_for_link: return self.__item_for_link[scheme_link] source = self.__item_for_node[scheme_link.source_node] sink = self.__item_for_node[scheme_link.sink_node] item = self.new_link_item(source, scheme_link.source_channel, sink, scheme_link.sink_channel) item.setEnabled(scheme_link.enabled) scheme_link.enabled_changed.connect(item.setEnabled) if scheme_link.is_dynamic(): item.setDynamic(True) item.setDynamicEnabled(scheme_link.dynamic_enabled) scheme_link.dynamic_enabled_changed.connect(item.setDynamicEnabled) item.setRuntimeState(scheme_link.runtime_state()) scheme_link.state_changed.connect(item.setRuntimeState) self.add_link_item(item) self.__item_for_link[scheme_link] = item return item def new_link_item(self, source_item, source_channel, sink_item, sink_channel): """ Construct and return a new :class:`.LinkItem` """ item = items.LinkItem() item.setSourceItem(source_item) item.setSinkItem(sink_item) def channel_name(channel): if isinstance(channel, str): return channel else: return channel.name source_name = channel_name(source_channel) sink_name = channel_name(sink_channel) fmt = "<b>{0}</b> \u2192 <b>{1}</b>" item.setToolTip( fmt.format(escape(source_name), escape(sink_name)) ) item.setSourceName(source_name) item.setSinkName(sink_name) item.setChannelNamesVisible(self.__channel_names_visible) return item def remove_link_item(self, item): """ Remove a link (:class:`.LinkItem`) from the scene. """ # Invalidate the anchor layout. self.__anchor_layout.invalidateAnchorItem( item.sourceItem.outputAnchorItem ) self.__anchor_layout.invalidateAnchorItem( item.sinkItem.inputAnchorItem ) self.__link_items.remove(item) # Remove the anchor points. item.removeLink() self.removeItem(item) self.link_item_removed.emit(item) log.info("Removed link '%s' from '%s'" % (item, self)) return item def remove_link(self, scheme_link): """ Remove a :class:`.LinkItem` instance that was previously constructed for a :class:`SchemeLink` instance `link` using the `add_link` method. """ item = self.__item_for_link.pop(scheme_link) scheme_link.enabled_changed.disconnect(item.setEnabled) if scheme_link.is_dynamic(): scheme_link.dynamic_enabled_changed.disconnect( item.setDynamicEnabled ) scheme_link.state_changed.disconnect(item.setRuntimeState) self.remove_link_item(item) def link_items(self): """ Return all :class:`.LinkItem`\s in the scene. """ return list(self.__link_items) def add_annotation_item(self, annotation): """ Add an :class:`.Annotation` item to the scene. """ self.__annotation_items.append(annotation) self.addItem(annotation) self.annotation_added.emit(annotation) return annotation def add_annotation(self, scheme_annot): """ Create a new item for :class:`SchemeAnnotation` and add it to the scene. If the `scheme_annot` is already in the scene do nothing and just return its item. """ if scheme_annot in self.__item_for_annotation: # Already added return self.__item_for_annotation[scheme_annot] if isinstance(scheme_annot, scheme.SchemeTextAnnotation): item = items.TextAnnotation() item.setPlainText(scheme_annot.text) x, y, w, h = scheme_annot.rect item.setPos(x, y) item.resize(w, h) item.setTextInteractionFlags(Qt.TextEditorInteraction) font = font_from_dict(scheme_annot.font, item.font()) item.setFont(font) scheme_annot.text_changed.connect(item.setPlainText) elif isinstance(scheme_annot, scheme.SchemeArrowAnnotation): item = items.ArrowAnnotation() start, end = scheme_annot.start_pos, scheme_annot.end_pos item.setLine(QLineF(QPointF(*start), QPointF(*end))) item.setColor(QColor(scheme_annot.color)) scheme_annot.geometry_changed.connect( self.__on_scheme_annot_geometry_change ) self.add_annotation_item(item) self.__item_for_annotation[scheme_annot] = item return item def remove_annotation_item(self, annotation): """ Remove an :class:`.Annotation` instance from the scene. """ self.__annotation_items.remove(annotation) self.removeItem(annotation) self.annotation_removed.emit(annotation) def remove_annotation(self, scheme_annotation): """ Remove an :class:`.Annotation` instance that was previously added using :func:`add_anotation`. """ item = self.__item_for_annotation.pop(scheme_annotation) scheme_annotation.geometry_changed.disconnect( self.__on_scheme_annot_geometry_change ) if isinstance(scheme_annotation, scheme.SchemeTextAnnotation): scheme_annotation.text_changed.disconnect( item.setPlainText ) self.remove_annotation_item(item) def annotation_items(self): """ Return all :class:`.Annotation` items in the scene. """ return self.__annotation_items def item_for_annotation(self, scheme_annotation): return self.__item_for_annotation[scheme_annotation] def annotation_for_item(self, item): rev = dict(reversed(item) \ for item in self.__item_for_annotation.items()) return rev[item] def commit_scheme_node(self, node): """ Commit the `node` into the scheme. """ if not self.editable: raise Exception("Scheme not editable.") if node not in self.__item_for_node: raise ValueError("No 'NodeItem' for node.") item = self.__item_for_node[node] try: self.scheme.add_node(node) except Exception: log.error("An error occurred while committing node '%s'", node, exc_info=True) # Cleanup (remove the node item) self.remove_node_item(item) raise log.info("Commited node '%s' from '%s' to '%s'" % \ (node, self, self.scheme)) def commit_scheme_link(self, link): """ Commit a scheme link. """ if not self.editable: raise Exception("Scheme not editable") if link not in self.__item_for_link: raise ValueError("No 'LinkItem' for link.") self.scheme.add_link(link) log.info("Commited link '%s' from '%s' to '%s'" % \ (link, self, self.scheme)) def node_for_item(self, item): """ Return the `SchemeNode` for the `item`. """ rev = dict([(v, k) for k, v in self.__item_for_node.items()]) return rev[item] def item_for_node(self, node): """ Return the :class:`NodeItem` instance for a :class:`SchemeNode`. """ return self.__item_for_node[node] def link_for_item(self, item): """ Return the `SchemeLink for `item` (:class:`LinkItem`). """ rev = dict([(v, k) for k, v in self.__item_for_link.items()]) return rev[item] def item_for_link(self, link): """ Return the :class:`LinkItem` for a :class:`SchemeLink` """ return self.__item_for_link[link] def selected_node_items(self): """ Return the selected :class:`NodeItem`'s. """ return [item for item in self.__node_items if item.isSelected()] def selected_annotation_items(self): """ Return the selected :class:`Annotation`'s """ return [item for item in self.__annotation_items if item.isSelected()] def node_links(self, node_item): """ Return all links from the `node_item` (:class:`NodeItem`). """ return self.node_output_links(node_item) + \ self.node_input_links(node_item) def node_output_links(self, node_item): """ Return a list of all output links from `node_item`. """ return [link for link in self.__link_items if link.sourceItem == node_item] def node_input_links(self, node_item): """ Return a list of all input links for `node_item`. """ return [link for link in self.__link_items if link.sinkItem == node_item] def neighbor_nodes(self, node_item): """ Return a list of `node_item`'s (class:`NodeItem`) neighbor nodes. """ neighbors = list(map(attrgetter("sourceItem"), self.node_input_links(node_item))) neighbors.extend(map(attrgetter("sinkItem"), self.node_output_links(node_item))) return neighbors def on_widget_state_change(self, widget, state): pass def on_link_state_change(self, link, state): pass def on_scheme_change(self, ): pass def _on_position_change(self, item): # Invalidate the anchor point layout and schedule a layout. self.__anchor_layout.invalidateNode(item) self.node_item_position_changed.emit(item, item.pos()) def __on_node_pos_changed(self, pos): node = self.sender() item = self.__item_for_node[node] item.setPos(*pos) def __on_scheme_annot_geometry_change(self): annot = self.sender() item = self.__item_for_annotation[annot] if isinstance(annot, scheme.SchemeTextAnnotation): item.setGeometry(QRectF(*annot.rect)) elif isinstance(annot, scheme.SchemeArrowAnnotation): p1 = item.mapFromScene(QPointF(*annot.start_pos)) p2 = item.mapFromScene(QPointF(*annot.end_pos)) item.setLine(QLineF(p1, p2)) else: pass def item_at(self, pos, type_or_tuple=None, buttons=0): """Return the item at `pos` that is an instance of the specified type (`type_or_tuple`). If `buttons` (`Qt.MouseButtons`) is given only return the item if it is the top level item that would accept any of the buttons (`QGraphicsItem.acceptedMouseButtons`). """ rect = QRectF(pos, QSizeF(1, 1)) items = self.items(rect) if buttons: items = itertools.dropwhile( lambda item: not item.acceptedMouseButtons() & buttons, items ) items = list(items)[:1] if type_or_tuple: items = [i for i in items if isinstance(i, type_or_tuple)] return items[0] if items else None if PYQT_VERSION < 0x40900: # For QGraphicsObject subclasses items, itemAt ... return a # QGraphicsItem wrapper instance and not the actual class instance. def itemAt(self, *args, **kwargs): item = QGraphicsScene.itemAt(self, *args, **kwargs) return toGraphicsObjectIfPossible(item) def items(self, *args, **kwargs): items = QGraphicsScene.items(self, *args, **kwargs) return list(map(toGraphicsObjectIfPossible, items)) def selectedItems(self, *args, **kwargs): return list(map(toGraphicsObjectIfPossible, QGraphicsScene.selectedItems(self, *args, **kwargs))) def collidingItems(self, *args, **kwargs): return list(map(toGraphicsObjectIfPossible, QGraphicsScene.collidingItems(self, *args, **kwargs))) def focusItem(self, *args, **kwargs): item = QGraphicsScene.focusItem(self, *args, **kwargs) return toGraphicsObjectIfPossible(item) def mouseGrabberItem(self, *args, **kwargs): item = QGraphicsScene.mouseGrabberItem(self, *args, **kwargs) return toGraphicsObjectIfPossible(item) def mousePressEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.mousePressEvent(event): return # Right (context) click on the node item. If the widget is not # in the current selection then select the widget (only the widget). # Else simply return and let customContextMenuReqested signal # handle it shape_item = self.item_at(event.scenePos(), items.NodeItem) if shape_item and event.button() == Qt.RightButton and \ shape_item.flags() & QGraphicsItem.ItemIsSelectable: if not shape_item.isSelected(): self.clearSelection() shape_item.setSelected(True) return QGraphicsScene.mousePressEvent(self, event) def mouseMoveEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.mouseMoveEvent(event): return return QGraphicsScene.mouseMoveEvent(self, event) def mouseReleaseEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.mouseReleaseEvent(event): return return QGraphicsScene.mouseReleaseEvent(self, event) def mouseDoubleClickEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.mouseDoubleClickEvent(event): return return QGraphicsScene.mouseDoubleClickEvent(self, event) def keyPressEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.keyPressEvent(event): return return QGraphicsScene.keyPressEvent(self, event) def keyReleaseEvent(self, event): if self.user_interaction_handler and \ self.user_interaction_handler.keyReleaseEvent(event): return return QGraphicsScene.keyReleaseEvent(self, event) def set_user_interaction_handler(self, handler): if self.user_interaction_handler and \ not self.user_interaction_handler.isFinished(): self.user_interaction_handler.cancel() log.info("Setting interaction '%s' to '%s'" % (handler, self)) self.user_interaction_handler = handler if handler: handler.start() def event(self, event): # TODO: change the base class of Node/LinkItem to QGraphicsWidget. # It already handles font changes. if event.type() == QEvent.FontChange: self.__update_font() return QGraphicsScene.event(self, event) def __update_font(self): font = self.font() for item in self.__node_items + self.__link_items: item.setFont(font) def __str__(self): return "%s(objectName=%r, ...)" % \ (type(self).__name__, str(self.objectName()))
class ExportToPdfForm(QtGui.QWidget): stop_signal = pyqtSignal() def __init__(self): QtGui.QWidget.__init__(self) self._log = logging.getLogger(ConfigStatic.logger_name) self._log.info('passed') self.ui = Ui_ExportToPdf() self.ui.setupUi(self) self.reportlab_imported = REPORTLAB_IMPORTED self.cprint = Print() self.btnSave = self.ui.buttonBox.button(Qt.QDialogButtonBox.Save) self.btnCancel = self.ui.buttonBox.button(Qt.QDialogButtonBox.Cancel) # self.ui.rbPaperEU.clicked.connect(self.slotik) # self.ui.rbPaperUS.clicked.connect(self.slotik) self.signal_mapper = QSignalMapper(self) # self.connect(self.ui.rbPaperEU, QtCore.SIGNAL("clicked()"), signal_mapper, QtCore.SLOT("map()")) # self.connect(self.ui.rbPaperEU, QtCore.SIGNAL("clicked()"), self.signal_mapper, QtCore.SLOT("map()")) self.signal_mapper.setMapping(self.ui.rbPaperEU, EnumPaperFormat.EU) self.signal_mapper.setMapping(self.ui.rbPaperUS, EnumPaperFormat.US) self.ui.rbPaperEU.clicked.connect(self.signal_mapper.map) self.ui.rbPaperUS.clicked.connect(self.signal_mapper.map) self.signal_mapper.mapped[QtCore.QString].connect( self.slot_paper_format) # self.signal_mapper.mapped.connect(self.slot_paper_format) # self.signal_mapper.connect(QtCore.SIGNAL("mapped()"), QtCore.SLOT("clicked()")) # connection self.ui.btnBrowse.clicked.connect(self._open_dir_source) self.ui.btnSelectAll.clicked.connect(self._select_all) self.ui.btnUnselect.clicked.connect(self._unselect_all) self.btnSave.clicked.connect(self.save) self.btnCancel.clicked.connect(self.close) self.ui.lnFileName.textChanged.connect(self.line_edit_changed) self.ui.lnFileName.setText(os.path.expanduser("~") + os.path.sep) self._progress_dialog = QtGui.QProgressDialog() self._progress_dialog.setLabelText("Exporting CTG file to pdf ...") self._progress_dialog.setWindowModality(QtCore.Qt.WindowModal) self._progress_dialog.close() self._progress_dialog.canceled.connect(self._stop_progress) self.cprint.nr_pages_processed.connect(self._update_progress) self.msgBoxError = QtGui.QMessageBox() self.msgBoxError.setText("The output file name is empy!") self.msgBoxError.setDefaultButton(QtGui.QMessageBox.Close) self.msgBoxError.setIcon(QtGui.QMessageBox.Critical) self.msgbox_unsaved = QtGui.QMessageBox() self.msgbox_unsaved.setText("The file already exists.") self.msgbox_unsaved.setInformativeText("Overwrite?") self.msgbox_unsaved.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) self.msgbox_unsaved.setDefaultButton(QtGui.QMessageBox.Ok) self.msgbox_unsaved.setIcon(QtGui.QMessageBox.Warning) e = EnumAnnType self._dict_ann = { self.ui.cbBasal: e.basal, self.ui.cbBaseline: e.baseline, self.ui.cbRecovery: e.recovery, self.ui.cbNoRecovery: e.no_recovery, self.ui.cbExcessiveUA: e.excessive_ua, self.ui.cbNote: e.note, self.ui.cbMark: e.ellipsenote, self.ui.cbFloatingBaseline: e.floating_baseline, self.ui.cbAccel: e.acceleration, self.ui.cbDecel: e.deceleration, self.ui.cbUA: e.uterine_contraction } self.ui.lbStatus.setText("(saving to pdf might take a while)") @pyqtSlot(QtCore.QString) def slot_paper_format(self, sformat): # print sformat self.set_paper_format(sformat) def _open_dir_source(self): dir1, dummy = os.path.split(str(self.ui.lnFileName.text())) file_name = QtGui.QFileDialog.getSaveFileName( self, 'Select a filename for exporting', dir1) if file_name == "": return -1 else: file_name = str(file_name) self.ui.lnFileName.setText(file_name) return 0 def line_edit_changed(self): self.btnSave.setEnabled(True) def get_file_to_save(self): """ Split the path, check if file exist. Default: Save to USER HOME directory """ file_name = str(self.ui.lnFileName.text()) path, file_name = os.path.split(file_name) if file_name == "": self.msgBoxError.exec_() return -1 if not file_name.endswith(".pdf"): file_name += '.pdf' if path == "": file_name = os.path.join(os.path.expanduser('~'), file_name) else: file_name = os.path.join(path, file_name) return file_name def set_paper_format(self, pformat): """Interface method to the Print() class""" if pformat == EnumPaperFormat.EU: self.ui.rbPaperEU.setChecked(True) self.cprint.set_paper_format(pformat) else: self.ui.rbPaperUS.setChecked(True) self.cprint.set_paper_format(pformat) def set_signals(self, fhr, toco, fs, timestamp): """Interface method to the Print() class""" self.cprint.set_signals(fhr, toco, fs, timestamp) def set_annotations(self, ann): """Interface method to the Print() class""" self.cprint.set_annotations(ann) def set_record_name(self, s): """Interface method to the Print() class""" self.cprint.set_record_name(s) def save(self): file_name = self.get_file_to_save() if file_name == -1: return -1 if os.path.exists(file_name): if self.msgbox_unsaved.exec_() == QtGui.QMessageBox.Cancel: return self.btnSave.setEnabled(False) self.btnCancel.setEnabled(False) App.processEvents() try: self._progress_dialog.close() self._progress_dialog.setRange( 0, self.cprint.number_of_pages_to_print()) self._progress_dialog.setValue(0) self._progress_dialog.open() self.cprint.set_ann_to_print(self._get_ann_to_print()) self.cprint.set_file_name(file_name) self.cprint.print_() except IOError, e: self._progress_dialog.close() self.msgBoxError.setText(str(e)) self.msgBoxError.exec_() self.btnSave.setEnabled(True) self.btnCancel.setEnabled(True) return -1 self._progress_dialog.close() s = "PDF saved to: " + file_name self.ui.lbStatus.setText(s) self._log.info(s) App.processEvents() time.sleep(2) self.ui.lbStatus.setText("") self.btnSave.setEnabled(True) self.btnCancel.setEnabled(True) self.close()
class ToolGrid(QFrame): """ A widget containing a grid of actions/buttons. Actions can be added using standard :ref:`QWidget.addAction(QAction)` and :ref:`QWidget.insertAction(int, QAction)` methods. Parameters ---------- parent : :class:`QWidget` Parent widget. columns : int Number of columns in the grid layout. buttonSize : :class:`QSize`, optional Size of tool buttons in the grid. iconSize : :class:`QSize`, optional Size of icons in the buttons. toolButtonStyle : :class:`Qt.ToolButtonStyle` Tool button style. """ actionTriggered = Signal(QAction) actionHovered = Signal(QAction) def __init__(self, parent=None, columns=4, buttonSize=None, iconSize=None, toolButtonStyle=Qt.ToolButtonTextUnderIcon): QFrame.__init__(self, parent) if buttonSize is not None: buttonSize = QSize(buttonSize) if iconSize is not None: iconSize = QSize(iconSize) self.__columns = columns self.__buttonSize = buttonSize or QSize(50, 50) self.__iconSize = iconSize or QSize(26, 26) self.__toolButtonStyle = toolButtonStyle self.__gridSlots = [] self.__buttonListener = ToolButtonEventListener(self) self.__buttonListener.buttonRightClicked.connect( self.__onButtonRightClick) self.__buttonListener.buttonEnter.connect( self.__onButtonEnter) self.__mapper = QSignalMapper() self.__mapper.mapped[QObject].connect(self.__onClicked) self.__setupUi() def __setupUi(self): layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.setSizeConstraint(QGridLayout.SetFixedSize) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) def setButtonSize(self, size): """ Set the button size. """ if self.__buttonSize != size: self.__buttonSize = size for slot in self.__gridSlots: slot.button.setFixedSize(size) def buttonSize(self): """ Return the button size. """ return QSize(self.__buttonSize) def setIconSize(self, size): """ Set the button icon size. """ if self.__iconSize != size: self.__iconSize = size for slot in self.__gridSlots: slot.button.setIconSize(size) def iconSize(self): """ Return the icon size """ return QSize(self.__iconSize) def setToolButtonStyle(self, style): """ Set the tool button style. """ if self.__toolButtonStyle != style: self.__toolButtonStyle = style for slot in self.__gridSlots: slot.button.setToolButtonStyle(style) def toolButtonStyle(self): """ Return the tool button style. """ return self.__toolButtonStyle def setColumnCount(self, columns): """ Set the number of button/action columns. """ if self.__columns != columns: self.__columns = columns self.__relayout() def columns(self): """ Return the number of columns in the grid. """ return self.__columns def clear(self): """ Clear all actions/buttons. """ for slot in reversed(list(self.__gridSlots)): self.removeAction(slot.action) self.__gridSlots = [] def insertAction(self, before, action): """ Insert a new action at the position currently occupied by `before` (can also be an index). Parameters ---------- before : :class:`QAction` or int Position where the `action` should be inserted. action : :class:`QAction` Action to insert """ if isinstance(before, int): actions = list(self.actions()) if len(actions) == 0 or before >= len(actions): # Insert as the first action or the last action. return self.addAction(action) before = actions[before] return QFrame.insertAction(self, before, action) def setActions(self, actions): """ Clear the grid and add `actions`. """ self.clear() for action in actions: self.addAction(action) def buttonForAction(self, action): """ Return the :class:`QToolButton` instance button for `action`. """ actions = [slot.action for slot in self.__gridSlots] index = actions.index(action) return self.__gridSlots[index].button def createButtonForAction(self, action): """ Create and return a :class:`QToolButton` for action. """ button = _ToolGridButton(self) button.setDefaultAction(action) if self.__buttonSize.isValid(): button.setFixedSize(self.__buttonSize) if self.__iconSize.isValid(): button.setIconSize(self.__iconSize) button.setToolButtonStyle(self.__toolButtonStyle) button.setProperty("tool-grid-button", qtcompat.qwrap(True)) return button def count(self): """ Return the number of buttons/actions in the grid. """ return len(self.__gridSlots) def actionEvent(self, event): QFrame.actionEvent(self, event) if event.type() == QEvent.ActionAdded: # Note: the action is already in the self.actions() list. actions = list(self.actions()) index = actions.index(event.action()) self.__insertActionButton(index, event.action()) elif event.type() == QEvent.ActionRemoved: self.__removeActionButton(event.action()) def __insertActionButton(self, index, action): """Create a button for the action and add it to the layout at index. """ self.__shiftGrid(index, 1) button = self.createButtonForAction(action) row = index / self.__columns column = index % self.__columns self.layout().addWidget( button, row, column, Qt.AlignLeft | Qt.AlignTop ) self.__gridSlots.insert( index, _ToolGridSlot(button, action, row, column) ) self.__mapper.setMapping(button, action) button.clicked.connect(self.__mapper.map) button.installEventFilter(self.__buttonListener) button.installEventFilter(self) def __removeActionButton(self, action): """Remove the button for the action from the layout and delete it. """ actions = [slot.action for slot in self.__gridSlots] index = actions.index(action) slot = self.__gridSlots.pop(index) slot.button.removeEventFilter(self.__buttonListener) slot.button.removeEventFilter(self) self.__mapper.removeMappings(slot.button) self.layout().removeWidget(slot.button) self.__shiftGrid(index + 1, -1) slot.button.deleteLater() def __shiftGrid(self, start, count=1): """Shift all buttons starting at index `start` by `count` cells. """ button_count = self.layout().count() direction = 1 if count >= 0 else -1 if direction == 1: start, end = button_count - 1, start - 1 else: start, end = start, button_count for index in range(start, end, -direction): item = self.layout().itemAtPosition(index / self.__columns, index % self.__columns) if item: button = item.widget() new_index = index + count self.layout().addWidget(button, new_index / self.__columns, new_index % self.__columns, Qt.AlignLeft | Qt.AlignTop) def __relayout(self): """Relayout the buttons. """ for i in reversed(range(self.layout().count())): self.layout().takeAt(i) self.__gridSlots = [_ToolGridSlot(slot.button, slot.action, i / self.__columns, i % self.__columns) for i, slot in enumerate(self.__gridSlots)] for slot in self.__gridSlots: self.layout().addWidget(slot.button, slot.row, slot.column, Qt.AlignLeft | Qt.AlignTop) def __indexOf(self, button): """Return the index of button widget. """ buttons = [slot.button for slot in self.__gridSlots] return buttons.index(button) def __onButtonRightClick(self, button): pass def __onButtonEnter(self, button): action = button.defaultAction() self.actionHovered.emit(action) def __onClicked(self, action): self.actionTriggered.emit(action) def paintEvent(self, event): return utils.StyledWidget_paintEvent(self, event) def eventFilter(self, obj, event): etype = event.type() if etype == QEvent.KeyPress and obj.hasFocus(): key = event.key() if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right]: if self.__focusMove(obj, key): event.accept() return True return QFrame.eventFilter(self, obj, event) def __focusMove(self, focus, key): assert(focus is self.focusWidget()) try: index = self.__indexOf(focus) except IndexError: return False if key == Qt.Key_Down: index += self.__columns elif key == Qt.Key_Up: index -= self.__columns elif key == Qt.Key_Left: index -= 1 elif key == Qt.Key_Right: index += 1 if index >= 0 and index < self.count(): button = self.__gridSlots[index].button button.setFocus(Qt.TabFocusReason) return True else: return False
def contextMenuEvent(self, event): menu = QtGui.QMenu() last_pos = event.pos() cursor = self.cursorForPosition(last_pos) pos = cursor.positionInBlock() line = cursor.blockNumber() word = self.error_at_pos(line, pos) if not word == None: suggestions = self.highlighter.checker.suggest(word[0]) if len(suggestions) > 0: suggestion_mapper = QSignalMapper(self) actions = [] # Generate our suggestions, packing all the data we need to make the replacement. for i, suggestion in enumerate(suggestions): if i == MAX_SUGGESTIONS: break actions.append(QtGui.QAction(suggestion, None)) # We can only send strings with the signal mapper, so pickle our data. data = pickle.dumps((word[0], line, word[1], suggestion)) data = QtCore.QString.fromAscii(data) self.connect(actions[-1], QtCore.SIGNAL("triggered()"), suggestion_mapper, QtCore.SLOT("map()")) suggestion_mapper.setMapping(actions[-1], data) self.connect(suggestion_mapper, QtCore.SIGNAL("mapped(QString)"), self.__replace) menu.addActions(actions) else: action = QtGui.QAction("(No spelling suggestions)", None) action.setDisabled(True) menu.addAction(action) menu.addSeparator() add_mapper = QSignalMapper(self) ignore_mapper = QSignalMapper(self) add_action = QtGui.QAction("Add to Dictionary", None) ignore_action = QtGui.QAction("Ignore All", None) self.connect(add_action, QtCore.SIGNAL("triggered()"), add_mapper, QtCore.SLOT("map()")) add_mapper.setMapping(add_action, word[0]) self.connect(add_mapper, QtCore.SIGNAL("mapped(QString)"), self.__add) self.connect(ignore_action, QtCore.SIGNAL("triggered()"), ignore_mapper, QtCore.SLOT("map()")) ignore_mapper.setMapping(ignore_action, word[0]) self.connect(ignore_mapper, QtCore.SIGNAL("mapped(QString)"), self.__ignore) menu.addAction(add_action) menu.addAction(ignore_action) menu.addSeparator() default_menu = self.createStandardContextMenu() menu.addActions(default_menu.actions()) menu.exec_(event.globalPos())
class DocumentViewManager(QMainWindow): """ MDI area for displaying supporting documents within a given context e.g. supporting documents for a specific household based on the lifetime of the 'SourceDocumentManager' instance. """ def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.setWindowFlags(Qt.Window) self._mdi_area = QMdiArea() self._mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self._mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self._mdi_area) # set the size of mid_area and DocumentViewManager based on the # screen size. screen = QDesktopWidget().availableGeometry() self._mdi_area.resize(screen.width() - 30, screen.height() - 80) self.resize(self._mdi_area.size()) self._mdi_area.subWindowActivated.connect(self.update_actions) self._viewer_mapper = QSignalMapper(self) self._viewer_mapper.mapped[QWidget].connect(self.set_active_sub_window) win_title = QApplication.translate( "DocumentViewManager", "Document Viewer" ) self.setWindowTitle(win_title) self.setUnifiedTitleAndToolBarOnMac(True) self.statusBar().showMessage( QApplication.translate( "DocumentViewManager", "Ready" ) ) self._doc_viewers = {} self._create_menu_actions() self.update_actions() def center(self): """ Move the Document viewer to the center of the screen. """ # Get the current screens' dimensions... screen = QDesktopWidget().availableGeometry() # ... and get this windows' dimensions mdi_area_size = self.frameGeometry() # The horizontal position hpos = (screen.width() - mdi_area_size.width()) / 2 # vertical position vpos = (screen.height() - mdi_area_size.height()) / 2 # repositions the window self.move(hpos, vpos) def _create_menu_actions(self): self._window_menu = self.menuBar().addMenu( QApplication.translate( "DocumentViewManager","&Windows")) self._close_act = QAction( QApplication.translate("DocumentViewManager", "Cl&ose"), self) self._close_act.setStatusTip( QApplication.translate("DocumentViewManager", "Close the active document viewer")) self._close_act.triggered.connect(self._mdi_area.closeActiveSubWindow) self._close_all_act = QAction(QApplication.translate( "DocumentViewManager", "Close &All"), self ) self._close_all_act.setStatusTip( QApplication.translate("DocumentViewManager", "Close all the document viewers") ) self._close_all_act.triggered.connect( self._mdi_area.closeAllSubWindows ) self._tile_act = QAction(QApplication.translate( "DocumentViewManager", "&Tile"), self ) self._tile_act.setStatusTip( QApplication.translate("DocumentViewManager", "Tile the document viewers")) self._tile_act.triggered.connect(self.tile_windows) self._cascade_act = QAction(QApplication.translate( "DocumentViewManager", "&Cascade"), self) self._cascade_act.setStatusTip(QApplication.translate( "DocumentViewManager", "Cascade the document viewers")) self._cascade_act.triggered.connect(self.cascade_windows) self._next_act = QAction(QApplication.translate( "DocumentViewManager", "Ne&xt"), self) self._next_act.setStatusTip( QApplication.translate( "DocumentViewManager", "Move the focus to the next document viewer" ) ) self._next_act.triggered.connect(self._mdi_area.activateNextSubWindow) self._previous_act = QAction(QApplication.translate( "DocumentViewManager", "Pre&vious"), self) self._previous_act.setStatusTip( QApplication.translate( "DocumentViewManager", "Move the focus to the previous document viewer" ) ) self._previous_act.triggered.connect( self._mdi_area.activatePreviousSubWindow ) self._separator_act = QAction(self) self._separator_act.setSeparator(True) self.update_window_menu() self._window_menu.aboutToShow.connect(self.update_window_menu) def cascade_windows(self): #Cascade document windows self._mdi_area.cascadeSubWindows() def tile_windows(self): #Arrange document windows to occupy the available space in mdi area self._mdi_area.tileSubWindows() def update_actions(self): if self._mdi_area.activeSubWindow(): has_mdi_child = True else: has_mdi_child = False self._close_act.setEnabled(has_mdi_child) self._close_all_act.setEnabled(has_mdi_child) self._tile_act.setEnabled(has_mdi_child) self._cascade_act.setEnabled(has_mdi_child) self._previous_act.setEnabled(has_mdi_child) self._next_act.setEnabled(has_mdi_child) self._separator_act.setVisible(has_mdi_child) def update_window_menu(self): self._window_menu.clear() self._window_menu.addAction(self._close_act) self._window_menu.addAction(self._close_all_act) self._window_menu.addSeparator() self._window_menu.addAction(self._tile_act) self._window_menu.addAction(self._cascade_act) self._window_menu.addSeparator() self._window_menu.addAction(self._next_act) self._window_menu.addAction(self._previous_act) self._window_menu.addAction(self._separator_act) windows = self._mdi_area.subWindowList() self._separator_act.setVisible(len(windows) != 0) for i, window in enumerate(windows): text = "%d. %s" % (i + 1, window.windowTitle()) win_action = self._window_menu.addAction(text) win_action.setCheckable(True) win_action.setChecked(window is self._mdi_area.activeSubWindow()) win_action.triggered.connect(self._viewer_mapper.map) self._viewer_mapper.setMapping(win_action, window) def load_viewer(self, document_widget, visible=True): """ Open a new instance of the viewer or activate an existing one if the document had been previously loaded. :param document_widget: Contains all the necessary information required to load the specific document. :type document_widget: DocumentWidget :param visible: True to show the view manager after the viewer has been loaded, otherwise it will be the responsibility of the caller to enable visibility. :type visible: bool :returns: True if the document was successfully loaded, else False. :rtype: bool """ doc_identifier = document_widget.file_identifier() if doc_identifier in self._doc_viewers: doc_sw = self._doc_viewers[doc_identifier] self._mdi_area.setActiveSubWindow(doc_sw) doc_sw.showNormal() else: doc_viewer = self._create_viewer(document_widget) abs_doc_path = self.absolute_document_path(document_widget) if not QFile.exists(abs_doc_path): msg = QApplication.translate( "DocumentViewManager", "The selected document does not exist." "\nPlease check the supporting documents' " "repository setting." ) QMessageBox.critical( self, QApplication.translate( "DocumentViewManager","Invalid Document" ), msg ) return False doc_viewer.load_document(abs_doc_path) self._doc_viewers[doc_identifier] = doc_viewer self._mdi_area.addSubWindow(doc_viewer) doc_viewer.show() if not self.isVisible() and visible: self.setVisible(True) if self.isMinimized(): self.showNormal() self.center() return True def set_active_sub_window(self, viewer): if viewer: self._mdi_area.setActiveSubWindow(viewer) def absolute_document_path(self, document_widget): """ Build the absolute document path using info from the document widget. :param document_widget: Instance of document widget. :return: Absolute path of the supporting document. :rtype: str """ abs_path = '' file_manager = document_widget.fileManager if not file_manager is None: network_repository = file_manager.networkPath file_id = document_widget.file_identifier() source_entity = document_widget.doc_source_entity() profile_name = current_profile().name doc_type = document_widget.doc_type_value().lower().replace( ' ', '_' ) file_name, file_extension = guess_extension( document_widget.displayName() ) abs_path = network_repository + "/" +profile_name + '/' +\ unicode(source_entity) + "/" + unicode(doc_type) + "/" +\ unicode(file_id) + unicode(file_extension) return abs_path def reset(self): """ Removes all document viewers in the view area. The QCloseEvent sent to each sub-window will decrement the register. """ self._mdi_area.closeAllSubWindows() def _create_viewer(self, document_widget): """ Creates a new instance of a document viewer. :param document_widget: Contains all the necessary information required to load the specific document. :return: Document viewer object :rtype: DocumentViewer """ doc_viewer = DocumentViewer( self._mdi_area, document_widget.file_identifier() ) doc_viewer.setAttribute(Qt.WA_DeleteOnClose) doc_viewer.setWindowTitle( document_widget.displayName() ) # TODO: Incorporate logic for determining # TODO: viewer based on document type ph_viewer = PhotoViewer() # v_layout = QVBoxLayout() # v_layout.addWidget(ph_viewer) # doc_viewer.setLayout(v_layout) doc_viewer.set_view_widget(ph_viewer) doc_viewer.closed.connect(self._on_viewer_closed) return doc_viewer def remove_viewer(self, viewer_id): """ Close and remove the viewer with the specified viewer ID. """ if viewer_id in self._doc_viewers: viewer = self._doc_viewers[viewer_id] self._mdi_area.setActiveSubWindow(viewer) self._mdi_area.closeActiveSubWindow() self._on_viewer_closed(viewer_id) def _on_viewer_closed(self,file_id): """ Slot raised when a document viewer is closed. """ if file_id in self._doc_viewers: del self._doc_viewers[file_id]
class K800IRec(QWidget, mfso): def __init__(self): mfso.__init__(self, "K800i-Recover") self.name = "K800i-Recover" self.icon = None self.__disown__() def start(self, args): self.vfs = vfs.vfs() self.dumpnumber = 1 try: self.nor = args['nor'].value() self.nand = args['nand'].value() except IndexError: return try: self.spareSize = args["spare-size"].value() except IndexError: self.spareSize = 16 try: self.pageSize = args["page-size"].value() except IndexError: self.pageSize = 512 self.k800n = Node("k800-base") self.k800n.__disown__() self.boot = SEBootBlock(self.nor, self.pageSize) self.blockSize = self.boot.blockSize self.nandClean = SpareNode(self, self.nand, "nandfs", self.pageSize, self.spareSize, self.k800n) self.norFs = NorFs(self, self.k800n, self.nor, "norfs", self.boot) self.fullFs = FullFs(self, self.k800n, self.norFs, self.nandClean, "fullfs", self.boot) self.gdfs = GDFS(self, self.k800n, self.nor, "gdfs", self.boot) self.firmware = Firmware(self, self.k800n, self.nor, "firmware", self.boot.norfsoffset) self.tables = Tables(self.fullFs, self.blockSize) self.registerTree(self.nand, self.k800n) def createDump(self): text, ok = QInputDialog.getText(self, "Create dump", "dump name:", QLineEdit.Normal, "k800-restore-" + str(self.dumpnumber)) if ok and text != "": if (self.vfs.getnode(self.nand.absolute() + "/" + str(text)) == None): self.dumpnumber += 1 newroot = Node(str(text)) newroot.__disown__() for id in range(0, len(self.tables.tablesIdWriteMap) - 1): write = int( str(self.gtable.cellWidget(id, 0).currentText()), 16) self.tables.map[id] = self.tables.tablesIdWriteMap[id][ write] virtual = VirtualMap(self, newroot, self.fullFs, self.tables, "virtual", self.blockSize) separt = SEPartitionBlock(virtual, self.boot.partitionblock, self.blockSize) self.createPart(separt, newroot, virtual) self.registerTree(self.nand, newroot) else: box = QMessageBox(QMessageBox.Warning, "Error", "Error node already exists", QMessageBox.NoButton, self) box.exec_() self.createDump() def createPart(self, separt, newroot, virtual): for part in separt.partTable: if part.start > 0: p = Partition(self, newroot, virtual, part, self.blockSize) def g_display(self): QWidget.__init__(self, None) self.layout = QVBoxLayout(self) self.hlayout = QSplitter(self) self.layout.insertWidget(0, self.hlayout) self.layout.setStretchFactor(self.hlayout, 1) self.gTable() self.viewTable() self.button = QPushButton("&Create dump") self.connect(self.button, SIGNAL("clicked()"), self.createDump) self.layout.addWidget(self.button) def viewTable(self): self.vtable = QTableWidget() self.vtable.setColumnCount(20) self.vtable.setRowCount(48) self.hlayout.addWidget(self.vtable) def viewTableUpdate(self, id): write = int(str(self.gtable.cellWidget(id, 0).currentText()), 16) t = self.tables.tablesIdWriteMap[id][write] l = t.blockList for x in xrange(0, len(t.blockList[0])): block = t.blockList[0][x] c = ((x) % 20) r = ((x) / 20) item = QTableWidgetItem(QString(hex(block))) tipBlock = (id * 960) + x item.setToolTip(QString(hex(tipBlock))) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.vtable.setItem(r, c, item) def gTable(self): self.gtable = QTableWidget() self.gtable.setColumnCount(1) self.gtable.setRowCount(len(self.tables.tablesIdWriteMap)) self.gtable.setHorizontalHeaderItem( 0, QTableWidgetItem(QString("version"))) self.hlayout.addWidget(self.gtable) self.sigMapper = QSignalMapper(self) for id in self.tables.tablesIdWriteMap: wlist = self.tables.tablesIdWriteMap[id] cbox = QComboBox(self.gtable) self.connect(cbox, SIGNAL("activated(QString)"), self.sigMapper, SLOT("map()")) self.sigMapper.setMapping(cbox, id) l = [] for write in wlist: l.append(write) l.sort() l.reverse() for write in l: cbox.addItem(QString(hex(write))) self.gtable.setCellWidget(id, 0, cbox) self.gtable.setVerticalHeaderItem( id, QTableWidgetItem(QString(hex(id)))) self.connect(self.sigMapper, SIGNAL("mapped(int)"), self.viewTableUpdate) self.gtable.setMaximumWidth( self.gtable.columnWidth(0) + self.gtable.verticalHeader().sectionSize(0) + 30) def updateWidget(self): pass
def _create_connections(self): """ Creates connections in MainWindow """ # TODO rewrite the connection to a modern style # Menu: File self.connect(self.ui.actionQuit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()')) self.connect(self.ui.actionOpen, QtCore.SIGNAL('triggered()'), self._open_file) self.connect(self.ui.actionOpen_folder, QtCore.SIGNAL('triggered()'), self._open_folder_dialog) # Menu: TOOLS self.connect(self.ui.actionConvert_files, QtCore.SIGNAL('triggered()'), self._show_convert_files) self.connect(self.ui.actionDownload_CTU_UHB_data, QtCore.SIGNAL('triggered()'), self._show_download_db) self.connect(self.ui.actionAttribute_selection, QtCore.SIGNAL('triggered()'), self._show_attribute_selection) self.connect(self.ui.actionExport_to_PDF, QtCore.SIGNAL('triggered()'), self._export_to_pdf) # self.connect(self.ui.actionSet_Clear_Baseline, QtCore.SIGNAL('triggered()'), self._show_set_clear_bsln) self.connect(self.ui.actionSent_annotations, QtCore.SIGNAL('triggered()'), self._show_sent_annotations) group_paper = QtGui.QActionGroup(self) self.ui.actionEU.setActionGroup(group_paper) self.ui.actionUS.setActionGroup(group_paper) self.connect(self.ui.actionEU, QtCore.SIGNAL('triggered()'), self._set_paper) self.connect(self.ui.actionUS, QtCore.SIGNAL('triggered()'), self._set_paper) ''' create dictionary in the following format: action: name ''' e = EnumAnnType pui = self.ui dactions = { pui.actionCursor: e.select, pui.actionBasal: e.basal, pui.actionBaseline: e.baseline, pui.actionRecovery: e.recovery, pui.actionNo_recovery: e.no_recovery, pui.actionExcessive_UA: e.excessive_ua, pui.actionEllipse: e.ellipse, pui.actionEllipseNote: e.ellipsenote, pui.actionNote: e.note, pui.actionEvaluationNote: e.evaluation_note } if DEBUG_FIGO_ANN: dactions[pui.actionFloating_Baseline] = e.floating_baseline dactions[pui.actionAcceleration] = e.acceleration dactions[pui.actionDeceleration] = e.deceleration dactions[pui.actionUA] = e.uterine_contraction group_ann = QtGui.QActionGroup(self) for action in dactions.iterkeys(): if isinstance(action, QtGui.QAction): # just to check the type action.setActionGroup(group_ann) # self.ui.actionBasal.triggered.connect(self._debug_slot) # self.ui.actionBaseline.triggered.connect(self._set_annotation_action) signal_mapper = QSignalMapper(self) # signal_mapper.setMapping(self.ui.actionBaseline, 'baseline') # self.connect(self.ui.actionBaseline, QtCore.SIGNAL('triggered()'), signal_mapper.map) # self.connect(signal_mapper, QtCore.SIGNAL('mapped()'), self._set_annotation_action) # for d in dactions.iteritems(): for action, name in dactions.iteritems(): signal_mapper.setMapping(action, name) if isinstance(action, QtGui.QAction): # just to check the type action.triggered.connect(signal_mapper.map) # self.connect(action, QtCore.SIGNAL('triggered()'), signal_mapper.map) signal_mapper.mapped[QtCore.QString].connect(self._ann_set_action) # self.connect(self.ui.actionUS, QtCore.SIGNAL('triggered()'), self._set_paper) # self.connect(signal_mapper, QtCore.SIGNAL('mapped()'), self._set_annotation_action) # signal_mapper = QtCore.QSignalMapper(self) # # # for d in dactions.iteritems(): # for action, name in dactions.iteritems(): # signal_mapper.setMapping(action, name) # # if isinstance(action, QtGui.QAction): # just to check the type # # action.triggered.connect(signal_mapper.map) # self.connect(action, QtCore.SIGNAL('triggered()'), signal_mapper.map) # # # signal_mapper.mapped[QtCore.QString].connect(self._set_annotation_action) # # self.connect(self.ui.actionUS, QtCore.SIGNAL('triggered()'), self._set_paper) # self.connect(signal_mapper, QtCore.SIGNAL('mapped()'), self._set_annotation_action) # self.ui.actionAnnShowHide.triggered.connect(self._show_ann_show_hide) self.connect(self.ui.actionSave, QtCore.SIGNAL('triggered()'), self._toolbar_ann_save) self.connect(self.ui.actionDelete, QtCore.SIGNAL('triggered()'), self._toolbar_ann_delete) self.ui.actionCaliper.triggered.connect(self._caliper_set) self.ui.actionCaliperFHR.triggered.connect(self._caliper_set) self.ui.actionCaliperTOCO.triggered.connect(self._caliper_set) self.ui.actionFIGO_acc_dec.triggered.connect( self._caliper_set_figo_acc_dec) self.ui.actionFIGO_UA.triggered.connect(self._caliper_set_figo_ua) self.ui.actionCaliperReset.triggered.connect(self._caliper_reset) # Menu: View self.connect(self.ui.actionClinical_information, QtCore.SIGNAL('triggered()'), self._dock_clin_info_toggle) self.connect(self.ui.dockClinInfo, QtCore.SIGNAL("visibilityChanged(bool)"), self._dock_clin_info_visibility) self.connect(self.ui.actionData_browser, QtCore.SIGNAL('triggered()'), self._dock_databrowse_toggle) self.connect(self.ui.dockDataBrowser, QtCore.SIGNAL("visibilityChanged(bool)"), self._dock_databrowse_visibility) self.connect(self.ui.actionAnnToolbarVisible, QtCore.SIGNAL('triggered()'), self._toolbar_ann_toggle) self.connect(self.ui.toolBar, QtCore.SIGNAL('visibilityChanged(bool)'), self._toolbar_ann_visibility) self.connect(self.ui.actionAnnToolbarAlign_right, QtCore.SIGNAL('triggered()'), self._toolbar_align) # Menu: Help self.connect(self.ui.actionAbout, QtCore.SIGNAL('triggered()'), self._show_about) # SIGNALS self.ui.PlotWidget.fhrPlot.signal_ann_changed.connect( self._toolbar_ann_changed) self.ui.PlotWidget.tocoPlot.signal_ann_changed.connect( self._toolbar_ann_changed) self._attSelectForm.signal_sel_att_changed.connect( self._update_data_browser) if DEBUG_TOOLS is True: self.ui.actionDebug_CalibSignal.triggered.connect( self.plot_calibration_signal) if self._dataBrowserWidget.bDebugStageIICorrect: self._dataBrowserWidget.debug_stageI_signal.connect( self.debug_plot_stage1)
class DocumentViewManager(QMainWindow): """ MDI area for displaying supporting documents within a given context e.g. supporting documents for a specific household based on the lifetime of the 'SourceDocumentManager' instance. """ def __init__(self, parent = None): QMainWindow.__init__(self, parent) self.setWindowFlags(Qt.Window) self._mdi_area = QMdiArea() self.setCentralWidget(self._mdi_area) self._mdi_area.subWindowActivated.connect(self.update_actions) self._viewer_mapper = QSignalMapper(self) self._viewer_mapper.mapped[QWidget].connect(self.set_active_sub_window) win_title = QApplication.translate("DocumentViewManager","Document Viewer") self.setWindowTitle(win_title) self.setUnifiedTitleAndToolBarOnMac(True) self.statusBar().showMessage(QApplication.translate("DocumentViewManager","Ready")) self._doc_viewers = {} self._create_menu_actions() self.update_actions() def _create_menu_actions(self): self._window_menu = self.menuBar().addMenu(QApplication.translate("DocumentViewManager","&Windows")) self._close_act = QAction(QApplication.translate("DocumentViewManager", "Cl&ose"), self) self._close_act.setStatusTip(QApplication.translate("DocumentViewManager", "Close the active document viewer")) self._close_act.triggered.connect(self._mdi_area.closeActiveSubWindow) self._close_all_act = QAction(QApplication.translate("DocumentViewManager", "Close &All"), self) self._close_all_act.setStatusTip(QApplication.translate("DocumentViewManager", "Close all the document viewers")) self._close_all_act.triggered.connect(self._mdi_area.closeAllSubWindows) self._tile_act = QAction(QApplication.translate("DocumentViewManager", "&Tile"), self) self._tile_act.setStatusTip(QApplication.translate("DocumentViewManager", "Tile the document viewers")) self._tile_act.triggered.connect(self._mdi_area.tileSubWindows) self._cascade_act = QAction(QApplication.translate("DocumentViewManager", "&Cascade"), self) self._cascade_act.setStatusTip(QApplication.translate("DocumentViewManager", "Cascade the document viewers")) self._cascade_act.triggered.connect(self._mdi_area.cascadeSubWindows) self._next_act = QAction(QApplication.translate("DocumentViewManager", "Ne&xt"), self) self._next_act.setStatusTip(QApplication.translate("DocumentViewManager", "Move the focus to the next document viewer")) self._next_act.triggered.connect(self._mdi_area.activateNextSubWindow) self._previous_act = QAction(QApplication.translate("DocumentViewManager", "Pre&vious"), self) self._previous_act.setStatusTip(QApplication.translate("DocumentViewManager", "Move the focus to the previous document viewer")) self._previous_act.triggered.connect(self._mdi_area.activatePreviousSubWindow) self._separator_act = QAction(self) self._separator_act.setSeparator(True) self.update_window_menu() self._window_menu.aboutToShow.connect(self.update_window_menu) def update_actions(self): if self._mdi_area.activeSubWindow(): has_mdi_child = True else: has_mdi_child = False self._close_act.setEnabled(has_mdi_child) self._close_all_act.setEnabled(has_mdi_child) self._tile_act.setEnabled(has_mdi_child) self._cascade_act.setEnabled(has_mdi_child) self._previous_act.setEnabled(has_mdi_child) self._next_act.setEnabled(has_mdi_child) self._separator_act.setVisible(has_mdi_child) def update_window_menu(self): self._window_menu.clear() self._window_menu.addAction(self._close_act) self._window_menu.addAction(self._close_all_act) self._window_menu.addSeparator() self._window_menu.addAction(self._tile_act) self._window_menu.addAction(self._cascade_act) self._window_menu.addSeparator() self._window_menu.addAction(self._next_act) self._window_menu.addAction(self._previous_act) self._window_menu.addAction(self._separator_act) windows = self._mdi_area.subWindowList() self._separator_act.setVisible(len(windows) != 0) for i, window in enumerate(windows): text = "%d. %s" % (i + 1, window.windowTitle()) win_action = self._window_menu.addAction(text) win_action.setCheckable(True) win_action.setChecked(window is self._mdi_area.activeSubWindow()) win_action.triggered.connect(self._viewer_mapper.map) self._viewer_mapper.setMapping(win_action, window) def load_viewer(self, document_widget): """ Open a new instance of the viewer or activate an existing one if the document had been previously loaded. :param document_widget: Contains all the necessary information required to load the specific document. """ doc_identifier = document_widget.file_identifier() if doc_identifier in self._doc_viewers: doc_sw = self._doc_viewers[doc_identifier] self._mdi_area.setActiveSubWindow(doc_sw) doc_sw.showNormal() else: doc_viewer = self._create_viewer(document_widget) abs_doc_path = self.absolute_document_path(document_widget) if not QFile.exists(abs_doc_path): msg = QApplication.translate("DocumentViewManager", "The selected document does not exist." "\nPlease check the supporting documents' " "repository setting.") QMessageBox.critical(self, QApplication.translate("DocumentViewManager","Invalid Document"), msg) return doc_viewer.load_document(abs_doc_path) self._doc_viewers[doc_identifier] = doc_viewer self._mdi_area.addSubWindow(doc_viewer) doc_viewer.show() if not self.isVisible(): self.setVisible(True) if self.isMinimized(): self.showNormal() def set_active_sub_window(self, viewer): if viewer: self._mdi_area.setActiveSubWindow(viewer) def absolute_document_path(self,document_widget): """ Build the absolute document path using info from the document widget. :param document_widget: Instance of document widget. :return: Absolute path of the supporting document. :rtype: str """ abs_path = "" file_manager = document_widget.fileManager if not file_manager is None: network_repository = file_manager.networkPath file_id = document_widget.file_identifier() doc_type = "%d"%(document_widget.documentType()) file_name, file_extension = guess_extension(document_widget.displayName()) abs_path = network_repository + "/" + doc_type + "/" +\ file_id + file_extension return abs_path def reset(self): """ Removes all document viewers in the view area. The QCloseEvent sent to each sub-window will decrement the register. """ self._mdi_area.closeAllSubWindows() def _create_viewer(self, document_widget): """ Creates a new instance of a document viewer. :param document_widget: Contains all the necessary information required to load the specific document. :return: Document viewer object :rtype: DocumentViewer """ doc_viewer = DocumentViewer(self._mdi_area, document_widget.file_identifier()) doc_viewer.setAttribute(Qt.WA_DeleteOnClose) doc_viewer.setWindowTitle(document_widget.displayName()) #TODO: Incorporate logic for determining viewer based on document type ph_viewer = PhotoViewer() doc_viewer.set_view_widget(ph_viewer) doc_viewer.closed.connect(self._on_viewer_closed) return doc_viewer def remove_viewer(self, viewer_id): """ Close and remove the viewer with the specified viewer ID. """ if viewer_id in self._doc_viewers: viewer = self._doc_viewers[viewer_id] self._mdi_area.setActiveSubWindow(viewer) self._mdi_area.closeActiveSubWindow() self._on_viewer_closed(viewer_id) def _on_viewer_closed(self,file_id): """ Slot raised when a document viewer is closed. """ if file_id in self._doc_viewers: del self._doc_viewers[file_id]
class CollectionAggregator (SatyrObject): def __init__ (self, parent, collections=None, songs=None, busName=None, busPath=None): SatyrObject.__init__ (self, parent, busName, busPath) self.configValues= ( ('collsNo', int, 0), ) self.loadConfig () self.signalMapper= QSignalMapper () self.collections= [] if songs is None: self.songs= [] self.count= 0 else: self.songs= songs self.count= len (songs) # if collections is not None we it means the may have changed if self.collsNo>0 and collections is None: logger.info ("loading collections from config", self.collsNo) for index in xrange (self.collsNo): collection= Collection (self, busName=busName, busPath="/collection_%04d" % index) self.append (collection) else: if collections is not None: for collection in collections: self.append (collection) self.signalMapper.mapped.connect (self.addSongs) def append (self, collection): logger.debug ("adding collection", collection) collection.loadOrScan () self.collections.append (collection) self.collsNo= len (self.collections) index= len (self.collections)-1 collection.newSongs.connect (self.signalMapper.map) self.signalMapper.setMapping (collection, index) collection.scanFinished.connect (self.updateIndexes) self.updateIndexes () def indexToCollection (self, index): """Selects the collection that contains the index""" prevCollection= self.collections[0] for collection in self.collections[1:]: # print index, collection.offset if index < collection.offset: break prevCollection= collection return prevCollection def indexToCollectionIndex (self, index): """Converts a global index to a index in a collection""" collection= self.indexToCollection (index) collectionIndex= index-collection.offset return collection, collectionIndex def songForIndex (self, index): if len (self.songs)==0: # we're not a queue PLM, so we use the collections collection, collectionIndex= self.indexToCollectionIndex (index) song= collection.songs[collectionIndex] else: song= self.songs[index] return song def indexForSong (self, song): logger.debug ("PLM.indexForSong", song) index= None if len (self.songs)>0: index= self.songs.index (song) else: collection= song.coll collectionIndex= collection.indexForSong (song) if collectionIndex is not None: index= collection.offset+collectionIndex return index def addSongs (self, collNo): collection= self.collections[collNo] # BUG? shouldn't we call updateIndexes? # HINT: we call it when scanFinished() self.count+= len (collection.newSongs_) def updateIndexes (self): # recalculate the count and the offsets # only if we don't hols the songs ourselves if len (self.songs)==0: # HINT: yes, self.count==offset, but the semantic is different # otherwise the update of offsets will not be so clear self.count= 0 offset= 0 for collection in self.collections: collection.offset= offset offset+= collection.count self.count+= collection.count logger.debug ("PLM: count:", self.count)
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)
def contextMenuEvent(self, event): menu = QtGui.QMenu() last_pos = event.pos() cursor = self.cursorForPosition(last_pos) pos = cursor.positionInBlock() line = cursor.blockNumber() word = self.error_at_pos(line, pos) if not word == None: suggestions = self.highlighter.checker.suggest(word[0]) if len(suggestions) > 0: suggestion_mapper = QSignalMapper(self) actions = [] # Generate our suggestions, packing all the data we need to make the replacement. for i, suggestion in enumerate(suggestions): if i == MAX_SUGGESTIONS: break actions.append(QtGui.QAction(suggestion, None)) # We can only send strings with the signal mapper, so pickle our data. data = pickle.dumps((word[0], line, word[1], suggestion)) data = QtCore.QString.fromAscii(data) self.connect(actions[-1], QtCore.SIGNAL("triggered()"), suggestion_mapper, QtCore.SLOT("map()")) suggestion_mapper.setMapping(actions[-1], data) self.connect(suggestion_mapper, QtCore.SIGNAL("mapped(QString)"), self.__replace) menu.addActions(actions) else: action = QtGui.QAction("(No spelling suggestions)", None) action.setDisabled(True) menu.addAction(action) menu.addSeparator() add_mapper = QSignalMapper(self) ignore_mapper = QSignalMapper(self) add_action = QtGui.QAction("Add to Dictionary", None) ignore_action = QtGui.QAction("Ignore All", None) self.connect(add_action, QtCore.SIGNAL("triggered()"), add_mapper, QtCore.SLOT("map()")) add_mapper.setMapping(add_action, word[0]) self.connect(add_mapper, QtCore.SIGNAL("mapped(QString)"), self.__add) self.connect(ignore_action, QtCore.SIGNAL("triggered()"), ignore_mapper, QtCore.SLOT("map()")) ignore_mapper.setMapping(ignore_action, word[0]) self.connect(ignore_mapper, QtCore.SIGNAL("mapped(QString)"), self.__ignore) menu.addAction(add_action) menu.addAction(ignore_action) menu.addSeparator() default_menu = self.createStandardContextMenu() menu.addActions(default_menu.actions()) menu.exec_(event.globalPos())
class TratadorInterface(QMainWindow, Ui_InterfaceQt, Observer): erro_vertice_existe = pyqtSignal() erro_vertice_nao_existe = pyqtSignal(str) def __init__(self): Observer.__init__(self) QMainWindow.__init__(self) super(TratadorInterface, self).__init__(self) self.setupUi(self) self.erro_vertice_existe.connect(self.verticeExiste) self.erro_vertice_nao_existe.connect(self.verticeNaoExiste) self.eh_funcao.setEnabled(False) self.funcao_edit.setEnabled(False) self.grafo = None self.signalMapper = QSignalMapper(self) @pyqtSlot() def on_adicionar_vertice_button_clicked(self): if not self.grafo: nome = str(self.nome_edit.text()) if nome == '': QMessageBox(self).critical(self, 'ERRO', 'O Gravo deve ter um nome!', buttons=QMessageBox.Ok) return self.grafo = AlgoritmosGrafoNO(nome) self.observe(self.grafo) self.limparInferencias() vertices = str(self.vertices_edit.text()) vertices = vertices.split(',') if len(vertices) > 0: self.signalMapper = QSignalMapper(self) for vertice in vertices: vertice = vertice.strip() if vertice != '': cont = self.tabela_adjacencia.rowCount() item = QTableWidgetItem(vertice) self.tabela_adjacencia.insertColumn(cont) self.tabela_adjacencia.insertRow(cont) self.tabela_adjacencia.setHorizontalHeaderItem(cont,item) self.tabela_adjacencia.setVerticalHeaderItem(cont,item) self.grafo.adicionarVertice(vertice) for x in xrange(self.tabela_adjacencia.rowCount()): comboV = QComboBox(self) comboH = QComboBox(self) comboV.addItems(['0','1']) comboH.addItems(['0','1']) comboV.setSizePolicy(QSizePolicy().Minimum, QSizePolicy().Minimum) comboH.setSizePolicy(QSizePolicy().Minimum, QSizePolicy().Minimum) self.tabela_adjacencia.setCellWidget(cont,x,comboH) self.tabela_adjacencia.setCellWidget(x,cont,comboV) for x in xrange(self.tabela_adjacencia.rowCount()): for y in xrange(self.tabela_adjacencia.rowCount()): item = self.tabela_adjacencia.cellWidget(x, y) self.connect(item, SIGNAL('currentIndexChanged(int)'),self.signalMapper, SLOT('map()')) self.signalMapper.setMapping(item, '{0};{1}'.format(x,y)) self.connect(self.signalMapper, SIGNAL('mapped(QString)'), self.valorAlterado) self.tabela_adjacencia.resizeColumnsToContents() self.tabela_adjacencia.resizeRowsToContents() self.fecho_origem.addItems(vertices) self.informacoes_vertice.addItems(vertices) self.busca_destino.addItems(vertices) self.busca_origem.addItems(vertices) self.remover_vertice_combo.addItems(vertices) self.ordem_resultado.setText(str(self.grafo.obterOrdem())) self.gerar_arestas_button.setEnabled(True) self.vertices_edit.clear() @pyqtSlot() def on_remover_vertice_button_clicked(self): QMessageBox.critical(self, 'ERRO INTERNO', u'Ação não disponívl devido à um erro interno do QT', buttons=QMessageBox.Ok, defaultButton=QMessageBox.NoButton) ''' vertice = self.remover_vertice_combo.currentText() colunas = self.tabela_adjacencia.columnCount() for x in xrange(colunas): if vertice == str(self.tabela_adjacencia.horizontalHeaderItem(x).text()): for y in xrange(self.tabela_adjacencia.columnCount()): self.tabela_adjacencia.removeCellWidget(y,x) self.tabela_adjacencia.removeColumn(x) self.fecho_origem.removeItem(self.fecho_origem.findText(vertice)) self.informacoes_vertice.removeItem(self.informacoes_vertice.findText(vertice)) self.busca_destino.removeItem(self.busca_destino.findText(vertice)) self.busca_origem.removeItem(self.busca_origem.findText(vertice)) self.remover_vertice_combo.removeItem(self.remover_vertice_combo.findText(vertice)) self.ordem_resultado.setText(str(self.grafo.obterOrdem())) break ''' @pyqtSlot() def on_gerar_arestas_button_clicked(self): self.limparInferencias() self.grafo.limpar() n_linhas = self.tabela_adjacencia.rowCount() n_colunas = self.tabela_adjacencia.columnCount() for x in xrange(n_linhas): for y in xrange(x,n_colunas): item = self.tabela_adjacencia.cellWidget(x, y) if item.currentText() == '1': v1 = str(self.tabela_adjacencia.horizontalHeaderItem(x).text()) v2 = str(self.tabela_adjacencia.verticalHeaderItem(y).text()) valor = None if self.valorar_aresta.isChecked(): ok = False while not ok: valor,ok = QInputDialog().getText(self, 'Dados para a aresta {0}-{1} (separados por ",")'.format(v1,v2), 'Dados:', mode=QLineEdit.Normal, text=QString()) valor = str(valor) if not ok: sair = QMessageBox.question(self, 'Cancelar?', u'deseja cancelar a criação do grafo?', buttons=QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.NoButton) if sair == QMessageBox.Yes: self.grafo.limpar() return elif valor == '': atribuir_zero = QMessageBox.question(self, 'Valor em branco!', u'Atribuit valor "0" à aresta?', buttons=QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.NoButton) if atribuir_zero: valor = '0' else: valor = None valor = valor.split(',') valor = {string.split(':')[0]:string.split(':')[1] for string in valor} for dado in valor: if valor[dado].isdigit(): valor[dado] = int(valor[dado]) else: try: valor[dado] = float(valor[dado]) except ValueError: pass self.grafo.adicionarAresta(v1, v2, valor) self.eh_conexo.setChecked(self.grafo.ehConexo()) self.eh_arvore.setChecked(self.grafo.ehArvore()) self.eh_regular.setChecked(self.grafo.ehRegular()) self.eh_completo.setChecked(self.grafo.ehCompleto()) @pyqtSlot() def on_busca_profundidade_button_clicked(self): self.busca_resultado.clear() verticeInicial = str(self.busca_origem.currentText()) verticeFinal = str(self.busca_destino.currentText()) resultados = self.grafo.buscaProfundidade(verticeInicial, verticeFinal) for resultado in resultados: caminho = str([vertice.obterNome() for vertice in resultado]) + '\n' item = QListWidgetItem(caminho) self.busca_resultado.addItem(item) @pyqtSlot() def on_informacoes_button_clicked(self): self.busca_resultado.clear() vertice = str(self.informacoes_vertice.currentText()) '''Mostrar grau''' grau = self.grafo.obterGrau(vertice) self.busca_resultado.addItem(QListWidgetItem('Grau: ' + str(grau))) '''Mostrar adjacentes e info das arestas''' adjacentes = self.grafo.obterAdjacentes(vertice) self.busca_resultado.addItem(QListWidgetItem('Adjacentes:')) for adjacente in adjacentes: texto_valor = '' valor = self.grafo.obterAresta(vertice, adjacente.obterNome()) texto = '\t'+adjacente.obterNome() if valor: for x in valor: texto_valor += str(x) + ' : ' + str(valor[x]) + ', ' texto += '-> ' + texto_valor[:-2] self.busca_resultado.addItem(QListWidgetItem(texto)) @pyqtSlot('QString') def on_fecho_origem_activated(self, vertice): fecho = self.grafo.fechoTransitivo(str(vertice), set()) resultado = '' while fecho: resultado += fecho.pop() + ', ' self.fecho_resultado.setText(resultado[:-2]) @pyqtSlot('bool') def on_valorar_aresta_toggled(self,marcado): if not marcado: self.eh_funcao.setChecked(marcado) self.funcao_edit.setEnabled(marcado) self.eh_funcao.setEnabled(marcado) @pyqtSlot('bool') def on_eh_funcao_toggled(self,marcado): self.funcao_edit.setEnabled(marcado) @pyqtSlot('QString') def on_nome_edit_textChanged(self, texto): if texto == '': self.adicionar_vertice_button.setEnabled(False) elif len(self.vertices_edit.text()) > 0: self.adicionar_vertice_button.setEnabled(True) @pyqtSlot('QString') def on_vertices_edit_textChanged(self, texto): if texto == '': self.adicionar_vertice_button.setEnabled(False) elif len(self.nome_edit.text()) > 0: self.adicionar_vertice_button.setEnabled(True) def processEvent(self, notificador, evento, *args): if notificador == self.grafo: if evento == 'VerticeNaoExiste': self.erro_vertice_nao_existe.emit(args[0]) elif evento == 'VerticeJahExiste': self.erro_vertice_existe.emit() def limparItens(self): self.fecho_origem.clear() self.informacoes_vertice.clear() self.busca_destino.clear() self.busca_origem.clear() self.ordem_resultado.setText('') def limparInferencias(self): self.eh_conexo.setChecked(False) self.eh_arvore.setChecked(False) self.eh_regular.setChecked(False) self.eh_completo.setChecked(False) def verticeExiste(self): QMessageBox(self).critical(self, 'ERRO!', u'Existe mais de um vértice com o mesmo nome!', buttons=QMessageBox.Ok) self.limparInferencias() self.limparItens() self.tabela_adjacencia.setRowCount(0) self.tabela_adjacencia.setColumnCount(0) def verticeNaoExiste(self,e): QMessageBox(self).critical(self, 'ERRO', e, buttons=QMessageBox.Ok) @pyqtSlot('QString') def valorAlterado(self,string): string = str(string) pos = string.split(';') item2 = self.tabela_adjacencia.cellWidget(int(pos[1]), int(pos[0])) item1 = self.tabela_adjacencia.cellWidget(int(pos[0]), int(pos[1])) item2.setCurrentIndex(item1.currentIndex())
class Panel(QDialog): # A list of two-sized tuples (QWidget's name, model field name). FIELDS = [] def __init__(self, parent=None): # The flags we pass are that so we don't get the "What's this" button in the title bar QDialog.__init__(self, parent, Qt.WindowTitleHint | Qt.WindowSystemMenuHint) self._widget2ModelAttr = {} def _changeComboBoxItems(self, comboBox, newItems): # When a combo box's items are changed, its currentIndex changed with a currentIndexChanged # signal, and if that signal results in the model being updated, it messes the model. # We thus have to disconnect the combo box's signal before changing the items. if comboBox in self._widget2ModelAttr: comboBox.currentIndexChanged.disconnect(self.comboBoxCurrentIndexChanged) index = comboBox.currentIndex() comboBox.clear() comboBox.addItems(newItems) comboBox.setCurrentIndex(index) if comboBox in self._widget2ModelAttr: comboBox.currentIndexChanged.connect(self.comboBoxCurrentIndexChanged) def _connectSignals(self): self._signalMapper = QSignalMapper() for widgetName, modelAttr in self.FIELDS: widget = getattr(self, widgetName) self._widget2ModelAttr[widget] = modelAttr self._signalMapper.setMapping(widget, widget) if isinstance(widget, QComboBox): widget.currentIndexChanged.connect(self._signalMapper.map) elif isinstance(widget, QSpinBox): widget.valueChanged.connect(self._signalMapper.map) elif isinstance(widget, QLineEdit): widget.editingFinished.connect(self._signalMapper.map) elif isinstance(widget, QPlainTextEdit): widget.textChanged.connect(self._signalMapper.map) elif isinstance(widget, QCheckBox): widget.stateChanged.connect(self._signalMapper.map) self._signalMapper.mapped[QWidget].connect(self.widgetChanged) def _loadFields(self): for widgetName, modelAttr in self.FIELDS: widget = getattr(self, widgetName) value = getattr(self.model, modelAttr) if isinstance(widget, QComboBox): widget.setCurrentIndex(value) elif isinstance(widget, QSpinBox): widget.setValue(value) elif isinstance(widget, QLineEdit): widget.setText(value) elif isinstance(widget, QPlainTextEdit): widget.setPlainText(value) elif isinstance(widget, QCheckBox): widget.setChecked(value) def _saveFields(self): pass def accept(self): # The setFocus() call is to force the last edited field to "commit". When the save button # is clicked, accept() is called before the last field to have focus has a chance to emit # its edition signal. self.setFocus() self.model.save() QDialog.accept(self) #--- Event Handlers def widgetChanged(self, sender): modelAttr = self._widget2ModelAttr[sender] if isinstance(sender, QComboBox): newvalue = sender.currentIndex() elif isinstance(sender, QSpinBox): newvalue = sender.value() elif isinstance(sender, QLineEdit): newvalue = sender.text() elif isinstance(sender, QPlainTextEdit): newvalue = sender.toPlainText() elif isinstance(sender, QCheckBox): newvalue = sender.isChecked() setattr(self.model, modelAttr, newvalue) #--- model --> view def pre_load(self): pass def pre_save(self): self._saveFields() def post_load(self): if not self._widget2ModelAttr: # signal not connected yet self._connectSignals() self._loadFields() self.show() # For initial text edits to have their text selected, we *have to* first select the dialog, # then setFocus on it with qt.TabFocusReason. Don't ask, I don't know why either... self.setFocus() focus = self.nextInFocusChain() while focus.focusPolicy() == Qt.NoFocus: focus = focus.nextInFocusChain() focus.setFocus(Qt.TabFocusReason)
class ToolBox(QFrame): """ A tool box widget. """ # Emitted when a tab is toggled. tabToogled = Signal(int, bool) def setExclusive(self, exclusive): """ Set exclusive tabs (only one tab can be open at a time). """ if self.__exclusive != exclusive: self.__exclusive = exclusive self.__tabActionGroup.setExclusive(exclusive) checked = self.__tabActionGroup.checkedAction() if checked is None: # The action group can be out of sync with the actions state # when switching between exclusive states. actions_checked = [ page.action for page in self.__pages if page.action.isChecked() ] if actions_checked: checked = actions_checked[0] # Trigger/toggle remaining open pages if exclusive and checked is not None: for page in self.__pages: if checked != page.action and page.action.isChecked(): page.action.trigger() def exclusive(self): """ Are the tabs in the toolbox exclusive. """ return self.__exclusive exclusive_ = Property(bool, fget=exclusive, fset=setExclusive, designable=True, doc="Exclusive tabs") def __init__(self, parent=None, **kwargs): QFrame.__init__(self, parent, **kwargs) self.__pages = [] self.__tabButtonHeight = -1 self.__tabIconSize = QSize() self.__exclusive = False self.__setupUi() def __setupUi(self): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) # Scroll area for the contents. self.__scrollArea = \ _ToolBoxScrollArea(self, objectName="toolbox-scroll-area") self.__scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.__scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.__scrollArea.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.__scrollArea.setFrameStyle(QScrollArea.NoFrame) self.__scrollArea.setWidgetResizable(True) # A widget with all of the contents. # The tabs/contents are placed in the layout inside this widget self.__contents = QWidget(self.__scrollArea, objectName="toolbox-contents") # The layout where all the tab/pages are placed self.__contentsLayout = QVBoxLayout() self.__contentsLayout.setContentsMargins(0, 0, 0, 0) self.__contentsLayout.setSizeConstraint(QVBoxLayout.SetMinAndMaxSize) self.__contentsLayout.setSpacing(0) self.__contents.setLayout(self.__contentsLayout) self.__scrollArea.setWidget(self.__contents) layout.addWidget(self.__scrollArea) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) self.__tabActionGroup = \ QActionGroup(self, objectName="toolbox-tab-action-group") self.__tabActionGroup.setExclusive(self.__exclusive) self.__actionMapper = QSignalMapper(self) self.__actionMapper.mapped[QObject].connect(self.__onTabActionToogled) def setTabButtonHeight(self, height): """ Set the tab button height. """ if self.__tabButtonHeight != height: self.__tabButtonHeight = height for page in self.__pages: page.button.setFixedHeight(height) def tabButtonHeight(self): """ Return the tab button height. """ return self.__tabButtonHeight def setTabIconSize(self, size): """ Set the tab button icon size. """ if self.__tabIconSize != size: self.__tabIconSize = size for page in self.__pages: page.button.setIconSize(size) def tabIconSize(self): """ Return the tab icon size. """ return self.__tabIconSize def tabButton(self, index): """ Return the tab button at `index` """ return self.__pages[index].button def tabAction(self, index): """ Return open/close action for the tab at `index`. """ return self.__pages[index].action def addItem(self, widget, text, icon=None, toolTip=None): """ Append the `widget` in a new tab and return its index. Parameters ---------- widget : :class:`QWidget` A widget to be inserted. The toolbox takes ownership of the widget. text : str Name/title of the new tab. icon : :class:`QIcon`, optional An icon for the tab button. toolTip : str, optional Tool tip for the tab button. """ return self.insertItem(self.count(), widget, text, icon, toolTip) def insertItem(self, index, widget, text, icon=None, toolTip=None): """ Insert the `widget` in a new tab at position `index`. See also -------- ToolBox.addItem """ button = self.createTabButton(widget, text, icon, toolTip) self.__contentsLayout.insertWidget(index * 2, button) self.__contentsLayout.insertWidget(index * 2 + 1, widget) widget.hide() page = _ToolBoxPage(index, widget, button.defaultAction(), button) self.__pages.insert(index, page) for i in range(index + 1, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) self.__updatePositions() # Show (open) the first tab. if self.count() == 1 and index == 0: page.action.trigger() self.__updateSelected() self.updateGeometry() return index def removeItem(self, index): """ Remove the widget at `index`. .. note:: The widget hidden but is is not deleted. """ self.__contentsLayout.takeAt(2 * index + 1) self.__contentsLayout.takeAt(2 * index) page = self.__pages.pop(index) # Update the page indexes for i in range(index, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) page.button.deleteLater() # Hide the widget and reparent to self # This follows QToolBox.removeItem page.widget.hide() page.widget.setParent(self) self.__updatePositions() self.__updateSelected() self.updateGeometry() def count(self): """ Return the number of widgets inserted in the toolbox. """ return len(self.__pages) def widget(self, index): """ Return the widget at `index`. """ return self.__pages[index].widget def createTabButton(self, widget, text, icon=None, toolTip=None): """ Create the tab button for `widget`. """ action = QAction(text, self) action.setCheckable(True) if icon: action.setIcon(icon) if toolTip: action.setToolTip(toolTip) self.__tabActionGroup.addAction(action) self.__actionMapper.setMapping(action, action) action.toggled.connect(self.__actionMapper.map) button = ToolBoxTabButton(self, objectName="toolbox-tab-button") button.setDefaultAction(action) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) if self.__tabIconSize.isValid(): button.setIconSize(self.__tabIconSize) if self.__tabButtonHeight > 0: button.setFixedHeight(self.__tabButtonHeight) return button def ensureWidgetVisible(self, child, xmargin=50, ymargin=50): """ Scroll the contents so child widget instance is visible inside the viewport. """ self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin) def sizeHint(self): hint = self.__contentsLayout.sizeHint() if self.count(): # Compute max width of hidden widgets also. scroll = self.__scrollArea scroll_w = scroll.verticalScrollBar().sizeHint().width() frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2 max_w = max([p.widget.sizeHint().width() for p in self.__pages]) hint = QSize( max(max_w, hint.width()) + scroll_w + frame_w, hint.height()) return QSize(200, 200).expandedTo(hint) def __onTabActionToogled(self, action): page = find(self.__pages, action, key=attrgetter("action")) on = action.isChecked() page.widget.setVisible(on) index = page.index if index > 0: # Update the `previous` tab buttons style hints previous = self.__pages[index - 1].button flag = QStyleOptionToolBoxV2.NextIsSelected if on: previous.selected |= flag else: previous.selected &= ~flag previous.update() if index < self.count() - 1: next = self.__pages[index + 1].button flag = QStyleOptionToolBoxV2.PreviousIsSelected if on: next.selected |= flag else: next.selected &= ~flag next.update() self.tabToogled.emit(index, on) self.__contentsLayout.invalidate() def __updateSelected(self): """Update the tab buttons selected style flags. """ if self.count() == 0: return opt = QStyleOptionToolBoxV2 def update(button, next_sel, prev_sel): if next_sel: button.selected |= opt.NextIsSelected else: button.selected &= ~opt.NextIsSelected if prev_sel: button.selected |= opt.PreviousIsSelected else: button.selected &= ~opt.PreviousIsSelected button.update() if self.count() == 1: update(self.__pages[0].button, False, False) elif self.count() >= 2: pages = self.__pages for i in range(1, self.count() - 1): update(pages[i].button, pages[i + 1].action.isChecked(), pages[i - 1].action.isChecked()) def __updatePositions(self): """Update the tab buttons position style flags. """ if self.count() == 0: return elif self.count() == 1: self.__pages[0].button.position = QStyleOptionToolBoxV2.OnlyOneTab else: self.__pages[0].button.position = QStyleOptionToolBoxV2.Beginning self.__pages[-1].button.position = QStyleOptionToolBoxV2.End for p in self.__pages[1:-1]: p.button.position = QStyleOptionToolBoxV2.Middle for p in self.__pages: p.button.update()
class QPlayListModel (QAbstractTableModel): def __init__ (self, collaggr=None, songs=None, view=None): QAbstractTableModel.__init__ (self, view) # TODO: different delegate for editing tags: one with completion self.view_= view self.playlist= view.playlist self.edited= False if songs is None: self.collaggr= collaggr self.collections= self.collaggr.collections self.signalMapper= QSignalMapper () for collNo, collection in enumerate (self.collections): collection.newSongs.connect (self.signalMapper.map) self.signalMapper.setMapping (collection, collNo) self.signalMapper.mapped.connect (self.addRows) else: self.collaggr= CollectionAggregator (self, songs=songs) self.attrNames= ('artist', 'year', 'collection', 'diskno', 'album', 'trackno', 'title', 'length', 'filepath') self.headers= (u'Artist', u'Year', u'Collection', u'CD', u'Album', u'Track', u'Title', u'Length', u'Path') # FIXME: kinda hacky self.fontMetrics= QFontMetrics (KGlobalSettings.generalFont ()) # FIXME: (even more) hackish self.columnWidths= ("M"*15, "M"*4, "M"*15, "M"*3, "M"*20, "M"*3, "M"*25, "M"*5, "M"*200) logger.debug ("QPLM: ", self) def data (self, modelIndex, role): # TODO: allow to enter to edit mode in filepath but don't save any changes # so we can copy the path, that is... if modelIndex.isValid () and modelIndex.row ()<self.collaggr.count: song= self.collaggr.songForIndex (modelIndex.row ()) if role==Qt.DisplayRole or role==Qt.EditRole: attr= self.attrNames [modelIndex.column ()] rawData= song[attr] if attr=='filepath': # filenames as they are rawData= QFile.decodeName (rawData) elif attr=='title' and role!=Qt.EditRole: # don't (even try to) add the [#] to the title try: queueIndex= self.playlist.indexQueue.index (modelIndex.row ()) # make it show as starting in 1, otherwise it's confusing rawData= "[%d] %s" % (queueIndex+1, rawData) except ValueError: pass data= QVariant (rawData) elif role==Qt.SizeHintRole: size= self.fontMetrics.size (Qt.TextSingleLine, self.columnWidths[modelIndex.column ()]) data= QVariant (size) elif role==Qt.BackgroundRole and self.view_.modelIndex is not None: if modelIndex.row ()==self.view_.modelIndex.row (): # highlight the current song # must return a QBrush data= QVariant (QApplication.palette ().dark ()) else: try: queueIndex= self.playlist.indexQueue.index (modelIndex.row ()) data= QVariant (QApplication.palette ().mid ()) except ValueError: data= QVariant () elif role==Qt.ForegroundRole and self.view_.modelIndex is not None: if modelIndex.row ()==self.view_.modelIndex.row (): # highlight the current song # must return a QBrush data= QVariant (QApplication.palette ().brightText ()) else: data= QVariant () else: data= QVariant () else: data= QVariant () return data def flags (self, modelIndex): ans= QAbstractTableModel.flags (self, modelIndex) if modelIndex.column ()<7: # length or filepath are not editable ans= ans|Qt.ItemIsEditable|Qt.ItemIsEditable return ans def match (self, start, role, value, hits=1, flags=None): # when you press a key on an uneditable cell, QTableView tries to search # calling this function for matching. we already have a way for searching # and it loads the metadata of all the songs anyways # so we disable it by constantly returning an empty list return [] def setData (self, modelIndex, variant, role=Qt.EditRole): # not length or filepath and editing if modelIndex.column ()<7 and role==Qt.EditRole: logger.debug ("QPLM.setData()", modelIndex.row (), modelIndex.column(), role, ">%s<", unicode (variant.toString ())) song= self.collaggr.songForIndex (modelIndex.row ()) attr= self.attrNames[modelIndex.column ()] try: song[attr]= unicode (variant.toString ()) # TODO: make a list of dirty songs and commit them later song.saveMetadata () except TagWriteError: # it failed ans= False else: self.edited= True self.dataChanged.emit (modelIndex, modelIndex) ans= True else: ans= QAbstractTableModel.setData (self, modelIndex, variant, role) logger.debug ("QPLM.setData():", modelIndex.row(), modelIndex.column (), role, ans) return ans def headerData (self, section, direction, role=Qt.DisplayRole): if direction==Qt.Horizontal and role==Qt.DisplayRole: data= QVariant (self.headers[section]) elif direction==Qt.Vertical: if role==Qt.SizeHintRole: # TODO: support bold fonts # again, hacky. 5 for enough witdh for 5 digits size= self.fontMetrics.size (Qt.TextSingleLine, "M"*5) data= QVariant (size) elif role==Qt.TextAlignmentRole: data= QVariant (Qt.AlignRight|Qt.AlignVCenter) else: data= QAbstractTableModel.headerData (self, section, direction, role) else: data= QAbstractTableModel.headerData (self, section, direction, role) return data def addRows (self, collNo): collection= self.collections[collNo] for index, filepath in collection.newSongs_: self.beginInsertRows (QModelIndex (), index, index) self.endInsertRows () modelIndex= self.index (index, 0) self.dataChanged.emit (modelIndex, modelIndex) def dirtyRow (self, index): logger.debug ("QLMP.dirtyRow():", index) columns= self.columnCount () start= self.index (index, 0) end= self.index (index, columns) self.edited= False self.dataChanged.emit (start, end) def rowCount (self, parent=None): return self.collaggr.count def columnCount (self, parent=None): return len (self.attrNames)
def main(icon_spec): app = QApplication(sys.argv) main_window = QMainWindow() def sigint_handler(*args): main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler signal_timer = QTimer() signal_timer.start(100) signal_timer.timeout.connect(lambda: None) tool_bar = QToolBar() main_window.addToolBar(Qt.TopToolBarArea, tool_bar) table_view = QTableView() table_view.setSelectionBehavior(QAbstractItemView.SelectRows) table_view.setSelectionMode(QAbstractItemView.SingleSelection) table_view.setSortingEnabled(True) main_window.setCentralWidget(table_view) proxy_model = QSortFilterProxyModel() proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive) proxy_model.setFilterKeyColumn(1) table_view.setModel(proxy_model) proxy_model.layoutChanged.connect(table_view.resizeRowsToContents) item_model = QStandardItemModel() proxy_model.setSourceModel(item_model) # get all icons and their available sizes QIcon.setThemeName("gnome") icons = [] all_sizes = set([]) for context, icon_names in icon_spec: for icon_name in icon_names: icon = QIcon.fromTheme(icon_name) sizes = [] for size in icon.availableSizes(): size = (size.width(), size.height()) sizes.append(size) all_sizes.add(size) sizes.sort() icons.append({ 'context': context, 'icon_name': icon_name, 'icon': icon, 'sizes': sizes, }) all_sizes = list(all_sizes) all_sizes.sort() # input field for filter def filter_changed(value): proxy_model.setFilterRegExp(value) table_view.resizeRowsToContents() filter_line_edit = QLineEdit() filter_line_edit.setMaximumWidth(200) filter_line_edit.setPlaceholderText('Filter name') filter_line_edit.setToolTip('Filter name optionally using regular expressions (' + QKeySequence(QKeySequence.Find).toString() + ')') filter_line_edit.textChanged.connect(filter_changed) tool_bar.addWidget(filter_line_edit) # actions to toggle visibility of available sizes/columns def action_toggled(index): column = 2 + index table_view.setColumnHidden(column, not table_view.isColumnHidden(column)) table_view.resizeColumnsToContents() table_view.resizeRowsToContents() signal_mapper = QSignalMapper() for i, size in enumerate(all_sizes): action = QAction('%dx%d' % size, tool_bar) action.setCheckable(True) action.setChecked(True) tool_bar.addAction(action) action.toggled.connect(signal_mapper.map) signal_mapper.setMapping(action, i) # set tool tip and handle key sequence tool_tip = 'Toggle visibility of column' if i < 10: digit = ('%d' % (i + 1))[-1] tool_tip += ' (%s)' % QKeySequence('Ctrl+%s' % digit).toString() action.setToolTip(tool_tip) signal_mapper.mapped.connect(action_toggled) # label columns header_labels = ['context', 'name'] for width, height in all_sizes: header_labels.append('%dx%d' % (width, height)) item_model.setColumnCount(len(header_labels)) item_model.setHorizontalHeaderLabels(header_labels) # fill rows item_model.setRowCount(len(icons)) for row, icon_data in enumerate(icons): # context item = QStandardItem(icon_data['context']) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, 0, item) # icon name item = QStandardItem(icon_data['icon_name']) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, 1, item) for index_in_all_sizes, size in enumerate(all_sizes): column = 2 + index_in_all_sizes if size in icon_data['sizes']: # icon as pixmap to keep specific size item = QStandardItem('') pixmap = icon_data['icon'].pixmap(size[0], size[1]) item.setData(pixmap, Qt.DecorationRole) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, column, item) else: # single space to be sortable against icons item = QStandardItem(' ') item.setFlags(item.flags() ^ Qt.ItemIsEditable) item_model.setItem(row, column, item) table_view.resizeColumnsToContents() # manually set row heights because resizeRowsToContents is not working properly for row, icon_data in enumerate(icons): if len(icon_data['sizes']) > 0: max_size = icon_data['sizes'][-1] table_view.setRowHeight(row, max_size[1]) # enable focus find (ctrl+f) and toggle columns (ctrl+NUM) def main_window_keyPressEvent(self, event, old_keyPressEvent=QMainWindow.keyPressEvent): if event.matches(QKeySequence.Find): filter_line_edit.setFocus() return if event.modifiers() == Qt.ControlModifier and event.key() >= Qt.Key_0 and event.key() <= Qt.Key_9: index = event.key() - Qt.Key_1 if event.key() == Qt.Key_0: index += 10 action = signal_mapper.mapping(index) if action: action.toggle() return old_keyPressEvent(self, event) main_window.keyPressEvent = new.instancemethod(main_window_keyPressEvent, table_view, None) # enable copy (ctrl+c) name of icon to clipboard def table_view_keyPressEvent(self, event, old_keyPressEvent=QTableView.keyPressEvent): if event.matches(QKeySequence.Copy): selection_model = self.selectionModel() if selection_model.hasSelection(): index = selection_model.selectedRows()[0] source_index = self.model().mapToSource(index) item = self.model().sourceModel().item(source_index.row(), 1) icon_name = item.data(Qt.EditRole) app.clipboard().setText(icon_name.toString()) return old_keyPressEvent(self, event) table_view.keyPressEvent = new.instancemethod(table_view_keyPressEvent, table_view, None) print 'Icon Theme: ', QIcon.themeName() print 'Theme Search Paths:' for item in QIcon.themeSearchPaths(): print item main_window.showMaximized() return app.exec_()
class TrackingWindow(QMainWindow): """ Main window of the application. This class is responsible for the global data structures too. :IVariables: undo_stack : `QUndoStack` Undo stack. All actions that can be undone should be pushed on the stack. _project : `project.Project` Project object managing the loaded data _data : `tracking_data.TrackingData` Data object keeping track of points and cells toolGroup : `QActionGroup` Group of actions to be enabled only when actions can be taken on images previousSelAct : `QActionGroup` Actions enabled when points are selected in the previous pane currentSelAct : `QActionGroup` Actions enabled when points are selected in the current pane projectAct : `QActionGroup` Actions to enable once a project is loaded _previousScene : `tracking_scene.TrackingScene` Object managing the previous pane _currentScene : `tracking_scene.LinkedTrackingScene` Object managing the current pane """ def __init__(self, *args, **kwords): QMainWindow.__init__(self, *args) self.undo_stack = QUndoStack(self) self.ui = Ui_TrackingWindow() self.ui.setupUi(self) self._project = None self._data = None self.toolGroup = QActionGroup(self) self.toolGroup.addAction(self.ui.actionAdd_point) self.toolGroup.addAction(self.ui.action_Move_point) self.toolGroup.addAction(self.ui.actionAdd_cell) self.toolGroup.addAction(self.ui.actionRemove_cell) self.toolGroup.addAction(self.ui.action_Pan) self.toolGroup.addAction(self.ui.actionZoom_out) self.toolGroup.addAction(self.ui.actionZoom_in) self.previousSelAct = QActionGroup(self) self.previousSelAct.addAction(self.ui.actionCopy_selection_from_Previous) self.previousSelAct.addAction(self.ui.actionDelete_Previous) self.previousSelAct.setEnabled(False) self.currentSelAct = QActionGroup(self) self.currentSelAct.addAction(self.ui.actionCopy_selection_from_Current) self.currentSelAct.addAction(self.ui.actionDelete_Current) self.currentSelAct.setEnabled(False) self.projectAct = QActionGroup(self) self.projectAct.addAction(self.ui.action_Next_image) self.projectAct.addAction(self.ui.action_Previous_image) self.projectAct.addAction(self.ui.actionAdd_point) self.projectAct.addAction(self.ui.action_Move_point) self.projectAct.addAction(self.ui.action_Pan) self.projectAct.addAction(self.ui.actionAdd_cell) self.projectAct.addAction(self.ui.actionRemove_cell) self.projectAct.addAction(self.ui.action_Change_data_file) self.projectAct.addAction(self.ui.actionNew_data_file) self.projectAct.addAction(self.ui.actionZoom_out) self.projectAct.addAction(self.ui.actionZoom_in) self.projectAct.addAction(self.ui.actionSave_as) self.projectAct.addAction(self.ui.action_Fit) self.projectAct.addAction(self.ui.actionZoom_100) self.projectAct.addAction(self.ui.actionMerge_points) self.projectAct.addAction(self.ui.actionCopy_from_previous) self.projectAct.addAction(self.ui.actionCopy_from_current) self.projectAct.addAction(self.ui.actionReset_alignment) self.projectAct.addAction(self.ui.actionAlign_images) self.projectAct.addAction(self.ui.actionSelectPreviousAll) self.projectAct.addAction(self.ui.actionSelectPreviousNew) self.projectAct.addAction(self.ui.actionSelectPreviousNone) self.projectAct.addAction(self.ui.actionSelectPreviousNon_associated) self.projectAct.addAction(self.ui.actionSelectPreviousAssociated) self.projectAct.addAction(self.ui.actionSelectPreviousInvert) self.projectAct.addAction(self.ui.actionSelectCurrentAll) self.projectAct.addAction(self.ui.actionSelectCurrentNew) self.projectAct.addAction(self.ui.actionSelectCurrentNone) self.projectAct.addAction(self.ui.actionSelectCurrentNon_associated) self.projectAct.addAction(self.ui.actionSelectCurrentAssociated) self.projectAct.addAction(self.ui.actionSelectCurrentInvert) self.projectAct.addAction(self.ui.actionEdit_timing) self.projectAct.addAction(self.ui.actionEdit_scales) self.projectAct.addAction(self.ui.actionCompute_growth) self.projectAct.addAction(self.ui.actionClean_cells) self.projectAct.addAction(self.ui.actionGotoCell) self.projectAct.setEnabled(False) current_sel_actions = [self.ui.actionSelectCurrentAll, self.ui.actionSelectCurrentNew, self.ui.actionSelectCurrentNone, self.ui.actionSelectCurrentInvert, '-', self.ui.actionSelectCurrentNon_associated, self.ui.actionSelectCurrentAssociated, self.ui.actionCopy_selection_from_Previous ] previous_sel_actions = [self.ui.actionSelectPreviousAll, self.ui.actionSelectPreviousNew, self.ui.actionSelectPreviousNone, self.ui.actionSelectPreviousInvert, '-', self.ui.actionSelectPreviousNon_associated, self.ui.actionSelectPreviousAssociated, self.ui.actionCopy_selection_from_Current ] self._previousScene = TrackingScene(self.undo_stack, self.ui.actionDelete_Previous, previous_sel_actions, self) self._currentScene = LinkedTrackingScene(self._previousScene, self.undo_stack, self.ui.actionDelete_Current, current_sel_actions, self) self._previousScene.hasSelectionChanged.connect(self.previousSelAct.setEnabled) self._currentScene.hasSelectionChanged.connect(self.currentSelAct.setEnabled) self._previousScene.realSceneSizeChanged.connect(self.sceneSizeChanged) self._currentScene.realSceneSizeChanged.connect(self.sceneSizeChanged) self._previousScene.zoomIn[QPointF].connect(self.zoomIn) self._currentScene.zoomIn.connect(self.zoomIn) self._previousScene.zoomOut[QPointF].connect(self.zoomOut) self._currentScene.zoomOut.connect(self.zoomOut) self.ui.previousData.setScene(self._previousScene) self.ui.currentData.setScene(self._currentScene) self.ui.previousData.setDragMode(QGraphicsView.ScrollHandDrag) self.ui.currentData.setDragMode(QGraphicsView.ScrollHandDrag) #self.ui.previousData.setCacheMode(QGraphicsView.CacheBackground) #self.ui.currentData.setCacheMode(QGraphicsView.CacheBackground) # Redefine shortcuts to standard key sequences self.ui.action_Save.setShortcut(QKeySequence.Save) self.ui.actionSave_as.setShortcut(QKeySequence.SaveAs) self.ui.action_Open_project.setShortcut(QKeySequence.Open) self.ui.action_Undo.setShortcut(QKeySequence.Undo) self.ui.action_Redo.setShortcut(QKeySequence.Redo) self.ui.action_Next_image.setShortcut(QKeySequence.Forward) self.ui.action_Previous_image.setShortcut(QKeySequence.Back) # Connecting undo stack signals self.ui.action_Undo.triggered.connect(self.undo) self.ui.action_Redo.triggered.connect(self.redo) self.undo_stack.canRedoChanged[bool].connect(self.ui.action_Redo.setEnabled) self.undo_stack.canUndoChanged[bool].connect(self.ui.action_Undo.setEnabled) self.undo_stack.redoTextChanged["const QString&"].connect(self.changeRedoText) self.undo_stack.undoTextChanged["const QString&"].connect(self.changeUndoText) self.undo_stack.cleanChanged[bool].connect(self.ui.action_Save.setDisabled) # link_icon = QIcon() # pix = QPixmap(":/icons/link.png") # link_icon.addPixmap(pix, QIcon.Normal, QIcon.On) # pix = QPixmap(":/icons/link_broken.png") # link_icon.addPixmap(pix, QIcon.Normal, QIcon.Off) # self.link_icon = link_icon # #self.ui.linkViews.setIconSize(QSize(64,32)) # self.ui.linkViews.setIcon(link_icon) self._recent_projects_menu = QMenu(self) self.ui.actionRecent_projects.setMenu(self._recent_projects_menu) self._recent_projects_act = [] self._projects_mapper = QSignalMapper(self) self._projects_mapper.mapped[int].connect(self.loadRecentProject) self.param_dlg = None # Setting up the status bar bar = self.statusBar() # Adding current directory cur_dir = QLabel("") bar.addPermanentWidget(cur_dir) self._current_dir_label = cur_dir # Adding up zoom zoom = QLabel("") bar.addPermanentWidget(zoom) self._zoom_label = zoom self.changeZoom(1) self.loadConfig() parameters.instance.renderingChanged.connect(self.changeRendering) self.changeRendering() def changeRendering(self): if parameters.instance.use_OpenGL: self.ui.previousData.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) self.ui.currentData.setViewport(QGLWidget(QGLFormat(QGL.SampleBuffers))) else: self.ui.previousData.setViewport(QWidget()) self.ui.currentData.setViewport(QWidget()) def undo(self): self.undo_stack.undo() def redo(self): self.undo_stack.redo() def changeRedoText(self, text): self.ui.action_Redo.setText(text) self.ui.action_Redo.setToolTip(text) self.ui.action_Redo.setStatusTip(text) def changeUndoText(self, text): self.ui.action_Undo.setText(text) self.ui.action_Undo.setToolTip(text) self.ui.action_Undo.setStatusTip(text) def closeEvent(self, event): self.saveConfig() if not self.ensure_save_data("Exiting whith unsaved data", "The last modifications you made were not saved." " Are you sure you want to exit?"): event.ignore() return QMainWindow.closeEvent(self, event) #sys.exit(0) def loadConfig(self): params = parameters.instance self.ui.action_Show_vector.setChecked(params.show_vectors) self.ui.linkViews.setChecked(params.link_views) self.ui.action_Show_template.setChecked(parameters.instance.show_template) self.ui.actionShow_id.setChecked(parameters.instance.show_id) self.ui.action_Estimate_position.setChecked(parameters.instance.estimate) self.updateRecentFiles() params.recentProjectsChange.connect(self.updateRecentFiles) def updateRecentFiles(self): for a in self._recent_projects_act: self._projects_mapper.removeMappings(a) del self._recent_projects_act[:] menu = self._recent_projects_menu menu.clear() recent_projects = parameters.instance.recent_projects for i, p in enumerate(recent_projects): act = QAction(self) act.setText("&{0:d} {1}".format(i + 1, p)) self._recent_projects_act.append(act) act.triggered.connect(self._projects_mapper.map) self._projects_mapper.setMapping(act, i) menu.addAction(act) def saveConfig(self): parameters.instance.save() def check_for_data(self): if self._project is None: QMessageBox.critical(self, "No project loaded", "You have to load a project before performing this operation") return False return True def loadRecentProject(self, i): if self.ensure_save_data("Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change project?"): self.loadProject(parameters.instance.recent_projects[i]) @pyqtSignature("") def on_action_Open_project_triggered(self): if self.ensure_save_data("Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change project?"): dir_ = QFileDialog.getExistingDirectory(self, "Select a project directory", parameters.instance._last_dir) if dir_: self.loadProject(dir_) def loadProject(self, dir_): dir_ = path(dir_) project = Project(dir_) if project.valid: self._project = project else: create = QMessageBox.question(self, "Invalid project directory", "This directory does not contain a valid project. Turn into a directory?", QMessageBox.No, QMessageBox.Yes) if create == QMessageBox.No: return project.create() self._project = project self._project.use() parameters.instance.add_recent_project(dir_) parameters.instance._last_dir = dir_ if self._data is not None: _data = self._data _data.saved.disconnect(self.undo_stack.setClean) try: #self._project.load() self.load_data() _data = self._project.data _data.saved.connect(self.undo_stack.setClean) self._project.changedDataFile.connect(self.dataFileChanged) self._data = _data self._previousScene.changeDataManager(self._data) self._currentScene.changeDataManager(self._data) self.initFromData() self.projectAct.setEnabled(True) except TrackingDataException as ex: showException(self, "Error while loaded data", ex) def dataFileChanged(self, new_file): if new_file is None: self._current_dir_label.setText("") else: self._current_dir_label.setText(new_file) def initFromData(self): """ Initialize the interface using the current data """ self.ui.previousState.clear() self.ui.currentState.clear() for name in self._data.images_name: self.ui.previousState.addItem(name) self.ui.currentState.addItem(name) self.ui.previousState.setCurrentIndex(0) self.ui.currentState.setCurrentIndex(1) self._previousScene.changeImage(self._data.image_path(self._data.images_name[0])) self._currentScene.changeImage(self._data.image_path(self._data.images_name[1])) self.dataFileChanged(self._project.data_file) @pyqtSignature("int") def on_previousState_currentIndexChanged(self, index): #print "Previous image loaded: %s" % self._data.images[index] self.changeScene(self._previousScene, index) self._currentScene.changeImage(None) @pyqtSignature("int") def on_currentState_currentIndexChanged(self, index): #print "Current image loaded: %s" % self._data.images[index] self.changeScene(self._currentScene, index) def changeScene(self, scene, index): """ Set the scene to use the image number index. """ scene.changeImage(self._data.image_path(self._data.images_name[index])) @pyqtSignature("") def on_action_Save_triggered(self): self.save_data() @pyqtSignature("") def on_actionSave_as_triggered(self): fn = QFileDialog.getSaveFileName(self, "Select a data file to save in", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: self.save_data(path(fn)) def save_data(self, data_file=None): if self._data is None: raise TrackingDataException("Trying to save data when none have been loaded") try: self._project.save(data_file) return True except TrackingDataException as ex: showException(self, "Error while saving data", ex) return False def load_data(self, **opts): if self._project is None: raise TrackingDataException("Trying to load data when no project have been loaded") try: if self._project.load(**opts): log_debug("Data file was corrected. Need saving.") self.ui.action_Save.setEnabled(True) else: log_debug("Data file is clean.") self.ui.action_Save.setEnabled(False) return True except TrackingDataException as ex: showException(self, "Error while loading data", ex) return False except RetryTrackingDataException as ex: if retryException(self, "Problem while loading data", ex): new_opts = dict(opts) new_opts.update(ex.method_args) return self.load_data(**new_opts) return False def ensure_save_data(self, title, reason): if self._data is not None and not self.undo_stack.isClean(): button = QMessageBox.warning(self, title, reason, QMessageBox.Yes | QMessageBox.Save | QMessageBox.Cancel) if button == QMessageBox.Save: return self.save_data() elif button == QMessageBox.Cancel: return False self.undo_stack.clear() return True @pyqtSignature("") def on_action_Change_data_file_triggered(self): if self.ensure_save_data("Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change the current data file?"): fn = QFileDialog.getOpenFileName(self, "Select a data file to load", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: self._project.data_file = str(fn) if self.load_data(): self._previousScene.resetNewPoints() self._currentScene.resetNewPoints() @pyqtSignature("bool") def on_action_Show_vector_toggled(self, value): parameters.instance.show_vector = value self._currentScene.showVector(value) @pyqtSignature("bool") def on_action_Show_template_toggled(self, value): parameters.instance.show_template = value @pyqtSignature("bool") def on_actionShow_id_toggled(self, value): parameters.instance.show_id = value @pyqtSignature("") def on_action_Next_image_triggered(self): cur = self.ui.currentState.currentIndex() pre = self.ui.previousState.currentIndex() l = len(self._data.images_name) if cur < l-1 and pre < l-1: self.ui.previousState.setCurrentIndex(pre+1) self.ui.currentState.setCurrentIndex(cur+1) @pyqtSignature("") def on_action_Previous_image_triggered(self): cur = self.ui.currentState.currentIndex() pre = self.ui.previousState.currentIndex() if cur > 0 and pre > 0: self.ui.previousState.setCurrentIndex(pre-1) self.ui.currentState.setCurrentIndex(cur-1) @pyqtSignature("") def on_copyToPrevious_clicked(self): self._currentScene.copyFromLinked(self._previousScene) @pyqtSignature("") def on_copyToCurrent_clicked(self): self._previousScene.copyToLinked(self._currentScene) @pyqtSignature("bool") def on_action_Estimate_position_toggled(self, value): parameters.instance.estimate = value # @pyqtSignature("") # def on_action_Undo_triggered(self): # print "Undo" # @pyqtSignature("") # def on_action_Redo_triggered(self): # print "Redo" @pyqtSignature("bool") def on_action_Parameters_toggled(self, value): if value: from .parametersdlg import ParametersDlg self._previousScene.showTemplates() self._currentScene.showTemplates() #tracking_scene.saveParameters() parameters.instance.save() max_size = max(self._currentScene.width(), self._currentScene.height(), self._previousScene.width(), self._previousScene.height(), 400) self.param_dlg = ParametersDlg(max_size, self) self.param_dlg.setModal(False) self.ui.action_Pan.setChecked(True) self.ui.actionAdd_point.setEnabled(False) self.ui.action_Move_point.setEnabled(False) self.ui.actionAdd_cell.setEnabled(False) self.ui.actionRemove_cell.setEnabled(False) self.ui.action_Undo.setEnabled(False) self.ui.action_Redo.setEnabled(False) self.ui.action_Open_project.setEnabled(False) self.ui.actionRecent_projects.setEnabled(False) self.ui.action_Change_data_file.setEnabled(False) self.ui.copyToCurrent.setEnabled(False) self.ui.copyToPrevious.setEnabled(False) self.param_dlg.finished[int].connect(self.closeParam) self.param_dlg.show() elif self.param_dlg: self.param_dlg.accept() def closeParam(self, value): if value == QDialog.Rejected: parameters.instance.load() self.ui.actionAdd_point.setEnabled(True) self.ui.action_Move_point.setEnabled(True) self.ui.actionAdd_cell.setEnabled(True) self.ui.actionRemove_cell.setEnabled(True) self.ui.action_Undo.setEnabled(True) self.ui.action_Redo.setEnabled(True) self.ui.action_Open_project.setEnabled(True) self.ui.actionRecent_projects.setEnabled(True) self.ui.action_Change_data_file.setEnabled(True) self.ui.copyToCurrent.setEnabled(True) self.ui.copyToPrevious.setEnabled(True) self._previousScene.showTemplates(False) self._currentScene.showTemplates(False) self._previousScene.update() self._currentScene.update() self.param_dlg = None self.ui.action_Parameters.setChecked(False) @pyqtSignature("bool") def on_actionZoom_in_toggled(self, value): if value: self._previousScene.mode = TrackingScene.ZoomIn self._currentScene.mode = TrackingScene.ZoomIn @pyqtSignature("bool") def on_actionZoom_out_toggled(self, value): if value: self._previousScene.mode = TrackingScene.ZoomOut self._currentScene.mode = TrackingScene.ZoomOut #def resizeEvent(self, event): # self.ensureZoomFit() def ensureZoomFit(self): if self._data: prev_rect = self._previousScene.sceneRect() cur_rect = self._currentScene.sceneRect() prev_wnd = QRectF(self.ui.previousData.childrenRect()) cur_wnd = QRectF(self.ui.currentData.childrenRect()) prev_matrix = self.ui.previousData.matrix() cur_matrix = self.ui.currentData.matrix() prev_mapped_rect = prev_matrix.mapRect(prev_rect) cur_mapped_rect = cur_matrix.mapRect(cur_rect) if (prev_mapped_rect.width() < prev_wnd.width() or prev_mapped_rect.height() < prev_wnd.height() or cur_mapped_rect.width() < cur_wnd.width() or cur_mapped_rect.height() < cur_wnd.height()): self.on_action_Fit_triggered() @pyqtSignature("") def on_action_Fit_triggered(self): prev_rect = self._previousScene.sceneRect() cur_rect = self._currentScene.sceneRect() prev_wnd = self.ui.previousData.childrenRect() cur_wnd = self.ui.currentData.childrenRect() prev_sw = prev_wnd.width() / prev_rect.width() prev_sh = prev_wnd.height() / prev_rect.height() cur_sw = cur_wnd.width() / cur_rect.width() cur_sh = cur_wnd.height() / cur_rect.height() s = max(prev_sw, prev_sh, cur_sw, cur_sh) self.ui.previousData.resetMatrix() self.ui.previousData.scale(s, s) self.ui.currentData.resetMatrix() self.ui.currentData.scale(s, s) self.changeZoom(s) def zoomOut(self, point=None): self.ui.currentData.scale(0.5, 0.5) self.ui.previousData.scale(0.5, 0.5) self.changeZoom(self.ui.previousData.matrix().m11()) if point is not None: self.ui.previousData.centerOn(point) self.ui.currentData.centerOn(point) #self.ensureZoomFit() def zoomIn(self, point=None): self.ui.currentData.scale(2, 2) self.ui.previousData.scale(2, 2) self.changeZoom(self.ui.previousData.matrix().m11()) if point is not None: self.ui.previousData.centerOn(point) self.ui.currentData.centerOn(point) def changeZoom(self, zoom): self._zoom_label.setText("Zoom: %.5g%%" % (100*zoom)) @pyqtSignature("") def on_actionZoom_100_triggered(self): self.ui.previousData.resetMatrix() self.ui.currentData.resetMatrix() self.changeZoom(1) @pyqtSignature("bool") def on_actionAdd_point_toggled(self, value): if value: self._previousScene.mode = TrackingScene.Add self._currentScene.mode = TrackingScene.Add @pyqtSignature("bool") def on_actionAdd_cell_toggled(self, value): if value: self._previousScene.mode = TrackingScene.AddCell self._currentScene.mode = TrackingScene.AddCell @pyqtSignature("bool") def on_actionRemove_cell_toggled(self, value): if value: self._previousScene.mode = TrackingScene.RemoveCell self._currentScene.mode = TrackingScene.RemoveCell @pyqtSignature("bool") def on_action_Move_point_toggled(self, value): if value: self._previousScene.mode = TrackingScene.Move self._currentScene.mode = TrackingScene.Move @pyqtSignature("bool") def on_action_Pan_toggled(self, value): if value: self._previousScene.mode = TrackingScene.Pan self._currentScene.mode = TrackingScene.Pan @pyqtSignature("bool") def on_linkViews_toggled(self, value): parameters.instance.link_views = value phor = self.ui.previousData.horizontalScrollBar() pver = self.ui.previousData.verticalScrollBar() chor = self.ui.currentData.horizontalScrollBar() cver = self.ui.currentData.verticalScrollBar() if value: phor.valueChanged[int].connect(chor.setValue) pver.valueChanged[int].connect(cver.setValue) chor.valueChanged[int].connect(phor.setValue) cver.valueChanged[int].connect(pver.setValue) self._previousScene.templatePosChange.connect(self._currentScene.setTemplatePos) self._currentScene.templatePosChange.connect(self._previousScene.setTemplatePos) phor.setValue(chor.value()) pver.setValue(cver.value()) else: phor.valueChanged[int].disconnect(chor.setValue) pver.valueChanged[int].disconnect(cver.setValue) chor.valueChanged[int].disconnect(phor.setValue) cver.valueChanged[int].disconnect(pver.setValue) self._previousScene.templatePosChange.disconnect(self._currentScene.setTemplatePos) self._currentScene.templatePosChange.disconnect(self._previousScene.setTemplatePos) def copyFrom(self, start, items): if parameters.instance.estimate: dlg = createForm('copy_progress.ui', None) dlg.buttonBox.clicked["QAbstractButton*"].connect(self.cancelCopy) params = parameters.instance ts = params.template_size ss = params.search_size fs = params.filter_size self.copy_thread = algo.FindInAll(self._data, start, items, ts, ss, fs, self) dlg.imageProgress.setMaximum(self.copy_thread.num_images) self.copy_thread.start() self.copy_dlg = dlg dlg.exec_() else: algo.copyFromImage(self._data, start, items, self.undo_stack) def cancelCopy(self, *args): self.copy_thread.stop = True dlg = self.copy_dlg dlg.buttonBox.clicked['QAbstractButton*)'].disconnect(self.cancelCopy) self._previousScene.changeImage(None) self._currentScene.changeImage(None) def event(self, event): if isinstance(event, algo.NextImage): dlg = self.copy_dlg if dlg is not None: dlg.imageProgress.setValue(event.currentImage) dlg.pointProgress.setMaximum(event.nbPoints) dlg.pointProgress.setValue(0) return True elif isinstance(event, algo.NextPoint): dlg = self.copy_dlg if dlg is not None: dlg.pointProgress.setValue(event.currentPoint) return True elif isinstance(event, algo.FoundAll): dlg = self.copy_dlg if dlg is not None: self.cancelCopy() dlg.accept() return True elif isinstance(event, algo.Aborted): dlg = self.copy_dlg if dlg is not None: self.cancelCopy() dlg.accept() return True return QMainWindow.event(self, event) def itemsToCopy(self, scene): items = scene.getSelectedIds() if items: answer = QMessageBox.question(self, "Copy of points", "Some points were selected in the previous data window." " Do you want to copy only these point on the successive images?", QMessageBox.Yes, QMessageBox.No) if answer == QMessageBox.Yes: return items return scene.getAllIds() @pyqtSignature("") def on_actionCopy_from_previous_triggered(self): items = self.itemsToCopy(self._previousScene) if items: self.copyFrom(self.ui.previousState.currentIndex(), items) @pyqtSignature("") def on_actionCopy_from_current_triggered(self): items = self.itemsToCopy(self._currentScene) if items: self.copyFrom(self.ui.currentState.currentIndex(), items) @pyqtSignature("") def on_actionSelectPreviousAll_triggered(self): self._previousScene.selectAll() @pyqtSignature("") def on_actionSelectPreviousNew_triggered(self): self._previousScene.selectNew() @pyqtSignature("") def on_actionSelectPreviousNone_triggered(self): self._previousScene.selectNone() @pyqtSignature("") def on_actionSelectPreviousNon_associated_triggered(self): self._previousScene.selectNonAssociated() @pyqtSignature("") def on_actionSelectPreviousAssociated_triggered(self): self._previousScene.selectAssociated() @pyqtSignature("") def on_actionSelectPreviousInvert_triggered(self): self._previousScene.selectInvert() @pyqtSignature("") def on_actionSelectCurrentAll_triggered(self): self._currentScene.selectAll() @pyqtSignature("") def on_actionSelectCurrentNew_triggered(self): self._currentScene.selectNew() @pyqtSignature("") def on_actionSelectCurrentNone_triggered(self): self._currentScene.selectNone() @pyqtSignature("") def on_actionSelectCurrentNon_associated_triggered(self): self._currentScene.selectNonAssociated() @pyqtSignature("") def on_actionSelectCurrentAssociated_triggered(self): self._currentScene.selectAssociated() @pyqtSignature("") def on_actionSelectCurrentInvert_triggered(self): self._currentScene.selectInvert() def whichDelete(self): """ Returns a function deleting what the user wants """ dlg = createForm("deletedlg.ui", None) ret = dlg.exec_() if ret: if dlg.inAllImages.isChecked(): return TrackingScene.deleteInAllImages if dlg.toImage.isChecked(): return TrackingScene.deleteToImage if dlg.fromImage.isChecked(): return TrackingScene.deleteFromImage return lambda x: None @pyqtSignature("") def on_actionDelete_Previous_triggered(self): del_fct = self.whichDelete() del_fct(self._previousScene) @pyqtSignature("") def on_actionDelete_Current_triggered(self): del_fct = self.whichDelete() del_fct(self._currentScene) @pyqtSignature("") def on_actionMerge_points_triggered(self): if self._previousScene.mode == TrackingScene.AddCell: old_cell = self._previousScene.selected_cell new_cell = self._currentScene.selected_cell if old_cell is None or new_cell is None: QMessageBox.critical(self, "Cannot merge cells", "You have to select exactly one cell in the old state " "and one in the new state to merge them.") return try: if old_cell != new_cell: self.undo_stack.push(MergeCells(self._data, self._previousScene.image_name, old_cell, new_cell)) else: self.undo_stack.push(SplitCells(self._data, self._previousScene.image_name, old_cell, new_cell)) except AssertionError as error: QMessageBox.critical(self, "Cannot merge the cells", str(error)) else: old_pts = self._previousScene.getSelectedIds() new_pts = self._currentScene.getSelectedIds() if len(old_pts) != 1 or len(new_pts) != 1: QMessageBox.critical(self, "Cannot merge points", "You have to select exactly one point in the old state " "and one in the new state to link them.") return try: if old_pts != new_pts: self.undo_stack.push(ChangePointsId(self._data, self._previousScene.image_name, old_pts, new_pts)) else: log_debug("Splitting point of id %d" % old_pts[0]) self.undo_stack.push(SplitPointsId(self._data, self._previousScene.image_name, old_pts)) except AssertionError as error: QMessageBox.critical(self, "Cannot merge the points", str(error)) @pyqtSignature("") def on_actionCopy_selection_from_Current_triggered(self): cur_sel = self._currentScene.getSelectedIds() self._previousScene.setSelectedIds(cur_sel) @pyqtSignature("") def on_actionCopy_selection_from_Previous_triggered(self): cur_sel = self._previousScene.getSelectedIds() self._currentScene.setSelectedIds(cur_sel) @pyqtSignature("") def on_actionNew_data_file_triggered(self): if self.ensure_save_data("Leaving unsaved data", "The last modifications you made were not saved." " Are you sure you want to change the current data file?"): fn = QFileDialog.getSaveFileName(self, "Select a new data file to create", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: fn = path(fn) if fn.exists(): button = QMessageBox.question(self, "Erasing existing file", "Are you sure yo want to empty the file '%s' ?" % fn, QMessageBox.Yes, QMessageBox.No) if button == QMessageBox.No: return fn.remove() self._data.clear() self._previousScene.resetNewPoints() self._currentScene.resetNewPoints() self._project.data_file = fn self.initFromData() log_debug("Data file = %s" % (self._project.data_file,)) @pyqtSignature("") def on_actionAbout_triggered(self): dlg = QMessageBox(self) dlg.setWindowTitle("About Point Tracker") dlg.setIconPixmap(self.windowIcon().pixmap(64, 64)) #dlg.setTextFormat(Qt.RichText) dlg.setText("""Point Tracker Tool version %s rev %s Developper: Pierre Barbier de Reuille <*****@*****.**> Copyright 2008 """ % (__version__, __revision__)) img_read = ", ".join(str(s) for s in QImageReader.supportedImageFormats()) img_write = ", ".join(str(s) for s in QImageWriter.supportedImageFormats()) dlg.setDetailedText("""Supported image formats: - For reading: %s - For writing: %s """ % (img_read, img_write)) dlg.exec_() @pyqtSignature("") def on_actionAbout_Qt_triggered(self): QMessageBox.aboutQt(self, "About Qt") @pyqtSignature("") def on_actionReset_alignment_triggered(self): self.undo_stack.push(ResetAlignment(self._data)) @pyqtSignature("") def on_actionAlign_images_triggered(self): fn = QFileDialog.getOpenFileName(self, "Select a data file for alignment", self._project.data_dir, "CSV Files (*.csv);;All files (*.*)") if fn: d = self._data.copy() fn = path(fn) try: d.load(fn) except TrackingDataException as ex: showException(self, "Error while loading data file", ex) return if d._last_pt_id > 0: dlg = AlignmentDlg(d._last_pt_id+1, self) if dlg.exec_(): ref = dlg.ui.referencePoint.currentText() try: ref = int(ref) except ValueError: ref = str(ref) if dlg.ui.twoPointsRotation.isChecked(): r1 = int(dlg.ui.rotationPt1.currentText()) r2 = int(dlg.ui.rotationPt2.currentText()) rotation = ("TwoPoint", r1, r2) else: rotation = None else: return else: ref = 0 rotation = None try: shifts, angles = algo.alignImages(self._data, d, ref, rotation) self.undo_stack.push(AlignImages(self._data, shifts, angles)) except algo.AlgoException as ex: showException(self, "Error while aligning images", ex) def sceneSizeChanged(self): previous_rect = self._previousScene.real_scene_rect current_rect = self._currentScene.real_scene_rect rect = previous_rect | current_rect self._previousScene.setSceneRect(rect) self._currentScene.setSceneRect(rect) @pyqtSignature("") def on_actionEdit_timing_triggered(self): data = self._data dlg = TimeEditDlg(data.images_name, data.images_time, [data.image_path(n) for n in data.images_name], self) self.current_dlg = dlg if dlg.exec_() == QDialog.Accepted: self.undo_stack.push(ChangeTiming(data, [t for n, t in dlg.model])) del self.current_dlg @pyqtSignature("") def on_actionEdit_scales_triggered(self): data = self._data dlg = EditResDlg(data.images_name, data.images_scale, [data.image_path(n) for n in data.images_name], self) self.current_dlg = dlg if dlg.exec_() == QDialog.Accepted: self.undo_stack.push(ChangeScales(data, [sc for n, sc in dlg.model])) del self.current_dlg @pyqtSignature("") def on_actionCompute_growth_triggered(self): data = self._data dlg = GrowthComputationDlg(data, self) self.current_dlg = dlg dlg.exec_() del self.current_dlg @pyqtSignature("") def on_actionPlot_growth_triggered(self): data = self._data dlg = PlottingDlg(data, self) self.current_dlg = dlg dlg.exec_() del self.current_dlg @pyqtSignature("") def on_actionClean_cells_triggered(self): self.undo_stack.push(CleanCells(self._data)) @pyqtSignature("") def on_actionGotoCell_triggered(self): cells = [str(cid) for cid in self._data.cells] selected, ok = QInputDialog.getItem(self, "Goto cell", "Select the cell to go to", cells, 0) if ok: cid = int(selected) self.ui.actionAdd_cell.setChecked(True) data = self._data if cid not in data.cells: return ls = data.cells_lifespan[cid] prev_pos = self._previousScene.current_data._current_index cur_pos = self._currentScene.current_data._current_index full_poly = data.cells[cid] poly = [pid for pid in full_poly if pid in data[prev_pos]] #log_debug("Cell %d on time %d: %s" % (cid, prev_pos, poly)) if prev_pos < ls.start or prev_pos >= ls.end or not poly: for i in range(*ls.slice().indices(len(data))): poly = [pid for pid in full_poly if pid in data[i]] if poly: log_debug("Found cell %d on image %d with polygon %s" % (cid, i, poly)) new_prev_pos = i break else: log_debug("Cell %d found nowhere in range %s!!!" % (cid, ls.slice())) else: new_prev_pos = prev_pos new_cur_pos = min(max(cur_pos + new_prev_pos - prev_pos, 0), len(data)) self.ui.previousState.setCurrentIndex(new_prev_pos) self.ui.currentState.setCurrentIndex(new_cur_pos) self._previousScene.current_cell = cid self._currentScene.current_cell = cid prev_data = self._previousScene.current_data poly = data.cells[cid] prev_poly = QPolygonF([prev_data[ptid] for ptid in poly if ptid in prev_data]) prev_bbox = prev_poly.boundingRect() log_debug("Previous bounding box = %dx%d+%d+%d" % (prev_bbox.width(), prev_bbox.height(), prev_bbox.left(), prev_bbox.top())) self.ui.previousData.ensureVisible(prev_bbox) @pyqtSignature("") def on_actionGotoPoint_triggered(self): data = self._data points = [str(pid) for pid in data.cell_points] selected, ok = QInputDialog.getItem(self, "Goto point", "Select the point to go to", points, 0) if ok: pid = int(selected) self.ui.action_Move_point.setChecked(True) if pid not in data.cell_points: return prev_pos = self._previousScene.current_data._current_index cur_pos = self._currentScene.current_data._current_index prev_data = self._previousScene.current_data if not pid in prev_data: closest = -1 best_dist = len(data)+1 for img_data in data: if pid in img_data: dist = abs(img_data._current_index - prev_pos) if dist < best_dist: best_dist = dist closest = img_data._current_index new_prev_pos = closest else: new_prev_pos = prev_pos new_cur_pos = min(max(cur_pos + new_prev_pos - prev_pos, 0), len(data)) self.ui.previousState.setCurrentIndex(new_prev_pos) self.ui.currentState.setCurrentIndex(new_cur_pos) self._previousScene.setSelectedIds([pid]) self._currentScene.setSelectedIds([pid]) self.ui.previousData.centerOn(self._previousScene.current_data[pid])
class ToolBox(QFrame): """ A tool box widget. """ # Emitted when a tab is toggled. tabToogled = Signal(int, bool) def setExclusive(self, exclusive): """ Set exclusive tabs (only one tab can be open at a time). """ if self.__exclusive != exclusive: self.__exclusive = exclusive self.__tabActionGroup.setExclusive(exclusive) checked = self.__tabActionGroup.checkedAction() if checked is None: # The action group can be out of sync with the actions state # when switching between exclusive states. actions_checked = [page.action for page in self.__pages if page.action.isChecked()] if actions_checked: checked = actions_checked[0] # Trigger/toggle remaining open pages if exclusive and checked is not None: for page in self.__pages: if checked != page.action and page.action.isChecked(): page.action.trigger() def exclusive(self): """ Are the tabs in the toolbox exclusive. """ return self.__exclusive exclusive_ = Property(bool, fget=exclusive, fset=setExclusive, designable=True, doc="Exclusive tabs") def __init__(self, parent=None, **kwargs): QFrame.__init__(self, parent, **kwargs) self.__pages = [] self.__tabButtonHeight = -1 self.__tabIconSize = QSize() self.__exclusive = False self.__setupUi() def __setupUi(self): layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) # Scroll area for the contents. self.__scrollArea = \ _ToolBoxScrollArea(self, objectName="toolbox-scroll-area") self.__scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.__scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.__scrollArea.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.__scrollArea.setFrameStyle(QScrollArea.NoFrame) self.__scrollArea.setWidgetResizable(True) # A widget with all of the contents. # The tabs/contents are placed in the layout inside this widget self.__contents = QWidget(self.__scrollArea, objectName="toolbox-contents") # The layout where all the tab/pages are placed self.__contentsLayout = QVBoxLayout() self.__contentsLayout.setContentsMargins(0, 0, 0, 0) self.__contentsLayout.setSizeConstraint(QVBoxLayout.SetMinAndMaxSize) self.__contentsLayout.setSpacing(0) self.__contents.setLayout(self.__contentsLayout) self.__scrollArea.setWidget(self.__contents) layout.addWidget(self.__scrollArea) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) self.__tabActionGroup = \ QActionGroup(self, objectName="toolbox-tab-action-group") self.__tabActionGroup.setExclusive(self.__exclusive) self.__actionMapper = QSignalMapper(self) self.__actionMapper.mapped[QObject].connect(self.__onTabActionToogled) def setTabButtonHeight(self, height): """ Set the tab button height. """ if self.__tabButtonHeight != height: self.__tabButtonHeight = height for page in self.__pages: page.button.setFixedHeight(height) def tabButtonHeight(self): """ Return the tab button height. """ return self.__tabButtonHeight def setTabIconSize(self, size): """ Set the tab button icon size. """ if self.__tabIconSize != size: self.__tabIconSize = size for page in self.__pages: page.button.setIconSize(size) def tabIconSize(self): """ Return the tab icon size. """ return self.__tabIconSize def tabButton(self, index): """ Return the tab button at `index` """ return self.__pages[index].button def tabAction(self, index): """ Return open/close action for the tab at `index`. """ return self.__pages[index].action def addItem(self, widget, text, icon=None, toolTip=None): """ Append the `widget` in a new tab and return its index. Parameters ---------- widget : :class:`QWidget` A widget to be inserted. The toolbox takes ownership of the widget. text : str Name/title of the new tab. icon : :class:`QIcon`, optional An icon for the tab button. toolTip : str, optional Tool tip for the tab button. """ return self.insertItem(self.count(), widget, text, icon, toolTip) def insertItem(self, index, widget, text, icon=None, toolTip=None): """ Insert the `widget` in a new tab at position `index`. See also -------- ToolBox.addItem """ button = self.createTabButton(widget, text, icon, toolTip) self.__contentsLayout.insertWidget(index * 2, button) self.__contentsLayout.insertWidget(index * 2 + 1, widget) widget.hide() page = _ToolBoxPage(index, widget, button.defaultAction(), button) self.__pages.insert(index, page) for i in range(index + 1, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) self.__updatePositions() # Show (open) the first tab. if self.count() == 1 and index == 0: page.action.trigger() self.__updateSelected() self.updateGeometry() return index def removeItem(self, index): """ Remove the widget at `index`. .. note:: The widget hidden but is is not deleted. """ self.__contentsLayout.takeAt(2 * index + 1) self.__contentsLayout.takeAt(2 * index) page = self.__pages.pop(index) # Update the page indexes for i in range(index, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) page.button.deleteLater() # Hide the widget and reparent to self # This follows QToolBox.removeItem page.widget.hide() page.widget.setParent(self) self.__updatePositions() self.__updateSelected() self.updateGeometry() def count(self): """ Return the number of widgets inserted in the toolbox. """ return len(self.__pages) def widget(self, index): """ Return the widget at `index`. """ return self.__pages[index].widget def createTabButton(self, widget, text, icon=None, toolTip=None): """ Create the tab button for `widget`. """ action = QAction(text, self) action.setCheckable(True) if icon: action.setIcon(icon) if toolTip: action.setToolTip(toolTip) self.__tabActionGroup.addAction(action) self.__actionMapper.setMapping(action, action) action.toggled.connect(self.__actionMapper.map) button = ToolBoxTabButton(self, objectName="toolbox-tab-button") button.setDefaultAction(action) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) if self.__tabIconSize.isValid(): button.setIconSize(self.__tabIconSize) if self.__tabButtonHeight > 0: button.setFixedHeight(self.__tabButtonHeight) return button def ensureWidgetVisible(self, child, xmargin=50, ymargin=50): """ Scroll the contents so child widget instance is visible inside the viewport. """ self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin) def sizeHint(self): hint = self.__contentsLayout.sizeHint() if self.count(): # Compute max width of hidden widgets also. scroll = self.__scrollArea scroll_w = scroll.verticalScrollBar().sizeHint().width() frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2 max_w = max([p.widget.sizeHint().width() for p in self.__pages]) hint = QSize(max(max_w, hint.width()) + scroll_w + frame_w, hint.height()) return QSize(200, 200).expandedTo(hint) def __onTabActionToogled(self, action): page = find(self.__pages, action, key=attrgetter("action")) on = action.isChecked() page.widget.setVisible(on) index = page.index if index > 0: # Update the `previous` tab buttons style hints previous = self.__pages[index - 1].button flag = QStyleOptionToolBoxV2.NextIsSelected if on: previous.selected |= flag else: previous.selected &= ~flag previous.update() if index < self.count() - 1: next = self.__pages[index + 1].button flag = QStyleOptionToolBoxV2.PreviousIsSelected if on: next.selected |= flag else: next.selected &= ~flag next.update() self.tabToogled.emit(index, on) self.__contentsLayout.invalidate() def __updateSelected(self): """Update the tab buttons selected style flags. """ if self.count() == 0: return opt = QStyleOptionToolBoxV2 def update(button, next_sel, prev_sel): if next_sel: button.selected |= opt.NextIsSelected else: button.selected &= ~opt.NextIsSelected if prev_sel: button.selected |= opt.PreviousIsSelected else: button.selected &= ~ opt.PreviousIsSelected button.update() if self.count() == 1: update(self.__pages[0].button, False, False) elif self.count() >= 2: pages = self.__pages for i in range(1, self.count() - 1): update(pages[i].button, pages[i + 1].action.isChecked(), pages[i - 1].action.isChecked()) def __updatePositions(self): """Update the tab buttons position style flags. """ if self.count() == 0: return elif self.count() == 1: self.__pages[0].button.position = QStyleOptionToolBoxV2.OnlyOneTab else: self.__pages[0].button.position = QStyleOptionToolBoxV2.Beginning self.__pages[-1].button.position = QStyleOptionToolBoxV2.End for p in self.__pages[1:-1]: p.button.position = QStyleOptionToolBoxV2.Middle for p in self.__pages: p.button.update()
class Context(QDialog): """ Context dialog. Responsoble for viewing full thread. There are a "bug" here - I have to duplicate some functions from main module due to architecture fail. I will fix that, soon. Parameters: @auth - connector instance for communication with statusnet installation @conversation_id - conversation id. Nuff said. @settings - QTDenter settings @self.version - QTDenter self.version """ def __init__(self, auth, conversation_id, settings, version, parent = None): QDialog.__init__(self, parent) self.ui = cg.Ui_Dialog() self.ui.setupUi(self) self.settings = settings self.auth = auth self.version = version # Initialize signal mappers for buttons and lists for buttons pointers self.context_buttons_mapper = QSignalMapper(self) self.context_buttons_list = [] self.destroy_buttons_mapper = QSignalMapper(self) self.destroy_buttons_list = [] self.redent_buttons_mapper = QSignalMapper(self) self.redent_buttons_list = [] self.like_buttons_mapper = QSignalMapper(self) self.like_buttons_list = [] self.dentid_buttons_mapper = QSignalMapper(self) self.dentid_buttons_list = [] self.ui.context.setSortingEnabled(True) self.ui.context.sortByColumn(2, Qt.DescendingOrder) for column in range(2, 6): self.ui.context.setColumnHidden(column, True) self.ui.context.setColumnWidth(0, 65) self.ui.context.itemActivated.connect(self.reply_to_dent) conversation = self.auth.get_conversation(conversation_id) self.list_handler = list_handler.List_Handler(self.callback) self.list_item = list_item.list_item() self.list_handler.add_data("conversation", conversation, self.settings["server"]) self.connect_buttons() def connect_buttons(self): print "Connecting buttons" for item in self.context_buttons_list: self.context_buttons_mapper.setMapping(item[0], item[1]) item[0].clicked.connect(self.context_buttons_mapper.map) for item in self.destroy_buttons_list: self.destroy_buttons_mapper.setMapping(item[0], item[1]) item[0].clicked.connect(self.destroy_buttons_mapper.map) for item in self.redent_buttons_list: self.redent_buttons_mapper.setMapping(item[0], item[1]) item[0].clicked.connect(self.redent_buttons_mapper.map) for item in self.like_buttons_list: self.like_buttons_mapper.setMapping(item[0], item[1]) item[0].clicked.connect(self.like_buttons_mapper.map) for item in self.dentid_buttons_list: self.dentid_buttons_mapper.setMapping(item[0], item[1]) item[0].clicked.connect(self.dentid_buttons_mapper.map) self.destroy_buttons_mapper.mapped.connect(self.delete_dent) self.redent_buttons_mapper.mapped.connect(self.redent_dent) self.like_buttons_mapper.mapped.connect(self.like_dent) self.dentid_buttons_mapper.mapped.connect(self.go_to_dent) def callback(self, dent_type, data, server): """ List callback """ if dent_type == "end": pass else: self.add_dent(data, dent_type) def add_dent(self, data, dent_type): """ Add dent to list widget """ item_data = self.list_item.process_item(data, self.settings["last_dent_id"], self.settings["user"], self.settings["server"], dent_type) item = item_data[0] avatar_widget = item_data[1] post_widget = item_data[2] destroy_button = avatar_widget.findChild(QPushButton, "destroy_button_" + str(data["id"])) dentid_button = post_widget.findChild(QPushButton, "dentid_button_" + str(data["id"])) redent_button = post_widget.findChild(QPushButton, "redent_button_" + str(data["id"])) like_button = post_widget.findChild(QPushButton, "like_button_" + str(data["id"])) # Adding buttons pointers to list for later mapping self.destroy_buttons_list.append([destroy_button, data["id"]]) self.dentid_buttons_list.append([dentid_button, data["id"]]) self.redent_buttons_list.append([redent_button, data["id"]]) self.like_buttons_list.append([like_button, data["id"]]) # If current dent is not self-posted - hide "Delete" button. if not data["nickname"] == self.settings["user"]: destroy_button.hide() else: redent_button.hide() if data["retweeted"]: redent_button.hide() elif data["nickname"] != self.settings["user"] and data["retweeted"]: redent_button.show() self.ui.context.addTopLevelItem(item) self.ui.context.setItemWidget(item, 0, avatar_widget) self.ui.context.setItemWidget(item, 1, post_widget) def like_dent(self, dent_id): """ Like dent button callback """ # Search for item that contain pressed "Like" button, get dent id, # and send a request to connector for dent like. try: root = self.ui.context.invisibleRootItem() count = root.childCount() for index in range(count): item = root.child(index) if item.text(2).split(":")[0] == str(dent_id): btn = self.ui.context.findChild(QPushButton, "like_button_" + str(dent_id)) btn.setText("...") if item.text(3) == "not": data = self.auth.favoritize_dent(dent_id, self.version) if data["favorited"]: item.setText(3, "favorited") btn.setText("X") else: btn.setText(u"\u2665") break else: data = self.auth.defavoritize_dent(dent_id, self.version) if not data["favorited"]: item.setText(3, "not") btn.setText(u"\u2665") else: btn.setText("X") break except: QMessageBox.critical(self, "QTDenter - Choose dent first!", "You have to choose dent") def redent_dent(self, dent_id): """ Redent dent button callback """ # Search for item that contain pressed "Redent" button, get dent id, # and send a request to connector for dent redenting. try: root = self.ui.context.invisibleRootItem() count = root.childCount() for index in range(count): item = root.child(index) if item.text(2).split(":")[0] == str(dent_id): print "FOUND!" data = self.auth.redent_dent(dent_id, self.version) if data != "FAIL": btn = self.ui.context.findChild(QPushButton, "redent_button_" + str(dent_id)) btn.hide() break except: QMessageBox.critical(self, "QTDenter - Choose dent first!", "You have to choose dent") def delete_dent(self, dent_id): """ Delete dent button callback """ # Search for item that contain pressed "Delete" button, get dent id, # and send a request to connector for dent deletion. try: root = self.ui.context.invisibleRootItem() count = root.childCount() for index in range(count): item = root.child(index) if item.text(2).split(":")[0] == str(dent_id): data = self.auth.delete_dent(dent_id) print data if data == "OK": index = self.ui.context.indexOfTopLevelItem(item) self.ui.context.takeTopLevelItem(index) break except: QMessageBox.critical(self, "QTDenter - Choose dent first!", "You have to choose dent") def reply_to_dent(self): """ Reply to dent """ try: dent_id = self.ui.context.currentItem().text(2).split(":")[0] to_username = self.ui.context.currentItem().text(2).split(":")[1] dent_text = self.ui.context.currentItem().text(4) params = {} params["type"] = "reply" params["reply_to_id"] = dent_id params["nickname"] = to_username params["text"] = dent_text newpostD = new_post.New_Post(self.settings["messageLength"], params, self.new_post_callback) newpostD.exec_() except: QMessageBox.critical(self, "QTDenter - Choose dent first!", "You have to choose dent") def go_to_dent(self, dent_id): """ Callback for ID button """ try: root = self.ui.context.invisibleRootItem() count = root.childCount() for index in range(count): item = root.child(index) if item.text(2).split(":")[0] == str(dent_id): server_address = self.settings["server"] if self.settings["useSecureConnection"] == 1: server_address = "https://" + server_address else: server_address = "http://" + server_address QDesktopServices.openUrl(QUrl(server_address + "/notice/" + str(dent_id))) except: QMessageBox.critical(self, "QTDenter - Choose dent first!", "You have to choose dent") def send_reply(self, data): """ Send reply """ data = self.auth.send_reply(data, self.version) self.list_handler.add_data("home", [data]) def new_post_callback(self, type, data): """ Callback for new post dialog. """ if type == "send_reply": self.send_reply(data)
class MainApp(QDockWidget, QMainWindow, Ui_MainApp): # signals goBack = pyqtSignal() searchOpsubByName = pyqtSignal(str) enableSearch = pyqtSignal(bool) refreshLegend = pyqtSignal(QgsMapLayer) ogrDatasourceLoaded = pyqtSignal(bool) class VfkLayer(object): Par = 0 Bud = 1 def __init__(self, iface): QDockWidget.__init__(self, iface.mainWindow()) self.setupUi(self) self.iface = iface # variables self.__mLastVfkFile = [] self.__mOgrDataSource = None self.__mDataSourceName = '' self.__fileName = [] self.__mLoadedLayers = {} self.__mDefaultPalette = self.vfkFileLineEdit.palette() # new lineEdits variables self.lineEditsCount = 1 self.__browseButtons = {} self.__vfkLineEdits = {} # data will be load from source according to checked radiobox self.__source_for_data = 'file' # apply changes into main database self.__databases = {} # self.pb_applyChanges.setEnabled(False) self.changes_instance = ApplyChanges() # Connect ui with functions self.__createToolbarsAndConnect() # check GDAL version self.__gdal_version = int(gdal.VersionInfo()) if self.__gdal_version < 2020000: self.actionZpracujZmeny.setEnabled(False) self.pb_nextFile.setEnabled(False) self.pb_nextFile.setToolTip( u'Není možné načíst více souborů, verze GDAL je nižší než 2.2.0.') self.actionZpracujZmeny.setToolTip(u'Zpracování změn není povoleno, verze GDAL je nižší než 2.2.0.') self.groupBox.setEnabled(False) # settings self.loadVfkButton.setDisabled(True) self.searchFormMainControls = SearchFormController.MainControls() self.searchFormMainControls.formCombobox = self.searchCombo self.searchFormMainControls.searchForms = self.searchForms self.searchFormMainControls.searchButton = self.searchButton self.searchForms = SearchFormController.SearchForms() self.searchForms.vlastnici = self.vlastniciSearchForm self.searchForms.parcely = self.parcelySearchForm self.searchForms.budovy = self.budovySearchForm self.searchForms.jednotky = self.jednotkySearchForm # search form controller self.__mSearchController = SearchFormController( self.searchFormMainControls, self.searchForms, self) self.connect(self.__mSearchController, SIGNAL( "actionTriggered(QUrl)"), self.vfkBrowser.processAction) self.connect( self, SIGNAL("enableSearch"), self.searchButton.setEnabled) self.connect(self.vfkBrowser, SIGNAL("showParcely"), self.showParInMap) self.connect(self.vfkBrowser, SIGNAL("showBudovy"), self.showBudInMap) # connect lineEdits and returnPressed action self.connect(self.vfkFileLineEdit, SIGNAL( "returnPressed()"), self.loadVfkButton_clicked) self.connect(self.vlastniciSearchForm.ui.jmenoLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.vlastniciSearchForm.ui.rcIcoLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.vlastniciSearchForm.ui.lvVlastniciLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.parcelySearchForm.ui.parCisloLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.parcelySearchForm.ui.lvParcelyLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.budovySearchForm.ui.cisloDomovniLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.budovySearchForm.ui.naParceleLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.budovySearchForm.ui.lvBudovyLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.jednotkySearchForm.ui.mCisloJednotkyLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.jednotkySearchForm.ui.mCisloDomovniLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.jednotkySearchForm.ui.mNaParceleLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.connect(self.jednotkySearchForm.ui.mLvJednotkyLineEdit, SIGNAL("returnPressed()"), self.__mSearchController.search) self.vfkBrowser.showHelpPage() def browseButton_clicked(self, browseButton_id=1): """ :param browseButton_id: ID of clicked browse button. :return: """ title = u'Načti soubor VFK' settings = QSettings() lastUsedDir = '' if self.__source_for_data == 'file': ext = '*.vfk' if self.__gdal_version >= 2020000: ext += ' *.db' loaded_file = QFileDialog.getOpenFileName( self, title, lastUsedDir, u'Soubory podporované ovladačem VFK GDAL ({})'.format(ext)) if not loaded_file: return else: self.__fileName.append(loaded_file) if browseButton_id == 1: self.vfkFileLineEdit.setText(self.__fileName[0]) else: self.__vfkLineEdits['vfkLineEdit_{}'.format(len(self.__vfkLineEdits))].setText( self.__fileName[browseButton_id - 1]) elif self.__source_for_data == 'directory': loaded_file = QFileDialog.getExistingDirectory(self, u"Vyberte adresář s daty VFK") if not loaded_file: return else: self.__fileName = [] self.__fileName.append(loaded_file) self.vfkFileLineEdit.setText(self.__fileName[0]) else: qDebug('(VFK) Not valid source') self.loadVfkButton.setEnabled(True) def browserGoBack(self): self.vfkBrowser.goBack() def browserGoForward(self): self.vfkBrowser.goForth() def selectParInMap(self): self.showInMap(self.vfkBrowser.currentParIds(), "PAR") def selectBudInMap(self): self.showInMap(self.vfkBrowser.currentBudIds(), "BUD") def latexExport(self): fileName = QFileDialog.getSaveFileName( self, u"Jméno exportovaného souboru", ".tex", "LaTeX (*.tex)") if fileName: export_succesfull = self.vfkBrowser.exportDocument( self.vfkBrowser.currentUrl(), fileName, self.vfkBrowser.ExportFormat.Latex) if export_succesfull: self.succesfullExport("LaTeX") def htmlExport(self): fileName = QFileDialog.getSaveFileName( self, u"Jméno exportovaného souboru", ".html", "HTML (*.html)") if fileName: export_succesfull = self.vfkBrowser.exportDocument( self.vfkBrowser.currentUrl(), fileName, self.vfkBrowser.ExportFormat.Html) if export_succesfull: self.succesfullExport("HTML") def setSelectionChangedConnected(self, connected): """ :type connected: bool :return: """ for layer in self.__mLoadedLayers: id = self.__mLoadedLayers[layer] vectorLayer = QgsMapLayerRegistry.instance().mapLayer(id) if connected: self.connect( vectorLayer, SIGNAL("selectionChanged()"), self.showInfoAboutSelection) else: self.disconnect( vectorLayer, SIGNAL("selectionChanged()"), self.showInfoAboutSelection) def showInMap(self, ids, layerName): """ :type ids: list :type layerName: str :return: """ if layerName in self.__mLoadedLayers: id = self.__mLoadedLayers[layerName] vectorLayer = QgsMapLayerRegistry.instance().mapLayer(id) searchString = "ID IN ({})".format(", ".join(ids)) error = '' fIds = self.__search(vectorLayer, searchString, error) if error: qDebug('\n (VFK) ERROR in showInMap: {}'.format(error)) return else: vectorLayer.setSelectedFeatures(fIds) def __search(self, layer, searchString, error): """ :type layer: QgsVectorLayer :type searchString: str :type error: str :return: """ # parse search string and build parsed tree search = QgsExpression(searchString) rect = QgsRectangle() fIds = [] if search.hasParserError(): error += "Parsing error:" + search.parserErrorString() return fIds if not search.prepare(layer.pendingFields()): error + "Evaluation error:" + search.evalErrorString() layer.select(rect, False) fit = QgsFeatureIterator(layer.getFeatures()) f = QgsFeature() while fit.nextFeature(f): if search.evaluate(f): fIds.append(f.id()) # check if there were errors during evaluating if search.hasEvalError(): qDebug('\n (VFK) Evaluate error: {}'.format(error)) break return fIds def loadVfkButton_clicked(self): """ After click method starts loading all inserted files """ # check the source of data if self.__source_for_data == 'directory': dir_path = self.__fileName[0] self.__fileName = self.__findVFKFilesInDirectory(dir_path) # check if first file is amendment amendment_file = self.__checkIfAmendmentFile(self.__fileName[0]) # prepare name for database if amendment_file: new_database_name = '{}_zmeny.db'.format(os.path.basename(self.__fileName[0]).split('.')[0]) else: new_database_name = '{}_stav.db'.format(os.path.basename(self.__fileName[0]).split('.')[0]) os.environ['OGR_VFK_DB_NAME'] = os.path.join( os.path.dirname(os.path.dirname(self.__fileName[0])), new_database_name) self.__mDataSourceName = self.__fileName[0] # os.environ['OGR_VFK_DB_NAME'] QgsApplication.processEvents() self.importThread = OpenThread(self.__fileName) self.importThread.working.connect(self.runLoadingLayer) if not self.importThread.isRunning(): self.importThread.start() def runLoadingLayer(self, fileName): """ :return: """ if fileName not in self.__mLastVfkFile: self.labelLoading.setText( u'Načítám data do SQLite databáze (může nějaký čas trvat...)') try: self.loadVfkFile(fileName) except VFKError as e: QMessageBox.critical( self, u'Chyba', u'{}'.format(e), QMessageBox.Ok) self.emit(SIGNAL("enableSearch"), False) return self.__mLastVfkFile.append(fileName) self.importThread.nextLayer = False if fileName == self.__fileName[-1]: self.loadingLayersFinished() def loadingLayersFinished(self): """ :return: """ try: self.__openDatabase( os.environ['OGR_VFK_DB_NAME']) # self.__mDataSourceName) except VFKError as e: QMessageBox.critical( self, u'Chyba', u'{}'.format(e), QMessageBox.Ok) self.emit(SIGNAL("enableSearch"), False) return self.vfkBrowser.setConnectionName(self.property("connectionName")) self.__mSearchController.setConnectionName( self.property("connectionName")) self.emit(SIGNAL("enableSearch"), True) self.__mLoadedLayers.clear() if self.parCheckBox.isChecked(): self.__loadVfkLayer('PAR') else: self.__unLoadVfkLayer('PAR') if self.budCheckBox.isChecked(): self.__loadVfkLayer('BUD') else: self.__unLoadVfkLayer('BUD') self.labelLoading.setText(u'Načítání souborů VFK bylo dokončeno.') def vfkFileLineEdit_textChanged(self, arg1): """ :type arg1: str :return: """ info = QFileInfo(arg1) if info.isFile(): self.loadVfkButton.setEnabled(True) self.vfkFileLineEdit.setPalette(self.__mDefaultPalette) else: self.loadVfkButton.setEnabled(False) pal = QPalette(self.vfkFileLineEdit.palette()) pal.setColor(QPalette.text(), Qt.red) self.vfkFileLineEdit.setPalette(pal) def __loadVfkLayer(self, vfkLayerName): """ :type vfkLayerName: str :return: """ qDebug("\n(VFK) Loading vfk layer {}".format(vfkLayerName)) if vfkLayerName in self.__mLoadedLayers: qDebug( "\n(VFK) Vfk layer {} is already loaded".format(vfkLayerName)) return composedURI = self.__mDataSourceName + "|layername=" + vfkLayerName layer = QgsVectorLayer(composedURI, vfkLayerName, "ogr") if not layer.isValid(): qDebug("\n(VFK) Layer failed to load!") self.__mLoadedLayers[vfkLayerName] = layer.id() try: self.__setSymbology(layer) except VFKWarning as e: QMessageBox.information(self, 'Load Style', e, QMessageBox.Ok) QgsMapLayerRegistry.instance().addMapLayer(layer) def __unLoadVfkLayer(self, vfkLayerName): """ :type vfkLayerName: str :return: """ qDebug("\n(VFK) Unloading vfk layer {}".format(vfkLayerName)) if vfkLayerName not in self.__mLoadedLayers: qDebug( "\n(VFK) Vfk layer {} is already unloaded".format(vfkLayerName)) return QgsMapLayerRegistry.instance().removeMapLayer( self.__mLoadedLayers[vfkLayerName]) del self.__mLoadedLayers[vfkLayerName] def __setSymbology(self, layer): """ :type layer: QgsVectorLayer :return: """ name = layer.name() symbologyFile = '' if name == 'PAR': symbologyFile = ':/parStyle.qml' elif name == 'BUD': symbologyFile = ':/budStyle.qml' errorMsg, resultFlag = layer.loadNamedStyle(symbologyFile) if not resultFlag: raise VFKWarning(u'Load style: {}'.format(errorMsg)) layer.triggerRepaint() self.emit(SIGNAL("refreshLegend"), layer) def __openDatabase(self, dbPath): """ :type dbPath: str :return: """ qDebug("\n(VFK) Open DB: {}".format(dbPath)) if not QSqlDatabase.isDriverAvailable('QSQLITE'): raise VFKError(u'Databázový ovladač QSQLITE není dostupný.') connectionName = QUuid.createUuid().toString() db = QSqlDatabase.addDatabase("QSQLITE", connectionName) db.setDatabaseName(dbPath) if not db.open(): raise VFKError(u'Nepodařilo se otevřít databázi. ') self.setProperty("connectionName", connectionName) def loadVfkFile(self, fileName): """ :type fileName: str :return: """ label_text = fileName.split('/') label_text = '...' + label_text[-2] + '/' + label_text[-1] # overwrite database if fileName == self.__fileName[0]: if self.overwriteCheckBox.isChecked(): qDebug('\n (VFK) Database will be overwritten') os.environ['OGR_VFK_DB_OVERWRITE'] = '1' if self.__mOgrDataSource: self.__mOgrDataSource.Destroy() self.__mOgrDataSource = None QgsApplication.registerOgrDrivers() self.progressBar.setRange(0, 1) self.progressBar.setValue(0) QgsApplication.processEvents() #os.environ['OGR_VFK_DB_READ_ALL_BLOCKS'] = 'NO' self.labelLoading.setText( u'Načítám soubor {} (může nějaký čas trvat...)'.format(label_text)) QgsApplication.processEvents() self.__mOgrDataSource = ogr.Open( fileName, 0) # 0 - datasource is open in read-only mode if not self.__mOgrDataSource: raise VFKError( u"Nelze otevřít VFK soubor '{}' jako platný OGR datasource.".format(fileName)) layerCount = self.__mOgrDataSource.GetLayerCount() layers_names = [] for i in xrange(layerCount): layers_names.append( self.__mOgrDataSource.GetLayer(i).GetLayerDefn().GetName()) if ('PAR' not in layers_names or 'BUD' not in layers_names) and len(self.__vfkLineEdits) == 1: self.__dataWithoutParBud() self.labelLoading.setText( u'Data nemohla být načtena. Vstupní soubor neobsahuje bloky PAR a BUD.') QgsApplication.processEvents() return # load all layers self.progressBar.setRange(0, layerCount - 1) for i in xrange(layerCount): self.progressBar.setValue(i) theLayerName = self.__mOgrDataSource.GetLayer( i).GetLayerDefn().GetName() self.labelLoading.setText( u"VFK data {}/{}: {}".format(i + 1, layerCount, theLayerName)) QgsApplication.processEvents() self.__mOgrDataSource.GetLayer(i).GetFeatureCount(True) time.sleep(0.02) self.labelLoading.setText( u'Soubor {} úspěšně načten.'.format(label_text)) os.environ['OGR_VFK_DB_OVERWRITE'] = '0' self.__mOgrDataSource.Destroy() self.__mOgrDataSource = None def __selectedIds(self, layer): """ :type layer: QgsVectorLayer :return: """ ids = [] flist = layer.selectedFeatures() for it in flist: f = QgsFeature(it) ids.append(str(f.attribute("ID"))) return ids def showInfoAboutSelection(self): layers = ["PAR", "BUD"] layerIds = {} for layer in layers: if layer in self.__mLoadedLayers: id = str(self.__mLoadedLayers[layer]) vectorLayer = QgsMapLayerRegistry.instance().mapLayer(id) layerIds[layer] = self.__selectedIds(vectorLayer) self.vfkBrowser.showInfoAboutSelection( layerIds["PAR"], layerIds["BUD"]) def showParInMap(self, ids): """ :type ids: list :return: """ if self.actionShowInfoaboutSelection.isChecked(): self.setSelectionChangedConnected(False) self.showInMap(ids, "PAR") self.setSelectionChangedConnected(True) else: self.showInMap(ids, "PAR") def showBudInMap(self, ids): """ :type ids: list :return: """ if self.actionShowInfoaboutSelection.isChecked(): self.setSelectionChangedConnected(False) self.showInMap(ids, "BUD") self.setSelectionChangedConnected(True) else: self.showInMap(ids, "BUD") def showOnCuzk(self): x = self.vfkBrowser.currentDefinitionPoint().first.split(".")[0] y = self.vfkBrowser.currentDefinitionPoint().second.split(".")[0] url = "http://nahlizenidokn.cuzk.cz/MapaIdentifikace.aspx?&x=-{}&y=-{}".format( y, x) QDesktopServices.openUrl(QUrl(url, QUrl.TolerantMode)) def switchToImport(self): self.actionImport.trigger() def switchToSearch(self, searchType): """ :type searchType: int """ self.actionVyhledavani.trigger() self.searchCombo.setCurrentIndex(searchType) self.searchFormMainControls.searchForms.setCurrentIndex(searchType) def switchToChanges(self): """ """ self.actionZpracujZmeny.trigger() def succesfullExport(self, export_format): """ :type export_format: str :return: """ QMessageBox.information( self, u'Export', u"Export do formátu {} proběhl úspěšně.".format( export_format), QMessageBox.Ok) def __dataWithoutParBud(self): """ :type export_format: str :return: """ QMessageBox.warning(self, u'Upozornění', u"Zvolený VFK soubor neobsahuje vrstvy s geometrií (PAR, BUD), proto nemohou " u"být pomocí VFK Pluginu načtena. Data je možné načíst v QGIS pomocí volby " u"'Načíst vektorovou vrstvu.'", QMessageBox.Ok) def __addRowToGridLayout(self): if len(self.__vfkLineEdits) >= 5: self.__maximumLineEditsReached() return # update label self.label.setText('VFK soubory:') # new layout horizontalLayout = QtGui.QHBoxLayout() # create new objects self.__browseButtons['browseButton_{}'.format( len(self.__vfkLineEdits) + 1)] = QtGui.QPushButton(u"Procházet") self.__vfkLineEdits['vfkLineEdit_{}'.format( len(self.__vfkLineEdits) + 1)] = QtGui.QLineEdit() horizontalLayout.addWidget(self.__vfkLineEdits[ 'vfkLineEdit_{}'.format(len(self.__vfkLineEdits))]) horizontalLayout.addWidget(self.__browseButtons[ 'browseButton_{}'.format(len(self.__vfkLineEdits))]) # number of lines in gridLayout rows_count = self.gridLayout_12.rowCount( ) # count of rows in gridLayout # export objects from gridLayout item_label = self.gridLayout_12.itemAtPosition(rows_count - 3, 0) item_par = self.gridLayout_12.itemAtPosition(rows_count - 3, 1) item_bud = self.gridLayout_12.itemAtPosition(rows_count - 2, 1) item_settings = self.gridLayout_12.itemAtPosition(rows_count - 1, 0) item_rewrite_db = self.gridLayout_12.itemAtPosition(rows_count - 1, 1) # remove objects from gridLayout self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 3, 0)) self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 3, 1)) self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 2, 1)) self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 1, 0)) self.gridLayout_12.removeItem( self.gridLayout_12.itemAtPosition(rows_count - 1, 1)) # re-build gridLayout self.gridLayout_12.addLayout(horizontalLayout, rows_count - 3, 1) self.gridLayout_12.addItem(item_label, rows_count - 2, 0) self.gridLayout_12.addItem(item_par, rows_count - 2, 1) self.gridLayout_12.addItem(item_bud, rows_count - 1, 1) self.gridLayout_12.addItem(item_settings, rows_count, 0) self.gridLayout_12.addItem(item_rewrite_db, rows_count, 1) self.__browseButtons['browseButton_{}'.format(len(self.__vfkLineEdits))].clicked.\ connect(lambda: self.browseButton_clicked( int('{}'.format(len(self.__vfkLineEdits))))) def __maximumLineEditsReached(self): QMessageBox.information(self, u'Upozornění', u"Byl dosažen maximální počet ({}) VFK souboru pro zpracování." u"\nNačítání dalších souborů není povoleno!". format(self.lineEditsCount), QMessageBox.Ok) def browseDb_clicked(self, database_type): """ Method run dialog for select database in widget with changes. According to pushButton name will fill in relevant lineEdit. :type database_type: str """ title = u'Vyber databázi' settings = QSettings() lastUsedDir = str(settings.value('/UI/' + "lastVectorFileFilter" + "Dir", ".")) if database_type == 'mainDb': self.__databases[database_type] = QFileDialog.getOpenFileName(self, title, lastUsedDir, u'Datábaze (*.db)') if not self.__databases[database_type]: return self.le_mainDb.setText(self.__databases[database_type]) elif database_type == 'amendmentDb': self.__databases[database_type] = QFileDialog.getOpenFileName(self, title, lastUsedDir, u'Datábaze (*.db)') if not self.__databases[database_type]: return self.le_amendmentDb.setText(self.__databases[database_type]) elif database_type == 'exportDb': title = u'Zadej jméno výstupní databáze' self.__databases[database_type] = QFileDialog.getSaveFileName(self, u"Jméno výstupní databáze", ".db", u"Databáze (*.db)") if not self.__databases[database_type]: return self.le_exportDb.setText(self.__databases[database_type]) if len(self.__databases) == 3: self.pb_applyChanges.setEnabled(True) def applyChanges(self): """ Method :return: """ self.changes_instance.run(self.__databases['mainDb'], self.__databases['amendmentDb'], self.__databases['exportDb']) def __updateProgressBarChanges(self, iteration, table_name): """ :type iteration: int :type table_name: str """ self.progressBar_changes.setValue(iteration) self.l_status.setText(u'Aplikuji změny na tabulku {}...'.format(table_name)) QgsApplication.processEvents() def __setRangeProgressBarChanges(self, max_range): """ :type max_range: int """ self.progressBar_changes.setRange(0, max_range) self.progressBar_changes.setValue(0) def __changesApplied(self): """ """ time.sleep(1) self.l_status.setText(u'Změny byly úspěšně aplikovány.') QgsApplication.processEvents() def __changesPreprocessingDatabase(self): """ """ self.l_status.setText(u'Připravuji výstupní databázi...') QgsApplication.processEvents() def __checkIfAmendmentFile(self, file_name): """ :param file_name: Name of the input file :type file_name: str :return: bool """ if file_name.endswith(".vfk"): with open(file_name, 'r') as f: for line in f: line_splited = str(line).split(';') if line_splited[0] == '&HZMENY': if line_splited[1] == '1': return True else: return False else: print 'database' # TODO: dopsat kontrolu, zda se jedna o stavovou, nebo zmenovou databazi def radioButtonValue(self): """ Check which radio button is checked """ self.vfkFileLineEdit.setText('') self.__fileName = [] self.loadVfkButton.setEnabled(False) if self.rb_file.isChecked(): self.__source_for_data = 'file' self.pb_nextFile.show() self.label.setText('VFK soubor:') elif self.rb_directory.isChecked(): self.__source_for_data = 'directory' self.pb_nextFile.hide() self.label.setText(u'Adresář:') # delete if len(self.__browseButtons) > 1: for i, button in enumerate(self.__browseButtons): if i > 0: self.__browseButtons[button].hide() if len(self.__vfkLineEdits) > 1: for i, le in enumerate(self.__vfkLineEdits): if i > 0: self.__vfkLineEdits[le].hide() def __findVFKFilesInDirectory(self, dir_path): """ Finds all files with extension '.vfk' in given directory including subdirectories :param dir_path: Path to directory. :type dir_path: str :return: List of VFK files """ file_paths = [] for root, dirs, files in os.walk(dir_path): for file in files: if file.endswith(".vfk"): file_paths.append(os.path.join(root, file)) return file_paths def __createToolbarsAndConnect(self): actionGroup = QActionGroup(self) actionGroup.addAction(self.actionImport) actionGroup.addAction(self.actionVyhledavani) actionGroup.addAction(self.actionZpracujZmeny) # QSignalMapper self.signalMapper = QSignalMapper(self) # connect to 'clicked' on all buttons self.connect(self.actionImport, SIGNAL( "triggered()"), self.signalMapper, SLOT("map()")) self.connect(self.actionVyhledavani, SIGNAL( "triggered()"), self.signalMapper, SLOT("map()")) self.connect(self.actionZpracujZmeny, SIGNAL( "triggered()"), self.signalMapper, SLOT("map()")) # setMapping on each button to the QStackedWidget index we'd like to # switch to self.signalMapper.setMapping(self.actionImport, 0) self.signalMapper.setMapping(self.actionVyhledavani, 2) self.signalMapper.setMapping(self.actionZpracujZmeny, 1) # connect mapper to stackedWidget self.connect(self.signalMapper, SIGNAL("mapped(int)"), self.stackedWidget, SLOT("setCurrentIndex(int)")) self.actionImport.trigger() self.connect(self.vfkBrowser, SIGNAL( "switchToPanelImport"), self.switchToImport) self.connect(self.vfkBrowser, SIGNAL( "switchToPanelSearch"), self.switchToSearch) self.connect(self.vfkBrowser, SIGNAL( "switchToPanelChanges"), self.switchToChanges) # Browser toolbar # --------------- self.__mBrowserToolbar = QToolBar(self) self.connect(self.actionBack, SIGNAL( "triggered()"), self.vfkBrowser.goBack) self.connect(self.actionForward, SIGNAL( "triggered()"), self.vfkBrowser.goForth) self.connect(self.actionSelectBudInMap, SIGNAL("triggered()"), self.selectBudInMap) self.connect(self.actionSelectParInMap, SIGNAL("triggered()"), self.selectParInMap) self.connect(self.actionCuzkPage, SIGNAL("triggered()"), self.showOnCuzk) self.connect(self.actionExportLatex, SIGNAL("triggered()"), self.latexExport) self.connect(self.actionExportHtml, SIGNAL("triggered()"), self.htmlExport) self.connect(self.actionShowInfoaboutSelection, SIGNAL( "toggled(bool)"), self.setSelectionChangedConnected) self.connect(self.actionShowHelpPage, SIGNAL( "triggered()"), self.vfkBrowser.showHelpPage) self.loadVfkButton.clicked.connect(self.loadVfkButton_clicked) self.__browseButtons['browseButton_1'] = self.browseButton self.__browseButtons['browseButton_1'].clicked.connect( lambda: self.browseButton_clicked(int('{}'.format(len(self.__vfkLineEdits))))) self.__vfkLineEdits['vfkLineEdit_1'] = self.vfkFileLineEdit bt = QToolButton(self.__mBrowserToolbar) bt.setPopupMode(QToolButton.InstantPopup) bt.setText("Export ") menu = QMenu(bt) menu.addAction(self.actionExportLatex) menu.addAction(self.actionExportHtml) bt.setMenu(menu) # add actions to toolbar icons self.__mBrowserToolbar.addAction(self.actionImport) self.__mBrowserToolbar.addAction(self.actionVyhledavani) self.__mBrowserToolbar.addAction(self.actionZpracujZmeny) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addAction(self.actionBack) self.__mBrowserToolbar.addAction(self.actionForward) self.__mBrowserToolbar.addAction(self.actionSelectParInMap) self.__mBrowserToolbar.addAction(self.actionSelectBudInMap) self.__mBrowserToolbar.addAction(self.actionCuzkPage) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addAction(self.actionShowInfoaboutSelection) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addWidget(bt) self.__mBrowserToolbar.addSeparator() self.__mBrowserToolbar.addAction(self.actionShowHelpPage) self.rightWidgetLayout.insertWidget(0, self.__mBrowserToolbar) # connect signals from vfkbrowser when changing history self.connect(self.vfkBrowser, SIGNAL( "currentParIdsChanged"), self.actionSelectParInMap.setEnabled) self.connect(self.vfkBrowser, SIGNAL("currentBudIdsChanged"), self.actionSelectBudInMap.setEnabled) self.connect(self.vfkBrowser, SIGNAL( "historyBefore"), self.actionBack.setEnabled) self.connect(self.vfkBrowser, SIGNAL( "historyAfter"), self.actionForward.setEnabled) self.connect(self.vfkBrowser, SIGNAL( "definitionPointAvailable"), self.actionCuzkPage.setEnabled) # add toolTips self.pb_nextFile.setToolTip(u'Přidej další soubor VFK') self.parCheckBox.setToolTip(u'Načti vrstvu parcel') self.budCheckBox.setToolTip(u'Načti vrstvu budov') # add new VFK file self.pb_nextFile.clicked.connect(self.__addRowToGridLayout) # widget apply changes self.pb_mainDb.clicked.connect( lambda: self.browseDb_clicked('mainDb')) self.pb_amendmentDb.clicked.connect( lambda: self.browseDb_clicked('amendmentDb')) self.pb_exportDb.clicked.connect( lambda: self.browseDb_clicked('exportDb')) self.pb_applyChanges.clicked.connect(self.applyChanges) self.pb_applyChanges.setEnabled(False) self.connect(self.changes_instance, SIGNAL("maxRangeProgressBar"), self.__setRangeProgressBarChanges) self.connect(self.changes_instance, SIGNAL("updateStatus"), self.__updateProgressBarChanges) self.connect(self.changes_instance, SIGNAL("finishedStatus"), self.__changesApplied) self.connect(self.changes_instance, SIGNAL("preprocessingDatabase"), self.__changesPreprocessingDatabase) # connect radio boxes self.rb_file.clicked.connect(self.radioButtonValue) self.rb_directory.clicked.connect(self.radioButtonValue)
class MainWindowStart(QMainWindow, MainWindow_Pro.Ui_MainWindow): def __init__(self, parent=None): super(MainWindowStart, self).__init__(parent) # Mappers for connecting buttons and labels self.myMapper = QSignalMapper(self) self.myMapper_StyleSheet = QSignalMapper(self) # Load UI self.setupUi(self) self.regex_edits = QRegExp(r"(^[0]+$|^$)") self._filter = Filter() self.filename = QString() self.edit1_delayh.installEventFilter(self._filter) self.sizeLabel = QLabel() self.sizeLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.statusBar1.addPermanentWidget(self.sizeLabel) self.statusBar1.setSizeGripEnabled(False) self.create_connections() self.assign_shortcuts() self.create_tool_bar() self.update_devices_list() # self.button_stop.clicked.connect(self.stop_all) # List of valve pushbuttons self.valve_list = [ self.valve1, self.valve2, self.valve3, self.valve4, self.valve5, self.valve6, self.valve7, self.valve8 ] # GroupBoxes for grouping labels and buttons on each row, used for applying StyleSheets self.group_boxes = [ self.groupbox1, self.groupbox2, self.groupbox3, self.groupbox4, self.groupbox5, self.groupbox6, self.groupbox7, self.groupbox8 ] # List of lineEdits self.lineEdits_list = [ (self.edit1_delayh, self.edit1_delaym, self.edit1_delays, self.edit1_onh, self.edit1_onm, self.edit1_ons, self.edit1_offh, self.edit1_offm, self.edit1_offs, self.edit1_totalh, self.edit1_totalm, self.edit1_totals), (self.edit2_delayh, self.edit2_delaym, self.edit2_delays, self.edit2_onh, self.edit2_onm, self.edit2_ons, self.edit2_offh, self.edit2_offm, self.edit2_offs, self.edit2_totalh, self.edit2_totalm, self.edit2_totals), (self.edit3_delayh, self.edit3_delaym, self.edit3_delays, self.edit3_onh, self.edit3_onm, self.edit3_ons, self.edit3_offh, self.edit3_offm, self.edit3_offs, self.edit3_totalh, self.edit3_totalm, self.edit3_totals), (self.edit4_delayh, self.edit4_delaym, self.edit4_delays, self.edit4_onh, self.edit4_onm, self.edit4_ons, self.edit4_offh, self.edit4_offm, self.edit4_offs, self.edit4_totalh, self.edit4_totalm, self.edit4_totals), (self.edit5_delayh, self.edit5_delaym, self.edit5_delays, self.edit5_onh, self.edit5_onm, self.edit5_ons, self.edit5_offh, self.edit5_offm, self.edit5_offs, self.edit5_totalh, self.edit5_totalm, self.edit5_totals), (self.edit6_delayh, self.edit6_delaym, self.edit6_delays, self.edit6_onh, self.edit6_onm, self.edit6_ons, self.edit6_offh, self.edit6_offm, self.edit6_offs, self.edit6_totalh, self.edit6_totalm, self.edit6_totals), (self.edit7_delayh, self.edit7_delaym, self.edit7_delays, self.edit7_onh, self.edit7_onm, self.edit7_ons, self.edit7_offh, self.edit7_offm, self.edit7_offs, self.edit7_totalh, self.edit7_totalm, self.edit7_totals), (self.edit8_delayh, self.edit8_delaym, self.edit8_delays, self.edit8_onh, self.edit8_onm, self.edit8_ons, self.edit8_offh, self.edit8_offm, self.edit8_offs, self.edit8_totalh, self.edit8_totalm, self.edit8_totals) ] for index, editLabels in enumerate(self.lineEdits_list, 1): for index2, lineedits in enumerate(editLabels, 0): # Apply mapper (GUIObject, objectIndex) self.myMapper_StyleSheet.setMapping( self.lineEdits_list[index - 1][index2], index - 1) # Connect mapper to signal (self.lineEdits_list[index - 1][index2]).textChanged.connect( self.myMapper_StyleSheet.map) # Set event Filter, for detecting when Focus changes self.lineEdits_list[index - 1][index2].installEventFilter( self._filter) # Set Mappers for buttons (1..8) self.myMapper.setMapping(self.valve_list[index - 1], index) # Connect mapper to signal for detecting clicks on buttons (self.valve_list[index - 1]).clicked.connect(self.myMapper.map) # Connect to signal for enabling labelEdits self.myMapper.mapped['int'].connect(self.enable_fields) # Connect to signal for changing color of groupbox used for visual indication self.myMapper_StyleSheet.mapped['int'].connect(self.valve_color_status) # Create Keyboard Shortcuts def assign_shortcuts(self): self.actionArchivo_Nuevo.setShortcut(QKeySequence.New) self.action_Abrir.setShortcut(QKeySequence.Open) self.action_Guardar.setShortcut(QKeySequence.Save) self.actionGuardar_Como.setShortcut(QKeySequence.SaveAs) self.action_Limpiar.setShortcut('Ctrl+L') self.actionVAL_508_Ayuda.setShortcut(QKeySequence.HelpContents) self.action_Salir.setShortcut(QKeySequence.Close) # self.actionPreferencias.setShortcut(QKeySequence.Preferences) self.action_Detener_USB.setShortcut('Ctrl+Shift+C') self.action_Ejecutar.setShortcut('Ctrl+Shift+X') self.action_Para_Valvulas.setShortcut('Ctrl+Shift+P') # Create connections to signals def create_connections(self): self.actionArchivo_Nuevo.triggered.connect(self.new_file) self.action_Abrir.triggered.connect(self.open_file) self.action_Guardar.triggered.connect(self.save_file) self.actionGuardar_Como.triggered.connect(self.save_file_as) self.action_Limpiar.triggered.connect(self.clean_fields) self.action_Salir.triggered.connect(self.close) self.actionVAL_508_Ayuda.triggered.connect(self.show_help) self.actionAcerca_de_VAL_508.triggered.connect(self.show_about) self.action_Detener_USB.triggered.connect(self.stop_usb) self.action_Ejecutar.triggered.connect(self.execute) self.action_Para_Valvulas.triggered.connect(self.stop_all) # Creation of About Dialog def show_about(self): about = aboutdialog.AboutDialog(self) about.show() # Creation of Help Form def show_help(self): form = helpform.HelpForm('Help.html', self) form.show() def new_file(self): self.filename = QString() self.clean_fields() # Close connection to arduino before closing Program def closeEvent(self, QCloseEvent): try: self.thread_connection.serial_connection.close() logging.debug("Thread running and killed at closing program") except AttributeError: logging.debug("Thread was not running when closing program OK") def clean_fields(self): for index, editLabels in enumerate(self.lineEdits_list, 1): for index2, lineedits in enumerate(editLabels, 0): self.lineEdits_list[index - 1][index2].setText('0') def save_file_as(self): filename_copy = self.filename logging.info("Current filename: %s" % self.filename) my_home = os.path.expanduser('~') self.filename = QFileDialog.getSaveFileName( self, self.tr('Guardar como'), os.path.join(my_home, "archivo.txt"), "", "", QFileDialog.DontUseNativeDialog) logging.info("Filename to save: %s" % self.filename) if not self.filename.isNull(): if self.filename.endsWith(QString('.txt')): self.write_data_to_file('w') else: self.filename.append(QString('.txt')) messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr('Advertencia')) messageBox.setText( self.tr(u"El archivo ya existe, ¿Reemplazar?")) messageBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) messageBox.setIconPixmap(QPixmap(':/broken_file.png')) if messageBox.exec_() == QMessageBox.Yes: self.write_data_to_file('w') else: try: while True: self.filename = QFileDialog.getSaveFileName( self, self.tr('Guardar como'), os.path.join(my_home, "archivo.txt"), "", "", QFileDialog.DontUseNativeDialog) if self.filename.isNull(): raise Saved_Canceled() else: messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}' ) messageBox.setWindowTitle( self.tr('Advertencia')) messageBox.setText( self.tr( u"El archivo ya existe, ¿Reemplazar?")) messageBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No) messageBox.setIconPixmap( QPixmap(':/broken_file.png')) if messageBox.exec_() == QMessageBox.Yes: self.write_data_to_file('w') raise Saved_Accepted() except Saved_Canceled: self.filename = filename_copy except Saved_Accepted: pass logging.info("Current filename after operation: %s" % self.filename) def save_file(self): if self.filename.isNull(): self.save_file_as() else: self.write_data_to_file('w') # Colect data for arduino def execute(self): string_data = '' list_strings = [] if str(self.arduino_combobox.currentText()): self.statusBar1.showMessage(self.tr('Conectando...')) # Gather all the contents of each row of valves and create a list of lists with them for elem_edit in self.lineEdits_list: # delay string_data = string_data + str( ((int(elem_edit[0].text()) * 3600) + (int(elem_edit[1].text()) * 60) + (int(elem_edit[2].text()))) * 1000) + ';' # ON string_data = string_data + str( ((int(elem_edit[3].text()) * 3600) + (int(elem_edit[4].text()) * 60) + (int(elem_edit[5].text()))) * 1000) + ';' # OFF string_data = string_data + str( ((int(elem_edit[6].text()) * 3600) + (int(elem_edit[7].text()) * 60) + (int(elem_edit[8].text()))) * 1000) + ';' # Total string_data = string_data + str( ((int(elem_edit[9].text()) * 3600) + (int(elem_edit[10].text()) * 60) + (int(elem_edit[11].text()))) * 1000) + ';' list_strings.append(string_data) string_data = '' # Start QThread for communicating with arduino self.thread_connection = Arduino_Communication( str(self.arduino_combobox.currentText()), list_strings) self.thread_connection.start() self.action_Ejecutar.setEnabled(False) self.action_Para_Valvulas.setEnabled(False) # Connect to current QThread instance in order to know the status of it's termination # This line used only when stopping current task self.thread_connection.finished.connect(self.finished_thread) self.thread_connection.connection_exit_status.connect( self.finished_thread) else: messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr('Advertencia')) messageBox.setText(self.tr("Arduino no seleccionado")) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.setIconPixmap(QPixmap(':/usb_error.png')) messageBox.exec_() # Inform QThread to stop sending data to arduino def stop_usb(self): if str(self.arduino_combobox.currentText()): try: self.statusBar1.showMessage(self.tr(u'Conexión detenida')) if self.thread_connection.isRunning(): mutex.lock() self.thread_connection.kill_serial = True mutex.unlock() except AttributeError: logging.debug("Thread not running \'disconnected! \'") else: messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr('Advertencia')) messageBox.setText(self.tr("Arduino no seleccionado")) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.setIconPixmap(QPixmap(':/usb_error.png')) messageBox.exec_() def enable_fields(self, index): hours_reg = QRegExp(r"0*[0-9]{1,3}") sec_reg = QRegExp(r"(0*[0-9])|(0*[0-5][0-9])") for counter, line_edit in enumerate(self.lineEdits_list[index - 1]): line_edit.setEnabled(self.valve_list[index - 1].isChecked()) if counter % 3 == 0: line_edit.setValidator(QRegExpValidator(hours_reg, self)) else: line_edit.setValidator(QRegExpValidator(sec_reg, self)) def valve_color_status(self, index): logging.info("Checking color from valve button") for edit in self.lineEdits_list[index]: if edit.text().contains(self.regex_edits): self.group_boxes[index].setStyleSheet('''QGroupBox { border: 2px solid; border-color: rgba(255, 255, 255, 0);}''' ) else: self.group_boxes[index].setStyleSheet( '''QGroupBox {background-color: rgba(103, 255, 126, 150); border: 2px solid; border-color: rgba(255, 255, 255, 255);}''' ) break def create_tool_bar(self): self.label_arduino = QLabel(self.tr('Dispositivos: ')) self.toolBar.addWidget(self.label_arduino) self.arduino_combobox = QComboBox() self.arduino_combobox.setToolTip(self.tr('Seleccionar Arduino')) self.arduino_combobox.setFocusPolicy(Qt.NoFocus) # Update List of Arduino devices self.reload = QAction(QIcon(":/reload.png"), self.tr("&Refrescar"), self) self.reload.setShortcut(QKeySequence.Refresh) self.reload.setToolTip(self.tr('Refrescar Dispositivos')) self.reload.triggered.connect(self.update_devices_list) self.toolBar.addWidget(self.arduino_combobox) self.toolBar.addAction(self.reload) # Update current usb devices connected to PC def update_devices_list(self): device_list = serial.tools.list_ports.comports() current_arduino = self.arduino_combobox.currentText() self.arduino_combobox.clear() for device_index, device in enumerate(sorted(device_list)): self.arduino_combobox.addItem(device.device) if device.device == current_arduino: self.arduino_combobox.setCurrentIndex(device_index) # Stop current arduino task def stop_all(self): if str(self.arduino_combobox.currentText()): self.thread_connection = Arduino_Communication( str(self.arduino_combobox.currentText())) self.thread_connection.start() self.action_Ejecutar.setEnabled(False) self.action_Para_Valvulas.setEnabled(False) self.action_Detener_USB.setEnabled(False) self.thread_connection.finished.connect(self.finished_thread) self.thread_connection.connection_exit_status.connect( self.finished_thread) else: messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr('Advertencia')) messageBox.setText(self.tr("Arduino no seleccionado")) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.setIconPixmap(QPixmap(':/usb_error.png')) messageBox.exec_() def open_file(self): try: my_home = os.path.expanduser('~') file_name = QFileDialog.getOpenFileName( self, self.tr('Abrir archivo'), my_home, '*.txt', '*.txt', QFileDialog.DontUseNativeDialog) logging.warning("file_name type: %s" % type(file_name)) list_values = [] if not file_name.isNull(): with open(file_name) as fp: for line in fp: list_values.extend([line.replace('\n', '')]) logging.info("List Content: %s" % list_values) count = 0 for elems in self.lineEdits_list: for inner_elem in elems: if not unicode(list_values[count]).isdigit(): raise Uncompatible_Data() inner_elem.setText(list_values[count]) count = count + 1 self.filename = file_name except (IOError, OSError): messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr('Error')) messageBox.setText(self.tr('No se pudo abrir el archivo')) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.setIconPixmap(QPixmap(':/broken_file.png')) messageBox.exec_() except (IndexError, Uncompatible_Data): messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr('Error')) messageBox.setText(self.tr('Formato Incompatible')) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.setIconPixmap(QPixmap(':/broken_file.png')) messageBox.exec_() # Inform the user if we were able to send data successfully to arduino def finished_thread(self, error=None, message=''): if error == 'error': messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr('Error')) messageBox.setText(message) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.setIconPixmap(QPixmap(':/usb_error.png')) messageBox.exec_() return elif error == 'success': messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr(u'Éxito')) messageBox.setText(message) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.setIconPixmap(QPixmap(':/usb_success.png')) messageBox.exec_() return elif error == 'stopped': messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr(u'Éxito')) messageBox.setText(message) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.setIconPixmap(QPixmap(':/success_general.png')) messageBox.exec_() return self.action_Ejecutar.setEnabled(True) self.action_Para_Valvulas.setEnabled(True) self.action_Detener_USB.setEnabled(True) self.statusBar1.showMessage(self.tr('Finalizado')) # Save data to disk def write_data_to_file(self, open_mode): progressDialog = QProgressDialog() progressDialog.setModal(True) progressDialog.setLabelText(self.tr('Guardando...')) progressDialog.setMaximum(8) progressDialog.setCancelButton(None) progressDialog.show() try: # File is closed automatically even on error with open(unicode(self.filename), open_mode) as file_obj: for count, elem_edit in enumerate(self.lineEdits_list, 1): file_obj.write(''.join([str(elem_edit[0].text()), '\n'])) file_obj.write(''.join([str(elem_edit[1].text()), '\n'])) file_obj.write(''.join([str(elem_edit[2].text()), '\n'])) file_obj.write(''.join([str(elem_edit[3].text()), '\n'])) file_obj.write(''.join([str(elem_edit[4].text()), '\n'])) file_obj.write(''.join([str(elem_edit[5].text()), '\n'])) file_obj.write(''.join([str(elem_edit[6].text()), '\n'])) file_obj.write(''.join([str(elem_edit[7].text()), '\n'])) file_obj.write(''.join([str(elem_edit[8].text()), '\n'])) file_obj.write(''.join([str(elem_edit[9].text()), '\n'])) file_obj.write(''.join([str(elem_edit[10].text()), '\n'])) file_obj.write(''.join([str(elem_edit[11].text()), '\n'])) progressDialog.setValue(count) except (IOError, OSError): progressDialog.close() messageBox = QMessageBox(self) messageBox.setStyleSheet( 'QMessageBox QLabel {font: bold 14pt "Cantarell";}') messageBox.setWindowTitle(self.tr('Error')) messageBox.setText(self.tr('Error al guardar')) messageBox.setStandardButtons(QMessageBox.Ok) messageBox.setIcon(QMessageBox.Critical) messageBox.exec_() else: self.statusBar1.showMessage(self.tr('Guardado'), 3000)
class CaseSelectionWidget(QWidget): caseSelectionChanged = pyqtSignal() def __init__(self, current_case): QWidget.__init__(self) self.__model = PlotCaseModel() self.__signal_mapper = QSignalMapper(self) self.__case_selectors = {} self.__case_selectors_order = [] layout = QVBoxLayout() add_button_layout = QHBoxLayout() button = QPushButton(util.resourceIcon("ide/small/add"), "Add case to plot") button.clicked.connect(self.addCaseSelector) add_button_layout.addStretch() add_button_layout.addWidget(button) add_button_layout.addStretch() layout.addLayout(add_button_layout) self.__case_layout = QVBoxLayout() self.__case_layout.setMargin(0) layout.addLayout(self.__case_layout) self.addCaseSelector(disabled=True, current_case=current_case) layout.addStretch() self.setLayout(layout) self.__signal_mapper.mapped[QWidget].connect(self.removeWidget) def __caseName(self, widget): """ @rtype: str """ return str(self.__case_selectors[widget].currentText()) def getPlotCaseNames(self): if self.__model.rowCount() == 0: return [] return [self.__caseName(widget) for widget in self.__case_selectors_order] def addCaseSelector(self, disabled=False, current_case=None): if len(self.__case_selectors_order) == 5: return widget = QWidget() layout = QHBoxLayout() layout.setMargin(0) widget.setLayout(layout) combo = QComboBox() combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) combo.setMinimumContentsLength(20) combo.setModel(self.__model) if current_case is not None: index = 0 for item in self.__model: if item == current_case: combo.setCurrentIndex(index) break index += 1 combo.currentIndexChanged.connect(self.caseSelectionChanged.emit) layout.addWidget(combo, 1) button = QToolButton() button.setAutoRaise(True) button.setDisabled(disabled) button.setIcon(util.resourceIcon("ide/small/delete")) button.clicked.connect(self.__signal_mapper.map) layout.addWidget(button) self.__case_selectors[widget] = combo self.__case_selectors_order.append(widget) self.__signal_mapper.setMapping(button, widget) self.__case_layout.addWidget(widget) self.caseSelectionChanged.emit() def removeWidget(self, widget): self.__case_layout.removeWidget(widget) del self.__case_selectors[widget] self.__case_selectors_order.remove(widget) widget.setParent(None) self.caseSelectionChanged.emit()
class CaseSelectionWidget(QWidget): caseSelectionChanged = pyqtSignal() def __init__(self, current_case): QWidget.__init__(self) self.__model = PlotCaseModel() self.__signal_mapper = QSignalMapper(self) self.__case_selectors = {} self.__case_selectors_order = [] layout = QVBoxLayout() add_button_layout = QHBoxLayout() self.__add_case_button = QToolButton() self.__add_case_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.__add_case_button.setText("Add case to plot") self.__add_case_button.setIcon(resourceIcon("ide/small/add")) self.__add_case_button.clicked.connect(self.addCaseSelector) add_button_layout.addStretch() add_button_layout.addWidget(self.__add_case_button) add_button_layout.addStretch() layout.addLayout(add_button_layout) self.__case_layout = QVBoxLayout() self.__case_layout.setMargin(0) layout.addLayout(self.__case_layout) self.addCaseSelector(disabled=True, current_case=current_case) layout.addStretch() self.setLayout(layout) self.__signal_mapper.mapped[QWidget].connect(self.removeWidget) def __caseName(self, widget): """ @rtype: str """ return str(self.__case_selectors[widget].currentText()) def getPlotCaseNames(self): if self.__model.rowCount() == 0: return [] return [ self.__caseName(widget) for widget in self.__case_selectors_order ] def checkCaseCount(self): state = True if len(self.__case_selectors_order) == 5: state = False self.__add_case_button.setEnabled(state) def addCaseSelector(self, disabled=False, current_case=None): widget = QWidget() layout = QHBoxLayout() layout.setMargin(0) widget.setLayout(layout) combo = QComboBox() combo.setSizeAdjustPolicy( QComboBox.AdjustToMinimumContentsLengthWithIcon) combo.setMinimumContentsLength(20) combo.setModel(self.__model) if current_case is not None: index = 0 for item in self.__model: if item == current_case: combo.setCurrentIndex(index) break index += 1 combo.currentIndexChanged.connect(self.caseSelectionChanged.emit) layout.addWidget(combo, 1) button = QToolButton() button.setAutoRaise(True) button.setDisabled(disabled) button.setIcon(resourceIcon("ide/small/delete")) button.clicked.connect(self.__signal_mapper.map) layout.addWidget(button) self.__case_selectors[widget] = combo self.__case_selectors_order.append(widget) self.__signal_mapper.setMapping(button, widget) self.__case_layout.addWidget(widget) self.checkCaseCount() self.caseSelectionChanged.emit() def removeWidget(self, widget): self.__case_layout.removeWidget(widget) del self.__case_selectors[widget] self.__case_selectors_order.remove(widget) widget.setParent(None) self.caseSelectionChanged.emit() self.checkCaseCount()
def __init__(self, parent, tester): """Construct a new dockwindow following the tester """ self.tester = tester QtGui.QDockWidget.__init__(self, tester.testname, parent) self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) if not self.widget(): self.setWidget(QtGui.QWidget(self)) vl = QtGui.QVBoxLayout(self.widget()) #self.widget().setLayout(vl) panel = QtGui.QFrame(self) vl.addWidget(panel) self.login = QtGui.QLineEdit(self) self.login.textEdited.connect(self.check_logpass) self.password = QtGui.QLineEdit(self) self.password.setEchoMode(QtGui.QLineEdit.Password) self.password.textEdited.connect(self.check_logpass) self.tests = [] fl = QtGui.QFormLayout(panel) fl.addRow('&Login:'******'&Password:', self.password) panel.setLayout(fl) panel = QtGui.QFrame(self) panel.setFrameShadow(QtGui.QFrame.Sunken) panel.setFrameShape(QtGui.QFrame.Panel) vl.addWidget(panel) vl2 = QtGui.QVBoxLayout(panel) signalmapper = QSignalMapper(self) signalmapper.mapped[int].connect(self.test) for i, test in enumerate(self.tester.tests): btn = QtGui.QPushButton("Test {}: {}".format(i + 1, test.name), panel) btn.setStyleSheet(self.btn_default_stylesheet) btn.setEnabled(False) vl2.addWidget(btn) self.tests.append(btn) signalmapper.setMapping(btn, i) btn.clicked.connect(signalmapper.map) panel.setLayout(vl2) self.text = QtGui.QLabel( "Enter your Coursera login and assignments password and push one of the test buttons above to run the test and submit the results to Coursera." ) self.text.setWordWrap(True) vl.addWidget(self.text) self.text.setFrameShadow(QtGui.QFrame.Sunken) self.text.setFrameShape(QtGui.QFrame.Panel) self.text.setMargin(5) #vl.setStretch(2,1) vl.addStretch(1)
class RadioEditor ( BaseEditor ): """ Enumeration editor, used for the "custom" style, that displays radio buttons. """ #-- Public Methods --------------------------------------------------------- def init ( self, parent ): """ Finishes initializing the editor by creating the underlying toolkit widget. """ super( RadioEditor, self ).init( parent ) # The control is a grid layout: self.control = layout = QGridLayout() layout.setSpacing( 0 ) layout.setMargin( 0 ) self._mapper = QSignalMapper() QObject.connect( self._mapper, SIGNAL( 'mapped(QWidget *)' ), self.update_object ) self.rebuild_editor() def update_object ( self, rb ): """ Handles the user clicking one of the custom radio buttons. """ try: self.value = rb.value except: pass def update_editor ( self ): """ Updates the editor when the object facet changes externally to the editor. """ value = self.value for i in range( self.control.count() ): rb = self.control.itemAt( i ).widget() rb.setChecked( rb.value == value ) def rebuild_editor ( self ): """ Rebuilds the contents of the editor whenever the original factory object's **values** facet changes. """ # Clear any existing content: ### self.clear_layout() # Get the current facet value: cur_name = self.str_value # Create a sizer to manage the radio buttons: names = self.names mapping = self.mapping n = len( names ) cols = self.factory.cols rows = (n + cols - 1) / cols incr = [ n / cols ] * cols rem = n % cols for i in range( cols ): incr[i] += (rem > i) incr[-1] = -( reduce( lambda x, y: x + y, incr[:-1], 0 ) - 1 ) # Add the set of all possible choices: index = 0 for i in range( rows ): for j in range( cols ): if n > 0: name = label = names[ index ] label = self.string_value( label, capitalize ) rb = QRadioButton( label ) rb.value = mapping[ name ] rb.setChecked( name == cur_name ) QObject.connect( rb, SIGNAL( 'clicked()' ), self._mapper, SLOT( 'map()' ) ) self._mapper.setMapping( rb, rb ) self.set_tooltip( rb ) self.control.addWidget( rb, i, j ) index += incr[j] n -= 1
class K800IRec(QWidget, mfso): def __init__(self): mfso.__init__(self, "K800i-Recover") self.name = "K800i-Recover" self.icon = None self.__disown__() def start(self, args): self.vfs = vfs.vfs() self.dumpnumber = 1 try : self.nor = args['nor'].value() self.nand = args['nand'].value() except IndexError: return try: self.spareSize = args["spare-size"].value() except IndexError: self.spareSize = 16 try: self.pageSize = args["page-size"].value() except IndexError: self.pageSize = 512 self.k800n = Node("k800-base") self.k800n.__disown__() self.boot = SEBootBlock(self.nor, self.pageSize) self.blockSize = self.boot.blockSize self.nandClean = SpareNode(self, self.nand, "nandfs", self.pageSize, self.spareSize, self.k800n) self.norFs = NorFs(self, self.k800n, self.nor, "norfs", self.boot) self.fullFs = FullFs(self, self.k800n, self.norFs, self.nandClean, "fullfs", self.boot) self.gdfs = GDFS(self, self.k800n, self.nor, "gdfs", self.boot) self.firmware = Firmware(self, self.k800n, self.nor, "firmware", self.boot.norfsoffset) self.tables = Tables(self.fullFs, self.blockSize) self.registerTree(self.nand, self.k800n) def createDump(self): text, ok = QInputDialog.getText(self, "Create dump", "dump name:", QLineEdit.Normal, "k800-restore-" + str(self.dumpnumber)) if ok and text != "": if (self.vfs.getnode(self.nand.absolute() + "/" + str(text)) == None): self.dumpnumber += 1 newroot = Node(str(text)) newroot.__disown__() for id in range(0, len(self.tables.tablesIdWriteMap) - 1): write = int(str(self.gtable.cellWidget(id, 0).currentText()), 16) self.tables.map[id] = self.tables.tablesIdWriteMap[id][write] virtual = VirtualMap(self, newroot, self.fullFs, self.tables, "virtual", self.blockSize) separt = SEPartitionBlock(virtual, self.boot.partitionblock, self.blockSize) self.createPart(separt, newroot, virtual) self.registerTree(self.nand, newroot) else : box = QMessageBox(QMessageBox.Warning, "Error", "Error node already exists", QMessageBox.NoButton, self) box.exec_() self.createDump() def createPart(self, separt, newroot, virtual): for part in separt.partTable: if part.start > 0: p = Partition(self, newroot, virtual, part, self.blockSize) def g_display(self): QWidget.__init__(self, None) self.layout = QVBoxLayout(self) self.hlayout = QSplitter(self) self.layout.insertWidget(0, self.hlayout) self.layout.setStretchFactor(self.hlayout, 1) self.gTable() self.viewTable() self.button = QPushButton("&Create dump") self.connect(self.button, SIGNAL("clicked()"), self.createDump) self.layout.addWidget(self.button) def viewTable(self): self.vtable = QTableWidget() self.vtable.setColumnCount(20) self.vtable.setRowCount(48) self.hlayout.addWidget(self.vtable) def viewTableUpdate(self, id): write = int(str(self.gtable.cellWidget(id, 0).currentText()), 16) t = self.tables.tablesIdWriteMap[id][write] l = t.blockList for x in xrange(0, len(t.blockList[0])): block = t.blockList[0][x] c = ((x) % 20) r = ((x) / 20) item = QTableWidgetItem(QString(hex(block))) tipBlock = (id * 960) + x item.setToolTip(QString(hex(tipBlock))) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.vtable.setItem(r ,c, item) def gTable(self): self.gtable = QTableWidget() self.gtable.setColumnCount(1) self.gtable.setRowCount(len(self.tables.tablesIdWriteMap)) self.gtable.setHorizontalHeaderItem(0, QTableWidgetItem(QString("version"))) self.hlayout.addWidget(self.gtable) self.sigMapper = QSignalMapper(self) for id in self.tables.tablesIdWriteMap: wlist = self.tables.tablesIdWriteMap[id] cbox = QComboBox(self.gtable) self.connect(cbox, SIGNAL("activated(QString)"), self.sigMapper, SLOT("map()")) self.sigMapper.setMapping(cbox, id) l = [] for write in wlist: l.append(write) l.sort() l.reverse() for write in l: cbox.addItem(QString(hex(write))) self.gtable.setCellWidget(id, 0, cbox) self.gtable.setVerticalHeaderItem(id, QTableWidgetItem(QString(hex(id)))) self.connect(self.sigMapper, SIGNAL("mapped(int)"), self.viewTableUpdate) self.gtable.setMaximumWidth(self.gtable.columnWidth(0) + self.gtable.verticalHeader().sectionSize(0) + 30) def updateWidget(self): pass
class ToolGrid(QFrame): """ A widget containing a grid of actions/buttons. Actions can be added using standard :func:`QWidget.addAction(QAction)` and :func:`QWidget.insertAction(int, QAction)` methods. Parameters ---------- parent : :class:`QWidget` Parent widget. columns : int Number of columns in the grid layout. buttonSize : :class:`QSize`, optional Size of tool buttons in the grid. iconSize : :class:`QSize`, optional Size of icons in the buttons. toolButtonStyle : :class:`Qt.ToolButtonStyle` Tool button style. """ actionTriggered = Signal(QAction) actionHovered = Signal(QAction) def __init__(self, parent=None, columns=4, buttonSize=None, iconSize=None, toolButtonStyle=Qt.ToolButtonTextUnderIcon): QFrame.__init__(self, parent) if buttonSize is not None: buttonSize = QSize(buttonSize) if iconSize is not None: iconSize = QSize(iconSize) self.__columns = columns self.__buttonSize = buttonSize or QSize(50, 50) self.__iconSize = iconSize or QSize(26, 26) self.__toolButtonStyle = toolButtonStyle self.__gridSlots = [] self.__buttonListener = ToolButtonEventListener(self) self.__buttonListener.buttonRightClicked.connect( self.__onButtonRightClick) self.__buttonListener.buttonEnter.connect(self.__onButtonEnter) self.__mapper = QSignalMapper() self.__mapper.mapped[QObject].connect(self.__onClicked) self.__setupUi() def __setupUi(self): layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.setSizeConstraint(QGridLayout.SetFixedSize) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) def setButtonSize(self, size): """ Set the button size. """ if self.__buttonSize != size: self.__buttonSize = size for slot in self.__gridSlots: slot.button.setFixedSize(size) def buttonSize(self): """ Return the button size. """ return QSize(self.__buttonSize) def setIconSize(self, size): """ Set the button icon size. """ if self.__iconSize != size: self.__iconSize = size for slot in self.__gridSlots: slot.button.setIconSize(size) def iconSize(self): """ Return the icon size """ return QSize(self.__iconSize) def setToolButtonStyle(self, style): """ Set the tool button style. """ if self.__toolButtonStyle != style: self.__toolButtonStyle = style for slot in self.__gridSlots: slot.button.setToolButtonStyle(style) def toolButtonStyle(self): """ Return the tool button style. """ return self.__toolButtonStyle def setColumnCount(self, columns): """ Set the number of button/action columns. """ if self.__columns != columns: self.__columns = columns self.__relayout() def columns(self): """ Return the number of columns in the grid. """ return self.__columns def clear(self): """ Clear all actions/buttons. """ for slot in reversed(list(self.__gridSlots)): self.removeAction(slot.action) self.__gridSlots = [] def insertAction(self, before, action): """ Insert a new action at the position currently occupied by `before` (can also be an index). Parameters ---------- before : :class:`QAction` or int Position where the `action` should be inserted. action : :class:`QAction` Action to insert """ if isinstance(before, int): actions = list(self.actions()) if len(actions) == 0 or before >= len(actions): # Insert as the first action or the last action. return self.addAction(action) before = actions[before] return QFrame.insertAction(self, before, action) def setActions(self, actions): """ Clear the grid and add `actions`. """ self.clear() for action in actions: self.addAction(action) def buttonForAction(self, action): """ Return the :class:`QToolButton` instance button for `action`. """ actions = [slot.action for slot in self.__gridSlots] index = actions.index(action) return self.__gridSlots[index].button def createButtonForAction(self, action): """ Create and return a :class:`QToolButton` for action. """ button = _ToolGridButton(self) button.setDefaultAction(action) if self.__buttonSize.isValid(): button.setFixedSize(self.__buttonSize) if self.__iconSize.isValid(): button.setIconSize(self.__iconSize) button.setToolButtonStyle(self.__toolButtonStyle) button.setProperty("tool-grid-button", QVariant(True)) return button def count(self): """ Return the number of buttons/actions in the grid. """ return len(self.__gridSlots) def actionEvent(self, event): QFrame.actionEvent(self, event) if event.type() == QEvent.ActionAdded: # Note: the action is already in the self.actions() list. actions = list(self.actions()) index = actions.index(event.action()) self.__insertActionButton(index, event.action()) elif event.type() == QEvent.ActionRemoved: self.__removeActionButton(event.action()) def __insertActionButton(self, index, action): """Create a button for the action and add it to the layout at index. """ self.__shiftGrid(index, 1) button = self.createButtonForAction(action) row = index / self.__columns column = index % self.__columns self.layout().addWidget(button, row, column, Qt.AlignLeft | Qt.AlignTop) self.__gridSlots.insert(index, _ToolGridSlot(button, action, row, column)) self.__mapper.setMapping(button, action) button.clicked.connect(self.__mapper.map) button.installEventFilter(self.__buttonListener) button.installEventFilter(self) def __removeActionButton(self, action): """Remove the button for the action from the layout and delete it. """ actions = [slot.action for slot in self.__gridSlots] index = actions.index(action) slot = self.__gridSlots.pop(index) slot.button.removeEventFilter(self.__buttonListener) slot.button.removeEventFilter(self) self.__mapper.removeMappings(slot.button) self.layout().removeWidget(slot.button) self.__shiftGrid(index + 1, -1) slot.button.deleteLater() def __shiftGrid(self, start, count=1): """Shift all buttons starting at index `start` by `count` cells. """ button_count = self.layout().count() direction = 1 if count >= 0 else -1 if direction == 1: start, end = button_count - 1, start - 1 else: start, end = start, button_count for index in range(start, end, -direction): item = self.layout().itemAtPosition(index / self.__columns, index % self.__columns) if item: button = item.widget() new_index = index + count self.layout().addWidget(button, new_index / self.__columns, new_index % self.__columns, Qt.AlignLeft | Qt.AlignTop) def __relayout(self): """Relayout the buttons. """ for i in reversed(range(self.layout().count())): self.layout().takeAt(i) self.__gridSlots = [ _ToolGridSlot(slot.button, slot.action, i / self.__columns, i % self.__columns) for i, slot in enumerate(self.__gridSlots) ] for slot in self.__gridSlots: self.layout().addWidget(slot.button, slot.row, slot.column, Qt.AlignLeft | Qt.AlignTop) def __indexOf(self, button): """Return the index of button widget. """ buttons = [slot.button for slot in self.__gridSlots] return buttons.index(button) def __onButtonRightClick(self, button): pass def __onButtonEnter(self, button): action = button.defaultAction() self.actionHovered.emit(action) def __onClicked(self, action): self.actionTriggered.emit(action) def paintEvent(self, event): return utils.StyledWidget_paintEvent(self, event) def eventFilter(self, obj, event): etype = event.type() if etype == QEvent.KeyPress and obj.hasFocus(): key = event.key() if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right]: if self.__focusMove(obj, key): event.accept() return True return QFrame.eventFilter(self, obj, event) def __focusMove(self, focus, key): assert (focus is self.focusWidget()) try: index = self.__indexOf(focus) except IndexError: return False if key == Qt.Key_Down: index += self.__columns elif key == Qt.Key_Up: index -= self.__columns elif key == Qt.Key_Left: index -= 1 elif key == Qt.Key_Right: index += 1 if index >= 0 and index < self.count(): button = self.__gridSlots[index].button button.setFocus(Qt.TabFocusReason) return True else: return False