def select(self, selection, flags): if isinstance(selection, QModelIndex): selection = QItemSelection(selection, selection) model = self.model() indexes = selection.indexes() sel_inds = {ind.row() for ind in indexes} | \ {ind.column() for ind in indexes} if flags == QItemSelectionModel.ClearAndSelect: selected = set() else: selected = {ind.row() for ind in self.selectedIndexes()} if flags & QItemSelectionModel.Select: selected |= sel_inds elif flags & QItemSelectionModel.Deselect: selected -= sel_inds new_selection = QItemSelection() regions = list(ranges(sorted(selected))) for r_start, r_end in regions: for c_start, c_end in regions: top_left = model.index(r_start, c_start) bottom_right = model.index(r_end - 1, c_end - 1) new_selection.select(top_left, bottom_right) QItemSelectionModel.select(self, new_selection, QItemSelectionModel.ClearAndSelect)
def __init__(self, elements=None, parent=None): ''' Common interface for the labelListModel and the boxListModel see concrete implementations for details :param elements: :param parent: ''' QAbstractTableModel.__init__(self, parent) if elements is None: elements = [] self._elements = list(elements) self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: ind = selected[0].indexes() if len(ind)>0: self.elementSelected.emit(ind[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {} self.unremovable_rows=[] #rows in this list cannot be removed from the gui,
def __init__(self, parent=None): QAbstractListModel.__init__(self, parent) self._layerStack = [] self.selectionModel = QItemSelectionModel(self) self.selectionModel.selectionChanged.connect(self.updateGUI) self.selectionModel.selectionChanged.connect(self._onSelectionChanged) self._movingRows = False QTimer.singleShot(0, self.updateGUI)
def onAnnotationsLoaded(self): self.labeltool.model().dirtyChanged.connect(self.onModelDirtyChanged) self.onModelDirtyChanged(self.labeltool.model().dirty()) self.treeview.setModel(self.labeltool.model()) self.scene.setModel(self.labeltool.model()) self.selectionmodel = QItemSelectionModel(self.labeltool.model()) self.treeview.setSelectionModel(self.selectionmodel) self.treeview.selectionModel().currentChanged.connect(self.labeltool.setCurrentImage) self.property_editor.onModelChanged(self.labeltool.model()) self.startBackgroundLoading()
def select(self, selection, flags): """Reimplemented.""" if isinstance(selection, QModelIndex): selection = QtGui.QItemSelection(selection, selection) model = self.model() indexes = self.selectedIndexes() rows = set(ind.row() for ind in indexes) cols = set(ind.column() for ind in indexes) if flags & QItemSelectionModel.Select and \ not flags & QItemSelectionModel.Clear and self.__selectBlocks: indexes = selection.indexes() sel_rows = set(ind.row() for ind in indexes).union(rows) sel_cols = set(ind.column() for ind in indexes).union(cols) selection = QtGui.QItemSelection() for r_start, r_end in ranges(sorted(sel_rows)): for c_start, c_end in ranges(sorted(sel_cols)): top_left = model.index(r_start, c_start) bottom_right = model.index(r_end - 1, c_end - 1) selection.select(top_left, bottom_right) elif self.__selectBlocks and flags & QItemSelectionModel.Deselect: indexes = selection.indexes() def to_ranges(indices): return list(range(*r) for r in ranges(indices)) selected_rows = to_ranges(sorted(rows)) selected_cols = to_ranges(sorted(cols)) desel_rows = to_ranges(set(ind.row() for ind in indexes)) desel_cols = to_ranges(set(ind.column() for ind in indexes)) selection = QtGui.QItemSelection() # deselection extended vertically for row_range, col_range in \ itertools.product(selected_rows, desel_cols): selection.select( model.index(row_range.start, col_range.start), model.index(row_range.stop - 1, col_range.stop - 1) ) # deselection extended horizontally for row_range, col_range in \ itertools.product(desel_rows, selected_cols): selection.select( model.index(row_range.start, col_range.start), model.index(row_range.stop - 1, col_range.stop - 1) ) QItemSelectionModel.select(self, selection, flags)
def __init__(self, labels=[], parent=None): QAbstractTableModel.__init__(self, parent) self._labels = labels self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: self.labelSelected.emit(selected[0].indexes()[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {}
def __setRowSelected(self, index, selected=True): " Selects a row " if not index.isValid(): return if selected: flags = QItemSelectionModel.SelectionFlags( QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) else: flags = QItemSelectionModel.SelectionFlags( QItemSelectionModel.Deselect | QItemSelectionModel.Rows) self.selectionModel().select(index, flags) return
def __init__(self, parent=None): super(Games, self).__init__(parent) self.selectedGame = None self.onlyPending = True self.setWindowTitle(m18nc('kajongg', 'Games') + ' - Kajongg') self.setObjectName('Games') self.resize(700, 400) self.model = GamesModel(self) if Debug.modelTest: self.modelTest = ModelTest(self.model, self) self.view = MJTableView(self) self.view.setModel(self.model) self.selection = QItemSelectionModel(self.model, self.view) self.view.setSelectionModel(self.selection) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.buttonBox = QDialogButtonBox(self) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.newButton = self.buttonBox.addButton( m18nc('start a new game', "&New"), QDialogButtonBox.ActionRole) self.newButton.setIcon(KIcon("document-new")) self.newButton.clicked.connect(self.accept) self.loadButton = self.buttonBox.addButton(m18n("&Load"), QDialogButtonBox.AcceptRole) self.loadButton.clicked.connect(self.loadGame) self.loadButton.setIcon(KIcon("document-open")) self.deleteButton = self.buttonBox.addButton( m18n("&Delete"), QDialogButtonBox.ActionRole) self.deleteButton.setIcon(KIcon("edit-delete")) self.deleteButton.clicked.connect(self.delete) chkPending = QCheckBox(m18n("Show only pending games"), self) chkPending.setChecked(True) cmdLayout = QHBoxLayout() cmdLayout.addWidget(chkPending) cmdLayout.addWidget(self.buttonBox) layout = QVBoxLayout() layout.addWidget(self.view) layout.addLayout(cmdLayout) self.setLayout(layout) StateSaver(self) self.selection.selectionChanged.connect(self.selectionChanged) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.view.doubleClicked.connect(self.loadGame) chkPending.stateChanged.connect(self.pendingOrNot)
def __init__(self, parent=None): QAbstractListModel.__init__(self, parent) self._layerStack = [] self.selectionModel = QItemSelectionModel(self) self.selectionModel.selectionChanged.connect(self.updateGUI) self.selectionModel.selectionChanged.connect(self._onSelectionChanged) self._movingRows = False QTimer.singleShot(0, self.updateGUI) def _handleRemovedLayer(layer): # Layerstacks *own* the layers they hold, and thus are # responsible for cleaning them up when they are removed: layer.clean_up() self.layerRemoved.connect(_handleRemovedLayer)
def __init__(self, parent = None): QAbstractListModel.__init__(self, parent) self._layerStack = [] self.selectionModel = QItemSelectionModel(self) self.selectionModel.selectionChanged.connect(self.updateGUI) self._movingRows = False QTimer.singleShot(0, self.updateGUI)
def __init__(self, labels = [], parent = None): QAbstractTableModel.__init__(self, parent) self._labels = labels self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: self.labelSelected.emit(selected[0].indexes()[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True
def __init__(self, parent=None, name=None, editable=False): QComboBox.__init__(self, parent) self.setMaxVisibleItems(20) self.setEditable(editable) self.maxWidth = 0 if name is not None: self.setObjectName(name) # save the model, use our view, create a new selection model model = self.model() oldview = self.view() self.setView(ComboListView(self, self)) self.view().setModel(model) self.view().setSelectionModel(QItemSelectionModel(model, self.view()))
def setupTables(self): """ Initialize all the application tablesview """ self.cTableView.setModel(self.cModel) self.cTableView.setItemDelegate(MyQSqlRelationalDelegate(self)) self.cTableView.setColumnHidden(CID, True) self.cTableView.setWordWrap(True) self.cTableView.resizeRowsToContents() self.cTableView.setAlternatingRowColors(True) self.cItmSelModel = QItemSelectionModel(self.cModel) self.cTableView.setSelectionModel(self.cItmSelModel) self.cTableView.setSelectionBehavior(QTableView.SelectRows) self.cTableView.setSortingEnabled(True)
def __init__(self, parent = None): QAbstractListModel.__init__(self, parent) self._layerStack = [] self.selectionModel = QItemSelectionModel(self) self.selectionModel.selectionChanged.connect(self.updateGUI) self.selectionModel.selectionChanged.connect(self._onSelectionChanged) self._movingRows = False QTimer.singleShot(0, self.updateGUI) def _handleRemovedLayer(layer): # Layerstacks *own* the layers they hold, and thus are # responsible for cleaning them up when they are removed: layer.clean_up() self.layerRemoved.connect( _handleRemovedLayer )
def __init__(self, labels=None, parent=None): QAbstractTableModel.__init__(self, parent) if labels is None: labels = [] self._labels = list(labels) self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: self.labelSelected.emit(selected[0].indexes()[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {}
def __init__(self): super(Window, self).__init__() central_widget = QWidget() self._current_path = None self._use_suffix = False self._file_model = QFileSystemModel() self._file_model.setNameFilters(['*.jpg', '*.png']) self._file_model.setNameFilterDisables(False) self._file_model.setRootPath(QDir.rootPath()) self._file_selection_model = QItemSelectionModel(self._file_model) self._file_selection_model.currentChanged.connect(self._on_current_file_changed) self._file_tree = QTreeView(parent=self) self._file_tree.collapsed.connect(self._on_tree_expanded_collapsed) self._file_tree.expanded.connect(self._on_tree_expanded_collapsed) self._file_tree.setModel(self._file_model) self._file_tree.setSelectionModel(self._file_selection_model) self._file_tree.setColumnHidden(1, True) self._file_tree.setColumnHidden(2, True) self._file_tree.setColumnHidden(3, True) self._file_tree.header().hide() self._viewer = Viewer(Loader(24)) self._splitter = QSplitter(); self._splitter.addWidget(self._file_tree) self._splitter.addWidget(self._viewer) self._splitter.setStretchFactor(0, 0) self._splitter.setStretchFactor(1, 1) self._splitter.setCollapsible(0, False) self._layout = QGridLayout() self._layout.addWidget(self._splitter) self._switch_to_normal() central_widget.setLayout(self._layout) self._file_tree.installEventFilter(self); self.resize(800, 600) self.setWindowTitle('pyQtures') self.setCentralWidget(central_widget) self.show()
def __init__(self, parent): QtGui.QDialog.__init__(self) self.ui = Ui_IMDBSearchDialog() self.ui.setupUi(self) self._main = parent QObject.connect(self.ui.searchMovieButton, SIGNAL("clicked(bool)"), self.onSearchMovieButton) QObject.connect(self.ui.movieInfoButton, SIGNAL("clicked(bool)"), self.onMovieInfoButton) QObject.connect(self.ui.okButton, SIGNAL("clicked(bool)"), self.onOkButton) QObject.connect(self.ui.cancelButton, SIGNAL("clicked(bool)"), self.onCancelButton) header = self.ui.searchResultsView.horizontalHeader() header.setResizeMode(QtGui.QHeaderView.Stretch) header.hide() self.ui.searchResultsView.verticalHeader().hide() self.imdbModel = ImdbListModel(self) self.ui.searchResultsView.setModel(self.imdbModel) self.imdbModel._main = self #FIXME: This connection should be cleaner. self.imdbSelectionModel = QItemSelectionModel(self.imdbModel) self.ui.searchResultsView.setSelectionModel(self.imdbSelectionModel) QObject.connect(self.imdbSelectionModel, SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.onIMDBChangeSelection) QObject.connect(self.ui.searchResultsView, SIGNAL("activated(QModelIndex)"), self.onOkButton)
def loadTables(self, tables): """build and use a model around the tables. Show all new tables (no gameid given yet) and all suspended tables that also exist locally. In theory all suspended games should exist locally but there might have been bugs or somebody might have removed the local database like when reinstalling linux""" if not Internal.field: return if Debug.traffic: for table in tables: if table.gameid and not table.gameExistsLocally(): logDebug('%s does not exist locally' % table) tables = [x for x in tables if not x.gameid or x.gameExistsLocally()] tables.sort(key=lambda x: x.tableid) preselectTableId = self.__preselectTableId(tables) self.__keepChatWindows(tables) model = TablesModel(tables) self.view.setModel(model) if Debug.modelTest: self.debugModelTest = ModelTest(model, self.view) selection = QItemSelectionModel(model, self.view) self.view.initView() self.view.setSelectionModel(selection) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSelectionMode(QAbstractItemView.SingleSelection) selection.selectionChanged.connect(self.selectionChanged) if len(tables) == 1: self.selectTable(0) self.startButton.setFocus() elif not tables: self.newButton.setFocus() else: _ = [x for x in tables if x.tableid >= preselectTableId] self.selectTable(tables.index(_[0]) if _ else 0) self.updateButtonsForTable(self.selectedTable()) self.view.setFocus()
def setupTables(self): """ Initialize all the application tablesview """ self.sTableView.setModel(self.sModel) self.sTableView.setItemDelegate(MSDelegate(self)) self.sTableView.setColumnHidden(ID, True) self.sTableView.setColumnHidden(MMID, True) self.sTableView.setWordWrap(True) self.sTableView.resizeRowsToContents() self.sTableView.setAlternatingRowColors(True) self.sItmSelModel = QItemSelectionModel(self.sModel) self.sTableView.setSelectionModel(self.sItmSelModel) self.sTableView.setSelectionBehavior(QTableView.SelectRows) #self.sTableView.setTabKeyNavigation(True) self.fTableView.setModel(self.fModel) self.fTableView.setColumnHidden(ID, True) self.fTableView.setWordWrap(True) self.fTableView.resizeRowsToContents() self.fTableView.setAlternatingRowColors(True) self.fItmSelModel = QItemSelectionModel(self.fModel) self.fTableView.setSelectionModel(self.fItmSelModel)
class Window(QMainWindow): def __init__(self): super(Window, self).__init__() central_widget = QWidget() self._current_path = None self._use_suffix = False self._file_model = QFileSystemModel() self._file_model.setNameFilters(['*.jpg', '*.png']) self._file_model.setNameFilterDisables(False) self._file_model.setRootPath(QDir.rootPath()) self._file_selection_model = QItemSelectionModel(self._file_model) self._file_selection_model.currentChanged.connect(self._on_current_file_changed) self._file_tree = QTreeView(parent=self) self._file_tree.collapsed.connect(self._on_tree_expanded_collapsed) self._file_tree.expanded.connect(self._on_tree_expanded_collapsed) self._file_tree.setModel(self._file_model) self._file_tree.setSelectionModel(self._file_selection_model) self._file_tree.setColumnHidden(1, True) self._file_tree.setColumnHidden(2, True) self._file_tree.setColumnHidden(3, True) self._file_tree.header().hide() self._viewer = Viewer(Loader(24)) self._splitter = QSplitter(); self._splitter.addWidget(self._file_tree) self._splitter.addWidget(self._viewer) self._splitter.setStretchFactor(0, 0) self._splitter.setStretchFactor(1, 1) self._splitter.setCollapsible(0, False) self._layout = QGridLayout() self._layout.addWidget(self._splitter) self._switch_to_normal() central_widget.setLayout(self._layout) self._file_tree.installEventFilter(self); self.resize(800, 600) self.setWindowTitle('pyQtures') self.setCentralWidget(central_widget) self.show() def eventFilter(self, widget, event): if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Tab: self._toggle_path_suffix() return True return QMainWindow.eventFilter(self, widget, event) def _toggle_path_suffix(self): self._use_suffix = not self._use_suffix self._update_path() def _switch_to_fullscreen(self): self._splitter.widget(0).hide() self._layout.setMargin(0) self.showFullScreen() def _switch_to_normal(self): self._splitter.widget(0).show() self._layout.setMargin(4) self.showNormal() def keyPressEvent(self, key_event): # Signal handler. key = key_event.key() if self.isFullScreen(): self._full_screen_key_handler(key) else: self._normal_key_handler(key) def _full_screen_key_handler(self, key): if Qt.Key_Escape == key: self._switch_to_normal() elif Qt.Key_Return == key: self._switch_to_normal() elif Qt.Key_Up == key: self._go_to_sibling_image(-1) elif Qt.Key_Down == key: self._go_to_sibling_image(1) elif Qt.Key_Tab == key: self._toggle_path_suffix() def _go_to_sibling_image(self, offset): current = self._file_selection_model.currentIndex() nxt = current.sibling(current.row() + offset, current.column()) if (nxt.parent() != current.parent()): return # TODO(eustas): Iterate through dirs? self._file_selection_model.setCurrentIndex(nxt, QItemSelectionModel.SelectCurrent) def _normal_key_handler(self, key): if Qt.Key_Escape == key: QCoreApplication.instance().quit() elif Qt.Key_Return == key: self._switch_to_fullscreen() def _on_current_file_changed(self, new_current): new_path = self._file_model.filePath(new_current) if not self._current_path == new_path: self._current_path = new_path self._update_path() def _update_path(self): if not self._use_suffix: self._viewer.set_path(self._current_path) return self._viewer.reset_path() if not self._current_path: return selected_file = QFileInfo(self._current_path) if not selected_file.exists(): return selected_dir = selected_file.absoluteDir() file_name = selected_file.fileName() if not selected_dir.exists(): return if not selected_dir.cd('converted'): return suffixed_path = selected_dir.absoluteFilePath(file_name) self._viewer.set_path(suffixed_path) def _on_tree_expanded_collapsed(self, unused_index): QTimer.singleShot(1, lambda: self._file_tree.resizeColumnToContents(0))
class MainWindow(QMainWindow): def __init__(self, labeltool, parent=None): QMainWindow.__init__(self, parent) self.idletimer = QTimer() self.loader = None self.labeltool = labeltool self.setupGui() self.loadApplicationSettings() self.onAnnotationsLoaded() # Slots def onPluginLoaded(self, action): self.ui.menuPlugins.addAction(action) def onStatusMessage(self, message=''): self.statusBar().showMessage(message, 5000) def onModelDirtyChanged(self, dirty): postfix = "[+]" if dirty else "" if self.labeltool.getCurrentFilename() is not None: self.setWindowTitle("%s - %s %s" % \ (APP_NAME, QFileInfo(self.labeltool.getCurrentFilename()).fileName(), postfix)) else: self.setWindowTitle("%s - Unnamed %s" % (APP_NAME, postfix)) def onMousePositionChanged(self, x, y): self.posinfo.setText("%d, %d" % (x, y)) def startBackgroundLoading(self): self.stopBackgroundLoading(forced=True) self.loader = BackgroundLoader(self.labeltool.model(), self.statusBar(), self.sb_progress) self.idletimer.timeout.connect(self.loader.load) self.loader.finished.connect(self.stopBackgroundLoading) self.statusBar().addWidget(self.sb_progress) self.sb_progress.show() self.idletimer.start() def stopBackgroundLoading(self, forced=False): if not forced: self.statusBar().showMessage("Background loading finished", 5000) self.idletimer.stop() if self.loader is not None: self.idletimer.timeout.disconnect(self.loader.load) self.statusBar().removeWidget(self.sb_progress) self.loader = None def onAnnotationsLoaded(self): self.labeltool.model().dirtyChanged.connect(self.onModelDirtyChanged) self.onModelDirtyChanged(self.labeltool.model().dirty()) self.treeview.setModel(self.labeltool.model()) self.scene.setModel(self.labeltool.model()) self.selectionmodel = QItemSelectionModel(self.labeltool.model()) self.treeview.setSelectionModel(self.selectionmodel) self.treeview.selectionModel().currentChanged.connect(self.labeltool.setCurrentImage) self.property_editor.onModelChanged(self.labeltool.model()) self.startBackgroundLoading() def onCurrentImageChanged(self): new_image = self.labeltool.currentImage() self.scene.setCurrentImage(new_image) self.onFitToWindowModeChanged() self.treeview.scrollTo(new_image.index()) img = self.labeltool.getImage(new_image) if img == None: self.controls.setFilename("") self.selectionmodel.setCurrentIndex(new_image.index(), QItemSelectionModel.ClearAndSelect|QItemSelectionModel.Rows) return h = img.shape[0] w = img.shape[1] self.image_resolution.setText("%dx%d" % (w, h)) # TODO: This info should be obtained from AnnotationModel or LabelTool if isinstance(new_image, FrameModelItem): self.controls.setFrameNumAndTimestamp(new_image.framenum(), new_image.timestamp()) elif isinstance(new_image, ImageFileModelItem): self.controls.setFilename(os.path.basename(new_image['filename'])) self.selectionmodel.setCurrentIndex(new_image.index(), QItemSelectionModel.ClearAndSelect|QItemSelectionModel.Rows) def onFitToWindowModeChanged(self): if self.options["Fit-to-window mode"].isChecked(): self.view.fitInView() def onScaleChanged(self, scale): self.zoominfo.setText("%.2f%%" % (100 * scale, )) def initShortcuts(self, HOTKEYS): self.shortcuts = [] for hotkey in HOTKEYS: assert len(hotkey) >= 2 key = hotkey[0] fun = hotkey[1] desc = "" if len(hotkey) > 2: desc = hotkey[2] if type(fun) == str: fun = import_callable(fun) hk = QAction(desc, self) hk.setShortcut(QKeySequence(key)) hk.setEnabled(True) if hasattr(fun, '__call__'): hk.triggered.connect(bind(fun, self.labeltool)) else: hk.triggered.connect(compose_noargs([bind(f, self.labeltool) for f in fun])) self.ui.menuShortcuts.addAction(hk) self.shortcuts.append(hk) def initOptions(self): self.options = {} for o in ["Fit-to-window mode"]: action = QAction(o, self) action.setCheckable(True) self.ui.menuOptions.addAction(action) self.options[o] = action ### ### GUI/Application setup ###___________________________________________________________________________________________ def setupGui(self): self.ui = uic.loadUi(os.path.join(GUIDIR, "labeltool.ui"), self) # get inserters and items from labels # FIXME for handling the new-style config correctly inserters = dict([(label['attributes']['class'], label['inserter']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'inserter' in label]) items = dict([(label['attributes']['class'], label['item']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'item' in label]) # Property Editor self.property_editor = PropertyEditor(config.LABELS) self.ui.dockProperties.setWidget(self.property_editor) # Scene self.scene = AnnotationScene(self.labeltool, items=items, inserters=inserters) self.property_editor.insertionModeStarted.connect(self.scene.onInsertionModeStarted) self.property_editor.insertionModeEnded.connect(self.scene.onInsertionModeEnded) # SceneView self.view = GraphicsView(self) self.view.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.view.setScene(self.scene) self.central_widget = QWidget() self.central_layout = QVBoxLayout() self.controls = ControlButtonWidget() self.controls.back_button.clicked.connect(self.labeltool.gotoPrevious) self.controls.forward_button.clicked.connect(self.labeltool.gotoNext) self.central_layout.addWidget(self.controls) self.central_layout.addWidget(self.view) self.central_widget.setLayout(self.central_layout) self.setCentralWidget(self.central_widget) self.initShortcuts(config.HOTKEYS) self.initOptions() self.treeview = AnnotationTreeView() self.treeview.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) self.ui.dockAnnotations.setWidget(self.treeview) self.scene.selectionChanged.connect(self.scene.onSelectionChanged) self.treeview.selectedItemsChanged.connect(self.scene.onSelectionChangedInTreeView) self.posinfo = QLabel("-1, -1") self.posinfo.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.posinfo) self.scene.mousePositionChanged.connect(self.onMousePositionChanged) self.image_resolution = QLabel("[no image]") self.image_resolution.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.image_resolution) self.zoominfo = QLabel() self.zoominfo.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.zoominfo) self.view.scaleChanged.connect(self.onScaleChanged) self.onScaleChanged(self.view.getScale()) self.sb_progress = QProgressBar() # View menu self.ui.menu_Views.addAction(self.ui.dockProperties.toggleViewAction()) self.ui.menu_Views.addAction(self.ui.dockAnnotations.toggleViewAction()) # Show the UI. It is important that this comes *after* the above # adding of custom widgets, especially the central widget. Otherwise the # dock widgets would be far to large. self.ui.show() ## connect action signals self.connectActions() def connectActions(self): ## File menu self.ui.actionNew. triggered.connect(self.fileNew) self.ui.actionOpen. triggered.connect(self.fileOpen) self.ui.actionSave. triggered.connect(self.fileSave) self.ui.actionSave_As.triggered.connect(self.fileSaveAs) self.ui.actionExit. triggered.connect(self.close) ## View menu self.ui.actionLocked.toggled.connect(self.onViewsLockedChanged) ## Help menu self.ui.action_About.triggered.connect(self.about) ## Navigation self.ui.action_Add_Image.triggered.connect(self.addMediaFile) self.ui.actionNext. triggered.connect(self.labeltool.gotoNext) self.ui.actionPrevious. triggered.connect(self.labeltool.gotoPrevious) self.ui.actionZoom_In. triggered.connect(functools.partial(self.view.setScaleRelative, 1.2)) self.ui.actionZoom_Out. triggered.connect(functools.partial(self.view.setScaleRelative, 1/1.2)) ## Connections to LabelTool self.labeltool.pluginLoaded. connect(self.onPluginLoaded) self.labeltool.statusMessage. connect(self.onStatusMessage) self.labeltool.annotationsLoaded. connect(self.onAnnotationsLoaded) self.labeltool.currentImageChanged.connect(self.onCurrentImageChanged) ## options menu self.options["Fit-to-window mode"].changed.connect(self.onFitToWindowModeChanged) def loadApplicationSettings(self): settings = QSettings() size = settings.value("MainWindow/Size", QSize(800, 600)) pos = settings.value("MainWindow/Position", QPoint(10, 10)) state = settings.value("MainWindow/State") locked = settings.value("MainWindow/ViewsLocked", False) if isinstance(size, QVariant): size = size.toSize() if isinstance(pos, QVariant): pos = pos.toPoint() if isinstance(state, QVariant): state = state.toByteArray() if isinstance(locked, QVariant): locked = locked.toBool() self.resize(size) self.move(pos) if state is not None: self.restoreState(state) self.ui.actionLocked.setChecked(bool(locked)) def saveApplicationSettings(self): settings = QSettings() settings.setValue("MainWindow/Size", self.size()) settings.setValue("MainWindow/Position", self.pos()) settings.setValue("MainWindow/State", self.saveState()) settings.setValue("MainWindow/ViewsLocked", self.ui.actionLocked.isChecked()) if self.labeltool.getCurrentFilename() is not None: filename = self.labeltool.getCurrentFilename() else: filename = None settings.setValue("LastFile", filename) def okToContinue(self): if self.labeltool.model().dirty(): reply = QMessageBox.question(self, "%s - Unsaved Changes" % (APP_NAME), "Save unsaved changes?", QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel) if reply == QMessageBox.Cancel: return False elif reply == QMessageBox.Yes: return self.fileSave() return True def fileNew(self): if self.okToContinue(): self.labeltool.clearAnnotations() def fileOpen(self): if not self.okToContinue(): return path = '.' filename = self.labeltool.getCurrentFilename() if (filename is not None) and (len(filename) > 0): path = QFileInfo(filename).path() format_str = ' '.join(self.labeltool.getAnnotationFilePatterns()) fname = QFileDialog.getOpenFileName(self, "%s - Load Annotations" % APP_NAME, path, "%s annotation files (%s)" % (APP_NAME, format_str)) if len(str(fname)) > 0: self.labeltool.loadAnnotations(fname) def fileSave(self): filename = self.labeltool.getCurrentFilename() if filename is None: return self.fileSaveAs() return self.labeltool.saveAnnotations(filename) def fileSaveAs(self): fname = '.' # self.annotations.filename() or '.' format_str = ' '.join(self.labeltool.getAnnotationFilePatterns()) fname = QFileDialog.getSaveFileName(self, "%s - Save Annotations" % APP_NAME, fname, "%s annotation files (%s)" % (APP_NAME, format_str)) if len(str(fname)) > 0: return self.labeltool.saveAnnotations(str(fname)) return False def addMediaFile(self): path = '.' filename = self.labeltool.getCurrentFilename() if (filename is not None) and (len(filename) > 0): path = QFileInfo(filename).path() image_types = [ '*.jpg', '*.bmp', '*.png', '*.pgm', '*.ppm', '*.ppm', '*.tif', '*.gif' ] video_types = [ '*.mp4', '*.mpg', '*.mpeg', '*.avi', '*.mov', '*.vob' ] format_str = ' '.join(image_types + video_types) fnames = QFileDialog.getOpenFileNames(self, "%s - Add Media File" % APP_NAME, path, "Media files (%s)" % (format_str, )) item = None numFiles = len(fnames) progress_bar = QProgressDialog('Importing files...', 'Cancel import', 0, numFiles, self) for fname,c in zip(fnames, range(numFiles)): if len(str(fname)) == 0: continue fname = str(fname) if os.path.isabs(fname): fname = os.path.relpath(fname, str(path)) for pattern in image_types: if fnmatch.fnmatch(fname, pattern): item = self.labeltool.addImageFile(fname) progress_bar.setValue(c) if item is None: return self.labeltool.addVideoFile(fname) progress_bar.close() return item def onViewsLockedChanged(self, checked): features = QDockWidget.AllDockWidgetFeatures if checked: features = QDockWidget.NoDockWidgetFeatures self.ui.dockProperties.setFeatures(features) self.ui.dockAnnotations.setFeatures(features) ### ### global event handling ###______________________________________________________________________________ def closeEvent(self, event): if self.okToContinue(): self.saveApplicationSettings() else: event.ignore() def about(self): QMessageBox.about(self, "About %s" % APP_NAME, """<b>%s</b> version %s <p>This labeling application for computer vision research was developed at the CVHCI research group at KIT. <p>For more details, visit our homepage: <a href="%s">%s</a>""" % (APP_NAME, __version__, ORGANIZATION_DOMAIN, ORGANIZATION_DOMAIN))
class LayerStackModel(QAbstractListModel): canMoveSelectedUp = pyqtSignal("bool") canMoveSelectedDown = pyqtSignal("bool") canDeleteSelected = pyqtSignal("bool") orderChanged = pyqtSignal() layerAdded = pyqtSignal(Layer, int) # is now in row layerRemoved = pyqtSignal(Layer, int) # was in row stackCleared = pyqtSignal() def __init__(self, parent=None): QAbstractListModel.__init__(self, parent) self._layerStack = [] self.selectionModel = QItemSelectionModel(self) self.selectionModel.selectionChanged.connect(self.updateGUI) self.selectionModel.selectionChanged.connect(self._onSelectionChanged) self._movingRows = False QTimer.singleShot(0, self.updateGUI) def _handleRemovedLayer(layer): # Layerstacks *own* the layers they hold, and thus are # responsible for cleaning them up when they are removed: layer.clean_up() self.layerRemoved.connect(_handleRemovedLayer) #### ## High level API to manipulate the layerstack ### def __len__(self): return self.rowCount() def __repr__(self): return "<LayerStackModel: layerStack='%r'>" % (self._layerStack, ) def __getitem__(self, i): return self._layerStack[i] def __iter__(self): return self._layerStack.__iter__() def layerIndex(self, layer): #note that the 'index' function already has a different implementation #from Qt side return self._layerStack.index(layer) def findMatchingIndex(self, func): """Call the given function with each layer and return the index of the first layer for which f is True.""" for index, layer in enumerate(self._layerStack): if func(layer): return index raise ValueError("No matching layer in stack.") def append(self, data): self.insert(0, data) def clear(self): if len(self) > 0: self.removeRows(0, len(self)) self.stackCleared.emit() def insert(self, index, data): """ Insert a layer into this layer stack, which *takes ownership* of the layer. """ assert isinstance( data, Layer), "Only Layers can be added to a LayerStackModel" self.insertRow(index) self.setData(self.index(index), data) if self.selectedRow() >= 0: self.selectionModel.select(self.index(self.selectedRow()), QItemSelectionModel.Deselect) self.selectionModel.select(self.index(index), QItemSelectionModel.Select) data.changed.connect( functools.partial(self._onLayerChanged, self.index(index))) index = self._layerStack.index(data) self.layerAdded.emit(data, index) self.updateGUI() def selectRow(self, row): already_selected_rows = self.selectionModel.selectedRows() if len(already_selected_rows) == 1 and row == already_selected_rows[0]: # Nothing to do if this row is already selected return self.selectionModel.clear() self.selectionModel.setCurrentIndex(self.index(row), QItemSelectionModel.SelectCurrent) @pyqtSignature("deleteSelected()") def deleteSelected(self): num_rows = len(self.selectionModel.selectedRows()) assert num_rows == 1, "Can't delete selected row: {} layers are currently selected.".format( num_rows) row = self.selectionModel.selectedRows()[0] layer = self._layerStack[row.row()] assert not layer._cleaned_up, "This layer ({}) has already been cleaned up. Shouldn't it already be removed from the layerstack?".format( layer.name) self.removeRow(row.row()) if self.rowCount() > 0: self.selectionModel.select(self.index(0), QItemSelectionModel.Select) self.layerRemoved.emit(layer, row.row()) self.updateGUI() @pyqtSignature("moveSelectedUp()") def moveSelectedUp(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != 0: oldRow = row.row() newRow = oldRow - 1 self._moveToRow(oldRow, newRow) @pyqtSignature("moveSelectedDown()") def moveSelectedDown(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != self.rowCount() - 1: oldRow = row.row() newRow = oldRow + 1 self._moveToRow(oldRow, newRow) @pyqtSignature("moveSelectedToTop()") def moveSelectedToTop(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != 0: oldRow = row.row() newRow = 0 self._moveToRow(oldRow, newRow) @pyqtSignature("moveSelectedToBottom()") def moveSelectedToBottom(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != self.rowCount() - 1: oldRow = row.row() newRow = self.rowCount() - 1 self._moveToRow(oldRow, newRow) def moveSelectedToRow(self, newRow): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != newRow: oldRow = row.row() self._moveToRow(oldRow, newRow) def _moveToRow(self, oldRow, newRow): d = self._layerStack[oldRow] self.removeRow(oldRow) self.insertRow(newRow) self.setData(self.index(newRow), d) self.selectionModel.select(self.index(newRow), QItemSelectionModel.Select) self.orderChanged.emit() self.updateGUI() #### ## Low level API. To add, remove etc. layers use the high level API from above. #### def updateGUI(self): self.canMoveSelectedUp.emit(self.selectedRow() > 0) self.canMoveSelectedDown.emit(self.selectedRow() < self.rowCount() - 1) self.canDeleteSelected.emit(self.rowCount() > 0) self.wantsUpdate() def selectedRow(self): selected = self.selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=QModelIndex()): if not parent.isValid(): return len(self._layerStack) return 0 def insertRows(self, row, count, parent=QModelIndex()): '''Insert empty rows in the stack. DO NOT USE THIS METHOD TO INSERT NEW LAYERS! Always use the insert() or append() method. ''' if parent.isValid(): return False oldRowCount = self.rowCount() #for some reason, row can be negative! beginRow = max(0, row) endRow = min(beginRow + count - 1, len(self._layerStack)) self.beginInsertRows(parent, beginRow, endRow) while (beginRow <= endRow): self._layerStack.insert(row, Layer(datasources=[])) beginRow += 1 self.endInsertRows() assert self.rowCount( ) == oldRowCount + 1, "oldRowCount = %d, self.rowCount() = %d" % ( oldRowCount, self.rowCount()) return True def removeRows(self, row, count, parent=QModelIndex()): '''Remove rows from the stack. DO NOT USE THIS METHOD TO REMOVE LAYERS! Use the deleteSelected() method instead. ''' if parent.isValid(): return False if row + count <= 0 or row >= len(self._layerStack): return False oldRowCount = self.rowCount() beginRow = max(0, row) endRow = min(row + count - 1, len(self._layerStack) - 1) self.beginRemoveRows(parent, beginRow, endRow) while (beginRow <= endRow): del self._layerStack[row] beginRow += 1 self.endRemoveRows() return True def flags(self, index): defaultFlags = Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled if index.isValid(): return Qt.ItemIsDragEnabled | defaultFlags else: return Qt.ItemIsDropEnabled | defaultFlags def supportedDropActions(self): return Qt.MoveAction def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None if index.row() > len(self._layerStack): return None if role == Qt.DisplayRole or role == Qt.EditRole: return self._layerStack[index.row()] elif role == Qt.ToolTipRole: return self._layerStack[index.row()].toolTip() else: return None def setData(self, index, value, role=Qt.EditRole): '''Replace one layer with another. DO NOT USE THIS METHOD TO INSERT NEW LAYERS! Use deleteSelected() followed by insert() or append(). ''' if role == Qt.EditRole: layer = value if not isinstance(value, Layer): layer = value.toPyObject() self._layerStack[index.row()] = layer self.dataChanged.emit(index, index) return True elif role == Qt.ToolTipRole: self._layerStack[index.row()].setToolTip() return True return False def headerData(self, section, orientation, role=Qt.DisplayRole): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: return "Column %r" % section else: return "Row %r" % section def wantsUpdate(self): self.layoutChanged.emit() def _onLayerChanged(self, idx): self.dataChanged.emit(idx, idx) self.updateGUI() def _onSelectionChanged(self, selected, deselected): for idx in deselected.indexes(): self[idx.row()].setActive(False) for idx in selected.indexes(): self[idx.row()].setActive(True)
class imdbSearchDialog(QtGui.QDialog): def __init__(self, parent): QtGui.QDialog.__init__(self) self.ui = Ui_IMDBSearchDialog() self.ui.setupUi(self) self._main = parent QObject.connect(self.ui.searchMovieButton, SIGNAL("clicked(bool)"), self.onSearchMovieButton) QObject.connect(self.ui.movieInfoButton, SIGNAL("clicked(bool)"), self.onMovieInfoButton) QObject.connect(self.ui.okButton, SIGNAL("clicked(bool)"), self.onOkButton) QObject.connect(self.ui.cancelButton, SIGNAL("clicked(bool)"), self.onCancelButton) header = self.ui.searchResultsView.horizontalHeader() header.setResizeMode(QtGui.QHeaderView.Stretch) header.hide() self.ui.searchResultsView.verticalHeader().hide() self.imdbModel = ImdbListModel(self) self.ui.searchResultsView.setModel(self.imdbModel) self.imdbModel._main = self #FIXME: This connection should be cleaner. self.imdbSelectionModel = QItemSelectionModel(self.imdbModel) self.ui.searchResultsView.setSelectionModel(self.imdbSelectionModel) QObject.connect(self.imdbSelectionModel, SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.onIMDBChangeSelection) QObject.connect(self.ui.searchResultsView, SIGNAL("activated(QModelIndex)"), self.onOkButton) def onSearchMovieButton(self): if not self.ui.movieSearch.text(): QMessageBox.about(self,_("Error"),_("Please fill out the search title")) else: self.setCursor(Qt.WaitCursor) text = self.ui.movieSearch.text() try: results = self._main.OSDBServer.SearchMoviesOnIMDB(str(text.toUtf8())) if not results or not len(results) or not results[0].has_key("id"): #In case of empty results results = [] except: QMessageBox.about(self,_("Error"),_("Error contacting the server. Please try again later")) results = [] self.imdbModel.emit(SIGNAL("layoutAboutToBeChanged()")) self.imdbModel.setImdbResults(results) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) self.imdbModel.emit(SIGNAL("layoutChanged()")) self.ui.searchResultsView.resizeRowsToContents() self.setCursor(Qt.ArrowCursor) def updateButtonsIMDB(self): self.ui.searchResultsView.resizeRowsToContents() selected = self.imdbSelectionModel.selection() if selected.count(): self.imdbModel.rowSelected = selected.last().bottomRight().row() self.ui.movieInfoButton.setEnabled(True) self.ui.okButton.setEnabled(True) else: self.imdbModel.rowSelected = None self.ui.movieInfoButton.setEnabled(False) self.ui.okButton.setEnabled(False) def onIMDBChangeSelection(self, selected, unselected): self.updateButtonsIMDB() def onMovieInfoButton(self): if self.imdbModel.rowSelected == None: QMessageBox.about(self,_("Error"),_("Please search and select a movie from the list")) else: imdbID = self.imdbModel.getSelectedImdb()["id"] webbrowser.open( "http://www.imdb.com/title/tt%s"% imdbID, new=2, autoraise=1) def onOkButton(self): if self.imdbModel.rowSelected == None: QMessageBox.about(self,_("Error"),_("Please search and select a movie from the list")) else: selection = self.imdbModel.getSelectedImdb() self._main.emit(SIGNAL('imdbDetected(QString,QString,QString)'),selection["id"], selection["title"], "search") self.accept() def onCancelButton(self): self.reject()
class LabelListModel(QAbstractTableModel): orderChanged = pyqtSignal() labelSelected = pyqtSignal(int) def __init__(self, labels=[], parent=None): QAbstractTableModel.__init__(self, parent) self._labels = labels self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: self.labelSelected.emit(selected[0].indexes()[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {} def __len__(self): return len(self._labels) def __getitem__(self, i): return self._labels[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._labels) def columnCount(self, parent): return 3 def _getToolTipSuffix(self, row): """ Get the middle column tooltip suffix """ suffix = "; Click to select" if row in self._toolTipSuffixes: suffix = self._toolTipSuffixes[row] return suffix def _setToolTipSuffix(self, row, text): """ Set the middle column tooltip suffix """ self._toolTipSuffixes[row] = text index = self.createIndex(row, 1) self.dataChanged.emit(index, index) class EntryToolTipAdapter(object): """ This class can be used to make each row look like a separate widget with its own tooltip. In this case, the "tooltip" is the suffix appended to the tooltip of the middle column. """ def __init__(self, table, row): self._row = row self._table = table def toolTip(self): return self._table._getToolTipSuffix(self._row) def setToolTip(self, text): self._table._setToolTipSuffix(self._row, text) def data(self, index, role): if role == Qt.EditRole and index.column() == 0: return self._labels[index.row()].color if role == Qt.EditRole and index.column() == 1: return self._labels[index.row()].name if role == Qt.ToolTipRole and index.column() == 0: return "Hex code : " + self._labels[ index.row()].color.name() + "\n DoubleClick to change" if role == Qt.ToolTipRole and index.column() == 1: suffix = self._getToolTipSuffix(index.row()) return self._labels[ index.row()].name + "\n DoubleClick to rename" + suffix if role == Qt.ToolTipRole and index.column() == 2: return "Delete " + self._labels[index.row()].name if role == Qt.DecorationRole and index.column() == 0: row = index.row() value = self._labels[row] pixmap = QPixmap(26, 26) pixmap.fill(value.color) icon = QIcon(pixmap) return icon if role == Qt.DecorationRole and index.column() == 2: row = index.row() pixmap = QPixmap(26, 26) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, 24, 24) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) painter.drawLine(8, 8, 18, 18) painter.drawLine(18, 8, 8, 18) painter.end() icon = QIcon(pixmap) return icon if role == Qt.DisplayRole and index.column() == 1: row = index.row() value = self._labels[row] return value.name def flags(self, index): if index.column() == 0: return Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == 1: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == 2: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags def setData(self, index, value, role=Qt.EditRole): if role == Qt.EditRole and index.column() == 0: row = index.row() color = QColor(value) if color.isValid(): self._labels[row].color = color self.dataChanged.emit(index, index) return True if role == Qt.EditRole and index.column() == 1: row = index.row() name = value self._labels[row].name = str(name.toString()) self.dataChanged.emit(index, index) return True return False def insertRow(self, position, object, parent=QModelIndex()): self.beginInsertRows(parent, position, position) self._labels.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent=QModelIndex()): self.beginRemoveRows(parent, position, position) value = self._labels[position] logger.debug("removing row: " + str(value)) self._labels.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, 2), self.createIndex(self.rowCount(), 2)) def select(self, row): self._selectionModel.clear() self._selectionModel.select(self.index(row, 0), QItemSelectionModel.Select) self._selectionModel.select(self.index(row, 1), QItemSelectionModel.Select)
class ListModel(QAbstractTableModel): orderChanged = pyqtSignal() elementSelected = pyqtSignal(int) class ColumnID(): ''' Define how many column the model holds and their type ''' ncols=2 Name=0 Delete=1 def __init__(self, elements=None, parent=None): ''' Common interface for the labelListModel and the boxListModel see concrete implementations for details :param elements: :param parent: ''' QAbstractTableModel.__init__(self, parent) if elements is None: elements = [] self._elements = list(elements) self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: ind = selected[0].indexes() if len(ind)>0: self.elementSelected.emit(ind[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {} self.unremovable_rows=[] #rows in this list cannot be removed from the gui, # to add to this list call self.makeRowPermanent(int) # to remove make the self.makeRowRemovable(int) def makeRowPermanent(self,rowindex): """ The rowindex cannot be removed from gui to remove this index use self.makeRowRemovable """ self.unremovable_rows.append(rowindex) def makeRowRemovable(self,rowindex): self.unremovable_rows.pop(rowindex) def __len__(self): return len(self._elements) def __getitem__(self, i): return self._elements[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._elements) def columnCount(self, parent): return self.ColumnID.ncols def _getToolTipSuffix(self, row): """ Get the middle column tooltip suffix """ suffix = "; Click to select" if row in self._toolTipSuffixes: suffix = self._toolTipSuffixes[row] return suffix def _setToolTipSuffix(self, row, text): """ Set the middle column tooltip suffix """ self._toolTipSuffixes[row] = text index = self.createIndex(row, 1) self.dataChanged.emit(index, index) class EntryToolTipAdapter(object): """This class can be used to make each row look like a separate widget with its own tooltip. In this case, the "tooltip" is the suffix appended to the tooltip of the middle column. """ def __init__(self, table, row): self._row = row self._table = table def toolTip(self): return self._table._getToolTipSuffix(self._row) def setToolTip(self, text): self._table._setToolTipSuffix(self._row, text) def insertRow(self, position, object, parent=QModelIndex()): self.beginInsertRows(parent, position, position) object.changed.connect(self.modelReset) self._elements.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent=QModelIndex()): if position in self.unremovable_rows: return False self.beginRemoveRows(parent, position, position) value = self._elements[position] logger.debug("removing row: " + str(value)) self._elements.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, self.ColumnID.Delete), self.createIndex(self.rowCount(), self.ColumnID.Delete)) def data(self, index, role): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: :param role: ''' if role == Qt.EditRole and index.column() == self.ColumnID.Name: name = self._elements[index.row()].name return decode_to_qstring(name, 'utf-8') elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Delete: s = "Delete {}".format(self._elements[index.row()].name) return decode_to_qstring(s, 'utf-8') elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Name: suffix = self._getToolTipSuffix(index.row()) s = "{}\nDouble click to rename {}".format( self._elements[index.row()].name, suffix) return decode_to_qstring(s, 'utf-8') elif role == Qt.DisplayRole and index.column() == self.ColumnID.Name: name = self._elements[index.row()].name return decode_to_qstring(name, 'utf-8') if role == Qt.DecorationRole and index.column() == self.ColumnID.Delete: if index.row() in self.unremovable_rows: return row = index.row() pixmap = QPixmap(_NPIXELS, _NPIXELS) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, _NPIXELS - 2, _NPIXELS - 2) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) x = _XSTART y = _NPIXELS - x painter.drawLine(x, x, y, y) painter.drawLine(y, x, x, y) painter.end() icon = QIcon(pixmap) return icon def flags(self, index): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: ''' if index.column() == self.ColumnID.Delete: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags elif index.column() == self.ColumnID.Name: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable def setData(self, index, value, role=Qt.EditRole): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: ''' if role == Qt.EditRole and index.column() == self.ColumnID.Name: row = index.row() # value is a user provided QVariant, possibly with unicode # characters in it. internally, we keep a str self._elements[row].name = encode_from_qstring(value.toString(), 'utf-8') self.dataChanged.emit(index, index) return True return False def select(self, row): ''' Reimplement, see labelListModel or boxListModel for concrete example :param row ''' self._selectionModel.clear() self._selectionModel.select(self.index(row, self.ColumnID.Name), QItemSelectionModel.Select) def clearSelectionModel(self): self._selectionModel.clear()
class Games(QDialog): """a dialog for selecting a game""" def __init__(self, parent=None): super(Games, self).__init__(parent) self.selectedGame = None self.onlyPending = True self.setWindowTitle(m18nc('kajongg', 'Games') + ' - Kajongg') self.setObjectName('Games') self.resize(700, 400) self.model = GamesModel(self) if Debug.modelTest: self.modelTest = ModelTest(self.model, self) self.view = MJTableView(self) self.view.setModel(self.model) self.selection = QItemSelectionModel(self.model, self.view) self.view.setSelectionModel(self.selection) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.buttonBox = QDialogButtonBox(self) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.newButton = self.buttonBox.addButton( m18nc('start a new game', "&New"), QDialogButtonBox.ActionRole) self.newButton.setIcon(KIcon("document-new")) self.newButton.clicked.connect(self.accept) self.loadButton = self.buttonBox.addButton(m18n("&Load"), QDialogButtonBox.AcceptRole) self.loadButton.clicked.connect(self.loadGame) self.loadButton.setIcon(KIcon("document-open")) self.deleteButton = self.buttonBox.addButton( m18n("&Delete"), QDialogButtonBox.ActionRole) self.deleteButton.setIcon(KIcon("edit-delete")) self.deleteButton.clicked.connect(self.delete) chkPending = QCheckBox(m18n("Show only pending games"), self) chkPending.setChecked(True) cmdLayout = QHBoxLayout() cmdLayout.addWidget(chkPending) cmdLayout.addWidget(self.buttonBox) layout = QVBoxLayout() layout.addWidget(self.view) layout.addLayout(cmdLayout) self.setLayout(layout) StateSaver(self) self.selection.selectionChanged.connect(self.selectionChanged) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.view.doubleClicked.connect(self.loadGame) chkPending.stateChanged.connect(self.pendingOrNot) def showEvent(self, dummyEvent): """only now get the data set. Not doing this in__init__ would eventually make it easier to subclass from some generic TableEditor class""" self.setQuery() self.view.initView() self.selectionChanged() def keyPressEvent(self, event): """use insert/delete keys for insert/delete""" key = event.key() if key == Qt.Key_Insert: self.newEntry() return if key == Qt.Key_Delete: self.delete() event.ignore() return QDialog.keyPressEvent(self, event) def selectionChanged(self): """update button states according to selection""" selectedRows = len(self.selection.selectedRows()) self.loadButton.setEnabled(selectedRows == 1) self.deleteButton.setEnabled(selectedRows >= 1) def setQuery(self): """define the query depending on self.OnlyPending""" query = "select g.id, g.starttime, " \ "p0.name||'///'||p1.name||'///'||p2.name||'///'||p3.name " \ "from game g, player p0," \ "player p1, player p2, player p3 " \ "where seed is null" \ " and p0.id=g.p0 and p1.id=g.p1 " \ " and p2.id=g.p2 and p3.id=g.p3 " \ "%s" \ "and exists(select 1 from score where game=g.id)" % \ ("and g.endtime is null " if self.onlyPending else "") self.model.setQuery(query, DBHandle.default) self.model.setHeaderData(1, Qt.Horizontal, QVariant(m18n("Started"))) self.model.setHeaderData(2, Qt.Horizontal, QVariant(m18n("Players"))) self.view.hideColumn(0) def __idxForGame(self, game): """returns the model index for game""" for row in range(self.model.rowCount()): if self.model.record(row).field(0).value().toInt()[0] == game: return self.model.index(row, 0) return self.model.index(0, 0) def pendingOrNot(self, chosen): """do we want to see all games or only pending games?""" if self.onlyPending != chosen: self.onlyPending = chosen idx = self.view.currentIndex() selectedGame = self.model.record(idx.row()).value(0).toInt()[0] self.setQuery() idx = self.__idxForGame(selectedGame) self.view.selectRow(idx.row()) self.view.setFocus() def loadGame(self): """load a game""" selnum = len(self.selection.selectedRows()) if selnum != 1: # should never happen logException('loadGame: %d rows selected' % selnum) idx = self.view.currentIndex() self.selectedGame = self.model.record(idx.row()).value(0).toInt()[0] self.buttonBox.accepted.emit() def delete(self): """delete a game""" def answered(result, games): """question answered, result is True or False""" if result: cmdList = [] for game in games: cmdList.append("DELETE FROM score WHERE game = %d" % game) cmdList.append("DELETE FROM game WHERE id = %d" % game) Query(cmdList) self.setQuery() # just reload entire table deleteGames = list(x.data().toInt()[0] for x in self.view.selectionModel().selectedRows(0)) if len(deleteGames) == 0: # should never happen logException('delete: 0 rows selected') WarningYesNo( m18n("Do you really want to delete <numid>%1</numid> games?<br>" \ "This will be final, you cannot cancel it with the cancel button", len(deleteGames))).addCallback(answered, deleteGames)
class MainWindow(QMainWindow, Ui_MainWindow): ''' Gestione Clienti v.0.2.0 by TIME di Stefano Zamprogno @2009 ''' def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) self.setupMenu() self.restoreWinSettings() self.filename = None self.filtered = False self.db = QSqlDatabase.addDatabase("QSQLITE") self.loadInitialFile() self.setupUiSignals() def keyPressEvent(self, event): if event.key() == Qt.Key_Down: self.addCliRecord() elif event.key() == Qt.Key_Escape: self.cModel.revertAll() self.cModel.select() else: QMainWindow.keyPressEvent(self, event) def setupMenu(self): # AboutBox self.connect(self.action_About, SIGNAL("triggered()"), self.showAboutBox) # FileNew self.connect(self.action_New_File, SIGNAL("triggered()"), self.newFile) # FileLoad self.connect(self.action_Load_File, SIGNAL("triggered()"), self.openFile) def showAboutBox(self): dlg = aboutcli.AboutBox(self) dlg.exec_() def creaStrutturaDB(self): query = QSqlQuery() if not ("clienti" in self.db.tables()): if not query.exec_("""CREATE TABLE clienti ( id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, ragsoc VARCHAR(200) NOT NULL, indirizzo VARCHAR(200) NOT NULL, piva VARCHAR(15), cf VARCHAR(15), tel VARCHAR(30), fax VARCHAR(30), cell VARCHAR(30), email VARCHAR(50), note VARCHAR(100))"""): QMessageBox.warning(self, "Gestione Clienti", QString("Creazione tabella clienti fallita!")) return False QMessageBox.information(self, "Gestione Clienti", QString("Database Creato!")) return True def loadFile(self, fname=None): if fname is None: return if self.db.isOpen(): self.db.close() self.db.setDatabaseName(QString(fname)) if not self.db.open(): QMessageBox.warning(self, "Gestione Clienti", QString("Database Error: %1") .arg(db.lastError().text())) else: if not self.creaStrutturaDB(): return self.filename = unicode(fname) self.setWindowTitle("Gestione Clienti - %s" % self.filename) self.setupModels() self.setupTables() self.restoreTablesSettings() def loadInitialFile(self): settings = QSettings() fname = unicode(settings.value("Settings/lastFile").toString()) if fname and QFile.exists(fname): self.loadFile(fname) def openFile(self): dir = os.path.dirname(self.filename) \ if self.filename is not None else "." fname = QFileDialog.getOpenFileName(self, "Gestione Clienti - Scegli database", dir, "*.db") if fname: self.loadFile(fname) def newFile(self): dir = os.path.dirname(self.filename) \ if self.filename is not None else "." fname = QFileDialog.getSaveFileName(self, "Gestione DDT - Scegli database", dir, "*.db") if fname: self.loadFile(fname) def restoreWinSettings(self): settings = QSettings() self.restoreGeometry( settings.value("MainWindow/Geometry").toByteArray()) def restoreTablesSettings(self): settings = QSettings(self) # per la tablelview for column in range(1, self.cModel.columnCount()-1): width = settings.value("Settings/cTableView/%s" % column, QVariant(60)).toInt()[0] self.cTableView.setColumnWidth(column, width if width > 0 else 60) def closeEvent(self, event): settings = QSettings() settings.setValue("MainWindow/Geometry", QVariant( self.saveGeometry())) if self.filename is not None: settings.setValue("Settings/lastFile", QVariant(self.filename)) if self.db.isOpen(): # salva larghezza colonne tabella for column in range(1, self.cModel.columnCount()-1): width = self.cTableView.columnWidth(column) if width: settings.setValue("Settings/cTableView/%s" % column, QVariant(width)) self.db.close() del self.db def setupModels(self): """ Initialize all the application models """ # setup clientiModel self.cModel = QSqlTableModel(self) self.cModel.setTable(QString("clienti")) self.cModel.setHeaderData(CID, Qt.Horizontal, QVariant("ID")) self.cModel.setHeaderData(CRAGSOC, Qt.Horizontal, QVariant("RagSoc")) self.cModel.setHeaderData(CIND, Qt.Horizontal, QVariant("Indirizzo")) self.cModel.setHeaderData(CPIVA, Qt.Horizontal, QVariant("PIva")) self.cModel.setHeaderData(CCF, Qt.Horizontal, QVariant("CF")) self.cModel.setHeaderData(CTEL, Qt.Horizontal, QVariant("Tel")) self.cModel.setHeaderData(CFAX, Qt.Horizontal, QVariant("Fax")) self.cModel.setHeaderData(CCELL, Qt.Horizontal, QVariant("Cell")) self.cModel.setHeaderData(CEMAIL, Qt.Horizontal, QVariant("Email")) self.cModel.setHeaderData(CNOTE, Qt.Horizontal, QVariant("Note")) self.cModel.select() def setupTables(self): """ Initialize all the application tablesview """ self.cTableView.setModel(self.cModel) self.cTableView.setItemDelegate(MyQSqlRelationalDelegate(self)) self.cTableView.setColumnHidden(CID, True) self.cTableView.setWordWrap(True) self.cTableView.resizeRowsToContents() self.cTableView.setAlternatingRowColors(True) self.cItmSelModel = QItemSelectionModel(self.cModel) self.cTableView.setSelectionModel(self.cItmSelModel) self.cTableView.setSelectionBehavior(QTableView.SelectRows) self.cTableView.setSortingEnabled(True) def updateFilter(self): self.cModel.select() self.cTableView.setColumnHidden(CID, True) def applyFilter(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return filter = ( "ragsoc LIKE '%s' OR " "indirizzo LIKE '%s' OR " "note LIKE '%s'" % ((self.filterLineEdit.text(),)*3)) self.cModel.setFilter(filter) self.filtered = True self.updateFilter() def resetFilter(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return self.filtered = False self.filterLineEdit.setText("") self.cModel.setFilter("") self.updateFilter() def addCliRecord(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return customerIndex = self.cTableView.currentIndex() if self.filtered: self.resetFilter() self.cModel.submitAll() self.cModel.select() row = self.cModel.rowCount() self.cModel.insertRow(row) self.editindex = self.cModel.index(row, CRAGSOC) self.cTableView.setCurrentIndex(self.editindex) self.cTableView.edit(self.editindex) def delCliRecord(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return selrows = self.cItmSelModel.selectedRows() if not selrows: self.statusbar.showMessage( "No selected customers to delete...", 5000) return if(QMessageBox.question(self, "Delete Customers", "Do you want to delete: {0} customer(s)?".format(len(selrows)), QMessageBox.Yes|QMessageBox.No) == QMessageBox.No): return QSqlDatabase.database().transaction() query = QSqlQuery() query.prepare("DELETE FROM clienti WHERE id = :val") for i in selrows: if i.isValid(): query.bindValue(":val", QVariant(i.data().toInt()[0])) query.exec_() QSqlDatabase.database().commit() self.cModel.select() def setupUiSignals(self): self.connect(self.addPushButton, SIGNAL("clicked()"), self.addCliRecord) self.connect(self.delPushButton, SIGNAL("clicked()"), self.delCliRecord) self.connect(self.filterPushButton, SIGNAL("clicked()"), self.applyFilter) self.connect(self.resetPushButton, SIGNAL("clicked()"), self.resetFilter) self.connect(self.filterLineEdit, SIGNAL("returnPressed()"), self.applyFilter)
class LayerStackModel(QAbstractListModel): canMoveSelectedUp = pyqtSignal("bool") canMoveSelectedDown = pyqtSignal("bool") canDeleteSelected = pyqtSignal("bool") orderChanged = pyqtSignal() layerAdded = pyqtSignal( Layer, int ) # is now in row layerRemoved = pyqtSignal( Layer, int ) # was in row stackCleared = pyqtSignal() def __init__(self, parent = None): QAbstractListModel.__init__(self, parent) self._layerStack = [] self.selectionModel = QItemSelectionModel(self) self.selectionModel.selectionChanged.connect(self.updateGUI) self.selectionModel.selectionChanged.connect(self._onSelectionChanged) self._movingRows = False QTimer.singleShot(0, self.updateGUI) def _handleRemovedLayer(layer): # Layerstacks *own* the layers they hold, and thus are # responsible for cleaning them up when they are removed: layer.clean_up() self.layerRemoved.connect( _handleRemovedLayer ) #### ## High level API to manipulate the layerstack ### def __len__(self): return self.rowCount() def __repr__(self): return "<LayerStackModel: layerStack='%r'>" % (self._layerStack,) def __getitem__(self, i): return self._layerStack[i] def __iter__(self): return self._layerStack.__iter__() def layerIndex(self, layer): #note that the 'index' function already has a different implementation #from Qt side return self._layerStack.index(layer) def findMatchingIndex(self, func): """Call the given function with each layer and return the index of the first layer for which f is True.""" for index, layer in enumerate(self._layerStack): if func(layer): return index raise ValueError("No matching layer in stack.") def append(self, data): self.insert(0, data) def clear(self): if len(self) > 0: self.removeRows(0,len(self)) self.stackCleared.emit() def insert(self, index, data): """ Insert a layer into this layer stack, which *takes ownership* of the layer. """ assert isinstance(data, Layer), "Only Layers can be added to a LayerStackModel" self.insertRow(index) self.setData(self.index(index), data) if self.selectedRow() >= 0: self.selectionModel.select(self.index(self.selectedRow()), QItemSelectionModel.Deselect) self.selectionModel.select(self.index(index), QItemSelectionModel.Select) data.changed.connect(functools.partial(self._onLayerChanged, self.index(index))) index = self._layerStack.index(data) self.layerAdded.emit(data, index) self.updateGUI() def selectRow( self, row ): self.selectionModel.setCurrentIndex( self.index(row), QItemSelectionModel.SelectCurrent) @pyqtSignature("deleteSelected()") def deleteSelected(self): num_rows = len(self.selectionModel.selectedRows()) assert num_rows == 1, "Can't delete selected row: {} layers are currently selected.".format( num_rows ) row = self.selectionModel.selectedRows()[0] layer = self._layerStack[row.row()] assert not layer._cleaned_up, "This layer ({}) has already been cleaned up. Shouldn't it already be removed from the layerstack?".format( layer.name ) self.removeRow(row.row()) if self.rowCount() > 0: self.selectionModel.select(self.index(0), QItemSelectionModel.Select) self.layerRemoved.emit( layer, row.row() ) self.updateGUI() @pyqtSignature("moveSelectedUp()") def moveSelectedUp(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != 0: oldRow = row.row() newRow = oldRow - 1 self._moveToRow(oldRow, newRow) @pyqtSignature("moveSelectedDown()") def moveSelectedDown(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != self.rowCount() - 1: oldRow = row.row() newRow = oldRow + 1 self._moveToRow(oldRow, newRow) @pyqtSignature("moveSelectedToTop()") def moveSelectedToTop(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != 0: oldRow = row.row() newRow = 0 self._moveToRow(oldRow, newRow) @pyqtSignature("moveSelectedToBottom()") def moveSelectedToBottom(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != self.rowCount() - 1: oldRow = row.row() newRow = self.rowCount() - 1 self._moveToRow(oldRow, newRow) def moveSelectedToRow(self, newRow): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != newRow: oldRow = row.row() self._moveToRow(oldRow, newRow) def _moveToRow(self, oldRow, newRow): d = self._layerStack[oldRow] self.removeRow(oldRow) self.insertRow(newRow) self.setData(self.index(newRow), d) self.selectionModel.select(self.index(newRow), QItemSelectionModel.Select) self.orderChanged.emit() self.updateGUI() #### ## Low level API. To add, remove etc. layers use the high level API from above. #### def updateGUI(self): self.canMoveSelectedUp.emit(self.selectedRow()>0) self.canMoveSelectedDown.emit(self.selectedRow()<self.rowCount()-1) self.canDeleteSelected.emit(self.rowCount() > 0) self.wantsUpdate() def selectedRow(self): selected = self.selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent = QModelIndex()): if not parent.isValid(): return len(self._layerStack) return 0 def insertRows(self, row, count, parent = QModelIndex()): '''Insert empty rows in the stack. DO NOT USE THIS METHOD TO INSERT NEW LAYERS! Always use the insert() or append() method. ''' if parent.isValid(): return False oldRowCount = self.rowCount() #for some reason, row can be negative! beginRow = max(0,row) endRow = min(beginRow+count-1, len(self._layerStack)) self.beginInsertRows(parent, beginRow, endRow) while(beginRow <= endRow): self._layerStack.insert(row, Layer(datasources=[])) beginRow += 1 self.endInsertRows() assert self.rowCount() == oldRowCount+1, "oldRowCount = %d, self.rowCount() = %d" % (oldRowCount, self.rowCount()) return True def removeRows(self, row, count, parent = QModelIndex()): '''Remove rows from the stack. DO NOT USE THIS METHOD TO REMOVE LAYERS! Use the deleteSelected() method instead. ''' if parent.isValid(): return False if row+count <= 0 or row >= len(self._layerStack): return False oldRowCount = self.rowCount() beginRow = max(0,row) endRow = min(row+count-1, len(self._layerStack)-1) self.beginRemoveRows(parent, beginRow, endRow) while(beginRow <= endRow): del self._layerStack[row] beginRow += 1 self.endRemoveRows() return True def flags(self, index): defaultFlags = Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled if index.isValid(): return Qt.ItemIsDragEnabled | defaultFlags else: return Qt.ItemIsDropEnabled | defaultFlags def supportedDropActions(self): return Qt.MoveAction def data(self, index, role = Qt.DisplayRole): if not index.isValid(): return None if index.row() > len(self._layerStack): return None if role == Qt.DisplayRole or role == Qt.EditRole: return self._layerStack[index.row()] elif role == Qt.ToolTipRole: return self._layerStack[index.row()].toolTip() else: return None def setData(self, index, value, role = Qt.EditRole): '''Replace one layer with another. DO NOT USE THIS METHOD TO INSERT NEW LAYERS! Use deleteSelected() followed by insert() or append(). ''' if role == Qt.EditRole: layer = value if not isinstance(value, Layer): layer = value.toPyObject() self._layerStack[index.row()] = layer self.dataChanged.emit(index, index) return True elif role == Qt.ToolTipRole: self._layerStack[index.row()].setToolTip() return True return False def headerData(self, section, orientation, role = Qt.DisplayRole): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: return "Column %r" % section else: return "Row %r" % section def wantsUpdate(self): self.layoutChanged.emit() def _onLayerChanged( self, idx ): self.dataChanged.emit(idx, idx) self.updateGUI() def _onSelectionChanged(self, selected, deselected): for idx in deselected.indexes(): self[idx.row()].setActive(False) for idx in selected.indexes(): self[idx.row()].setActive(True)
def __init__(self, parent=None, aptinkerQSettings=None): """Constructor""" QDialog.__init__(self, parent=parent) self.setupUi(self) self.setWindowFlags(Qt.Window) # To add Maximize & Minimize buttons self.setWindowTitle('Select Configuration from Database') # Load Startup Preferences for Config Table self.default_config_pref = dict( vis_col_key_list=config.DEF_VIS_COL_KEYS['config_setup'][:]) if osp.exists(PREF_CONFIG_JSON_FILEPATH): with open(PREF_CONFIG_JSON_FILEPATH, 'r') as f: self.config_pref = json.load(f) else: self.config_pref = self.default_config_pref # Load Startup Preferences for Config Meta Table self.default_config_meta_pref = dict( vis_col_key_list=['config_id', 'config_ctime', 'config_name', 'username', 'config_ref_step_size', 'config_masar_id']) if osp.exists(PREF_CONFIG_META_JSON_FILEPATH): with open(PREF_CONFIG_META_JSON_FILEPATH, 'r') as f: self.config_meta_pref = json.load(f) else: self.config_meta_pref = self.default_config_meta_pref self.configDBViewWidget = ConfigDBViewWidget( self.groupBox_selected_conf) self.tableView_config = self.configDBViewWidget.tableView self.configMetaDBViewWidget = ConfigMetaDBViewWidget( self.groupBox_search_result) self.tableView_config_meta = self.configMetaDBViewWidget.tableView self.textEdit_description = \ self.configMetaDBViewWidget.textEdit_description self.settings = QSettings('APHLA', 'TinkerConfigDBSelector') self.loadViewSettings() self._aptinkerQSettings = aptinkerQSettings self.pushButton_search.setIcon(QIcon(':/search.png')) all_ctime_operators = [ self.comboBox_time_created_1.itemText(i) for i in range(self.comboBox_time_created_1.count())] self.comboBox_time_created_1.setCurrentIndex( all_ctime_operators.index('')) self.dateTimeEdit_time_created_1.setDateTime( QDateTime.currentDateTime()) self.dateTimeEdit_time_created_2.setDateTime( QDateTime.currentDateTime()) self.search_params = dict( config_id_1='', config_id_2='', ref_step_size_1='', ref_step_size_2='', config_name='', config_desc='', username='', ctime_1='', ctime_2='', synced_gruop_weight='', masar_id_1='', masar_id_2='') db_id_validator = QIntValidator() db_id_validator.setBottom(1) self.lineEdit_config_id_1.setValidator(db_id_validator) self.lineEdit_config_id_2.setValidator(db_id_validator) self.lineEdit_masar_id_1.setValidator(db_id_validator) self.lineEdit_masar_id_2.setValidator(db_id_validator) self.prev_valid_ref_step_sizes = dict( lineEdit_ref_step_size_1=np.nan, lineEdit_ref_step_size_2=np.nan) # Set up Config Table self.config_model = ConfigModel() self.tableModel_config = self.config_model.table proxyModel = QSortFilterProxyModel() proxyModel.setSourceModel(self.tableModel_config) proxyModel.setDynamicSortFilter(False) tbV = self.tableView_config tbV.setModel(proxyModel) tbV.setItemDelegate(ConfigDBTableViewItemDelegate( tbV, self.tableModel_config, tbV.parent())) self.db = self.config_model.db if '[config_meta_table text view]' not in self.db.getViewNames( square_brackets=True): self.db.create_temp_config_meta_table_text_view() # Set up Config Meta Table self.config_meta_all_col_keys = self.configMetaDBViewWidget.all_col_keys self.search_result = {k: [] for k in self.config_meta_all_col_keys} self.tableModel_config_meta = MetaTableModel( self.search_result, self.configMetaDBViewWidget) proxyModel = QSortFilterProxyModel() proxyModel.setSourceModel(self.tableModel_config_meta) proxyModel.setDynamicSortFilter(False) tbV = self.tableView_config_meta tbV.setModel(proxyModel) self.selectionModel = QItemSelectionModel(proxyModel) tbV.setSelectionModel(self.selectionModel) # Apply Visible Column Preference to Config Table (col_keys, col_names) = config.COL_DEF.getColumnDataFromTable( 'column_table', column_name_list=['column_key','short_descrip_name'], condition_str='column_key in ({0:s})'.format( ','.join(['"{0:s}"'.format(k) for k in self.config_pref['vis_col_key_list']] ) ) ) config_vis_col_name_list = [ col_names[col_keys.index(k)] for k in self.config_pref['vis_col_key_list']] self.configDBViewWidget.on_column_selection_change( config_vis_col_name_list, force_visibility_update=True) # Apply Visible Column Preference to Config Meta Table config_meta_vis_col_name_list = [ self.configMetaDBViewWidget.col_names_wo_desc[ self.configMetaDBViewWidget.col_keys_wo_desc.index(k)] for k in self.config_meta_pref['vis_col_key_list']] self.configMetaDBViewWidget.on_column_selection_change( config_meta_vis_col_name_list, force_visibility_update=True) # Make connection self.connect(self.lineEdit_ref_step_size_1, SIGNAL('editingFinished()'), self.validate_ref_step_size) self.connect(self.lineEdit_ref_step_size_2, SIGNAL('editingFinished()'), self.validate_ref_step_size) self.connect(self.pushButton_search, SIGNAL('clicked()'), self.update_search) self.connect( self.selectionModel, SIGNAL( 'currentRowChanged(const QModelIndex &, const QModelIndex &)'), self.on_selection_change ) self.connect(self.configMetaDBViewWidget, SIGNAL('exportConfigToFile'), self.exportConfigToFile) self.connect(self.configMetaDBViewWidget, SIGNAL('editConfigNameOrDescription'), self.editConfigNameOrDescription)
def __init__(self, parent=None, aptinkerQSettings=None): """Constructor""" QDialog.__init__(self, parent=parent) self.setupUi(self) self.setWindowFlags(Qt.Window) # To add Maximize & Minimize buttons self.setWindowTitle('Select Snapshot from Database') # Load Startup Preferences for Snapshot Table self.default_ss_pref = dict( vis_col_key_list=config.DEF_VIS_COL_KEYS['snapshot_DB'][:]) if osp.exists(PREF_SS_JSON_FILEPATH): with open(PREF_SS_JSON_FILEPATH, 'r') as f: self.ss_pref = json.load(f) else: self.ss_pref = self.default_ss_pref # Load Startup Preferences for Snapshot Meta Table self.default_ss_meta_pref = dict(vis_col_key_list=[ 'ss_id', 'config_id', 'ss_ctime', 'ss_name', 'ss_username' ]) if osp.exists(PREF_SS_META_JSON_FILEPATH): with open(PREF_SS_META_JSON_FILEPATH, 'r') as f: self.ss_meta_pref = json.load(f) else: self.ss_meta_pref = self.default_ss_meta_pref self.ssDBViewWidget = SnapshotDBViewWidget(self.groupBox_selected_ss, DB_selector=True) self.tableView_ss = self.ssDBViewWidget.tableView self.ssMetaDBViewWidget = SnapshotMetaDBViewWidget( self.groupBox_search_result) self.tableView_ss_meta = self.ssMetaDBViewWidget.tableView self.textEdit_description = \ self.ssMetaDBViewWidget.textEdit_description self.settings = QSettings('APHLA', 'TinkerSSDBSelector') self.loadViewSettings() self._aptinkerQSettings = aptinkerQSettings self.pushButton_search.setIcon(QIcon(':/search.png')) all_ctime_operators = [ self.comboBox_time_created_1.itemText(i) for i in range(self.comboBox_time_created_1.count()) ] self.comboBox_time_created_1.setCurrentIndex( all_ctime_operators.index('')) self.dateTimeEdit_time_created_1.setDateTime( QDateTime.currentDateTime()) self.dateTimeEdit_time_created_2.setDateTime( QDateTime.currentDateTime()) self.search_params = dict(ss_id_1='', ss_id_2='', config_id_1='', config_id_2='', ss_ref_step_size_1='', ss_ref_step_size_2='', ss_name='', ss_desc='', ss_username='', ss_ctime_1='', ss_ctime_2='', ss_synced_gruop_weight='', ss_masar_id_1='', ss_masar_id_2='') db_id_validator = QIntValidator() db_id_validator.setBottom(1) self.lineEdit_ss_id_1.setValidator(db_id_validator) self.lineEdit_ss_id_2.setValidator(db_id_validator) self.lineEdit_config_id_1.setValidator(db_id_validator) self.lineEdit_config_id_2.setValidator(db_id_validator) self.lineEdit_masar_id_1.setValidator(db_id_validator) self.lineEdit_masar_id_2.setValidator(db_id_validator) self.prev_valid_ref_step_sizes = dict(lineEdit_ref_step_size_1=np.nan, lineEdit_ref_step_size_2=np.nan) # Set up Snapshot Table self.ss_model = SnapshotModel(self.ss_pref['vis_col_key_list']) self.tableModel_ss = self.ss_model.table proxyModel = QSortFilterProxyModel() proxyModel.setSourceModel(self.tableModel_ss) proxyModel.setDynamicSortFilter(False) tbV = self.tableView_ss tbV.setModel(proxyModel) tbV.setItemDelegate( SnapshotDBTableViewItemDelegate(tbV, self.tableModel_ss, tbV.parent())) #self.db = TinkerMainDatabase() self.db = self.ss_model.db if '[ss_meta_table text view]' not in self.db.getViewNames( square_brackets=True): self.db.create_temp_ss_meta_table_text_view() # Set up Snapshot Meta Table self.ss_meta_all_col_keys = self.ssMetaDBViewWidget.all_col_keys self.search_result = {k: [] for k in self.ss_meta_all_col_keys} self.tableModel_ss_meta = MetaTableModel(self.search_result, self.ssMetaDBViewWidget) proxyModel = QSortFilterProxyModel() proxyModel.setSourceModel(self.tableModel_ss_meta) proxyModel.setDynamicSortFilter(False) tbV = self.tableView_ss_meta tbV.setModel(proxyModel) self.selectionModel = QItemSelectionModel(proxyModel) tbV.setSelectionModel(self.selectionModel) # Apply Visible Column Preference to Snapshot Meta Table ss_meta_vis_col_name_list = [ self.ssMetaDBViewWidget.col_names_wo_desc[ self.ssMetaDBViewWidget.col_keys_wo_desc.index(k)] for k in self.ss_meta_pref['vis_col_key_list'] ] self.ssMetaDBViewWidget.on_column_selection_change( ss_meta_vis_col_name_list, force_visibility_update=True) # Make connection self.connect(self.lineEdit_ref_step_size_1, SIGNAL('editingFinished()'), self.validate_ref_step_size) self.connect(self.lineEdit_ref_step_size_2, SIGNAL('editingFinished()'), self.validate_ref_step_size) self.connect(self.pushButton_search, SIGNAL('clicked()'), self.update_search) self.connect( self.selectionModel, SIGNAL( 'currentRowChanged(const QModelIndex &, const QModelIndex &)'), self.on_selection_change)
class LabelListModel(QAbstractTableModel): orderChanged = pyqtSignal() labelSelected = pyqtSignal(int) def __init__(self, labels = [], parent = None): QAbstractTableModel.__init__(self, parent) self._labels = labels self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: self.labelSelected.emit(selected[0].indexes()[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True def __len__(self): return len(self._labels) def __getitem__(self, i): return self._labels[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._labels) def columnCount(self, parent): return 3 def data(self, index, role): if role == Qt.EditRole and index.column() == 0: return self._labels[index.row()].color if role == Qt.EditRole and index.column() == 1: return self._labels[index.row()].name if role == Qt.ToolTipRole and index.column() == 0: return "Hex code : " + self._labels[index.row()].color.name() + "\n DoubleClick to change" if role == Qt.ToolTipRole and index.column() == 1: return self._labels[index.row()].name + "\n DoubleClick to rename" if role == Qt.ToolTipRole and index.column() == 2: return "Delete " + self._labels[index.row()].name if role == Qt.DecorationRole and index.column() == 0: row = index.row() value = self._labels[row] pixmap = QPixmap(26, 26) pixmap.fill(value.color) icon = QIcon(pixmap) return icon if role == Qt.DecorationRole and index.column() == 2: row = index.row() pixmap = QPixmap(26, 26) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, 24, 24) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) painter.drawLine(8,8, 18,18) painter.drawLine(18,8, 8,18) painter.end() icon = QIcon(pixmap) return icon if role == Qt.DisplayRole and index.column() == 1: row = index.row() value = self._labels[row] return value.name def flags(self, index): if index.column() == 0: return Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == 1: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == 2: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags def setData(self, index, value, role = Qt.EditRole): if role == Qt.EditRole and index.column() == 0: row = index.row() color = QColor(value) if color.isValid(): self._labels[row].color = color self.dataChanged.emit(index, index) return True if role == Qt.EditRole and index.column() == 1: row = index.row() name = value self._labels[row].name = str(name.toString()) self.dataChanged.emit(index, index) return True return False def insertRow(self, position, object, parent = QModelIndex()): self.beginInsertRows(parent, position, position) self._labels.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent = QModelIndex()): self.beginRemoveRows(parent, position, position) value = self._labels[position] print "removing row: ", value self._labels.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, 2), self.createIndex(self.rowCount(), 2)) def select(self, row): self._selectionModel.clear() self._selectionModel.select(self.index(row, 0), QItemSelectionModel.Select) self._selectionModel.select(self.index(row, 1), QItemSelectionModel.Select)
class ConfigDBSelector(QDialog, Ui_Dialog): """""" #---------------------------------------------------------------------- def __init__(self, parent=None, aptinkerQSettings=None): """Constructor""" QDialog.__init__(self, parent=parent) self.setupUi(self) self.setWindowFlags(Qt.Window) # To add Maximize & Minimize buttons self.setWindowTitle('Select Configuration from Database') # Load Startup Preferences for Config Table self.default_config_pref = dict( vis_col_key_list=config.DEF_VIS_COL_KEYS['config_setup'][:]) if osp.exists(PREF_CONFIG_JSON_FILEPATH): with open(PREF_CONFIG_JSON_FILEPATH, 'r') as f: self.config_pref = json.load(f) else: self.config_pref = self.default_config_pref # Load Startup Preferences for Config Meta Table self.default_config_meta_pref = dict(vis_col_key_list=[ 'config_id', 'config_ctime', 'config_name', 'username', 'config_ref_step_size', 'config_masar_id' ]) if osp.exists(PREF_CONFIG_META_JSON_FILEPATH): with open(PREF_CONFIG_META_JSON_FILEPATH, 'r') as f: self.config_meta_pref = json.load(f) else: self.config_meta_pref = self.default_config_meta_pref self.configDBViewWidget = ConfigDBViewWidget( self.groupBox_selected_conf) self.tableView_config = self.configDBViewWidget.tableView self.configMetaDBViewWidget = ConfigMetaDBViewWidget( self.groupBox_search_result) self.tableView_config_meta = self.configMetaDBViewWidget.tableView self.textEdit_description = \ self.configMetaDBViewWidget.textEdit_description self.settings = QSettings('APHLA', 'TinkerConfigDBSelector') self.loadViewSettings() self._aptinkerQSettings = aptinkerQSettings self.pushButton_search.setIcon(QIcon(':/search.png')) all_ctime_operators = [ self.comboBox_time_created_1.itemText(i) for i in range(self.comboBox_time_created_1.count()) ] self.comboBox_time_created_1.setCurrentIndex( all_ctime_operators.index('')) self.dateTimeEdit_time_created_1.setDateTime( QDateTime.currentDateTime()) self.dateTimeEdit_time_created_2.setDateTime( QDateTime.currentDateTime()) self.search_params = dict(config_id_1='', config_id_2='', ref_step_size_1='', ref_step_size_2='', config_name='', config_desc='', username='', ctime_1='', ctime_2='', synced_gruop_weight='', masar_id_1='', masar_id_2='') db_id_validator = QIntValidator() db_id_validator.setBottom(1) self.lineEdit_config_id_1.setValidator(db_id_validator) self.lineEdit_config_id_2.setValidator(db_id_validator) self.lineEdit_masar_id_1.setValidator(db_id_validator) self.lineEdit_masar_id_2.setValidator(db_id_validator) self.prev_valid_ref_step_sizes = dict(lineEdit_ref_step_size_1=np.nan, lineEdit_ref_step_size_2=np.nan) # Set up Config Table self.config_model = ConfigModel() self.tableModel_config = self.config_model.table proxyModel = QSortFilterProxyModel() proxyModel.setSourceModel(self.tableModel_config) proxyModel.setDynamicSortFilter(False) tbV = self.tableView_config tbV.setModel(proxyModel) tbV.setItemDelegate( ConfigDBTableViewItemDelegate(tbV, self.tableModel_config, tbV.parent())) self.db = self.config_model.db if '[config_meta_table text view]' not in self.db.getViewNames( square_brackets=True): self.db.create_temp_config_meta_table_text_view() # Set up Config Meta Table self.config_meta_all_col_keys = self.configMetaDBViewWidget.all_col_keys self.search_result = {k: [] for k in self.config_meta_all_col_keys} self.tableModel_config_meta = MetaTableModel( self.search_result, self.configMetaDBViewWidget) proxyModel = QSortFilterProxyModel() proxyModel.setSourceModel(self.tableModel_config_meta) proxyModel.setDynamicSortFilter(False) tbV = self.tableView_config_meta tbV.setModel(proxyModel) self.selectionModel = QItemSelectionModel(proxyModel) tbV.setSelectionModel(self.selectionModel) # Apply Visible Column Preference to Config Table (col_keys, col_names) = config.COL_DEF.getColumnDataFromTable( 'column_table', column_name_list=['column_key', 'short_descrip_name'], condition_str='column_key in ({0:s})'.format(','.join([ '"{0:s}"'.format(k) for k in self.config_pref['vis_col_key_list'] ]))) config_vis_col_name_list = [ col_names[col_keys.index(k)] for k in self.config_pref['vis_col_key_list'] ] self.configDBViewWidget.on_column_selection_change( config_vis_col_name_list, force_visibility_update=True) # Apply Visible Column Preference to Config Meta Table config_meta_vis_col_name_list = [ self.configMetaDBViewWidget.col_names_wo_desc[ self.configMetaDBViewWidget.col_keys_wo_desc.index(k)] for k in self.config_meta_pref['vis_col_key_list'] ] self.configMetaDBViewWidget.on_column_selection_change( config_meta_vis_col_name_list, force_visibility_update=True) # Make connection self.connect(self.lineEdit_ref_step_size_1, SIGNAL('editingFinished()'), self.validate_ref_step_size) self.connect(self.lineEdit_ref_step_size_2, SIGNAL('editingFinished()'), self.validate_ref_step_size) self.connect(self.pushButton_search, SIGNAL('clicked()'), self.update_search) self.connect( self.selectionModel, SIGNAL( 'currentRowChanged(const QModelIndex &, const QModelIndex &)'), self.on_selection_change) self.connect(self.configMetaDBViewWidget, SIGNAL('exportConfigToFile'), self.exportConfigToFile) self.connect(self.configMetaDBViewWidget, SIGNAL('editConfigNameOrDescription'), self.editConfigNameOrDescription) #---------------------------------------------------------------------- def exportConfigToFile(self): """""" msg = QMessageBox() inds = self.selectionModel.selectedRows() if inds != []: row = inds[0].row() config_id = self.search_result['config_id'][row] saved_filepath = self.config_model.abstract.exportToFile( config_id, self._aptinkerQSettings) if saved_filepath: msg.setText( 'Successfully exported config (ID={0:d}) to the file "{1}".' .format(config_id, saved_filepath)) msg.exec_() else: msg.setText('You must select a configuration to be exported.') msg.setIcon(QMessageBox.Critical) msg.exec_() #---------------------------------------------------------------------- def editConfigNameOrDescription(self): """""" msg = QMessageBox() inds = self.selectionModel.selectedRows() if inds != []: row = inds[0].row() config_id = self.search_result['config_id'][row] title = 'Edit Config Name or Description' prompt = 'Property to Edit:' result = QInputDialog.getItem(self, title, prompt, ['Name', 'Description'], current=1, editable=False) if result[1]: table_name = 'config_meta_text_search_table' if result[0] == 'Name': current_name, current_desc = self.db.getColumnDataFromTable( table_name, column_name_list=['config_name', 'config_description'], condition_str='config_id={0:d}'.format(config_id)) current_name = current_name[0] current_desc = current_desc[0] title = 'Edit Config Name' prompt = 'Enter a new name:' result = QInputDialog.getText(self, title, prompt, text=current_name) if result[1]: new_name = result[0] mod_time_str = datestr(time.time()) new_desc = current_desc + (( '\n### Modified by "{0}" on {1}###\n Config Name ' 'changed from "{2}" to "{3}"\n').format( getusername(), mod_time_str, current_name, new_name)) self.db.changeValues( table_name, 'config_name', '"{0}"'.format(new_name.replace('"', '""')), condition_str='config_id={0:d}'.format(config_id)) self.db.changeValues( table_name, 'config_description', '"{0}"'.format(new_desc.replace('"', '""')), condition_str='config_id={0:d}'.format(config_id)) # Update Config Meta DB Table row = self.search_result['config_id'].index(config_id) self.search_result['config_name'][row] = new_name self.search_result['config_description'][ row] = new_desc self.tableModel_config_meta.repaint() self.on_selection_change(None, None) else: current_desc, = self.db.getColumnDataFromTable( table_name, column_name_list=['config_description'], condition_str='config_id={0:d}'.format(config_id)) current_desc = current_desc[0] dialog = ConfigDescriptionEditor(current_desc, parent=self) dialog.exec_() if dialog.result() == QDialog.Accepted: mod_time_str = datestr(time.time()) temp_new_desc = dialog.plainTextEdit_new.property( 'plainText') new_desc = current_desc + ( ('\n### Modified by "{0}" on {1}###\n{2}\n'.format( getusername(), mod_time_str, temp_new_desc))) self.db.changeValues( table_name, 'config_description', '"{0}"'.format(new_desc.replace('"', '""')), condition_str='config_id={0:d}'.format(config_id)) # Update Config Meta DB Table row = self.search_result['config_id'].index(config_id) self.search_result['config_description'][ row] = new_desc self.tableModel_config_meta.repaint() self.on_selection_change(None, None) else: msg.setText('You must select a configuration whose name or ' 'description to be edited.') msg.setIcon(QMessageBox.Critical) msg.exec_() #---------------------------------------------------------------------- def closeEvent(self, event): """""" self.saveViewSettings() self.config_model.abstract.channel_ids = [] event.accept() #---------------------------------------------------------------------- def accept(self): """""" a = self.config_model.abstract if not a.isDataValid(): return if not a.check_aphla_unitconv_updates(): return self.saveViewSettings() super(ConfigDBSelector, self).accept() # will close the dialog #---------------------------------------------------------------------- def reject(self): """""" self.config_model.abstract.channel_ids = [] self.saveViewSettings() super(ConfigDBSelector, self).reject() # will close the dialog #---------------------------------------------------------------------- def loadViewSettings(self): """""" self.settings.beginGroup('viewSize') rect = self.settings.value('position') if not rect: rect = QRect(0, 0, self.sizeHint().width(), self.sizeHint().height()) self.setGeometry(rect) splitter_sizes = self.settings.value('splitter_1') if splitter_sizes is None: splitter_sizes = [self.width() * (1. / 2), self.width() * (1. / 2)] else: splitter_sizes = [int(s) for s in splitter_sizes] self.splitter.setSizes(splitter_sizes) splitter_sizes = self.settings.value('splitter_2') if splitter_sizes is None: splitter_sizes = [self.width() * (1. / 5), self.width() * (4. / 5)] else: splitter_sizes = [int(s) for s in splitter_sizes] self.splitter_2.setSizes(splitter_sizes) splitter_sizes = self.settings.value('splitter_3') if splitter_sizes is None: splitter_sizes = [ self.configMetaDBViewWidget.height() * (2. / 3), self.configMetaDBViewWidget.height() * (1. / 3) ] else: splitter_sizes = [int(s) for s in splitter_sizes] self.configMetaDBViewWidget.splitter.setSizes(splitter_sizes) self.settings.endGroup() #---------------------------------------------------------------------- def saveViewSettings(self): """""" self.settings.beginGroup('viewSize') self.settings.setValue('position', self.geometry()) self.settings.setValue('splitter_1', self.splitter.sizes()) self.settings.setValue('splitter_2', self.splitter_2.sizes()) self.settings.setValue('splitter_3', self.configMetaDBViewWidget.splitter.sizes()) self.settings.endGroup() #---------------------------------------------------------------------- def validate_ref_step_size(self): """""" sender = self.sender() name = sender.objectName() text = sender.text().strip() if text == '': text = 'nan' try: new_float = float(text) if np.isnan(new_float): sender.setText('') self.prev_valid_ref_step_sizes[name] = new_float except: msg = QMessageBox() msg.setText('Invalid float string: {0:s}'.format(text)) msg.setIcon(QMessageBox.Critical) msg.exec_() if np.isnan(self.prev_valid_ref_step_sizes[name]): sender.setText('') else: sender.setText(str(self.prev_valid_ref_step_sizes[name])) #---------------------------------------------------------------------- def update_search(self): """""" try: config_id_1_text = str(int(self.lineEdit_config_id_1.text())) except: config_id_1_text = '' try: config_id_2_text = str(int(self.lineEdit_config_id_2.text())) except: config_id_2_text = '' config_id_1_operator = \ self.comboBox_config_id_1.currentText().strip() config_id_2_operator = \ self.comboBox_config_id_2.currentText().strip() if (config_id_1_text != '') and (config_id_1_operator != ''): self.search_params['config_id_1'] = ( 'config_id {0:s} {1:s}'.format(config_id_1_operator, config_id_1_text)) else: self.search_params['config_id_1'] = '' if (config_id_2_text != '') and (config_id_2_operator != ''): self.search_params['config_id_2'] = ( 'config_id {0:s} {1:s}'.format(config_id_2_operator, config_id_2_text)) else: self.search_params['config_id_2'] = '' try: masar_id_1_text = str(int(self.lineEdit_masar_id_1.text())) except: masar_id_1_text = '' try: masar_id_2_text = str(int(self.lineEdit_masar_id_2.text())) except: masar_id_2_text = '' masar_id_1_operator = \ self.comboBox_masar_id_1.currentText().strip() masar_id_2_operator = \ self.comboBox_masar_id_2.currentText().strip() if (masar_id_1_text != '') and (masar_id_1_operator != ''): self.search_params['masar_id_1'] = ( 'config_masar_id {0:s} {1:s}'.format(masar_id_1_operator, masar_id_1_text)) else: self.search_params['masar_id_1'] = '' if (masar_id_2_text != '') and (masar_id_2_operator != ''): self.search_params['masar_id_2'] = ( 'config_masar_id {0:s} {1:s}'.format(masar_id_2_operator, masar_id_2_text)) else: self.search_params['masar_id_2'] = '' try: ref_step_size_1_text = '{0:.9e}'.format( float(self.lineEdit_ref_step_size_1.text())) except: ref_step_size_1_text = '' try: ref_step_size_2_text = '{0:.9e}'.format( float(self.lineEdit_ref_step_size_2.text())) except: ref_step_size_2_text = '' ref_step_size_1_operator = \ self.comboBox_ref_step_size_1.currentText().strip() ref_step_size_2_operator = \ self.comboBox_ref_step_size_2.currentText().strip() if (ref_step_size_1_text != '') and (ref_step_size_1_operator != ''): self.search_params['ref_step_size_1'] = ( 'config_ref_step_size {0:s} {1:s}'.format( ref_step_size_1_operator, ref_step_size_1_text)) else: self.search_params['ref_step_size_1'] = '' if (ref_step_size_2_text != '') and (ref_step_size_2_operator != ''): self.search_params['ref_step_size_2'] = ( 'config_ref_step_size {0:s} {1:s}'.format( ref_step_size_2_operator, ref_step_size_2_text)) else: self.search_params['ref_step_size_2'] = '' synced_group_weight_text = \ self.comboBox_synced_group_weight.currentText() if synced_group_weight_text == 'False': self.search_params['synced_group_weight'] = \ 'config_synced_group_weight = 0' elif synced_group_weight_text == 'True': self.search_params['synced_group_weight'] = \ 'config_synced_group_weight = 1' else: self.search_params['synced_group_weight'] = '' config_name_text = self.lineEdit_config_name.text().strip() if config_name_text != '': cond_str = self.db.get_MATCH_condition_str(config_name_text) if cond_str is not None: try: self.search_params['config_name'] = \ self.db.get_config_ids_with_MATCH(cond_str, 'config_name') except: msg = QMessageBox() msg.setText('Invalid search strings for "Config Name"') msg.setInformativeText(sys.exc_info()[1].__repr__()) msg.setIcon(QMessageBox.Critical) msg.exec_() return else: return else: self.search_params['config_name'] = [] config_desc_text = self.lineEdit_config_description.text().strip() if config_desc_text != '': cond_str = self.db.get_MATCH_condition_str(config_desc_text) if cond_str is not None: try: self.search_params['config_description'] = \ self.db.get_config_ids_with_MATCH(cond_str, 'config_description') except: msg = QMessageBox() msg.setText( 'Invalid search strings for "Config Description"') msg.setInformativeText(sys.exc_info()[1].__repr__()) msg.setIcon(QMessageBox.Critical) msg.exec_() return else: return else: self.search_params['config_description'] = [] username_text = self.lineEdit_username.text().strip() if username_text != '': self.search_params['username'] = \ self.db.get_GLOB_condition_str(username_text, 'username') else: self.search_params['username'] = '' ctime_1_operator = self.comboBox_time_created_1.currentText().strip() if ctime_1_operator != '': ctime_epoch_1 = self.dateTimeEdit_time_created_1.dateTime() ctime_epoch_1 = time.mktime( ctime_epoch_1.toPyDateTime().timetuple()) self.search_params['ctime_1'] = ( 'config_ctime {0:s} {1:.3f}'.format(ctime_1_operator, ctime_epoch_1)) else: self.search_params['ctime_1'] = '' ctime_2_operator = self.comboBox_time_created_2.currentText().strip() if ctime_2_operator != '': ctime_epoch_2 = self.dateTimeEdit_time_created_2.dateTime() ctime_epoch_2 = time.mktime( ctime_epoch_2.toPyDateTime().timetuple()) self.search_params['ctime_2'] = ( 'config_ctime {0:s} {1:.3f}'.format(ctime_2_operator, ctime_epoch_2)) else: self.search_params['ctime_2'] = '' if (self.search_params['config_name'] is None) or \ (self.search_params['config_description'] is None): for k in self.config_meta_all_col_keys: self.search_result[k] = [] else: condition_str = '' for k, v in self.search_params.iteritems(): if k in ('config_name', 'config_description'): if v: if condition_str != '': condition_str += ' AND ' condition_str += '(config_id IN ({0:s}))'.format( ','.join([str(i) for i in v])) else: if v: if condition_str != '': condition_str += ' AND ' condition_str += '({0:s})'.format(v) out = self.db.getColumnDataFromTable( '[config_meta_table text view]', column_name_list=self.config_meta_all_col_keys, condition_str=condition_str, order_by_str='config_id') if out != []: if self.checkBox_hide_NG.isChecked(): config_name_col_ind = self.config_meta_all_col_keys.index( 'config_name') valid_indexes = [ i for i, name in enumerate(out[config_name_col_ind]) if '$NG$' not in name ] else: valid_indexes = range(len(out[0])) for k, v in zip(self.config_meta_all_col_keys, out): self.search_result[k] = [ x for i, x in enumerate(v) if i in valid_indexes ] else: for k in self.config_meta_all_col_keys: self.search_result[k] = [] self.tableModel_config_meta.repaint() self.on_selection_change(None, None) #---------------------------------------------------------------------- def convert_GLOB_to_LIKE_wildcards(self, char): """""" if char == '*': return '%' elif char == '?': return '_' else: raise ValueError('Unexpected char: {0:s}'.format(char)) #---------------------------------------------------------------------- def get_LIKE_condition_str(self, glob_pattern, column_name): """""" backslahs_inds = [i for i, c in enumerate(glob_pattern) if c == '\\'] like_pattern = ''.join([ self.convert_GLOB_to_LIKE_wildcards(c) if (c in ('*', '?')) and (i - 1 not in backslahs_inds) else c for i, c in enumerate(glob_pattern) ]) cond_str = '({0:s} LIKE "{1:s}" ESCAPE "\\")'.format( column_name, like_pattern) return cond_str #---------------------------------------------------------------------- def on_selection_change(self, current_index, previous_index): """""" a = self.config_model.abstract if current_index is None: self.textEdit_description.setText('') a.ref_step_size = np.nan (a.group_name_ids, a.channel_ids, a.weights, a.caput_enabled_rows) = [], [], [], [] else: row = current_index.row() a.config_id = self.search_result['config_id'][row] a.name = self.search_result['config_name'][row] a.description = self.search_result['config_description'][row] a.config_ctime = self.search_result['config_ctime'][row] self.textEdit_description.setText(a.description) a.ref_step_size = self.search_result['config_ref_step_size'][row] out = self.db.getColumnDataFromTable( 'config_table', column_name_list=[ 'group_name_id', 'channel_id', 'config_weight', 'config_caput_enabled' ], condition_str='config_id={0:d}'.format( self.search_result['config_id'][row])) if out != []: (a.group_name_ids, a.channel_ids, a.weights, a.caput_enabled_rows) = map(list, out) else: (a.group_name_ids, a.channel_ids, a.weights, a.caput_enabled_rows) = [], [], [], [] a.weights = [w if w is not None else np.nan for w in a.weights] self.config_model.table.updateModel() self.config_model.table.repaint()
class MainWindow(QMainWindow): def __init__(self, labeltool, parent=None): QMainWindow.__init__(self, parent) self.idletimer = QTimer() self.loader = None self.labeltool = labeltool self.setupGui() self.loadApplicationSettings() self.onAnnotationsLoaded() # Slots def onPluginLoaded(self, action): self.ui.menuPlugins.addAction(action) def onStatusMessage(self, message=''): self.statusBar().showMessage(message, 5000) def onModelDirtyChanged(self, dirty): postfix = "[+]" if dirty else "" if self.labeltool.getCurrentFilename() is not None: self.setWindowTitle("%s - %s %s" % \ (APP_NAME, QFileInfo(self.labeltool.getCurrentFilename()).fileName(), postfix)) else: self.setWindowTitle("%s - Unnamed %s" % (APP_NAME, postfix)) def onMousePositionChanged(self, x, y): self.posinfo.setText("%d, %d" % (x, y)) def startBackgroundLoading(self): self.stopBackgroundLoading(forced=True) self.loader = BackgroundLoader(self.labeltool.model(), self.statusBar(), self.sb_progress) self.idletimer.timeout.connect(self.loader.load) self.loader.finished.connect(self.stopBackgroundLoading) self.statusBar().addWidget(self.sb_progress) self.sb_progress.show() self.idletimer.start() def stopBackgroundLoading(self, forced=False): if not forced: self.statusBar().showMessage("Background loading finished", 5000) self.idletimer.stop() if self.loader is not None: self.idletimer.timeout.disconnect(self.loader.load) self.statusBar().removeWidget(self.sb_progress) self.loader = None def onAnnotationsLoaded(self): self.labeltool.model().dirtyChanged.connect(self.onModelDirtyChanged) self.onModelDirtyChanged(self.labeltool.model().dirty()) self.treeview.setModel(self.labeltool.model()) self.scene.setModel(self.labeltool.model()) self.selectionmodel = QItemSelectionModel(self.labeltool.model()) self.treeview.setSelectionModel(self.selectionmodel) self.treeview.selectionModel().currentChanged.connect( self.labeltool.setCurrentImage) self.property_editor.onModelChanged(self.labeltool.model()) self.startBackgroundLoading() def onCurrentImageChanged(self): new_image = self.labeltool.currentImage() self.scene.setCurrentImage(new_image) self.onFitToWindowModeChanged() self.treeview.scrollTo(new_image.index()) img = self.labeltool.getImage(new_image) if img == None: self.controls.setFilename("") self.selectionmodel.setCurrentIndex( new_image.index(), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) return h = img.shape[0] w = img.shape[1] self.image_resolution.setText("%dx%d" % (w, h)) # TODO: This info should be obtained from AnnotationModel or LabelTool if isinstance(new_image, FrameModelItem): self.controls.setFrameNumAndTimestamp(new_image.framenum(), new_image.timestamp()) elif isinstance(new_image, ImageFileModelItem): self.controls.setFilename(os.path.basename(new_image['filename'])) self.selectionmodel.setCurrentIndex( new_image.index(), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) def onFitToWindowModeChanged(self): if self.options["Fit-to-window mode"].isChecked(): self.view.fitInView() def onEnumerateCornersModeChanged(self): if self.options["Enumerate-corners mode"].isChecked(): self.scene.enumerateCorners() self.onCurrentImageChanged() else: self.scene.removeCorners() self.onCurrentImageChanged() def onCopyAnnotationsModeChanged(self): if self.annotationMenu["Copy from previous"].isChecked(): self.copyAnnotations.copy() self.annotationMenu["Copy from previous"].setChecked(False) def onInterpolateRangeModeChanged(self): if self.annotationMenu["Interpolate range"].isChecked(): self.interpolateRange.interpolateRange() self.annotationMenu["Interpolate range"].setChecked(False) def onScaleChanged(self, scale): self.zoominfo.setText("%.2f%%" % (100 * scale, )) def initShortcuts(self, HOTKEYS): self.shortcuts = [] for hotkey in HOTKEYS: assert len(hotkey) >= 2 key = hotkey[0] fun = hotkey[1] desc = "" if len(hotkey) > 2: desc = hotkey[2] if type(fun) == str: fun = import_callable(fun) hk = QAction(desc, self) hk.setShortcut(QKeySequence(key)) hk.setEnabled(True) if hasattr(fun, '__call__'): hk.triggered.connect(bind(fun, self.labeltool)) else: hk.triggered.connect( compose_noargs([bind(f, self.labeltool) for f in fun])) self.ui.menuShortcuts.addAction(hk) self.shortcuts.append(hk) def initOptions(self): self.options = {} for o in ["Fit-to-window mode"]: action = QAction(o, self) action.setCheckable(True) self.ui.menuOptions.addAction(action) self.options[o] = action for o in ["Enumerate-corners mode"]: action = QAction(o, self) action.setCheckable(True) self.ui.menuOptions.addAction(action) self.options[o] = action def initAnnotationMenu(self): self.annotationMenu = {} for a in ["Copy from previous"]: action = QAction(a, self) action.setCheckable(True) self.ui.menuAnnotation.addAction(action) self.annotationMenu[a] = action for a in ["Interpolate range"]: action = QAction(a, self) action.setCheckable(True) self.ui.menuAnnotation.addAction(action) self.annotationMenu[a] = action ### ### GUI/Application setup ###___________________________________________________________________________________________ def setupGui(self): self.ui = uic.loadUi(os.path.join(GUIDIR, "labeltool.ui"), self) # get inserters and items from labels # FIXME for handling the new-style config correctly inserters = dict([ (label['attributes']['class'], label['inserter']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'inserter' in label ]) items = dict([ (label['attributes']['class'], label['item']) for label in config.LABELS if 'class' in label.get('attributes', {}) and 'item' in label ]) # Property Editor self.property_editor = PropertyEditor(config.LABELS) self.ui.dockProperties.setWidget(self.property_editor) # Scene self.scene = AnnotationScene(self.labeltool, items=items, inserters=inserters) self.property_editor.insertionModeStarted.connect( self.scene.onInsertionModeStarted) self.property_editor.insertionModeEnded.connect( self.scene.onInsertionModeEnded) # SceneView self.view = GraphicsView(self) self.view.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.view.setScene(self.scene) self.central_widget = QWidget() self.central_layout = QVBoxLayout() self.controls = ControlButtonWidget() self.controls.back_button.clicked.connect(self.labeltool.gotoPrevious) self.controls.forward_button.clicked.connect(self.labeltool.gotoNext) self.central_layout.addWidget(self.controls) self.central_layout.addWidget(self.view) self.central_widget.setLayout(self.central_layout) self.setCentralWidget(self.central_widget) self.initShortcuts(config.HOTKEYS) self.initOptions() self.initAnnotationMenu() self.treeview = AnnotationTreeView() self.treeview.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) self.ui.dockAnnotations.setWidget(self.treeview) self.scene.selectionChanged.connect(self.scene.onSelectionChanged) self.treeview.selectedItemsChanged.connect( self.scene.onSelectionChangedInTreeView) self.posinfo = QLabel("-1, -1") self.posinfo.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.posinfo) self.scene.mousePositionChanged.connect(self.onMousePositionChanged) self.image_resolution = QLabel("[no image]") self.image_resolution.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.image_resolution) self.zoominfo = QLabel() self.zoominfo.setFrameStyle(QFrame.StyledPanel) self.statusBar().addPermanentWidget(self.zoominfo) self.view.scaleChanged.connect(self.onScaleChanged) self.onScaleChanged(self.view.getScale()) self.sb_progress = QProgressBar() # View menu self.ui.menu_Views.addAction(self.ui.dockProperties.toggleViewAction()) self.ui.menu_Views.addAction( self.ui.dockAnnotations.toggleViewAction()) # Annotation menu self.copyAnnotations = CopyAnnotations(self.labeltool) self.interpolateRange = InterpolateRange(self.labeltool) # Show the UI. It is important that this comes *after* the above # adding of custom widgets, especially the central widget. Otherwise the # dock widgets would be far to large. self.ui.show() ## connect action signals self.connectActions() def connectActions(self): ## File menu self.ui.actionNew.triggered.connect(self.fileNew) self.ui.actionOpen.triggered.connect(self.fileOpen) self.ui.actionSave.triggered.connect(self.fileSave) self.ui.actionSave_As.triggered.connect(self.fileSaveAs) self.ui.actionExit.triggered.connect(self.close) ## View menu self.ui.actionLocked.toggled.connect(self.onViewsLockedChanged) ## Help menu self.ui.action_About.triggered.connect(self.about) ## Navigation self.ui.action_Add_Image.triggered.connect(self.addMediaFile) self.ui.actionNext.triggered.connect(self.labeltool.gotoNext) self.ui.actionPrevious.triggered.connect(self.labeltool.gotoPrevious) self.ui.actionZoom_In.triggered.connect( functools.partial(self.view.setScaleRelative, 1.2)) self.ui.actionZoom_Out.triggered.connect( functools.partial(self.view.setScaleRelative, 1 / 1.2)) ## Connections to LabelTool self.labeltool.pluginLoaded.connect(self.onPluginLoaded) self.labeltool.statusMessage.connect(self.onStatusMessage) self.labeltool.annotationsLoaded.connect(self.onAnnotationsLoaded) self.labeltool.currentImageChanged.connect(self.onCurrentImageChanged) ## options menu self.options["Fit-to-window mode"].changed.connect( self.onFitToWindowModeChanged) self.options["Enumerate-corners mode"].changed.connect( self.onEnumerateCornersModeChanged) ## annotation menu self.annotationMenu["Copy from previous"].changed.connect( self.onCopyAnnotationsModeChanged) self.annotationMenu["Interpolate range"].changed.connect( self.onInterpolateRangeModeChanged) def loadApplicationSettings(self): settings = QSettings() size = settings.value("MainWindow/Size", QSize(800, 600)) pos = settings.value("MainWindow/Position", QPoint(10, 10)) state = settings.value("MainWindow/State") locked = settings.value("MainWindow/ViewsLocked", False) if isinstance(size, QVariant): size = size.toSize() if isinstance(pos, QVariant): pos = pos.toPoint() if isinstance(state, QVariant): state = state.toByteArray() if isinstance(locked, QVariant): locked = locked.toBool() self.resize(size) self.move(pos) if state is not None: self.restoreState(state) self.ui.actionLocked.setChecked(bool(locked)) def saveApplicationSettings(self): settings = QSettings() settings.setValue("MainWindow/Size", self.size()) settings.setValue("MainWindow/Position", self.pos()) settings.setValue("MainWindow/State", self.saveState()) settings.setValue("MainWindow/ViewsLocked", self.ui.actionLocked.isChecked()) if self.labeltool.getCurrentFilename() is not None: filename = self.labeltool.getCurrentFilename() else: filename = None settings.setValue("LastFile", filename) def okToContinue(self): if self.labeltool.model().dirty(): reply = QMessageBox.question( self, "%s - Unsaved Changes" % (APP_NAME), "Save unsaved changes?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if reply == QMessageBox.Cancel: return False elif reply == QMessageBox.Yes: return self.fileSave() return True def fileNew(self): if self.okToContinue(): self.labeltool.clearAnnotations() def fileOpen(self): if not self.okToContinue(): return path = '.' filename = self.labeltool.getCurrentFilename() if (filename is not None) and (len(filename) > 0): path = QFileInfo(filename).path() format_str = ' '.join(self.labeltool.getAnnotationFilePatterns()) fname = QFileDialog.getOpenFileName( self, "%s - Load Annotations" % APP_NAME, path, "%s annotation files (%s)" % (APP_NAME, format_str)) if len(str(fname)) > 0: self.labeltool.loadAnnotations(fname) def fileSave(self): filename = self.labeltool.getCurrentFilename() if filename is None: return self.fileSaveAs() return self.labeltool.saveAnnotations(filename) def fileSaveAs(self): fname = '.' # self.annotations.filename() or '.' format_str = ' '.join(self.labeltool.getAnnotationFilePatterns()) fname = QFileDialog.getSaveFileName( self, "%s - Save Annotations" % APP_NAME, fname, "%s annotation files (%s)" % (APP_NAME, format_str)) if len(str(fname)) > 0: return self.labeltool.saveAnnotations(str(fname)) return False def addMediaFile(self): filename = self.labeltool.getCurrentFilename() image_types = [ '*.jpg', '*.bmp', '*.png', '*.pgm', '*.ppm', '*.ppm', '*.tif', '*.gif' ] video_types = [ '*.mp4', '*.mpg', '*.mpeg', '*.avi', '*.mov', '*.vob', '*.json' ] format_str = ' '.join(image_types + video_types) fnames = QFileDialog.getOpenFileNames( self, "%s - Add Media File" % APP_NAME, path, "Media files (%s)" % (format_str, )) item = None numFiles = len(fnames) progress_bar = QProgressDialog('Importing files...', 'Cancel import', 0, numFiles, self) for fname, c in zip(fnames, range(numFiles)): if len(str(fname)) == 0: continue fname = str(fname) if os.path.isabs(fname): fname = os.path.relpath(fname) for pattern in image_types: if fnmatch.fnmatch(fname, pattern): item = self.labeltool.addImageFile(fname) progress_bar.setValue(c) if item is None: item = self.labeltool.addVideoFile(fname, progress_bar) progress_bar.close() return item def onViewsLockedChanged(self, checked): features = QDockWidget.AllDockWidgetFeatures if checked: features = QDockWidget.NoDockWidgetFeatures self.ui.dockProperties.setFeatures(features) self.ui.dockAnnotations.setFeatures(features) ### ### global event handling ###______________________________________________________________________________ def closeEvent(self, event): if self.okToContinue(): self.saveApplicationSettings() else: event.ignore() def about(self): QMessageBox.about( self, "About %s" % APP_NAME, """<b>%s</b> version %s <p>This labeling application for computer vision research was developed at the CVHCI research group at KIT. <p>For more details, visit our homepage: <a href="%s">%s</a>""" % (APP_NAME, __version__, ORGANIZATION_DOMAIN, ORGANIZATION_DOMAIN))
class LabelListModel(QAbstractTableModel): orderChanged = pyqtSignal() labelSelected = pyqtSignal(int) def __init__(self, labels=None, parent=None): QAbstractTableModel.__init__(self, parent) if labels is None: labels = [] self._labels = list(labels) self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: self.labelSelected.emit(selected[0].indexes()[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {} def __len__(self): return len(self._labels) def __getitem__(self, i): return self._labels[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._labels) def columnCount(self, parent): return 3 def _getToolTipSuffix(self, row): """ Get the middle column tooltip suffix """ suffix = "; Click to select" if row in self._toolTipSuffixes: suffix = self._toolTipSuffixes[row] return suffix def _setToolTipSuffix(self, row, text): """ Set the middle column tooltip suffix """ self._toolTipSuffixes[row] = text index = self.createIndex(row, 1) self.dataChanged.emit(index, index) class EntryToolTipAdapter(object): """This class can be used to make each row look like a separate widget with its own tooltip. In this case, the "tooltip" is the suffix appended to the tooltip of the middle column. """ def __init__(self, table, row): self._row = row self._table = table def toolTip(self): return self._table._getToolTipSuffix(self._row) def setToolTip(self, text): self._table._setToolTipSuffix(self._row, text) def data(self, index, role): if role == Qt.EditRole and index.column() == ColumnID.Color: return (self._labels[index.row()].brushColor(), self._labels[index.row()].pmapColor()) if role == Qt.EditRole and index.column() == ColumnID.Name: return self._labels[index.row()].name if role == Qt.ToolTipRole and index.column() == ColumnID.Color: return ("Hex code : {}\nDouble click to change".format( self._labels[index.row()].brushColor().name())) if role == Qt.ToolTipRole and index.column() == ColumnID.Name: suffix = self._getToolTipSuffix(index.row()) return "{}\nDouble click to rename {}".format( self._labels[index.row()].name, suffix) if role == Qt.ToolTipRole and index.column() == ColumnID.Delete: return "Delete {}".format(self._labels[index.row()].name) if role == Qt.DecorationRole and index.column() == ColumnID.Color: row = index.row() value = self._labels[row] if value.brushColor == value.pmapColor(): pixmap = QPixmap(_NPIXELS, _NPIXELS) pixmap.fill(value.brushColor) else: a = value.brushColor().rgba() b = value.pmapColor().rgba() img = QImage(_NPIXELS,_NPIXELS, QImage.Format_RGB32) for i in range(_NPIXELS): for j in range(0, _NPIXELS - i): img.setPixel(i, j, a) for i in range(_NPIXELS): for j in range(_NPIXELS - i, _NPIXELS): img.setPixel(i, j, b) pixmap = QPixmap.fromImage(img) icon = QIcon(pixmap) return icon if role == Qt.DecorationRole and index.column() == ColumnID.Delete: row = index.row() pixmap = QPixmap(_NPIXELS, _NPIXELS) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, _NPIXELS - 2, _NPIXELS - 2) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) x = _XSTART y = _NPIXELS - x painter.drawLine(x, x, y, y) painter.drawLine(y, x, x, y) painter.end() icon = QIcon(pixmap) return icon if role == Qt.DisplayRole and index.column() == ColumnID.Name: row = index.row() value = self._labels[row] return value.name def flags(self, index): if index.column() == ColumnID.Color: return Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == ColumnID.Name: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == ColumnID.Delete: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags def setData(self, index, value, role=Qt.EditRole): if role == Qt.EditRole and index.column() == ColumnID.Color: row = index.row() brushColor = QColor(value[0]) pmapColor = QColor(value[1]) if brushColor.isValid() and pmapColor.isValid(): print "setData: brushColor = {}, pmapColor = {}".format( brushColor.name(), pmapColor.name()) print " self._labels[row] has type {}".format( type(self._labels[row])) self._labels[row].setBrushColor(brushColor) self._labels[row].setPmapColor(pmapColor) print " self._labels[row].brushColor = {}".format( self._labels[row].brushColor().name()) print " self._labels[row].pmapColor = {}".format( self._labels[row].pmapColor().name()) self.dataChanged.emit(index, index) return True if role == Qt.EditRole and index.column() == ColumnID.Name: row = index.row() name = value self._labels[row].name = str(name.toString()) self.dataChanged.emit(index, index) return True return False def insertRow(self, position, object, parent=QModelIndex()): self.beginInsertRows(parent, position, position) self._labels.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent=QModelIndex()): self.beginRemoveRows(parent, position, position) value = self._labels[position] logger.debug("removing row: " + str(value)) self._labels.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, ColumnID.Delete), self.createIndex(self.rowCount(), ColumnID.Delete)) def select(self, row): self._selectionModel.clear() self._selectionModel.select(self.index(row, ColumnID.Color), QItemSelectionModel.Select) self._selectionModel.select(self.index(row, ColumnID.Name), QItemSelectionModel.Select)
class MainWindow(QMainWindow, magazzino_ui.Ui_MainWindow): FIRST, PREV, NEXT, LAST = range(4) Clipboard = [] # lista di oggetti def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) self.setupMenu() self.restoreWinSettings() self.editindex = None self.filename = None self.db = QSqlDatabase.addDatabase("QSQLITE") self.loadInitialFile() self.setupUiSignals() def keyPressEvent(self, event): if event.key() == Qt.Key_Down: self.addDettRecord() else: QMainWindow.keyPressEvent(self, event) def creaStrutturaDB(self): query = QSqlQuery() if not ("magamaster" in self.db.tables()): if not query.exec_("""CREATE TABLE magamaster ( id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, scaff VARCHAR(10) NOT NULL)"""): QMessageBox.warning(self, "Magazzino", QString("Creazione tabella fallita!")) return False if not ("magaslave" in self.db.tables()): if not query.exec_("""CREATE TABLE magaslave ( id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, datains DATE NOT NULL, abbi VARCHAR(50), angro VARCHAR(50), desc VARCHAR(100), qt INTEGER NOT NULL DEFAULT '1', imp DOUBLE NOT NULL DEFAULT '0.0', equiv VARCHAR(100), mmid INTEGER NOT NULL, fatt VARCHAR(50), note VARCHAR(200), FOREIGN KEY (mmid) REFERENCES magamaster)"""): QMessageBox.warning(self, "Magazzino", QString("Creazione tabella fallita!")) return False QMessageBox.information(self, "Magazzino", QString("Database Creato!")) return True def loadFile(self, fname=None): if fname is None: return if self.db.isOpen(): self.db.close() self.db.setDatabaseName(QString(fname)) if not self.db.open(): QMessageBox.warning(self, "Magazzino", QString("Database Error: %1") .arg(self.db.lastError().text())) else: if not self.creaStrutturaDB(): return self.filename = unicode(fname) self.setWindowTitle("Gestione Magazzino - %s" % self.filename) self.setupModels() self.setupMappers() self.setupTables() #self.setupItmSignals() self.restoreTablesSettings() self.mmUpdate() def loadInitialFile(self): settings = QSettings() fname = unicode(settings.value("Settings/lastFile").toString()) if fname and QFile.exists(fname): self.loadFile(fname) def openFile(self): dir = os.path.dirname(self.filename) \ if self.filename is not None else "." fname = QFileDialog.getOpenFileName(self, "Gestione Magazzino - Scegli database", dir, "*.db") if fname: self.loadFile(fname) def newFile(self): dir = os.path.dirname(self.filename) \ if self.filename is not None else "." fname = QFileDialog.getSaveFileName(self, "Gestione Magazzino - Scegli database", dir, "*.db") if fname: self.loadFile(fname) def setupMenu(self): # AboutBox self.connect(self.actionA_bout, SIGNAL("triggered()"), self.showAboutBox) # FileNew self.connect(self.action_New_File, SIGNAL("triggered()"), self.newFile) # FileLoad self.connect(self.action_Load_File, SIGNAL("triggered()"), self.openFile) def showAboutBox(self): dlg = aboutmaga.AboutBox(self) dlg.exec_() def printInventory(self): ''' Print Inventory ''' if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return querygrp = QSqlQuery() querydett = QSqlQuery() querygrp.exec_("SELECT abbi,qt,imp,sum(qt*imp) " "FROM magaslave GROUP BY abbi") querydett.prepare("SELECT datains,abbi,angro,desc,qt,imp " "FROM magaslave WHERE abbi = :abbi AND " "qt > 0 ORDER BY datains") from reportlab.pdfgen.canvas import Canvas from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import cm from reportlab.lib.enums import TA_LEFT,TA_RIGHT,TA_CENTER from reportlab.platypus import Spacer, SimpleDocTemplate from reportlab.platypus import Table, TableStyle, Paragraph from reportlab.rl_config import defaultPageSize from reportlab.lib import colors PAGE_WIDTH, PAGE_HEIGHT=defaultPageSize styles = getSampleStyleSheet() styleN = styles['Normal'] styleH = styles['Heading1'] styleH.alignment=TA_CENTER Elements = [] #add some flowables p=Paragraph ps=ParagraphStyle Title = unicode(self.prtTitleLineEdit.text()) Year = unicode(self.prtDateLineEdit.text()) Author = "Stefano Zamprogno" URL = "http://www.zamprogno.it/" email = "*****@*****.**" pageinfo = "%s / %s / %s" % (Author, email, Title) def myFirstPage(canvas, doc): canvas.saveState() canvas.setStrokeColorRGB(0.50,0.50,0.50) canvas.setLineWidth(10) canvas.line(45,72,45,PAGE_HEIGHT-72) #canvas.setFont('Times-Bold',16) #canvas.drawCentredString(3*cm, 1.5*cm,Title) canvas.setFont('Times-Roman',9) canvas.drawString(3*cm, 1.5*cm, "First Page / %s" % pageinfo) canvas.restoreState() def myLaterPages(canvas, doc): canvas.saveState() canvas.setStrokeColorRGB(0.50,0.50,0.50) canvas.setLineWidth(5) canvas.line(45,72,45,PAGE_HEIGHT-72) canvas.setFont('Times-Roman',9) canvas.drawString(3*cm, 1.5*cm, "Page %d %s" % (doc.page, pageinfo)) canvas.restoreState() Elements.append(Paragraph(Title, styleH)) Elements.append(Paragraph(Year,styleN)) Elements.append(Spacer(0.5*cm, 0.5*cm)) tot=0 while querygrp.next(): tot += querygrp.value(3).toDouble()[0] querydett.bindValue(":abbi", QVariant(querygrp.value(0).toString())) querydett.exec_() data = [['Abbi', 'Angro', 'Descrizione', 'Qt', 'Imp'],] while querydett.next(): data.append([ p(unicode(querydett.value(1).toString()), ps(name='Normal')), p(unicode(querydett.value(2).toString()), ps(name='Normal')), p(unicode(querydett.value(3).toString()), ps(name='Normal')), querydett.value(4).toInt()[0], unicode("%.2f" % querydett.value(5).toDouble()[0])]) data.append([None, None, unicode("GRUPPO '%s'" % querygrp.value(0).toString()), unicode("Subtotale:"), unicode("€ %.2f" % querygrp.value(3).toDouble()[0])]) Elements.append(Table(data,repeatRows=1, style=(['LINEBELOW', (3,-2), (-1,-2), 1, colors.black], ['LINEBELOW', (0,0), (-1,0), 1, colors.black], ['ALIGN', (1,0), (3,-1),'CENTER'], ['ALIGN', (4,0), (-1,0),'RIGHT'], ['VALIGN', (0,0), (-1,-1), 'TOP'], ['ALIGN', (4,0), (-1,-1), 'RIGHT'], # ['TEXTCOLOR', (0,0), (-1,0), # colors.red], ['BACKGROUND',(0,0),(-1,0), colors.lightgrey], ['GRID',(0,0),(-1,-1), 0.2, colors.black], ['FONT', (0, 0), (-1, 0), 'Helvetica-Bold', 10], ['FONT', (3, -1), (3, -1), 'Helvetica-Bold', 10]))) Elements.append(Spacer(0.5*cm, 0.5*cm)) Elements.append(Paragraph("<para align=right><b>TOTALE GENERALE:" "€ %.2f</b></para>" % tot, styleN)) doc = SimpleDocTemplate(os.path.join(os.path.dirname(__file__), 'mydoc.pdf')) doc.build(Elements,onFirstPage=myFirstPage, onLaterPages=myLaterPages) subprocess.Popen(['gnome-open',os.path.join(os.path.dirname(__file__), 'mydoc.pdf')]) def setupMappers(self): ''' Initialize all the application mappers ''' self.mapper = QDataWidgetMapper(self) self.mapper.setModel(self.mModel) self.mapper.addMapping(self.scaffLineEdit, SCAFF) self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit) self.mapper.toFirst() def setupTables(self): """ Initialize all the application tablesview """ self.sTableView.setModel(self.sModel) self.sTableView.setItemDelegate(MSDelegate(self)) self.sTableView.setColumnHidden(ID, True) self.sTableView.setColumnHidden(MMID, True) self.sTableView.setWordWrap(True) self.sTableView.resizeRowsToContents() self.sTableView.setAlternatingRowColors(True) self.sItmSelModel = QItemSelectionModel(self.sModel) self.sTableView.setSelectionModel(self.sItmSelModel) self.sTableView.setSelectionBehavior(QTableView.SelectRows) #self.sTableView.setTabKeyNavigation(True) self.fTableView.setModel(self.fModel) self.fTableView.setColumnHidden(ID, True) self.fTableView.setWordWrap(True) self.fTableView.resizeRowsToContents() self.fTableView.setAlternatingRowColors(True) self.fItmSelModel = QItemSelectionModel(self.fModel) self.fTableView.setSelectionModel(self.fItmSelModel) def setupModels(self): """ Initialize all the application models """ # setup slaveModel self.sModel = ssModel(self) self.sModel.setTable(QString("magaslave")) self.sModel.setHeaderData(ID, Qt.Horizontal, QVariant("ID")) self.sModel.setHeaderData(DATAINS, Qt.Horizontal, QVariant("DataIns")) self.sModel.setHeaderData(ABBI, Qt.Horizontal, QVariant("Abbi")) self.sModel.setHeaderData(ANGRO, Qt.Horizontal, QVariant("Angro")) self.sModel.setHeaderData(DESC, Qt.Horizontal, QVariant("Desc")) self.sModel.setHeaderData(QT, Qt.Horizontal, QVariant("Qt")) self.sModel.setHeaderData(IMP, Qt.Horizontal, QVariant("Imp")) self.sModel.setHeaderData(EQUIV, Qt.Horizontal, QVariant("Equiv")) self.sModel.setHeaderData(MMID, Qt.Horizontal, QVariant("ScaffId")) self.sModel.setHeaderData(FATT, Qt.Horizontal, QVariant("Fatt")) self.sModel.setHeaderData(NOTE, Qt.Horizontal, QVariant("Note")) self.sModel.setSort(DATAINS, Qt.AscendingOrder) self.sModel.setEditStrategy(QSqlTableModel.OnRowChange) self.sModel.select() # setup masterModel self.mModel = QSqlTableModel(self) self.mModel.setTable(QString("magamaster")) self.mModel.setSort(SCAFF, Qt.AscendingOrder) self.mModel.setHeaderData(ID, Qt.Horizontal, QVariant("ID")) self.mModel.setHeaderData(SCAFF, Qt.Horizontal, QVariant("Scaff")) self.mModel.select() # setup findModel self.fModel = QSqlRelationalTableModel(self) self.fModel.setTable(QString("magaslave")) self.fModel.setHeaderData(ID, Qt.Horizontal, QVariant("ID")) self.fModel.setHeaderData(DATAINS, Qt.Horizontal, QVariant("DataIns")) self.fModel.setHeaderData(ABBI, Qt.Horizontal, QVariant("Abbi")) self.fModel.setHeaderData(ANGRO, Qt.Horizontal, QVariant("Angro")) self.fModel.setHeaderData(DESC, Qt.Horizontal, QVariant("Desc")) self.fModel.setHeaderData(QT, Qt.Horizontal, QVariant("Qt")) self.fModel.setHeaderData(IMP, Qt.Horizontal, QVariant("Imp")) self.fModel.setHeaderData(EQUIV, Qt.Horizontal, QVariant("Equiv")) self.fModel.setHeaderData(MMID, Qt.Horizontal, QVariant("ScaffId")) self.fModel.setHeaderData(FATT, Qt.Horizontal, QVariant("Fatt")) self.fModel.setHeaderData(NOTE, Qt.Horizontal, QVariant("Note")) self.fModel.setSort(MMID, Qt.AscendingOrder) self.fModel.setRelation(MMID, QSqlRelation("magamaster", "id", "scaff")) self.fModel.select() def clipCopy(self): self.Clipboard = self.sTableView.selectedIndexes() selrows = self.sItmSelModel.selectedRows() # TODO : da usare: selrows = self.sItmSelModel.selectedRows() print(selrows, len(selrows)) print(len(self.Clipboard)) # FIXME : bla bla bla def clipDel(self): self.delDettRecord() def clipPaste(self): pass def ctxtMenu(self, point): menu = QMenu(self) copyAction = menu.addAction("&Copy") self.connect(copyAction, SIGNAL("triggered()"), self.clipCopy) delAction = menu.addAction("&Del") self.connect(delAction, SIGNAL("triggered()"), self.clipDel) if len(self.Clipboard) > 0: pasteAction = menu.addAction("&Paste") self.connect(pasteAction, SIGNAL("triggered()"), self.clipPaste) menu.exec_(self.sTableView.mapToGlobal(point)) def setupUiSignals(self): self.sTableView.setContextMenuPolicy(Qt.CustomContextMenu) self.connect(self.sTableView, SIGNAL( "customContextMenuRequested(const QPoint &)"), self.ctxtMenu) self.connect(self.scaffLineEdit, SIGNAL("returnPressed()"), lambda: self.saveRecord(MainWindow.FIRST)) self.connect(self.findLineEdit, SIGNAL("returnPressed()"), self.globalFilter) self.connect(self.printPushButton, SIGNAL("clicked()"), self.printInventory) self.connect(self.createFilterPushButton, SIGNAL("clicked()"), self.createFilter) self.connect(self.findPushButton, SIGNAL("clicked()"), self.applyFilter) self.connect(self.gSearchPushButton, SIGNAL("clicked()"), self.globalFilter) self.connect(self.addscaffPushButton, SIGNAL("clicked()"), self.addScaffRecord) self.connect(self.adddettPushButton, SIGNAL("clicked()"), self.addDettRecord) self.connect(self.deldettPushButton, SIGNAL("clicked()"), self.delDettRecord) self.connect(self.delscaffPushButton, SIGNAL("clicked()"), self.delScaffRecord) self.connect(self.scaffFirstPushButton, SIGNAL("clicked()"), lambda: self.saveRecord(MainWindow.FIRST)) self.connect(self.scaffPrevPushButton, SIGNAL("clicked()"), lambda: self.saveRecord(MainWindow.PREV)) self.connect(self.scaffNextPushButton, SIGNAL("clicked()"), lambda: self.saveRecord(MainWindow.NEXT)) self.connect(self.scaffLastPushButton, SIGNAL("clicked()"), lambda: self.saveRecord(MainWindow.LAST)) def globalFilter(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return txt = self.findLineEdit.text() qry = ("(datains like '%s') OR " "(abbi like '%s') OR " "(angro like '%s') OR " "(desc like '%s') OR " "(equiv like '%s') OR" "(fatt like '%s') OR" "(note like '%s')") % ((txt,)*7) self.fModel.setFilter(qry) self.updateFilter() def updateFilter(self): self.fModel.select() self.fTableView.setColumnHidden(ID, True) def applyFilter(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return self.fModel.setFilter(self.findLineEdit.text()) self.updateFilter() def createFilter(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return headerDef = ("datains VARCHAR(100)", "abbi VARCHAR(100)", "angro VARCHAR(100)", "desc VARCHAR(100)", "qt VARCHAR(100)", "imp VARCHAR(100)", "equiv VARCHAR(100)", "fatt VARCHAR(100)", "note VARCHAR(100)") dlg = filterdialog.FilterDialog(headerDef, QSqlDatabase.database(), self) if(dlg.exec_()): self.findLineEdit.setText(dlg.filterDone() if dlg.filterDone() else "") self.applyFilter() #~ def editEsc(self, idxcur, idxold): #~ if self.editindex and self.editindex.isValid(): #~ if idxcur.row() != self.editindex.row(): #~ self.sModel.revertAll() #~ self.editindex = None def mmUpdate(self): row = self.mapper.currentIndex() id = self.mModel.data(self.mModel.index(row,ID)).toString() self.sModel.setFilter("mmid=%s" % id) self.sModel.select() self.sTableView.setColumnHidden(ID, True) self.sTableView.setColumnHidden(MMID, True) def saveRecord(self, where): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return row = self.mapper.currentIndex() self.mapper.submit() self.sModel.revertAll() if where == MainWindow.FIRST: row=0 elif where == MainWindow.PREV: row = 0 if row <= 1 else row - 1 elif where == MainWindow.NEXT: row += 1 if row >= self.mModel.rowCount(): row = self.mModel.rowCount() -1 elif where == MainWindow.LAST: row = self.mModel.rowCount()- 1 self.mapper.setCurrentIndex(row) self.mmUpdate() def addScaffRecord(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return row = self.mModel.rowCount() self.mapper.submit() self.mModel.insertRow(row) self.mapper.setCurrentIndex(row) self.scaffLineEdit.setFocus() self.mmUpdate() def addDettRecord(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return rowscaff = self.mapper.currentIndex() record = self.mModel.record(rowscaff) masterid = record.value(ID).toInt()[0] if masterid < 1: self.statusbar.showMessage( "Scaffale non valido o non confermato...", 5000) self.scaffLineEdit.setFocus() return # aggiunge la nuova riga alla vista self.sModel.submitAll() self.sModel.select() row = self.sModel.rowCount() self.sModel.insertRow(row) if row > 1: precfatt = self.sModel.data(self.sModel.index(row-1, FATT)) else: precfatt = '' if row > 1: lastData = self.sModel.data(self.sModel.index(row-1, DATAINS)) else: lastData = '' self.sModel.setData(self.sModel.index(row, MMID), QVariant(masterid)) self.sModel.setData(self.sModel.index(row, QT), QVariant(1)) self.sModel.setData(self.sModel.index(row, IMP), QVariant(0.0)) self.sModel.setData(self.sModel.index(row, FATT), QVariant(precfatt)) self.editindex = self.sModel.index(row, DATAINS) self.sTableView.setCurrentIndex(self.editindex) self.sTableView.edit(self.editindex) def delDettRecord(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return selrows = self.sItmSelModel.selectedRows() if not selrows: self.statusbar.showMessage( "No articles selected to delete...", 5000) return if(QMessageBox.question(self, "Cancella Articoli", "Vuoi cancellare: {0} articoli?".format(len(selrows)), QMessageBox.Yes|QMessageBox.No) == QMessageBox.No): return QSqlDatabase.database().transaction() query = QSqlQuery() query.prepare("DELETE FROM magaslave WHERE id = :val") for i in selrows: if i.isValid(): query.bindValue(":val", QVariant(i.data().toInt()[0])) query.exec_() QSqlDatabase.database().commit() self.sModel.revertAll() self.mmUpdate() def delScaffRecord(self): if not self.db.isOpen(): self.statusbar.showMessage( "Database non aperto...", 5000) return row = self.mapper.currentIndex() if row == -1: self.statusbar.showMessage( "Nulla da cancellare...", 5000) return record = self.mModel.record(row) id = record.value(ID).toInt()[0] scaff = record.value(SCAFF).toString() if(QMessageBox.question(self, "Cancella Scaffale", "Vuoi cancellare lo scaffale: {0} ?".format(scaff), QMessageBox.Yes|QMessageBox.No) == QMessageBox.No): self.statusbar.showMessage( "Cancellazione scaffale annullata...", 5000) return # cancella scaffale self.mModel.removeRow(row) self.mModel.submitAll() if row + 1 >= self.mModel.rowCount(): row = self.mModel.rowCount() - 1 self.mapper.setCurrentIndex(row) if self.mModel.rowCount() == 0: self.scaffLineEdit.setText(QString("")) # cancella tutti gli articoli che si riferiscono # allo scaffale cancellato self.sModel.setFilter("mmid=%s" % id) self.sModel.select() self.sModel.removeRows(0, self.sModel.rowCount()) self.sModel.submitAll() self.statusbar.showMessage( "Cancellazione eseguita...", 5000) self.mmUpdate() def restoreTablesSettings(self): settings = QSettings(self) if self.saveTableGeometryCheckBox.isChecked(): # per la tabella slave for c in range(1, self.sModel.columnCount()-1): width = settings.value("Settings/sTableView/%s" % c, QVariant(60)).toInt()[0] self.sTableView.setColumnWidth(c, width if width > 0 else 60) # per la tabella find for c in range(1, self.fModel.columnCount()): width = settings.value("Settings/fTableView/%s" % c, QVariant(60)).toInt()[0] self.fTableView.setColumnWidth(c, width if width > 0 else 60) def restoreWinSettings(self): settings = QSettings() self.prtTitleLineEdit.setText(QString(settings.value( "Settings/printTitle", QVariant( "Situazione Magazzino - TIME di Stefano Zamprogno")).toString())) self.prtDateLineEdit.setText(QString(settings.value( "Settings/printDate", QVariant( "Al 31/12/2008")).toString())) self.saveWinPosCheckBox.setChecked( settings.value("Settings/saveWinPos", QVariant(True)).toBool()) self.saveTableGeometryCheckBox.setChecked( settings.value("Settings/saveTableGeometry", QVariant(True)).toBool()) self.restoreGeometry( settings.value("MainWindow/Geometry").toByteArray()) def closeEvent(self, event): settings = QSettings() if self.filename is not None: settings.setValue("Settings/lastFile", QVariant(self.filename)) settings.setValue("MainWindow/Geometry", QVariant( self.saveGeometry())) settings.setValue("Settings/saveWinPos", QVariant( self.saveWinPosCheckBox.isChecked())) settings.setValue("Settings/saveTableGeometry", QVariant( self.saveTableGeometryCheckBox.isChecked())) settings.setValue("Settings/printTitle", QVariant( self.prtTitleLineEdit.text())) settings.setValue("Settings/printDate", QVariant( self.prtDateLineEdit.text())) if self.db.isOpen(): # salva larghezza colonne tabella slave for c in range(1, self.sModel.columnCount()-1): width = self.sTableView.columnWidth(c) if width: settings.setValue("Settings/sTableView/%s" % c, QVariant(width)) # salva larghezza colonne tabella find for c in range(1, self.fModel.columnCount()): width = self.fTableView.columnWidth(c) if width: settings.setValue("Settings/fTableView/%s" % c, QVariant(width)) self.db.close() del self.db
def __init__(self, model, parent=None): QItemSelectionModel.__init__(self, model, parent) self.selectionChanged.connect(self.onSelectionChanged)
class ListModel(QAbstractTableModel): orderChanged = pyqtSignal() elementSelected = pyqtSignal(int) class ColumnID(): ''' Define how many column the model holds and their type ''' ncols=2 Name=0 Delete=1 def __init__(self, elements=None, parent=None): ''' Common interface for the labelListModel and the boxListModel see concrete implementations for details :param elements: :param parent: ''' QAbstractTableModel.__init__(self, parent) if elements is None: elements = [] self._elements = list(elements) self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: ind = selected[0].indexes() if len(ind)>0: self.elementSelected.emit(ind[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {} self.unremovable_rows=[] #rows in this list cannot be removed from the gui, # to add to this list call self.makeRowPermanent(int) # to remove make the self.makeRowRemovable(int) def makeRowPermanent(self,rowindex): """ The rowindex cannot be removed from gui to remove this index use self.makeRowRemovable """ self.unremovable_rows.append(rowindex) def makeRowRemovable(self,rowindex): self.unremovable_rows.pop(rowindex) def __len__(self): return len(self._elements) def __getitem__(self, i): return self._elements[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._elements) def columnCount(self, parent): return self.ColumnID.ncols def _getToolTipSuffix(self, row): """ Get the middle column tooltip suffix """ suffix = "; Click to select" if row in self._toolTipSuffixes: suffix = self._toolTipSuffixes[row] return suffix def _setToolTipSuffix(self, row, text): """ Set the middle column tooltip suffix """ self._toolTipSuffixes[row] = text index = self.createIndex(row, 1) self.dataChanged.emit(index, index) class EntryToolTipAdapter(object): """This class can be used to make each row look like a separate widget with its own tooltip. In this case, the "tooltip" is the suffix appended to the tooltip of the middle column. """ def __init__(self, table, row): self._row = row self._table = table def toolTip(self): return self._table._getToolTipSuffix(self._row) def setToolTip(self, text): self._table._setToolTipSuffix(self._row, text) def insertRow(self, position, object, parent=QModelIndex()): self.beginInsertRows(parent, position, position) object.changed.connect(self.modelReset) self._elements.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent=QModelIndex()): if position in self.unremovable_rows: return False self.beginRemoveRows(parent, position, position) value = self._elements[position] logger.debug("removing row: " + str(value)) self._elements.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, self.ColumnID.Delete), self.createIndex(self.rowCount(), self.ColumnID.Delete)) def data(self, index, role): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: :param role: ''' if role == Qt.EditRole and index.column() == self.ColumnID.Name: return self._elements[index.row()].name elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Delete: return "Delete {}".format(self._elements[index.row()].name) elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Name: suffix = self._getToolTipSuffix(index.row()) return "{}\nDouble click to rename {}".format( self._elements[index.row()].name, suffix) elif role == Qt.DisplayRole and index.column() == self.ColumnID.Name: row = index.row() value = self._elements[row] return value.name if role == Qt.DecorationRole and index.column() == self.ColumnID.Delete: if index.row() in self.unremovable_rows: return row = index.row() pixmap = QPixmap(_NPIXELS, _NPIXELS) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, _NPIXELS - 2, _NPIXELS - 2) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) x = _XSTART y = _NPIXELS - x painter.drawLine(x, x, y, y) painter.drawLine(y, x, x, y) painter.end() icon = QIcon(pixmap) return icon def flags(self, index): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: ''' if index.column() == self.ColumnID.Delete: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags elif index.column() == self.ColumnID.Name: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable def setData(self, index, value, role=Qt.EditRole): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: ''' if role == Qt.EditRole and index.column() == self.ColumnID.Name: row = index.row() name = value self._elements[row].name = str(name.toString()) self.dataChanged.emit(index, index) return True return False def select(self, row): ''' Reimplement, see labelListModel or boxListModel for concrete example :param row ''' self._selectionModel.clear() self._selectionModel.select(self.index(row, self.ColumnID.Name), QItemSelectionModel.Select) def clearSelectionModel(self): self._selectionModel.clear()
class ConfigDBSelector(QDialog, Ui_Dialog): """""" #---------------------------------------------------------------------- def __init__(self, parent=None, aptinkerQSettings=None): """Constructor""" QDialog.__init__(self, parent=parent) self.setupUi(self) self.setWindowFlags(Qt.Window) # To add Maximize & Minimize buttons self.setWindowTitle('Select Configuration from Database') # Load Startup Preferences for Config Table self.default_config_pref = dict( vis_col_key_list=config.DEF_VIS_COL_KEYS['config_setup'][:]) if osp.exists(PREF_CONFIG_JSON_FILEPATH): with open(PREF_CONFIG_JSON_FILEPATH, 'r') as f: self.config_pref = json.load(f) else: self.config_pref = self.default_config_pref # Load Startup Preferences for Config Meta Table self.default_config_meta_pref = dict( vis_col_key_list=['config_id', 'config_ctime', 'config_name', 'username', 'config_ref_step_size', 'config_masar_id']) if osp.exists(PREF_CONFIG_META_JSON_FILEPATH): with open(PREF_CONFIG_META_JSON_FILEPATH, 'r') as f: self.config_meta_pref = json.load(f) else: self.config_meta_pref = self.default_config_meta_pref self.configDBViewWidget = ConfigDBViewWidget( self.groupBox_selected_conf) self.tableView_config = self.configDBViewWidget.tableView self.configMetaDBViewWidget = ConfigMetaDBViewWidget( self.groupBox_search_result) self.tableView_config_meta = self.configMetaDBViewWidget.tableView self.textEdit_description = \ self.configMetaDBViewWidget.textEdit_description self.settings = QSettings('APHLA', 'TinkerConfigDBSelector') self.loadViewSettings() self._aptinkerQSettings = aptinkerQSettings self.pushButton_search.setIcon(QIcon(':/search.png')) all_ctime_operators = [ self.comboBox_time_created_1.itemText(i) for i in range(self.comboBox_time_created_1.count())] self.comboBox_time_created_1.setCurrentIndex( all_ctime_operators.index('')) self.dateTimeEdit_time_created_1.setDateTime( QDateTime.currentDateTime()) self.dateTimeEdit_time_created_2.setDateTime( QDateTime.currentDateTime()) self.search_params = dict( config_id_1='', config_id_2='', ref_step_size_1='', ref_step_size_2='', config_name='', config_desc='', username='', ctime_1='', ctime_2='', synced_gruop_weight='', masar_id_1='', masar_id_2='') db_id_validator = QIntValidator() db_id_validator.setBottom(1) self.lineEdit_config_id_1.setValidator(db_id_validator) self.lineEdit_config_id_2.setValidator(db_id_validator) self.lineEdit_masar_id_1.setValidator(db_id_validator) self.lineEdit_masar_id_2.setValidator(db_id_validator) self.prev_valid_ref_step_sizes = dict( lineEdit_ref_step_size_1=np.nan, lineEdit_ref_step_size_2=np.nan) # Set up Config Table self.config_model = ConfigModel() self.tableModel_config = self.config_model.table proxyModel = QSortFilterProxyModel() proxyModel.setSourceModel(self.tableModel_config) proxyModel.setDynamicSortFilter(False) tbV = self.tableView_config tbV.setModel(proxyModel) tbV.setItemDelegate(ConfigDBTableViewItemDelegate( tbV, self.tableModel_config, tbV.parent())) self.db = self.config_model.db if '[config_meta_table text view]' not in self.db.getViewNames( square_brackets=True): self.db.create_temp_config_meta_table_text_view() # Set up Config Meta Table self.config_meta_all_col_keys = self.configMetaDBViewWidget.all_col_keys self.search_result = {k: [] for k in self.config_meta_all_col_keys} self.tableModel_config_meta = MetaTableModel( self.search_result, self.configMetaDBViewWidget) proxyModel = QSortFilterProxyModel() proxyModel.setSourceModel(self.tableModel_config_meta) proxyModel.setDynamicSortFilter(False) tbV = self.tableView_config_meta tbV.setModel(proxyModel) self.selectionModel = QItemSelectionModel(proxyModel) tbV.setSelectionModel(self.selectionModel) # Apply Visible Column Preference to Config Table (col_keys, col_names) = config.COL_DEF.getColumnDataFromTable( 'column_table', column_name_list=['column_key','short_descrip_name'], condition_str='column_key in ({0:s})'.format( ','.join(['"{0:s}"'.format(k) for k in self.config_pref['vis_col_key_list']] ) ) ) config_vis_col_name_list = [ col_names[col_keys.index(k)] for k in self.config_pref['vis_col_key_list']] self.configDBViewWidget.on_column_selection_change( config_vis_col_name_list, force_visibility_update=True) # Apply Visible Column Preference to Config Meta Table config_meta_vis_col_name_list = [ self.configMetaDBViewWidget.col_names_wo_desc[ self.configMetaDBViewWidget.col_keys_wo_desc.index(k)] for k in self.config_meta_pref['vis_col_key_list']] self.configMetaDBViewWidget.on_column_selection_change( config_meta_vis_col_name_list, force_visibility_update=True) # Make connection self.connect(self.lineEdit_ref_step_size_1, SIGNAL('editingFinished()'), self.validate_ref_step_size) self.connect(self.lineEdit_ref_step_size_2, SIGNAL('editingFinished()'), self.validate_ref_step_size) self.connect(self.pushButton_search, SIGNAL('clicked()'), self.update_search) self.connect( self.selectionModel, SIGNAL( 'currentRowChanged(const QModelIndex &, const QModelIndex &)'), self.on_selection_change ) self.connect(self.configMetaDBViewWidget, SIGNAL('exportConfigToFile'), self.exportConfigToFile) self.connect(self.configMetaDBViewWidget, SIGNAL('editConfigNameOrDescription'), self.editConfigNameOrDescription) #---------------------------------------------------------------------- def exportConfigToFile(self): """""" msg = QMessageBox() inds = self.selectionModel.selectedRows() if inds != []: row = inds[0].row() config_id = self.search_result['config_id'][row] saved_filepath = self.config_model.abstract.exportToFile( config_id, self._aptinkerQSettings) if saved_filepath: msg.setText( 'Successfully exported config (ID={0:d}) to the file "{1}".'. format(config_id, saved_filepath)) msg.exec_() else: msg.setText('You must select a configuration to be exported.') msg.setIcon(QMessageBox.Critical) msg.exec_() #---------------------------------------------------------------------- def editConfigNameOrDescription(self): """""" msg = QMessageBox() inds = self.selectionModel.selectedRows() if inds != []: row = inds[0].row() config_id = self.search_result['config_id'][row] title = 'Edit Config Name or Description' prompt = 'Property to Edit:' result = QInputDialog.getItem( self, title, prompt, ['Name', 'Description'], current=1, editable=False) if result[1]: table_name = 'config_meta_text_search_table' if result[0] == 'Name': current_name, current_desc = self.db.getColumnDataFromTable( table_name, column_name_list=['config_name', 'config_description'], condition_str='config_id={0:d}'.format(config_id)) current_name = current_name[0] current_desc = current_desc[0] title = 'Edit Config Name' prompt = 'Enter a new name:' result = QInputDialog.getText(self, title, prompt, text=current_name) if result[1]: new_name = result[0] mod_time_str = datestr(time.time()) new_desc = current_desc + ( ('\n### Modified by "{0}" on {1}###\n Config Name ' 'changed from "{2}" to "{3}"\n').format( getusername(), mod_time_str, current_name, new_name)) self.db.changeValues( table_name, 'config_name', '"{0}"'.format(new_name.replace('"', '""')), condition_str='config_id={0:d}'.format(config_id)) self.db.changeValues( table_name, 'config_description', '"{0}"'.format(new_desc.replace('"', '""')), condition_str='config_id={0:d}'.format(config_id)) # Update Config Meta DB Table row = self.search_result['config_id'].index(config_id) self.search_result['config_name'][row] = new_name self.search_result['config_description'][row] = new_desc self.tableModel_config_meta.repaint() self.on_selection_change(None, None) else: current_desc, = self.db.getColumnDataFromTable( table_name, column_name_list=['config_description'], condition_str='config_id={0:d}'.format(config_id)) current_desc = current_desc[0] dialog = ConfigDescriptionEditor(current_desc, parent=self) dialog.exec_() if dialog.result() == QDialog.Accepted: mod_time_str = datestr(time.time()) temp_new_desc = dialog.plainTextEdit_new.property( 'plainText') new_desc = current_desc + ( ('\n### Modified by "{0}" on {1}###\n{2}\n'.format( getusername(), mod_time_str, temp_new_desc)) ) self.db.changeValues( table_name, 'config_description', '"{0}"'.format(new_desc.replace('"', '""')), condition_str='config_id={0:d}'.format(config_id)) # Update Config Meta DB Table row = self.search_result['config_id'].index(config_id) self.search_result['config_description'][row] = new_desc self.tableModel_config_meta.repaint() self.on_selection_change(None, None) else: msg.setText('You must select a configuration whose name or ' 'description to be edited.') msg.setIcon(QMessageBox.Critical) msg.exec_() #---------------------------------------------------------------------- def closeEvent(self, event): """""" self.saveViewSettings() self.config_model.abstract.channel_ids = [] event.accept() #---------------------------------------------------------------------- def accept(self): """""" a = self.config_model.abstract if not a.isDataValid(): return if not a.check_aphla_unitconv_updates(): return self.saveViewSettings() super(ConfigDBSelector, self).accept() # will close the dialog #---------------------------------------------------------------------- def reject(self): """""" self.config_model.abstract.channel_ids = [] self.saveViewSettings() super(ConfigDBSelector, self).reject() # will close the dialog #---------------------------------------------------------------------- def loadViewSettings(self): """""" self.settings.beginGroup('viewSize') rect = self.settings.value('position') if not rect: rect = QRect(0, 0, self.sizeHint().width(), self.sizeHint().height()) self.setGeometry(rect) splitter_sizes = self.settings.value('splitter_1') if splitter_sizes is None: splitter_sizes = [self.width()*(1./2), self.width()*(1./2)] else: splitter_sizes = [int(s) for s in splitter_sizes] self.splitter.setSizes(splitter_sizes) splitter_sizes = self.settings.value('splitter_2') if splitter_sizes is None: splitter_sizes = [self.width()*(1./5), self.width()*(4./5)] else: splitter_sizes = [int(s) for s in splitter_sizes] self.splitter_2.setSizes(splitter_sizes) splitter_sizes = self.settings.value('splitter_3') if splitter_sizes is None: splitter_sizes = [self.configMetaDBViewWidget.height()*(2./3), self.configMetaDBViewWidget.height()*(1./3)] else: splitter_sizes = [int(s) for s in splitter_sizes] self.configMetaDBViewWidget.splitter.setSizes(splitter_sizes) self.settings.endGroup() #---------------------------------------------------------------------- def saveViewSettings(self): """""" self.settings.beginGroup('viewSize') self.settings.setValue('position', self.geometry()) self.settings.setValue('splitter_1', self.splitter.sizes()) self.settings.setValue('splitter_2', self.splitter_2.sizes()) self.settings.setValue('splitter_3', self.configMetaDBViewWidget.splitter.sizes()) self.settings.endGroup() #---------------------------------------------------------------------- def validate_ref_step_size(self): """""" sender = self.sender() name = sender.objectName() text = sender.text().strip() if text == '': text = 'nan' try: new_float = float(text) if np.isnan(new_float): sender.setText('') self.prev_valid_ref_step_sizes[name] = new_float except: msg = QMessageBox() msg.setText('Invalid float string: {0:s}'.format(text)) msg.setIcon(QMessageBox.Critical) msg.exec_() if np.isnan(self.prev_valid_ref_step_sizes[name]): sender.setText('') else: sender.setText(str(self.prev_valid_ref_step_sizes[name])) #---------------------------------------------------------------------- def update_search(self): """""" try: config_id_1_text = str(int(self.lineEdit_config_id_1.text())) except: config_id_1_text = '' try: config_id_2_text = str(int(self.lineEdit_config_id_2.text())) except: config_id_2_text = '' config_id_1_operator = \ self.comboBox_config_id_1.currentText().strip() config_id_2_operator = \ self.comboBox_config_id_2.currentText().strip() if (config_id_1_text != '') and (config_id_1_operator != ''): self.search_params['config_id_1'] = ( 'config_id {0:s} {1:s}'.format(config_id_1_operator, config_id_1_text)) else: self.search_params['config_id_1'] = '' if (config_id_2_text != '') and (config_id_2_operator != ''): self.search_params['config_id_2'] = ( 'config_id {0:s} {1:s}'.format(config_id_2_operator, config_id_2_text)) else: self.search_params['config_id_2'] = '' try: masar_id_1_text = str(int(self.lineEdit_masar_id_1.text())) except: masar_id_1_text = '' try: masar_id_2_text = str(int(self.lineEdit_masar_id_2.text())) except: masar_id_2_text = '' masar_id_1_operator = \ self.comboBox_masar_id_1.currentText().strip() masar_id_2_operator = \ self.comboBox_masar_id_2.currentText().strip() if (masar_id_1_text != '') and (masar_id_1_operator != ''): self.search_params['masar_id_1'] = ( 'config_masar_id {0:s} {1:s}'.format(masar_id_1_operator, masar_id_1_text)) else: self.search_params['masar_id_1'] = '' if (masar_id_2_text != '') and (masar_id_2_operator != ''): self.search_params['masar_id_2'] = ( 'config_masar_id {0:s} {1:s}'.format(masar_id_2_operator, masar_id_2_text)) else: self.search_params['masar_id_2'] = '' try: ref_step_size_1_text = '{0:.9e}'.format( float(self.lineEdit_ref_step_size_1.text())) except: ref_step_size_1_text = '' try: ref_step_size_2_text = '{0:.9e}'.format( float(self.lineEdit_ref_step_size_2.text())) except: ref_step_size_2_text = '' ref_step_size_1_operator = \ self.comboBox_ref_step_size_1.currentText().strip() ref_step_size_2_operator = \ self.comboBox_ref_step_size_2.currentText().strip() if (ref_step_size_1_text != '') and (ref_step_size_1_operator != ''): self.search_params['ref_step_size_1'] = ( 'config_ref_step_size {0:s} {1:s}'.format( ref_step_size_1_operator, ref_step_size_1_text)) else: self.search_params['ref_step_size_1'] = '' if (ref_step_size_2_text != '') and (ref_step_size_2_operator != ''): self.search_params['ref_step_size_2'] = ( 'config_ref_step_size {0:s} {1:s}'.format( ref_step_size_2_operator, ref_step_size_2_text)) else: self.search_params['ref_step_size_2'] = '' synced_group_weight_text = \ self.comboBox_synced_group_weight.currentText() if synced_group_weight_text == 'False': self.search_params['synced_group_weight'] = \ 'config_synced_group_weight = 0' elif synced_group_weight_text == 'True': self.search_params['synced_group_weight'] = \ 'config_synced_group_weight = 1' else: self.search_params['synced_group_weight'] = '' config_name_text = self.lineEdit_config_name.text().strip() if config_name_text != '': cond_str = self.db.get_MATCH_condition_str(config_name_text) if cond_str is not None: try: self.search_params['config_name'] = \ self.db.get_config_ids_with_MATCH(cond_str, 'config_name') except: msg = QMessageBox() msg.setText('Invalid search strings for "Config Name"') msg.setInformativeText(sys.exc_info()[1].__repr__()) msg.setIcon(QMessageBox.Critical) msg.exec_() return else: return else: self.search_params['config_name'] = [] config_desc_text = self.lineEdit_config_description.text().strip() if config_desc_text != '': cond_str = self.db.get_MATCH_condition_str(config_desc_text) if cond_str is not None: try: self.search_params['config_description'] = \ self.db.get_config_ids_with_MATCH(cond_str, 'config_description') except: msg = QMessageBox() msg.setText('Invalid search strings for "Config Description"') msg.setInformativeText(sys.exc_info()[1].__repr__()) msg.setIcon(QMessageBox.Critical) msg.exec_() return else: return else: self.search_params['config_description'] = [] username_text = self.lineEdit_username.text().strip() if username_text != '': self.search_params['username'] = \ self.db.get_GLOB_condition_str(username_text, 'username') else: self.search_params['username'] = '' ctime_1_operator = self.comboBox_time_created_1.currentText().strip() if ctime_1_operator != '': ctime_epoch_1 = self.dateTimeEdit_time_created_1.dateTime() ctime_epoch_1 = time.mktime(ctime_epoch_1.toPyDateTime().timetuple()) self.search_params['ctime_1'] = ( 'config_ctime {0:s} {1:.3f}'.format(ctime_1_operator, ctime_epoch_1)) else: self.search_params['ctime_1'] = '' ctime_2_operator = self.comboBox_time_created_2.currentText().strip() if ctime_2_operator != '': ctime_epoch_2 = self.dateTimeEdit_time_created_2.dateTime() ctime_epoch_2 = time.mktime(ctime_epoch_2.toPyDateTime().timetuple()) self.search_params['ctime_2'] = ( 'config_ctime {0:s} {1:.3f}'.format(ctime_2_operator, ctime_epoch_2)) else: self.search_params['ctime_2'] = '' if (self.search_params['config_name'] is None) or \ (self.search_params['config_description'] is None): for k in self.config_meta_all_col_keys: self.search_result[k] = [] else: condition_str = '' for k, v in self.search_params.iteritems(): if k in ('config_name', 'config_description'): if v: if condition_str != '': condition_str += ' AND ' condition_str += '(config_id IN ({0:s}))'.format( ','.join([str(i) for i in v])) else: if v: if condition_str != '': condition_str += ' AND ' condition_str += '({0:s})'.format(v) out = self.db.getColumnDataFromTable( '[config_meta_table text view]', column_name_list=self.config_meta_all_col_keys, condition_str=condition_str, order_by_str='config_id') if out != []: if self.checkBox_hide_NG.isChecked(): config_name_col_ind = self.config_meta_all_col_keys.index( 'config_name') valid_indexes = [ i for i, name in enumerate(out[config_name_col_ind]) if '$NG$' not in name] else: valid_indexes = range(len(out[0])) for k, v in zip(self.config_meta_all_col_keys, out): self.search_result[k] = [x for i, x in enumerate(v) if i in valid_indexes] else: for k in self.config_meta_all_col_keys: self.search_result[k] = [] self.tableModel_config_meta.repaint() self.on_selection_change(None, None) #---------------------------------------------------------------------- def convert_GLOB_to_LIKE_wildcards(self, char): """""" if char == '*': return '%' elif char == '?': return '_' else: raise ValueError('Unexpected char: {0:s}'.format(char)) #---------------------------------------------------------------------- def get_LIKE_condition_str(self, glob_pattern, column_name): """""" backslahs_inds = [i for i, c in enumerate(glob_pattern) if c == '\\'] like_pattern = ''.join( [self.convert_GLOB_to_LIKE_wildcards(c) if (c in ('*','?')) and (i-1 not in backslahs_inds) else c for i, c in enumerate(glob_pattern)]) cond_str = '({0:s} LIKE "{1:s}" ESCAPE "\\")'.format(column_name, like_pattern) return cond_str #---------------------------------------------------------------------- def on_selection_change(self, current_index, previous_index): """""" a = self.config_model.abstract if current_index is None: self.textEdit_description.setText('') a.ref_step_size = np.nan (a.group_name_ids, a.channel_ids, a.weights, a.caput_enabled_rows) = [], [], [], [] else: row = current_index.row() a.config_id = self.search_result['config_id'][row] a.name = self.search_result['config_name'][row] a.description = self.search_result['config_description'][row] a.config_ctime = self.search_result['config_ctime'][row] self.textEdit_description.setText(a.description) a.ref_step_size = self.search_result['config_ref_step_size'][row] out = self.db.getColumnDataFromTable( 'config_table', column_name_list=['group_name_id', 'channel_id', 'config_weight', 'config_caput_enabled'], condition_str='config_id={0:d}'.format( self.search_result['config_id'][row])) if out != []: (a.group_name_ids, a.channel_ids, a.weights, a.caput_enabled_rows) = map(list, out) else: (a.group_name_ids, a.channel_ids, a.weights, a.caput_enabled_rows) = [], [], [], [] a.weights = [w if w is not None else np.nan for w in a.weights] self.config_model.table.updateModel() self.config_model.table.repaint()
class LayerStackModel(QAbstractListModel): canMoveSelectedUp = pyqtSignal("bool") canMoveSelectedDown = pyqtSignal("bool") canDeleteSelected = pyqtSignal("bool") orderChanged = pyqtSignal() def __init__(self, parent = None): QAbstractListModel.__init__(self, parent) self._layerStack = [] self.selectionModel = QItemSelectionModel(self) self.selectionModel.selectionChanged.connect(self.onSelectionChanged) QTimer.singleShot(0, self.updateGUI) def __repr__(self): return "<LayerStackModel: layerStack='%r'>" % (self._layerStack,) def __getitem__(self, i): return self._layerStack[-i-1] def __iter__(self): return reversed(self._layerStack) def updateGUI(self): self.canMoveSelectedUp.emit(self.selectedRow()>0) self.canMoveSelectedDown.emit(self.selectedRow()<self.rowCount()-1) self.canDeleteSelected.emit(self.rowCount() > 0) self.wantsUpdate() def append(self, data): #self.insertRow(self.rowCount()) #self.setData(self.index(self.rowCount()-1), data) self.insertRow(0) self.setData(self.index(0), data) def selectedRow(self): selected = self.selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def onSelectionChanged(self, selected, deselected): if len(deselected) > 0: self._layerStack[deselected[0].indexes()[0].row()].mode = 'ReadOnly' if len(selected) > 0: self._layerStack[selected[0].indexes()[0].row()].mode = 'Expanded' self.updateGUI() def rowCount(self, parent = QModelIndex()): if not parent.isValid(): return len(self._layerStack) return 0 def insertRows(self, row, count, parent = QModelIndex()): if parent.isValid(): return False oldRowCount = self.rowCount() beginRow = max(0,row) endRow = min(row+count-1, len(self._layerStack)) self.beginInsertRows(parent, beginRow, endRow) while(beginRow <= endRow): self._layerStack.insert(row, Layer()) beginRow += 1 self.endInsertRows() assert self.rowCount() == oldRowCount+1 return True def removeRows(self, row, count, parent = QModelIndex()): if parent.isValid(): return False if row+count <= 0 or row >= len(self._layerStack): return False oldRowCount = self.rowCount() beginRow = max(0,row) endRow = min(row+count-1, len(self._layerStack)-1) self.beginRemoveRows(parent, beginRow, endRow) while(beginRow <= endRow): del self._layerStack[row] beginRow += 1 self.endRemoveRows() assert self.rowCount() == oldRowCount-1 return True def flags(self, index): defaultFlags = Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled if index.isValid(): return Qt.ItemIsDragEnabled | defaultFlags else: return Qt.ItemIsDropEnabled | defaultFlags def supportedDropActions(self): return Qt.MoveAction def data(self, index, role): if not index.isValid(): return None if index.row() > len(self._layerStack): return None if role == Qt.DisplayRole or role == Qt.EditRole: return self._layerStack[index.row()] return None def setData(self, index, value, role = Qt.EditRole): layer = value if not isinstance(value, Layer): layer = value.toPyObject() self._layerStack[index.row()] = layer self.dataChanged.emit(index, index) return True def headerData(section, orientation, role = Qt.DisplayRole): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: return QString("Column %1").arg(section) else: return QString("Row %1").arg(section) def wantsUpdate(self): self.layoutChanged.emit() def deleteSelected(self): print "delete" assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] self.removeRow(row.row()) if self.rowCount() > 0: self.selectionModel.select(self.index(0), QItemSelectionModel.Select) self.updateGUI() def moveSelectedUp(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != 0: oldRow = row.row() newRow = oldRow - 1 d = self._layerStack[oldRow] self.removeRow(oldRow) self.insertRow(newRow) self.setData(self.index(newRow), d) self.selectionModel.select(self.index(newRow), QItemSelectionModel.Select) self.orderChanged.emit() self.updateGUI() def moveSelectedDown(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != self.rowCount() - 1: oldRow = row.row() newRow = oldRow + 1 d = self._layerStack[oldRow] self.removeRow(oldRow) self.insertRow(newRow) self.setData(self.index(newRow), d) self.selectionModel.select(self.index(newRow), QItemSelectionModel.Select) self.orderChanged.emit() self.updateGUI()
class LayerStackModel(QAbstractListModel): canMoveSelectedUp = pyqtSignal("bool") canMoveSelectedDown = pyqtSignal("bool") canDeleteSelected = pyqtSignal("bool") orderChanged = pyqtSignal() layerAdded = pyqtSignal( Layer, int ) # is now in row layerRemoved = pyqtSignal( Layer, int ) # was in row stackCleared = pyqtSignal() def __init__(self, parent = None): QAbstractListModel.__init__(self, parent) self._layerStack = [] self.selectionModel = QItemSelectionModel(self) self.selectionModel.selectionChanged.connect(self.updateGUI) self._movingRows = False QTimer.singleShot(0, self.updateGUI) #### ## High level API to manipulate the layerstack ### def __len__(self): return self.rowCount() def __repr__(self): return "<LayerStackModel: layerStack='%r'>" % (self._layerStack,) def __getitem__(self, i): return self._layerStack[i] def __iter__(self): return self._layerStack.__iter__() def layerIndex(self, layer): #note that the 'index' function already has a different implementation #from Qt side return self._layerStack.index(layer) def append(self, data): self.insert(0, data) def clear(self): if len(self) > 0: self.removeRows(0,len(self)) self.stackCleared.emit() def insert(self, index, data): self.insertRow(index) self.setData(self.index(index), data) if self.selectedRow() >= 0: self.selectionModel.select(self.index(self.selectedRow()), QItemSelectionModel.Deselect) self.selectionModel.select(self.index(index), QItemSelectionModel.Select) def onChanged(): #assumes that data is unique! idx = self.index(self._layerStack.index(data)) self.dataChanged.emit(idx, idx) self.updateGUI() data.changed.connect(onChanged) self.layerAdded.emit( data, self._layerStack.index(data)) self.updateGUI() def selectRow( self, row ): self.selectionModel.setCurrentIndex( self.index(row), QItemSelectionModel.SelectCurrent) @pyqtSignature("deleteSelected()") def deleteSelected(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] layer = self._layerStack[row.row()] self.removeRow(row.row()) if self.rowCount() > 0: self.selectionModel.select(self.index(0), QItemSelectionModel.Select) self.layerRemoved.emit( layer, row.row() ) self.updateGUI() @pyqtSignature("moveSelectedUp()") def moveSelectedUp(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != 0: oldRow = row.row() newRow = oldRow - 1 d = self._layerStack[oldRow] self.removeRow(oldRow) self.insertRow(newRow) self.setData(self.index(newRow), d) self.selectionModel.select(self.index(newRow), QItemSelectionModel.Select) self.orderChanged.emit() self.updateGUI() @pyqtSignature("moveSelectedDown()") def moveSelectedDown(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != self.rowCount() - 1: oldRow = row.row() newRow = oldRow + 1 d = self._layerStack[oldRow] self.removeRow(oldRow) self.insertRow(newRow) self.setData(self.index(newRow), d) self.selectionModel.select(self.index(newRow), QItemSelectionModel.Select) self.orderChanged.emit() self.updateGUI() #### ## Low level API. To add, remove etc. layers use the high level API from above. #### def updateGUI(self): self.canMoveSelectedUp.emit(self.selectedRow()>0) self.canMoveSelectedDown.emit(self.selectedRow()<self.rowCount()-1) self.canDeleteSelected.emit(self.rowCount() > 0) self.wantsUpdate() def selectedRow(self): selected = self.selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent = QModelIndex()): if not parent.isValid(): return len(self._layerStack) return 0 def insertRows(self, row, count, parent = QModelIndex()): '''Insert empty rows in the stack. DO NOT USE THIS METHOD TO INSERT NEW LAYERS! Always use the insert() or append() method. ''' if parent.isValid(): return False oldRowCount = self.rowCount() #for some reason, row can be negative! beginRow = max(0,row) endRow = min(beginRow+count-1, len(self._layerStack)) self.beginInsertRows(parent, beginRow, endRow) while(beginRow <= endRow): self._layerStack.insert(row, Layer()) beginRow += 1 self.endInsertRows() assert self.rowCount() == oldRowCount+1, "oldRowCount = %d, self.rowCount() = %d" % (oldRowCount, self.rowCount()) return True def removeRows(self, row, count, parent = QModelIndex()): '''Remove rows from the stack. DO NOT USE THIS METHOD TO REMOVE LAYERS! Use the deleteSelected() method instead. ''' if parent.isValid(): return False if row+count <= 0 or row >= len(self._layerStack): return False oldRowCount = self.rowCount() beginRow = max(0,row) endRow = min(row+count-1, len(self._layerStack)-1) self.beginRemoveRows(parent, beginRow, endRow) while(beginRow <= endRow): del self._layerStack[row] beginRow += 1 self.endRemoveRows() return True def flags(self, index): defaultFlags = Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled if index.isValid(): return Qt.ItemIsDragEnabled | defaultFlags else: return Qt.ItemIsDropEnabled | defaultFlags def supportedDropActions(self): return Qt.MoveAction def data(self, index, role = Qt.DisplayRole): if not index.isValid(): return None if index.row() > len(self._layerStack): return None if role == Qt.DisplayRole or role == Qt.EditRole: return self._layerStack[index.row()] return None def setData(self, index, value, role = Qt.EditRole): '''Replace one layer with another. DO NOT USE THIS METHOD TO INSERT NEW LAYERS! Use deleteSelected() followed by insert() or append(). ''' layer = value if not isinstance(value, Layer): layer = value.toPyObject() self._layerStack[index.row()] = layer self.dataChanged.emit(index, index) return True def headerData(self, section, orientation, role = Qt.DisplayRole): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: return "Column %r" % section else: return "Row %r" % section def wantsUpdate(self): self.layoutChanged.emit()
def select(self, index, flags=QItemSelectionModel.ClearAndSelect): if isinstance(index, int): index = self.model().index(index) return QItemSelectionModel.select(self, index, flags)
def createWidgets(self): ## Message Table self.table = QTableView() self.table.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setGridStyle(Qt.NoPen) self.model = QStandardItemModel(8, 3, self) self.model.setHeaderData(0, Qt.Horizontal, "From") self.model.setHeaderData(1, Qt.Horizontal, "Subject") self.model.setHeaderData(2, Qt.Horizontal, "Received") self.table.setModel(self.model) self.selectionModel = QItemSelectionModel(self.model) self.selectionModel.SelectionFlag = 0x0020 self.table.setSelectionModel(self.selectionModel) self.table.horizontalHeader().setStretchLastSection(True) self.table.verticalHeader().setVisible(False) self.table.setSortingEnabled(True) self.table.setAlternatingRowColors(True) ## Folder Tree View self.folderTree = QTreeWidget() self.folderTree.setColumnCount(1) self.folderTree.setHeaderLabel(QString('Folders')) self.treeItem = QTreeWidgetItem() self.treeItem.setText(0, 'Folders') self.inbox = QTreeWidgetItem() self.inbox.setText(0, 'Inbox') self.deletedItems = QTreeWidgetItem() self.deletedItems.setText(0, 'Deleted Items') self.drafts = QTreeWidgetItem() self.drafts.setText(0, 'Drafts') self.junk = QTreeWidgetItem() self.junk.setText(0, 'Junk') self.outbox = QTreeWidgetItem() self.outbox.setText(0, 'Outbox') self.sent = QTreeWidgetItem() self.sent.setText(0, 'Sent') self.treeItem.addChild(self.inbox) self.treeItem.addChild(self.deletedItems) self.treeItem.addChild(self.drafts) self.treeItem.addChild(self.junk) self.treeItem.addChild(self.outbox) self.treeItem.addChild(self.sent) self.folderTree.addTopLevelItem(self.treeItem) self.folderTree.expandAll() self.folderTree.setAnimated(True) self.folderTree.setMaximumWidth(150) ## Temp. placeholders self.textEdit = QTextEdit() self.textEdit2 = QTextEdit()