def __init__(self, config): # Initialize the object as a QWidget and # set its title and minimum width QWidget.__init__(self) self.config = config self.peerList = config.peerList self.setWindowTitle('BlastShare') self.setMinimumSize(320, 480) self.setMaximumWidth(320) self.prefw = None # connects the signals! self.connect(self.peerList, SIGNAL("initTransfer"), self.sendFileToPeer) ''' Will add feature in future version ''' ''' shareFilesAction = QAction(QIcon('exit.png'), '&Share File(s)', self) shareFilesAction.setShortcut('Ctrl+O') shareFilesAction.setStatusTip('Share File(s)') shareFilesAction.triggered.connect(quitApp) ''' preferencesAction = QAction(QIcon('exit.png'), '&Preferences', self) preferencesAction.setShortcut('Ctrl+P') preferencesAction.setStatusTip('Preferences') preferencesAction.triggered.connect(self.editPreferences) exitAction = QAction(QIcon('exit.png'), '&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit application') exitAction.triggered.connect(quitApp) menubar = QMenuBar() fileMenu = menubar.addMenu('&File') ''' Will enable in future versions ''' # fileMenu.addAction(shareFilesAction) fileMenu.addAction(preferencesAction) fileMenu.addAction(exitAction) layout = QVBoxLayout() layout.setContentsMargins(QMargins(0, 0, 0, 0)) self.setLayout(layout) statusBar = QStatusBar() statusBar.showMessage('Ready') layout.addWidget(menubar) layout.addWidget(self.peerList) layout.addWidget(statusBar)
class Tab(QWidget): def __init__(self, parent, configuration): QWidget.__init__(self, parent) self._configuration = configuration layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) main_widget = QWidget(self) self._layout_main = QVBoxLayout() self._layout_main.setContentsMargins(0, 0, 0, 0) self._layout_main.setSpacing(6) main_widget.setLayout(self._layout_main) layout.addWidget(main_widget) self._status_bar = QStatusBar() layout.addWidget(self._status_bar) self.setLayout(layout) def _add_widget(self, widget): self._layout_main.addWidget(widget) def set_error_message(self, msg): self._status_bar.showMessage(msg)
class MainWindow(QWidget): progress = pyqtSignal(int) status = pyqtSignal(str) """Main window of the MTurKClient application""" def __init__(self, projFile): QWidget.__init__(self) self.projPath = os.path.split(projFile)[0] self.projName = os.path.split(projFile)[1] self.setFixedSize(500, 680) self.setWindowIcon(QIcon('icon.png')) self.setWindowIconText("MTurk Client") if os.path.exists(os.path.join(self.projPath, 'mturk_segmentation.ini')): self.task = SegmentationTask(projFile) self.segmentation_mode = True elif os.path.exists(os.path.join(self.projPath, 'mturk_features.ini')): self.task = CorrespondenceTask(projFile) self.segmentation_mode = False else: raise Exception('No configuration file found!') self.task.setParent(self) self.setWindowFlags(Qt.WindowCloseButtonHint) self.setTitle() ###Tab widget### self.tabWidget = QTabWidget() self.initUploadTab() self.initDownloadTab() self.initManageTab() self.initSettingTab() self.initStatusBar() # Layout management vbox = QVBoxLayout() vbox.addWidget(self.tabWidget) vbox.addWidget(self.statusBar) self.setLayout(vbox) self.show() self.task.loadVideoLabelFile() if not self.task.connect(): self.status.emit("MTurk connection failed.") else: self.getBalance() def setTitle(self): if self.task.sandbox: self.setWindowTitle("MTurk Client - Sandbox Mode") else: self.setWindowTitle("MTurk Client") def getTotalNumberOfHITs(self): if self.segmentation_mode: return len(self.task.videoLabelHandler.objects) * int(self.task.assignments) else: if self.tabWidget.widget(0).findChildren(QCheckBox)[0].isChecked(): shift = 25 else: shift = 50 xN = len(range(0, self.task.videoLabelHandler.imageWidth - shift, shift)) yN = len(range(0, self.task.videoLabelHandler.imageHeight - shift, shift)) return xN * yN * int(self.task.assignments) def getBalance(self): """Get current account balance and compute costs""" self.balance = self.task.connection.get_account_balance()[0] self.credit_label.setText("Your current account balance is:\t\t\t{0}".format(self.balance)) costs = self.task.reward * self.getTotalNumberOfHITs() self.costLabel.setText("Costs per frame:\t\t\t\t\t${0}".format(costs)) def updateTable(self): appDict, rejDict = self.task.reviewTool.updateTable() for i in range(self.table_turk.rowCount()): # Get workerID workerID = str(self.table_turk.item(i, 0).text()) # Set rejected or approved if workerID in rejDict.keys(): self.table_turk.item(i, 2).setText(str(rejDict[workerID])) if workerID in appDict.keys(): self.table_turk.item(i, 3).setText(str(appDict[workerID])) def getStatus(self): """ Get current status of the HITs that are being worked on and current account balance. """ # Get Status as dictionary {WorkerID:23; ...} hitStatus, assignments = self.task.status() totalAssignments = self.getTotalNumberOfHITs() self.status.emit("Finished HITs: ({0}/{1})".format(assignments, totalAssignments)) # Update Table for i, entry in enumerate(hitStatus): self.table_turk.insertRow(i) turker = QTableWidgetItem(entry[0]) assignments = QTableWidgetItem(str(entry[1])) rejected = QTableWidgetItem(str(0)) approved = QTableWidgetItem(str(0)) self.table_turk.setItem(i, 0, turker) self.table_turk.setItem(i, 1, assignments) self.table_turk.setItem(i, 2, rejected) self.table_turk.setItem(i, 3, approved) def initSettingTab(self): # ##Setting Tab### setting_tab = QWidget() setting_tab_layout = QVBoxLayout() self.tabWidget.addTab(setting_tab, "Settings") # Task Box task_box = QGroupBox() task_box.setTitle(QString("Task properties")) task_layout = QGridLayout() # Name name = QLabel("Name:") name_value = QLineEdit() name_value.setText(self.task.hittypename) name_value.setEnabled(False) clickable(name_value).connect(self.enable) task_layout.addWidget(name, 0, 1) task_layout.addWidget(name_value, 0, 2, 1, 3) # Description description = QLabel("Description:") description_value = QLineEdit() description_value.setText(self.task.description) description_value.setEnabled(False) clickable(description_value).connect(self.enable) task_layout.addWidget(description, 1, 1) task_layout.addWidget(description_value, 1, 2, 1, 3) # Keywords keywords = QLabel("Keywords:") keywords_value = QLineEdit() keywords_value.setText(','.join(self.task.keywords)) keywords_value.setEnabled(False) clickable(keywords_value).connect(self.enable) task_layout.addWidget(keywords, 2, 1) task_layout.addWidget(keywords_value, 2, 2, 1, 3) # Qualification qualification = QLabel("Qualification [%]:") qualification_value = QSpinBox() qualification_value.setSuffix('%') qualification_value.setValue(int(self.task.qualification)) qualification_value.setEnabled(False) clickable(qualification_value).connect(self.enable) task_layout.addWidget(qualification, 3, 1) task_layout.addWidget(qualification_value, 3, 4) # Assignments assignments = QLabel("Assignments:") assignments_value = QSpinBox() assignments_value.setSuffix('') assignments_value.setValue(int(self.task.assignments)) assignments_value.setEnabled(False) clickable(assignments_value).connect(self.enable) task_layout.addWidget(assignments, 4, 1) task_layout.addWidget(assignments_value, 4, 4) # Duration duration = QLabel("Duration [min]:") duration_value = QSpinBox() duration_value.setSuffix('min') duration_value.setValue(int(self.task.duration)) duration_value.setEnabled(False) clickable(duration_value).connect(self.enable) task_layout.addWidget(duration, 5, 1) task_layout.addWidget(duration_value, 5, 4) # Reward reward = QLabel("Reward [0.01$]:") reward_value = QDoubleSpinBox() reward_value.setRange(0.01, 0.5) reward_value.setSingleStep(0.01) reward_value.setSuffix('$') reward_value.setValue(self.task.reward) reward_value.setEnabled(False) clickable(reward_value).connect(self.enable) task_layout.addWidget(reward, 6, 1) task_layout.addWidget(reward_value, 6, 4) # Lifetime lifetime = QLabel("Lifetime [d]:") lifetime_value = QSpinBox() lifetime_value.setSuffix('d') lifetime_value.setValue(self.task.lifetime) lifetime_value.setEnabled(False) clickable(lifetime_value).connect(self.enable) task_layout.addWidget(lifetime, 7, 1) task_layout.addWidget(lifetime_value, 7, 4) # sandbox sandbox = QCheckBox("Sandbox") sandbox.setChecked(self.task.sandbox) task_layout.addWidget(sandbox, 8, 1) task_box.setLayout(task_layout) task_layout.setColumnMinimumWidth(1, 120) # Image Storage Box storage_box = QGroupBox() storage_box.setTitle(QString("Image Storage")) storage_layout = QGridLayout() # Host URL host_url = QLabel("Host-URL:") host_url_value = QLineEdit() host_url_value.setText(self.task.host_url) host_url_value.setEnabled(False) clickable(host_url_value).connect(self.enable) # Dropbox Path dropbox_path = QLabel("Dropbox-Path:") dropbox_path_value = QLineEdit() dropbox_path_value.setText(self.task.dropbox_path) dropbox_path_value.setEnabled(False) clickable(dropbox_path_value).connect(self.enable) # Dropbox or S3 usingS3 = QRadioButton("S3") usingS3.setChecked(self.task.usingS3) usingS3.setEnabled(False) usingDropbox = QRadioButton("Dropbox") usingDropbox.setChecked(self.task.usingDropbox) storage_layout.addWidget(host_url, 0, 1) storage_layout.addWidget(host_url_value, 0, 2, 1, 3) storage_layout.addWidget(dropbox_path, 1, 1) storage_layout.addWidget(dropbox_path_value, 1, 2, 1, 3) # Add Layouts save_button = QPushButton("Save Settings") setting_tab_layout.addWidget(task_box) setting_tab_layout.addWidget(storage_box) setting_tab.setLayout(setting_tab_layout) save_button.clicked.connect(self.SaveSettings) storage_layout.addWidget(usingS3, 2, 1) storage_layout.addWidget(usingDropbox, 3, 1) storage_layout.addWidget(save_button, 3, 4) # storage_layout.addStretch(1) storage_box.setLayout(storage_layout) def initListView(self): """Init status table""" # List Box model model = QStandardItemModel() # Init list objects for i, frame in enumerate(self.task.videoLabelHandler.files): item = QStandardItem(frame + "\t{0} Objects".format(len(self.task.videoLabelHandler.objects))) item.setCheckState(Qt.Checked) item.setCheckable(True) model.appendRow(item) if self.segmentation_mode: model.item(0).setEnabled(False) else: model.item(i).setCheckState(Qt.Unchecked) model.item(i).setEnabled(False) # Set model self.view.setModel(model) def initUploadTab(self): ###Upload Tab### upload_tab = QWidget() upload_tab_layout = QVBoxLayout() self.tabWidget.addTab(upload_tab, "Upload HITs") # Frames Box frames_box = QGroupBox() frames_box.setTitle("Frame selection") frames_layout = QVBoxLayout() frames_label = QLabel("Select which frames to upload:") frames_layout.addWidget(frames_label) # Init list view self.view = QListView() frames_layout.addWidget(self.view) upload_button = QPushButton("Upload HITs") upload_button.clicked.connect(self.upload) frames_layout.addWidget(upload_button) frames_box.setLayout(frames_layout) # MotionOptions Box motionOptionsBox = QGroupBox() motionOptionsBox.setTitle("Motion Annotation Options") patchLabel = QLabel("Patch size: 50x50 Pixels") motionOptionsBoxLayout = QHBoxLayout() overlapping = QCheckBox("Overlapping Patches") overlapping.clicked.connect(self.getBalance) motionOptionsBoxLayout.addWidget(overlapping) motionOptionsBoxLayout.addWidget(patchLabel) motionOptionsBox.setLayout(motionOptionsBoxLayout) # LayerOptions Box options_box = QGroupBox() options_box.setTitle('Layer Annotation Options') blurLabel = QLabel("Amount: 4 Pixels") options_box_layout = QHBoxLayout() blur = QCheckBox("Blur Outlines") options_box_layout.addWidget(blur) options_box_layout.addWidget(blurLabel) options_box.setLayout(options_box_layout) # Disable not needed options if self.segmentation_mode: motionOptionsBox.setEnabled(False) else: options_box.setEnabled(False) # Costs costs_box = QGroupBox() costs_box.setTitle('Costs') costs_box_layout = QVBoxLayout() self.credit_label = QLabel("Your current account balance is:") self.costLabel = QLabel("Costs per frame:") costs_box_layout.addWidget(self.credit_label) costs_box_layout.addWidget(self.costLabel) costs_box.setLayout(costs_box_layout) # Upload Box upload_tab_layout.addWidget(frames_box) upload_tab_layout.addWidget(motionOptionsBox) upload_tab_layout.addWidget(options_box) upload_tab_layout.addWidget(costs_box) upload_tab.setLayout(upload_tab_layout) def initStatusBar(self): """Init status bar""" # Status bar self.progress.connect(self.updateProgressBar) self.status.connect(self.updateStatusBar) self.statusBar = QStatusBar() progress = QProgressBar() self.statusBar.addPermanentWidget(progress) def initDownloadTab(self): # # # Download Tab# # # download_tab = QWidget() download_tab_layout = QVBoxLayout() self.tabWidget.addTab(download_tab, "Download HITs") # Status status_box = QGroupBox() status_box.setTitle("Status") status_layout = QGridLayout() self.table_turk = ContextTable(0, 4, self) self.table_turk.setSelectionBehavior(QAbstractItemView.SelectRows) self.table_turk.setColumnWidth(0, 160) self.table_turk.setColumnWidth(1, 50) self.table_turk.setColumnWidth(2, 90) self.table_turk.setColumnWidth(3, 90) # self.table_turk.verticalHeader().setVisible(False) # Set Headers header_0 = QTableWidgetItem() header_0.setText("Turker") self.table_turk.setHorizontalHeaderItem(0, header_0) header_1 = QTableWidgetItem() header_1.setText("HITs") self.table_turk.setHorizontalHeaderItem(1, header_1) header_2 = QTableWidgetItem() header_2.setText("Rejected") self.table_turk.setHorizontalHeaderItem(2, header_2) header_3 = QTableWidgetItem() header_3.setText("Approved") self.table_turk.setHorizontalHeaderItem(3, header_3) status_layout.addWidget(self.table_turk, 0, 0, 1, 2) # Status Button status_button = QPushButton('Update Status') status_button.clicked.connect(self.getStatus) status_layout.addWidget(status_button, 1, 1) status_box.setLayout(status_layout) download_tab_layout.addWidget(status_box) # Download Button download_button = QPushButton("Download Results") download_button.clicked.connect(self.download) status_layout.addWidget(download_button, 1, 0) # Options Box options_box = QGroupBox() options_box.setTitle("Import results") options_box_layout = QGridLayout() matching = QRadioButton("Choose best matching Outlines") matching.setEnabled(False) review = QRadioButton("Review by hand") review.setChecked(True) review_button = QPushButton("Review Results") review_button.clicked.connect(self.review) # Import Button import_button = QPushButton("Import results") import_button.clicked.connect(self.importResults) options_box_layout.addWidget(review, 0, 0) options_box_layout.addWidget(review_button, 0, 1) options_box_layout.addWidget(matching, 1, 0) options_box_layout.addWidget(import_button, 2, 0, 1, 2) options_box.setLayout(options_box_layout) download_tab_layout.addWidget(options_box) download_tab.setLayout(download_tab_layout) def initManageTab(self): ###Manage Tab### manage_tab = QWidget() manage_tab_layout = QVBoxLayout() self.tabWidget.addTab(manage_tab, "Manage HITs") # Send Box send_box_layout = QVBoxLayout() subject = QLineEdit() subject_label = QLabel("Subject:") send_text = QTextEdit() send_button = QPushButton("Send Message") send_button.setMinimumWidth(135) send_button.clicked.connect(self.sendMessage) allTurkers = QRadioButton("Send message to all Turkers") allTurkers.setChecked(True) singleTurker = QRadioButton("Send message to single Turker") workerIDLabel = QLabel('Worker-ID:') workerID = QLineEdit() def checkState(): # Set enabled if checked if allTurkers.isChecked(): workerIDLabel.setEnabled(False) workerID.setEnabled(False) else: workerIDLabel.setEnabled(True) workerID.setEnabled(True) # Connect to check state allTurkers.clicked.connect(checkState) singleTurker.clicked.connect(checkState) checkState() # Choose if single or all turkers receive message chooseSendLayout = QHBoxLayout() chooseSendLayout.addWidget(singleTurker) chooseSendLayout.addWidget(workerIDLabel) chooseSendLayout.addWidget(workerID) # Send box layout send_box = QGroupBox() send_box_layout.addWidget(allTurkers) send_box_layout.addLayout(chooseSendLayout) send_box_layout.addWidget(subject_label) send_box_layout.addWidget(subject) send_box_layout.addWidget(send_text) send_box_layout.addWidget(send_button) send_box_layout.setAlignment(send_button, Qt.AlignRight) send_box.setTitle("Notify Workers") send_box.setLayout(send_box_layout) manage_tab_layout.addWidget(send_box) # Pay box payBox = QGroupBox() payBox.setTitle("Pay Workers") payBox_layout = QGridLayout() approveFeedbackLabel = QLabel("Approve Feedback:") approveFeedback = QTextEdit() approveFeedback.setText("Thank you for your work.") rejectFeedback = QTextEdit() rejectFeedback.setText("We are sorry, but we cannot accept your work because you did not follow the instructions or submitted careless work.") payBox_layout.addWidget(approveFeedbackLabel, 0, 0) payBox_layout.addWidget(approveFeedback , 1, 0, 1, 0) reject_label = QLabel("{0} HITs will be rejected".format(0)) approve_label = QLabel("{0} HITs will be approved".format(0)) pay_button = QPushButton("Pay Turkers") pay_button.clicked.connect(self.pay) payBox_layout.addWidget(reject_label, 2, 0) payBox_layout.addWidget(approve_label, 3, 0) payBox_layout.addWidget(pay_button, 4, 0) payBox.setLayout(payBox_layout) manage_tab_layout.addWidget(payBox) # Delete Box deleteBox = QGroupBox() deleteBox.setTitle("Clean up finished HITs") deleteBox_layout = QHBoxLayout() delete_label = QLabel("{0} HITs are finished and can be deleted".format(0)) delete_button = QPushButton("Delete HITs") delete_button.clicked.connect(self.delete) deleteBox_layout.addWidget(delete_label) deleteBox_layout.addWidget(delete_button) deleteBox.setLayout(deleteBox_layout) manage_tab_layout.addWidget(deleteBox) # Evaluation Button evalButton = QPushButton("Evaluate") evalButton.clicked.connect(self.evaluate) manage_tab_layout.addWidget(evalButton) # Add layouts to tab manage_tab.setLayout(manage_tab_layout) def SaveSettings(self): """Save settings to config file""" # Line edits lineEdits = self.tabWidget.widget(3).findChildren(QLineEdit) self.task._conf.set('Task', 'name', lineEdits[0].text()) self.task._conf.set('Task', 'Description', lineEdits[1].text()) self.task._conf.set('Task', 'Keywords', lineEdits[2].text()) self.task._conf.set('Image storage', 'host-url', lineEdits[8].text()) self.task._conf.set('Image storage', 'dropbox-path', lineEdits[9].text()) # Spin boxes spinBoxes = self.tabWidget.widget(3).findChildren(QSpinBox) self.task._conf.set('Task', 'assignments', spinBoxes[1].value()) self.task._conf.set('Task', 'qualification', spinBoxes[0].value()) self.task._conf.set('Task', 'duration', spinBoxes[2].value()) self.task._conf.set('Task', 'lifetime', spinBoxes[3].value()) self.task._conf.set('Task', 'reward', self.tabWidget.widget(3).findChild(QDoubleSpinBox).value()) # Dropbox usage radioButtons = self.tabWidget.widget(3).findChildren(QRadioButton) self.task._conf.set('Task', 'usings3', radioButtons[0].isChecked()) self.task._conf.set('Task', 'usingdropbox', radioButtons[1].isChecked()) # Sandbox self.task._conf.set('Task', 'sandbox', self.tabWidget.widget(3).findChild(QCheckBox).isChecked()) # Write config file self.task.saveConfigFile() # Disable objects for obj in lineEdits: obj.setEnabled(False) for obj in spinBoxes: obj.setEnabled(False) self.tabWidget.widget(3).findChild(QDoubleSpinBox).setEnabled(False) # Reload Config File self.task.loadConfigFile() self.task.connect() self.setTitle() self.getBalance() self.status.emit('Settings saved') def pay(self): self.status.emit("Paying turkers...") feedback = self.tabWidget.widget(2).findChildren(QTextEdit)[1].document() self.task.pay(feedback.toPlainText()) self.status.emit("Done") def enable(self): """Does nothing. But is required for the clickable widget option for now. Yeah!!""" pass def review(self): self.task.reviewHITs() self.updateTable() def sendMessage(self): """Send message to turkers""" # Goes the message to all or a single turker? if self.tabWidget.widget(2).findChildren(QRadioButton)[1].isChecked(): workerID = self.tabWidget.widget(2).findChildren(QLineEdit)[0].text() if workerID == "": self.status.emit('Please provide Worker-ID') return False else: workerID = "" # Get subject and message subject = self.tabWidget.widget(2).findChildren(QLineEdit)[1].text() message = self.tabWidget.widget(2).findChildren(QTextEdit)[0].document() self.task.sendMessage(subject, message.toPlainText(), workerID=workerID) return True def upload(self): """Upload HITs""" message_box = QMessageBox() reply = message_box.question(self, 'Upload HITs', "Are you sure you want to upload the HITs?", QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: if self.segmentation_mode: if self.tabWidget.widget(0).findChildren(QCheckBox)[1].isChecked(): self.task.videoLabelHandler.blurOutlines() self.task.upload() else: if self.tabWidget.widget(0).findChildren(QCheckBox)[0].isChecked(): overlapping = True else: overlapping = False self.task.upload(overlapping) def updateProgressBar(self, value): self.statusBar.children()[2].setValue(value) def updateStatusBar(self, mesg): self.statusBar.showMessage(mesg) def importResults(self): self.task.getTurked() def delete(self): self.task.deleteHit() def download(self): self.task.harvest() def evaluate(self): """ Evaluate additional data from MTurk. The following statistics are computed: * Working Time * Feedback * Hit statistics * Working time per worker * Worker statistics """ evaluation = Evaluation(self.task) evaluation.workingTime() evaluation.extractFeedback() evaluation.HITStatistics() evaluation.workingTimePerWorker() evaluation.workerStatistics()
class UniFileSyncUI(QMainWindow): """UniFileSyncUI class""" def __init__(self, name=None): super(UniFileSyncUI, self).__init__() self.ui = Ui_UniFileSyncPop() self.ui.setupUi(self) self.setFixedSize(self.size()) self.server = UServer('UServer') self.server.regSelfToBus() if name: self.setName(name) self.createActions() self.createTrayIcon() self.createStatusBar() qApp.setQuitOnLastWindowClosed(False) #connect the signal with slot self.connectUISlots(self.ui) #set UI label username = ConfManager.getManager().getValue('UI', 'username') self.ui.nameLabel.setText(username) #Start server immediately self.server.start() #self.server.getHandler('start')({'name': 'all'}) msg = self.server.initMsg('start', None, MSG_UNIQUE_ID_T_CONTROLLER, False, {'name': 'all'}) UMsgBus.getBus().send(msg) self.server.addCallBack(self.statusupdate) self.server.addCallBack(self.cloudinfoupdate) #setup list self.setupFolderList(self.ui.folderList) self.setupPluginList(self.ui.pluginList) self.setupNetworkConf() #Init status bar stBarConf = ConfManager.getManager().getValue('UI', 'statusbar') self.statusbar.showMessage(stBarConf['messages']['init']) #Init system icon self.trayIcon.show() self.showTrayIconMessage() def setupNetworkConf(self): """setup network configuration into UI""" conf = ConfManager.getManager().getValue('common', 'network') self.ui.proxyLineEdit.setText(conf['proxy']) self.ui.portLineEdit.setText("%s" % conf['port']) def closeEvent(self, event): """override close event""" if self.trayIcon.isVisible(): self.hide() event.ignore() ConfManager.getManager().save() logging.debug('[%s] is closed, window is hide, configuration is saved', self.getName()) def createActions(self): """create tray icon menu action""" self.configAction = QAction("&ShowConfig", self, triggered=self.show) self.exitAction = QAction("&Exit", self) self.exitAction.triggered.connect(lambda: UMsgBus.getBus().send(self.server.initMsg('stop', None, MSG_UNIQUE_ID_T_CONTROLLER, False, {'name': 'all'}))) self.exitAction.triggered.connect(qApp.quit) def createTrayIcon(self): """create system tray icon""" self.trayIconMenu = QMenu(self) es = self.trayIconMenu.addAction(self.configAction) self.trayIconMenu.addSeparator() ea = self.trayIconMenu.addAction(self.exitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setIcon(QIcon('icon/tray.png')) def showTrayIconMessage(self): """show tray icon message""" conf = ConfManager.getManager().getValue('UI', 'trayicon') popup = conf['popup'] self.trayIcon.showMessage(popup['title'], popup['message']) def setupFolderList(self, folderList): """setup folder list for showing""" fts = ConfManager.getManager().getValue('common', 'folders') i = 0 for ft in fts: flistItem = QListWidgetItem(QIcon('icon/folder.png'), ft, folderList) folderList.insertItem(i, flistItem) i += 1 def setupPluginList(self, pluginList): """setup plugin list from configuration file""" fts = ConfManager.getManager().getValue('common', 'plugins') i = 0 for ft in fts: flistItem = QListWidgetItem(QIcon('icon/plugin.png'), ft['name'], pluginList) pluginList.insertItem(i, flistItem) i += 1 def show(self): """ovrride parent show method""" super(UniFileSyncUI, self).show() #Init status bar stBarConf = ConfManager.getManager().getValue('UI', 'statusbar') self.statusbar.showMessage(stBarConf['messages']['init']) #set UI label username = ConfManager.getManager().getValue('UI', 'username') self.ui.nameLabel.setText(username) def connectUISlots(self, ui): """connect ui component with slots""" ui.connBtn.clicked.connect(lambda : self.connBtnSlots(ui.connBtn)) ui.addFolderBtn.clicked.connect(lambda: self.connBtnSlots(ui.addFolderBtn)) ui.rmFolderBtn.clicked.connect(lambda: self.connBtnSlots(ui.rmFolderBtn)) ui.saveBtn.clicked.connect(lambda: self.connBtnSlots(ui.saveBtn)) ui.unloadBtn.clicked.connect(lambda: self.connBtnSlots(ui.unloadBtn)) ui.reloadBtn.clicked.connect(lambda: self.connBtnSlots(ui.reloadBtn)) ui.resetBtn.clicked.connect(lambda: self.connBtnSlots(ui.resetBtn)) ui.addPluginBtn.clicked.connect(lambda: self.connBtnSlots(ui.addPluginBtn)) ui.syncFolderBtn.clicked.connect(lambda: self.connBtnSlots(ui.syncFolderBtn)) self.connect(self, SIGNAL('statusupdate'), self.statusbarUpdate) self.connect(self, SIGNAL('cloudinfoupdate'), self.infoLabelUpdate) def connBtnSlots(self, btn): """docstring for connBtnSlots""" if btn is self.ui.connBtn: if btn.text() == 'Connect': msg = self.server.initMsg('info', None, MSG_UNIQUE_ID_T_CONTROLLER, True, {'name': 'all'}) UMsgBus.getBus().send(msg) #res, data = self.server.getHandler('info')({'name': 'all'}) btn.setText('Connecting') #self.ui.infoLabel.setText(data) logging.debug('[%s]: Press Connect to getCloudInfo', self.getName()) elif btn.text() == 'Disconnect': #self.server.getHandler('stop')({'name': 'all'}) btn.setText('Connect') self.ui.infoLabel.setText('Cloud Disk is disconnected') elif btn is self.ui.addFolderBtn: fileDialog = QFileDialog(self) fileDialog.setWindowTitle('Select Folder') folderPath = fileDialog.getExistingDirectory() if folderPath != "": listItem = QListWidgetItem(QIcon('icon/folder.png'), folderPath, self.ui.folderList) pyStr = '%s' % folderPath logging.debug('[%s]: add folder path %s', self.getName(), pyStr) self.ui.folderList.insertItem(self.ui.folderList.count(), listItem) folderList = ConfManager.getManager().getValue('common', 'folders') folderList.append(pyStr) ConfManager.getManager().setValue('common', 'folders', folderList) #send watch dir request msg = self.server.initMsg('watch', None, MSG_UNIQUE_ID_T_CONTROLLER, True, {'path': pyStr, 'action': 'add'}) UMsgBus.getBus().send(msg) self.statusbar.showMessage('Adding watch path %s' % folderPath) elif btn is self.ui.rmFolderBtn: row = self.ui.folderList.currentRow() item = self.ui.folderList.currentItem() folderList = ConfManager.getManager().getValue('common', 'folders') self.statusbar.showMessage('Removing watch path %s' % item.text()) pyStr = '%s' % item.text() folderList.remove(pyStr) ConfManager.getManager().setValue('common', 'folders', folderList) logging.debug('[%s]: remove item %d %s', self.getName(), row, item.text()) msg = self.server.initMsg('watch', None, MSG_UNIQUE_ID_T_CONTROLLER, True, {'path': pyStr, 'action': 'rm'}) UMsgBus.getBus().send(msg) self.ui.folderList.takeItem(row) elif btn is self.ui.saveBtn: proxyConf = ConfManager.getManager().getValue('common', 'network') server = str(self.ui.proxyLineEdit.text()) if server != "" and server != None: user = str(self.ui.proxyNameLineEdit.text()) password = str(self.ui.proxyPwdLineEdit.text()) logging.debug('[%s]: save server=>%s user=>%s password=>%s into configuration', self.getName(), server, user, password) proxyConf['proxy'] = server proxyConf['user'] = user proxyConf['password'] = password ConfManager.getManager().setValue('common', 'network', proxyConf) msg = self.server.initMsg('proxy', None, MSG_UNIQUE_ID_T_CONTROLLER, True, {'http': 'http://%s' % server, 'https': 'https://%s' % server}) UMsgBus.getBus().send(msg) self.statusbar.showMessage('Applying proxy %s' % server) ConfManager.getManager().save() elif btn is self.ui.resetBtn: proxyConf = ConfManager.getManager().getValue('common', 'network') server = proxyConf['proxy'] user = proxyConf['user'] password = proxyConf['password'] port = proxyConf['port'] self.ui.proxyLineEdit.setText(server) self.ui.proxyNameLineEdit.setText(user) self.ui.proxyPwdLineEdit.setText(password) self.ui.portLineEdit.setText(str(port)) self.ui.serverEnableCheckBox.setCheckState(0) elif btn is self.ui.unloadBtn: row = self.ui.pluginList.currentRow() it = str(self.ui.pluginList.currentItem().text()) logging.debug('[%s]: unload plugin name %s', self.getName(), it) self.statusbar.showMessage('Unloading plugin %s' % it) PluginManager.getManager().unload(it) self.ui.pluginList.takeItem(row) conf = ConfManager.getManager().getValue('common', 'plugins') for pc in conf: if pc['name'] == it: conf.remove(pc) ConfManager.getManager().setValue('common', 'plugins', conf) elif btn is self.ui.reloadBtn: row = self.ui.pluginList.currentRow() it = str(self.ui.pluginList.currentItem().text()) logging.debug('[%s]: reload plugin name %s', self.getName(), it) self.statusbar.showMessage('Reloading plugin %s' % it) PluginManager.getManager().reload(it) elif btn is self.ui.addPluginBtn: path = QFileDialog.getOpenFileName(self) PluginManager.getManager().loadPluginFromPath(str(path)) elif btn is self.ui.syncFolderBtn: folder = str(self.ui.folderList.currentItem().text()) msg = self.server.initMsg('sync', None, MSG_UNIQUE_ID_T_CONTROLLER, True, {'path': str(folderPath), 'action': 'add'}) UMsgBus.getBus().send(msg) self.statusbar.showMessage('Sync %s with cloud' % folder) def createStatusBar(self): """create status bar""" self.statusbar = QStatusBar(self) self.setStatusBar(self.statusbar) def setName(self, name): """set server name""" self.name = name def getName(self): """get server name""" return self.name def statusupdate(self, param): """call back for status update""" self.emit(SIGNAL('statusupdate'), param['result']) def statusbarUpdate(self, res): """statusbar update callback""" self.statusbar.showMessage(ERR_STR_TABLE[res]) def cloudinfoupdate(self, param): """cloud infor update callback""" self.emit(SIGNAL('cloudinfoupdate'), param['data']) def infoLabelUpdate(self, res): """infoLabelUpdate""" if res: self.ui.infoLabel.setText(res) self.ui.connBtn.setText('Disconnect')
class OWWidget(QDialog, metaclass=WidgetMetaClass): # Global widget count widget_id = 0 # Widget description name = None id = None category = None version = None description = None long_description = None icon = "icons/Unknown.png" priority = sys.maxsize author = None author_email = None maintainer = None maintainer_email = None help = None help_ref = None url = None keywords = [] background = None replaces = None inputs = [] outputs = [] # Default widget layout settings want_basic_layout = True want_main_area = True want_control_area = True want_graph = False show_save_graph = True want_status_bar = False no_report = False save_position = True resizing_enabled = True widgetStateChanged = Signal(str, int, str) blockingStateChanged = Signal(bool) asyncCallsStateChange = Signal() progressBarValueChanged = Signal(float) processingStateChanged = Signal(int) settingsHandler = None """:type: SettingsHandler""" savedWidgetGeometry = settings.Setting(None) def __new__(cls, parent=None, *args, **kwargs): self = super().__new__(cls, None, cls.get_flags()) QDialog.__init__(self, None, self.get_flags()) stored_settings = kwargs.get('stored_settings', None) if self.settingsHandler: self.settingsHandler.initialize(self, stored_settings) self.signalManager = kwargs.get('signal_manager', None) setattr(self, gui.CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self)) self._guiElements = [] # used for automatic widget debugging self.__reportData = None # TODO: position used to be saved like this. Reimplement. #if save_position: # self.settingsList = getattr(self, "settingsList", []) + \ # ["widgetShown", "savedWidgetGeometry"] OWWidget.widget_id += 1 self.widget_id = OWWidget.widget_id if self.name: self.setCaption(self.name) self.setFocusPolicy(Qt.StrongFocus) self.startTime = time.time() # used in progressbar self.widgetState = {"Info": {}, "Warning": {}, "Error": {}} self.__blocking = False # flag indicating if the widget's position was already restored self.__was_restored = False self.__progressBarValue = -1 self.__progressState = 0 self.__statusMessage = "" if self.want_basic_layout: self.insertLayout() return self def __init__(self, *args, **kwargs): """QDialog __init__ was already called in __new__, please do not call it here.""" @classmethod def get_flags(cls): return (Qt.Window if cls.resizing_enabled else Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) # noinspection PyAttributeOutsideInit def insertLayout(self): def createPixmapWidget(self, parent, iconName): w = QLabel(parent) parent.layout().addWidget(w) w.setFixedSize(16, 16) w.hide() if os.path.exists(iconName): w.setPixmap(QPixmap(iconName)) return w self.setLayout(QVBoxLayout()) self.layout().setMargin(2) self.warning_bar = gui.widgetBox(self, orientation="horizontal", margin=0, spacing=0) self.warning_icon = gui.widgetLabel(self.warning_bar, "") self.warning_label = gui.widgetLabel(self.warning_bar, "") self.warning_label.setStyleSheet("padding-top: 5px") self.warning_bar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum) gui.rubber(self.warning_bar) self.warning_bar.setVisible(False) self.topWidgetPart = gui.widgetBox(self, orientation="horizontal", margin=0) self.leftWidgetPart = gui.widgetBox(self.topWidgetPart, orientation="vertical", margin=0) if self.want_main_area: self.leftWidgetPart.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) self.leftWidgetPart.updateGeometry() self.mainArea = gui.widgetBox(self.topWidgetPart, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding), margin=0) self.mainArea.layout().setMargin(4) self.mainArea.updateGeometry() if self.want_control_area: self.controlArea = gui.widgetBox(self.leftWidgetPart, orientation="vertical", margin=4) if self.want_graph and self.show_save_graph: graphButtonBackground = gui.widgetBox(self.leftWidgetPart, orientation="horizontal", margin=4) self.graphButton = gui.button(graphButtonBackground, self, "&Save Graph") self.graphButton.setAutoDefault(0) if self.want_status_bar: self.widgetStatusArea = QFrame(self) self.statusBarIconArea = QFrame(self) self.widgetStatusBar = QStatusBar(self) self.layout().addWidget(self.widgetStatusArea) self.widgetStatusArea.setLayout(QHBoxLayout(self.widgetStatusArea)) self.widgetStatusArea.layout().addWidget(self.statusBarIconArea) self.widgetStatusArea.layout().addWidget(self.widgetStatusBar) self.widgetStatusArea.layout().setMargin(0) self.widgetStatusArea.setFrameShape(QFrame.StyledPanel) self.statusBarIconArea.setLayout(QHBoxLayout()) self.widgetStatusBar.setSizeGripEnabled(0) self.statusBarIconArea.hide() self._warningWidget = createPixmapWidget( self.statusBarIconArea, os.path.join(environ.widget_install_dir, "icons/triangle-orange.png")) self._errorWidget = createPixmapWidget( self.statusBarIconArea, os.path.join(environ.widget_install_dir, "icons/triangle-red.png")) # status bar handler functions def setState(self, stateType, id, text): stateChanged = super().setState(stateType, id, text) if not stateChanged or not hasattr(self, "widgetStatusArea"): return iconsShown = 0 warnings = [("Warning", self._warningWidget, self._owWarning), ("Error", self._errorWidget, self._owError)] for state, widget, use in warnings: if not widget: continue if use and self.widgetState[state]: widget.setToolTip("\n".join(self.widgetState[state].values())) widget.show() iconsShown = 1 else: widget.setToolTip("") widget.hide() if iconsShown: self.statusBarIconArea.show() else: self.statusBarIconArea.hide() if (stateType == "Warning" and self._owWarning) or \ (stateType == "Error" and self._owError): if text: self.setStatusBarText(stateType + ": " + text) else: self.setStatusBarText("") self.updateStatusBarState() def updateWidgetStateInfo(self, stateType, id, text): html = self.widgetStateToHtml(self._owInfo, self._owWarning, self._owError) if html: self.widgetStateInfoBox.show() self.widgetStateInfo.setText(html) self.widgetStateInfo.setToolTip(html) else: if not self.widgetStateInfoBox.isVisible(): dHeight = - self.widgetStateInfoBox.height() else: dHeight = 0 self.widgetStateInfoBox.hide() self.widgetStateInfo.setText("") self.widgetStateInfo.setToolTip("") width, height = self.width(), self.height() + dHeight self.resize(width, height) def updateStatusBarState(self): if not hasattr(self, "widgetStatusArea"): return if self.widgetState["Warning"] or self.widgetState["Error"]: self.widgetStatusArea.show() else: self.widgetStatusArea.hide() def setStatusBarText(self, text, timeout=5000): if hasattr(self, "widgetStatusBar"): self.widgetStatusBar.showMessage(" " + text, timeout) # TODO add! def prepareDataReport(self, data): pass # ############################################## """ def isDataWithClass(self, data, wantedVarType=None, checkMissing=False): self.error([1234, 1235, 1236]) if not data: return 0 if not data.domain.classVar: self.error(1234, "A data set with a class attribute is required.") return 0 if wantedVarType and data.domain.classVar.varType != wantedVarType: self.error(1235, "Unable to handle %s class." % str(data.domain.class_var.var_type).lower()) return 0 if checkMissing and not orange.Preprocessor_dropMissingClasses(data): self.error(1236, "Unable to handle data set with no known classes") return 0 return 1 """ def restoreWidgetPosition(self): restored = False if self.save_position: geometry = self.savedWidgetGeometry if geometry is not None: restored = self.restoreGeometry(QByteArray(geometry)) if restored: space = qApp.desktop().availableGeometry(self) frame, geometry = self.frameGeometry(), self.geometry() #Fix the widget size to fit inside the available space width = space.width() - (frame.width() - geometry.width()) width = min(width, geometry.width()) height = space.height() - (frame.height() - geometry.height()) height = min(height, geometry.height()) self.resize(width, height) # Move the widget to the center of available space if it is # currently outside it if not space.contains(self.frameGeometry()): x = max(0, space.width() / 2 - width / 2) y = max(0, space.height() / 2 - height / 2) self.move(x, y) return restored def __updateSavedGeometry(self): if self.__was_restored: # Update the saved geometry only between explicit show/hide # events (i.e. changes initiated by the user not by Qt's default # window management). self.savedWidgetGeometry = self.saveGeometry() # when widget is resized, save the new width and height def resizeEvent(self, ev): QDialog.resizeEvent(self, ev) # Don't store geometry if the widget is not visible # (the widget receives a resizeEvent (with the default sizeHint) # before showEvent and we must not overwrite the the savedGeometry # with it) if self.save_position and self.isVisible(): self.__updateSavedGeometry() def moveEvent(self, ev): QDialog.moveEvent(self, ev) if self.save_position and self.isVisible(): self.__updateSavedGeometry() # set widget state to hidden def hideEvent(self, ev): if self.save_position: self.__updateSavedGeometry() self.__was_restored = False QDialog.hideEvent(self, ev) def closeEvent(self, ev): if self.save_position and self.isVisible(): self.__updateSavedGeometry() self.__was_restored = False QDialog.closeEvent(self, ev) def showEvent(self, ev): QDialog.showEvent(self, ev) if self.save_position: # Restore saved geometry on show self.restoreWidgetPosition() self.__was_restored = True def wheelEvent(self, event): """ Silently accept the wheel event. This is to ensure combo boxes and other controls that have focus don't receive this event unless the cursor is over them. """ event.accept() def setCaption(self, caption): # we have to save caption title in case progressbar will change it self.captionTitle = str(caption) self.setWindowTitle(caption) # put this widget on top of all windows def reshow(self): self.show() self.raise_() self.activateWindow() def send(self, signalName, value, id=None): if self.signalManager is not None: self.signalManager.send(self, signalName, value, id) def __setattr__(self, name, value): """Set value to members of this instance or any of its members. If member is used in a gui control, notify the control about the change. name: name of the member, dot is used for nesting ("graph.point.size"). value: value to set to the member. """ names = name.rsplit(".") field_name = names.pop() obj = reduce(lambda o, n: getattr(o, n, None), names, self) if obj is None: raise AttributeError("Cannot set '{}' to {} ".format(name, value)) if obj is self: super().__setattr__(field_name, value) else: setattr(obj, field_name, value) notify_changed(obj, field_name, value) if self.settingsHandler: self.settingsHandler.fast_save(self, name, value) def openContext(self, *a): self.settingsHandler.open_context(self, *a) def closeContext(self): self.settingsHandler.close_context(self) def retrieveSpecificSettings(self): pass def storeSpecificSettings(self): pass def saveSettings(self): self.settingsHandler.update_defaults(self) # this function is only intended for derived classes to send appropriate # signals when all settings are loaded def activate_loaded_settings(self): pass # reimplemented in other widgets def onDeleteWidget(self): pass def handleNewSignals(self): # this is called after all new signals have been handled # implement this in your widget if you want to process something only # after you received multiple signals pass # ############################################ # PROGRESS BAR FUNCTIONS def progressBarInit(self): self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") if self.__progressState != 1: self.__progressState = 1 self.processingStateChanged.emit(1) self.progressBarValue = 0 def progressBarSet(self, value): old = self.__progressBarValue self.__progressBarValue = value if value > 0: if self.__progressState != 1: warnings.warn("progressBarSet() called without a " "preceding progressBarInit()", stacklevel=2) self.__progressState = 1 self.processingStateChanged.emit(1) usedTime = max(1, time.time() - self.startTime) totalTime = (100.0 * usedTime) / float(value) remainingTime = max(0, totalTime - usedTime) h = int(remainingTime / 3600) min = int((remainingTime - h * 3600) / 60) sec = int(remainingTime - h * 3600 - min * 60) if h > 0: text = "%(h)d:%(min)02d:%(sec)02d" % vars() else: text = "%(min)d:%(sec)02d" % vars() self.setWindowTitle(self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars()) else: self.setWindowTitle(self.captionTitle + " (0% complete)") self.progressBarValueChanged.emit(value) if old != value: self.progressBarValueChanged.emit(value) qApp.processEvents() def progressBarValue(self): return self.__progressBarValue progressBarValue = pyqtProperty(float, fset=progressBarSet, fget=progressBarValue) processingState = pyqtProperty(int, fget=lambda self: self.__progressState) def progressBarAdvance(self, value): self.progressBarSet(self.progressBarValue + value) def progressBarFinished(self): self.setWindowTitle(self.captionTitle) if self.__progressState != 0: self.__progressState = 0 self.processingStateChanged.emit(0) #: Widget's status message has changed. statusMessageChanged = Signal(str) def setStatusMessage(self, text): if self.__statusMessage != text: self.__statusMessage = text self.statusMessageChanged.emit(text) def statusMessage(self): return self.__statusMessage def keyPressEvent(self, e): if (int(e.modifiers()), e.key()) in OWWidget.defaultKeyActions: OWWidget.defaultKeyActions[int(e.modifiers()), e.key()](self) else: QDialog.keyPressEvent(self, e) def information(self, id=0, text=None): self.setState("Info", id, text) def warning(self, id=0, text=""): self.setState("Warning", id, text) def error(self, id=0, text=""): self.setState("Error", id, text) def setState(self, state_type, id, text): changed = 0 if type(id) == list: for val in id: if val in self.widgetState[state_type]: self.widgetState[state_type].pop(val) changed = 1 else: if type(id) == str: text = id id = 0 if not text: if id in self.widgetState[state_type]: self.widgetState[state_type].pop(id) changed = 1 else: self.widgetState[state_type][id] = text changed = 1 if changed: if type(id) == list: for i in id: self.widgetStateChanged.emit(state_type, i, "") else: self.widgetStateChanged.emit(state_type, id, text or "") tooltip_lines = [] highest_type = None for a_type in ("Error", "Warning", "Info"): msgs_for_ids = self.widgetState.get(a_type) if not msgs_for_ids: continue msgs_for_ids = list(msgs_for_ids.values()) if not msgs_for_ids: continue tooltip_lines += msgs_for_ids if highest_type is None: highest_type = a_type if highest_type is None: self.set_warning_bar(None) elif len(tooltip_lines) == 1: msg = tooltip_lines[0] if "\n" in msg: self.set_warning_bar( highest_type, msg[:msg.index("\n")] + " (...)", msg) else: self.set_warning_bar( highest_type, tooltip_lines[0], tooltip_lines[0]) else: self.set_warning_bar( highest_type, "{} problems during execution".format(len(tooltip_lines)), "\n".join(tooltip_lines)) return changed def set_warning_bar(self, state_type, text=None, tooltip=None): colors = {"Error": ("#ffc6c6", "black", QStyle.SP_MessageBoxCritical), "Warning": ("#ffffc9", "black", QStyle.SP_MessageBoxWarning), "Info": ("#ceceff", "black", QStyle.SP_MessageBoxInformation)} current_height = self.height() if state_type is None: if not self.warning_bar.isHidden(): new_height = current_height - self.warning_bar.height() self.warning_bar.setVisible(False) self.resize(self.width(), new_height) return background, foreground, icon = colors[state_type] style = QApplication.instance().style() self.warning_icon.setPixmap(style.standardIcon(icon).pixmap(14, 14)) self.warning_bar.setStyleSheet( "background-color: {}; color: {};" "padding: 3px; padding-left: 6px; vertical-align: center". format(background, foreground)) self.warning_label.setText(text) self.warning_label.setToolTip(tooltip) if self.warning_bar.isHidden(): self.warning_bar.setVisible(True) new_height = current_height + self.warning_bar.height() self.resize(self.width(), new_height) def widgetStateToHtml(self, info=True, warning=True, error=True): pixmaps = self.getWidgetStateIcons() items = [] iconPath = {"Info": "canvasIcons:information.png", "Warning": "canvasIcons:warning.png", "Error": "canvasIcons:error.png"} for show, what in [(info, "Info"), (warning, "Warning"), (error, "Error")]: if show and self.widgetState[what]: items.append('<img src="%s" style="float: left;"> %s' % (iconPath[what], "\n".join(self.widgetState[what].values()))) return "<br>".join(items) @classmethod def getWidgetStateIcons(cls): if not hasattr(cls, "_cached__widget_state_icons"): iconsDir = os.path.join(environ.canvas_install_dir, "icons") QDir.addSearchPath("canvasIcons", os.path.join(environ.canvas_install_dir, "icons/")) info = QPixmap("canvasIcons:information.png") warning = QPixmap("canvasIcons:warning.png") error = QPixmap("canvasIcons:error.png") cls._cached__widget_state_icons = \ {"Info": info, "Warning": warning, "Error": error} return cls._cached__widget_state_icons defaultKeyActions = {} if sys.platform == "darwin": defaultKeyActions = { (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(), (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible())} def setBlocking(self, state=True): """ Set blocking flag for this widget. While this flag is set this widget and all its descendants will not receive any new signals from the signal manager """ if self.__blocking != state: self.__blocking = state self.blockingStateChanged.emit(state) def isBlocking(self): """ Is this widget blocking signal processing. """ return self.__blocking def resetSettings(self): self.settingsHandler.reset_settings(self)
def showMessage(self, message, timeout): if settings.SHOW_STATUS_NOTIFICATIONS: self._widgetStatus.hide() self._replaceWidget.setVisible(False) self.show() QStatusBar.showMessage(self, message, timeout)
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.sync_delay = 5 self.sync_active = False self.verbose = False self.timePattern = re.compile('\.[0-9]+$') self.setWindowTitle('%s %s' % (QApplication.applicationName(), QApplication.applicationVersion())); self.widget = QWidget() self.setCentralWidget(self.widget) self.statusBar = QStatusBar(self) self.setStatusBar(self.statusBar) self.mAction = self.menuBar().addMenu(self.tr("&Action")) #self.mAction.addAction(self.tr("&update"), self.updateTplTable(), QKeySequence('F5')) self.mAction.addAction(self.tr('&import records'), self.onImport, QKeySequence('F6')) self.mAction.addAction(self.tr('edit &settings'), self.onSettings, QKeySequence('F8')) self.mAction.addAction(self.tr("e&xit"), self.onExit, 'Ctrl+Q') self.mAbout = self.menuBar().addMenu(self.tr("&about")) self.mAbout.addAction(QApplication.applicationName(), self.onAboutAppAction) self.mAbout.addAction("Qt", self.onAboutQtAction) self.pageForwardButton = QPushButton(self) self.pageForwardButton.setText('>') self.connect(self.pageForwardButton, SIGNAL('clicked()'), self.pageForward) self.pageBackwardButton = QPushButton(self) self.pageBackwardButton.setText('<') self.connect(self.pageBackwardButton, SIGNAL('clicked()'), self.pageBackward) self.timer = QTimer(self) self.timer.setInterval(1000) self.connect(self.timer, SIGNAL('timeout()'), self, SLOT('onTimer()')) self.time_begin = datetime.now() self.time_end = datetime.now() self.db_path = os.path.join(os.path.dirname(sys.argv[0]) if os.name != 'posix' else os.path.expanduser('~'), '.tt2.db') self.db = sqlite3.connect(self.db_path) self.cursor = self.db.cursor() try: self.cursor.execute('SELECT id FROM tt LIMIT 1') except: self.createDb() self.settings = self.fetchSettings() self.syncer = Syncer(self.db_path, self) self.connect( self.syncer, SIGNAL('active'), self.setSyncerActive ) self.connect( self.syncer, SIGNAL('message'), self.msg ) self.connect( self.syncer, SIGNAL('newSettings'), self.fetchSettings ) self.layout = QGridLayout(self.widget) self.descriptionLabel = QLabel(self.widget) self.descriptionLabel.setText('Beschreibung') self.descriptionLabel.setMaximumHeight( self.font().pointSize() * 2 ) self.descriptionInput = QLineEdit(self.widget) self.updateDescriptionEditCompleter() self.noteLabel = QLabel(self.widget) self.noteLabel.setText('Notiz') self.noteLabel.setMaximumHeight( self.font().pointSize() * 2 ) self.noteInput = QLineEdit(self.widget) self.startStopButton = QPushButton(self.widget) self.startStopButton.setText('Start') self.tableView = TplTable(self, int( self.getSetting('displayrows', DEFAULTROWS) ) ) self.pageForwardAction = QAction(self) self.pageForwardAction.setShortcut(QKeySequence('Right')) self.connect(self.pageForwardAction, SIGNAL('triggered()'), self.pageForward); self.pageForwardButton.addAction(self.pageForwardAction) self.pageBackwardAction = QAction(self) self.pageBackwardAction.setShortcut(QKeySequence('Left')) self.connect(self.pageBackwardAction, SIGNAL('triggered()'), self.pageBackward); self.pageBackwardButton.addAction(self.pageBackwardAction) self.updateTplTable() self.layout.addWidget(self.descriptionLabel, 0, 0, 1, 1) self.layout.addWidget(self.descriptionInput, 1, 0, 1, 1) self.layout.addWidget(self.noteLabel, 0, 1, 1, 1) self.layout.addWidget(self.noteInput, 1, 1, 1, 1) self.layout.addWidget(self.startStopButton, 2, 0, 1, 2) self.layout.addWidget(self.tableView, 3,0,1,2) self.layout.addWidget(self.pageBackwardButton, 4, 0, 1, 1) self.layout.addWidget(self.pageForwardButton, 4, 1, 1, 1) self.connect(self.descriptionInput, SIGNAL('returnPressed ()'), self.onStartStop ) self.connect(self.noteInput, SIGNAL('returnPressed ()'), self.onStartStop ) self.connect(self.startStopButton, SIGNAL('clicked()'), self.onStartStop ) self.connect(self.tableView, SIGNAL('valueChanged(int)'), self.onValueChanged ) self.connect(self.tableView, SIGNAL('del(int)'), self.onDelete ) self.last_sync = datetime.now() self.sync() def __del__(self): pass def setSyncerActive(self, active): if not active: self.last_sync = datetime.now() self.sync_active = active def msg(self, msg, timeout = 0): #print(msg) self.statusBar.showMessage(msg, timeout) def sync(self): if self.getSetting('syncEnabled','False') == 'False': return # reset delay if still active if self.sync_active: self.last_sync = datetime.now() if datetime.now() < ( self.last_sync + timedelta( seconds = self.sync_delay ) ): try: #print 'cancel' self.t.cancel() except: pass #print 'start +',( self.last_sync + timedelta( seconds = self.sync_delay ) - datetime.now() ).seconds + 1 self.t = Timer( ( self.last_sync + timedelta( seconds = self.sync_delay ) - datetime.now() ).seconds + 1 ,self.sync) self.t.start() else: # print 'start syncer instance' Thread(target=self.syncer.do_sync).start() def createDb(self): try: self.q('''CREATE TABLE tt ( id INTEGER PRIMARY KEY AUTOINCREMENT, remote_id INTEGER, time_begin INTEGER, time_end INTEGER, description STRING, note STRING DEFAULT "", is_new INTEGER DEFAULT 1, need_update INTEGER DEFAULT 0, is_delete INTEGER DEFAULT 0 )''') self.q('''CREATE TABLE settings ( key STRING UNIQUE, value STRING )''') self.q('CREATE INDEX idx_time_begin ON tt (time_begin)') except: self.statusBar.showMessage('error creating Database!') else: self.statusBar.showMessage('Table tt created successfully') def q(self, query): try: self.cursor.execute(query) except Exception as e: print( e ) self.statusBar.showMessage('query execution failed "%s"' % query) else: self.db.commit() def updateTplTable(self): self.q('SELECT id,time_begin,time_end,description,note FROM tt WHERE is_delete != 1 ORDER BY time_begin DESC LIMIT %d' % ( int( self.getSetting( 'displayrows', DEFAULTROWS ) ) ) ) self.tableView.set(self.cursor.fetchall()) def updateDescriptionEditCompleter(self): self.q('SELECT DISTINCT description FROM tt WHERE is_delete != 1') #words = QStringList() words = [] for word in self.cursor.fetchall(): words.append(str(word[0])) self.descriptionInput.setCompleter(QCompleter(words, self)) @pyqtSlot() def pageForward(self): self.q('SELECT MIN(time_begin) FROM tt') if not self.tableView.getLastTime() == self.cursor.fetchone()[0]: sql = 'SELECT id,time_begin,time_end,description,note FROM tt WHERE is_delete != 1 AND time_begin < %d ORDER BY time_begin DESC LIMIT %s' % ( self.tableView.getLastTime(), int( self.getSetting( 'displayrows', DEFAULTROWS ) ) ) if self.verbose: print( sql ) self.q( sql ) self.tableView.set(self.cursor.fetchall()) @pyqtSlot() def pageBackward(self): self.q('SELECT MAX(time_begin) FROM tt') if not self.tableView.getFirstTime() == self.cursor.fetchone()[0]: sql = 'SELECT * FROM ( SELECT id,time_begin,time_end,description,note FROM tt WHERE is_delete != 1 AND time_begin > %d ORDER BY time_begin LIMIT %s ) as tbl ORDER BY time_begin DESC' % ( self.tableView.getFirstTime(), int( self.getSetting( 'displayrows', DEFAULTROWS ) ) ) if self.verbose: print( sql ) self.q( sql ) self.tableView.set(self.cursor.fetchall()) @pyqtSlot() def onExit(self): QApplication.exit(); @pyqtSlot() def onValueChanged(self, _id): if self.verbose: print('changed:', _id) print(self.tableView.get(_id)) data = self.tableView.get(_id) self.q(''' UPDATE tt SET time_begin = %d, time_end = %d, description = '%s', note = '%s', need_update = 1 WHERE id = %d ''' % ( data[1], data[2], data[3], data[4], data[0] )) self.updateDescriptionEditCompleter() self.sync() @pyqtSlot() def onDelete(self, _id): if self.verbose: print('del:', _id,self.tableView.get(_id)[0]) self.q('UPDATE tt SET is_delete = 1 WHERE id = %d' % self.tableView.get(_id)[0]) self.updateTplTable() self.updateDescriptionEditCompleter() self.sync() @pyqtSlot() def onTimer(self): self.startStopButton.setText('Stop (%s)' % self.timePattern.sub( '', str( datetime.now() - self.time_begin ) ) ) @pyqtSlot() def onStartStop(self): if self.timer.isActive(): self.timer.stop() self.time_end = datetime.now() self.q(''' INSERT INTO tt (time_begin,time_end,description,note) VALUES ('%d','%d','%s','%s') ''' % ( int(mktime(self.time_begin.timetuple())), int(mktime(self.time_end.timetuple())), self.descriptionInput.text(), self.noteInput.text() )) self.noteInput.clear() self.updateTplTable() self.updateDescriptionEditCompleter() self.startStopButton.setText('Start') self.sync() else: self.time_begin = datetime.now() self.timer.start() self.onTimer() def onAboutAppAction(self): QMessageBox.about(self, self.tr("&about"), self.tr("%1 version %2").arg(QApplication.applicationName()).arg(QApplication.applicationVersion())) def onAboutQtAction(self): QMessageBox.aboutQt(self, self.tr("&about")) def checkSync(self): if self.sync_active: QMessageBox.information(self, 'Information', '''Sync is currently active. Please wait until it's finished''') return False else: return True def onSettings(self): if not self.checkSync(): return settings = self.fetchSettings() inp = SettingsWidget(settings, self) if inp.exec_(): for key in inp.data(): if type( inp.data()[key] ) == bytes: value = inp.data()[key].decode( 'UTF-8' ) else: value = inp.data()[key] self.q( '''REPLACE INTO settings VALUES ('%s','%s')''' % ( key, value ) ) try: dr = settings['displayrows'] except: dr = None if dr != inp.data()['displayrows']: QMessageBox.information(self, 'displayrows changed...', 'exiting now.') sys.exit(0) self.settings = self.fetchSettings() def onImport(self): if not self.checkSync(): return ImportWidget( self.db_path, self ).exec_() self.updateTplTable() self.updateDescriptionEditCompleter() def fetchSettings(self): s = {} self.q( 'SELECT key,value FROM settings' ) for key,value in self.cursor.fetchall(): s[key] = value return s def getSetting(self,key,default = None): try: v = self.settings[key] except: if default != None: v = default else: v = '' return v
class QTSeedEditor(QDialog): """ DICOM viewer. """ @staticmethod def get_line(mode='h'): line = QFrame() if mode == 'h': line.setFrameStyle(QFrame.HLine) elif mode == 'v': line.setFrameStyle(QFrame.VLine) line.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) return line def initUI(self, shape, vscale, height=600, mode='seed'): """ Initialize UI. Parameters ---------- shape : (int, int, int) Shape of data matrix. vscale : (float, float, float) Voxel scaling. height : int Maximal slice height in pixels. mode : str Editor mode. """ # picture grid = height / float(shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box = SliceBox(shape[:-1], mgrid, mode) self.slice_box.setScrollFun(self.scrollSlices) self.connect(self.slice_box, SIGNAL('focus_slider'), self.focusSliceSlider) # sliders self.allow_select_slice = True self.n_slices = shape[2] self.slider = QSlider(Qt.Vertical) self.slider.label = QLabel() self.slider.label.setText('Slice: %d / %d' % (self.actual_slice, self.n_slices)) self.slider.setRange(1, self.n_slices) self.slider.valueChanged.connect(self.sliderSelectSlice) self.slider.setValue(self.actual_slice) self.slider_cw = {} self.slider_cw['c'] = QSlider(Qt.Horizontal) self.slider_cw['c'].valueChanged.connect(self.changeC) self.slider_cw['c'].label = QLabel() self.slider_cw['w'] = QSlider(Qt.Horizontal) self.slider_cw['w'].valueChanged.connect(self.changeW) self.slider_cw['w'].label = QLabel() self.view_label = QLabel('View size: %d x %d' % self.img_aview.shape[:-1]) self.voxel_label = QLabel('Voxel size [mm]:\n %.2f x %.2f x %.2f'\ % tuple(self.voxel_size[np.array(self.act_transposition)])) combo_view_options = VIEW_TABLE.keys() combo_view = QComboBox(self) combo_view.activated[str].connect(self.setView) combo_view.addItems(combo_view_options) # buttons self.btn_quit = QPushButton("Return", self) self.btn_quit.clicked.connect(self.quit) combo_dmask = QComboBox(self) combo_dmask.activated.connect(self.changeMask) self.mask_points_tab, aux = self.init_draw_mask(DRAW_MASK, mgrid) for icon, label in aux: combo_dmask.addItem(icon, label) self.slice_box.setMaskPoints( self.mask_points_tab[combo_dmask.currentIndex()]) self.status_bar = QStatusBar() self.seeds_copy = None vopts = [] vmenu = [] appmenu = [] if mode == 'seed' and self.mode_fun is not None: btn_recalc = QPushButton("Recalculate", self) btn_recalc.clicked.connect(self.recalculate) self.btn_save = QPushButton("Save seeds", self) self.btn_save.clicked.connect(self.saveload_seeds) btn_s2b = QPushButton("Seg. to bckgr.", self) btn_s2b.clicked.connect(self.seg_to_background_seeds) btn_s2f = QPushButton("Seg. to forgr.", self) btn_s2f.clicked.connect(self.seg_to_foreground_seeds) appmenu.append( QLabel('<b>Segmentation mode</b><br><br><br>' + 'Select the region of interest<br>' + 'using the mouse buttons:<br><br>' + ' <i>left</i> - inner region<br>' + ' <i>right</i> - outer region<br><br>')) appmenu.append(btn_recalc) appmenu.append(self.btn_save) appmenu.append(btn_s2f) appmenu.append(btn_s2b) appmenu.append(QLabel()) self.volume_label = QLabel('Volume:\n unknown') appmenu.append(self.volume_label) # Set middle pencil as default (M. Jirik) combo_dmask.setCurrentIndex(1) self.slice_box.setMaskPoints( self.mask_points_tab[combo_dmask.currentIndex()]) # -----mjirik---end------ if mode == 'seed' or mode == 'crop'\ or mode == 'mask' or mode == 'draw': combo_seed_label_options = ['all', '1', '2', '3', '4'] combo_seed_label = QComboBox(self) combo_seed_label.activated[str].connect(self.changeFocusedLabel) combo_seed_label.addItems(combo_seed_label_options) self.changeFocusedLabel( combo_seed_label_options[combo_seed_label.currentIndex()]) # vopts.append(QLabel('Label to delete:')) # vopts.append(combo_seed_label) vmenu.append(QLabel('Label to delete:')) vmenu.append(combo_seed_label) btn_del = QPushButton("Del Slice Seeds", self) btn_del.clicked.connect(self.deleteSliceSeeds) vmenu.append(None) vmenu.append(btn_del) btn_del = QPushButton("Del All Seeds", self) btn_del.clicked.connect(self.deleteSeedsInAllImage) vmenu.append(None) vmenu.append(btn_del) combo_contour_options = ['fill', 'contours', 'off'] combo_contour = QComboBox(self) combo_contour.activated[str].connect(self.changeContourMode) combo_contour.addItems(combo_contour_options) self.changeContourMode( combo_contour_options[combo_contour.currentIndex()]) vopts.append(QLabel('Selection mode:')) vopts.append(combo_contour) if mode == 'mask': btn_recalc_mask = QPushButton("Recalculate mask", self) btn_recalc_mask.clicked.connect(self.updateMaskRegion_btn) btn_all = QPushButton("Select all", self) btn_all.clicked.connect(self.maskSelectAll) btn_reset = QPushButton("Reset selection", self) btn_reset.clicked.connect(self.resetSelection) btn_reset_seads = QPushButton("Reset seads", self) btn_reset_seads.clicked.connect(self.resetSeads) btn_add = QPushButton("Add selection", self) btn_add.clicked.connect(self.maskAddSelection) btn_rem = QPushButton("Remove selection", self) btn_rem.clicked.connect(self.maskRemoveSelection) btn_mask = QPushButton("Mask region", self) btn_mask.clicked.connect(self.maskRegion) appmenu.append( QLabel('<b>Mask mode</b><br><br><br>' + 'Select the region to mask<br>' + 'using the left mouse button<br><br>')) appmenu.append(self.get_line('h')) appmenu.append(btn_recalc_mask) appmenu.append(btn_all) appmenu.append(btn_reset) appmenu.append(btn_reset_seads) appmenu.append(self.get_line('h')) appmenu.append(btn_add) appmenu.append(btn_rem) appmenu.append(self.get_line('h')) appmenu.append(btn_mask) appmenu.append(self.get_line('h')) self.mask_qhull = None if mode == 'crop': btn_crop = QPushButton("Crop", self) btn_crop.clicked.connect(self.crop) appmenu.append( QLabel('<b>Crop mode</b><br><br><br>' + 'Select the crop region<br>' + 'using the left mouse button<br><br>')) appmenu.append(btn_crop) if mode == 'draw': appmenu.append( QLabel('<b>Manual segmentation<br> mode</b><br><br><br>' + 'Mark the region of interest<br>' + 'using the mouse buttons:<br><br>' + ' <i>left</i> - draw<br>' + ' <i>right</i> - erase<br>' + ' <i>middle</i> - vol. erase<br><br>')) btn_reset = QPushButton("Reset", self) btn_reset.clicked.connect(self.resetSliceDraw) vmenu.append(None) vmenu.append(btn_reset) combo_erase_options = ['inside', 'outside'] combo_erase = QComboBox(self) combo_erase.activated[str].connect(self.changeEraseMode) combo_erase.addItems(combo_erase_options) self.changeEraseMode( combo_erase_options[combo_erase.currentIndex()]) vopts.append(QLabel('Volume erase mode:')) vopts.append(combo_erase) hbox = QHBoxLayout() vbox = QVBoxLayout() vbox_left = QVBoxLayout() vbox_app = QVBoxLayout() hbox.addWidget(self.slice_box) hbox.addWidget(self.slider) vbox_left.addWidget(self.slider.label) vbox_left.addWidget(self.view_label) vbox_left.addWidget(self.voxel_label) vbox_left.addWidget(QLabel()) vbox_left.addWidget(QLabel('View plane:')) vbox_left.addWidget(combo_view) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(self.slider_cw['c'].label) vbox_left.addWidget(self.slider_cw['c']) vbox_left.addWidget(self.slider_cw['w'].label) vbox_left.addWidget(self.slider_cw['w']) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(QLabel('Drawing mask:')) vbox_left.addWidget(combo_dmask) for ii in vopts: vbox_left.addWidget(ii) for ii in vmenu: if ii is None: vbox_left.addStretch(1) else: vbox_left.addWidget(ii) for ii in appmenu: if ii is None: vbox_app.addStretch(1) else: vbox_app.addWidget(ii) vbox_app.addStretch(1) vbox_app.addWidget(self.btn_quit) hbox.addLayout(vbox_left) hbox.addWidget(self.get_line('v')) hbox.addLayout(vbox_app) vbox.addLayout(hbox) vbox.addWidget(self.status_bar) self.my_layout = vbox self.setLayout(vbox) self.setWindowTitle('Segmentation Editor') self.show() def __init__(self, img, viewPositions=None, seeds=None, contours=None, mode='seed', modeFun=None, voxelSize=[1, 1, 1], volume_unit='mm3'): """ Initiate Editor Parameters ---------- img : array DICOM data matrix. actualSlice : int Index of actual slice. seeds : array Seeds, user defined regions of interest. contours : array Computed segmentation. mode : str Editor modes: 'seed' - seed editor 'crop' - manual crop 'draw' - drawing 'mask' - mask region modeFun : fun Mode function invoked by user button. voxelSize : tuple of float voxel size [mm] volume_unit : allow select output volume in mililiters or mm3 [mm, ml] """ QDialog.__init__(self) self.BACKGROUND_NOMODEL_SEED_LABEL = 4 self.FOREGROUND_NOMODEL_SEED_LABEL = 3 self.mode = mode self.mode_fun = modeFun self.actual_view = 'axial' self.act_transposition = VIEW_TABLE[self.actual_view] self.img = img self.img_aview = self.img.transpose(self.act_transposition) self.volume_unit = volume_unit self.last_view_position = {} for jj, ii in enumerate(VIEW_TABLE.keys()): if viewPositions is None: viewpos = img.shape[VIEW_TABLE[ii][-1]] / 2 else: viewpos = viewPositions[jj] self.last_view_position[ii] =\ img.shape[VIEW_TABLE[ii][-1]] - viewpos - 1 self.actual_slice = self.last_view_position[self.actual_view] # set contours self.contours = contours if self.contours is None: self.contours_aview = None else: self.contours_aview = self.contours.transpose( self.act_transposition) # masked data - has information about which data were removed # 1 == enabled, 0 == deleted # How to return: # editorDialog.exec_() # masked_data = editorDialog.masked self.masked = np.ones(self.img.shape, np.int8) self.voxel_size = np.squeeze(np.asarray(voxelSize)) self.voxel_scale = self.voxel_size / float(np.min(self.voxel_size)) self.voxel_volume = np.prod(voxelSize) # set seeds if seeds is None: self.seeds = np.zeros(self.img.shape, np.int8) else: self.seeds = seeds self.seeds_aview = self.seeds.transpose(self.act_transposition) self.seeds_modified = False self.initUI(self.img_aview.shape, self.voxel_scale[np.array(self.act_transposition)], 600, mode) if mode == 'draw': self.seeds_orig = self.seeds.copy() self.slice_box.setEraseFun(self.eraseVolume) # set view window values C/W lb = np.min(img) self.img_min_val = lb ub = np.max(img) dul = np.double(ub) - np.double(lb) self.cw_range = {'c': [lb, ub], 'w': [1, dul]} self.slider_cw['c'].setRange(lb, ub) self.slider_cw['w'].setRange(1, dul) self.changeC(lb + dul / 2) self.changeW(dul) self.offset = np.zeros((3, ), dtype=np.int16) # set what labels will be deleted by 'delete seeds' button self.textFocusedLabel = "all" def showStatus(self, msg): self.status_bar.showMessage(QString(msg)) QApplication.processEvents() def init_draw_mask(self, draw_mask, grid): mask_points = [] mask_iconlabel = [] for mask, label in draw_mask: w, h = mask.shape xx, yy = mask.nonzero() mask_points.append((xx - w / 2, yy - h / 2)) img = QImage(w, h, QImage.Format_ARGB32) img.fill(qRgba(255, 255, 255, 0)) for ii in range(xx.shape[0]): img.setPixel(xx[ii], yy[ii], qRgba(0, 0, 0, 255)) img = img.scaled(QSize(w * grid[0], h * grid[1])) icon = QIcon(QPixmap.fromImage(img)) mask_iconlabel.append((icon, label)) return mask_points, mask_iconlabel def saveSliceSeeds(self): aux = self.slice_box.getSliceSeeds() if aux is not None: self.seeds_aview[..., self.actual_slice] = aux self.seeds_modified = True else: self.seeds_modified = False def updateMaskRegion_btn(self): self.saveSliceSeeds() self.updateMaskRegion() def updateMaskRegion(self): crp = self.getCropBounds(return_nzs=True) if crp is not None: off, cri, nzs = crp if nzs[0].shape[0] <= 5: self.showStatus("Not enough points (need >= 5)!") else: points = np.transpose(nzs) hull = Delaunay(points) X, Y, Z = np.mgrid[cri[0], cri[1], cri[2]] grid = np.vstack([X.ravel(), Y.ravel(), Z.ravel()]).T simplex = hull.find_simplex(grid) fill = grid[simplex >= 0, :] fill = (fill[:, 0], fill[:, 1], fill[:, 2]) if self.contours is None or self.contours_old is None: self.contours = np.zeros(self.img.shape, np.int8) self.contours_old = self.contours.copy() else: self.contours[self.contours != 2] = 0 self.contours[fill] = 1 self.contours_aview = self.contours.transpose( self.act_transposition) self.selectSlice(self.actual_slice) def maskRegion(self): self.masked[self.contours == 0] = 0 self.img[self.contours != 2] = self.img_min_val self.contours.fill(0) self.contours_old = self.contours.copy() self.seeds.fill(0) self.selectSlice(self.actual_slice) def maskAddSelection(self): self.updateMaskRegion() if self.contours is None: return self.contours[self.contours == 1] = 2 self.contours_old = self.contours.copy() self.seeds.fill(0) self.selectSlice(self.actual_slice) def maskRemoveSelection(self): self.updateMaskRegion() if self.contours is None: return self.contours[self.contours == 1] = 0 self.contours_old = self.contours.copy() self.seeds.fill(0) self.selectSlice(self.actual_slice) def maskSelectAll(self): self.updateMaskRegion() self.seeds[0][0][0] = 1 self.seeds[0][0][-1] = 1 self.seeds[0][-1][0] = 1 self.seeds[0][-1][-1] = 1 self.seeds[-1][0][0] = 1 self.seeds[-1][0][-1] = 1 self.seeds[-1][-1][0] = 1 self.seeds[-1][-1][-1] = 1 self.updateMaskRegion() self.selectSlice(self.actual_slice) def resetSelection(self): self.updateMaskRegion() if self.contours is None: return self.contours.fill(0) self.contours_old = self.contours.copy() self.seeds.fill(0) self.selectSlice(self.actual_slice) def resetSeads(self): self.seeds.fill(0) if self.contours is not None: self.contours = self.contours_old.copy() self.contours_aview = self.contours.transpose( self.act_transposition) self.updateMaskRegion() self.selectSlice(self.actual_slice) def updateCropBounds(self): crp = self.getCropBounds() if crp is not None: _, cri = crp self.contours = np.zeros(self.img.shape, np.int8) self.contours[cri].fill(1) self.contours_aview = self.contours.transpose( self.act_transposition) def focusSliceSlider(self): self.slider.setFocus(True) def sliderSelectSlice(self, value): self.selectSlice(self.n_slices - value) def scrollSlices(self, inc): if abs(inc) > 0: new = self.actual_slice + inc self.selectSlice(new) def selectSlice(self, value, force=False): if not (self.allow_select_slice): return if (value < 0) or (value >= self.n_slices): return if (value != self.actual_slice) or force: self.saveSliceSeeds() if self.seeds_modified: if self.mode == 'crop': self.updateCropBounds() elif self.mode == 'mask': self.updateMaskRegion() if self.contours is None: contours = None else: contours = self.contours_aview[..., value] slider_val = self.n_slices - value self.slider.setValue(slider_val) self.slider.label.setText('Slice: %d / %d' % (slider_val, self.n_slices)) self.slice_box.setSlice(self.img_aview[..., value], self.seeds_aview[..., value], contours) self.actual_slice = value def getSeeds(self): return self.seeds def getImg(self): return self.img def getOffset(self): return self.offset * self.voxel_size def getSeedsVal(self, label): return self.img[self.seeds == label] def getContours(self): return self.contours def setContours(self, contours): """ store segmentation :param contours: segmentation :return: Nothing """ """ :param contours: :return: """ self.contours = contours self.contours_aview = self.contours.transpose(self.act_transposition) self.selectSlice(self.actual_slice) def changeCW(self, value, key): rg = self.cw_range[key] if (value < rg[0]) or (value > rg[1]): return if (value != self.slice_box.getCW()[key]): self.slider_cw[key].setValue(value) self.slider_cw[key].label.setText('%s: %d' % (key.upper(), value)) self.slice_box.setCW(value, key) self.slice_box.updateSliceCW(self.img_aview[..., self.actual_slice]) def changeC(self, value): self.changeCW(value, 'c') def changeW(self, value): self.changeCW(value, 'w') def setView(self, value): self.last_view_position[self.actual_view] = self.actual_slice # save seeds self.saveSliceSeeds() if self.seeds_modified: if self.mode == 'crop': self.updateCropBounds() elif self.mode == 'mask': self.updateMaskRegion() key = str(value) self.actual_view = key self.actual_slice = self.last_view_position[key] self.act_transposition = VIEW_TABLE[key] self.img_aview = self.img.transpose(self.act_transposition) self.seeds_aview = self.seeds.transpose(self.act_transposition) if self.contours is not None: self.contours_aview = self.contours.transpose( self.act_transposition) contours = self.contours_aview[..., self.actual_slice] else: contours = None vscale = self.voxel_scale[np.array(self.act_transposition)] height = self.slice_box.height() grid = height / float(self.img_aview.shape[1] * vscale[1]) # width = (self.img_aview.shape[0] * vscale[0])[0] # if width > 800: # height = 400 # grid = height / float(self.img_aview.shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box.resizeSlice(new_slice_size=self.img_aview.shape[:-1], new_grid=mgrid) self.slice_box.setSlice(self.img_aview[..., self.actual_slice], self.seeds_aview[..., self.actual_slice], contours) self.allow_select_slice = False self.n_slices = self.img_aview.shape[2] slider_val = self.n_slices - self.actual_slice self.slider.setRange(1, self.n_slices) self.slider.setValue(slider_val) self.allow_select_slice = True self.slider.label.setText('Slice: %d / %d' % (slider_val, self.n_slices)) self.view_label.setText('View size: %d x %d' % self.img_aview.shape[:-1]) self.adjustSize() self.adjustSize() def changeMask(self, val): self.slice_box.setMaskPoints(self.mask_points_tab[val]) def changeContourMode(self, val): self.slice_box.contour_mode = str(val) self.slice_box.updateSlice() def changeEraseMode(self, val): self.slice_box.erase_mode = str(val) def eraseVolume(self, pos, mode): self.showStatus("Processing...") xyz = np.array(pos + (self.actual_slice, )) p = np.zeros_like(xyz) p[np.array(self.act_transposition)] = xyz p = tuple(p) if self.seeds[p] > 0: if mode == 'inside': erase_reg(self.seeds, p, val=0) elif mode == 'outside': erase_reg(self.seeds, p, val=-1) idxs = np.where(self.seeds < 0) self.seeds.fill(0) self.seeds[idxs] = 1 if self.contours is None: contours = None else: contours = self.contours_aview[..., self.actual_slice] self.slice_box.setSlice(self.img_aview[..., self.actual_slice], self.seeds_aview[..., self.actual_slice], contours) self.showStatus("Done") def cropUpdate(self, img): for ii in VIEW_TABLE.keys(): self.last_view_position[ii] = 0 self.actual_slice = 0 self.img = img self.img_aview = self.img.transpose(self.act_transposition) self.contours = None self.contours_aview = None self.seeds = np.zeros(self.img.shape, np.int8) self.seeds_aview = self.seeds.transpose(self.act_transposition) self.seeds_modified = False vscale = self.voxel_scale[np.array(self.act_transposition)] height = self.slice_box.height() grid = height / float(self.img_aview.shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box.resizeSlice(new_slice_size=self.img_aview.shape[:-1], new_grid=mgrid) self.slice_box.setSlice(self.img_aview[..., self.actual_slice], self.seeds_aview[..., self.actual_slice], None) self.allow_select_slice = False self.n_slices = self.img_aview.shape[2] self.slider.setValue(self.actual_slice + 1) self.slider.setRange(1, self.n_slices) self.allow_select_slice = True self.slider.label.setText('Slice: %d / %d' % (self.actual_slice + 1, self.n_slices)) self.view_label.setText('View size: %d x %d' % self.img_aview.shape[:-1]) def getCropBounds(self, return_nzs=False, flat=False): nzs = self.seeds.nonzero() cri = [] flag = True for ii in range(3): if nzs[ii].shape[0] == 0: flag = False break smin, smax = np.min(nzs[ii]), np.max(nzs[ii]) if not (flat): if smin == smax: flag = False break cri.append((smin, smax)) if flag: cri = np.array(cri) out = [] offset = [] for jj, ii in enumerate(cri): out.append(slice(ii[0], ii[1] + 1)) offset.append(ii[0]) if return_nzs: return np.array(offset), tuple(out), nzs else: return np.array(offset), tuple(out) else: return None def crop(self): self.showStatus("Processing...") crp = self.getCropBounds() if crp is not None: offset, cri = crp crop = self.img[cri] self.img = np.ascontiguousarray(crop) self.offset += offset self.showStatus('Done') else: self.showStatus('Region not selected!') self.cropUpdate(self.img) def seg_to_background_seeds(self, event): self.saveSliceSeeds() self.seeds[self.seeds < 3] = 0 from PyQt4.QtCore import pyqtRemoveInputHook # pyqtRemoveInputHook() # import ipdb; ipdb.set_trace() self.seeds[(self.contours == 1) & (self.seeds < 3)] = self.BACKGROUND_NOMODEL_SEED_LABEL self.contours[...] = 0 def seg_to_foreground_seeds(self, event): self.saveSliceSeeds() self.seeds[self.seeds < 3] = 0 # from PyQt4.QtCore import pyqtRemoveInputHook # pyqtRemoveInputHook() # import ipdb; ipdb.set_trace() self.seeds[(self.contours == 1) & (self.seeds < 3)] = self.FOREGROUND_NOMODEL_SEED_LABEL self.contours[...] = 0 def saveload_seeds(self, event): if self.seeds_copy is None: self.seeds_copy = self.seeds.copy() self.seeds[...] = 0 # print "save" # from PyQt4.QtCore import pyqtRemoveInputHook # pyqtRemoveInputHook() # import ipdb; ipdb.set_trace() self.btn_save.setText("Load seeds") else: # from PyQt4.QtCore import pyqtRemoveInputHook # pyqtRemoveInputHook() # import ipdb; ipdb.set_trace() self.seeds[self.seeds_copy > 0] = self.seeds_copy[ self.seeds_copy > 0] self.seeds_copy = None self.btn_save.setText("Save seeds") def recalculate(self, event): self.saveSliceSeeds() if np.abs(np.min(self.seeds) - np.max(self.seeds)) < 2: self.showStatus('Inner and outer regions not defined!') return self.showStatus("Processing...") self.mode_fun(self) self.selectSlice(self.actual_slice) self.updateVolume() self.showStatus("Done") def changeFocusedLabel(self, textlabel): self.textFocusedLabel = textlabel # logger # print " lakjlfkj ", textlabel logger.debug(self.textFocusedLabel) def deleteSliceSeeds(self, event): if self.textFocusedLabel == 'all': self.seeds_aview[..., self.actual_slice] = 0 else: # delete only seeds with specific label self.seeds_aview[self.seeds_aview[..., self.actual_slice] == int(self.textFocusedLabel), self.actual_slice] = 0 self.slice_box.setSlice(seeds=self.seeds_aview[..., self.actual_slice]) self.slice_box.updateSlice() def deleteSeedsInAllImage(self, event): if self.textFocusedLabel == 'all': self.seeds_aview[...] = 0 else: # delete only seeds with specific label self.seeds_aview[self.seeds_aview[...] == int( self.textFocusedLabel)] = 0 self.slice_box.setSlice(seeds=self.seeds_aview[..., self.actual_slice]) self.slice_box.updateSlice() def resetSliceDraw(self, event): seeds_orig_aview = self.seeds_orig.transpose(self.act_transposition) self.seeds_aview[..., self.actual_slice] = seeds_orig_aview[ ..., self.actual_slice] self.slice_box.setSlice(seeds=self.seeds_aview[..., self.actual_slice]) self.slice_box.updateSlice() def quit(self, event): self.close() def updateVolume(self): text = 'Volume:\n unknown' if self.voxel_volume is not None: if self.mode == 'draw': vd = self.seeds else: vd = self.contours if vd is not None: nzs = vd.nonzero() nn = nzs[0].shape[0] if self.volume_unit == 'ml': text = 'Volume [ml]:\n %.2f' %\ (nn * self.voxel_volume / 1000) else: text = 'Volume [mm3]:\n %.2e' % (nn * self.voxel_volume) self.volume_label.setText(text) def getROI(self): crp = self.getCropBounds() if crp is not None: _, cri = crp else: cri = [] for jj, ii in enumerate(self.img.shape): off = self.offset[jj] cri.append(slice(off, off + ii)) return cri
class OWWidget(QDialog, Report, metaclass=WidgetMetaClass): # Global widget count widget_id = 0 # Widget Meta Description # ----------------------- #: Widget name (:class:`str`) as presented in the Canvas name = None id = None category = None version = None #: Short widget description (:class:`str` optional), displayed in #: canvas help tooltips. description = None #: A longer widget description (:class:`str` optional) long_description = None #: Widget icon path relative to the defining module icon = "icons/Unknown.png" #: Widget priority used for sorting within a category #: (default ``sys.maxsize``). priority = sys.maxsize author = None author_email = None maintainer = None maintainer_email = None help = None help_ref = None url = None keywords = [] background = None replaces = None #: A list of published input definitions inputs = [] #: A list of published output definitions outputs = [] # Default widget GUI layout settings # ---------------------------------- #: Should the widget have basic layout #: (If this flag is false then the `want_main_area` and #: `want_control_area` are ignored). want_basic_layout = True #: Should the widget construct a `mainArea` (this is a resizable #: area to the right of the `controlArea`). want_main_area = True #: Should the widget construct a `controlArea`. want_control_area = True #: Widget painted by `Save graph" button graph_name = None graph_writers = FileFormat.img_writers want_status_bar = False save_position = True #: If false the widget will receive fixed size constraint #: (derived from it's layout). Use for widgets which have simple #: static size contents. resizing_enabled = True widgetStateChanged = Signal(str, int, str) blockingStateChanged = Signal(bool) progressBarValueChanged = Signal(float) processingStateChanged = Signal(int) settingsHandler = None """:type: SettingsHandler""" savedWidgetGeometry = settings.Setting(None) #: A list of advice messages (:class:`Message`) to display to the user. #: When a widget is first shown a message from this list is selected #: for display. If a user accepts (clicks 'Ok. Got it') the choice is #: recorded and the message is never shown again (closing the message #: will not mark it as seen). Messages can be displayed again by pressing #: Shift + F1 #: #: :type: list of :class:`Message` UserAdviceMessages = [] def __new__(cls, *args, **kwargs): self = super().__new__(cls, None, cls.get_flags()) QDialog.__init__(self, None, self.get_flags()) stored_settings = kwargs.get('stored_settings', None) if self.settingsHandler: self.settingsHandler.initialize(self, stored_settings) self.signalManager = kwargs.get('signal_manager', None) self.__env = _asmappingproxy(kwargs.get("env", {})) setattr(self, gui.CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self)) self.__reportData = None OWWidget.widget_id += 1 self.widget_id = OWWidget.widget_id if self.name: self.setCaption(self.name) self.setFocusPolicy(Qt.StrongFocus) self.startTime = time.time() # used in progressbar self.widgetState = {"Info": {}, "Warning": {}, "Error": {}} self.__blocking = False # flag indicating if the widget's position was already restored self.__was_restored = False self.__progressBarValue = -1 self.__progressState = 0 self.__statusMessage = "" self.__msgwidget = None self.__msgchoice = 0 if self.want_basic_layout: self.insertLayout() sc = QShortcut(QKeySequence(Qt.ShiftModifier | Qt.Key_F1), self) sc.activated.connect(self.__quicktip) return self def __init__(self, *args, **kwargs): """QDialog __init__ was already called in __new__, please do not call it here.""" def inline_graph_report(self): box = gui.widgetBox(self.controlArea, orientation="horizontal") box.layout().addWidget(self.graphButton) box.layout().addWidget(self.report_button) # self.report_button_background.hide() # self.graphButtonBackground.hide() @classmethod def get_flags(cls): return (Qt.Window if cls.resizing_enabled else Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) class Splitter(QSplitter): def createHandle(self): return self.Handle(self.orientation(), self, cursor=Qt.PointingHandCursor) class Handle(QSplitterHandle): def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: splitter = self.splitter() splitter.setSizes([int(splitter.sizes()[0] == 0), 1000]) super().mouseReleaseEvent(event) def mouseMoveEvent(self, event): return # Prevent moving; just show/hide # noinspection PyAttributeOutsideInit def insertLayout(self): def createPixmapWidget(self, parent, iconName): w = QLabel(parent) parent.layout().addWidget(w) w.setFixedSize(16, 16) w.hide() if os.path.exists(iconName): w.setPixmap(QPixmap(iconName)) return w self.setLayout(QVBoxLayout()) self.layout().setMargin(2) self.warning_bar = gui.widgetBox(self, orientation="horizontal", margin=0, spacing=0) self.warning_icon = gui.widgetLabel(self.warning_bar, "") self.warning_label = gui.widgetLabel(self.warning_bar, "") self.warning_label.setStyleSheet("padding-top: 5px") self.warning_bar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum) gui.rubber(self.warning_bar) self.warning_bar.setVisible(False) self.want_main_area = self.graph_name is not None or self.want_main_area splitter = self.Splitter(Qt.Horizontal, self) self.layout().addWidget(splitter) if self.want_control_area: self.controlArea = gui.widgetBox(splitter, orientation="vertical", margin=0) splitter.setSizes([1]) # Results in smallest size allowed by policy if self.graph_name is not None or hasattr(self, "send_report"): leftSide = self.controlArea self.controlArea = gui.widgetBox(leftSide, margin=0) if self.graph_name is not None: self.graphButton = gui.button(leftSide, None, "&Save Graph") self.graphButton.clicked.connect(self.save_graph) self.graphButton.setAutoDefault(0) if hasattr(self, "send_report"): self.report_button = gui.button(leftSide, None, "&Report", callback=self.show_report) self.report_button.setAutoDefault(0) if self.want_main_area: self.controlArea.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) self.controlArea.layout().setContentsMargins(4, 4, 0 if self.want_main_area else 4, 4) if self.want_main_area: self.mainArea = gui.widgetBox(splitter, orientation="vertical", margin=4, sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) splitter.setCollapsible(1, False) self.mainArea.layout().setContentsMargins(0 if self.want_control_area else 4, 4, 4, 4) if self.want_status_bar: self.widgetStatusArea = QFrame(self) self.statusBarIconArea = QFrame(self) self.widgetStatusBar = QStatusBar(self) self.layout().addWidget(self.widgetStatusArea) self.widgetStatusArea.setLayout(QHBoxLayout(self.widgetStatusArea)) self.widgetStatusArea.layout().addWidget(self.statusBarIconArea) self.widgetStatusArea.layout().addWidget(self.widgetStatusBar) self.widgetStatusArea.layout().setMargin(0) self.widgetStatusArea.setFrameShape(QFrame.StyledPanel) self.statusBarIconArea.setLayout(QHBoxLayout()) self.widgetStatusBar.setSizeGripEnabled(0) self.statusBarIconArea.hide() self._warningWidget = createPixmapWidget( self.statusBarIconArea, gui.resource_filename("icons/triangle-orange.png")) self._errorWidget = createPixmapWidget( self.statusBarIconArea, gui.resource_filename("icons/triangle-red.png")) if not self.resizing_enabled: self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) def save_graph(self): graph_obj = getdeepattr(self, self.graph_name, None) if graph_obj is None: return saveplot.save_plot(graph_obj, self.graph_writers) def updateStatusBarState(self): if not hasattr(self, "widgetStatusArea"): return if self.widgetState["Warning"] or self.widgetState["Error"]: self.widgetStatusArea.show() else: self.widgetStatusArea.hide() def setStatusBarText(self, text, timeout=5000): if hasattr(self, "widgetStatusBar"): self.widgetStatusBar.showMessage(" " + text, timeout) def __restoreWidgetGeometry(self): restored = False if self.save_position: geometry = self.savedWidgetGeometry if geometry is not None: restored = self.restoreGeometry(QByteArray(geometry)) if restored and not self.windowState() & \ (Qt.WindowMaximized | Qt.WindowFullScreen): space = qApp.desktop().availableGeometry(self) frame, geometry = self.frameGeometry(), self.geometry() #Fix the widget size to fit inside the available space width = space.width() - (frame.width() - geometry.width()) width = min(width, geometry.width()) height = space.height() - (frame.height() - geometry.height()) height = min(height, geometry.height()) self.resize(width, height) # Move the widget to the center of available space if it is # currently outside it if not space.contains(self.frameGeometry()): x = max(0, space.width() / 2 - width / 2) y = max(0, space.height() / 2 - height / 2) self.move(x, y) return restored def __updateSavedGeometry(self): if self.__was_restored and self.isVisible(): # Update the saved geometry only between explicit show/hide # events (i.e. changes initiated by the user not by Qt's default # window management). self.savedWidgetGeometry = self.saveGeometry() # when widget is resized, save the new width and height def resizeEvent(self, ev): QDialog.resizeEvent(self, ev) # Don't store geometry if the widget is not visible # (the widget receives a resizeEvent (with the default sizeHint) # before first showEvent and we must not overwrite the the # savedGeometry with it) if self.save_position and self.isVisible(): self.__updateSavedGeometry() def moveEvent(self, ev): QDialog.moveEvent(self, ev) if self.save_position and self.isVisible(): self.__updateSavedGeometry() # set widget state to hidden def hideEvent(self, ev): if self.save_position: self.__updateSavedGeometry() QDialog.hideEvent(self, ev) def closeEvent(self, ev): if self.save_position and self.isVisible(): self.__updateSavedGeometry() QDialog.closeEvent(self, ev) def showEvent(self, ev): QDialog.showEvent(self, ev) if self.save_position and not self.__was_restored: # Restore saved geometry on show self.__restoreWidgetGeometry() self.__was_restored = True self.__quicktipOnce() def wheelEvent(self, event): # Silently accept the wheel event. This is to ensure combo boxes # and other controls that have focus don't receive this event unless # the cursor is over them. event.accept() def setCaption(self, caption): # we have to save caption title in case progressbar will change it self.captionTitle = str(caption) self.setWindowTitle(caption) # put this widget on top of all windows def reshow(self): self.show() self.raise_() self.activateWindow() def send(self, signalName, value, id=None): """ Send a `value` on the `signalName` widget output. An output with `signalName` must be defined in the class ``outputs`` list. """ if not any(s.name == signalName for s in self.outputs): raise ValueError('{} is not a valid output signal for widget {}'.format( signalName, self.name)) if self.signalManager is not None: self.signalManager.send(self, signalName, value, id) def __setattr__(self, name, value): """Set value to members of this instance or any of its members. If member is used in a gui control, notify the control about the change. name: name of the member, dot is used for nesting ("graph.point.size"). value: value to set to the member. """ names = name.rsplit(".") field_name = names.pop() obj = reduce(lambda o, n: getattr(o, n, None), names, self) if obj is None: raise AttributeError("Cannot set '{}' to {} ".format(name, value)) if obj is self: super().__setattr__(field_name, value) else: setattr(obj, field_name, value) notify_changed(obj, field_name, value) if self.settingsHandler: self.settingsHandler.fast_save(self, name, value) def openContext(self, *a): self.settingsHandler.open_context(self, *a) def closeContext(self): self.settingsHandler.close_context(self) def retrieveSpecificSettings(self): pass def storeSpecificSettings(self): pass def saveSettings(self): self.settingsHandler.update_defaults(self) def onDeleteWidget(self): """ Invoked by the canvas to notify the widget it has been deleted from the workflow. If possible, subclasses should gracefully cancel any currently executing tasks. """ pass def handleNewSignals(self): """ Invoked by the workflow signal propagation manager after all signals handlers have been called. Reimplement this method in order to coalesce updates from multiple updated inputs. """ pass # ############################################ # PROGRESS BAR FUNCTIONS def progressBarInit(self, processEvents=QEventLoop.AllEvents): """ Initialize the widget's progress (i.e show and set progress to 0%). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") if self.__progressState != 1: self.__progressState = 1 self.processingStateChanged.emit(1) self.progressBarSet(0, processEvents) def progressBarSet(self, value, processEvents=QEventLoop.AllEvents): """ Set the current progress bar to `value`. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param float value: Progress value :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ old = self.__progressBarValue self.__progressBarValue = value if value > 0: if self.__progressState != 1: warnings.warn("progressBarSet() called without a " "preceding progressBarInit()", stacklevel=2) self.__progressState = 1 self.processingStateChanged.emit(1) usedTime = max(1, time.time() - self.startTime) totalTime = (100.0 * usedTime) / float(value) remainingTime = max(0, totalTime - usedTime) h = int(remainingTime / 3600) min = int((remainingTime - h * 3600) / 60) sec = int(remainingTime - h * 3600 - min * 60) if h > 0: text = "%(h)d:%(min)02d:%(sec)02d" % vars() else: text = "%(min)d:%(sec)02d" % vars() self.setWindowTitle(self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars()) else: self.setWindowTitle(self.captionTitle + " (0% complete)") if old != value: self.progressBarValueChanged.emit(value) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) def progressBarValue(self): return self.__progressBarValue progressBarValue = pyqtProperty(float, fset=progressBarSet, fget=progressBarValue) processingState = pyqtProperty(int, fget=lambda self: self.__progressState) def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents): self.progressBarSet(self.progressBarValue + value, processEvents) def progressBarFinished(self, processEvents=QEventLoop.AllEvents): """ Stop the widget's progress (i.e hide the progress bar). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.setWindowTitle(self.captionTitle) if self.__progressState != 0: self.__progressState = 0 self.processingStateChanged.emit(0) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) @contextlib.contextmanager def progressBar(self, iterations=0): """ Context manager for progress bar. Using it ensures that the progress bar is removed at the end without needing the `finally` blocks. Usage: with self.progressBar(20) as progress: ... progress.advance() or with self.progressBar() as progress: ... progress.advance(0.15) or with self.progressBar(): ... self.progressBarSet(50) :param iterations: the number of iterations (optional) :type iterations: int """ progress_bar = gui.ProgressBar(self, iterations) yield progress_bar progress_bar.finish() # Let us not rely on garbage collector #: Widget's status message has changed. statusMessageChanged = Signal(str) def setStatusMessage(self, text): """ Set widget's status message. This is a short status string to be displayed inline next to the instantiated widget icon in the canvas. """ if self.__statusMessage != text: self.__statusMessage = text self.statusMessageChanged.emit(text) def statusMessage(self): """ Return the widget's status message. """ return self.__statusMessage def keyPressEvent(self, e): if (int(e.modifiers()), e.key()) in OWWidget.defaultKeyActions: OWWidget.defaultKeyActions[int(e.modifiers()), e.key()](self) else: QDialog.keyPressEvent(self, e) def information(self, id=0, text=None): """ Set/clear a widget information message (for `id`). Args: id (int or list): The id of the message text (str): Text of the message. """ self.setState("Info", id, text) def warning(self, id=0, text=""): """ Set/clear a widget warning message (for `id`). Args: id (int or list): The id of the message text (str): Text of the message. """ self.setState("Warning", id, text) def error(self, id=0, text=""): """ Set/clear a widget error message (for `id`). Args: id (int or list): The id of the message text (str): Text of the message. """ self.setState("Error", id, text) def setState(self, state_type, id, text): changed = 0 if type(id) == list: for val in id: if val in self.widgetState[state_type]: self.widgetState[state_type].pop(val) changed = 1 else: if type(id) == str: text = id id = 0 if not text: if id in self.widgetState[state_type]: self.widgetState[state_type].pop(id) changed = 1 else: self.widgetState[state_type][id] = text changed = 1 if changed: if type(id) == list: for i in id: self.widgetStateChanged.emit(state_type, i, "") else: self.widgetStateChanged.emit(state_type, id, text or "") tooltip_lines = [] highest_type = None for a_type in ("Error", "Warning", "Info"): msgs_for_ids = self.widgetState.get(a_type) if not msgs_for_ids: continue msgs_for_ids = list(msgs_for_ids.values()) if not msgs_for_ids: continue tooltip_lines += msgs_for_ids if highest_type is None: highest_type = a_type if highest_type is None: self.set_warning_bar(None) elif len(tooltip_lines) == 1: msg = tooltip_lines[0] if "\n" in msg: self.set_warning_bar( highest_type, msg[:msg.index("\n")] + " (...)", msg) else: self.set_warning_bar( highest_type, tooltip_lines[0], tooltip_lines[0]) else: self.set_warning_bar( highest_type, "{} problems during execution".format(len(tooltip_lines)), "\n".join(tooltip_lines)) return changed def set_warning_bar(self, state_type, text=None, tooltip=None): colors = {"Error": ("#ffc6c6", "black", QStyle.SP_MessageBoxCritical), "Warning": ("#ffffc9", "black", QStyle.SP_MessageBoxWarning), "Info": ("#ceceff", "black", QStyle.SP_MessageBoxInformation)} current_height = self.height() if state_type is None: if not self.warning_bar.isHidden(): new_height = current_height - self.warning_bar.height() self.warning_bar.setVisible(False) self.resize(self.width(), new_height) return background, foreground, icon = colors[state_type] style = QApplication.instance().style() self.warning_icon.setPixmap(style.standardIcon(icon).pixmap(14, 14)) self.warning_bar.setStyleSheet( "background-color: {}; color: {};" "padding: 3px; padding-left: 6px; vertical-align: center". format(background, foreground)) self.warning_label.setText(text) self.warning_bar.setToolTip(tooltip) if self.warning_bar.isHidden(): self.warning_bar.setVisible(True) new_height = current_height + self.warning_bar.height() self.resize(self.width(), new_height) def widgetStateToHtml(self, info=True, warning=True, error=True): iconpaths = { "Info": gui.resource_filename("icons/information.png"), "Warning": gui.resource_filename("icons/warning.png"), "Error": gui.resource_filename("icons/error.png") } items = [] for show, what in [(info, "Info"), (warning, "Warning"), (error, "Error")]: if show and self.widgetState[what]: items.append('<img src="%s" style="float: left;"> %s' % (iconpaths[what], "\n".join(self.widgetState[what].values()))) return "<br>".join(items) @classmethod def getWidgetStateIcons(cls): if not hasattr(cls, "_cached__widget_state_icons"): info = QPixmap(gui.resource_filename("icons/information.png")) warning = QPixmap(gui.resource_filename("icons/warning.png")) error = QPixmap(gui.resource_filename("icons/error.png")) cls._cached__widget_state_icons = \ {"Info": info, "Warning": warning, "Error": error} return cls._cached__widget_state_icons defaultKeyActions = {} if sys.platform == "darwin": defaultKeyActions = { (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(), (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible())} def setBlocking(self, state=True): """ Set blocking flag for this widget. While this flag is set this widget and all its descendants will not receive any new signals from the workflow signal manager. This is useful for instance if the widget does it's work in a separate thread or schedules processing from the event queue. In this case it can set the blocking flag in it's processNewSignals method schedule the task and return immediately. After the task has completed the widget can clear the flag and send the updated outputs. .. note:: Failure to clear this flag will block dependent nodes forever. """ if self.__blocking != state: self.__blocking = state self.blockingStateChanged.emit(state) def isBlocking(self): """ Is this widget blocking signal processing. """ return self.__blocking def resetSettings(self): self.settingsHandler.reset_settings(self) def workflowEnv(self): """ Return (a view to) the workflow runtime environment. Returns ------- env : types.MappingProxyType """ return self.__env def workflowEnvChanged(self, key, value, oldvalue): """ A workflow environment variable `key` has changed to value. Called by the canvas framework to notify widget of a change in the workflow runtime environment. The default implementation does nothing. """ pass def __showMessage(self, message): if self.__msgwidget is not None: self.__msgwidget.hide() self.__msgwidget.deleteLater() self.__msgwidget = None if message is None: return buttons = MessageOverlayWidget.Ok | MessageOverlayWidget.Close if message.moreurl is not None: buttons |= MessageOverlayWidget.Help if message.icon is not None: icon = message.icon else: icon = Message.Information self.__msgwidget = MessageOverlayWidget( parent=self, text=message.text, icon=icon, wordWrap=True, standardButtons=buttons) b = self.__msgwidget.button(MessageOverlayWidget.Ok) b.setText("Ok, got it") self.__msgwidget.setStyleSheet(""" MessageOverlayWidget { background: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #666, stop:0.3 #6D6D6D, stop:1 #666) } MessageOverlayWidget QLabel#text-label { color: white; }""" ) if message.moreurl is not None: helpbutton = self.__msgwidget.button(MessageOverlayWidget.Help) helpbutton.setText("Learn more\N{HORIZONTAL ELLIPSIS}") self.__msgwidget.helpRequested.connect( lambda: QDesktopServices.openUrl(QUrl(message.moreurl))) self.__msgwidget.setWidget(self) self.__msgwidget.show() def __quicktip(self): messages = list(self.UserAdviceMessages) if messages: message = messages[self.__msgchoice % len(messages)] self.__msgchoice += 1 self.__showMessage(message) def __quicktipOnce(self): filename = os.path.join(settings.widget_settings_dir(), "user-session-state.ini") namespace = ("user-message-history/{0.__module__}.{0.__qualname__}" .format(type(self))) session_hist = QSettings(filename, QSettings.IniFormat) session_hist.beginGroup(namespace) messages = self.UserAdviceMessages def ispending(msg): return not session_hist.value( "{}/confirmed".format(msg.persistent_id), defaultValue=False, type=bool) messages = list(filter(ispending, messages)) if not messages: return message = messages[self.__msgchoice % len(messages)] self.__msgchoice += 1 self.__showMessage(message) def userconfirmed(): session_hist = QSettings(filename, QSettings.IniFormat) session_hist.beginGroup(namespace) session_hist.setValue( "{}/confirmed".format(message.persistent_id), True) session_hist.sync() self.__msgwidget.accepted.connect(userconfirmed)
class UniFileSyncUI(QMainWindow): """UniFileSyncUI class""" def __init__(self, name=None): super(UniFileSyncUI, self).__init__() self.ui = Ui_UniFileSyncPop() self.ui.setupUi(self) self.setFixedSize(self.size()) self.server = UServer('UServer') self.server.regSelfToBus() if name: self.setName(name) self.createActions() self.createTrayIcon() self.createStatusBar() qApp.setQuitOnLastWindowClosed(False) #connect the signal with slot self.connectUISlots(self.ui) #set UI label username = ConfManager.getManager().getValue('UI', 'username') self.ui.nameLabel.setText(username) #Start server immediately self.server.start() #self.server.getHandler('start')({'name': 'all'}) msg = self.server.initMsg('start', None, MSG_UNIQUE_ID_T_CONTROLLER, False, {'name': 'all'}) UMsgBus.getBus().send(msg) self.server.addCallBack(self.statusupdate) self.server.addCallBack(self.cloudinfoupdate) #setup list self.setupFolderList(self.ui.folderList) self.setupPluginList(self.ui.pluginList) self.setupNetworkConf() #Init status bar stBarConf = ConfManager.getManager().getValue('UI', 'statusbar') self.statusbar.showMessage(stBarConf['messages']['init']) #Init system icon self.trayIcon.show() self.showTrayIconMessage() def setupNetworkConf(self): """setup network configuration into UI""" conf = ConfManager.getManager().getValue('common', 'network') self.ui.proxyLineEdit.setText(conf['proxy']) self.ui.portLineEdit.setText("%s" % conf['port']) def closeEvent(self, event): """override close event""" if self.trayIcon.isVisible(): self.hide() event.ignore() ConfManager.getManager().save() logging.debug('[%s] is closed, window is hide, configuration is saved', self.getName()) def createActions(self): """create tray icon menu action""" self.configAction = QAction("&ShowConfig", self, triggered=self.show) self.exitAction = QAction("&Exit", self) self.exitAction.triggered.connect(lambda: UMsgBus.getBus().send( self.server.initMsg('stop', None, MSG_UNIQUE_ID_T_CONTROLLER, False, {'name': 'all'}))) self.exitAction.triggered.connect(qApp.quit) def createTrayIcon(self): """create system tray icon""" self.trayIconMenu = QMenu(self) es = self.trayIconMenu.addAction(self.configAction) self.trayIconMenu.addSeparator() ea = self.trayIconMenu.addAction(self.exitAction) self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setIcon(QIcon('icon/tray.png')) def showTrayIconMessage(self): """show tray icon message""" conf = ConfManager.getManager().getValue('UI', 'trayicon') popup = conf['popup'] self.trayIcon.showMessage(popup['title'], popup['message']) def setupFolderList(self, folderList): """setup folder list for showing""" fts = ConfManager.getManager().getValue('common', 'folders') i = 0 for ft in fts: flistItem = QListWidgetItem(QIcon('icon/folder.png'), ft, folderList) folderList.insertItem(i, flistItem) i += 1 def setupPluginList(self, pluginList): """setup plugin list from configuration file""" fts = ConfManager.getManager().getValue('common', 'plugins') i = 0 for ft in fts: flistItem = QListWidgetItem(QIcon('icon/plugin.png'), ft['name'], pluginList) pluginList.insertItem(i, flistItem) i += 1 def show(self): """ovrride parent show method""" super(UniFileSyncUI, self).show() #Init status bar stBarConf = ConfManager.getManager().getValue('UI', 'statusbar') self.statusbar.showMessage(stBarConf['messages']['init']) #set UI label username = ConfManager.getManager().getValue('UI', 'username') self.ui.nameLabel.setText(username) def connectUISlots(self, ui): """connect ui component with slots""" ui.connBtn.clicked.connect(lambda: self.connBtnSlots(ui.connBtn)) ui.addFolderBtn.clicked.connect( lambda: self.connBtnSlots(ui.addFolderBtn)) ui.rmFolderBtn.clicked.connect( lambda: self.connBtnSlots(ui.rmFolderBtn)) ui.saveBtn.clicked.connect(lambda: self.connBtnSlots(ui.saveBtn)) ui.unloadBtn.clicked.connect(lambda: self.connBtnSlots(ui.unloadBtn)) ui.reloadBtn.clicked.connect(lambda: self.connBtnSlots(ui.reloadBtn)) ui.resetBtn.clicked.connect(lambda: self.connBtnSlots(ui.resetBtn)) ui.addPluginBtn.clicked.connect( lambda: self.connBtnSlots(ui.addPluginBtn)) ui.syncFolderBtn.clicked.connect( lambda: self.connBtnSlots(ui.syncFolderBtn)) self.connect(self, SIGNAL('statusupdate'), self.statusbarUpdate) self.connect(self, SIGNAL('cloudinfoupdate'), self.infoLabelUpdate) def connBtnSlots(self, btn): """docstring for connBtnSlots""" if btn is self.ui.connBtn: if btn.text() == 'Connect': msg = self.server.initMsg('info', None, MSG_UNIQUE_ID_T_CONTROLLER, True, {'name': 'all'}) UMsgBus.getBus().send(msg) #res, data = self.server.getHandler('info')({'name': 'all'}) btn.setText('Connecting') #self.ui.infoLabel.setText(data) logging.debug('[%s]: Press Connect to getCloudInfo', self.getName()) elif btn.text() == 'Disconnect': #self.server.getHandler('stop')({'name': 'all'}) btn.setText('Connect') self.ui.infoLabel.setText('Cloud Disk is disconnected') elif btn is self.ui.addFolderBtn: fileDialog = QFileDialog(self) fileDialog.setWindowTitle('Select Folder') folderPath = fileDialog.getExistingDirectory() if folderPath != "": listItem = QListWidgetItem(QIcon('icon/folder.png'), folderPath, self.ui.folderList) pyStr = '%s' % folderPath logging.debug('[%s]: add folder path %s', self.getName(), pyStr) self.ui.folderList.insertItem(self.ui.folderList.count(), listItem) folderList = ConfManager.getManager().getValue( 'common', 'folders') folderList.append(pyStr) ConfManager.getManager().setValue('common', 'folders', folderList) #send watch dir request msg = self.server.initMsg('watch', None, MSG_UNIQUE_ID_T_CONTROLLER, True, { 'path': pyStr, 'action': 'add' }) UMsgBus.getBus().send(msg) self.statusbar.showMessage('Adding watch path %s' % folderPath) elif btn is self.ui.rmFolderBtn: row = self.ui.folderList.currentRow() item = self.ui.folderList.currentItem() folderList = ConfManager.getManager().getValue('common', 'folders') self.statusbar.showMessage('Removing watch path %s' % item.text()) pyStr = '%s' % item.text() folderList.remove(pyStr) ConfManager.getManager().setValue('common', 'folders', folderList) logging.debug('[%s]: remove item %d %s', self.getName(), row, item.text()) msg = self.server.initMsg('watch', None, MSG_UNIQUE_ID_T_CONTROLLER, True, { 'path': pyStr, 'action': 'rm' }) UMsgBus.getBus().send(msg) self.ui.folderList.takeItem(row) elif btn is self.ui.saveBtn: proxyConf = ConfManager.getManager().getValue('common', 'network') server = str(self.ui.proxyLineEdit.text()) if server != "" and server != None: user = str(self.ui.proxyNameLineEdit.text()) password = str(self.ui.proxyPwdLineEdit.text()) logging.debug( '[%s]: save server=>%s user=>%s password=>%s into configuration', self.getName(), server, user, password) proxyConf['proxy'] = server proxyConf['user'] = user proxyConf['password'] = password ConfManager.getManager().setValue('common', 'network', proxyConf) msg = self.server.initMsg('proxy', None, MSG_UNIQUE_ID_T_CONTROLLER, True, { 'http': 'http://%s' % server, 'https': 'https://%s' % server }) UMsgBus.getBus().send(msg) self.statusbar.showMessage('Applying proxy %s' % server) ConfManager.getManager().save() elif btn is self.ui.resetBtn: proxyConf = ConfManager.getManager().getValue('common', 'network') server = proxyConf['proxy'] user = proxyConf['user'] password = proxyConf['password'] port = proxyConf['port'] self.ui.proxyLineEdit.setText(server) self.ui.proxyNameLineEdit.setText(user) self.ui.proxyPwdLineEdit.setText(password) self.ui.portLineEdit.setText(str(port)) self.ui.serverEnableCheckBox.setCheckState(0) elif btn is self.ui.unloadBtn: row = self.ui.pluginList.currentRow() it = str(self.ui.pluginList.currentItem().text()) logging.debug('[%s]: unload plugin name %s', self.getName(), it) self.statusbar.showMessage('Unloading plugin %s' % it) PluginManager.getManager().unload(it) self.ui.pluginList.takeItem(row) conf = ConfManager.getManager().getValue('common', 'plugins') for pc in conf: if pc['name'] == it: conf.remove(pc) ConfManager.getManager().setValue('common', 'plugins', conf) elif btn is self.ui.reloadBtn: row = self.ui.pluginList.currentRow() it = str(self.ui.pluginList.currentItem().text()) logging.debug('[%s]: reload plugin name %s', self.getName(), it) self.statusbar.showMessage('Reloading plugin %s' % it) PluginManager.getManager().reload(it) elif btn is self.ui.addPluginBtn: path = QFileDialog.getOpenFileName(self) PluginManager.getManager().loadPluginFromPath(str(path)) elif btn is self.ui.syncFolderBtn: folder = str(self.ui.folderList.currentItem().text()) msg = self.server.initMsg('sync', None, MSG_UNIQUE_ID_T_CONTROLLER, True, { 'path': str(folderPath), 'action': 'add' }) UMsgBus.getBus().send(msg) self.statusbar.showMessage('Sync %s with cloud' % folder) def createStatusBar(self): """create status bar""" self.statusbar = QStatusBar(self) self.setStatusBar(self.statusbar) def setName(self, name): """set server name""" self.name = name def getName(self): """get server name""" return self.name def statusupdate(self, param): """call back for status update""" self.emit(SIGNAL('statusupdate'), param['result']) def statusbarUpdate(self, res): """statusbar update callback""" self.statusbar.showMessage(ERR_STR_TABLE[res]) def cloudinfoupdate(self, param): """cloud infor update callback""" self.emit(SIGNAL('cloudinfoupdate'), param['data']) def infoLabelUpdate(self, res): """infoLabelUpdate""" if res: self.ui.infoLabel.setText(res) self.ui.connBtn.setText('Disconnect')
class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.listSize = 20 self.verbose = False self.timePattern = re.compile('\.[0-9]+$') self.setWindowTitle('%s %s' % (QApplication.applicationName(), QApplication.applicationVersion())); self.widget = QWidget() self.setCentralWidget(self.widget) self.statusBar = QStatusBar(self) self.setStatusBar(self.statusBar) self.mAction = self.menuBar().addMenu(self.tr("&Action")) #self.mAction.addAction(self.tr("&update"), self.updateTplTable(), QKeySequence('F5')) self.mAction.addAction(self.tr("e&xit"), self.onExit, 'Ctrl+Q') self.mAbout = self.menuBar().addMenu(self.tr("&about")) self.mAbout.addAction(QApplication.applicationName(), self.onAboutAppAction) self.mAbout.addAction("Qt", self.onAboutQtAction) self.pageForwardButton = QPushButton(self) self.pageForwardButton.setText('>') self.connect(self.pageForwardButton, SIGNAL('clicked()'), self.pageForward) self.pageBackwardButton = QPushButton(self) self.pageBackwardButton.setText('<') self.connect(self.pageBackwardButton, SIGNAL('clicked()'), self.pageBackward) self.timer = QTimer(self) self.timer.setInterval(1000) self.connect(self.timer, SIGNAL('timeout()'), self, SLOT('onTimer()')) self.time_begin = datetime.now() self.time_end = datetime.now() db_path = os.path.join(os.path.dirname(sys.argv[0]) if os.name != 'posix' else os.path.expanduser('~'), '.tt.db') self.db = sqlite3.connect(db_path) self.cursor = self.db.cursor() try: self.cursor.execute('SELECT id FROM timebrowser_record LIMIT 1') except: try: # print 'migrate' # print 'try tt' self.cursor.execute('SELECT id FROM tt LIMIT 1') # print 'may drop timebrowser_record' self.cursor.execute('DROP TABLE IF EXISTS timebrowser_record') # print 'get create statements' self.cursor.execute('''SELECT sql FROM sqlite_master WHERE type='table' AND name='tt' ''') sql1 = self.cursor.fetchone()[0].replace( ' tt ', ' timebrowser_record ' ) self.cursor.execute('''SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name='tt' ''') sql2 = self.cursor.fetchone()[0].replace( ' tt ', ' timebrowser_record ' ) # print 'drop index' self.cursor.execute('DROP INDEX IF EXISTS idx_time_begin') # print 'apply create statements' self.cursor.execute( sql1 ) self.cursor.execute( sql2 ) # print 'copy data' self.cursor.execute( 'INSERT INTO timebrowser_record SELECT * FROM tt' ) # print 'drop tt' self.cursor.execute( 'DROP TABLE tt' ) self.statusBar.showMessage('successfully migrated Database!') # print 'done' except:# Exception, e: # print e self.createDb() self.layout = QGridLayout(self.widget) self.descriptionLabel = QLabel(self.widget) self.descriptionLabel.setText('Beschreibung') self.descriptionLabel.setMaximumHeight( self.font().pointSize() * 2 ) self.descriptionInput = QLineEdit(self.widget) self.updateDescriptionEditCompleter() self.noteLabel = QLabel(self.widget) self.noteLabel.setText('Notiz') self.noteLabel.setMaximumHeight( self.font().pointSize() * 2 ) self.noteInput = QLineEdit(self.widget) self.startStopButton = QPushButton(self.widget) self.startStopButton.setText('Start') self.tableView = TplTable(self, self.listSize) self.pageForwardAction = QAction(self) self.pageForwardAction.setShortcut(QKeySequence('Right')) self.connect(self.pageForwardAction, SIGNAL('triggered()'), self.pageForward); self.pageForwardButton.addAction(self.pageForwardAction) self.pageBackwardAction = QAction(self) self.pageBackwardAction.setShortcut(QKeySequence('Left')) self.connect(self.pageBackwardAction, SIGNAL('triggered()'), self.pageBackward); self.pageBackwardButton.addAction(self.pageBackwardAction) self.updateTplTable() self.layout.addWidget(self.descriptionLabel, 0, 0, 1, 1) self.layout.addWidget(self.descriptionInput, 1, 0, 1, 1) self.layout.addWidget(self.noteLabel, 0, 1, 1, 1) self.layout.addWidget(self.noteInput, 1, 1, 1, 1) self.layout.addWidget(self.startStopButton, 2, 0, 1, 2) self.layout.addWidget(self.tableView, 3,0,1,2) self.layout.addWidget(self.pageBackwardButton, 4, 0, 1, 1) self.layout.addWidget(self.pageForwardButton, 4, 1, 1, 1) self.connect(self.descriptionInput, SIGNAL('returnPressed ()'), self.onStartStop ) self.connect(self.noteInput, SIGNAL('returnPressed ()'), self.onStartStop ) self.connect(self.startStopButton, SIGNAL('clicked()'), self.onStartStop ) self.connect(self.tableView, SIGNAL('valueChanged(int)'), self.onValueChanged ) self.connect(self.tableView, SIGNAL('del(int)'), self.onDelete ) def __del__(self): pass def createDb(self): try: self.q('''CREATE TABLE timebrowser_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, time_begin INTEGER, time_end INTEGER, description STRING, note STRING DEFAULT "" )''') self.q('CREATE INDEX idx_time_begin ON timebrowser_record (time_begin)') except: self.statusBar.showMessage('error creating Database!') else: self.statusBar.showMessage('Table timebrowser_record created successfully') def q(self, query): try: self.cursor.execute(query) except: self.statusBar.showMessage('query execution failed "%s"' % query) else: self.db.commit() def updateTplTable(self): self.q('SELECT * FROM timebrowser_record ORDER BY time_begin DESC LIMIT %d' % ( self.listSize ) ) self.tableView.set(self.cursor.fetchall()) def updateDescriptionEditCompleter(self): self.q('SELECT DISTINCT description FROM timebrowser_record') words = QStringList() for word in self.cursor.fetchall(): words.append(word[0]) self.descriptionInput.setCompleter(QCompleter(words, self)) @pyqtSlot() def pageForward(self): self.q('SELECT MIN(time_begin) FROM timebrowser_record') if not self.tableView.getLastTime() == self.cursor.fetchone()[0]: sql = 'SELECT * FROM timebrowser_record WHERE time_begin < %d ORDER BY time_begin DESC LIMIT %s' % ( self.tableView.getLastTime(), self.listSize) if self.verbose: print( sql ) self.q( sql ) self.tableView.set(self.cursor.fetchall()) @pyqtSlot() def pageBackward(self): self.q('SELECT MAX(time_begin) FROM timebrowser_record') if not self.tableView.getFirstTime() == self.cursor.fetchone()[0]: sql = 'SELECT * FROM ( SELECT * FROM timebrowser_record WHERE time_begin > %d ORDER BY time_begin LIMIT %s ) as tbl ORDER BY time_begin DESC' % ( self.tableView.getFirstTime(), self.listSize) if self.verbose: print( sql ) self.q( sql ) self.tableView.set(self.cursor.fetchall()) @pyqtSlot() def onExit(self): QApplication.exit(); @pyqtSlot() def onValueChanged(self, _id): if self.verbose: print 'changed:', _id print self.tableView.get(_id) data = self.tableView.get(_id) self.q(''' UPDATE timebrowser_record SET time_begin = %d, time_end = %d, description = '%s', note = '%s' WHERE id = %d ''' % ( data[1], data[2], data[3], data[4], data[0] )) self.updateDescriptionEditCompleter() @pyqtSlot() def onDelete(self, _id): if self.verbose: print 'del:', _id,self.tableView.get(_id)[0] self.q('DELETE FROM timebrowser_record WHERE id = %d' % self.tableView.get(_id)[0]) self.updateTplTable() self.updateDescriptionEditCompleter() @pyqtSlot() def onTimer(self): self.startStopButton.setText('Stop (%s)' % self.timePattern.sub( '', str( datetime.now() - self.time_begin ) ) ) @pyqtSlot() def onStartStop(self): if self.timer.isActive(): self.timer.stop() self.time_end = datetime.now() self.q(''' INSERT INTO timebrowser_record (time_begin,time_end,description,note) VALUES ('%d','%d','%s','%s') ''' % ( int(mktime(self.time_begin.timetuple())), int(mktime(self.time_end.timetuple())), self.descriptionInput.text(), self.noteInput.text() )) self.noteInput.clear() self.updateTplTable() self.updateDescriptionEditCompleter() self.startStopButton.setText('Start') else: self.time_begin = datetime.now() self.timer.start() self.onTimer() def onAboutAppAction(self): QMessageBox.about(self, self.tr("&about"), self.tr("%1 version %2").arg(QApplication.applicationName()).arg(QApplication.applicationVersion())) def onAboutQtAction(self): QMessageBox.aboutQt(self, self.tr("&about"))
confManager = ConfManager.getManager() fts = confManager.getValue('common', 'folders') ui.nameLabel.setText(confManager.getValue('UI', 'username')) i = 0 for ft in fts: flistItem = QListWidgetItem(QIcon('icon/folder.png'), ft, ui.folderList) ui.folderList.insertItem(i, flistItem) i += 1 statusBar = QStatusBar(d) #print confManager.getValue('UI', 'window') statusBar.showMessage( confManager.getValue('UI', 'statusbar')['messages']['init']) d.setStatusBar(statusBar) def connect(btn): """connect to UniFileSync Server""" req['param'] = {'name': 'all'} print btn.text() if btn.text() == 'Connect': req['action'] = 'start' else: req['action'] = 'stop' try:
class QtStatusBar(QtWidget, ProxyStatusBar): """ A Qt implementation of an Enaml ProxyStatusBar. """ #: A reference to the widget created by the proxy. widget = Typed(QStatusBar) #-------------------------------------------------------------------------- # Initialization API #-------------------------------------------------------------------------- def create_widget(self): """ Create the QStatusBar widget. """ self.widget = QStatusBar(self.parent_widget()) def init_widget(self): """ Initialize the widget. """ super(QtStatusBar, self).init_widget() self.set_size_grip_enabled(self.declaration.size_grip_enabled) def init_layout(self): """ Initialize the layout for the widget. """ super(QtStatusBar, self).init_layout() widget = self.widget for child in self.children(): if isinstance(child, QtStatusItem): s_widget = child.status_widget() if s_widget is not None: stretch = child.stretch() if child.is_permanent(): widget.addPermanentWidget(s_widget, stretch) else: widget.addWidget(s_widget, stretch) #-------------------------------------------------------------------------- # Utility Methods #-------------------------------------------------------------------------- def refresh_item(self, item): """ A method invoked by a child status item. This method can be called when the widget for the item should be refreshed in the status bar. """ w = self.widget s = item.status_widget() if s is not None: w.removeWidget(s) for index, child in enumerate(self.children()): if child is item: stretch = item.stretch() if item.is_permanent(): w.insertPermanentWidget(index, s, stretch) else: w.insertWidget(index, s, stretch) s.show() break #-------------------------------------------------------------------------- # Child Events #-------------------------------------------------------------------------- def child_added(self, child): """ Handle the child added event for a QtStatusBar. """ super(QtStatusBar, self).child_added(child) if isinstance(child, QtStatusItem): w = self.widget s = child.status_widget() if s is not None: for index, item in enumerate(self.children()): if child is item: stretch = item.stretch() if item.is_permanent(): w.insertPermanentWidget(index, s, stretch) else: w.insertWidget(index, s, stretch) break def child_removed(self, child): """ Handle the child removed event for a QtStatusBar. """ if isinstance(child, QtStatusItem): s = child.status_widget() if s is not None: self.widget.removeWidget(s) #-------------------------------------------------------------------------- # ProxyStatusBar API #-------------------------------------------------------------------------- def set_size_grip_enabled(self, enabled): """ Set the size grip enabled on the underlying widget. """ self.widget.setSizeGripEnabled(enabled) def show_message(self, message, timeout=0): """ Show a temporary message in the status bar. """ self.widget.showMessage(message, timeout) def clear_message(self): """ Clear any temporary message shown in the status bar. """ self.widget.clearMessage()
confManager = ConfManager.getManager() fts = confManager.getValue("common", "folders") ui.nameLabel.setText(confManager.getValue("UI", "username")) i = 0 for ft in fts: flistItem = QListWidgetItem(QIcon("icon/folder.png"), ft, ui.folderList) ui.folderList.insertItem(i, flistItem) i += 1 statusBar = QStatusBar(d) # print confManager.getValue('UI', 'window') statusBar.showMessage(confManager.getValue("UI", "statusbar")["messages"]["init"]) d.setStatusBar(statusBar) def connect(btn): """connect to UniFileSync Server""" req["param"] = {"name": "all"} print btn.text() if btn.text() == "Connect": req["action"] = "start" else: req["action"] = "stop" try:
class OWWidget(QDialog, metaclass=WidgetMetaClass): # Global widget count widget_id = 0 # Widget description name = None id = None category = None version = None description = None long_description = None icon = "icons/Unknown.png" priority = sys.maxsize author = None author_email = None maintainer = None maintainer_email = None help = None help_ref = None url = None keywords = [] background = None replaces = None inputs = [] outputs = [] # Default widget layout settings want_basic_layout = True want_main_area = True want_control_area = True want_graph = False show_save_graph = True want_status_bar = False no_report = False save_position = True resizing_enabled = True widgetStateChanged = Signal(str, int, str) blockingStateChanged = Signal(bool) asyncCallsStateChange = Signal() progressBarValueChanged = Signal(float) processingStateChanged = Signal(int) settingsHandler = None """:type: SettingsHandler""" savedWidgetGeometry = settings.Setting(None) def __new__(cls, parent=None, *args, **kwargs): self = super().__new__(cls, None, cls.get_flags()) QDialog.__init__(self, None, self.get_flags()) stored_settings = kwargs.get('stored_settings', None) if self.settingsHandler: self.settingsHandler.initialize(self, stored_settings) self.signalManager = kwargs.get('signal_manager', None) setattr(self, gui.CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self)) self._guiElements = [] # used for automatic widget debugging self.__reportData = None # TODO: position used to be saved like this. Reimplement. #if save_position: # self.settingsList = getattr(self, "settingsList", []) + \ # ["widgetShown", "savedWidgetGeometry"] OWWidget.widget_id += 1 self.widget_id = OWWidget.widget_id if self.name: self.setCaption(self.name) self.setFocusPolicy(Qt.StrongFocus) self.startTime = time.time() # used in progressbar self.widgetState = {"Info": {}, "Warning": {}, "Error": {}} self.__blocking = False # flag indicating if the widget's position was already restored self.__was_restored = False self.__progressBarValue = -1 self.__progressState = 0 self.__statusMessage = "" if self.want_basic_layout: self.insertLayout() return self def __init__(self, *args, **kwargs): """QDialog __init__ was already called in __new__, please do not call it here.""" @classmethod def get_flags(cls): return (Qt.Window if cls.resizing_enabled else Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) # noinspection PyAttributeOutsideInit def insertLayout(self): def createPixmapWidget(self, parent, iconName): w = QLabel(parent) parent.layout().addWidget(w) w.setFixedSize(16, 16) w.hide() if os.path.exists(iconName): w.setPixmap(QPixmap(iconName)) return w self.setLayout(QVBoxLayout()) self.layout().setMargin(2) self.warning_bar = gui.widgetBox(self, orientation="horizontal", margin=0, spacing=0) self.warning_icon = gui.widgetLabel(self.warning_bar, "") self.warning_label = gui.widgetLabel(self.warning_bar, "") self.warning_label.setStyleSheet("padding-top: 5px") self.warning_bar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum) gui.rubber(self.warning_bar) self.warning_bar.setVisible(False) self.topWidgetPart = gui.widgetBox(self, orientation="horizontal", margin=0) self.leftWidgetPart = gui.widgetBox(self.topWidgetPart, orientation="vertical", margin=0) if self.want_main_area: self.leftWidgetPart.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) self.leftWidgetPart.updateGeometry() self.mainArea = gui.widgetBox(self.topWidgetPart, orientation="vertical", sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding), margin=0) self.mainArea.layout().setMargin(4) self.mainArea.updateGeometry() if self.want_control_area: self.controlArea = gui.widgetBox(self.leftWidgetPart, orientation="vertical", margin=4) if self.want_graph and self.show_save_graph: graphButtonBackground = gui.widgetBox(self.leftWidgetPart, orientation="horizontal", margin=4) self.graphButton = gui.button(graphButtonBackground, self, "&Save Graph") self.graphButton.setAutoDefault(0) if self.want_status_bar: self.widgetStatusArea = QFrame(self) self.statusBarIconArea = QFrame(self) self.widgetStatusBar = QStatusBar(self) self.layout().addWidget(self.widgetStatusArea) self.widgetStatusArea.setLayout(QHBoxLayout(self.widgetStatusArea)) self.widgetStatusArea.layout().addWidget(self.statusBarIconArea) self.widgetStatusArea.layout().addWidget(self.widgetStatusBar) self.widgetStatusArea.layout().setMargin(0) self.widgetStatusArea.setFrameShape(QFrame.StyledPanel) self.statusBarIconArea.setLayout(QHBoxLayout()) self.widgetStatusBar.setSizeGripEnabled(0) self.statusBarIconArea.hide() self._warningWidget = createPixmapWidget( self.statusBarIconArea, os.path.join(environ.widget_install_dir, "icons/triangle-orange.png")) self._errorWidget = createPixmapWidget( self.statusBarIconArea, os.path.join(environ.widget_install_dir, "icons/triangle-red.png")) # status bar handler functions def setState(self, stateType, id, text): stateChanged = super().setState(stateType, id, text) if not stateChanged or not hasattr(self, "widgetStatusArea"): return iconsShown = 0 warnings = [("Warning", self._warningWidget, self._owWarning), ("Error", self._errorWidget, self._owError)] for state, widget, use in warnings: if not widget: continue if use and self.widgetState[state]: widget.setToolTip("\n".join(self.widgetState[state].values())) widget.show() iconsShown = 1 else: widget.setToolTip("") widget.hide() if iconsShown: self.statusBarIconArea.show() else: self.statusBarIconArea.hide() if (stateType == "Warning" and self._owWarning) or \ (stateType == "Error" and self._owError): if text: self.setStatusBarText(stateType + ": " + text) else: self.setStatusBarText("") self.updateStatusBarState() def updateWidgetStateInfo(self, stateType, id, text): html = self.widgetStateToHtml(self._owInfo, self._owWarning, self._owError) if html: self.widgetStateInfoBox.show() self.widgetStateInfo.setText(html) self.widgetStateInfo.setToolTip(html) else: if not self.widgetStateInfoBox.isVisible(): dHeight = -self.widgetStateInfoBox.height() else: dHeight = 0 self.widgetStateInfoBox.hide() self.widgetStateInfo.setText("") self.widgetStateInfo.setToolTip("") width, height = self.width(), self.height() + dHeight self.resize(width, height) def updateStatusBarState(self): if not hasattr(self, "widgetStatusArea"): return if self.widgetState["Warning"] or self.widgetState["Error"]: self.widgetStatusArea.show() else: self.widgetStatusArea.hide() def setStatusBarText(self, text, timeout=5000): if hasattr(self, "widgetStatusBar"): self.widgetStatusBar.showMessage(" " + text, timeout) # TODO add! def prepareDataReport(self, data): pass # ############################################## """ def isDataWithClass(self, data, wantedVarType=None, checkMissing=False): self.error([1234, 1235, 1236]) if not data: return 0 if not data.domain.classVar: self.error(1234, "A data set with a class attribute is required.") return 0 if wantedVarType and data.domain.classVar.varType != wantedVarType: self.error(1235, "Unable to handle %s class." % str(data.domain.class_var.var_type).lower()) return 0 if checkMissing and not orange.Preprocessor_dropMissingClasses(data): self.error(1236, "Unable to handle data set with no known classes") return 0 return 1 """ def restoreWidgetPosition(self): restored = False if self.save_position: geometry = self.savedWidgetGeometry if geometry is not None: restored = self.restoreGeometry(QByteArray(geometry)) if restored: space = qApp.desktop().availableGeometry(self) frame, geometry = self.frameGeometry(), self.geometry() #Fix the widget size to fit inside the available space width = space.width() - (frame.width() - geometry.width()) width = min(width, geometry.width()) height = space.height() - (frame.height() - geometry.height()) height = min(height, geometry.height()) self.resize(width, height) # Move the widget to the center of available space if it is # currently outside it if not space.contains(self.frameGeometry()): x = max(0, space.width() / 2 - width / 2) y = max(0, space.height() / 2 - height / 2) self.move(x, y) return restored def __updateSavedGeometry(self): if self.__was_restored: # Update the saved geometry only between explicit show/hide # events (i.e. changes initiated by the user not by Qt's default # window management). self.savedWidgetGeometry = self.saveGeometry() # when widget is resized, save the new width and height def resizeEvent(self, ev): QDialog.resizeEvent(self, ev) # Don't store geometry if the widget is not visible # (the widget receives a resizeEvent (with the default sizeHint) # before showEvent and we must not overwrite the the savedGeometry # with it) if self.save_position and self.isVisible(): self.__updateSavedGeometry() def moveEvent(self, ev): QDialog.moveEvent(self, ev) if self.save_position and self.isVisible(): self.__updateSavedGeometry() # set widget state to hidden def hideEvent(self, ev): if self.save_position: self.__updateSavedGeometry() self.__was_restored = False QDialog.hideEvent(self, ev) def closeEvent(self, ev): if self.save_position and self.isVisible(): self.__updateSavedGeometry() self.__was_restored = False QDialog.closeEvent(self, ev) def showEvent(self, ev): QDialog.showEvent(self, ev) if self.save_position: # Restore saved geometry on show self.restoreWidgetPosition() self.__was_restored = True def wheelEvent(self, event): """ Silently accept the wheel event. This is to ensure combo boxes and other controls that have focus don't receive this event unless the cursor is over them. """ event.accept() def setCaption(self, caption): # we have to save caption title in case progressbar will change it self.captionTitle = str(caption) self.setWindowTitle(caption) # put this widget on top of all windows def reshow(self): self.show() self.raise_() self.activateWindow() def send(self, signalName, value, id=None): if not any(s.name == signalName for s in self.outputs): raise ValueError( '{} is not a valid output signal for widget {}'.format( signalName, self.name)) if self.signalManager is not None: self.signalManager.send(self, signalName, value, id) def __setattr__(self, name, value): """Set value to members of this instance or any of its members. If member is used in a gui control, notify the control about the change. name: name of the member, dot is used for nesting ("graph.point.size"). value: value to set to the member. """ names = name.rsplit(".") field_name = names.pop() obj = reduce(lambda o, n: getattr(o, n, None), names, self) if obj is None: raise AttributeError("Cannot set '{}' to {} ".format(name, value)) if obj is self: super().__setattr__(field_name, value) else: setattr(obj, field_name, value) notify_changed(obj, field_name, value) if self.settingsHandler: self.settingsHandler.fast_save(self, name, value) def openContext(self, *a): self.settingsHandler.open_context(self, *a) def closeContext(self): self.settingsHandler.close_context(self) def retrieveSpecificSettings(self): pass def storeSpecificSettings(self): pass def saveSettings(self): self.settingsHandler.update_defaults(self) # this function is only intended for derived classes to send appropriate # signals when all settings are loaded def activate_loaded_settings(self): pass # reimplemented in other widgets def onDeleteWidget(self): pass def handleNewSignals(self): # this is called after all new signals have been handled # implement this in your widget if you want to process something only # after you received multiple signals pass # ############################################ # PROGRESS BAR FUNCTIONS def progressBarInit(self, processEvents=QEventLoop.AllEvents): """ Initialize the widget's progress (i.e show and set progress to 0%). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") if self.__progressState != 1: self.__progressState = 1 self.processingStateChanged.emit(1) self.progressBarSet(0, processEvents) def progressBarSet(self, value, processEvents=QEventLoop.AllEvents): """ Set the current progress bar to `value`. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param float value: Progress value :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ old = self.__progressBarValue self.__progressBarValue = value if value > 0: if self.__progressState != 1: warnings.warn( "progressBarSet() called without a " "preceding progressBarInit()", stacklevel=2) self.__progressState = 1 self.processingStateChanged.emit(1) usedTime = max(1, time.time() - self.startTime) totalTime = (100.0 * usedTime) / float(value) remainingTime = max(0, totalTime - usedTime) h = int(remainingTime / 3600) min = int((remainingTime - h * 3600) / 60) sec = int(remainingTime - h * 3600 - min * 60) if h > 0: text = "%(h)d:%(min)02d:%(sec)02d" % vars() else: text = "%(min)d:%(sec)02d" % vars() self.setWindowTitle( self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars()) else: self.setWindowTitle(self.captionTitle + " (0% complete)") if old != value: self.progressBarValueChanged.emit(value) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) def progressBarValue(self): return self.__progressBarValue progressBarValue = pyqtProperty(float, fset=progressBarSet, fget=progressBarValue) processingState = pyqtProperty(int, fget=lambda self: self.__progressState) def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents): self.progressBarSet(self.progressBarValue + value, processEvents) def progressBarFinished(self, processEvents=QEventLoop.AllEvents): """ Stop the widget's progress (i.e hide the progress bar). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.setWindowTitle(self.captionTitle) if self.__progressState != 0: self.__progressState = 0 self.processingStateChanged.emit(0) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) #: Widget's status message has changed. statusMessageChanged = Signal(str) def setStatusMessage(self, text): if self.__statusMessage != text: self.__statusMessage = text self.statusMessageChanged.emit(text) def statusMessage(self): return self.__statusMessage def keyPressEvent(self, e): if (int(e.modifiers()), e.key()) in OWWidget.defaultKeyActions: OWWidget.defaultKeyActions[int(e.modifiers()), e.key()](self) else: QDialog.keyPressEvent(self, e) def information(self, id=0, text=None): self.setState("Info", id, text) def warning(self, id=0, text=""): self.setState("Warning", id, text) def error(self, id=0, text=""): self.setState("Error", id, text) def setState(self, state_type, id, text): changed = 0 if type(id) == list: for val in id: if val in self.widgetState[state_type]: self.widgetState[state_type].pop(val) changed = 1 else: if type(id) == str: text = id id = 0 if not text: if id in self.widgetState[state_type]: self.widgetState[state_type].pop(id) changed = 1 else: self.widgetState[state_type][id] = text changed = 1 if changed: if type(id) == list: for i in id: self.widgetStateChanged.emit(state_type, i, "") else: self.widgetStateChanged.emit(state_type, id, text or "") tooltip_lines = [] highest_type = None for a_type in ("Error", "Warning", "Info"): msgs_for_ids = self.widgetState.get(a_type) if not msgs_for_ids: continue msgs_for_ids = list(msgs_for_ids.values()) if not msgs_for_ids: continue tooltip_lines += msgs_for_ids if highest_type is None: highest_type = a_type if highest_type is None: self.set_warning_bar(None) elif len(tooltip_lines) == 1: msg = tooltip_lines[0] if "\n" in msg: self.set_warning_bar(highest_type, msg[:msg.index("\n")] + " (...)", msg) else: self.set_warning_bar(highest_type, tooltip_lines[0], tooltip_lines[0]) else: self.set_warning_bar( highest_type, "{} problems during execution".format(len(tooltip_lines)), "\n".join(tooltip_lines)) return changed def set_warning_bar(self, state_type, text=None, tooltip=None): colors = { "Error": ("#ffc6c6", "black", QStyle.SP_MessageBoxCritical), "Warning": ("#ffffc9", "black", QStyle.SP_MessageBoxWarning), "Info": ("#ceceff", "black", QStyle.SP_MessageBoxInformation) } current_height = self.height() if state_type is None: if not self.warning_bar.isHidden(): new_height = current_height - self.warning_bar.height() self.warning_bar.setVisible(False) self.resize(self.width(), new_height) return background, foreground, icon = colors[state_type] style = QApplication.instance().style() self.warning_icon.setPixmap(style.standardIcon(icon).pixmap(14, 14)) self.warning_bar.setStyleSheet( "background-color: {}; color: {};" "padding: 3px; padding-left: 6px; vertical-align: center".format( background, foreground)) self.warning_label.setText(text) self.warning_bar.setToolTip(tooltip) if self.warning_bar.isHidden(): self.warning_bar.setVisible(True) new_height = current_height + self.warning_bar.height() self.resize(self.width(), new_height) def widgetStateToHtml(self, info=True, warning=True, error=True): pixmaps = self.getWidgetStateIcons() items = [] iconPath = { "Info": "canvasIcons:information.png", "Warning": "canvasIcons:warning.png", "Error": "canvasIcons:error.png" } for show, what in [(info, "Info"), (warning, "Warning"), (error, "Error")]: if show and self.widgetState[what]: items.append('<img src="%s" style="float: left;"> %s' % (iconPath[what], "\n".join( self.widgetState[what].values()))) return "<br>".join(items) @classmethod def getWidgetStateIcons(cls): if not hasattr(cls, "_cached__widget_state_icons"): iconsDir = os.path.join(environ.canvas_install_dir, "icons") QDir.addSearchPath( "canvasIcons", os.path.join(environ.canvas_install_dir, "icons/")) info = QPixmap("canvasIcons:information.png") warning = QPixmap("canvasIcons:warning.png") error = QPixmap("canvasIcons:error.png") cls._cached__widget_state_icons = \ {"Info": info, "Warning": warning, "Error": error} return cls._cached__widget_state_icons defaultKeyActions = {} if sys.platform == "darwin": defaultKeyActions = { (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(), (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible()) } def setBlocking(self, state=True): """ Set blocking flag for this widget. While this flag is set this widget and all its descendants will not receive any new signals from the signal manager """ if self.__blocking != state: self.__blocking = state self.blockingStateChanged.emit(state) def isBlocking(self): """ Is this widget blocking signal processing. """ return self.__blocking def resetSettings(self): self.settingsHandler.reset_settings(self)
class TestDlg(QDialog): """docstring for TestDlg""" classDefChanged = pyqtSignal() def __init__(self, parent=None): super(TestDlg, self).__init__(parent) self.mymodel = None self.myscene = DragEnabledScene(QRectF(-400,-300,800,600)) self.myview = QGraphicsView() self.myview.setScene(self.myscene) self.myfile = None layout = QVBoxLayout() layout.addWidget(self.myview) buttonLayout = QHBoxLayout() self.savebutton = QPushButton('Save') self.loadbutton = QPushButton('Load') self.renderbutton = QPushButton('Accept') buttonLayout.addWidget(self.savebutton) buttonLayout.addWidget(self.loadbutton) buttonLayout.addWidget(self.renderbutton) layout.addLayout(buttonLayout) self.statusbar = QStatusBar() layout.addWidget(self.statusbar) self.statusbar.showMessage("Ready.",2000) self.setLayout(layout) self.loadfromInitData() self.savebutton.pressed.connect(self.saveMatrix) self.loadbutton.pressed.connect(self.loadMatrix) self.myscene.selectionChanged.connect(self.updateStatus) self.myscene.modelchanged.connect(self.changeModel) self.renderbutton.pressed.connect(self.testDistance) def testDistance(self): print "Toplogical Distance:",self.mymodel.toplogicalDistance(1,10) print "Matrix Distance:",self.mymodel.matrixDistance(1,10) print "Posibility:", self.mymodel.transactionPossibility(1,10) def changeModel(self): self.classDefChanged.emit() def updateStatus(self): items = self.myscene.selectedItems() message = " " if items and isinstance(items[0],NodeGraphicsItem): message = items[0].model.name self.statusbar.showMessage(message) def loadfromInitData(self): rootNode = TreeNode(0,'root') tempid = 100 for key in data: parentNode = TreeNode(tempid,key,parent=rootNode) for leaf in data[key]: leafNode = TreeNode(leaf[0],leaf[1],color=leaf[2],accu=leaf[3],parent=parentNode) tempid += 1 self.mymodel = rootNode for node in self.mymodel.children: self.myscene.addItem(NodeGraphicsItem(node)) def saveMatrix(self): path = '.' fname = QFileDialog.getSaveFileName(self,"Save class relation",path,'class relation file (*.crf)') if fname: if not "." in fname: fname+='.crf' self.myfile = fname with open(fname,'w') as savefile: savefile.write(json.dumps(self.mymodel.toJSON())) def loadMatrix(self): path = '.' fname = QFileDialog.getOpenFileName(self,"Load class relation",path,'class relation file (*.crf)') if fname: if not "." in fname: fname += '.crf' self.myfile = fname with open(fname,'r') as loadfile: rawobj = json.loads(loadfile.read()) rootNode = TreeNode(rawobj['id'],rawobj['name'],rawobj['rect'],rawobj['color'],rawobj['pos'],rawobj['accu']) for child in rawobj['children']: parentNode = TreeNode(child['id'],child['name'],child['rect'],child['color'],child['pos'],child['accu'],rootNode) for leaf in child['children']: leafNode = TreeNode(leaf['id'],leaf['name'],leaf['rect'],leaf['color'],leaf['pos'],leaf['accu'],parentNode) self.mymodel = rootNode self.myscene.clear() for node in self.mymodel.children: self.myscene.addItem(NodeGraphicsItem(node)) self.changeModel()
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.statusBar.showMessage('Welcom to the GUI') openFile = QAction('Open File', self) openFile.setShortcut('Ctrl+O') openFile.setStatusTip('Open new File (like gamess, Bunge Slater)') openFile.triggered.connect(self.showDialogFile) openEZFIO = QAction('Open EZFIO', self) openEZFIO.setShortcut('Ctrl+E') openEZFIO.setStatusTip('Open new EZFIO') openEZFIO.triggered.connect(self.showDialogFolderRead) saveToEZFIO = QAction('Save to EZFIO', self) saveToEZFIO.setShortcut('Ctrl+S') saveToEZFIO.setStatusTip('Save on folder EZFIO proof') saveToEZFIO.triggered.connect(self.showDialogFolderSave) inputCipsi = QAction('CIPSI', self) inputCipsi.setStatusTip('create/edit CIPSI input') inputCipsi.triggered.connect(self.creatCipsiInputTab) inputQMC = QAction('QMC', self) inputQMC.setStatusTip('create/edit QMC input') inputQMC.triggered.connect(self.creatQMCInputTab) input_menu = QMenu("Input", self) input_menu.addAction(inputQMC) input_menu.addAction(inputCipsi) outputCipsi = QAction('CIPSI', self) outputCipsi.setStatusTip('create/edit CIPSI output') outputCipsi.triggered.connect(self.emptySlot) outputQMC = QAction('QMC', self) outputQMC.setStatusTip('create/edit QMC output') outputQMC.triggered.connect(self.emptySlot) output_menu = QMenu("Output", self) output_menu.addAction(outputQMC) output_menu.addAction(outputCipsi) exitAction = QAction(QIcon('exit.png'), '&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit application') exitAction.triggered.connect(QApplication.quit) menubar = self.menuBar() fileMenu = menubar.addMenu('&File') fileMenu.addAction(openFile) fileMenu.addAction(openEZFIO) fileMenu.addAction(saveToEZFIO) fileMenu.addMenu(input_menu) fileMenu.addMenu(output_menu) fileMenu.addAction(exitAction) helpMenu = menubar.addMenu('&Help') tab_widget = QTabWidget() tab_widget.setTabsClosable(True) tab_widget.tabCloseRequested.connect(tab_widget.removeTab) self.setCentralWidget(tab_widget) self.tab_widget=tab_widget self.setWindowTitle("Pig Is a Gui") self.setMinimumSize(160, 160) self.resize(800, 500) def showDialogFile(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setOption(QFileDialog.ShowDirsOnly, False) dialog.setOption(QFileDialog.ReadOnly, True) if dialog.exec_(): for d in dialog.selectedFiles(): global wrater wrater = slater_wraper(d) ot = self.findChild(orbitalTable) ot.setmydata({'Energy': wrater.get_mo_energy(), 'Occu': wrater.get_mo_occ(), 'Orbital': wrater.get_mo_name()}) bt = self.findChild(basisTypeWidget) bt.setSlater() iw = self.findChild(informationWidget) iw.setName("He") iw.setNbElec("6") def showDialogFolderRead(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.Directory) dialog.setOption(QFileDialog.ShowDirsOnly, True) if dialog.exec_(): for d in dialog.selectedFiles(): print d def showDialogFolderSave(self): dialog = QFileDialog(self) dialog.setFileMode(QFileDialog.Directory) dialog.setOption(QFileDialog.ShowDirsOnly, True) for name in dic_stoping_criterum: d = self.findChild(mySpinBoxWidget, name) print name, d.getValue() if dialog.exec_(): for d in dialog.selectedFiles(): wrater.write_ezfio(str(d)) def creatCipsiInputTab(self): tab = CipsiWidget() self.tab_widget.addTab(tab, "CIPSI input") def creatQMCInputTab(self): tab = QWidget() self.tab_widget.addTab(tab, "QMC input") def emptySlot(self): return
class QTSeedEditor(QDialog): """ DICOM viewer. """ @staticmethod def get_line(mode='h'): line = QFrame() if mode == 'h': line.setFrameStyle(QFrame.HLine) elif mode == 'v': line.setFrameStyle(QFrame.VLine) line.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) return line def initUI(self, shape, vscale, height=600, mode='seed'): """ Initialize UI. Parameters ---------- shape : (int, int, int) Shape of data matrix. vscale : (float, float, float) Voxel scaling. height : int Maximal slice height in pixels. mode : str Editor mode. """ self.slab = {} # picture grid = height / float(shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box = SliceBox(shape[:-1], mgrid, mode) self.slice_box.setScrollFun(self.scrollSlices) self.connect(self.slice_box, SIGNAL('focus_slider'), self.focusSliceSlider) # sliders self.allow_select_slice = True self.n_slices = shape[2] self.slider = QSlider(Qt.Vertical) self.slider.valueChanged.connect(self.sliderSelectSlice) self.slider.label = QLabel() self.slider.setRange(1, self.n_slices) self.slider_cw = {} self.slider_cw['c'] = QSlider(Qt.Horizontal) self.slider_cw['c'].valueChanged.connect(self.changeC) self.slider_cw['c'].label = QLabel() self.slider_cw['w'] = QSlider(Qt.Horizontal) self.slider_cw['w'].valueChanged.connect(self.changeW) self.slider_cw['w'].label = QLabel() self.view_label = QLabel('View size: %d x %d' % self.img_aview.shape[:-1]) self.voxel_label = QLabel('Voxel size [mm]:\n %.2f x %.2f x %.2f'\ % tuple(self.voxel_size[np.array(self.act_transposition)])) # combo_view_options = VIEW_TABLE.keys() # combo_view = QComboBox(self) # combo_view.activated[str].connect(self.setView) # combo_view.addItems(combo_view_options) #radio button group for choosing seed class ------------------------ self.current_class = 1 self.slice_box.seed_mark = self.current_class number_group = QGroupBox(QString('Class markers')) vbox_NG = QVBoxLayout() r1 = QRadioButton('class 1') r1.setStyleSheet('QRadioButton {color: red}') r1.setChecked(True) r2 = QRadioButton('class 2') r2.setStyleSheet('QRadioButton {color: green}') r3 = QRadioButton('class 3') r3.setStyleSheet('QRadioButton {color: blue}') r4 = QRadioButton('class 4') r4.setStyleSheet('QRadioButton {color: cyan}') r5 = QRadioButton('class 5') r5.setStyleSheet('QRadioButton {color: magenta}') vbox_NG.addWidget(r1) vbox_NG.addWidget(r2) vbox_NG.addWidget(r3) vbox_NG.addWidget(r4) vbox_NG.addWidget(r5) number_group.setLayout(vbox_NG) self.button_group = QButtonGroup() self.button_group.addButton(r1, 1) self.button_group.addButton(r2, 2) self.button_group.addButton(r3, 3) self.button_group.addButton(r4, 4) self.button_group.addButton(r5, 5) self.connect(self.button_group, SIGNAL("buttonClicked(int)"), self.change_seed_class) #------------------------------------------------------------------- # buttons # btn_save = QPushButton('Save', self) # btn_save.clicked.connect(self.save) btn_quit = QPushButton("Quit", self) btn_quit.clicked.connect(self.quit) # btn_crop = QPushButton('Crop', self) # btn_crop.clicked.connect(self.crop) combo_dmask = QComboBox(self) combo_dmask.activated.connect(self.changeMask) self.mask_points_tab, aux = self.init_draw_mask(DRAW_MASK, mgrid) for icon, label in aux: combo_dmask.addItem(icon, label) self.slice_box.setMaskPoints( self.mask_points_tab[combo_dmask.currentIndex()]) self.status_bar = QStatusBar() vopts = [] vmenu = [] appmenu = [] # btn_recalc = QPushButton("Recalculate", self) # btn_recalc.clicked.connect(self.recalculate) # appmenu.append(QLabel('<b>Segmentation mode</b><br><br><br>' + # 'Select the region of interest<br>' + # 'using the mouse buttons.<br><br>')) # appmenu.append(btn_recalc) # appmenu.append(QLabel()) # self.volume_label = QLabel('Volume [mm3]:\n unknown') # appmenu.append(self.volume_label) # btn_crop = QPushButton("Crop", self) # btn_crop.clicked.connect(self.crop) # appmenu.append(btn_crop) btn_save = QPushButton("Save Seeds", self) btn_save.clicked.connect(self.saveSeeds) appmenu.append(btn_save) btn_del = QPushButton("Delete Seeds", self) btn_del.clicked.connect(self.deleteSliceSeeds) appmenu.append(btn_del) # combo_contour_options = ['fill', 'contours', 'off'] # combo_contour = QComboBox(self) # combo_contour.activated[str].connect(self.changeContourMode) # combo_contour.addItems(combo_contour_options) # self.changeContourMode(combo_contour_options[combo_contour.currentIndex()]) # vopts.append(QLabel('Selection mode:')) # vopts.append(combo_contour) # btn_reset = QPushButton("Reset Seeds", self) # btn_reset.clicked.connect(self.resetSliceDraw) # # appmenu.append(None) # appmenu.append(btn_reset) hbox = QHBoxLayout() vbox = QVBoxLayout() vbox_left = QVBoxLayout() vbox_app = QVBoxLayout() hbox.addWidget(self.slice_box) hbox.addWidget(self.slider) vbox_left.addWidget(self.slider.label) vbox_left.addWidget(self.view_label) vbox_left.addWidget(self.voxel_label) # vbox_left.addWidget(QLabel()) # vbox_left.addWidget(QLabel('View plane:')) # vbox_left.addWidget(combo_view) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(self.slider_cw['c'].label) vbox_left.addWidget(self.slider_cw['c']) vbox_left.addWidget(self.slider_cw['w'].label) vbox_left.addWidget(self.slider_cw['w']) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(QLabel('Drawing mask:')) vbox_left.addWidget(combo_dmask) for ii in vopts: vbox_left.addWidget(ii) for ii in vmenu: if ii is None: vbox_left.addStretch(1) else: vbox_left.addWidget(ii) for ii in appmenu: if ii is None: vbox_app.addStretch(1) else: vbox_app.addWidget(ii) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(number_group) # vbox_app.addWidget(btn_crop) vbox_app.addStretch(1) # vbox_app.addWidget(btn_save) vbox_app.addWidget(btn_quit) hbox.addLayout(vbox_left) hbox.addWidget(self.get_line('v')) hbox.addLayout(vbox_app) vbox.addLayout(hbox) vbox.addWidget(self.status_bar) self.setLayout(vbox) self.setWindowTitle('Seed Editor') self.show() def __init__(self, img, seeds_fname='seeds.npy', actualSlice=0, seeds=None, contours=None, mode='seed', modeFun=None, voxelSize=[1, 1, 1]): """ Initiate Editor Parameters ---------- img : array DICOM data matrix. actualSlice : int Index of actual slice. seeds : array Seeds, user defined regions of interest. contours : array Computed segmentation. mode : str Editor modes: 'seed' - seed editor 'crop' - manual crop 'draw' - drawing modeFun : fun Mode function invoked by user button. voxelSize : tuple of float voxel size [mm] """ QDialog.__init__(self) self.mode = mode self.mode_fun = modeFun self.seeds_fname = seeds_fname # self.datapath = datapath self.actual_view = 'axial' self.act_transposition = VIEW_TABLE[self.actual_view] self.last_view_position = {} for ii in VIEW_TABLE.iterkeys(): self.last_view_position[ii] = img.shape[VIEW_TABLE[ii][-1]] - 1 self.img = img self.img_aview = self.img.transpose(self.act_transposition) self.actual_slice = self.img_aview.shape[-1] - actualSlice - 1 self.last_view_position[self.actual_view] = self.actual_slice # set contours self.contours = contours if self.contours is None: self.contours_aview = None else: self.contours_aview = self.contours.transpose( self.act_transposition) self.voxel_size = np.array(voxelSize) self.voxel_scale = self.voxel_size / float(np.min(self.voxel_size)) self.voxel_volume = np.prod(voxelSize) # set seeds if seeds is None: self.seeds = np.zeros(self.img.shape, np.int8) else: self.seeds = seeds self.seeds_aview = self.seeds.transpose(self.act_transposition) self.seeds_modified = False self.initUI(self.img_aview.shape, self.voxel_scale[np.array(self.act_transposition)], 600, mode) if mode == 'draw': self.seeds_orig = self.seeds.copy() self.slice_box.setEraseFun(self.eraseVolume) # set view window values C/W lb = np.min(img) ub = np.max(img) dul = ub - lb self.cw_range = {'c': [lb, ub], 'w': [1, dul]} self.slider_cw['c'].setRange(lb, ub) self.slider_cw['w'].setRange(1, dul) self.changeC(lb + dul / 2) self.changeW(dul) self.offset = np.zeros((3, ), dtype=np.int16) def change_seed_class(self, id): self.current_class = id self.slice_box.seed_mark = self.current_class # print 'Current seed class changed to ', id, '.' def showStatus(self, msg): self.status_bar.showMessage(QString(msg)) QApplication.processEvents() def init_draw_mask(self, draw_mask, grid): mask_points = [] mask_iconlabel = [] for mask, label in draw_mask: w, h = mask.shape xx, yy = mask.nonzero() mask_points.append((xx - w / 2, yy - h / 2)) img = QImage(w, h, QImage.Format_ARGB32) img.fill(qRgba(255, 255, 255, 0)) for ii in range(xx.shape[0]): img.setPixel(xx[ii], yy[ii], qRgba(0, 0, 0, 255)) img = img.scaled(QSize(w * grid[0], h * grid[1])) icon = QIcon(QPixmap.fromImage(img)) mask_iconlabel.append((icon, label)) return mask_points, mask_iconlabel def saveSliceSeeds(self): aux = self.slice_box.getSliceSeeds() if aux is not None: self.seeds_aview[..., self.actual_slice] = aux self.seeds_modified = True else: self.seeds_modified = False def saveSeeds(self): print 'Saving seeds array ... ', # aux = np.swapaxes(np.swapaxes(self.seeds_aview, 1, 2), 0, 1) aux = np.swapaxes(self.seeds_aview, 0, 2) np.save(self.seeds_fname, aux) print 'done' def updateCropBounds(self): crp = self.getCropBounds() if crp is not None: _, cri = crp self.contours = np.zeros(self.img.shape, np.int8) self.contours[cri].fill(1) self.contours_aview = self.contours.transpose( self.act_transposition) def focusSliceSlider(self): self.slider.setFocus(True) def sliderSelectSlice(self, value): self.selectSlice(self.n_slices - value) def scrollSlices(self, inc): if abs(inc) > 0: new = self.actual_slice + inc self.selectSlice(new) def selectSlice(self, value, force=False): if not (self.allow_select_slice): return if (value < 0) or (value >= self.n_slices): return if (value != self.actual_slice) or force: self.saveSliceSeeds() if self.seeds_modified and (self.mode == 'crop'): self.updateCropBounds() if self.contours is None: contours = None else: contours = self.contours_aview[..., value] slider_val = self.n_slices - value self.slider.setValue(slider_val) self.slider.label.setText('Slice: %d / %d' % (slider_val, self.n_slices)) self.slice_box.setSlice(self.img_aview[..., value], self.seeds_aview[..., value], contours) self.actual_slice = value def getSeeds(self): return self.seeds def getImg(self): return self.img def getOffset(self): return self.offset * self.voxel_size def getSeedsVal(self, label): return self.img[self.seeds == label] def getContours(self): return self.contours def setContours(self, contours): self.contours = contours self.contours_aview = self.contours.transpose(self.act_transposition) self.selectSlice(self.actual_slice) def changeCW(self, value, key): rg = self.cw_range[key] if (value < rg[0]) or (value > rg[1]): return if (value != self.slice_box.getCW()[key]): self.slider_cw[key].setValue(value) self.slider_cw[key].label.setText('%s: %d' % (key.upper(), value)) self.slice_box.setCW(value, key) self.slice_box.updateSliceCW(self.img_aview[..., self.actual_slice]) def changeC(self, value): self.changeCW(value, 'c') def changeW(self, value): self.changeCW(value, 'w') def setView(self, value): self.last_view_position[self.actual_view] = self.actual_slice # save seeds self.saveSliceSeeds() if self.seeds_modified and (self.mode == 'crop'): self.updateCropBounds(self.seeds_aview[..., self.actual_slice]) key = str(value) self.actual_view = key self.actual_slice = self.last_view_position[key] self.act_transposition = VIEW_TABLE[key] self.img_aview = self.img.transpose(self.act_transposition) self.seeds_aview = self.seeds.transpose(self.act_transposition) if self.contours is not None: self.contours_aview = self.contours.transpose( self.act_transposition) contours = self.contours_aview[..., self.actual_slice] else: contours = None vscale = self.voxel_scale[np.array(self.act_transposition)] height = self.slice_box.height() grid = height / float(self.img_aview.shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box.resizeSlice(new_slice_size=self.img_aview.shape[:-1], new_grid=mgrid) self.slice_box.setSlice(self.img_aview[..., self.actual_slice], self.seeds_aview[..., self.actual_slice], contours) self.allow_select_slice = False self.n_slices = self.img_aview.shape[2] slider_val = self.n_slices - self.actual_slice self.slider.setValue(slider_val) self.slider.setRange(1, self.n_slices) self.allow_select_slice = True self.slider.label.setText('Slice: %d / %d' % (slider_val, self.n_slices)) self.view_label.setText('View size: %d x %d' % self.img_aview.shape[:-1]) def changeMask(self, val): self.slice_box.setMaskPoints(self.mask_points_tab[val]) def changeContourMode(self, val): self.slice_box.contour_mode = str(val) self.slice_box.updateSlice() def changeEraseMode(self, val): self.slice_box.erase_mode = str(val) def eraseVolume(self, pos, mode): self.showStatus("Processing...") xyz = pos + (self.actual_slice, ) p = tuple(np.array(xyz)[np.array(self.act_transposition)]) if self.seeds[p] > 0: if mode == 'inside': erase_reg(self.seeds, p, val=0) elif mode == 'outside': erase_reg(self.seeds, p, val=-1) idxs = np.where(self.seeds < 0) self.seeds.fill(0) self.seeds[idxs] = 1 if self.contours is None: contours = None else: contours = self.contours_aview[..., self.actual_slice] self.slice_box.setSlice(self.img_aview[..., self.actual_slice], self.seeds_aview[..., self.actual_slice], contours) self.showStatus("Done") def cropUpdate(self, img): for ii in VIEW_TABLE.iterkeys(): self.last_view_position[ii] = 0 self.actual_slice = 0 self.img = img self.img_aview = self.img.transpose(self.act_transposition) self.contours = None self.contours_aview = None self.seeds = np.zeros(self.img.shape, np.int8) self.seeds_aview = self.seeds.transpose(self.act_transposition) self.seeds_modified = False vscale = self.voxel_scale[np.array(self.act_transposition)] height = self.slice_box.height() grid = height / float(self.img_aview.shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box.resizeSlice(new_slice_size=self.img_aview.shape[:-1], new_grid=mgrid) self.slice_box.setSlice(self.img_aview[..., self.actual_slice], self.seeds_aview[..., self.actual_slice], None) self.allow_select_slice = False self.n_slices = self.img_aview.shape[2] self.slider.setValue(self.actual_slice + 1) self.slider.setRange(1, self.n_slices) self.allow_select_slice = True self.slider.label.setText('Slice: %d / %d' % (self.actual_slice + 1, self.n_slices)) self.view_label.setText('View size: %d x %d' % self.img_aview.shape[:-1]) def getCropBounds(self): nzs = self.seeds.nonzero() cri = [] flag = True for ii in range(3): if nzs[ii].shape[0] == 0: flag = False break smin, smax = np.min(nzs[ii]), np.max(nzs[ii]) if smin == smax: flag = False break cri.append((smin, smax)) if flag: cri = np.array(cri) out = [] offset = [] for jj, ii in enumerate(cri): out.append(slice(ii[0], ii[1] + 1)) offset.append(ii[0]) return np.array(offset), tuple(out) else: return None def crop(self): self.showStatus("Processing...") crp = self.getCropBounds() if crp is not None: offset, cri = crp crop = self.img[cri] self.img = np.ascontiguousarray(crop) self.offset += offset self.showStatus('Done') else: self.showStatus('Region not selected!') self.cropUpdate(self.img) def recalculate(self, event): self.saveSliceSeeds() if np.abs(np.min(self.seeds) - np.max(self.seeds)) < 2: self.showStatus('At least two regions must be marked!') return self.showStatus("Processing...") # idx = 3 # s = random_walker(self.img[idx,:,:], self.seeds[idx,:,:])#, mode='cg_mg') # plt.figure() # plt.imshow(mark_boundaries(self.img[idx,:,:], s)) # plt.show() # self.segmentation = np.zeros(self.img.shape) # self.segmentation[idx,:,:] = s self.segmentation = random_walker(self.img, self.seeds, mode='cg_mg') self.setContours(self.segmentation - 1) self.selectSlice(self.actual_slice) # self.updateVolume() self.showStatus("Done") def deleteSliceSeeds(self, event): self.seeds_aview[..., self.actual_slice] = 0 self.slice_box.setSlice(seeds=self.seeds_aview[..., self.actual_slice]) self.slice_box.updateSlice() def resetSliceDraw(self, event): seeds_orig_aview = self.seeds_orig.transpose(self.act_transposition) self.seeds_aview[..., self.actual_slice] = seeds_orig_aview[ ..., self.actual_slice] self.slice_box.setSlice(seeds=self.seeds_aview[..., self.actual_slice]) self.slice_box.updateSlice() def quit(self, event): self.close() # def save(self, event): # odp = os.path.expanduser("~/lisa_data") # if not op.exists(odp): # os.makedirs(odp) # # data = self.export() # # data['version'] = self.version # # data['experiment_caption'] = self.experiment_caption # # data['lisa_operator_identifier'] = self.lisa_operator_identifier # pth, filename = op.split(op.normpath(self.datapath)) # # filename += "-" + self.experiment_caption # filepath = 'org-' + filename + '.pklz' # filepath = op.join(odp, filepath) # filepath = misc.suggest_filename(filepath) # misc.obj_to_file(data, filepath, filetype='pklz') # # filepath = 'organ_last.pklz' # filepath = op.join(odp, filepath) # misc.obj_to_file(data, filepath, filetype='pklz') # def export(self): # slab = {} # slab['none'] = 0 # slab['liver'] = 1 # slab['lesions'] = 6 # slab.update(self.slab) # # data = {} # data['version'] = (1, 0, 1) # data['data3d'] = self.img # # data['crinfo'] = self.crinfo # data['segmentation'] = self.segmentation # data['slab'] = slab # # data['voxelsize_mm'] = self.voxelsize_mm # # data['orig_shape'] = self.orig_shape # # data['processing_time'] = self.processing_time # return data def updateVolume(self): text = 'Volume [mm3]:\n unknown' if self.voxel_volume is not None: if self.mode == 'draw': vd = self.seeds else: vd = self.contours if vd is not None: nzs = vd.nonzero() nn = nzs[0].shape[0] text = 'Volume [mm3]:\n %.2e' % (nn * self.voxel_volume) self.volume_label.setText(text) def getROI(self): crp = self.getCropBounds() if crp is not None: _, cri = crp else: cri = [] for jj, ii in enumerate(self.img.shape): off = self.offset[jj] cri.append(slice(off, off + ii)) return cri
class VocDialog(QDialog) : """This is the dialog which presents the interface and organise everything.""" MAGICWORD = 'CHANGEME' findDight = reCompile(r'\d+') baseURL = 'http://www.gstatic.com/dictionary/static/sounds/de/0/CHANGEME.mp3' def __init__(self, autoProxy=False, parent=None) : super(VocDialog, self).__init__(parent) self.logger = getLogger('VocVoc.VocDialog') self.info = self.logger.info self.warn = self.logger.warn self.debug = self.logger.debug if autoProxy : self.info('Starting VocDialog with autoProxy.') else : self.info('Starting VocDialog without autoProxy.') self.mediaObeject = Phonon.createPlayer(Phonon.MusicCategory, Phonon.MediaSource('')) self.setupUi() self.connect() self.initCountWord() self.candidates = None self.autoProxy = autoProxy self.spellChecker = SpellChecker() self.correct = self.spellChecker.correct self.corpusDir = self.spellChecker.corpusDir self.info('VocDialog started.') def keyPressEvent(self, event) : self.debug('Key is {}.'.format(event.key())) super(VocDialog, self).keyPressEvent(event) def resizeEvent(self, event) : self.debug("Resized to {}.".format(self.size())) super(VocDialog, self).resizeEvent(event) def initCountWord(self) : """ The first one is a count about how many time the input is wrong. WRONG : Not collected in or can be corrected by the wordModel. The second one is the last time's wrong input. """ self.countWord = [0, ''] def setupUi(self) : "Setup the UI." self.info('Seting up the UI.') self.fileDialog = QFileDialog() self.fileDialog.setFileMode(QFileDialog.AnyFile) self.fileDialog.setViewMode(QFileDialog.Detail) self.loadButton = QPushButton( r'Open/New :', self) self.loadButton.setAutoDefault(False) self.textList = QListWidget(self) self.inputLine = tabEnabledLineEdit(self) self.toggleButton = QPushButton(r'Show/Hide', self) self.toggleButton.setAutoDefault(False) self.toggleButton.setCheckable(True) self.textLabel = QLabel() self.hBox = QHBoxLayout() self.hBox.addWidget(self.inputLine) self.hBox.addWidget(self.toggleButton) self.statusBar = QStatusBar(self) msg = 'Hello World! I love YOU!!!' self.statusBar.showMessage(msg, 5000) vBox = QVBoxLayout() items = [self.loadButton, self.textList, self.hBox, self.statusBar] for item in items : try : vBox.addWidget(item) except : vBox.addLayout(item) self.textViewer = QTextEdit() self.textViewer.setHidden(True) self.textViewer.setReadOnly(True) HBox = QHBoxLayout() items = [vBox, self.textViewer] for item in items : try : HBox.addWidget(item) except : HBox.addLayout(item) self.setLayout(HBox) self.resize(350, 500) self.setWindowTitle("VocVoc -- Your Vocabulary Helper") self.info('UI is set up now.') def connect(self) : "Connect signals and slots in the UI." self.info('Connecting signals and slots.') self.loadButton.clicked.connect(self.loadFile) self.inputLine.returnPressed.connect(self.enteredText) self.inputLine.ctrlN.connect(self.completeHandler) self.inputLine.ctrlP.connect(lambda : self.completeHandler(False)) self.textList.itemActivated.connect(self.itemActivated) self.toggleButton.clicked.connect(self.toggleViewer) if self.logger.isEnabledFor(DEBUG) : self.mediaObeject.stateChanged.connect( self.errorState ) self.info('Signals and slots connected.') def errorState(self, state) : errorStates = { 0: 'Loading', 1: 'Stopped', 2: 'Playing', 3: 'Buffering', 4: 'Paused', 5: 'Error' } msg ='{} state in Phonon!'.format( errorStates[state]) self.info(self.mediaObeject.errorType()) if state == 5 : self.warn(msg) else : self.info(msg) def itemActivated(self, item) : row = self.textList.row(item) text = item.text() if not text.startswith('#') : self.pronounce(item.text()) self.findWord(text) if row+1 != self.textList.count() : self.debug('NOT last row!') self.textList.setCurrentRow(row+1) else : self.debug('Last row!') def toggleViewer(self) : if self.textViewer.isHidden() : self.resize(700, 500) self.textViewer.show() else : self.textViewer.hide() self.resize(350, 500) def backAndForward(self, forward=True) : inputLine = self.inputLine word = inputLine.text() setText = inputLine.setText candidates = self.candidates count = len(candidates) try : position = candidates.index(word) self.debug('Position found.') except : position = None self.debug('Position not found.') if forward : if position is None or position == count - 1 : # At end position = -1 setText( candidates[position+1] ) else : if position is None or position == 0 : position = count setText( candidates[position-1] ) def completeHandler(self, goNext=True) : inputLine = self.inputLine candidates = self.candidates word = inputLine.text() if candidates : self.backAndForward(goNext) def play(self, path) : self.mediaObeject.setCurrentSource(Phonon.MediaSource(path)) self.mediaObeject.play() def pronounce(self, word) : self.info('Preparing the url to pronounce.') url = self.baseURL.replace(self.MAGICWORD, word) if not self.autoProxy : self.debug('Without the autoProxy, play it using the url as the source.') self.play(url) else : self.info('With the autoProxy, play it after downloading the file.') try : # May happen HTTPError. resource = urlopen(url).read() tempFile = NamedTemporaryFile() tempFile.write(resource) self.play(tempFile.name) except HTTPError as error : self.warn(repr(error)) self.warn('Pronounciation FAILED.') self.info('Pronounciation ended.') def findWord(self, word) : self.info('Finding word in the text file.') textViewer = self.textViewer if textViewer.isHidden() : return else : pass limit = 5 contexts = list() textLines = list() corpuses = glob(''.join([self.corpusDir, '/*'])) self.debug('Found corpuses : {}.'.format(corpuses)) textViewer.clear() for corpus in corpuses : textLines.append(locateWord(corpus, word)) for textLine in textLines : text, lines = textLine[0], textLine[1] title = ''.join( ['Title : ', basename(text[-1])] ) if lines : for line in lines : wantedLines = text[line-limit: line+limit] #cleanLines = map(self.replace, wantedLines) context = ''.join(wantedLines) context = context.replace(word, ' '.join(['*', word, '*'])) context = context.replace('\n\n', self.MAGICWORD) context = context.replace('\n', ' ') context = context.replace(self.MAGICWORD, '\n\n') contexts.append(''.join([title, '\n', context, '\n\n'])) if contexts : for context in contexts : textViewer.append(context) else : textViewer.append('Sorry, {} not found.'.format(word)) self.info('Word found and showed in the textViewer.') def wordCount(self, word=None) : """ This function uses self.countWord to decide whether record and pronounce the input or not. RECORD : Add the input into the textList and write it into the file. If the word itself is correct, return True. Or if a wrong input were entered twice, return True. Otherwise with a one-time-entered wrong input, return False. """ if self.countWord[0] == 0 : # The word is correct. self.countWord[1] = '' return True elif self.countWord[0] == 1 : msg = 'Maybe the word is WRONG? Playing beep and saving the word.' self.debug(msg) self.countWord[1] = word self.play('beep.mp3') return False elif self.countWord[0] == 2 : if word != self.countWord[1] : # Different word. self.debug('DIFEFRENT WORD.') self.countWord[0] = 1 # Check again. self.countWord[1] = word # Update it. self.play('beep.mp3') return False else : self.countWord[0] = 0 self.countWord[1] = '' return True else : self.countWord[0] = 0 def checkWord(self, word) : statusBar = self.statusBar showMessage = statusBar.showMessage candidates = self.correct(word) if candidates is None : # Not collected. self.countWord[0] += 1 showMessage('Are you sure?', 3000) elif candidates[0] != word : # Can be corrected. self.countWord[0] += 1 self.candidates = candidates msg = 'Do you mean {} ?'.format(' ,'.join(candidates)) showMessage(msg, 5000) else : # Collected in the wordModel. self.findWord(word) self.countWord[0] = 0 self.debug('Word collected in the wordModel.') return True msg = 'wrongTime = {} with the word {}.'.format(self.countWord[0], word) self.logger.debug(msg) return self.wordCount(word) def addText(self, text) : self.info('Starting to add text.') textList = self.textList if text.startswith('#') : # It is a comment. pass else : # It is a word. if self.checkWord(text) : self.pronounce(text) else : # self.checkWord(text) return False return self.inputLine.clear() textList.addItem(text) self.statusBar.clearMessage() textList.setCurrentRow( textList.count() - 1 ) try : # With the try statement, it can be used as a pronunciation helper. flush(self.filePath, text) except Exception : self.debug('Using this freely without writing to a file as a pronunciation helper.') self.info('Text added.') def enteredText(self) : "Get the text from the input line and add it to the file and the list." self.info('Adding text to textList and the file.') textList = self.textList text = self.inputLine.text().strip().lower() self.debug( 'Input is {}.'.format(text) ) self.addText(text) self.info('Text added.') def loadFile(self) : "Open the file dialog to select the file and try to start." # Open the file dialog. logger = getLogger('VocVoc.VocDialog.loadFile') info = logger.info debug = logger.debug debug('Preparing to load file.') textList = self.textList if ( self.fileDialog.exec() ) : debug('Dialog executed sucessfully.') filePath = self.fileDialog.selectedFiles()[0] fileName = basename(filePath) # Create or read file. try : with open(filePath, 'r+') as textFile : debug('File exists, openning up.') writenText = textFile.read() writenText = writenText.splitlines() textList.clear() textList.addItems( writenText ) if not 'end' in writenText[-1].strip().lower() : textList.setCurrentRow( len(writenText)-1 ) else : textList.setCurrentRow( 0 ) debug('Added items to list and set current row to the last row.') except IOError as error : # File does not exist. We create one. debug('File does not exist. Trying to find the dight in the name.') listNumber = self.findDight.search(fileName) if listNumber is None : # No number found in the text. logger.warn('Dight not found in the filename. Try again.') msg = 'No number found in the file name.\nPlease try again.' QMessageBox.warning(self, 'List number NOT found.', msg, QMessageBox.Ok) return msg else : # No existing file but found the number in the file name. debug('Dight Found. Creating file and adding first line.') with open(filePath, 'x') as textFile : firstLine = ''.join( ['# list ' ,str( listNumber.group() )] ) # Cannot put '\n' here. textFile.write( ''.join([firstLine ,'\n']) ) textList.clear() textList.addItem(firstLine) # Otherwise there would be a new line in the list. debug('Set inputLine to write-enabled.') self.inputLine.setReadOnly(False) debug('Pass textFile to the dialog') self.filePath = filePath info('File loaded.')
class Visor(QtGui.QDockWidget, FORM_CLASS): """ UI manager for the visor. This is the View component of the MVC pattern. Will manage inputs from the user using a combobox to select which dataset to use, provide a tree view of the elements included in those datasets, and provide the basic configuration options to download maps through WMS and WCS services using a controller and show them in QGIS. """ groupAssignmentLock = RLock() def __init__(self, showEmptyDatasetNodes = False, parent=None): super(Visor, self).__init__(parent) self.setupUi(self) self.controller = VisorController.VisorController() self.controller.threddsServerMapObjectRetrieved.connect(self.onNewDatasetsAvailable) self.controller.threddsDataSetUpdated.connect(self.onDataSetUpdated) self.controller.mapImageRetrieved.connect(self.showNewImage) self.controller.standardMessage.connect(self.postInformationMessageToUser) self.controller.errorMessage.connect(self.postCriticalErrorToUser) self.controller.mapInfoRetrieved.connect(self._onMapInfoReceivedFromController) self.controller.batchDownloadFinished.connect(self.createLayerGroup) self.showEmptyDatasetNodes = showEmptyDatasetNodes #TODO: Self-explanatory... self.combo_dataset_list.currentIndexChanged.connect(self._onDataSetItemChanged) self.tree_widget.itemClicked.connect(self._onMapTreeWidgetItemClicked) self.tree_widget.itemExpanded.connect(self._onMapTreeWidgetItemExpanded) self.connect(self.combo_wcs_coverage, SIGNAL("currentIndexChanged(const QString&)"), self._onCoverageSelectorItemChanged) self.connect(self.combo_wms_layer, SIGNAL("currentIndexChanged(const QString&)"), self._onWMSLayerSelectorItemChanged) self.connect(self.combo_wms_style_type, SIGNAL("currentIndexChanged(const QString&)"), self._onWMSStyleTypeSelectorItemChanged) self.connect(self.combo_wms_time, SIGNAL("currentIndexChanged(int)"), self._onWMSFirstTimeChanged) self.connect(self.combo_wcs_time, SIGNAL("currentIndexChanged(int)"), self._onWCSFirstTimeChanged) self.button_req_map.clicked.connect(self._onbuttonReqMapClicked) #self.actionToggleAlwaysOnTop.toggled.connect(self._onAlwaysOnTopPrefsChanged) self.buttonManageServers.clicked.connect(self._onManageServersRequested) self.button_req_animation.clicked.connect(self.toggleAnimationMenu) #We add a status bar to this QDockWidget self.statusbar = QStatusBar() self.gridLayout.addWidget(self.statusbar) self.datasetInUse = None self.uiAnimation = None self.currentMap = None self.wcsAvailableTimes = [] self.wmsAvailableTimes = [] self.firstRunThisSession = True def show(self): if iface and not iface.mainWindow().restoreDockWidget(self): iface.mainWindow().addDockWidget(Qt.LeftDockWidgetArea, self) super(Visor, self).show() if self.firstRunThisSession is True: self.firstRunChecks() self.firstRunThisSession = False #If no dataset is selected yet, we will assume the first #thing the user will want to do is actually doing something #related to the servers (pick one from a list, or add a new #one) as there is little else that can be done at this point. #So we present them the screen to do so.. if self.datasetInUse is None: self._onManageServersRequested() def firstRunChecks(self): """ Convenience method to add any checks which should be performed when the user opens the plug-in for first time (be advised this is not the same as the first time the plug-in object is created, which is on QGIS load). """ #Check GDAL version. Versions < 2.0 had a bug regarding #driver selection for network resource retrieval. #https://trac.osgeo.org/gdal/ticket/2696 persistenceManager = ServerDataPersistenceManager.ServerStorageManager() if persistenceManager.getDontShowGDALErrorAnymore() is False: try: from osgeo import gdal if int(gdal.VersionInfo()) < 5000000: message = ("Your GDAL libraries version is outdated. Versions\n" +"under 2.0 are not guaranteed to work when\n" +"attempting to load WCS Layers.\n" +"Please update GDAL.") except ImportError: message = ("Your GDAL libraries version could not be read" +"Versions under 2.0 are not guaranteed to work when\n" +"attempting to load WCS Layers. If you have any issues,\n" +"please update GDAL.") reply = QtGui.QMessageBox.question(self, 'GDAL: Unsupported version found', message, "Close", "Don't show again") if reply == 1: persistenceManager.setDontShowGDALErrorAnymore() def toggleAnimationMenu(self): """ Shows (or hides) the animation menu elements, and instantiate a controller. It seems I can not directly hide elements, but I can make another Widget in QDesigner and create/add it to a layout here so... oh well.. """ if self.uiAnimation is None: self.uiAnimation = AnimationFrame(parent = self) self.uiAnimation.errorSignal.connect(self.postCriticalErrorToUser) self.controller.mapInfoRetrieved.connect(self.uiAnimation.setAnimationInformation) if None is not self.currentMap : self.uiAnimation.setAnimationInformation(self.currentMap) self.uiAnimation.show() self.button_req_animation.setText("Hide animation menu <<") else: self.uiAnimation.hide() self.uiAnimation = None self.button_req_animation.setText("Show animation menu >>") def clearData(self): self.WMSBoundingBoxInfo.setText("No Bounding Box or CRS information available.") self.WMS_eastLabel.setText("East: No information.\n") self.WMS_westLabel.setText("West: No information.") self.WMS_northLabel.setText("North: No information.") self.WMS_southLabel.setText("South: No information.") self.combo_wms_layer.clear() self.combo_wms_style_type.clear() self.combo_wms_style_palette.clear() self.combo_wms_time.clear() self.combo_wms_time_last.clear() self.combo_wcs_coverage.clear() self.combo_wcs_time.clear() self.combo_wcs_time_last.clear() self.WCSBoundingBoxInfo.setText("No Bounding Box or CRS information available." ) self.WCS_eastLabel.setText("East: No information.\n") self.WCS_westLabel.setText("West: No information.\n") self.WCS_northLabel.setText("North: No information.\n") self.WCS_southLabel.setText("South: No information.\n") #TODO: Unused (for now) @pyqtSlot(bool) def _onAlwaysOnTopPrefsChanged(self, newSettingBool): """ Will change the alwaysontop window modifier to suit the user selection. """ self.setWindowFlags(self.windowFlags() ^ Qt.WindowStaysOnTopHint) QtGui.QMainWindow.show(self) @pyqtSlot(list, str) def onNewDatasetsAvailable(self, inDataSets, serverName): """ A callback for when the dataSet displayed needs to be updated. :param inDataSets: list of DataSet objects which will be available to the user. :type inDataSets: list of threddsFetcherRecursos.DataSet objects. :param serverName: An user-friendly representation of this server name. :type serverName: str """ StringList = [] for dataSet in inDataSets: StringList.append(dataSet.getName()) self.setWindowTitle("THREDDS Explorer - Connected: "+serverName) self.combo_dataset_list.clear() self.combo_dataset_list.addItems(StringList) self.combo_dataset_list.setCurrentIndex(0) self.postInformationMessageToUser("Dataset list updated: "+str(len(StringList))+ " elements.") self.clearData() @pyqtSlot(str) def postInformationMessageToUser(self, message): """ Will post information messages to the user through the status bar. :param message: String to use as message to the user. :type message: str """ self.statusbar.showMessage(message) pass @pyqtSlot(str) def postCriticalErrorToUser(self, errorString): """ To be used with non-recoverable error situations. Shows a message box with the error message. :param errorString: String to use as message to the user. :type errorString: str """ box = QMessageBox() box.setText(errorString) box.setIcon(QMessageBox.Critical) box.exec_() @pyqtSlot(str) def _onDataSetItemChanged(self, stringItem): """ Will receive notifications about this window dataSet chosen combobox when the item selected changes. """ self.tree_widget.clear() self.datasetInUse = self.controller.getSingleDataset(self.combo_dataset_list.currentText()) if self.datasetInUse is None: return #If no dataset is available to be shown, we will create no tree. rootItem = self.tree_widget.invisibleRootItem(); newItem = QtGui.QTreeWidgetItem(rootItem, [self.datasetInUse.getName()]) rootItem.addChild(self._createHierarchy(self.datasetInUse, newItem)) def _createHierarchy(self, dataSet, treeItemParent): """ Recursively creates a hierarchy of elements to populate a treeWidgetItem from a given dataSet. :param dataSet: DataSet object to create an hierarchy from. :type dataset: threddsFetcherRecursos.DataSet :param treeItemParent: Item which will be this branch parent. :type treeItemParent: QTreeWidgetItem """ i = 0 itemsAlreadyAddedToElement = [] while i < treeItemParent.childCount(): child = treeItemParent.child(i) if child.text(0) == "Loading..." or child.text(0) == "No subsets found": treeItemParent.removeChild(child) else: itemsAlreadyAddedToElement.append(child) i = i+1 elementsAlreadyInTreeItemParent = [x.text(0) for x in itemsAlreadyAddedToElement] if dataSet != None: for mapElement in dataSet.getAvailableMapList(): if mapElement.getName() in elementsAlreadyInTreeItemParent: continue else: newItem = QtGui.QTreeWidgetItem(treeItemParent, [mapElement.getName()]) treeItemParent.addChild(newItem) subSets = dataSet.getSubSets() if len(subSets) == 0: #We add a dummy element so the element open icon is created.. newItem = QtGui.QTreeWidgetItem(treeItemParent) newItem.setText(0,"No subsets found") treeItemParent.addChild(newItem) else: for dataset in subSets: #If an item with the same name as this dataset is found as a subchild #of the parent item, we will use it to build our tree. Otherwise, we #create a new one and append it. itemList = ([x for x in itemsAlreadyAddedToElement if x.text(0) == dataset.getName()]) if itemList is None or len(itemList) == 0: item = QtGui.QTreeWidgetItem(treeItemParent, [dataset.getName()]) treeItemParent.addChild(self._createHierarchy(dataset, item)) else: item = itemList[0] self._createHierarchy(dataset, item) else: self.postCriticalErrorToUser("WARNING: Attempted to add a null dataset to view.") def _onMapTreeWidgetItemClicked(self, mQTreeWidgetItem, column): """ Will receive notifications about the MapTreeWidget elements being clicked, so we can update the first combobox of WMS/WCS tabs with the layer list. """ self.clearData() self.postInformationMessageToUser("") if None is mQTreeWidgetItem or None is mQTreeWidgetItem.parent(): return self.controller.getMapObject(str(mQTreeWidgetItem.text(0)), str(mQTreeWidgetItem.parent().text(0)), self.datasetInUse) @pyqtSlot(object) def _onMapInfoReceivedFromController(self, mapInfoObject): #print("_onMapInfoReceivedFromController 1"+str(mapInfoObject)) self.currentMap = mapInfoObject #print("_onMapInfoReceivedFromController 2"+str(self.currentMap)) if self.currentMap is not None: #WCS Data update self.currentCoverages = self.controller.getWCSCoverages(self.currentMap) if self.currentCoverages is not None: for c in self.currentCoverages: self.combo_wcs_coverage.addItem(c.getName()) else: self.combo_wcs_coverage.addItem("No data available.") #WMS Data update self.currentWMSMapInfo = self.controller.getWMSMapInfo(self.currentMap) if self.currentWMSMapInfo is not None: for l in self.currentWMSMapInfo.getLayers(): self.combo_wms_layer.addItem(l.getName()) else: self.combo_wms_layer.addItem("No data available.") def _onMapTreeWidgetItemExpanded(self, mQTreeWidgetItem): """ Once a set is expanded in the tree view we will attempt to recover it's data and present it to the user. """ setToUpdate = self.datasetInUse.searchSubsetsByName( str(mQTreeWidgetItem.text(0)), exactMatch=True) if setToUpdate is not None and len(setToUpdate) > 0: self.controller.mapDataSet(setToUpdate[0], depth=1) def onDataSetUpdated(self, dataSetObject): """ Will update the QTreeWidget to include the updated dataset object and it's new data. """ if dataSetObject.getParent() is not None: parent = self.tree_widget.findItems(dataSetObject.getName(), Qt.MatchRecursive) self._createHierarchy(dataSetObject, parent[0]) @pyqtSlot(str) def _onCoverageSelectorItemChanged(self, QStringItem): """ Will triger when the user selects a coverage name in the combobox (or that list is updated) so the available times to request to server are updated in the other combobox for the WCS service. """ self.combo_wcs_time.clear() if self.currentCoverages is not None: coverageElement = [ x for x in self.currentCoverages if x.getName() == str(QStringItem) ] if None is not coverageElement or len(coverageElement) > 0: try: self.wcsAvailableTimes = coverageElement[0].getTiempos() self.combo_wcs_time.addItems(self.wcsAvailableTimes) BBinfo = coverageElement[0].getBoundingBoxInfo() self.WCSBoundingBoxInfo.setText("CRS = "+BBinfo.getCRS() +"\n\n Bounding Box information (decimal degrees):" ) self.WCS_eastLabel.setText("East: \n"+BBinfo.getEast()) self.WCS_westLabel.setText("West: \n"+BBinfo.getWest()) self.WCS_northLabel.setText("North: \n"+BBinfo.getNorth()) self.WCS_southLabel.setText("South: \n"+BBinfo.getSouth()) except IndexError: pass @pyqtSlot(str) def _onWMSLayerSelectorItemChanged(self, QStringItem): self.combo_wms_style_type.clear() self.combo_wms_style_palette.clear() self.combo_wms_time.clear() #Only one should be returned here. if self.currentWMSMapInfo is not None: layerSelectedObject = [ x for x in self.currentWMSMapInfo.getLayers() if x.getName() == str(QStringItem) ] if layerSelectedObject is not None and len(layerSelectedObject) == 1: self.wmsAvailableTimes = layerSelectedObject[0].getTimes() self.combo_wms_time.addItems(self.wmsAvailableTimes) self.wmsAvailableStyles = layerSelectedObject[0].getStyles() self.combo_wms_style_type.addItems(list({(x.getName().split(r"/"))[0] for x in self.wmsAvailableStyles})) BBinfo = layerSelectedObject[0].getBoundingBoxInfo() self.WMSBoundingBoxInfo.setText("CRS = "+BBinfo.getCRS() +"\n\n Bounding Box information (decimal degrees):" ) self.WMS_eastLabel.setText("East: \n"+BBinfo.getEast()) self.WMS_westLabel.setText("West: \n"+BBinfo.getWest()) self.WMS_northLabel.setText("North: \n"+BBinfo.getNorth()) self.WMS_southLabel.setText("South: \n"+BBinfo.getSouth()) @pyqtSlot(str) def _onWMSStyleTypeSelectorItemChanged(self, qstringitem): self.combo_wms_style_palette.clear() self.combo_wms_style_palette.addItems(list({(x.getName().split(r"/"))[1] for x in self.wmsAvailableStyles if str(qstringitem) in x.getName()})) @pyqtSlot(int) def _onWCSFirstTimeChanged(self, position): #print("self.wcsAvailableTimes"+str((sorted(self.wcsAvailableTimes)))) #print("WCS INDEX: "+str(position)) self.combo_wcs_time_last.clear() #print self.wcsAvailableTimes[position:] self.combo_wcs_time_last.addItems( (sorted(self.wcsAvailableTimes))[position:]) @pyqtSlot(int) def _onWMSFirstTimeChanged(self, position): #print("self.wmsAvailableTimes"+str((sorted(self.wmsAvailableTimes)))) #print("WMS INDEX: "+str(position)) self.combo_wms_time_last.clear() #print self.wmsAvailableTimes[position:] self.combo_wms_time_last.addItems( self.wmsAvailableTimes[position:]) def _onbuttonReqMapClicked(self): """ Action to be performed when the user clicks the button to request a new map to be displayed, after selecting proper values in the rest of fields. This will also begin a qTimer which will check for async messages which would report to us the availability of a new image to be displayed. """ #print(self.currentMap) self.postInformationMessageToUser("") #Reset error display. if self.tabWidget.currentIndex() == self.tabWidget.indexOf(self.tab_WCS): try: selectedBeginTimeIndex = self.wcsAvailableTimes.index(self.combo_wcs_time.currentText()) selectedFinishTimeIndex = self.wcsAvailableTimes.index(self.combo_wcs_time_last.currentText())+1 #print(str(self.currentMap)) self.controller.asyncFetchWCSImageFile(self.currentMap, self.combo_wcs_coverage.currentText(), self.wcsAvailableTimes[selectedBeginTimeIndex :selectedFinishTimeIndex]) except Exception: self.postInformationMessageToUser("There was an error retrieving the WCS data.") elif self.tabWidget.currentIndex() == self.tabWidget.indexOf(self.tab_WMS): try: selectedBeginTimeIndex = self.wmsAvailableTimes.index(self.combo_wms_time.currentText()) selectedFinishTimeIndex = self.wmsAvailableTimes.index(self.combo_wms_time_last.currentText())+1 style = self.combo_wms_style_type.currentText()+r"/"+self.combo_wms_style_palette.currentText() self.controller.asyncFetchWMSImageFile(self.currentMap, self.combo_wms_layer.currentText(), style, self.wmsAvailableTimes[selectedBeginTimeIndex :selectedFinishTimeIndex]) except Exception: self.postInformationMessageToUser("There was an error retrieving the WMS data.") @pyqtSlot(list, str) def createLayerGroup(self, layerList, groupName): groupifier = LayerGroupifier(layerList, groupName) groupifier.setSingleLayerSelectionModeInGroup(False) groupifier.statusSignal.connect(self.postInformationMessageToUser, Qt.DirectConnection) groupifier.groupifyComplete.connect(self._onNewLayerGroupGenerated) groupifier.groupify() @pyqtSlot(QgsLayerTreeGroup, list) def _onNewLayerGroupGenerated(self, groupObject, layerList): """ Currently only used to show the first image of a newly created group so the user knows when the operation finishes. :param groupObject: The legend group object which was created. :type groupObject: QgsLayerTreeGrupo :param layerList: The layers which are held in the group object. :type layerList: [QgsLayer] """ if (layerList[0]).isValid() is True: iface.legendInterface().setLayerVisible(layerList[0], True) else: self.postInformationMessageToUser("There was a problem showing a layer.") @pyqtSlot(tuple) def showNewImage(self, image): """ Will order this UI to post a new image to the user through the qgis window. :param image: a tuple consisting of (imageOrLayerObject, Name, Service) :type image: (QgsRasterLayer, String, String) """ self.postInformationMessageToUser("Layer '"+image[1]+"' ["+image[2]+"]retrieved") layer = image[0] if layer.isValid() is True: QgsMapLayerRegistry.instance().addMapLayer(layer) iface.zoomToActiveLayer() iface.legendInterface().refreshLayerSymbology(layer) else: self.postInformationMessageToUser("There was a problem loading the layer.") @pyqtSlot() def _onManageServersRequested(self): """ Delegates the action of showing the server manager window to the controller. """ self.controller.showServerManager()
class MainWindow(QMainWindow, Ui_MainWindow): ''' Main app window ''' def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) # promenne self.work_dir = '' self.master_work_dir = '' self.logovat = False self.flowini_file_master = '' self.flowini_data = '' self.file_dict = None self.displayed_mesh_list = {} self.displayed_mtr_list = {} self.result_elements = None self.result_times = None self.surface_elements = None self.comparation_table = None self.material = None self.mtr_index_disp_lst = {} self.logger = None self.msh = None self.tasks = None self.solutions = None self.problem_type = None self.canvas = None self.bcd_file = '' self.substances = False # setup app self.setup = None self._load_setup() # toolbar self.__setup_toolbar() # status bar self.status_bar = None self.__setup_statusbar() # mesh control self.__setup_mesh_control() # misc self.button_compare.clicked.connect(self.compare_concentrations) self.check_compare_sum.setChecked(True) self.button_export_csv.clicked.connect(self.export_compare_conc) self.button_export_csv.setDisabled(True) self.spin_grade_filter.setRange(0, 5) self.spin_grade_filter.setDisabled(True) self.button_grade_filter.setDisabled(True) self.button_grade_filter.clicked.connect(self.__filter_table_rslt) self.maps_section_height.textChanged.connect(self.__focus_on_cut) # progress bar self.tabWidget.currentChanged.connect(self._tab_controller) self.button_process_all.clicked.connect(self._analyze_data) self.button_process_newonly.clicked.connect( self._analyze_data_selected) # na zacatku neni nic self.tabWidget.hide() self.__remove_all_tabs() self.button_draw_maps.clicked.connect(self.map_concentrations) def map_concentrations(self): ''' pracovni metoda pro obsluhu mapy koncentraci ''' if not self.result_elements: self.read_concentrations() map_options = { "map_format": "png", "map_file": "{}{}mapa.png".format(self.work_dir, SEPAR), 'xlabel': u'{}'.format(self.edit_chart_x_text.text()), 'ylabel': u'{}'.format(self.edit_chart_y_text.text()), 'title': u'{}'.format(self.edit_chart_title_text.text()) } sim_time = str(self.maps_sim_time_select.currentText()) if self.maps_radio_surface.isChecked(): vals = self._read_surface_elements() if self.maps_check_nonzero.isChecked(): vals = self.__remove_zeros_from_mesh_list(vals) self.messenger("Drawing map of concetration to file...") triangles = mapcon.get_triangles_surface( vals, self.msh.nodes, self.result_elements, sim_time, self.bcd_file) elif self.maps_radio_section.isChecked(): try: height = float(self.maps_section_height.text()) except ValueError: self.messenger("ERROR: Need altitude for the plane to cut") return False else: vals = self._mesh_find_through('z', height) if self.maps_check_nonzero.isChecked(): vals = self.__remove_zeros_from_mesh_list(vals) self.messenger("Drawing map of concetration to file...") triangles = mapcon.get_triangles_section( vals, self.msh.nodes, self.result_elements, height, sim_time) else: self.messenger("NEXT TIME") return False triangulation = mapcon.prepare_triangulation(triangles) mapcon.draw_map(triangulation, map_options) self.map_conc_poup(triangulation) self.messenger("OK - map of concentrations is ready in the file") def map_conc_poup(self, triangulation): ''' popup window for map concentration ''' map_options = { 'xlabel': u'{}'.format(self.edit_chart_x_text.text()), 'ylabel': u'{}'.format(self.edit_chart_y_text.text()), 'title': u'{}'.format(self.edit_chart_title_text.text()) } self.canvas = mapcanvas.MapCanvas(triangulation, map_options) self.canvas.setGeometry(QRect(100, 100, 700, 700)) self.canvas.show() def __focus_on_cut(self): ''' if user adds height, switch the radio maps to section cut ''' self.maps_radio_section.setChecked(True) def __remove_all_tabs(self): ''' removes all tabs from the tab widget ''' count = self.tabWidget.count() - 1 while count >= 1: self.tabWidget.removeTab(count) count -= 1 def __setup_statusbar(self): ''' status bar setup - private method ''' # status bar status_font = QFont("Helvetica [Cronyx]", 12) self.status_bar = QStatusBar(self) self.setStatusBar(self.status_bar) self.status_bar.setFont(status_font) def __setup_toolbar(self): ''' toolbar setup ''' # toolbar self.button_exit.clicked.connect(self.close) self.button_draw_charts.clicked.connect(self.draw_charts) self.button_save_tables.clicked.connect(self.create_tables) self.button_merge.clicked.connect(self.merge_result_files) self.box_merger.setHidden(True) float_validator = QDoubleValidator() self.edit_merge_minval.setValidator(float_validator) self.maps_section_height.setValidator(float_validator) self.edit_chart_min_conc.setValidator(float_validator) self.action_Open.triggered.connect(self.open_task_dir) self.action_Basic_problem.triggered.connect(self.analyse_basic_problem) self.actionSensitivity_task.triggered.connect( self.analyse_sensitivity_task) self.actionMonte_Carlo.triggered.connect(self.analyse_monte_carlo) def __setup_mesh_control(self): ''' setup for mesh control block ''' # mesh control self.button_mesh_imp_surface.clicked.connect(self._mesh_import_surface) self.button_mesh_imp_nonzero.clicked.connect(self._mesh_import_nonzero) self.button_mesh_import_all.clicked.connect(self._mesh_import_all) self.button_mesh_remove_all.clicked.connect(self._mesh_remove_all) self.button_mesh_import_mtr.clicked.connect(self._mesh_import_mtr) self.button_mesh_remove_mtr.clicked.connect(self._mesh_remove_mtr) self.button_mesh_imp_over.clicked.connect(self._mesh_import_over) self.button_mesh_imp_bellow.clicked.connect(self._mesh_import_bellow) self.button_mesh_rem_over.clicked.connect(self._mesh_remove_over) self.button_mesh_rem_bellow.clicked.connect(self._mesh_remove_bellow) self.button_mesh_imp_through.clicked.connect(self._mesh_import_through) self.button_mesh_rem_through.clicked.connect(self._mesh_remove_through) self.button_mesh_imp_elmid.clicked.connect(self._mesh_import_id) self.button_mesh_rem_elmid.clicked.connect(self._mesh_remove_id) self.mesh_list.itemClicked.connect(self._mesh_element_explorer_control) self.mesh_radio_z.setChecked(True) self.maps_radio_surface.setChecked(True) self.button_mesh_remove_zero.clicked.connect(self._mesh_remove_zero) integer_validator = QIntValidator() self.mesh_element_id_edit.setValidator(integer_validator) self.edit_mesh_crd.setValidator(integer_validator) def export_compare_conc(self): ''' method for export comparsion table to csv file comparation has to be done first ''' if not self.comparation_table: self.messenger('export error - data not exists') return False sname = None #selected substance if self.substances: sname = str(self.select_substance_compar.currentText()) csvexport.write_comparsion_tab(self.comparation_table, self.work_dir, sname) self.messenger('Table has been successfully exported to CSV file') self.button_export_csv.setDisabled(True) def compare_concentrations(self): ''' method for concentration comparsion check what list and method has to be used ''' if self.radio_compare_all.isChecked(): self.__compare_selected_conc() elif self.radio_compare_selected.isChecked(): self.__compare_selected_conc(self.displayed_mesh_list.keys()) elif self.radio_compare_surface.isChecked(): if not self.surface_elements: fname = self.work_dir + 'SEPAR' + 'master' + SEPAR + FNAME_SURF self.surface_elements = surface.read_result(fname) self.__compare_selected_conc(self.surface_elements) def merge_result_files(self): ''' Method for merging result files parses all task dirs, opens result json files and merge values to one big table discards results in master dir ''' sname, filename = self.__get_proper_filename_suma() conc = transport.parse_task_dirs(self.work_dir, FNAME_ELEMS, sname) merged_table = {} minval = self.edit_merge_minval.text() try: minval = float(minval) except ValueError: minval = 0.0 for fname in conc: elem_conc = transport.load_vysledek(fname) if not 'master' in fname: for elid, time_dict in elem_conc.items(): if merged_table.has_key(elid): merged_table[elid].append(time_dict) else: merged_table[elid] = [time_dict, ] else: master_conc = elem_conc output_file = self.work_dir + SEPAR + FNAME_MERGE self.messenger( 'Merging the results to one file / it may take a while...') merger.table_to_file( merged_table, master_conc, output_file, len(conc) - 1, minval) self.messenger( 'Data has been successfully merged and exported to CSV file') def __get_proper_filename_suma(self): ''' get proper filename for SUM concetration depends if substances are present or not ''' if self.substances: sname = str(self.select_substance_compar.currentText()) filename = path.join(sname, FNAME_SUMA) else: sname = False filename = FNAME_SUMA return sname, filename def __compare_selected_conc(self, elm_list=None): ''' wrapper for selected method parses all dirs and sums concentrations for all/selected elements output a table with sum ''' sname, filename = self.__get_proper_filename_suma() master = path.join(self.work_dir, 'master', filename) mas_conc_suma = transport.load_vysledek(master) mas_total = concentrations.sum_conc(mas_conc_suma, elm_list) list_of_conc = [] task_numbers = [] conc = transport.parse_task_dirs(self.work_dir, FNAME_SUMA, sname) for fname in conc: conc_suma = transport.load_vysledek(fname) task_nr = path.split(fname)[0] task_name = task_nr.split('\\')[-1] if task_name == 'master': task_name = '9999999' task_numbers.append(task_name) list_of_conc.append(concentrations.sum_conc(conc_suma, elm_list)) grade_vector = concentrations.grade_result(mas_total, list_of_conc) table_rows = zip(task_numbers, list_of_conc, grade_vector) table_rows = sorted( table_rows, key=lambda grade: grade[2], reverse=True) self.__display_table_rslt(table_rows) # enable export self.comparation_table = table_rows self.button_export_csv.setEnabled(True) self.spin_grade_filter.setEnabled(True) self.button_grade_filter.setEnabled(True) def __display_table_rslt(self, table_rows): ''' controls table_comp_rslt widget fill the widget with table_rows list @param table_rows: [] ''' rows_count = len(table_rows) model = QStandardItemModel(rows_count, 3) model.setHorizontalHeaderLabels(TAB_LABELS) # fill model with data for row in range(rows_count): for col in range(3): item = QStandardItem() item.setData(str(table_rows[row][col]), Qt.DisplayRole) model.setItem(row, col, item) # self.table_comp_rslt.clearContents() if self.table_comp_rslt.isSortingEnabled(): self.table_comp_rslt.setSortingEnabled(False) proxy = numsort.NumberSortModel() proxy.setSourceModel(model) self.table_comp_rslt.setModel(proxy) self.table_comp_rslt.resizeColumnsToContents() self.table_comp_rslt.setSortingEnabled(True) def __filter_table_rslt(self): ''' filter for grades ''' grval = int(self.spin_grade_filter.value()) newtab = [(elm, conc, grade) for (elm, conc, grade) in self.comparation_table if (grade >= grval) or (elm == '9999999')] self.__display_table_rslt(newtab) def _tab_controller(self): ''' controlling index of tab widget and throw action signal: tabWidget index change ''' if not self.tabWidget.isHidden(): actions = { 'data_processing': '_data_dialog', 'basic_analyser': '_analyser_dialog', } idx = str(self.tabWidget.currentWidget().objectName()) if actions.has_key(idx) and self.problem_type: getattr(self, actions[idx])() if not self.result_elements: self.basic_analyser.setDisabled(True) else: self.basic_analyser.setEnabled(True) def _analyser_dialog(self): ''' a dialog for analyser screen ''' if not self.result_elements: self.read_concentrations() try: msg = 'found {} elements with non-zero concentration'.format( len(self.result_elements)) except TypeError: self.messenger('Process data first.') else: self.label_basic_1.setText(msg) try: msg = '{} selected by element selector for drawing'.format( len(self.displayed_mesh_list)) except TypeError: self.messenger('Process data first.') else: self.label_basic_2.setText(msg) def _data_dialog(self): ''' a dialog for data processing ''' self.progress_processing.setHidden(True) self.progress_processing.setMinimum(0) self.progress_processing.setValue(0) self.tasks = transport.get_result_files(self.work_dir, self.substances) n_tas = len(self.tasks) self.solutions = transport.parse_task_dirs(self.work_dir, FNAME_ELEMS) n_sols = len(self.solutions) if n_tas - n_sols == 0: self.button_process_newonly.setHidden(True) self.button_process_all.setText('Start') else: self.button_process_newonly.setVisible(True) self.button_process_all.setText('All') msg = "Found {} tasks to analyze. It may take a while\n".format(n_tas) msg += "{} tasks was already processed (compressed file found), {} still need processing\n".format( n_sols, n_tas - n_sols) if self.substances and n_sols > 0: msg += "\n\nYour task works with multiple substances. Now you close this primary task and open result for single substance in subfolder." self.label_processing_text.setText(msg) self.progress_processing.setMaximum(n_tas * 4) def _analyze_data(self): ''' action for button_process_all takes all tasks in work_dir to process ''' if not self.tasks: self.tasks = transport.get_result_files( self.work_dir, self.substances) self.messenger('Processing data, it may take a while') self._analyze_data_routine(self.tasks) self.messenger('Processing successfully finished', 300) self._data_dialog() def _analyze_data_selected(self): ''' action for button_process_newonly takes only unprocessed tasks in work_dir to process ''' if not self.tasks: self.tasks = transport.get_result_files( self.work_dir, self.substances) if not self.solutions: self.solutions = transport.parse_task_dirs( self.work_dir, FNAME_ELEMS) dir1 = [path.split(sol)[0] for sol in self.solutions] dir2 = [path.split(sol)[0] for sol in self.tasks] unproc_list = [transport.get_result_files(i, self.substances)[0] for i in dir2 if i not in dir1] self.messenger('Processing data, it may take a while') self._analyze_data_routine(unproc_list) self.messenger('Processing successfully finished', 300) self._data_dialog() def _analyze_data_routine(self, task_list): ''' analyze transport out data uses transport_multi module for multiprocess computation ''' nr_of_proc = cpu_count() - 1 self.progress_processing.setMaximum(len(task_list) * 4) self.progress_processing.setVisible(True) # Create queues task_queue = Queue() done_queue = Queue() # populate queue with data for result in task_list: task_queue.put(result) # Start worker processes for _ in range(nr_of_proc): Process(target=transport.worker, args=( task_queue, done_queue, self.substances)).start() # Get and print results nval = 0 for _ in range(len(task_list)): nval = nval + done_queue.get() self.progress_processing.setValue(nval) self.progress_processing.setMaximum(nval) # Tell child processes to stop for _ in range(nr_of_proc): task_queue.put('STOP') def draw_charts(self): ''' Draw charts apply filter of displayed list if any if not draw for all non-zero elements ''' if not self.result_elements: self.read_concentrations() self.messenger('Data loaded sucessfully, starting drawing the charts') gdir = self.work_dir + SEPAR + 'charts' + SEPAR if not exists(gdir): mkdir(gdir) if len(self.displayed_mesh_list) > 0: filtered_dict = grafLinear.list_filter( self.result_elements, self.displayed_mesh_list.keys()) else: filtered_dict = self.result_elements for xkey, xval in filtered_dict.items(): self.draw_routine(xkey, xval, gdir) self.messenger('all charts sucessfully created') def draw_routine(self, xkey, xval, gdir): ''' Test minimal conc and draw chart if values are greater then given minimum. ''' min_con = float(self.edit_chart_min_conc.text() ) if self.edit_chart_min_conc.text() else 0 if max(xval.values()) > min_con: disp = grafLinear.fill_up_zeros(self.result_times, xval) data = {'disp': disp, 'times': self.result_times} settings = { 'xkey': xkey, 'where': gdir, 'xlabel': str(self.edit_chart_x_text.text()), 'ylabel': str(self.edit_chart_y_text.text()), 'title': str(self.edit_chart_title_text.text()) } grafLinear.draw_chart(data, settings) self.messenger('Chart for element {} created'.format(xkey)) else: self.messenger( 'Maximal concentration for element {} is bellow given value.'.format(xkey)) def create_tables(self): '''Create csv table with results''' if not self.result_elements: self.read_concentrations() csvexport.write_single_conc_tab( self.result_elements, self.result_times, self.work_dir) self.messenger('table of concentrations created') def read_concentrations(self, fname=None): '''read concentration from TransportOut result file''' if fname == None: fname = self.master_work_dir + \ SEPAR + self.file_dict['Transport_out'] msg = '' try: self.result_elements = transport.load_vysledek( str(self.master_work_dir) + SEPAR + FNAME_ELEMS) except IOError: msg += 'Failed to load result data for elements.' self.result_elements = None try: self.result_times = transport.load_vysledek( str(self.master_work_dir) + SEPAR + FNAME_TIME) except IOError: msg += 'Failed to load result data for times.' self.result_times = None if msg: if self.substances: self.messenger("Your task works with multiple substances. Please open result for single substance in subfolder.") else: self.messenger('{}. Please process data first'.format(msg)) self.tabWidget.setCurrentWidget(self.data_processing) self.tabWidget.setCurrentIndex(2) # 2 should be data processing return False else: return True def start_logger(self): ''' logging tool startup ''' self.logger = logger.get_it( __appname__, '{}{}.log'.format(self.output_dir, __appname__.lower())) self.logovat = True def messenger(self, msg, dtime=0): '''sending messages to status bar, and to log, if it's enabled''' self.status_bar.showMessage(msg, dtime) if self.logovat: self.logger.info(msg) def init_mesh_tools(self): ''' mesh tools starter ''' self._load_msh() self._load_mtr() self._load_surface() self._fill_mesh_mtr_form() self.messenger('MESH tools successfully loaded all data', 8000) def analyse_basic_problem(self): ''' action for basic analyser ''' self.master_work_dir = self.work_dir + SEPAR self.problem_type = 'basic' if self.find_and_open_inifile(): self.box_merger.setHidden(True) self.__remove_all_tabs() self.tabWidget.addTab(self.basic_analyser, 'Basic Analyser') self.tabWidget.addTab(self.data_processing, 'Data Processing') self.init_mesh_tools() self.tabWidget.setCurrentWidget(self.basic_analyser) self._save_setup() if not self.result_elements: self.read_concentrations() self.__fill_maps_times() self._analyser_dialog() # check the data before display manualy self._data_dialog() self.tabWidget.show() def analyse_sensitivity_task(self): ''' action for sensitivity analyser ''' self.master_work_dir = self.work_dir + SEPAR + 'master' + SEPAR self.problem_type = 'compare' if self.find_and_open_inifile(): self.box_merger.setHidden(True) self.tabWidget.addTab(self.comparative_analyser, 'Sensitivity Analyser') self.tabWidget.addTab(self.data_processing, 'Data Processing') self.label_analyser_title.setText('Sensitivity Analyser') self.init_mesh_tools() self.tabWidget.setCurrentIndex(2) self._save_setup() self._analyser_dialog() # check the data before display manualy self._data_dialog() self.tabWidget.show() def analyse_monte_carlo(self): ''' action for monte carlo analyser ''' self.master_work_dir = self.work_dir + SEPAR + 'master' + SEPAR self.problem_type = 'compare' if self.find_and_open_inifile(): self.tabWidget.addTab(self.comparative_analyser, 'Monte Carlo Analyser') self.tabWidget.addTab(self.data_processing, 'Data Processing') self.label_analyser_title.setText('Monte Carlo Analyser') self.init_mesh_tools() self.box_merger.setHidden(False) self.tabWidget.setCurrentIndex(2) self._save_setup() self._analyser_dialog() # check the data before display manualy self._data_dialog() self.tabWidget.show() def open_task_dir(self): '''SetUP output using Qfiledialog''' adr = "." tmp = QFileDialog.getExistingDirectory( self, "Open output directory", adr, options=QFileDialog.ShowDirsOnly) self.work_dir = str(tmp) if ruzne.isit_task_folder(tmp): self.identify_problem_type() else: self.messenger( 'Directory does not contain problem.type file! Assuming basic problem.') self.analyse_basic_problem() def identify_problem_type(self): ''' search dir for file problem.type fail if file not exist ''' try: rfile = open(self.work_dir + SEPAR + 'problem.type') except IOError: self.messenger('Failed to open problem.type file') self.analyse_basic_problem() else: istr = rfile.read() if istr.strip() in IDSDICT.keys(): func = getattr(self, IDSDICT[istr.strip()]) func() else: self.messenger('ERROR: failed to recognize given problem') def find_and_open_inifile(self): '''search work dir for a flow ini to open it''' list_of_files = [ff for ff in listdir( self.master_work_dir) if ff.lower().endswith('ini')] if not list_of_files: self.messenger('ERROR: no ini files found, failed to continue') return False elif len(list_of_files) > 2: self.messenger( 'ERROR: 2 or more ini files found, failed to continue') return False else: self.flowini_file_master = list_of_files[0].strip() fname = self.master_work_dir + self.flowini_file_master self.file_dict = flow.get_dict_from_file(fname) subs = flow.get_substances_from_file(fname) if int(subs['N_substances']) > 1: self.substances = True self.setup_substances_form(subs) else: #hide the group box for substances in comparative analyser self.group_compar_subst.setHidden(True) return True def setup_substances_form(self, subs): ''' fill qcombobox in comparative form with names of substances ''' names = subs['Substances'].split() self.select_substance_compar.clear() self.select_substance_compar.insertItems(0, names) self.select_substance_compar.repaint() def _load_setup(self): '''load setup, create a new file if not exists''' try: fread = open(__inifile__) self.setup = INIConfig(fread) self.work_dir = self.setup['Work']['Dir'] fread.close() except IOError: setup = "[Work]\nDir = ''\n" fnew = open(__inifile__, 'w') print >> fnew, setup fnew.close() def __fill_maps_times(self): '''fill form in the maps draw / with simulation times''' if self.result_times: self.maps_sim_time_select.clear() wherefrom = self.result_times data = ["%s" % str(k) for k in sorted(wherefrom, reverse=True)] self.maps_sim_time_select.insertItems(0, data) self.maps_sim_time_select.repaint() def _save_setup(self): '''saves ini file''' self.setup['Work']['Dir'] = self.work_dir fnew = open(__inifile__, 'w') print >> fnew, self.setup fnew.close() def _load_surface(self, file_name=None): ''' load surface elems using surface module first try to open prepared file, in case of file create a new one using surface module ''' wdir = {'basic': self.work_dir, 'compare': self.master_work_dir} fname_bcd = wdir[self.problem_type] + SEPAR + self.file_dict['Boundary'] self.bcd_file = fname_bcd if file_name is None: fname = self.master_work_dir + FNAME_SURF self.messenger('Loading Surface elements') if path.isfile(fname): self.surface_elements = surface.read_result(fname) else: fname_msh = wdir[self.problem_type] + SEPAR + self.file_dict['Mesh'] self.surface_elements = surface.read(fname_bcd, fname_msh) surface.write(fname, self.surface_elements) self.messenger('Surface elements successfully loaded', 8000) def _load_msh(self, file_name=None): ''' load mesh using mesh module ''' if file_name is None: file_name = self.master_work_dir + self.file_dict['Mesh'] self.messenger('Loading MSH') self.msh = mesh.Mesh() self.msh.read(file_name) self.messenger('MSH successfully loaded', 8000) def _load_mtr(self, file_name=None): '''load mtr from file, if not given look to the flow.ini settings''' if file_name == None: file_name = self.master_work_dir + SEPAR + self.file_dict['Material'] self.messenger('Loading materials from MTR file') self.material = material.Material() self.material.getDictFromFile(file_name) self.messenger('MTR successfully loaded', 8000) def _fill_mesh_mtr_form(self): '''fill form in the mesh editor''' self.select_mesh_mtr.clear() wherefrom = self.msh.mtr_index.keys() data = ["%s" % str(k) for k in sorted(wherefrom)] self.select_mesh_mtr.insertItems(0, data) self.select_mesh_mtr.repaint() def _mesh_element_explorer_control(self): ''' action for controlin mesh_element_explorer display block ''' idxtu = str(self.mesh_list.currentIndex().data().toString()) idxtu = idxtu.split() idx = idxtu[0] doc = "element id: {0}\n".format(idx) doc += "node : [x, y, z]\n" for node in self.msh.elements[int(idx)][2]: doc += "{0} : {1}\n".format(node, self.msh.nodes[node]) self.mesh_element_explorer.clear() self.mesh_element_explorer.insertPlainText(doc) self.mesh_element_explorer.setReadOnly(True) def _mesh_import_list_updater(self, vals=None, todisp=0): '''helper for various list update functions @PARAM vals {mesh elements} @PARAM todisp number of elements for message ''' if vals is not None and len(vals) > 0: self.displayed_mesh_list.update(vals) todisp = len(vals) msg = 'Loading %s elements to the list. It may take a while...' % str( todisp) self.messenger(msg) self._mesh_list_refresh() self.messenger('Selection of elements loaded', 8000) def _mesh_import_list_deleter(self, vals): ''' deletes given values from displayed mesh list @param vals: {} of values to be deleted ''' for key in vals.keys(): try: del self.displayed_mesh_list[key] except KeyError: pass msg = 'Refreshing the list' self.messenger(msg) self._mesh_list_refresh() self.messenger('Refreshing finished', 8000) def _get_mesh_axis(self): ''' check what radio is checked, return axis @return: axis string identifier ''' if self.mesh_radio_x.isChecked(): return 'x' elif self.mesh_radio_y.isChecked(): return 'y' elif self.mesh_radio_z.isChecked(): return 'z' else: self.messenger('Choose axis first!', 8000) return False def _read_surface_elements(self): ''' imports surface elements to mesh list need surface_result file to work ''' if not self.surface_elements: fname = self.work_dir + SEPAR + 'master' + SEPAR + FNAME_SURF self.surface_elements = surface.read_result(fname) vals = {} for elid in self.surface_elements: elid = int(elid) if self.msh.elements.has_key(elid): vals[elid] = self.msh.elements[elid] return vals def _mesh_import_surface(self): ''' imports surface elements to mesh list need surface_result file to work ''' vals = self._read_surface_elements() self._mesh_import_list_updater(vals) def __remove_zeros_from_mesh_list(self, mesh_list): ''' removes elemtns with no (zero values) concentration in time from given mesh list ''' if not self.result_elements: self.read_concentrations() for key in mesh_list.keys(): if str(key) not in self.result_elements: del mesh_list[key] return mesh_list def _mesh_remove_zero(self): ''' removes elemtns with no (zero values) concentration in time from mesh list ''' self.displayed_mesh_list = self.__remove_zeros_from_mesh_list( self.displayed_mesh_list) msg = 'Refreshing the list' self.messenger(msg) self._mesh_list_refresh() self.messenger('Refreshing finished', 8000) def _mesh_import_nonzero(self): ''' imports elements with nonzero concentration in time to mesh list ''' if not self.result_elements: self.read_concentrations() vals = {} for elid in self.result_elements: elid = int(elid) if self.msh.elements.has_key(elid): vals[elid] = self.msh.elements[elid] self._mesh_import_list_updater(vals) def _mesh_import_axis(self, compare, axis='z'): '''import elements with coordinate in given axis, compare given val with the value of mesh spinbox @param compare: -1 for elements bellow, 0 for through, 1 for over @param axix: what axis (x, y, z) ''' val = int(self.edit_mesh_crd.text()) vals = {} for elid, elem in self.msh.elements.items(): pridat = True for node_id in elem[2]: node_coord = self.msh.nodes[node_id][AXIS_TRANS[axis]] if cmp(node_coord, val) == compare or cmp(node_coord, val) == 0: pridat = False if pridat: vals[elid] = self.msh.elements[elid] return vals def _mesh_find_through(self, axis='z', val='default'): '''import elements with at last one node over coordinate in given axis, such elements has to be cuted through given plane compare given val with the value of mesh spinbox @param axix: what axis (x, y, z) ''' if val == 'default': val = int(self.edit_mesh_crd.text()) vals = {} for elid, elem in self.msh.elements.items(): nad = False pod = False for node_id in elem[2]: node_coord = self.msh.nodes[node_id][AXIS_TRANS[axis]] if cmp(node_coord, val) == 1 or cmp(node_coord, val) == 0: nad = True elif cmp(node_coord, val) == -1 or cmp(node_coord, val) == 0: pod = True if nad and pod: vals[elid] = self.msh.elements[elid] return vals def _mesh_import_through(self): '''import elements where one node has Z over value of mesh spinbox and therefore this element is cut by given alt ''' axis = self._get_mesh_axis() if axis: vals = self._mesh_find_through(axis) self._mesh_import_list_updater(vals) def _mesh_import_over(self): '''import elements where all nodes has Z over value of mesh spinbox ''' axis = self._get_mesh_axis() if axis: vals = self._mesh_import_axis(-1, axis) self._mesh_import_list_updater(vals) def _mesh_import_bellow(self): '''import elements where all nodes has Z bellow value of mesh spinbox ''' axis = self._get_mesh_axis() if axis: vals = self._mesh_import_axis(1, axis) self._mesh_import_list_updater(vals) def _mesh_import_id(self): ''' Import elemet with id given in form and update displayed list ''' try: elmid = int(self.mesh_element_id_edit.text()) vals = {elmid: self.msh.elements[elmid]} self._mesh_import_list_updater(vals) except KeyError: self.messenger('ERROR: no such element', 8000) except ValueError: self.messenger('ERROR: not valid element id', 8000) def _mesh_remove_id(self): ''' Remove elemet with id given in form and update displayed list ''' elmid = int(self.mesh_element_id_edit.text()) try: vals = {elmid: self.msh.elements[elmid]} self._mesh_import_list_deleter(vals) except KeyError: self.messenger('ERROR: no such element', 8000) def _mesh_remove_over(self): '''removes elements where all nodes has coordinate in axis over value of mesh spinbox ''' axis = self._get_mesh_axis() if axis: vals = self._mesh_import_axis(-1, axis) self._mesh_import_list_deleter(vals) def _mesh_remove_bellow(self): ''' removes elements with coordinate in given axis bellow value of mesh spinbox ''' axis = self._get_mesh_axis() if axis: vals = self._mesh_import_axis(1, axis) self._mesh_import_list_deleter(vals) def _mesh_remove_through(self): ''' removes elements cuted through by given plane ''' axis = self._get_mesh_axis() if axis: vals = self._mesh_find_through(axis) self._mesh_import_list_deleter(vals) def _mesh_import_mtr(self): '''import all nodes with material selected in the selec_mtr_mesh''' idx = int(self.select_mesh_mtr.currentText()) vals = {} for i in self.msh.mtr_index[idx]: vals[i] = self.msh.elements[i] self._mesh_import_list_updater(vals) def _mesh_remove_mtr(self): '''delete all nodes with material selected in the selec_mtr_mesh''' idx = int(self.select_mesh_mtr.currentText()) for i in self.msh.mtr_index[idx]: del self.displayed_mesh_list[i] msg = 'Deleting %s elements from the list. It may take a while...' % str( len(self.msh.mtr_index[idx])) self.messenger(msg) self._mesh_list_refresh() self.messenger('Selection of elements loaded', 8000) def _mesh_import_all(self): '''imports all mesh to list - msh file need to be loaded first''' try: msg = 'Loading %s elements to the list. It may take a while...' % str( len(self.msh.elements)) self.messenger(msg) self.displayed_mesh_list = self.msh.elements.copy() self._mesh_list_refresh() self.messenger('MSH data loaded', 8000) except AttributeError: self.messenger('Error: load MSH data first!', 8000) def _mesh_remove_all(self): ''' Clear mesh selected list completely ''' try: self.displayed_mesh_list = {} self.mesh_list.clear() finally: self.messenger('Data removed from list', 8000) self.groupBox_2.setTitle('0 elements in the list') def _mesh_list_refresh(self): ''' takes actual dict self.displayedList and refresh the view displays - element id, element type, material creates and refreshes dict of mtr {mtrid : [el1, el2]} ''' try: self.mesh_list.clear() self.mtr_index_disp_lst = {} for key in sorted(self.displayed_mesh_list.keys()): # update of list mtr = self.displayed_mesh_list[key][1][0] tst = "%s %s %s" % (key, self.displayed_mesh_list[key][0], mtr) QListWidgetItem(str(tst), self.mesh_list) # update of index if self.mtr_index_disp_lst.has_key(mtr): self.mtr_index_disp_lst[mtr].append(key) else: self.mtr_index_disp_lst[mtr] = [key] # update the default index self.msh.mtr_index.update(self.mtr_index_disp_lst) self.mesh_list.repaint() msg = "{0} elements in the list".format( len(self.displayed_mesh_list)) self.groupBox_2.setTitle(msg) except: self.messenger('Unexpected Error', 8000)
class QTSeedEditor(QDialog): """ DICOM viewer. """ @staticmethod def get_line(mode='h'): line = QFrame() if mode == 'h': line.setFrameStyle(QFrame.HLine) elif mode == 'v': line.setFrameStyle(QFrame.VLine) line.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) return line def initUI(self, shape, vscale, height=600, mode='seed'): """ Initialize UI. Parameters ---------- shape : (int, int, int) Shape of data matrix. vscale : (float, float, float) Voxel scaling. height : int Maximal slice height in pixels. mode : str Editor mode. """ # picture grid = height / float(shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box = SliceBox(shape[:-1], mgrid, mode) self.slice_box.setScrollFun(self.scrollSlices) self.connect(self.slice_box, SIGNAL('focus_slider'), self.focusSliceSlider) # sliders self.allow_select_slice = True self.n_slices = shape[2] self.slider = QSlider(Qt.Vertical) self.slider.label = QLabel() self.slider.label.setText('Slice: %d / %d' % (self.actual_slice, self.n_slices)) self.slider.setRange(1, self.n_slices) self.slider.valueChanged.connect(self.sliderSelectSlice) self.slider.setValue(self.actual_slice) self.slider_cw = {} self.slider_cw['c'] = QSlider(Qt.Horizontal) self.slider_cw['c'].valueChanged.connect(self.changeC) self.slider_cw['c'].label = QLabel() self.slider_cw['w'] = QSlider(Qt.Horizontal) self.slider_cw['w'].valueChanged.connect(self.changeW) self.slider_cw['w'].label = QLabel() self.view_label = QLabel('View size: %d x %d' % self.img_aview.shape[:-1]) self.voxel_label = QLabel('Voxel size [mm]:\n %.2f x %.2f x %.2f'\ % tuple(self.voxel_size[np.array(self.act_transposition)])) combo_view_options = VIEW_TABLE.keys() combo_view = QComboBox(self) combo_view.activated[str].connect(self.setView) combo_view.addItems(combo_view_options) # buttons self.btn_quit = QPushButton("Return", self) self.btn_quit.clicked.connect(self.quit) combo_dmask = QComboBox(self) combo_dmask.activated.connect(self.changeMask) self.mask_points_tab, aux = self.init_draw_mask(DRAW_MASK, mgrid) for icon, label in aux: combo_dmask.addItem(icon, label) self.slice_box.setMaskPoints(self.mask_points_tab[combo_dmask.currentIndex()]) self.status_bar = QStatusBar() vopts = [] vmenu = [] appmenu = [] if mode == 'seed' and self.mode_fun is not None: btn_recalc = QPushButton("Recalculate", self) btn_recalc.clicked.connect(self.recalculate) appmenu.append(QLabel('<b>Segmentation mode</b><br><br><br>' + 'Select the region of interest<br>' + 'using the mouse buttons:<br><br>' + ' <i>left</i> - inner region<br>' + ' <i>right</i> - outer region<br><br>')) appmenu.append(btn_recalc) appmenu.append(QLabel()) self.volume_label = QLabel('Volume:\n unknown') appmenu.append(self.volume_label) # Set middle pencil as default (M. Jirik) combo_dmask.setCurrentIndex(1) self.slice_box.setMaskPoints( self.mask_points_tab[combo_dmask.currentIndex()]) # -----mjirik---end------ if mode == 'seed' or mode == 'crop'\ or mode == 'mask' or mode == 'draw': btn_del = QPushButton("Delete Seeds", self) btn_del.clicked.connect(self.deleteSliceSeeds) vmenu.append(None) vmenu.append(btn_del) combo_contour_options = ['fill', 'contours', 'off'] combo_contour = QComboBox(self) combo_contour.activated[str].connect(self.changeContourMode) combo_contour.addItems(combo_contour_options) self.changeContourMode(combo_contour_options[combo_contour.currentIndex()]) vopts.append(QLabel('Selection mode:')) vopts.append(combo_contour) if mode == 'mask': btn_recalc_mask = QPushButton("Recalculate mask", self) btn_recalc_mask.clicked.connect(self.updateMaskRegion_btn) btn_all = QPushButton("Select all", self) btn_all.clicked.connect(self.maskSelectAll) btn_reset = QPushButton("Reset selection", self) btn_reset.clicked.connect(self.resetSelection) btn_reset_seads = QPushButton("Reset seads", self) btn_reset_seads.clicked.connect(self.resetSeads) btn_add = QPushButton("Add selection", self) btn_add.clicked.connect(self.maskAddSelection) btn_rem = QPushButton("Remove selection", self) btn_rem.clicked.connect(self.maskRemoveSelection) btn_mask = QPushButton("Mask region", self) btn_mask.clicked.connect(self.maskRegion) appmenu.append(QLabel('<b>Mask mode</b><br><br><br>' + 'Select the region to mask<br>' + 'using the left mouse button<br><br>')) appmenu.append(self.get_line('h')) appmenu.append(btn_recalc_mask) appmenu.append(btn_all) appmenu.append(btn_reset) appmenu.append(btn_reset_seads) appmenu.append(self.get_line('h')) appmenu.append(btn_add) appmenu.append(btn_rem) appmenu.append(self.get_line('h')) appmenu.append(btn_mask) appmenu.append(self.get_line('h')) self.mask_qhull = None if mode == 'crop': btn_crop = QPushButton("Crop", self) btn_crop.clicked.connect(self.crop) appmenu.append(QLabel('<b>Crop mode</b><br><br><br>' + 'Select the crop region<br>' + 'using the left mouse button<br><br>')) appmenu.append(btn_crop) if mode == 'draw': appmenu.append(QLabel('<b>Manual segmentation<br> mode</b><br><br><br>' + 'Mark the region of interest<br>' + 'using the mouse buttons:<br><br>' + ' <i>left</i> - draw<br>' + ' <i>right</i> - erase<br>' + ' <i>middle</i> - vol. erase<br><br>')) btn_reset = QPushButton("Reset", self) btn_reset.clicked.connect(self.resetSliceDraw) vmenu.append(None) vmenu.append(btn_reset) combo_erase_options = ['inside', 'outside'] combo_erase = QComboBox(self) combo_erase.activated[str].connect(self.changeEraseMode) combo_erase.addItems(combo_erase_options) self.changeEraseMode(combo_erase_options[combo_erase.currentIndex()]) vopts.append(QLabel('Volume erase mode:')) vopts.append(combo_erase) hbox = QHBoxLayout() vbox = QVBoxLayout() vbox_left = QVBoxLayout() vbox_app = QVBoxLayout() hbox.addWidget(self.slice_box) hbox.addWidget(self.slider) vbox_left.addWidget(self.slider.label) vbox_left.addWidget(self.view_label) vbox_left.addWidget(self.voxel_label) vbox_left.addWidget(QLabel()) vbox_left.addWidget(QLabel('View plane:')) vbox_left.addWidget(combo_view) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(self.slider_cw['c'].label) vbox_left.addWidget(self.slider_cw['c']) vbox_left.addWidget(self.slider_cw['w'].label) vbox_left.addWidget(self.slider_cw['w']) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(QLabel('Drawing mask:')) vbox_left.addWidget(combo_dmask) for ii in vopts: vbox_left.addWidget(ii) for ii in vmenu: if ii is None: vbox_left.addStretch(1) else: vbox_left.addWidget(ii) for ii in appmenu: if ii is None: vbox_app.addStretch(1) else: vbox_app.addWidget(ii) vbox_app.addStretch(1) vbox_app.addWidget(self.btn_quit) hbox.addLayout(vbox_left) hbox.addWidget(self.get_line('v')) hbox.addLayout(vbox_app) vbox.addLayout(hbox) vbox.addWidget(self.status_bar) self.my_layout = vbox self.setLayout(vbox) self.setWindowTitle('Segmentation Editor') self.show() def __init__(self, img, viewPositions=None, seeds=None, contours=None, mode='seed', modeFun=None, voxelSize=[1,1,1], volume_unit='mm3'): """ Initiate Editor Parameters ---------- img : array DICOM data matrix. actualSlice : int Index of actual slice. seeds : array Seeds, user defined regions of interest. contours : array Computed segmentation. mode : str Editor modes: 'seed' - seed editor 'crop' - manual crop 'draw' - drawing 'mask' - mask region modeFun : fun Mode function invoked by user button. voxelSize : tuple of float voxel size [mm] volume_unit : allow select output volume in mililiters or mm3 [mm, ml] """ QDialog.__init__(self) self.mode = mode self.mode_fun = modeFun self.actual_view = 'axial' self.act_transposition = VIEW_TABLE[self.actual_view] self.img = img self.img_aview = self.img.transpose(self.act_transposition) self.volume_unit = volume_unit self.last_view_position = {} for jj, ii in enumerate(VIEW_TABLE.iterkeys()): if viewPositions is None: viewpos = img.shape[VIEW_TABLE[ii][-1]] / 2 else: viewpos = viewPositions[jj] self.last_view_position[ii] =\ img.shape[VIEW_TABLE[ii][-1]] - viewpos - 1 self.actual_slice = self.last_view_position[self.actual_view] # set contours self.contours = contours if self.contours is None: self.contours_aview = None else: self.contours_aview = self.contours.transpose(self.act_transposition) # masked data - has information about which data were removed # 1 == enabled, 0 == deleted # How to return: # editorDialog.exec_() # masked_data = editorDialog.masked self.masked = np.ones(self.img.shape, np.int8) self.voxel_size = np.squeeze(np.asarray(voxelSize)) self.voxel_scale = self.voxel_size / float(np.min(self.voxel_size)) self.voxel_volume = np.prod(voxelSize) # set seeds if seeds is None: self.seeds = np.zeros(self.img.shape, np.int8) else: self.seeds = seeds self.seeds_aview = self.seeds.transpose(self.act_transposition) self.seeds_modified = False self.initUI(self.img_aview.shape, self.voxel_scale[np.array(self.act_transposition)], 600, mode) if mode == 'draw': self.seeds_orig = self.seeds.copy() self.slice_box.setEraseFun(self.eraseVolume) # set view window values C/W lb = np.min(img) self.img_min_val = lb ub = np.max(img) dul = np.double(ub) - np.double(lb) self.cw_range = {'c': [lb, ub], 'w': [1, dul]} self.slider_cw['c'].setRange(lb, ub) self.slider_cw['w'].setRange(1, dul) self.changeC(lb + dul / 2) self.changeW(dul) self.offset = np.zeros((3,), dtype=np.int16) def showStatus(self, msg): self.status_bar.showMessage(QString(msg)) QApplication.processEvents() def init_draw_mask(self, draw_mask, grid): mask_points = [] mask_iconlabel = [] for mask, label in draw_mask: w, h = mask.shape xx, yy = mask.nonzero() mask_points.append((xx - w/2, yy - h/2)) img = QImage(w, h, QImage.Format_ARGB32) img.fill(qRgba(255, 255, 255, 0)) for ii in range(xx.shape[0]): img.setPixel(xx[ii], yy[ii], qRgba(0, 0, 0, 255)) img = img.scaled(QSize(w * grid[0], h * grid[1])) icon = QIcon(QPixmap.fromImage(img)) mask_iconlabel.append((icon, label)) return mask_points, mask_iconlabel def saveSliceSeeds(self): aux = self.slice_box.getSliceSeeds() if aux is not None: self.seeds_aview[...,self.actual_slice] = aux self.seeds_modified = True else: self.seeds_modified = False def updateMaskRegion_btn(self): self.saveSliceSeeds() self.updateMaskRegion() def updateMaskRegion(self): crp = self.getCropBounds(return_nzs=True) if crp is not None: off, cri, nzs = crp if nzs[0].shape[0] <=5: self.showStatus("Not enough points (need >= 5)!") else: points = np.transpose(nzs) hull = Delaunay(points) X, Y, Z = np.mgrid[cri[0], cri[1], cri[2]] grid = np.vstack([X.ravel(), Y.ravel(), Z.ravel()]).T simplex = hull.find_simplex(grid) fill = grid[simplex >=0,:] fill = (fill[:,0], fill[:,1], fill[:,2]) if self.contours is None or self.contours_old is None: self.contours = np.zeros(self.img.shape, np.int8) self.contours_old = self.contours.copy() else: self.contours[self.contours != 2] = 0 self.contours[fill] = 1 self.contours_aview = self.contours.transpose(self.act_transposition) self.selectSlice(self.actual_slice) def maskRegion(self): self.masked[self.contours == 0] = 0 self.img[self.contours != 2] = self.img_min_val self.contours.fill(0) self.contours_old = self.contours.copy() self.seeds.fill(0) self.selectSlice(self.actual_slice) def maskAddSelection(self): self.updateMaskRegion() if self.contours is None: return self.contours[self.contours == 1] = 2 self.contours_old = self.contours.copy() self.seeds.fill(0) self.selectSlice(self.actual_slice) def maskRemoveSelection(self): self.updateMaskRegion() if self.contours is None: return self.contours[self.contours == 1] = 0 self.contours_old = self.contours.copy() self.seeds.fill(0) self.selectSlice(self.actual_slice) def maskSelectAll(self): self.updateMaskRegion() self.seeds[0][0][0] = 1 self.seeds[0][0][-1] = 1 self.seeds[0][-1][0] = 1 self.seeds[0][-1][-1] = 1 self.seeds[-1][0][0] = 1 self.seeds[-1][0][-1] = 1 self.seeds[-1][-1][0] = 1 self.seeds[-1][-1][-1] = 1 self.updateMaskRegion() self.selectSlice(self.actual_slice) def resetSelection(self): self.updateMaskRegion() if self.contours is None: return self.contours.fill(0) self.contours_old = self.contours.copy() self.seeds.fill(0) self.selectSlice(self.actual_slice) def resetSeads(self): self.seeds.fill(0) if self.contours is not None: self.contours = self.contours_old.copy() self.contours_aview = self.contours.transpose(self.act_transposition) self.updateMaskRegion() self.selectSlice(self.actual_slice) def updateCropBounds(self): crp = self.getCropBounds() if crp is not None: _, cri = crp self.contours = np.zeros(self.img.shape, np.int8) self.contours[cri].fill(1) self.contours_aview = self.contours.transpose(self.act_transposition) def focusSliceSlider(self): self.slider.setFocus(True) def sliderSelectSlice(self, value): self.selectSlice(self.n_slices - value) def scrollSlices(self, inc): if abs(inc) > 0: new = self.actual_slice + inc self.selectSlice(new) def selectSlice(self, value, force=False): if not(self.allow_select_slice): return if (value < 0) or (value >= self.n_slices): return if (value != self.actual_slice) or force: self.saveSliceSeeds() if self.seeds_modified: if self.mode == 'crop': self.updateCropBounds() elif self.mode == 'mask': self.updateMaskRegion() if self.contours is None: contours = None else: contours = self.contours_aview[...,value] slider_val = self.n_slices - value self.slider.setValue(slider_val) self.slider.label.setText('Slice: %d / %d' % (slider_val, self.n_slices)) self.slice_box.setSlice(self.img_aview[...,value], self.seeds_aview[...,value], contours) self.actual_slice = value def getSeeds(self): return self.seeds def getImg(self): return self.img def getOffset(self): return self.offset * self.voxel_size def getSeedsVal(self, label): return self.img[self.seeds==label] def getContours(self): return self.contours def setContours(self, contours): self.contours = contours self.contours_aview = self.contours.transpose(self.act_transposition) self.selectSlice(self.actual_slice) def changeCW(self, value, key): rg = self.cw_range[key] if (value < rg[0]) or (value > rg[1]): return if (value != self.slice_box.getCW()[key]): self.slider_cw[key].setValue(value) self.slider_cw[key].label.setText('%s: %d' % (key.upper(), value)) self.slice_box.setCW(value, key) self.slice_box.updateSliceCW(self.img_aview[...,self.actual_slice]) def changeC(self, value): self.changeCW(value, 'c') def changeW(self, value): self.changeCW(value, 'w') def setView(self, value): self.last_view_position[self.actual_view] = self.actual_slice # save seeds self.saveSliceSeeds() if self.seeds_modified: if self.mode == 'crop': self.updateCropBounds() elif self.mode == 'mask': self.updateMaskRegion() key = str(value) self.actual_view = key self.actual_slice = self.last_view_position[key] self.act_transposition = VIEW_TABLE[key] self.img_aview = self.img.transpose(self.act_transposition) self.seeds_aview = self.seeds.transpose(self.act_transposition) if self.contours is not None: self.contours_aview = self.contours.transpose(self.act_transposition) contours = self.contours_aview[...,self.actual_slice] else: contours = None vscale = self.voxel_scale[np.array(self.act_transposition)] height = self.slice_box.height() grid = height / float(self.img_aview.shape[1] * vscale[1]) # width = (self.img_aview.shape[0] * vscale[0])[0] # if width > 800: # height = 400 # grid = height / float(self.img_aview.shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box.resizeSlice(new_slice_size=self.img_aview.shape[:-1], new_grid=mgrid) self.slice_box.setSlice(self.img_aview[...,self.actual_slice], self.seeds_aview[...,self.actual_slice], contours) self.allow_select_slice = False self.n_slices = self.img_aview.shape[2] slider_val = self.n_slices - self.actual_slice self.slider.setRange(1, self.n_slices) self.slider.setValue(slider_val) self.allow_select_slice = True self.slider.label.setText('Slice: %d / %d' % (slider_val, self.n_slices)) self.view_label.setText('View size: %d x %d' % self.img_aview.shape[:-1]) self.adjustSize() self.adjustSize() def changeMask(self, val): self.slice_box.setMaskPoints(self.mask_points_tab[val]) def changeContourMode(self, val): self.slice_box.contour_mode = str(val) self.slice_box.updateSlice() def changeEraseMode(self, val): self.slice_box.erase_mode = str(val) def eraseVolume(self, pos, mode): self.showStatus("Processing...") xyz = np.array(pos + (self.actual_slice,)) p = np.zeros_like(xyz) p[np.array(self.act_transposition)] = xyz p = tuple(p) if self.seeds[p] > 0: if mode == 'inside': erase_reg(self.seeds, p, val=0) elif mode == 'outside': erase_reg(self.seeds, p, val=-1) idxs = np.where(self.seeds < 0) self.seeds.fill(0) self.seeds[idxs] = 1 if self.contours is None: contours = None else: contours = self.contours_aview[...,self.actual_slice] self.slice_box.setSlice(self.img_aview[...,self.actual_slice], self.seeds_aview[...,self.actual_slice], contours) self.showStatus("Done") def cropUpdate(self, img): for ii in VIEW_TABLE.iterkeys(): self.last_view_position[ii] = 0 self.actual_slice = 0 self.img = img self.img_aview = self.img.transpose(self.act_transposition) self.contours = None self.contours_aview = None self.seeds = np.zeros(self.img.shape, np.int8) self.seeds_aview = self.seeds.transpose(self.act_transposition) self.seeds_modified = False vscale = self.voxel_scale[np.array(self.act_transposition)] height = self.slice_box.height() grid = height / float(self.img_aview.shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box.resizeSlice(new_slice_size=self.img_aview.shape[:-1], new_grid=mgrid) self.slice_box.setSlice(self.img_aview[...,self.actual_slice], self.seeds_aview[...,self.actual_slice], None) self.allow_select_slice = False self.n_slices = self.img_aview.shape[2] self.slider.setValue(self.actual_slice + 1) self.slider.setRange(1, self.n_slices) self.allow_select_slice = True self.slider.label.setText('Slice: %d / %d' % (self.actual_slice + 1, self.n_slices)) self.view_label.setText('View size: %d x %d' % self.img_aview.shape[:-1]) def getCropBounds(self, return_nzs=False, flat=False): nzs = self.seeds.nonzero() cri = [] flag = True for ii in range(3): if nzs[ii].shape[0] == 0: flag = False break smin, smax = np.min(nzs[ii]), np.max(nzs[ii]) if not(flat): if smin == smax: flag = False break cri.append((smin, smax)) if flag: cri = np.array(cri) out = [] offset = [] for jj, ii in enumerate(cri): out.append(slice(ii[0], ii[1] + 1)) offset.append(ii[0]) if return_nzs: return np.array(offset), tuple(out), nzs else: return np.array(offset), tuple(out) else: return None def crop(self): self.showStatus("Processing...") crp = self.getCropBounds() if crp is not None: offset, cri = crp crop = self.img[cri] self.img = np.ascontiguousarray(crop) self.offset += offset self.showStatus('Done') else: self.showStatus('Region not selected!') self.cropUpdate(self.img) def recalculate(self, event): self.saveSliceSeeds() if np.abs(np.min(self.seeds) - np.max(self.seeds)) < 2: self.showStatus('Inner and outer regions not defined!') return self.showStatus("Processing...") self.mode_fun(self) self.selectSlice(self.actual_slice) self.updateVolume() self.showStatus("Done") def deleteSliceSeeds(self, event): self.seeds_aview[...,self.actual_slice] = 0 self.slice_box.setSlice(seeds=self.seeds_aview[...,self.actual_slice]) self.slice_box.updateSlice() def resetSliceDraw(self, event): seeds_orig_aview = self.seeds_orig.transpose(self.act_transposition) self.seeds_aview[...,self.actual_slice] = seeds_orig_aview[...,self.actual_slice] self.slice_box.setSlice(seeds=self.seeds_aview[...,self.actual_slice]) self.slice_box.updateSlice() def quit(self, event): self.close() def updateVolume(self): text = 'Volume:\n unknown' if self.voxel_volume is not None: if self.mode == 'draw': vd = self.seeds else: vd = self.contours if vd is not None: nzs = vd.nonzero() nn = nzs[0].shape[0] if self.volume_unit == 'ml': text = 'Volume [ml]:\n %.2f' %\ (nn * self.voxel_volume / 1000) else: text = 'Volume [mm3]:\n %.2e' % (nn * self.voxel_volume) self.volume_label.setText(text) def getROI(self): crp = self.getCropBounds() if crp is not None: _, cri = crp else: cri = [] for jj, ii in enumerate(self.img.shape): off = self.offset[jj] cri.append(slice(off, off + ii)) return cri
class QTSeedEditor(QDialog): """ DICOM viewer. """ @staticmethod def get_line(mode='h'): line = QFrame() if mode == 'h': line.setFrameStyle(QFrame.HLine) elif mode == 'v': line.setFrameStyle(QFrame.VLine) line.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) return line def initUI(self, shape, vscale, height=600, mode='seed'): """ Initialize UI. Parameters ---------- shape : (int, int, int) Shape of data matrix. vscale : (float, float, float) Voxel scaling. height : int Maximal slice height in pixels. mode : str Editor mode. """ self.slab = {} # picture grid = height / float(shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box = SliceBox(shape[:-1], mgrid, mode) self.slice_box.setScrollFun(self.scrollSlices) self.connect(self.slice_box, SIGNAL('focus_slider'), self.focusSliceSlider) # sliders self.allow_select_slice = True self.n_slices = shape[2] self.slider = QSlider(Qt.Vertical) self.slider.valueChanged.connect(self.sliderSelectSlice) self.slider.label = QLabel() self.slider.setRange(1, self.n_slices) self.slider_cw = {} self.slider_cw['c'] = QSlider(Qt.Horizontal) self.slider_cw['c'].valueChanged.connect(self.changeC) self.slider_cw['c'].label = QLabel() self.slider_cw['w'] = QSlider(Qt.Horizontal) self.slider_cw['w'].valueChanged.connect(self.changeW) self.slider_cw['w'].label = QLabel() self.view_label = QLabel('View size: %d x %d' % self.img_aview.shape[:-1]) self.voxel_label = QLabel('Voxel size [mm]:\n %.2f x %.2f x %.2f'\ % tuple(self.voxel_size[np.array(self.act_transposition)])) combo_view_options = VIEW_TABLE.keys() combo_view = QComboBox(self) combo_view.activated[str].connect(self.setView) combo_view.addItems(combo_view_options) #radio button group for choosing seed class ------------------------ self.current_class = 1 self.slice_box.seed_mark = self.current_class number_group = QGroupBox(QString('Class markers')) vbox_NG = QVBoxLayout() r1 = QRadioButton('class 1') r1.setStyleSheet('QRadioButton {color: red}') r1.setChecked(True) r2 = QRadioButton('class 2') r2.setStyleSheet('QRadioButton {color: green}') r3 = QRadioButton('class 3') r3.setStyleSheet('QRadioButton {color: blue}') r4 = QRadioButton('class 4') r4.setStyleSheet('QRadioButton {color: cyan}') r5 = QRadioButton('class 5') r5.setStyleSheet('QRadioButton {color: magenta}') vbox_NG.addWidget(r1) vbox_NG.addWidget(r2) vbox_NG.addWidget(r3) vbox_NG.addWidget(r4) vbox_NG.addWidget(r5) number_group.setLayout(vbox_NG) self.button_group = QButtonGroup() self.button_group.addButton(r1, 1) self.button_group.addButton(r2, 2) self.button_group.addButton(r3, 3) self.button_group.addButton(r4, 4) self.button_group.addButton(r5, 5) self.connect(self.button_group, SIGNAL("buttonClicked(int)"), self.change_seed_class) #------------------------------------------------------------------- # buttons # btn_save = QPushButton('Save', self) # btn_save.clicked.connect(self.save) btn_quit = QPushButton("Quit", self) btn_quit.clicked.connect(self.quit) # btn_crop = QPushButton('Crop', self) # btn_crop.clicked.connect(self.crop) combo_dmask = QComboBox(self) combo_dmask.activated.connect(self.changeMask) self.mask_points_tab, aux = self.init_draw_mask(DRAW_MASK, mgrid) for icon, label in aux: combo_dmask.addItem(icon, label) self.slice_box.setMaskPoints(self.mask_points_tab[combo_dmask.currentIndex()]) self.status_bar = QStatusBar() vopts = [] vmenu = [] appmenu = [] if mode == 'seed': btn_recalc = QPushButton("Recalculate", self) btn_recalc.clicked.connect(self.recalculate) appmenu.append(QLabel('<b>Segmentation mode</b><br><br><br>' + 'Select the region of interest<br>' + 'using the mouse buttons.<br><br>')) appmenu.append(btn_recalc) appmenu.append(QLabel()) self.volume_label = QLabel('Volume [mm3]:\n unknown') appmenu.append(self.volume_label) if mode == 'seed' or mode == 'crop': btn_del = QPushButton("Delete Seeds", self) btn_del.clicked.connect(self.deleteSliceSeeds) vmenu.append(None) vmenu.append(btn_del) combo_contour_options = ['fill', 'contours', 'off'] combo_contour = QComboBox(self) combo_contour.activated[str].connect(self.changeContourMode) combo_contour.addItems(combo_contour_options) self.changeContourMode(combo_contour_options[combo_contour.currentIndex()]) vopts.append(QLabel('Selection mode:')) vopts.append(combo_contour) if mode == 'crop': btn_crop = QPushButton("Crop", self) btn_crop.clicked.connect(self.crop) appmenu.append(QLabel('<b>Crop mode</b><br><br><br>' + 'Select the crop region<br>' + 'using the left mouse button<br><br>')) appmenu.append(btn_crop) # if mode == 'draw': # appmenu.append(QLabel('<b>Manual segmentation<br> mode</b><br><br><br>' + # 'Mark the region of interest<br>' + # 'using the mouse buttons:<br><br>' + # ' <i>left</i> - draw<br>' + # ' <i>right</i> - erase<br>' + # ' <i>middle</i> - vol. erase<br><br>')) # # btn_reset = QPushButton("Reset", self) # btn_reset.clicked.connect(self.resetSliceDraw) # vmenu.append(None) # vmenu.append(btn_reset) # # combo_erase_options = ['inside', 'outside'] # combo_erase = QComboBox(self) # combo_erase.activated[str].connect(self.changeEraseMode) # combo_erase.addItems(combo_erase_options) # self.changeEraseMode(combo_erase_options[combo_erase.currentIndex()]) # vopts.append(QLabel('Volume erase mode:')) # vopts.append(combo_erase) hbox = QHBoxLayout() vbox = QVBoxLayout() vbox_left = QVBoxLayout() vbox_app = QVBoxLayout() hbox.addWidget(self.slice_box) hbox.addWidget(self.slider) vbox_left.addWidget(self.slider.label) vbox_left.addWidget(self.view_label) vbox_left.addWidget(self.voxel_label) vbox_left.addWidget(QLabel()) vbox_left.addWidget(QLabel('View plane:')) vbox_left.addWidget(combo_view) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(self.slider_cw['c'].label) vbox_left.addWidget(self.slider_cw['c']) vbox_left.addWidget(self.slider_cw['w'].label) vbox_left.addWidget(self.slider_cw['w']) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(QLabel('Drawing mask:')) vbox_left.addWidget(combo_dmask) for ii in vopts: vbox_left.addWidget(ii) for ii in vmenu: if ii is None: vbox_left.addStretch(1) else: vbox_left.addWidget(ii) for ii in appmenu: if ii is None: vbox_app.addStretch(1) else: vbox_app.addWidget(ii) vbox_left.addWidget(self.get_line()) vbox_left.addWidget(number_group) # vbox_app.addWidget(btn_crop) vbox_app.addStretch(1) # vbox_app.addWidget(btn_save) vbox_app.addWidget(btn_quit) hbox.addLayout(vbox_left) hbox.addWidget(self.get_line('v')) hbox.addLayout(vbox_app) vbox.addLayout(hbox) vbox.addWidget(self.status_bar) self.setLayout(vbox) self.setWindowTitle('Segmentation Editor') self.show() def __init__(self, img, actualSlice=0, seeds=None, contours=None, mode='seed', modeFun=None, voxelSize=[1,1,1]): """ Initiate Editor Parameters ---------- img : array DICOM data matrix. actualSlice : int Index of actual slice. seeds : array Seeds, user defined regions of interest. contours : array Computed segmentation. mode : str Editor modes: 'seed' - seed editor 'crop' - manual crop 'draw' - drawing modeFun : fun Mode function invoked by user button. voxelSize : tuple of float voxel size [mm] """ QDialog.__init__(self) self.mode = mode self.mode_fun = modeFun # self.datapath = datapath self.actual_view = 'axial' self.act_transposition = VIEW_TABLE[self.actual_view] self.last_view_position = {} for ii in VIEW_TABLE.iterkeys(): self.last_view_position[ii] = img.shape[VIEW_TABLE[ii][-1]] - 1 self.img = img self.img_aview = self.img.transpose(self.act_transposition) self.actual_slice = self.img_aview.shape[-1] - actualSlice - 1 self.last_view_position[self.actual_view] = self.actual_slice # set contours self.contours = contours if self.contours is None: self.contours_aview = None else: self.contours_aview = self.contours.transpose(self.act_transposition) self.voxel_size = np.array(voxelSize) self.voxel_scale = self.voxel_size / float(np.min(self.voxel_size)) self.voxel_volume = np.prod(voxelSize) # set seeds if seeds is None: self.seeds = np.zeros(self.img.shape, np.int8) else: self.seeds = seeds self.seeds_aview = self.seeds.transpose(self.act_transposition) self.seeds_modified = False self.initUI(self.img_aview.shape, self.voxel_scale[np.array(self.act_transposition)], 600, mode) if mode == 'draw': self.seeds_orig = self.seeds.copy() self.slice_box.setEraseFun(self.eraseVolume) # set view window values C/W lb = np.min(img) ub = np.max(img) dul = ub - lb self.cw_range = {'c': [lb, ub], 'w': [1, dul]} self.slider_cw['c'].setRange(lb, ub) self.slider_cw['w'].setRange(1, dul) self.changeC(lb + dul / 2) self.changeW(dul) self.offset = np.zeros((3,), dtype=np.int16) def change_seed_class(self, id): self.current_class = id self.slice_box.seed_mark = self.current_class # print 'Current seed class changed to ', id, '.' def showStatus(self, msg): self.status_bar.showMessage(QString(msg)) QApplication.processEvents() def init_draw_mask(self, draw_mask, grid): mask_points = [] mask_iconlabel = [] for mask, label in draw_mask: w, h = mask.shape xx, yy = mask.nonzero() mask_points.append((xx - w/2, yy - h/2)) img = QImage(w, h, QImage.Format_ARGB32) img.fill(qRgba(255, 255, 255, 0)) for ii in range(xx.shape[0]): img.setPixel(xx[ii], yy[ii], qRgba(0, 0, 0, 255)) img = img.scaled(QSize(w * grid[0], h * grid[1])) icon = QIcon(QPixmap.fromImage(img)) mask_iconlabel.append((icon, label)) return mask_points, mask_iconlabel def saveSliceSeeds(self): aux = self.slice_box.getSliceSeeds() if aux is not None: self.seeds_aview[...,self.actual_slice] = aux self.seeds_modified = True else: self.seeds_modified = False def updateCropBounds(self): crp = self.getCropBounds() if crp is not None: _, cri = crp self.contours = np.zeros(self.img.shape, np.int8) self.contours[cri].fill(1) self.contours_aview = self.contours.transpose(self.act_transposition) def focusSliceSlider(self): self.slider.setFocus(True) def sliderSelectSlice(self, value): self.selectSlice(self.n_slices - value) def scrollSlices(self, inc): if abs(inc) > 0: new = self.actual_slice + inc self.selectSlice(new) def selectSlice(self, value, force=False): if not(self.allow_select_slice): return if (value < 0) or (value >= self.n_slices): return if (value != self.actual_slice) or force: self.saveSliceSeeds() if self.seeds_modified and (self.mode == 'crop'): self.updateCropBounds() if self.contours is None: contours = None else: contours = self.contours_aview[...,value] slider_val = self.n_slices - value self.slider.setValue(slider_val) self.slider.label.setText('Slice: %d / %d' % (slider_val, self.n_slices)) self.slice_box.setSlice(self.img_aview[...,value], self.seeds_aview[...,value], contours) self.actual_slice = value def getSeeds(self): return self.seeds def getImg(self): return self.img def getOffset(self): return self.offset * self.voxel_size def getSeedsVal(self, label): return self.img[self.seeds==label] def getContours(self): return self.contours def setContours(self, contours): self.contours = contours self.contours_aview = self.contours.transpose(self.act_transposition) self.selectSlice(self.actual_slice) def changeCW(self, value, key): rg = self.cw_range[key] if (value < rg[0]) or (value > rg[1]): return if (value != self.slice_box.getCW()[key]): self.slider_cw[key].setValue(value) self.slider_cw[key].label.setText('%s: %d' % (key.upper(), value)) self.slice_box.setCW(value, key) self.slice_box.updateSliceCW(self.img_aview[...,self.actual_slice]) def changeC(self, value): self.changeCW(value, 'c') def changeW(self, value): self.changeCW(value, 'w') def setView(self, value): self.last_view_position[self.actual_view] = self.actual_slice # save seeds self.saveSliceSeeds() if self.seeds_modified and (self.mode == 'crop'): self.updateCropBounds(self.seeds_aview[...,self.actual_slice]) key = str(value) self.actual_view = key self.actual_slice = self.last_view_position[key] self.act_transposition = VIEW_TABLE[key] self.img_aview = self.img.transpose(self.act_transposition) self.seeds_aview = self.seeds.transpose(self.act_transposition) if self.contours is not None: self.contours_aview = self.contours.transpose(self.act_transposition) contours = self.contours_aview[...,self.actual_slice] else: contours = None vscale = self.voxel_scale[np.array(self.act_transposition)] height = self.slice_box.height() grid = height / float(self.img_aview.shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box.resizeSlice(new_slice_size=self.img_aview.shape[:-1], new_grid=mgrid) self.slice_box.setSlice(self.img_aview[...,self.actual_slice], self.seeds_aview[...,self.actual_slice], contours) self.allow_select_slice = False self.n_slices = self.img_aview.shape[2] slider_val = self.n_slices - self.actual_slice self.slider.setValue(slider_val) self.slider.setRange(1, self.n_slices) self.allow_select_slice = True self.slider.label.setText('Slice: %d / %d' % (slider_val, self.n_slices)) self.view_label.setText('View size: %d x %d' % self.img_aview.shape[:-1]) def changeMask(self, val): self.slice_box.setMaskPoints(self.mask_points_tab[val]) def changeContourMode(self, val): self.slice_box.contour_mode = str(val) self.slice_box.updateSlice() def changeEraseMode(self, val): self.slice_box.erase_mode = str(val) def eraseVolume(self, pos, mode): self.showStatus("Processing...") xyz = pos + (self.actual_slice,) p = tuple(np.array(xyz)[np.array(self.act_transposition)]) if self.seeds[p] > 0: if mode == 'inside': erase_reg(self.seeds, p, val=0) elif mode == 'outside': erase_reg(self.seeds, p, val=-1) idxs = np.where(self.seeds < 0) self.seeds.fill(0) self.seeds[idxs] = 1 if self.contours is None: contours = None else: contours = self.contours_aview[...,self.actual_slice] self.slice_box.setSlice(self.img_aview[...,self.actual_slice], self.seeds_aview[...,self.actual_slice], contours) self.showStatus("Done") def cropUpdate(self, img): for ii in VIEW_TABLE.iterkeys(): self.last_view_position[ii] = 0 self.actual_slice = 0 self.img = img self.img_aview = self.img.transpose(self.act_transposition) self.contours = None self.contours_aview = None self.seeds = np.zeros(self.img.shape, np.int8) self.seeds_aview = self.seeds.transpose(self.act_transposition) self.seeds_modified = False vscale = self.voxel_scale[np.array(self.act_transposition)] height = self.slice_box.height() grid = height / float(self.img_aview.shape[1] * vscale[1]) mgrid = (grid * vscale[0], grid * vscale[1]) self.slice_box.resizeSlice(new_slice_size=self.img_aview.shape[:-1], new_grid=mgrid) self.slice_box.setSlice(self.img_aview[...,self.actual_slice], self.seeds_aview[...,self.actual_slice], None) self.allow_select_slice = False self.n_slices = self.img_aview.shape[2] self.slider.setValue(self.actual_slice + 1) self.slider.setRange(1, self.n_slices) self.allow_select_slice = True self.slider.label.setText('Slice: %d / %d' % (self.actual_slice + 1, self.n_slices)) self.view_label.setText('View size: %d x %d' % self.img_aview.shape[:-1]) def getCropBounds(self): nzs = self.seeds.nonzero() cri = [] flag = True for ii in range(3): if nzs[ii].shape[0] == 0: flag = False break smin, smax = np.min(nzs[ii]), np.max(nzs[ii]) if smin == smax: flag = False break cri.append((smin, smax)) if flag: cri = np.array(cri) out = [] offset = [] for jj, ii in enumerate(cri): out.append(slice(ii[0], ii[1] + 1)) offset.append(ii[0]) return np.array(offset), tuple(out) else: return None def crop(self): self.showStatus("Processing...") crp = self.getCropBounds() if crp is not None: offset, cri = crp crop = self.img[cri] self.img = np.ascontiguousarray(crop) self.offset += offset self.showStatus('Done') else: self.showStatus('Region not selected!') self.cropUpdate(self.img) def recalculate(self, event): self.saveSliceSeeds() if np.abs(np.min(self.seeds) - np.max(self.seeds)) < 2: self.showStatus('At least two regions must be marked!') return self.showStatus("Processing...") # idx = 3 # s = random_walker(self.img[idx,:,:], self.seeds[idx,:,:])#, mode='cg_mg') # plt.figure() # plt.imshow(mark_boundaries(self.img[idx,:,:], s)) # plt.show() # self.segmentation = np.zeros(self.img.shape) # self.segmentation[idx,:,:] = s self.segmentation = random_walker(self.img, self.seeds, mode='cg_mg') self.setContours(self.segmentation - 1) self.selectSlice(self.actual_slice) # self.updateVolume() self.showStatus("Done") def deleteSliceSeeds(self, event): self.seeds_aview[...,self.actual_slice] = 0 self.slice_box.setSlice(seeds=self.seeds_aview[...,self.actual_slice]) self.slice_box.updateSlice() def resetSliceDraw(self, event): seeds_orig_aview = self.seeds_orig.transpose(self.act_transposition) self.seeds_aview[...,self.actual_slice] = seeds_orig_aview[...,self.actual_slice] self.slice_box.setSlice(seeds=self.seeds_aview[...,self.actual_slice]) self.slice_box.updateSlice() def quit(self, event): self.close() # def save(self, event): # odp = os.path.expanduser("~/lisa_data") # if not op.exists(odp): # os.makedirs(odp) # # data = self.export() # # data['version'] = self.version # # data['experiment_caption'] = self.experiment_caption # # data['lisa_operator_identifier'] = self.lisa_operator_identifier # pth, filename = op.split(op.normpath(self.datapath)) # # filename += "-" + self.experiment_caption # filepath = 'org-' + filename + '.pklz' # filepath = op.join(odp, filepath) # filepath = misc.suggest_filename(filepath) # misc.obj_to_file(data, filepath, filetype='pklz') # # filepath = 'organ_last.pklz' # filepath = op.join(odp, filepath) # misc.obj_to_file(data, filepath, filetype='pklz') # def export(self): # slab = {} # slab['none'] = 0 # slab['liver'] = 1 # slab['lesions'] = 6 # slab.update(self.slab) # # data = {} # data['version'] = (1, 0, 1) # data['data3d'] = self.img # # data['crinfo'] = self.crinfo # data['segmentation'] = self.segmentation # data['slab'] = slab # # data['voxelsize_mm'] = self.voxelsize_mm # # data['orig_shape'] = self.orig_shape # # data['processing_time'] = self.processing_time # return data def updateVolume(self): text = 'Volume [mm3]:\n unknown' if self.voxel_volume is not None: if self.mode == 'draw': vd = self.seeds else: vd = self.contours if vd is not None: nzs = vd.nonzero() nn = nzs[0].shape[0] text = 'Volume [mm3]:\n %.2e' % (nn * self.voxel_volume) self.volume_label.setText(text) def getROI(self): crp = self.getCropBounds() if crp is not None: _, cri = crp else: cri = [] for jj, ii in enumerate(self.img.shape): off = self.offset[jj] cri.append(slice(off, off + ii)) return cri
def showMessage(self, message, timeout): self._widgetStatus.hide() self._replaceWidget.setVisible(False) self.show() QStatusBar.showMessage(self, message, timeout)
class OWWidget(QDialog, metaclass=WidgetMetaClass): # Global widget count widget_id = 0 # Widget description name = None id = None category = None version = None description = None long_description = None icon = "icons/Unknown.png" priority = sys.maxsize author = None author_email = None maintainer = None maintainer_email = None help = None help_ref = None url = None keywords = [] background = None replaces = None inputs = [] outputs = [] # Default widget layout settings want_basic_layout = True want_main_area = True want_control_area = True want_graph = False show_save_graph = True want_status_bar = False no_report = False save_position = True resizing_enabled = True widgetStateChanged = Signal(str, int, str) blockingStateChanged = Signal(bool) progressBarValueChanged = Signal(float) processingStateChanged = Signal(int) settingsHandler = None """:type: SettingsHandler""" savedWidgetGeometry = settings.Setting(None) def __new__(cls, parent=None, *args, **kwargs): self = super().__new__(cls, None, cls.get_flags()) QDialog.__init__(self, None, self.get_flags()) stored_settings = kwargs.get('stored_settings', None) if self.settingsHandler: self.settingsHandler.initialize(self, stored_settings) self.signalManager = kwargs.get('signal_manager', None) self.__env = _asmappingproxy(kwargs.get("env", {})) setattr(self, gui.CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self)) self.__reportData = None OWWidget.widget_id += 1 self.widget_id = OWWidget.widget_id if self.name: self.setCaption(self.name) self.setFocusPolicy(Qt.StrongFocus) self.startTime = time.time() # used in progressbar self.widgetState = {"Info": {}, "Warning": {}, "Error": {}} self.__blocking = False # flag indicating if the widget's position was already restored self.__was_restored = False self.__progressBarValue = -1 self.__progressState = 0 self.__statusMessage = "" if self.want_basic_layout: self.insertLayout() return self def __init__(self, *args, **kwargs): """QDialog __init__ was already called in __new__, please do not call it here.""" @classmethod def get_flags(cls): return (Qt.Window if cls.resizing_enabled else Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) # noinspection PyAttributeOutsideInit def insertLayout(self): def createPixmapWidget(self, parent, iconName): w = QLabel(parent) parent.layout().addWidget(w) w.setFixedSize(16, 16) w.hide() if os.path.exists(iconName): w.setPixmap(QPixmap(iconName)) return w self.setLayout(QVBoxLayout()) self.layout().setMargin(2) self.warning_bar = gui.widgetBox(self, orientation="horizontal", margin=0, spacing=0) self.warning_icon = gui.widgetLabel(self.warning_bar, "") self.warning_label = gui.widgetLabel(self.warning_bar, "") self.warning_label.setStyleSheet("padding-top: 5px") self.warning_bar.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum) gui.rubber(self.warning_bar) self.warning_bar.setVisible(False) self.topWidgetPart = gui.widgetBox(self, orientation="horizontal", margin=0) self.leftWidgetPart = gui.widgetBox(self.topWidgetPart, orientation="vertical", margin=0) if self.want_main_area: self.leftWidgetPart.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) self.leftWidgetPart.updateGeometry() self.mainArea = gui.widgetBox(self.topWidgetPart, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding), margin=0) self.mainArea.layout().setMargin(4) self.mainArea.updateGeometry() if self.want_control_area: self.controlArea = gui.widgetBox(self.leftWidgetPart, orientation="vertical", margin=4) if self.want_graph and self.show_save_graph: graphButtonBackground = gui.widgetBox(self.leftWidgetPart, orientation="horizontal", margin=4) self.graphButton = gui.button(graphButtonBackground, self, "&Save Graph") self.graphButton.setAutoDefault(0) if self.want_status_bar: self.widgetStatusArea = QFrame(self) self.statusBarIconArea = QFrame(self) self.widgetStatusBar = QStatusBar(self) self.layout().addWidget(self.widgetStatusArea) self.widgetStatusArea.setLayout(QHBoxLayout(self.widgetStatusArea)) self.widgetStatusArea.layout().addWidget(self.statusBarIconArea) self.widgetStatusArea.layout().addWidget(self.widgetStatusBar) self.widgetStatusArea.layout().setMargin(0) self.widgetStatusArea.setFrameShape(QFrame.StyledPanel) self.statusBarIconArea.setLayout(QHBoxLayout()) self.widgetStatusBar.setSizeGripEnabled(0) self.statusBarIconArea.hide() self._warningWidget = createPixmapWidget( self.statusBarIconArea, gui.resource_filename("icons/triangle-orange.png")) self._errorWidget = createPixmapWidget( self.statusBarIconArea, gui.resource_filename("icons/triangle-red.png")) if not self.resizing_enabled: self.layout().setSizeConstraint(QVBoxLayout.SetFixedSize) def updateStatusBarState(self): if not hasattr(self, "widgetStatusArea"): return if self.widgetState["Warning"] or self.widgetState["Error"]: self.widgetStatusArea.show() else: self.widgetStatusArea.hide() def setStatusBarText(self, text, timeout=5000): if hasattr(self, "widgetStatusBar"): self.widgetStatusBar.showMessage(" " + text, timeout) # TODO add! def prepareDataReport(self, data): pass def __restoreWidgetGeometry(self): restored = False if self.save_position: geometry = self.savedWidgetGeometry if geometry is not None: restored = self.restoreGeometry(QByteArray(geometry)) if restored and not self.windowState() & \ (Qt.WindowMaximized | Qt.WindowFullScreen): space = qApp.desktop().availableGeometry(self) frame, geometry = self.frameGeometry(), self.geometry() #Fix the widget size to fit inside the available space width = space.width() - (frame.width() - geometry.width()) width = min(width, geometry.width()) height = space.height() - (frame.height() - geometry.height()) height = min(height, geometry.height()) self.resize(width, height) # Move the widget to the center of available space if it is # currently outside it if not space.contains(self.frameGeometry()): x = max(0, space.width() / 2 - width / 2) y = max(0, space.height() / 2 - height / 2) self.move(x, y) return restored def __updateSavedGeometry(self): if self.__was_restored and self.isVisible(): # Update the saved geometry only between explicit show/hide # events (i.e. changes initiated by the user not by Qt's default # window management). self.savedWidgetGeometry = self.saveGeometry() # when widget is resized, save the new width and height def resizeEvent(self, ev): QDialog.resizeEvent(self, ev) # Don't store geometry if the widget is not visible # (the widget receives a resizeEvent (with the default sizeHint) # before first showEvent and we must not overwrite the the # savedGeometry with it) if self.save_position and self.isVisible(): self.__updateSavedGeometry() def moveEvent(self, ev): QDialog.moveEvent(self, ev) if self.save_position and self.isVisible(): self.__updateSavedGeometry() # set widget state to hidden def hideEvent(self, ev): if self.save_position: self.__updateSavedGeometry() QDialog.hideEvent(self, ev) def closeEvent(self, ev): if self.save_position and self.isVisible(): self.__updateSavedGeometry() QDialog.closeEvent(self, ev) def showEvent(self, ev): QDialog.showEvent(self, ev) if self.save_position and not self.__was_restored: # Restore saved geometry on show self.__restoreWidgetGeometry() self.__was_restored = True def wheelEvent(self, event): """ Silently accept the wheel event. This is to ensure combo boxes and other controls that have focus don't receive this event unless the cursor is over them. """ event.accept() def setCaption(self, caption): # we have to save caption title in case progressbar will change it self.captionTitle = str(caption) self.setWindowTitle(caption) # put this widget on top of all windows def reshow(self): self.show() self.raise_() self.activateWindow() def send(self, signalName, value, id=None): if not any(s.name == signalName for s in self.outputs): raise ValueError('{} is not a valid output signal for widget {}'.format( signalName, self.name)) if self.signalManager is not None: self.signalManager.send(self, signalName, value, id) def __setattr__(self, name, value): """Set value to members of this instance or any of its members. If member is used in a gui control, notify the control about the change. name: name of the member, dot is used for nesting ("graph.point.size"). value: value to set to the member. """ names = name.rsplit(".") field_name = names.pop() obj = reduce(lambda o, n: getattr(o, n, None), names, self) if obj is None: raise AttributeError("Cannot set '{}' to {} ".format(name, value)) if obj is self: super().__setattr__(field_name, value) else: setattr(obj, field_name, value) notify_changed(obj, field_name, value) if self.settingsHandler: self.settingsHandler.fast_save(self, name, value) def openContext(self, *a): self.settingsHandler.open_context(self, *a) def closeContext(self): self.settingsHandler.close_context(self) def retrieveSpecificSettings(self): pass def storeSpecificSettings(self): pass def saveSettings(self): self.settingsHandler.update_defaults(self) def onDeleteWidget(self): """ Invoked by the canvas to notify the widget it has been deleted from the workflow. If possible, subclasses should gracefully cancel any currently executing tasks. """ pass def handleNewSignals(self): """ Invoked by the workflow signal propagation manager after all signals handlers have been called. Reimplement this method in order to coalesce updates from multiple updated inputs. """ pass # ############################################ # PROGRESS BAR FUNCTIONS def progressBarInit(self, processEvents=QEventLoop.AllEvents): """ Initialize the widget's progress (i.e show and set progress to 0%). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") if self.__progressState != 1: self.__progressState = 1 self.processingStateChanged.emit(1) self.progressBarSet(0, processEvents) def progressBarSet(self, value, processEvents=QEventLoop.AllEvents): """ Set the current progress bar to `value`. .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param float value: Progress value :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ old = self.__progressBarValue self.__progressBarValue = value if value > 0: if self.__progressState != 1: warnings.warn("progressBarSet() called without a " "preceding progressBarInit()", stacklevel=2) self.__progressState = 1 self.processingStateChanged.emit(1) usedTime = max(1, time.time() - self.startTime) totalTime = (100.0 * usedTime) / float(value) remainingTime = max(0, totalTime - usedTime) h = int(remainingTime / 3600) min = int((remainingTime - h * 3600) / 60) sec = int(remainingTime - h * 3600 - min * 60) if h > 0: text = "%(h)d:%(min)02d:%(sec)02d" % vars() else: text = "%(min)d:%(sec)02d" % vars() self.setWindowTitle(self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars()) else: self.setWindowTitle(self.captionTitle + " (0% complete)") if old != value: self.progressBarValueChanged.emit(value) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) def progressBarValue(self): return self.__progressBarValue progressBarValue = pyqtProperty(float, fset=progressBarSet, fget=progressBarValue) processingState = pyqtProperty(int, fget=lambda self: self.__progressState) def progressBarAdvance(self, value, processEvents=QEventLoop.AllEvents): self.progressBarSet(self.progressBarValue + value, processEvents) def progressBarFinished(self, processEvents=QEventLoop.AllEvents): """ Stop the widget's progress (i.e hide the progress bar). .. note:: This method will by default call `QApplication.processEvents` with `processEvents`. To suppress this behavior pass ``processEvents=None``. :param processEvents: Process events flag :type processEvents: `QEventLoop.ProcessEventsFlags` or `None` """ self.setWindowTitle(self.captionTitle) if self.__progressState != 0: self.__progressState = 0 self.processingStateChanged.emit(0) if processEvents is not None and processEvents is not False: qApp.processEvents(processEvents) #: Widget's status message has changed. statusMessageChanged = Signal(str) def setStatusMessage(self, text): if self.__statusMessage != text: self.__statusMessage = text self.statusMessageChanged.emit(text) def statusMessage(self): return self.__statusMessage def keyPressEvent(self, e): if (int(e.modifiers()), e.key()) in OWWidget.defaultKeyActions: OWWidget.defaultKeyActions[int(e.modifiers()), e.key()](self) else: QDialog.keyPressEvent(self, e) def information(self, id=0, text=None): self.setState("Info", id, text) def warning(self, id=0, text=""): self.setState("Warning", id, text) def error(self, id=0, text=""): self.setState("Error", id, text) def setState(self, state_type, id, text): changed = 0 if type(id) == list: for val in id: if val in self.widgetState[state_type]: self.widgetState[state_type].pop(val) changed = 1 else: if type(id) == str: text = id id = 0 if not text: if id in self.widgetState[state_type]: self.widgetState[state_type].pop(id) changed = 1 else: self.widgetState[state_type][id] = text changed = 1 if changed: if type(id) == list: for i in id: self.widgetStateChanged.emit(state_type, i, "") else: self.widgetStateChanged.emit(state_type, id, text or "") tooltip_lines = [] highest_type = None for a_type in ("Error", "Warning", "Info"): msgs_for_ids = self.widgetState.get(a_type) if not msgs_for_ids: continue msgs_for_ids = list(msgs_for_ids.values()) if not msgs_for_ids: continue tooltip_lines += msgs_for_ids if highest_type is None: highest_type = a_type if highest_type is None: self.set_warning_bar(None) elif len(tooltip_lines) == 1: msg = tooltip_lines[0] if "\n" in msg: self.set_warning_bar( highest_type, msg[:msg.index("\n")] + " (...)", msg) else: self.set_warning_bar( highest_type, tooltip_lines[0], tooltip_lines[0]) else: self.set_warning_bar( highest_type, "{} problems during execution".format(len(tooltip_lines)), "\n".join(tooltip_lines)) return changed def set_warning_bar(self, state_type, text=None, tooltip=None): colors = {"Error": ("#ffc6c6", "black", QStyle.SP_MessageBoxCritical), "Warning": ("#ffffc9", "black", QStyle.SP_MessageBoxWarning), "Info": ("#ceceff", "black", QStyle.SP_MessageBoxInformation)} current_height = self.height() if state_type is None: if not self.warning_bar.isHidden(): new_height = current_height - self.warning_bar.height() self.warning_bar.setVisible(False) self.resize(self.width(), new_height) return background, foreground, icon = colors[state_type] style = QApplication.instance().style() self.warning_icon.setPixmap(style.standardIcon(icon).pixmap(14, 14)) self.warning_bar.setStyleSheet( "background-color: {}; color: {};" "padding: 3px; padding-left: 6px; vertical-align: center". format(background, foreground)) self.warning_label.setText(text) self.warning_bar.setToolTip(tooltip) if self.warning_bar.isHidden(): self.warning_bar.setVisible(True) new_height = current_height + self.warning_bar.height() self.resize(self.width(), new_height) def widgetStateToHtml(self, info=True, warning=True, error=True): iconpaths = { "Info": gui.resource_filename("icons/information.png"), "Warning": gui.resource_filename("icons/warning.png"), "Error": gui.resource_filename("icons/error.png") } items = [] for show, what in [(info, "Info"), (warning, "Warning"), (error, "Error")]: if show and self.widgetState[what]: items.append('<img src="%s" style="float: left;"> %s' % (iconpaths[what], "\n".join(self.widgetState[what].values()))) return "<br>".join(items) @classmethod def getWidgetStateIcons(cls): if not hasattr(cls, "_cached__widget_state_icons"): info = QPixmap(gui.resource_filename("icons/information.png")) warning = QPixmap(gui.resource_filename("icons/warning.png")) error = QPixmap(gui.resource_filename("icons/error.png")) cls._cached__widget_state_icons = \ {"Info": info, "Warning": warning, "Error": error} return cls._cached__widget_state_icons defaultKeyActions = {} if sys.platform == "darwin": defaultKeyActions = { (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(), (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible())} def setBlocking(self, state=True): """ Set blocking flag for this widget. While this flag is set this widget and all its descendants will not receive any new signals from the workflow signal manager. This is useful for instance if the widget does it's work in a separate thread or schedules processing from the event queue. In this case it can set the blocking flag in it's processNewSignals method schedule the task and return immediately. After the task has completed the widget can clear the flag and send the updated outputs. .. note:: Failure to clear this flag will block dependent nodes forever. """ if self.__blocking != state: self.__blocking = state self.blockingStateChanged.emit(state) def isBlocking(self): """ Is this widget blocking signal processing. """ return self.__blocking def resetSettings(self): self.settingsHandler.reset_settings(self) def workflowEnv(self): """ Return (a view to) the workflow runtime environment. Returns ------- env : types.MappingProxyType """ return self.__env def workflowEnvChanged(self, key, value, oldvalue): """ A workflow environment variable `key` has changed to value. Called by the canvas framework to notify widget of a change in the workflow runtime environment. The default implementation does nothing. """ pass
class OWWidget(QDialog, metaclass=WidgetMetaClass): # Global widget count widget_id = 0 # Widget description name = None id = None category = None version = None description = None long_description = None icon = "icons/Unknown.png" priority = sys.maxsize author = None author_email = None maintainer = None maintainer_email = None help = None help_ref = None url = None keywords = [] background = None replaces = None inputs = [] outputs = [] # Default widget layout settings want_basic_layout = True want_main_area = True want_control_area = True want_graph = False show_save_graph = True want_status_bar = False no_report = False save_position = False resizing_enabled = True widgetStateChanged = Signal(str, int, str) blockingStateChanged = Signal(bool) asyncCallsStateChange = Signal() progressBarValueChanged = Signal(float) processingStateChanged = Signal(int) settingsHandler = None """:type: SettingsHandler""" def __new__(cls, parent=None, *args, **kwargs): self = super().__new__(cls, None, cls.get_flags()) QDialog.__init__(self, None, self.get_flags()) # 'current_context' MUST be the first thing assigned to a widget self.current_context = settings.Context() if self.settingsHandler: stored_settings = kwargs.get("stored_settings", None) self.settingsHandler.initialize(self, stored_settings) # number of control signals that are currently being processed # needed by signalWrapper to know when everything was sent self.needProcessing = 0 # used by signalManager self.signalManager = kwargs.get("signal_manager", None) setattr(self, gui.CONTROLLED_ATTRIBUTES, ControlledAttributesDict(self)) self._guiElements = [] # used for automatic widget debugging self.__reportData = None # TODO: position used to be saved like this. Reimplement. # if save_position: # self.settingsList = getattr(self, "settingsList", []) + \ # ["widgetShown", "savedWidgetGeometry"] OWWidget.widget_id += 1 self.widget_id = OWWidget.widget_id # TODO: kill me self.__dict__.update(environ.directories) if self.name: self.setCaption(self.name.replace("&", "")) self.setFocusPolicy(Qt.StrongFocus) self.wrappers = [] # stored wrappers for widget events self.linksIn = {} # signalName : (dirty, widFrom, handler, signalData) self.linksOut = {} # signalName: (signalData, id) self.connections = {} # keys are (control, signal) and values are # wrapper instances. Used in connect/disconnect self.callbackDeposit = [] self.startTime = time.time() # used in progressbar self.widgetState = {"Info": {}, "Warning": {}, "Error": {}} self.__blocking = False if self.want_basic_layout: self.insertLayout() return self def __init__(self, *args, **kwargs): """QDialog __init__ was already called in __new__, please do not call it here.""" @classmethod def get_flags(cls): return Qt.Window if cls.resizing_enabled else Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint def insertLayout(self): def createPixmapWidget(self, parent, iconName): w = QLabel(parent) parent.layout().addWidget(w) w.setFixedSize(16, 16) w.hide() if os.path.exists(iconName): w.setPixmap(QPixmap(iconName)) return w self.setLayout(QVBoxLayout()) self.layout().setMargin(2) self.topWidgetPart = gui.widgetBox(self, orientation="horizontal", margin=0) self.leftWidgetPart = gui.widgetBox(self.topWidgetPart, orientation="vertical", margin=0) if self.want_main_area: self.leftWidgetPart.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) self.leftWidgetPart.updateGeometry() self.mainArea = gui.widgetBox( self.topWidgetPart, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding), margin=0, ) self.mainArea.layout().setMargin(4) self.mainArea.updateGeometry() if self.want_control_area: self.controlArea = gui.widgetBox(self.leftWidgetPart, orientation="vertical", margin=4) if self.want_graph and self.show_save_graph: graphButtonBackground = gui.widgetBox(self.leftWidgetPart, orientation="horizontal", margin=4) self.graphButton = gui.button(graphButtonBackground, self, "&Save Graph") self.graphButton.setAutoDefault(0) if self.want_status_bar: self.widgetStatusArea = QFrame(self) self.statusBarIconArea = QFrame(self) self.widgetStatusBar = QStatusBar(self) self.layout().addWidget(self.widgetStatusArea) self.widgetStatusArea.setLayout(QHBoxLayout(self.widgetStatusArea)) self.widgetStatusArea.layout().addWidget(self.statusBarIconArea) self.widgetStatusArea.layout().addWidget(self.widgetStatusBar) self.widgetStatusArea.layout().setMargin(0) self.widgetStatusArea.setFrameShape(QFrame.StyledPanel) self.statusBarIconArea.setLayout(QHBoxLayout()) self.widgetStatusBar.setSizeGripEnabled(0) self.statusBarIconArea.hide() self._warningWidget = createPixmapWidget( self.statusBarIconArea, os.path.join(self.widgetDir, "icons/triangle-orange.png") ) self._errorWidget = createPixmapWidget( self.statusBarIconArea, os.path.join(self.widgetDir + "icons/triangle-red.png") ) # status bar handler functions def setState(self, stateType, id, text): stateChanged = super().setState(stateType, id, text) if not stateChanged or not hasattr(self, "widgetStatusArea"): return iconsShown = 0 warnings = [("Warning", self._warningWidget, self._owWarning), ("Error", self._errorWidget, self._owError)] for state, widget, use in warnings: if not widget: continue if use and self.widgetState[state]: widget.setToolTip("\n".join(self.widgetState[state].values())) widget.show() iconsShown = 1 else: widget.setToolTip("") widget.hide() if iconsShown: self.statusBarIconArea.show() else: self.statusBarIconArea.hide() if (stateType == "Warning" and self._owWarning) or (stateType == "Error" and self._owError): if text: self.setStatusBarText(stateType + ": " + text) else: self.setStatusBarText("") self.updateStatusBarState() def updateWidgetStateInfo(self, stateType, id, text): html = self.widgetStateToHtml(self._owInfo, self._owWarning, self._owError) if html: self.widgetStateInfoBox.show() self.widgetStateInfo.setText(html) self.widgetStateInfo.setToolTip(html) else: if not self.widgetStateInfoBox.isVisible(): dHeight = -self.widgetStateInfoBox.height() else: dHeight = 0 self.widgetStateInfoBox.hide() self.widgetStateInfo.setText("") self.widgetStateInfo.setToolTip("") width, height = self.width(), self.height() + dHeight self.resize(width, height) def updateStatusBarState(self): if not hasattr(self, "widgetStatusArea"): return if self.widgetState["Warning"] or self.widgetState["Error"]: self.widgetStatusArea.show() else: self.widgetStatusArea.hide() def setStatusBarText(self, text, timeout=5000): if hasattr(self, "widgetStatusBar"): self.widgetStatusBar.showMessage(" " + text, timeout) # TODO add! def prepareDataReport(self, data): pass def getIconNames(self, iconName): # if canvas sent us a prepared list of valid names, just return those if type(iconName) == list: return iconName names = [] name, ext = os.path.splitext(iconName) for num in [16, 32, 42, 60]: names.append("%s_%d%s" % (name, num, ext)) fullPaths = [] module_dir = os.path.dirname(sys.modules[self.__module__].__file__) for paths in [(self.widgetDir, name), (self.widgetDir, "icons", name), (module_dir, "icons", name)]: for name in names + [iconName]: fname = os.path.join(*paths) if os.path.exists(fname): fullPaths.append(fname) if fullPaths != []: break if len(fullPaths) > 1 and fullPaths[-1].endswith(iconName): # if we have the new icons we can remove the default icon fullPaths.pop() return fullPaths def setWidgetIcon(self, iconName): iconNames = self.getIconNames(iconName) icon = QIcon() for name in iconNames: pix = QPixmap(name) icon.addPixmap(pix) self.setWindowIcon(icon) # ############################################## def isDataWithClass(self, data, wantedVarType=None, checkMissing=False): self.error([1234, 1235, 1236]) if not data: return 0 if not data.domain.classVar: self.error(1234, "A data set with a class attribute is required.") return 0 if wantedVarType and data.domain.classVar.varType != wantedVarType: self.error(1235, "Unable to handle %s class." % str(data.domain.class_var.var_type).lower()) return 0 if checkMissing and not orange.Preprocessor_dropMissingClasses(data): self.error(1236, "Unable to handle data set with no known classes") return 0 return 1 # call processEvents(), but first remember position and size of widget in # case one of the events would be move or resize # call this function if needed in __init__ of the widget def safeProcessEvents(self): keys = ["widgetShown"] vals = [(key, getattr(self, key, None)) for key in keys] qApp.processEvents() for (key, val) in vals: if val != None: setattr(self, key, val) # this function is called at the end of the widget's __init__ when the # widgets is saving its position and size parameters def restoreWidgetPosition(self): if self.save_position: geometry = getattr(self, "savedWidgetGeometry", None) restored = False if geometry is not None: restored = self.restoreGeometry(QByteArray(geometry)) if restored: space = qApp.desktop().availableGeometry(self) frame, geometry = self.frameGeometry(), self.geometry() # Fix the widget size to fit inside the available space width = space.width() - (frame.width() - geometry.width()) width = min(width, geometry.width()) height = space.height() - (frame.height() - geometry.height()) height = min(height, geometry.height()) self.resize(width, height) # Move the widget to the center of available space if it is # currently outside it if not space.contains(self.frameGeometry()): x = max(0, space.width() / 2 - width / 2) y = max(0, space.height() / 2 - height / 2) self.move(x, y) # this is called in canvas when loading a schema. it opens the widgets # that were shown when saving the schema def restoreWidgetStatus(self): if self.save_position and getattr(self, "widgetShown", None): self.show() # when widget is resized, save new width and height into widgetWidth and # widgetHeight. some widgets can put this two variables into settings and # last widget shape is restored after restart def resizeEvent(self, ev): QDialog.resizeEvent(self, ev) # Don't store geometry if the widget is not visible # (the widget receives the resizeEvent before showEvent and we must not # overwrite the the savedGeometry before then) if self.save_position and self.isVisible(): self.savedWidgetGeometry = str(self.saveGeometry()) # set widget state to hidden def hideEvent(self, ev): if self.save_position: self.widgetShown = 0 self.savedWidgetGeometry = str(self.saveGeometry()) QDialog.hideEvent(self, ev) # set widget state to shown def showEvent(self, ev): QDialog.showEvent(self, ev) if self.save_position: self.widgetShown = 1 self.restoreWidgetPosition() def closeEvent(self, ev): if self.save_position: self.savedWidgetGeometry = str(self.saveGeometry()) QDialog.closeEvent(self, ev) def wheelEvent(self, event): """ Silently accept the wheel event. This is to ensure combo boxes and other controls that have focus don't receive this event unless the cursor is over them. """ event.accept() def setCaption(self, caption): if self.parent != None and isinstance(self.parent, QTabWidget): self.parent.setTabText(self.parent.indexOf(self), caption) else: # we have to save caption title in case progressbar will change it self.captionTitle = str(caption) self.setWindowTitle(caption) # put this widget on top of all windows def reshow(self): self.show() self.raise_() self.activateWindow() def send(self, signalName, value, id=None): if self.signalManager is not None: self.signalManager.send(self, signalName, value, id) def __setattr__(self, name, value): """Set value to members of this instance or any of its members. If member is used in a gui control, notify the control about the change. name: name of the member, dot is used for nesting ("graph.point.size"). value: value to set to the member. """ names = name.rsplit(".") field_name = names.pop() obj = reduce(lambda o, n: getattr(o, n, None), names, self) if obj is None: raise AttributeError("Cannot set '{}' to {} ".format(name, value)) if obj is self: super().__setattr__(field_name, value) else: setattr(obj, field_name, value) notify_changed(obj, field_name, value) if self.settingsHandler: self.settingsHandler.fast_save(self, name, value) def openContext(self, *a): self.settingsHandler.open_context(self, *a) def closeContext(self): if self.current_context is not None: self.settingsHandler.close_context(self) self.current_context = None def retrieveSpecificSettings(self): pass def storeSpecificSettings(self): pass def saveSettings(self): self.settingsHandler.update_defaults(self) # this function is only intended for derived classes to send appropriate # signals when all settings are loaded def activateLoadedSettings(self): pass # reimplemented in other widgets def onDeleteWidget(self): pass def setOptions(self): pass def handleNewSignals(self): # this is called after all new signals have been handled # implement this in your widget if you want to process something only # after you received multiple signals pass # ############################################ # PROGRESS BAR FUNCTIONS def progressBarInit(self): self.progressBarValue = 0 self.startTime = time.time() self.setWindowTitle(self.captionTitle + " (0% complete)") self.processingStateChanged.emit(1) def progressBarSet(self, value): if value > 0: self.__progressBarValue = value usedTime = max(1, time.time() - self.startTime) totalTime = (100.0 * usedTime) / float(value) remainingTime = max(0, totalTime - usedTime) h = int(remainingTime / 3600) min = int((remainingTime - h * 3600) / 60) sec = int(remainingTime - h * 3600 - min * 60) if h > 0: text = "%(h)d:%(min)02d:%(sec)02d" % vars() else: text = "%(min)d:%(sec)02d" % vars() self.setWindowTitle(self.captionTitle + " (%(value).2f%% complete, remaining time: %(text)s)" % vars()) else: self.setWindowTitle(self.captionTitle + " (0% complete)") self.progressBarValueChanged.emit(value) qApp.processEvents() def progressBarValue(self): return self.__progressBarValue progressBarValue = pyqtProperty(float, fset=progressBarSet, fget=progressBarValue) def progressBarAdvance(self, value): self.progressBarSet(self.progressBarValue + value) def progressBarFinished(self): self.setWindowTitle(self.captionTitle) self.processingStateChanged.emit(0) def openWidgetHelp(self): if "widgetInfo" in self.__dict__: # This widget is on a canvas. qApp.canvasDlg.helpWindow.showHelpFor(self.widgetInfo, True) def keyPressEvent(self, e): if e.key() in (Qt.Key_Help, Qt.Key_F1): self.openWidgetHelp() # e.ignore() elif (int(e.modifiers()), e.key()) in OWWidget.defaultKeyActions: OWWidget.defaultKeyActions[int(e.modifiers()), e.key()](self) else: QDialog.keyPressEvent(self, e) def information(self, id=0, text=None): self.setState("Info", id, text) # self.setState("Warning", id, text) def warning(self, id=0, text=""): self.setState("Warning", id, text) def error(self, id=0, text=""): self.setState("Error", id, text) def setState(self, stateType, id, text): changed = 0 if type(id) == list: for val in id: if val in self.widgetState[stateType]: self.widgetState[stateType].pop(val) changed = 1 else: if type(id) == str: text = id id = 0 if not text: if id in self.widgetState[stateType]: self.widgetState[stateType].pop(id) changed = 1 else: self.widgetState[stateType][id] = text changed = 1 if changed: if type(id) == list: for i in id: self.widgetStateChanged.emit(stateType, i, "") else: self.widgetStateChanged.emit(stateType, id, text or "") return changed def widgetStateToHtml(self, info=True, warning=True, error=True): pixmaps = self.getWidgetStateIcons() items = [] iconPath = { "Info": "canvasIcons:information.png", "Warning": "canvasIcons:warning.png", "Error": "canvasIcons:error.png", } for show, what in [(info, "Info"), (warning, "Warning"), (error, "Error")]: if show and self.widgetState[what]: items.append( '<img src="%s" style="float: left;"> %s' % (iconPath[what], "\n".join(self.widgetState[what].values())) ) return "<br>".join(items) @classmethod def getWidgetStateIcons(cls): if not hasattr(cls, "_cached__widget_state_icons"): iconsDir = os.path.join(environ.canvas_install_dir, "icons") QDir.addSearchPath("canvasIcons", os.path.join(environ.canvas_install_dir, "icons/")) info = QPixmap("canvasIcons:information.png") warning = QPixmap("canvasIcons:warning.png") error = QPixmap("canvasIcons:error.png") cls._cached__widget_state_icons = {"Info": info, "Warning": warning, "Error": error} return cls._cached__widget_state_icons defaultKeyActions = {} if sys.platform == "darwin": defaultKeyActions = { (Qt.ControlModifier, Qt.Key_M): lambda self: self.showMaximized if self.isMinimized() else self.showMinimized(), (Qt.ControlModifier, Qt.Key_W): lambda self: self.setVisible(not self.isVisible()), } def setBlocking(self, state=True): """ Set blocking flag for this widget. While this flag is set this widget and all its descendants will not receive any new signals from the signal manager """ if self.__blocking != state: self.__blocking = state self.blockingStateChanged.emit(state) def isBlocking(self): """ Is this widget blocking signal processing. """ return self.__blocking def resetSettings(self): self.settingsHandler.reset_settings(self)
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.__central_widget = CentralWidget(None) self.__board = self.__central_widget.board self.__nodes_view = self.__central_widget.nodes_widget self.__status_bar = QStatusBar() self.setCentralWidget(self.__central_widget) self.setStatusBar(self.__status_bar) self.setMinimumSize(self.__central_widget.size()) self.__board.on_click.connect(self.on_piece_click) self.__nodes_view.node_click.connect(self.on_point_click) self.__central_widget.button_bar.open_click.connect(self.on_open_click) self.__central_widget.button_bar.save_click.connect(self.on_save_click) self.__central_widget.button_bar.pass_click.connect(self.on_pass_click) self.__central_widget.button_bar.resign_click.connect( self.on_resign_click) self.__central_widget.button_bar.count_click.connect( self.on_count_click) self.__status_bar.showMessage("Juego cargado.") def add_piece(self, letter, y, text, color): self.__board.add_piece(letter, y, text, color) def add_square(self, letter, y, color): self.__board.add_square(letter, y, color) def remove_piece(self, letter, y): self.__board.remove_piece(letter, y) def add_point(self, x, y, text, color): self.__nodes_view.add_point(x, y, text, color) def remove_point(self, x, y): self.__nodes_view.remove_point(x, y) def add_line(self, cord_1, cord_2, color): self.__nodes_view.add_line(cord_1, cord_2, color) def remove_line(self, cord_1, cord_2): self.__nodes_view.remove_line(cord_1, cord_2) def set_result(self, points): self.__central_widget.button_bar.set_result(points) def on_piece_click(self, letra, y): pass def on_point_click(self, x, y): pass def on_open_click(self, path): pass def on_save_click(self, path): pass def on_pass_click(self): pass def on_count_click(self): pass def on_resign_click(self): pass def show_message(self, message): self.__status_bar.showMessage(message)
class Visor(QtGui.QDockWidget, FORM_CLASS): """ UI manager for the visor. This is the View component of the MVC pattern. Will manage inputs from the user using a combobox to select which dataset to use, provide a tree view of the elements included in those datasets, and provide the basic configuration options to download maps through WMS and WCS services using a controller and show them in QGIS. """ groupAssignmentLock = RLock() def __init__(self, showEmptyDatasetNodes=False, parent=None): """Constructor.""" super(Visor, self).__init__(parent) self.setupUi(self) self.controller = VisorController.VisorController() self.controller.threddsServerMapObjectRetrieved.connect( self.onNewDatasetsAvailable) self.controller.threddsDataSetUpdated.connect(self.onDataSetUpdated) self.controller.mapImageRetrieved.connect(self.showNewImage) self.controller.standardMessage.connect( self.postInformationMessageToUser) self.controller.errorMessage.connect(self.postCriticalErrorToUser) self.controller.mapInfoRetrieved.connect( self._onMapInfoReceivedFromController) self.controller.batchDownloadFinished.connect(self.createLayerGroup) self.showEmptyDatasetNodes = showEmptyDatasetNodes # TODO: self-explanatory... self.combo_dataset_list.currentIndexChanged.connect( self._onDataSetItemChanged) self.tree_widget.itemClicked.connect(self._onMapTreeWidgetItemClicked) self.tree_widget.itemExpanded.connect( self._onMapTreeWidgetItemExpanded) self.tabWidget.currentChanged.connect(self.runWhenTabChange) self.connect(self.combo_wcs_coverage, SIGNAL("currentIndexChanged(const QString&)"), self._onCoverageSelectorItemChanged) self.connect(self.combo_wms_layer, SIGNAL("currentIndexChanged(const QString&)"), self._onWMSLayerSelectorItemChanged) self.connect(self.combo_wms_style_type, SIGNAL("currentIndexChanged(const QString&)"), self._onWMSStyleTypeSelectorItemChanged) self.connect(self.combo_wms_time, SIGNAL("currentIndexChanged(int)"), self._onWMSFirstTimeChanged) self.connect(self.combo_wcs_time, SIGNAL("currentIndexChanged(int)"), self._onWCSFirstTimeChanged) self.button_req_map.clicked.connect(self._onbuttonReqMapClicked) #self.actionToggleAlwaysOnTop.toggled.connect(self._onAlwaysOnTopPrefsChanged) self.buttonManageServers.clicked.connect( self._onManageServersRequested) self.button_req_animation.clicked.connect(self.toggleAnimationMenu) # We add a status bar to this QDockWidget: self.statusbar = QStatusBar() self.gridLayout.addWidget(self.statusbar) self.datasetInUse = None self.uiAnimation = None self.currentMap = None self.wcsAvailableTimes = [] self.wmsAvailableTimes = [] self.firstRunThisSession = True def show(self): if iface and not iface.mainWindow().restoreDockWidget(self): iface.mainWindow().addDockWidget(Qt.LeftDockWidgetArea, self) super(Visor, self).show() if self.firstRunThisSession: self.firstRunChecks() self.firstRunThisSession = False # If no dataset is selected yet, we will assume the first # thing the user will want to do is actually doing something # related to the servers (pick one from a list, or add a new # one) as there is little else that can be done at this point. # So we present them the screen to do so... if self.datasetInUse is None: self._onManageServersRequested() def firstRunChecks(self): """Convenience method to add any checks which should be performed when the user opens the plug-in for first time (be advised this is not the same as the first time the plug-in object is created, which is on QGIS load).""" pass def runWhenTabChange(self): """Convenience method to add any actions to be performed at tab change.""" # Show warning for GDAL version, if needed. self.checkGdalWindowWarning() def checkGdalWindowWarning(self): """Method to show GDAL version warning if need be.""" # If OS is linux: if sys.platform.startswith('linux'): # Show warning only if WCS tab selected: if self.tabWidget.currentIndex() == self.tabWidget.indexOf( self.tab_WCS): self.createGDALWindowWarning() def createGDALWindowWarning(self): """Check GDAL version. Versions < 2.0 had a bug regarding driver selection for network resource retrieval (https://trac.osgeo.org/gdal/ticket/2696).""" persistenceManager = ServerDataPersistenceManager.ServerStorageManager( ) if persistenceManager.show_GDAL_error: try: from osgeo import gdal if int(gdal.VersionInfo()) < 2000000: # Show warning window, and allow for "don't show again": message = "Your GDAL libraries version is outdated. Versions\n" message += "under 2.0 are not guaranteed to work when\n" message += "attempting to load WCS Layers.\nPlease update GDAL." reply = QtGui.QMessageBox.question( self, 'GDAL: Unsupported version found', (message), "Close", "Don't show again") # If requested to, record setting not to show warning again: if reply == 1: persistenceManager.show_GDAL_error = False except ImportError: # Show warning window, and allow for "don't show again": message = "Your GDAL libraries version could not be read" message += "Versions under 2.0 are not guaranteed to work when\n" message += "attempting to load WCS Layers. If you have any issues,\n" message += "please update GDAL." reply = QtGui.QMessageBox.question( self, 'GDAL: Unsupported version found', (message), "Close", "Don't show again") # If requested to, record setting not to show warning again: if reply == 1: persistenceManager.show_GDAL_error = False def toggleAnimationMenu(self): """Shows (or hides) the animation menu elements, and instantiate a controller. It seems I can not directly hide elements, but I can make another Widget in QDesigner and create/add it to a layout here so... oh well...""" if self.uiAnimation is None: self.uiAnimation = AnimationFrame(parent=self) self.uiAnimation.errorSignal.connect(self.postCriticalErrorToUser) self.controller.mapInfoRetrieved.connect( self.uiAnimation.setAnimationInformation) if None is not self.currentMap: self.uiAnimation.setAnimationInformation(self.currentMap) self.uiAnimation.show() self.button_req_animation.setText("Hide animation menu <<") else: self.uiAnimation.hide() self.uiAnimation = None self.button_req_animation.setText("Show animation menu >>") def clearData(self): self.WMSBoundingBoxInfo.setText( "No Bounding Box or CRS information available.") self.WMS_northBound.setText("East: No info") self.WMS_southBound.setText("West: No info") self.WMS_eastBound.setText("North: No info") self.WMS_westBound.setText("South: No info") self.combo_wms_layer.clear() self.combo_wms_style_type.clear() self.combo_wms_style_palette.clear() self.combo_wms_time.clear() self.combo_wms_time_last.clear() self.combo_wcs_coverage.clear() self.combo_wcs_time.clear() self.combo_wcs_time_last.clear() self.WCSBoundingBoxInfo.setText( "No Bounding Box or CRS information available.") self.WCS_northBound.setText("East: No info") self.WCS_southBound.setText("West: No info") self.WCS_eastBound.setText("North: No info") self.WCS_westBound.setText("South: No info") # TODO: Unused (for now) @pyqtSlot(bool) def _onAlwaysOnTopPrefsChanged(self, newSettingBool): """Will change the alwaysontop window modifier to suit the user selection.""" self.setWindowFlags(self.windowFlags() ^ Qt.WindowStaysOnTopHint) QtGui.QMainWindow.show(self) @pyqtSlot(list, str) def onNewDatasetsAvailable(self, inDataSets, serverName): """ A callback for when the dataSet displayed needs to be updated. :param inDataSets: list of DataSet objects which will be available to the user. :type inDataSets: list of threddsFetcherRecursos.DataSet objects. :param serverName: An user-friendly representation of this server name. :type serverName: str """ StringList = [] for dataSet in inDataSets: StringList.append(dataSet.getName()) self.setWindowTitle("THREDDS Explorer - Connected: " + serverName) self.combo_dataset_list.clear() self.combo_dataset_list.addItems(StringList) self.combo_dataset_list.setCurrentIndex(0) self.postInformationMessageToUser("Dataset list updated: " + str(len(StringList)) + " elements.") self.clearData() @pyqtSlot(str) def postInformationMessageToUser(self, message): """ Will post information messages to the user through the status bar. :param message: String to use as message to the user. :type message: str """ self.statusbar.showMessage(message) @pyqtSlot(str) def postCriticalErrorToUser(self, errorString): """ To be used with non-recoverable error situations. Shows a message box with the error message. :param errorString: String to use as message to the user. :type errorString: str """ box = QMessageBox() box.setText(errorString) box.setIcon(QMessageBox.Critical) box.exec_() @pyqtSlot(str) def _onDataSetItemChanged(self, stringItem): """Will receive notifications about this window dataSet chosen combobox when the item selected changes.""" self.tree_widget.clear() self.datasetInUse = self.controller.getSingleDataset( self.combo_dataset_list.currentText()) if self.datasetInUse is None: return #If no dataset is available to be shown, we will create no tree. rootItem = self.tree_widget.invisibleRootItem() newItem = QtGui.QTreeWidgetItem(rootItem, [self.datasetInUse.getName()]) rootItem.addChild(self._createHierarchy(self.datasetInUse, newItem)) def _createHierarchy(self, dataSet, treeItemParent): """Recursively creates a hierarchy of elements to populate a treeWidgetItem from a given dataSet. :param dataSet: DataSet object to create an hierarchy from. :type dataset: threddsFetcherRecursos.DataSet :param treeItemParent: Item which will be this branch parent. :type treeItemParent: QTreeWidgetItem""" i = 0 itemsAlreadyAddedToElement = [] while i < treeItemParent.childCount(): child = treeItemParent.child(i) if child.text(0) == "Loading..." or child.text( 0) == "No subsets found": treeItemParent.removeChild(child) else: itemsAlreadyAddedToElement.append(child) i = i + 1 elementsAlreadyInTreeItemParent = [ x.text(0) for x in itemsAlreadyAddedToElement ] if dataSet != None: for mapElement in dataSet.getAvailableMapList(): if mapElement.getName() in elementsAlreadyInTreeItemParent: continue else: newItem = QtGui.QTreeWidgetItem(treeItemParent, [mapElement.getName()]) treeItemParent.addChild(newItem) subSets = dataSet.getSubSets() if len(subSets) == 0: #We add a dummy element so the element open icon is created.. newItem = QtGui.QTreeWidgetItem(treeItemParent) newItem.setText(0, "No subsets found") treeItemParent.addChild(newItem) else: for dataset in subSets: #If an item with the same name as this dataset is found as a subchild #of the parent item, we will use it to build our tree. Otherwise, we #create a new one and append it. itemList = ([ x for x in itemsAlreadyAddedToElement if x.text(0) == dataset.getName() ]) if itemList is None or len(itemList) == 0: item = QtGui.QTreeWidgetItem(treeItemParent, [dataset.getName()]) treeItemParent.addChild( self._createHierarchy(dataset, item)) else: item = itemList[0] self._createHierarchy(dataset, item) else: self.postCriticalErrorToUser( "WARNING: Attempted to add a null dataset to view.") def _onMapTreeWidgetItemClicked(self, mQTreeWidgetItem, column): """ Will receive notifications about the MapTreeWidget elements being clicked, so we can update the first combobox of WMS/WCS tabs with the layer list. """ self.clearData() self.postInformationMessageToUser("") if None is mQTreeWidgetItem or None is mQTreeWidgetItem.parent(): return self.controller.getMapObject(str(mQTreeWidgetItem.text(0)), str(mQTreeWidgetItem.parent().text(0)), self.datasetInUse) @pyqtSlot(object) def _onMapInfoReceivedFromController(self, mapInfoObject): #print("_onMapInfoReceivedFromController 1"+str(mapInfoObject)) self.currentMap = mapInfoObject #print("_onMapInfoReceivedFromController 2"+str(self.currentMap)) if self.currentMap is not None: #WCS Data update self.currentCoverages = self.controller.getWCSCoverages( self.currentMap) if self.currentCoverages is not None: for c in self.currentCoverages: self.combo_wcs_coverage.addItem(c.getName()) else: self.combo_wcs_coverage.addItem("No data available.") #WMS Data update self.currentWMSMapInfo = self.controller.getWMSMapInfo( self.currentMap) if self.currentWMSMapInfo is not None: for l in self.currentWMSMapInfo.getLayers(): self.combo_wms_layer.addItem(l.getName()) else: self.combo_wms_layer.addItem("No data available.") def _onMapTreeWidgetItemExpanded(self, mQTreeWidgetItem): """ Once a set is expanded in the tree view we will attempt to recover it's data and present it to the user. """ setToUpdate = self.datasetInUse.searchSubsetsByName(str( mQTreeWidgetItem.text(0)), exactMatch=True) if setToUpdate is not None and len(setToUpdate) > 0: self.controller.mapDataSet(setToUpdate[0], depth=1) def onDataSetUpdated(self, dataSetObject): """ Will update the QTreeWidget to include the updated dataset object and it's new data. """ if dataSetObject.getParent() is not None: parent = self.tree_widget.findItems(dataSetObject.getName(), Qt.MatchRecursive) self._createHierarchy(dataSetObject, parent[0]) @pyqtSlot(str) def _onCoverageSelectorItemChanged(self, QStringItem): """ Will triger when the user selects a coverage name in the combobox (or that list is updated) so the available times to request to server are updated in the other combobox for the WCS service. """ self.combo_wcs_time.clear() if self.currentCoverages is not None: coverageElement = [ x for x in self.currentCoverages if x.getName() == str(QStringItem) ] if None is not coverageElement or len(coverageElement) > 0: try: self.wcsAvailableTimes = coverageElement[0].getTiempos() self.combo_wcs_time.addItems(self.wcsAvailableTimes) BBinfo = coverageElement[0].getBoundingBoxInfo() self.WCSBoundingBoxInfo.setText( "CRS = " + BBinfo.getCRS() + "\n\n Bounding Box information (decimal degrees):") self.WCS_eastBound.setText(BBinfo.getEast()) self.WCS_westBound.setText(BBinfo.getWest()) self.WCS_northBound.setText(BBinfo.getNorth()) self.WCS_southBound.setText(BBinfo.getSouth()) except IndexError: pass @pyqtSlot(str) def _onWMSLayerSelectorItemChanged(self, QStringItem): self.combo_wms_style_type.clear() self.combo_wms_style_palette.clear() self.combo_wms_time.clear() # Only one should be returned here. if self.currentWMSMapInfo is not None: layerSelectedObject = [ x for x in self.currentWMSMapInfo.getLayers() if x.getName() == str(QStringItem) ] if layerSelectedObject is not None and len( layerSelectedObject) == 1: self.wmsAvailableTimes = layerSelectedObject[0].getTimes() self.combo_wms_time.addItems(self.wmsAvailableTimes) self.wmsAvailableStyles = layerSelectedObject[0].getStyles() self.combo_wms_style_type.addItems( list({(x.getName().split(r"/"))[0] for x in self.wmsAvailableStyles})) BBinfo = layerSelectedObject[0].getBoundingBoxInfo() self.WMSBoundingBoxInfo.setText( "CRS = " + BBinfo.getCRS() + "\n\n Bounding Box information (decimal degrees):") self.WMS_eastBound.setText(BBinfo.getEast()) self.WMS_westBound.setText(BBinfo.getWest()) self.WMS_northBound.setText(BBinfo.getNorth()) self.WMS_southBound.setText(BBinfo.getSouth()) @pyqtSlot(str) def _onWMSStyleTypeSelectorItemChanged(self, qstringitem): self.combo_wms_style_palette.clear() self.combo_wms_style_palette.addItems( list({(x.getName().split(r"/"))[1] for x in self.wmsAvailableStyles if str(qstringitem) in x.getName()})) @pyqtSlot(int) def _onWCSFirstTimeChanged(self, position): #print("self.wcsAvailableTimes"+str((sorted(self.wcsAvailableTimes)))) #print("WCS INDEX: "+str(position)) self.combo_wcs_time_last.clear() #print self.wcsAvailableTimes[position:] self.combo_wcs_time_last.addItems( (sorted(self.wcsAvailableTimes))[position:]) @pyqtSlot(int) def _onWMSFirstTimeChanged(self, position): #print("self.wmsAvailableTimes"+str((sorted(self.wmsAvailableTimes)))) #print("WMS INDEX: "+str(position)) self.combo_wms_time_last.clear() #print self.wmsAvailableTimes[position:] self.combo_wms_time_last.addItems(self.wmsAvailableTimes[position:]) def _onbuttonReqMapClicked(self): """ Action to be performed when the user clicks the button to request a new map to be displayed, after selecting proper values in the rest of fields. """ self.postInformationMessageToUser("") # reset error display. if self.tabWidget.currentIndex() == self.tabWidget.indexOf( self.tab_WCS): try: selectedBeginTimeIndex = self.wcsAvailableTimes.index( self.combo_wcs_time.currentText()) selectedFinishTimeIndex = self.wcsAvailableTimes.index( self.combo_wcs_time_last.currentText()) + 1 # We retrieve some information about the current selected map, useful # to grab the actual CRS used by the map service. Should be changed if # CRS is to be user-selectable later via dropdown menu or anything like # that. if self.currentCoverages is not None: coverageElement = [ x for x in self.currentCoverages if x.getName() == str( self.combo_wcs_coverage.currentText()) ] if None is not coverageElement or len(coverageElement) > 0: try: try: north = float(self.WCS_northBound.text()) south = float(self.WCS_southBound.text()) east = float(self.WCS_eastBound.text()) west = float(self.WCS_westBound.text()) except ValueError: self.postCriticalErrorToUser( "Bounding box values were not valid." + "\nCheck only decimal numbers are used\n(example: 12.44)" ) return # We retrieve the bounding box CRS information from the # requested coverage, and get the actual box values # from the UI. BBinfo = coverageElement[0].getBoundingBoxInfo() boundingBoxToDownload = BoundingBox() boundingBoxToDownload.setCRS(BBinfo.getCRS()) boundingBoxToDownload.setEast(east) boundingBoxToDownload.setWest(west) boundingBoxToDownload.setNorth(north) boundingBoxToDownload.setSouth(south) self.controller.asyncFetchWCSImageFile( self.currentMap, self.combo_wcs_coverage.currentText(), self.wcsAvailableTimes[selectedBeginTimeIndex: selectedFinishTimeIndex], boundingBox=boundingBoxToDownload) except IndexError: pass except Exception as exc: self.postInformationMessageToUser( "There was an error retrieving the WCS data.") QgsMessageLog.logMessage(traceback.format_exc(), "THREDDS Explorer", QgsMessageLog.CRITICAL) elif self.tabWidget.currentIndex() == self.tabWidget.indexOf( self.tab_WMS): try: selectedBeginTimeIndex = self.wmsAvailableTimes.index( self.combo_wms_time.currentText()) selectedFinishTimeIndex = self.wmsAvailableTimes.index( self.combo_wms_time_last.currentText()) + 1 style = self.combo_wms_style_type.currentText( ) + r"/" + self.combo_wms_style_palette.currentText() #We retrieve some information about the current selected map, useful #to grab the actual CRS used by the map service. Should be changed if #CRS is to be user-selectable later via dropdown menu or anything like #that. #Only one should be returned here. if self.currentWMSMapInfo is not None: layerSelectedObject = [ x for x in self.currentWMSMapInfo.getLayers() if x.getName() == str(self.combo_wms_layer.currentText()) ] #We retrieve the bounding box CRS information from the #requested coverage, and get the actual box values #from the UI. if None is not layerSelectedObject or len( layerSelectedObject) > 0: try: north = float(self.WMS_northBound.text()) south = float(self.WMS_southBound.text()) east = float(self.WMS_eastBound.text()) west = float(self.WMS_westBound.text()) except ValueError: self.postCriticalErrorToUser( "Bounding box values were not valid." + "\nCheck only decimal numbers are used\n(example: 12.44)" ) return BBinfo = layerSelectedObject[0].getBoundingBoxInfo() boundingBoxToDownload = BoundingBox() boundingBoxToDownload.setCRS(BBinfo.getCRS()) boundingBoxToDownload.setEast(east) boundingBoxToDownload.setWest(west) boundingBoxToDownload.setNorth(north) boundingBoxToDownload.setSouth(south) self.controller.asyncFetchWMSImageFile( self.currentMap, self.combo_wms_layer.currentText(), style, self.wmsAvailableTimes[ selectedBeginTimeIndex:selectedFinishTimeIndex], boundingBox=boundingBoxToDownload) except Exception as exc: print(exc) self.postInformationMessageToUser( "There was an error retrieving the WMS data.") QgsMessageLog.logMessage(traceback.format_exc(), "THREDDS Explorer", QgsMessageLog.CRITICAL) @pyqtSlot(list, str) def createLayerGroup(self, layerList, groupName): if layerList: groupifier = LayerGroupifier(layerList, groupName) groupifier.setSingleLayerSelectionModeInGroup(False) groupifier.statusSignal.connect(self.postInformationMessageToUser, Qt.DirectConnection) groupifier.groupifyComplete.connect(self._onNewLayerGroupGenerated) groupifier.groupify() else: self.postInformationMessageToUser( "There was a problem showing the time series.") @pyqtSlot(QgsLayerTreeGroup, list) def _onNewLayerGroupGenerated(self, groupObject, layerList): """ Currently only used to show the first image of a newly created group so the user knows when the operation finishes. :param groupObject: The legend group object which was created. :type groupObject: QgsLayerTreeGrupo :param layerList: The layers which are held in the group object. :type layerList: [QgsLayer] """ if (layerList[0]).isValid(): iface.legendInterface().setLayerVisible(layerList[0], True) else: self.postInformationMessageToUser( "There was a problem showing a layer.") @pyqtSlot(tuple) def showNewImage(self, image): """ Will order this UI to post a new image to the user through the qgis window. :param image: a tuple consisting of (imageOrLayerObject, Name, Service) :type image: (QgsRasterLayer, String, String) """ self.postInformationMessageToUser("Layer '" + image[1] + "' [" + image[2] + "]retrieved") layer = image[0] if layer and layer.isValid(): QgsMapLayerRegistry.instance().addMapLayer(layer) iface.zoomToActiveLayer() iface.legendInterface().refreshLayerSymbology(layer) else: self.postInformationMessageToUser( "There was a problem loading the layer.") @pyqtSlot() def _onManageServersRequested(self): """Delegates the action of showing the server manager window to the controller.""" self.controller.showServerManager()
class StatusBarManager ( HasFacets ): """ A status bar manager realizes itself in a status bar control. """ #-- Facet Definitions ------------------------------------------------------ # FIXME v3: Is this used anywhere? # The manager's unique identifier (if it has one). id = Str # The message displayed in the first field of the status bar. message = Property # The messages to be displayed in the status bar fields. messages = List( Unicode ) # The toolkit-specific control that represents the status bar. status_bar = Any #-- StatusBarManager Interface --------------------------------------------- def create_status_bar ( self, parent ): """ Creates a status bar. """ if self.status_bar is None: self.status_bar = QStatusBar( parent ) self.status_bar.setSizeGripEnabled( False ) if len( self.messages ) > 1: self._show_messages() else: self.status_bar.showMessage( self.message ) return self.status_bar #-- Property Implementations ----------------------------------------------- def _get_message ( self ): if len( self.messages ) > 0: message = self.messages[ 0 ] else: message = '' return message def _set_message ( self, value ): if len( self.messages ) > 0: old = self.messages[ 0 ] self.messages[ 0 ] = value else: old = '' self.messages.append( old ) self.facet_property_set( 'message', old, value ) #-- Facet Event Handlers --------------------------------------------------- def _messages_set ( self ): """ Sets the text displayed on the status bar. """ if self.status_bar is not None: self._show_messages() def _messages_items_set ( self ): """ Sets the text displayed on the status bar. """ if self.status_bar is not None: self._show_messages() #-- Private Methods -------------------------------------------------------- def _show_messages ( self ): """ Display the list of messages. """ # FIXME v3: At the moment we just string them together but we may # decide to put all but the first message into separate widgets. We # probably also need to extend the API to allow a "message" to be a # widget - depends on what wx is capable of. self.status_bar.showMessage( " ".join( self.messages ) ) #-- EOF ------------------------------------------------------------------------
class ImportDataDlg(QDialog): __isJy = False # 数据校验成功的标志 __mlist = [] # 定义一个列表用于保存从excel表中取出的数据 def __init__(self, iface, parent=None, impType=ImpDateType.SITEANDCELL): super(ImportDataDlg, self).__init__() self.iface = iface self.parent = parent self.impType = impType self.initView() def initView(self): if self.impType == ImpDateType.SERVINGCELL: self.setWindowTitle(u'相邻小区数据导入') else: self.setWindowTitle(u'基站和小区数据导入') self.setWindowIcon(QIcon('images/logo.png')) self.resize(620, 480) # 数据表格 self.tableWidget = QTableWidget(self) self.tableWidget.setAlternatingRowColors(True) self.tableWidget.setRowCount(7) # 设置当前Table不能编辑 self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) # 初始化表格上下文菜单 self.initTableContextMenu() # 初始化表头 self.initTableHeader() # 导入出错列表 self.listWidget = QListWidget(self) # 按钮组 impBtn = QPushButton(u"导入EXCEL表", self) yzBtn = QPushButton(u"数据检验", self) impdateBtn = QPushButton(u"导入数据", self) btnVBox = QVBoxLayout() btnVBox.addWidget(impBtn) btnVBox.addWidget(yzBtn) btnVBox.addWidget(impdateBtn) # 错误列表与按钮组 hBox = QHBoxLayout() hBox.setMargin(20) hBox.addWidget(self.listWidget) hBox.addLayout(btnVBox) self.mbar = QStatusBar(self) self.mbar.showMessage(u'准备就绪...') self.maction = QToolBar(self) self.editAction = QAction(u'编辑', self.maction) self.editAction.setCheckable(True) self.combox = QComboBox(self) self.combox.addItems(HeadsConfig.ImpExcelName) self.maction.addWidget(self.combox) self.maction.addAction(self.editAction) vBox = QVBoxLayout() vBox.addWidget(self.maction) vBox.addWidget(self.tableWidget) vBox.addLayout(hBox) vBox.addWidget(self.mbar) vBox.setStretchFactor(self.tableWidget, 9) vBox.setStretchFactor(hBox, 5) vBox.setStretchFactor(self.mbar, 1) self.setLayout(vBox) QObject.connect(impBtn, SIGNAL('clicked()'), self.impClick) QObject.connect(yzBtn, SIGNAL('clicked()'), self.yzClick) QObject.connect(impdateBtn, SIGNAL('clicked()'), self.impdateClick) QObject.connect(self.editAction, SIGNAL('triggered()'), self.editClick) QObject.connect(self.combox, SIGNAL('currentIndexChanged(int)'), self.comboxChange) self.listWidget.doubleClicked.connect(self.mlistClicked) # self.connect(self.listWidget, SIGNAL("itemDoubleClicked (QListWidgetItem)"), self.mlistClicked) def initTableContextMenu(self): self.tableWidget.setContextMenuPolicy(Qt.CustomContextMenu) self.popMenu = QMenu(self.tableWidget) delAction = QAction(u'删除', self) # 删除 self.popMenu.addAction(delAction) # 设置表格可以双击修改数据 def setEditTriggers(self, isTrigger): if isTrigger: self.tableWidget.setEditTriggers(QAbstractItemView.DoubleClicked) else: self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers) # 提供给外部修改状态栏消息 def setStatusBarMsg(self, msg): self.mbar.showMessage(msg) # 选框改变时,清空当前表格的全部内容,包括表格头 def updateType(self, mtype): self.impType = mtype self.tableWidget.clear() # 清空表格所有内容 self.initTableHeader() # 初始化表格的每个Item def initTable(self, mlist): self.tableWidget.setRowCount(len(mlist)) for (i, v) in enumerate(mlist): for (j, item) in enumerate(v): if type(item) != str: item = unicode(item) if item == None: item = u"" tabItem = QTableWidgetItem(item) tabItem.setTextAlignment(Qt.AlignCenter) self.tableWidget.setItem(i, j, tabItem) # 初始化错误信息列表 def initListView(self, mlist): for iv in mlist: lisItm = QListWidgetItem(self.listWidget) lisItm.setTextColor(Qt.red) lisItm.setData(Qt.UserRole, iv) if isinstance(iv, basestring): # 如果错误信息是一行字符串 lisItm.setText(iv) else: lisItm.setText(u'第' + unicode(str(iv['row'] + 1)) + u'行,第' + unicode(str(iv['col'] + 1)) + u'列:' + iv['msg']) # 初始化Table的头 def initTableHeader(self): self.heads = [] if self.impType == ImpDateType.SITEANDCELL: # 获取当前项目图层的字段名 cell_layer = getLayerByName(u"小区", self.iface) for head in HeadsConfig.SiteANDCellHead: self.heads.append(head) if len(cell_layer.pendingFields()) > 55: for (index, field) in enumerate(cell_layer.pendingFields()): if index > 54: field_name = field.name().strip() self.heads.append(field_name) else: self.heads = HeadsConfig.ServingCellHead self.tableWidget.setColumnCount(len(self.heads)) # 设置表格的列数 for (i, h) in enumerate(self.heads): tabItem = QTableWidgetItem(h) self.tableWidget.setHorizontalHeaderItem(i, tabItem) # 自定义为Table添加Item def addTableItem(self, row, col, content): tabItem = QTableWidgetItem(content) self.tableWidget.setItem(row, col, tabItem) # 修改Item的内容 def editTableItem(self, row, col, content): tabItem = self.tableWidget.item(row, col) tabItem.setText(content) self.tableWidget.setItem(row, col, tabItem) # 从Excel表读取数据(导入Excel表) def impClick(self): fileName = QFileDialog.getOpenFileName(self, u'基站小区数据导入', '/', 'Excel Files (*.xls *.xlsx)') if fileName.strip() != "": self.setStatusBarMsg(u'选择完毕:' + fileName) importData = GetDataFromExcel(fileName, self.impType, self.heads) self.__mlist = [] self.__mlist.extend(importData.getData()) self.tableWidget.clearContents() self.listWidget.clear() self.initTable(self.__mlist) self.setStatusBarMsg(u'数据导入完成...') self.__isJy = False # 导入完数据后,说明需要重新验证数据的正确性 else: QMessageBox.information(self.parent, u"错误", u"请选中文件") # 数据验证按钮点击事件处理 def yzClick(self): if len(self.__mlist) > 0: self.erlist = [] # 定义一个列表用于保存数据验证错误的数据 # 清楚全部的Item if self.listWidget.count() > 0: self.listWidget.clear() # 根据tableWidget更新self.__mlist for (r, items) in enumerate(self.__mlist): for (v, item) in enumerate(self.__mlist[r]): if self.tableWidget.item(r, v).text() == u"": continue else: # 跟据self.__mlist[r][v]数据类型进行比对 if type(self.__mlist[r][v]) == int: if unicode(self.__mlist[r][v]) != ( self.tableWidget.item(r, v).text()): self.__mlist[r][v] = int( self.tableWidget.item(r, v).text()) elif type(self.__mlist[r][v]) == float: if unicode(self.__mlist[r][v]) != ( self.tableWidget.item(r, v).text()): self.__mlist[r][v] = float( self.tableWidget.item(r, v).text()) elif type(self.__mlist[r][v]) == str: if unicode(self.__mlist[r][v] ) != self.tableWidget.item(r, v).text(): self.__mlist[r][v] = str( self.tableWidget.item(r, v).text()) elif type(self.__mlist[r][v]) == unicode: if (self.__mlist[r][v]) != self.tableWidget.item( r, v).text(): self.__mlist[r][v] = self.tableWidget.item( r, v).text() else: print type(self.__mlist[r][v]) # 执行数据校验函数 self.erlist = checkDataByDataType(self.__mlist, self.impType) if len(self.erlist) > 0: self.initListView(self.erlist) QMessageBox.information(self.parent, u'数据校验', u'数据校验失败,请检查数据正确性后,再导入到地图中') self.__isJy = False else: QMessageBox.information(self.parent, u'数据校验', u'数据校验成功,没有错误数据') self.__isJy = True else: QMessageBox.warning(self.parent, u'数据校验', u'请先导入Excel数据后再操作!') # 导入数据到地图中 def impdateClick(self): if self.__isJy: # 如果数据校验成功 if self.impType == ImpDateType.SITEANDCELL: # 导入基站小区 importDataToLayer = ImportDataToLayer(self.iface, self.__mlist, self.parent) if importDataToLayer.importSiteAndCellData(): QMessageBox.information(self.parent, u"导入数据", u"导入数据成功!") else: QMessageBox.critical(self.parent, u"导入数据", u"导入数据失败!") else: # 导入相邻小区 importDataToLayer = ImportDataToLayer(self.iface, self.__mlist, self.parent) if importDataToLayer.importSCellData(): QMessageBox.information(self.parent, u"导入数据", u"导入数据成功!") else: QMessageBox.critical(self.parent, u"导入数据", u"导入数据失败!") else: QMessageBox.warning(self.parent, u'数据导入', u'请确保校验数据成功后,再导入到地图中') # 编辑Action点击事件 def editClick(self): self.setEditTriggers(self.editAction.isChecked()) # 错误列表双击事件处理 def mlistClicked(self, listItem): itemData = listItem.data(Qt.UserRole) self.tableWidget.setFocus() self.tableWidget.setCurrentCell(itemData['row'], itemData['col']) # 选框改变事件 def comboxChange(self, index): self.updateType(index) # 字段验证是否为空 def __validNull(self, name, col, row, itm, rowitm): if itm is None or itm == '': tmpMap = {} tmpMap['col'] = col tmpMap['row'] = row tmpMap['msg'] = unicode(name) + u'不能为空' tmpMap['item'] = rowitm return tmpMap else: return None # 导入数据线程开始信号 绑定函数 def impStart(self): self.setStatusBarMsg(u'准备导入...') # 导入数据线程发生异常信号 绑定函数 def impError(self, e, exception_string): self.setStatusBarMsg(u'发生错误:' + unicode(e)) QMessageBox.warning(self.parent, u'Excel数据导入', u'发生错误:' + unicode(e)) # 导入数据线程完成信号 绑定函数 def impFinish(self, mylist): self.__mlist = [] self.__mlist.extend(mylist) self.mthread.quit() self.mthread.wait() self.mthread.deleteLater() self.impDateThread.deleteLater() self.tableWidget.clearContents() self.listWidget.clear() self.initTable(self.__mlist) self.setStatusBarMsg(u'数据导入完成...') self.__isJy = False # 导入完数据后,说明需要重新验证数据的正确性 # 导入数据到地图线程发生异常信号 绑定函数 def impError1(self, e, exception_string): self.setStatusBarMsg(u"导入数据发生错误") QMessageBox.critical(self, u'数据导入', u"发生错误:" + unicode(e)) # 导入数据到地图线程完成信号 绑定函数 def impFinish1(self, mylist): self.threadImp.quit() self.threadImp.wait() self.threadImp.deleteLater() self.impFeatureThread.deleteLater() remsg = u'数据导入完成!' layer = None if self.impType == LayerType.SITE: # remsg = u'基站' + remsg layer = getLayerByName(u'基站', self.iface) elif self.impType == LayerType.CELL: # remsg = u'小区' + remsg layer = getLayerByName(u'小区', self.iface) else: remsg = u'相邻小区' + remsg layer = getLayerByName(u'相邻小区', self.iface) self.setStatusBarMsg(remsg) layer.updateExtents() # 更新地图数据 self.iface.actionDraw().trigger() QMessageBox.information(self, u'数据导入', remsg) merlist = [] for eritm in self.erlist: merlist.append(eritm['item']) self.tableWidget.clearContents() # 先清楚表格的内容,再将错误的行显示到表格中 self.initTable(merlist)
class WordTester(QMainWindow, ui.ui_WordTesterWindow.Ui_WordTesterWindow): """ Main class of the program. Subclass of QMainWindow. """ def __init__(self, clipboard, parent=None): super(WordTester, self).__init__(parent) self.setupUi(self) ######################################## # WORDS TABLE STUFF (most of it) ######################################## self.model = WordTableModel.WordTableModel() self.wordsTable.setModel(self.model) self.wordsTable.setItemDelegate(WordTableDelegate.WordTableDelegate(self)) self.wordsTable.setSelectionMode(QAbstractItemView.ExtendedSelection) self.wordsTable.setContextMenuPolicy(Qt.CustomContextMenu) self.wordsTable.customContextMenuRequested.connect(self.contextMenu) self.wordsTable.setSelectionBehavior(QAbstractItemView.SelectRows) self.wordsTable.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) showSearchBarAction = self.createAction("Find",self.showSearchBar, QKeySequence.Find) closeSearchBarAction = self.createAction("Close search bar", self.closeSearchBar, Qt.Key_Escape) searchForNextWordAction = self.createAction("Find next", self.searchForNextOccurance, QKeySequence.FindNext) wordsTableActions = (showSearchBarAction, closeSearchBarAction, searchForNextWordAction) self.wordsTable.addActions(wordsTableActions) header = self.wordsTable.horizontalHeader() self.connect(header, SIGNAL("sectionClicked(int)"), self.sortTable) self.connect(self.model, SIGNAL("dataChanged(QModelIndex,QModelIndex)"), self.fnameChanged) ############################################## # OTHER WIDGETS ############################################## self.clipboard = clipboard self.statusBar = QStatusBar(self) self.setStatusBar(self.statusBar) self.allCheckBox.setChecked(True) self.searchBarFrame.setVisible(False) self.searchCheckBox.setChecked(True) ############################################### # ACTIONS ############################################### # file menu actions newFileAction = self.createAction("&New", self.newFile, QKeySequence.New, tip = "Create list of words") openFileAction = self.createAction("&Open", self.load, QKeySequence.Open, tip = "Open Word Tester file") saveFileAction = self.createAction("&Save", self.save, QKeySequence.Save, tip = "Save Word Tester file") saveAsFileAction = self.createAction("Save &As", self.saveAs, tip = "Save Word Tester file using a new name") importFileAction = self.createAction("&Import", self.importWords, tip = "Import words from .txt file") exportFileAction = self.createAction("&Export", self.exportWords, tip = "Export words to .txt file") closeAction = self.createAction("&Quit", self.close) self.fileMenu = self.menuBar().addMenu("&File") self.fileMenuActions = (newFileAction, openFileAction, saveFileAction, saveAsFileAction, importFileAction, exportFileAction, closeAction) # edit menu actions addWordsAction = self.createAction("Add", self.addWords, "CTRL++", tip = "Add new words") deleteWordsAction = self.createAction("Delete", self.deleteWords, QKeySequence.Delete, tip = "Delete words") showInfoAction = self.createAction("Properties", self.showInfo) easyAction = self.createAction("To easy", lambda difficulty = "EASY": self.changeDifficulty(difficulty)) mediumAction = self.createAction("To medium", lambda difficulty = "MEDIUM": self.changeDifficulty(difficulty)) hardAction = self.createAction("To hard", lambda difficulty = "HARD": self.changeDifficulty(difficulty)) addWordsFromFileAction = self.createAction("Add from file..", self.addWordsFromFile, \ tip = "Add words from existing file, duplicates won't be appended") showAllAction = self.createAction("Show all", self.showAll, "CTRL+A", tip = "Show all words") hideAction = self.createAction("Hide", self.hideWords, "CTRL+H", tip = "Hide selected words") self.editMenu = self.menuBar().addMenu("&Edit") self.editMenuActions = (addWordsAction, hideAction, deleteWordsAction, showSearchBarAction, showAllAction) self.editMenu.addActions(self.editMenuActions) # submenu change difficulty difficultyMenu = self.editMenu.addMenu("Change difficulty") self.difficultyActions = (easyAction, mediumAction, hardAction) difficultyMenu.addActions(self.difficultyActions) self.editMenu.addSeparator() self.editMenu.addAction(addWordsFromFileAction) # words table context menu actions self.contextMenuActions = (deleteWordsAction, hideAction, showAllAction, showInfoAction) ############################################### # CONNECTIONS ############################################### self.connect(self, SIGNAL("resizeColumns"), self.resizeColumns) self.connect(self, SIGNAL("fnameChanged"), self.fnameChanged) self.connect(self.searchLineEdit, SIGNAL("returnPressed()"), self.searchForWord) self.connect(self.searchCheckBox, SIGNAL("stateChanged(int)"), self.searchLineEdit.setFocus) self.connect(self.fileMenu, SIGNAL("aboutToShow()"), self.updateFileMenu) for checkBox in (self.allCheckBox, self.easyCheckBox, \ self.mediumCheckBox, self.hardCheckBox): self.connect(checkBox, SIGNAL("clicked()"), lambda text = checkBox.text().toLower(): self.showGroup(text)) self.connect(self.beginTestButton, SIGNAL("clicked()"), self.beginTest) self.connect(self.wordsOnlyCheckBox, SIGNAL("stateChanged(int)"), self.showWordsOnly) self.connect(self.clipboard, SIGNAL("dataChanged()"), self.scanKeyboard) #################################################### # SETTINGS #################################################### settings = QSettings(QSettings.IniFormat, QSettings.UserScope, "Word Tester", "michauStuff") self.recentFiles = settings.value("RecentFiles").toStringList() self.restoreGeometry(settings.value("Geometry").toByteArray()) ##################################################### # VARIABLES AND STARTUP FUNCTIONS ##################################################### self.setWindowTitle('Word Tester - Unknown File') self.recentlySearchedWord = "" self.updateFileMenu() # QTimer.singleShot just in case the file is large and will take a while to load if settings.value("LastFile") == QVariant() or settings.value("LastFile").toString().isEmpty(): QTimer.singleShot(0, lambda num = 5: self.initialWords(num)) # if there is no Last File else: QTimer.singleShot(0, self.loadInitialFile) ###################################################### # reimplemented QMainWindow methods ###################################################### def resizeEvent(self, event): self.emit(SIGNAL("resizeColumns")) QMainWindow.resizeEvent(self, event) def closeEvent(self, event): if self.okToContinue(): self.addRecentFiles(self.model.fname) settings = QSettings(QSettings.IniFormat, QSettings.UserScope, "Word Tester", "michauStuff") filename = QVariant(QString(self.model.fname)) \ if self.model.fname is not None else QVariant() settings.setValue("LastFile", filename) settings.setValue("Geometry", self.saveGeometry()) recentFiles = QVariant(self.recentFiles) if self.recentFiles \ else QVariant() settings.setValue("RecentFiles", recentFiles) else: event.ignore() # this is not really a QMainWindow method, but fits in here def contextMenu(self, pos): menu = QMenu() menu.addActions(self.contextMenuActions) difficultyMenu = menu.addMenu("Change difficulty") difficultyMenu.addActions(self.difficultyActions) # change position a little to have a corner of the menu where the mouse is if platform.system() != "Windows": menu.exec_(self.wordsTable.mapToGlobal(QPoint(pos.x()+18,pos.y() +24))) else: menu.exec_(self.wordsTable.mapToGlobal(QPoint(pos.x()+16,pos.y() +24))) ###################################################### # SIGNALS ###################################################### def resizeColumns(self): """ Resizes WORD and DIFFICULTY columns to match their content. MEANIGS and CONTEXT columns share the left space - 50 pixels of margin. """ size = self.wordsTable.size() tableWidth = size.width() # this used to be resizeColumns() method for column in (WORD,DIFFICULTY): self.wordsTable.resizeColumnToContents(column) wordColumnWidth = self.wordsTable.columnWidth(WORD) difficultyColumnWidth = self.wordsTable.columnWidth(DIFFICULTY) self.wordsTable.setColumnWidth(MEANINGS, \ (tableWidth - wordColumnWidth - difficultyColumnWidth)/2 - 25) self.wordsTable.setColumnWidth(CONTEXT, \ (tableWidth - wordColumnWidth - difficultyColumnWidth)/2 - 25) def fnameChanged(self): """ Sets main window's title. """ if self.model.fname.isEmpty(): if self.model.dirty: title = 'Word Tester - Unknown File*' else: title = 'Word Tester - Unknown File' else: if self.model.dirty: title = 'Word Tester - %s*' % \ os.path.basename(unicode(self.model.fname)) else: title = 'Word Tester - %s' % \ os.path.basename(unicode(self.model.fname)) self.setWindowTitle(title) def scanKeyboard(self, mode = QClipboard.Clipboard): """ If the scan option is on, retrieves text from system keyboard and places it in a new word. """ if not self.scanCheckBox.isChecked(): return None text = self.clipboard.text(mode) text = text.split("\n") # if there is more than one word for word in text: row = self.model.rowCount() self.model.insertRows(row+1,1) index = self.model.index(self.model.rowCount()-1,WORD) self.wordsTable.setCurrentIndex(index) self.model.setData(index,QVariant(word)) ############################################################ # HELPER METHODS ############################################################ def createAction(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False, signal="triggered()"): action = QAction(text, self) if icon is not None: action.setIcon(QIcon(":/%s.png" % icon)) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: self.connect(action, SIGNAL(signal), slot) if checkable: action.setCheckable(True) return action def getVisibleWords(self): """ Returns words that are visible at the moment. :rtype WordContainer WordContainer with a list of all visible words. """ visibleWords = WordContainerClass.WordContainer() # iterate over main WordContainer. If the word is hidden, create # index in visibleWords (*) and then tie that index to the word in self.model.words. # visibleWords.append(self.model.words[row]) would create a shallow copy, so # changes made to it wouldn't be reflected in self.model.words for row in range(len(self.model.words)): if not self.wordsTable.isRowHidden(row): visibleWords.append('x') #(*) visibleWords[-1] = self.model.words[row] return visibleWords def okToContinue(self): if self.model.dirty: ans = QMessageBox.question(self, "Word Tester - Unsaved changes", "Save unsaved changes?", QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if ans == QMessageBox.Yes: self.save() elif ans == QMessageBox.Cancel: return False return True def addRecentFiles(self, fname): if fname is None: return if not self.recentFiles.contains(fname): self.recentFiles.prepend(QString(fname)) while self.recentFiles.count() > 9: self.recentFiles.takeLast() def loadRecentFile(self, fname): if self.okToContinue(): try: self.addRecentFiles(self.model.fname) self.model.fname = fname self.model.load() except (pickle.UnpicklingError, IOError, AttributeError, wtexception.FileHandlingExceptions) as e: self.statusBar.showMessage(str(e),5000) self.emit(SIGNAL("resizeColumns")) self.emit(SIGNAL("fnameChanged")) ###################################################### # GUI FUNCTIONALITY ###################################################### def showSearchBar(self): if self.searchBarFrame.isVisible(): self.searchBarFrame.setVisible(False) self.wordsTable.setFocus() else: self.searchBarFrame.setVisible(True) self.searchLineEdit.selectAll() self.searchLineEdit.setFocus() def updateFileMenu(self): """ Creates new file menu everytime the user invokes it. File menu can't be created only once, because of recent files section which has to be refreshed. """ self.fileMenu.clear() # add all fixed actions, but close self.fileMenu.addActions(self.fileMenuActions[:-1]) current = QString(self.model.fname) \ if self.model.fname is not None else None recentFiles = [] for fname in self.recentFiles: if fname != current and QFile.exists(fname): recentFiles.append(fname) if recentFiles: self.fileMenu.addSeparator() self.recentFilesMenu = self.fileMenu.addMenu("Recent Files") for i, fname in enumerate(recentFiles): action = QAction("&%d %s" % ( i + 1, QFileInfo(fname).fileName()), self) action.setData(QVariant(fname)) self.connect(action, SIGNAL("triggered()"), lambda file = fname: self.loadRecentFile(file)) self.recentFilesMenu.addAction(action) self.fileMenu.addSeparator() # add the last action - close self.fileMenu.addAction(self.fileMenuActions[-1]) def closeSearchBar(self): if self.searchBarFrame.isVisible(): self.searchBarFrame.setVisible(False) self.wordsTable.setFocus() def searchForWord(self): self.recentlySearchedWord = unicode(self.searchLineEdit.text()) backwards = self.searchCheckBox.isChecked() try: row = self.model.words.searchForWord(unicode(self.searchLineEdit.text())) except ValueError: return None # not found if backwards: # of backwards is on, we don't have to worry if searched word is before # or after the current one index = self.model.index(row, WORD) else: currentRow = self.wordsTable.currentIndex().row() # if the searched word is in the [currentRow:] part of self.model.words row = self.model.words.slice(currentRow).searchForWord(unicode(self.searchLineEdit.text())) if row != -1: index = self.model.index(row + currentRow, WORD) else: return None self.wordsTable.setCurrentIndex(index) self.searchLineEdit.selectAll() self.wordsTable.setFocus() def searchForNextOccurance(self): if self.recentlySearchedWord == "": return None else: backwards = self.searchCheckBox.isChecked() currentRow = self.wordsTable.currentIndex().row()+1 row = self.model.words.slice(currentRow).searchForWord(self.recentlySearchedWord) if row != -1: # row is a position of searched row in [currentRow:] list, so row + currentRow # is a position in the whole list index = self.model.index(row + currentRow, WORD) else: if backwards: row = self.model.words.slice(0,currentRow-1).searchForWord(self.recentlySearchedWord) if row != -1: index = self.model.index(row, WORD) else: return None else: return None self.wordsTable.setCurrentIndex(index) self.searchLineEdit.selectAll() def showInfo(self): indexes = self.wordsTable.selectedIndexes() if (len(indexes)/self.model.columnCount() > 1): QMessageBox.warning(self,"Error!","Please select only one word.") return else: index = indexes[0] dialog = InfoDialog.InfoDialog(self.model.words[index.row()]) if dialog.exec_(): whatChanged = dialog.result() if whatChanged[0]: self.model.words[index.row()].setWord(whatChanged[0]) self.model.dirty = True if whatChanged[1]: self.model.words[index.row()].setMeanings(whatChanged[1]) self.model.dirty = True if whatChanged[2]: self.model.words[index.row()].setContext(whatChanged[2]) self.model.dirty = True if whatChanged[3]: self.model.words[index.row()].setDifficulty(whatChanged[3]) self.model.dirty = True if self.model.dirty: self.emit(SIGNAL("fnameChanged")) self.emit(SIGNAL("resizeColumns")) self.model.reset() ########################################################### # SAVE, LOAD, EXPORT, IMPORT ########################################################### def loadInitialFile(self): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, "Word Tester", "michauStuff") fname = settings.value("LastFile").toString() if fname and QFile.exists(fname): self.model.fname = fname self.model.load() self.emit(SIGNAL("resizeColumns")) else: self.newFile() self.emit(SIGNAL("fnameChanged")) def addWordsFromFile(self): dir = os.path.dirname(unicode(self.model.fname)) \ if not self.model.fname.isEmpty() else '.' fname = QFileDialog.getOpenFileName(self, "Select a file to add words from", dir, "Word Tester file *.pkl *.txt") if fname: if fname.endsWith(".pkl"): try: self.model.load(fname,True) except (pickle.UnpicklingError, IOError, AttributeError, wtexception.FileHandlingExceptions) as e: self.statusBar.showMessage(str(e),5000) elif fname.endsWith(".txt"): try: self.model.importWords(fname,True) except (IOError, wtexception.FileHandlingExceptions) as e: self.statusBar.showMessage(unicode(e),15000) self.emit(SIGNAL("resizeColumns")) self.emit(SIGNAL("fnameChanged")) def newFile(self): if self.okToContinue(): self.model.removeRows(0,len(self.model.words)) self.initialWords(5) self.addRecentFiles(self.model.fname) self.model.fname = QString() self.model.dirty = False self.emit(SIGNAL("fnameChanged")) def save(self): if self.model.fname.isEmpty(): self.saveAs() else: self.model.removeDuplicates() try: self.model.save() except (pickle.PicklingError, IOError, wtexception.FileHandlingExceptions) as e: self.statusBar.showMessage(str(e),5000) else: self.emit(SIGNAL("fnameChanged")) def saveAs(self): dir = self.model.fname if not self.model.fname.isEmpty() else '.' fname = QFileDialog.getSaveFileName(self, "Word Tester - Save Words", dir, "Word Tester file *.pkl") if fname: if not fname.contains('.'): fname.append('.pkl') self.model.fname = fname self.save() def load(self): """ Loads one or more files. If there are > 1 files to load, the new filename is set to Unknown. :throws: pickle.UnpicklingError, IOError, AttributeError, wtexception.FileHandlingExceptions (from model.import()) """ if not self.okToContinue(): return dir = os.path.dirname(unicode(self.model.fname)) \ if not self.model.fname.isEmpty() else '.' fnames = QFileDialog.getOpenFileNames(self, "Select a file to open", dir, "Word Tester files *.pkl") if not fnames.isEmpty(): try: if len(fnames) != 1: # if the user chose more than one file to open self.model.load(fnames[0]) else: self.addRecentFiles(self.model.fname) self.model.fname = fnames[0] self.model.load() except (pickle.UnpicklingError, IOError, AttributeError, wtexception.FileHandlingExceptions) as e: self.statusBar.showMessage(str(e),5000) # the first file was already loaded fnames.removeAt(0) for fname in fnames: # if there are any left try: self.model.load(fname, True) # true, because we are appending to the fnames[0] except (pickle.UnpicklingError, IOError, AttributeError, wtexception.FileHandlingExceptions) as e: self.statusBar.showMessage(str(e),5000) # if the user loaded more than one file, we set fname as Unknown if len(fnames) != 0: self.model.fname = QString() self.emit(SIGNAL("fnameChanged")) self.emit(SIGNAL("fillTheSize")) def importWords(self): """ Imports one or more .txt files. The current filename is not changed. :throws: IOError, wtexception.FileHandlingExceptions (from model.import()) """ if not self.okToContinue(): return dir = os.path.dirname(unicode(self.model.fname)) \ if not self.model.fname.isEmpty() else '.' fnames = QFileDialog.getOpenFileNames(self, "Select a file to import from", dir, "(UTF-8) *.txt") # the idea is identical as in self.load() if not fnames.isEmpty(): try: self.model.importWords(fnames[0]) except (IOError, wtexception.FileHandlingExceptions) as e: self.statusBar.showMessage(unicode(e),15000) fnames.removeAt(0) for fname in fnames: try: self.model.importWords(fname, True) except (IOError, wtexception.FileHandlingExceptions) as e: self.statusBar.showMessage(unicode(e),15000) self.emit(SIGNAL("fnameChanged")) self.emit(SIGNAL("resizeColumns")) def exportWords(self): """ Exports all visibleWords to the .txt file. :throws: IOError, wtexception.FileHandlingExceptions (from model.export()) """ dir = os.path.dirname(unicode(self.model.fname)) \ if not self.model.fname.isEmpty() else '.' fname = QFileDialog.getSaveFileName(self, "Word Tester - Export to txt", dir, "(UTF-8) *.txt") if fname: if not fname.contains('.'): fname.append('.txt') try: # if all words are visible at the moment if len(self.getVisibleWords()) == len(self.model.words): self.model.exportWords(fname) else: self.model.exportWords(fname, self.getVisibleWords()) except (IOError, wtexception.FileHandlingExceptions) as e: self.statusBar.showMessage(str(e),5000) ######################################################### # PRESENTING DATA ######################################################### def hideWords(self): index = self.wordsTable.selectedIndexes() for item in index: self.wordsTable.hideRow(item.row()) self.emit(SIGNAL("resizeColumns")) def addWords(self): dialog = AddWordsDialog.AddWordsDialog(self) if dialog.exec_(): toAdd = dialog.spinBox.value() row = self.model.rowCount() self.model.insertRows(row, toAdd) self.emit(SIGNAL("fnameChanged")) self.emit(SIGNAL("resizeColumns")) def deleteWords(self): index = self.wordsTable.selectedIndexes() while len(index) != 0: self.model.removeRows(index[0].row(),1,index[0]) index = self.wordsTable.selectedIndexes() self.emit(SIGNAL("fnameChanged")) self.emit(SIGNAL("resizeColumns")) def changeDifficulty(self, difficulty): index = self.wordsTable.selectedIndexes() for item in index: # index can be in any column, by calling item.sibling we take an index # from the same row, but DIFFICULTY column self.model.setData(item.sibling(item.row(),DIFFICULTY),QVariant(difficulty)) self.model.dirty = True self.emit(SIGNAL("fnameChanged")) self.model.reset() def showWordsOnly(self): if self.wordsOnlyCheckBox.isChecked(): for column in (MEANINGS,CONTEXT,DIFFICULTY): self.wordsTable.hideColumn(column) else: for column in (MEANINGS,CONTEXT,DIFFICULTY): self.wordsTable.showColumn(column) # looks likes it unnecessary, but showAllAction is connected to it def showAll(self): self.showGroup() def showGroup(self,text = None): def showRows(self): for row in range(len(self.model.words)): index = self.model.index(row, DIFFICULTY) checked = (self.easyCheckBox.isChecked(),self.mediumCheckBox.isChecked(), \ self.hardCheckBox.isChecked()) difficulty = index.data().toString() if difficulty == 'EASY' and not checked[0]: self.wordsTable.hideRow(row) elif difficulty == 'MEDIUM' and not checked[1]: self.wordsTable.hideRow(row) elif difficulty == 'HARD' and not checked[2]: self.wordsTable.hideRow(row) else: self.wordsTable.showRow(row) # checkBoxes send a signal and their text if text in ('easy','medium','hard'): if self.allCheckBox.isChecked(): self.allCheckBox.setChecked(False) showRows(self) else: # show all words, and uncheck any checked checkBoxes self.allCheckBox.setChecked(True) for row in range(len(self.model.words)): self.wordsTable.showRow(row) for checkBox in (self.easyCheckBox, self.mediumCheckBox, self.hardCheckBox): checkBox.setChecked(False) # one of the checkBoxes always has to be checked if not self.easyCheckBox.isChecked() and not self.mediumCheckBox.isChecked() and not \ self.hardCheckBox.isChecked() and not self.allCheckBox.isChecked(): self.allCheckBox.setChecked(True) # hard coded checking as above apparently doesn't send a signal, so # we hard code a click self.allCheckBox.click() def sortTable(self, section): if section == WORD: self.model.sortByWord() elif section == DIFFICULTY: self.model.sortByDifficulty() # to update visible words for checkBox in (self.easyCheckBox, self.mediumCheckBox, self.hardCheckBox): if checkBox.isChecked(): self.showGroup(checkBox.text().toLower()) break self.emit(SIGNAL("resizeColumns")) def initialWords(self, number = 1): """ Supplies number of empty words, when there is no file to load at the startup. """ row = self.model.rowCount() self.model.insertRows(row, number) index = self.model.index(row, 0) self.model.dirty = False self.wordsTable.setFocus() self.wordsTable.setCurrentIndex(index) ######################################################### # TEST ######################################################### def beginTest(self): """ Prepares WordContainer of words that the test will be performed on. Executes TestDialog. """ visibleWords = self.getVisibleWords() # we don't want any empty words in the test if not visibleWords.validWords(): return None difficulty = self.testDifficultyComboBox.currentText() # some idiot-proof checking if not self.repetitionsCheckBox.isChecked(): if self.numberOfWordsSpinBox.value() > len(visibleWords) \ and difficulty == 'All': QMessageBox.warning(self,"Error!","Maxiumum number of words (without 'Allow repetition')" + \ " is %d." % len(visibleWords)) return if difficulty != 'All' and len(visibleWords.getGroupOfWords(difficulty)) < self.numberOfWordsSpinBox.value(): QMessageBox.warning(self,"Error!","Maxiumum number of words (without 'Allow repetition')" + \ " is %d." % len(visibleWords.getGroupOfWords(difficulty))) return if difficulty == 'All': dialog = TestDialog.TestDialog(visibleWords, self) # in every difficulty there is some more idiot-proof checking # if all goes well create a TestDialog instance, which is later executed elif difficulty == 'Easy': if len(visibleWords.getGroupOfWords(difficulty)) == 0: self.statusBar.showMessage("No words with difficulty: " + difficulty,5000) return None dialog = TestDialog.TestDialog(visibleWords, self, difficulty) elif difficulty == 'Medium': if len(visibleWords.getGroupOfWords(difficulty)) == 0: self.statusBar.showMessage("No words with difficulty: " + difficulty,5000) return None dialog = TestDialog.TestDialog(visibleWords, self, difficulty) elif difficulty == 'Hard': if len(visibleWords.getGroupOfWords(difficulty)) == 0: self.statusBar.showMessage("No words with difficulty: " + difficulty,5000) return None dialog = TestDialog.TestDialog(visibleWords, self, difficulty) if dialog.exec_(): # if the user is working on an Unknown File (not saved), the program # won't record results, without saving the file if not self.model.fname.isEmpty(): self.model.dirty = True QTimer.singleShot(0, self.save) else: ans = QMessageBox.question(self, "Word Tester - Unsaved changes", "Do you want to save the file? If not, results of " + \ "the test won't be saved.", QMessageBox.Yes|QMessageBox.No) if ans == QMessageBox.Yes: self.model.dirty = True QTimer.singleShot(0, self.save) # to update visible words for checkBox in (self.easyCheckBox, self.mediumCheckBox, self.hardCheckBox): if checkBox.isChecked(): self.showGroup(checkBox.text().toLower()) return