class LabelConfigurator(QDialog): def __init__(self, boxManager, spawnPos): super().__init__() # Variables self.spawnPos = spawnPos # Objects self.boxManager = boxManager self.layout = QVBoxLayout(self) self.contentLayout = QHBoxLayout() self.settingsLayout = QVBoxLayout() self.isOccludedButton = Check('Occluded', boxManager.getRecentIsOccluded()) self.isTruncatedButton = Check('Truncated', boxManager.getRecentIsTruncated()) self.isGroupOfButton = Check('Group Of', boxManager.getRecentIsGroupOf()) self.isDepictionButton = Check('Depiction', boxManager.getRecentIsDepiction()) self.isInsideButton = Check('Inside', boxManager.getRecentIsInside()) self.acceptButton = QPushButton('Accept') self.cancelButton = QPushButton('Cancel') self.labelsModel = QStringListModel() self.labelsView = QListView() # Layout self.setWindowFlags(Qt.Popup) self.settingsLayout.setAlignment(Qt.AlignTop | Qt.AlignCenter) self.settingsLayout.setMargin(0) self.settingsLayout.addWidget(self.isOccludedButton) self.settingsLayout.addWidget(self.isTruncatedButton) self.settingsLayout.addWidget(self.isGroupOfButton) self.settingsLayout.addWidget(self.isDepictionButton) self.settingsLayout.addWidget(self.isInsideButton) self.settingsLayout.addStretch(1) self.settingsLayout.addWidget(self.acceptButton) self.settingsLayout.addWidget(self.cancelButton) self.contentLayout.addWidget(self.labelsView) self.contentLayout.addLayout(self.settingsLayout) self.layout.addLayout(self.contentLayout) # Styling self.setStyleSheet('LabelConfigurator { ' 'background-color: ' + ThemeManager.BG_L1 + ';' 'border-top-left-radius: ' + str(ThemeManager.CURVE) + 'px;' 'border-bottom-left-radius: ' + str(ThemeManager.CURVE) + 'px;' 'border-top-right-radius: ' + str(ThemeManager.CURVE) + 'px;' 'border-bottom-right-radius: ' + str(ThemeManager.CURVE) + 'px;' 'border-width: 0px;' 'border-style: solid;' '}' 'QPushButton {' 'background-color: ' + ThemeManager.BG_L2 + ';' 'border-radius: 10px;' 'color: ' + ThemeManager.LABEL + ';' 'font-size: 14px;' '}' 'QPushButton:hover {' 'background-color: ' + ThemeManager.BG_L3 + ';' '}' 'QListView { ' 'background-color: ' + ThemeManager.BG_L2 + ';' '}') self.layout.setMargin(20) self.layout.setSpacing(10) self.contentLayout.setMargin(0) self.labelsModel.setStringList(boxManager.loadLabels()) self.labelsView.setFixedWidth(80) self.labelsView.setFrameStyle(QFrame.NoFrame) self.labelsView.setModel(self.labelsModel) self.labelsView.setItemDelegate(ListDelegate.ListDelegate()) index = None try: row = self.labelsModel.stringList().index( boxManager.getRecentLabelName()) index = self.labelsModel.index(row) except ValueError: index = self.labelsModel.index(0) if index is not None: self.labelsView.setCurrentIndex(index) # Connections self.acceptButton.clicked.connect(self.close) self.cancelButton.clicked.connect(self.reject) def showEvent(self, event): super().showEvent(event) self.move(self.spawnPos) def closeEvent(self, event): labelConfig = (self.labelsView.selectedIndexes()[0].data( role=Qt.DisplayRole), self.isOccludedButton.getEnabled(), self.isTruncatedButton.getEnabled(), self.isGroupOfButton.getEnabled(), self.isDepictionButton.getEnabled(), self.isInsideButton.getEnabled()) self.labelAccepted.emit(labelConfig) super().closeEvent(event) def getLabelConfig(self): return labelAccepted = Signal(object)
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setupUi(self) self.player = QMediaPlayer() self.player.error.connect(self.erroralert) self.player.play() # Setup the playlist. self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) # Add viewer for video playback, separate floating window. self.viewer = ViewerWindow(self) self.viewer.setWindowFlags(self.viewer.windowFlags() | Qt.WindowStaysOnTopHint) self.viewer.setMinimumSize(QSize(480, 360)) videoWidget = QVideoWidget() self.viewer.setCentralWidget(videoWidget) self.player.setVideoOutput(videoWidget) # Connect control buttons/slides for media player. self.playButton.pressed.connect(self.player.play) self.pauseButton.pressed.connect(self.player.pause) self.stopButton.pressed.connect(self.player.stop) self.volumeSlider.valueChanged.connect(self.player.setVolume) self.viewButton.toggled.connect(self.toggle_viewer) self.viewer.state.connect(self.viewButton.setChecked) self.previousButton.pressed.connect(self.playlist.previous) self.nextButton.pressed.connect(self.playlist.next) self.model = PlaylistModel(self.playlist) self.playlistView.setModel(self.model) self.playlist.currentIndexChanged.connect( self.playlist_position_changed) selection_model = self.playlistView.selectionModel() selection_model.selectionChanged.connect( self.playlist_selection_changed) self.player.durationChanged.connect(self.update_duration) self.player.positionChanged.connect(self.update_position) self.timeSlider.valueChanged.connect(self.player.setPosition) self.open_file_action.triggered.connect(self.open_file) self.setAcceptDrops(True) self.show() def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(484, 371) self.centralWidget = QWidget(MainWindow) sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.centralWidget.sizePolicy().hasHeightForWidth()) self.centralWidget.setSizePolicy(sizePolicy) self.centralWidget.setObjectName("centralWidget") self.horizontalLayout = QHBoxLayout(self.centralWidget) self.horizontalLayout.setContentsMargins(11, 11, 11, 11) self.horizontalLayout.setSpacing(6) self.horizontalLayout.setObjectName("horizontalLayout") self.verticalLayout = QVBoxLayout() self.verticalLayout.setSpacing(6) self.verticalLayout.setObjectName("verticalLayout") self.playlistView = QListView(self.centralWidget) self.playlistView.setAcceptDrops(True) self.playlistView.setProperty("showDropIndicator", True) self.playlistView.setDragDropMode(QAbstractItemView.DropOnly) self.playlistView.setAlternatingRowColors(True) self.playlistView.setUniformItemSizes(True) self.playlistView.setObjectName("playlistView") self.verticalLayout.addWidget(self.playlistView) self.horizontalLayout_4 = QHBoxLayout() self.horizontalLayout_4.setSpacing(6) self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.currentTimeLabel = QLabel(self.centralWidget) self.currentTimeLabel.setMinimumSize(QSize(80, 0)) self.currentTimeLabel.setAlignment(Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter) self.currentTimeLabel.setObjectName("currentTimeLabel") self.horizontalLayout_4.addWidget(self.currentTimeLabel) self.timeSlider = QSlider(self.centralWidget) self.timeSlider.setOrientation(Qt.Horizontal) self.timeSlider.setObjectName("timeSlider") self.horizontalLayout_4.addWidget(self.timeSlider) self.totalTimeLabel = QLabel(self.centralWidget) self.totalTimeLabel.setMinimumSize(QSize(80, 0)) self.totalTimeLabel.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.totalTimeLabel.setObjectName("totalTimeLabel") self.horizontalLayout_4.addWidget(self.totalTimeLabel) self.verticalLayout.addLayout(self.horizontalLayout_4) self.horizontalLayout_5 = QHBoxLayout() self.horizontalLayout_5.setSpacing(6) self.horizontalLayout_5.setObjectName("horizontalLayout_5") self.previousButton = QPushButton(self.centralWidget) self.previousButton.setText("") icon = QIcon() icon.addPixmap(QPixmap("images/control-skip-180.png"), QIcon.Normal, QIcon.Off) self.previousButton.setIcon(icon) self.previousButton.setObjectName("previousButton") self.horizontalLayout_5.addWidget(self.previousButton) self.playButton = QPushButton(self.centralWidget) self.playButton.setText("") icon1 = QIcon() icon1.addPixmap(QPixmap("images/control.png"), QIcon.Normal, QIcon.Off) self.playButton.setIcon(icon1) self.playButton.setObjectName("playButton") self.horizontalLayout_5.addWidget(self.playButton) self.pauseButton = QPushButton(self.centralWidget) self.pauseButton.setText("") icon2 = QIcon() icon2.addPixmap(QPixmap("images/control-pause.png"), QIcon.Normal, QIcon.Off) self.pauseButton.setIcon(icon2) self.pauseButton.setObjectName("pauseButton") self.horizontalLayout_5.addWidget(self.pauseButton) self.stopButton = QPushButton(self.centralWidget) self.stopButton.setText("") icon3 = QIcon() icon3.addPixmap(QPixmap("images/control-stop-square.png"), QIcon.Normal, QIcon.Off) self.stopButton.setIcon(icon3) self.stopButton.setObjectName("stopButton") self.horizontalLayout_5.addWidget(self.stopButton) self.nextButton = QPushButton(self.centralWidget) self.nextButton.setText("") icon4 = QIcon() icon4.addPixmap(QPixmap("images/control-skip.png"), QIcon.Normal, QIcon.Off) self.nextButton.setIcon(icon4) self.nextButton.setObjectName("nextButton") self.horizontalLayout_5.addWidget(self.nextButton) self.viewButton = QPushButton(self.centralWidget) self.viewButton.setText("") icon5 = QIcon() icon5.addPixmap(QPixmap("images/application-image.png"), QIcon.Normal, QIcon.Off) self.viewButton.setIcon(icon5) self.viewButton.setCheckable(True) self.viewButton.setObjectName("viewButton") self.horizontalLayout_5.addWidget(self.viewButton) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout_5.addItem(spacerItem) self.label = QLabel(self.centralWidget) self.label.setText("") self.label.setPixmap(QPixmap("images/speaker-volume.png")) self.label.setObjectName("label") self.horizontalLayout_5.addWidget(self.label) self.volumeSlider = QSlider(self.centralWidget) self.volumeSlider.setMaximum(100) self.volumeSlider.setProperty("value", 100) self.volumeSlider.setOrientation(Qt.Horizontal) self.volumeSlider.setObjectName("volumeSlider") self.horizontalLayout_5.addWidget(self.volumeSlider) self.verticalLayout.addLayout(self.horizontalLayout_5) self.horizontalLayout.addLayout(self.verticalLayout) MainWindow.setCentralWidget(self.centralWidget) self.menuBar = QMenuBar(MainWindow) self.menuBar.setGeometry(QRect(0, 0, 484, 22)) self.menuBar.setObjectName("menuBar") self.menuFIle = QMenu(self.menuBar) self.menuFIle.setObjectName("menuFIle") MainWindow.setMenuBar(self.menuBar) self.statusBar = QStatusBar(MainWindow) self.statusBar.setObjectName("statusBar") MainWindow.setStatusBar(self.statusBar) self.open_file_action = QAction(MainWindow) self.open_file_action.setObjectName("open_file_action") self.menuFIle.addAction(self.open_file_action) self.menuBar.addAction(self.menuFIle.menuAction()) self.retranslateUi(MainWindow) QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Failamp")) self.currentTimeLabel.setText(_translate("MainWindow", "0:00")) self.totalTimeLabel.setText(_translate("MainWindow", "0:00")) self.menuFIle.setTitle(_translate("MainWindow", "FIle")) self.open_file_action.setText(_translate("MainWindow", "Open file...")) def dragEnterEvent(self, e): if e.mimeData().hasUrls(): e.acceptProposedAction() def dropEvent(self, e): for url in e.mimeData().urls(): self.playlist.addMedia(QMediaContent(url)) self.model.layoutChanged.emit() # If not playing, seeking to first of newly added + play. if self.player.state() != QMediaPlayer.PlayingState: i = self.playlist.mediaCount() - len(e.mimeData().urls()) self.playlist.setCurrentIndex(i) self.player.play() def open_file(self): path, _ = QFileDialog.getOpenFileName( self, "Open file", "", "mp3 Audio (*.mp3);mp4 Video (*.mp4);Movie files (*.mov);All files (*.*)" ) if path: self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(path))) self.model.layoutChanged.emit() def update_duration(self, duration): print("!", duration) print("?", self.player.duration()) self.timeSlider.setMaximum(duration) if duration >= 0: self.totalTimeLabel.setText(hhmmss(duration)) def update_position(self, position): if position >= 0: self.currentTimeLabel.setText(hhmmss(position)) # Disable the events to prevent updating triggering a setPosition event (can cause stuttering). self.timeSlider.blockSignals(True) self.timeSlider.setValue(position) self.timeSlider.blockSignals(False) def playlist_selection_changed(self, ix): # We receive a QItemSelection from selectionChanged. i = ix.indexes()[0].row() self.playlist.setCurrentIndex(i) def playlist_position_changed(self, i): if i > -1: ix = self.model.index(i) self.playlistView.setCurrentIndex(ix) def toggle_viewer(self, state): if state: self.viewer.show() else: self.viewer.hide() def erroralert(self, *args): print(args)
class IconColorEditor(QDialog): """An editor to let the user select an icon and a color for an object_class. """ def __init__(self, parent): """Init class.""" super().__init__(parent) # , Qt.Popup) icon_size = QSize(32, 32) self.icon_mngr = IconListManager(icon_size) self.setWindowTitle("Select icon and color") self.icon_widget = QWidget(self) self.icon_list = QListView(self.icon_widget) self.icon_list.setViewMode(QListView.IconMode) self.icon_list.setIconSize(icon_size) self.icon_list.setResizeMode(QListView.Adjust) self.icon_list.setItemDelegate(_IconPainterDelegate(self)) self.icon_list.setMovement(QListView.Static) self.icon_list.setMinimumHeight(400) icon_widget_layout = QVBoxLayout(self.icon_widget) icon_widget_layout.addWidget(QLabel("Font Awesome icons")) self.line_edit = QLineEdit() self.line_edit.setPlaceholderText("Search icons for...") icon_widget_layout.addWidget(self.line_edit) icon_widget_layout.addWidget(self.icon_list) self.color_dialog = QColorDialog(self) self.color_dialog.setWindowFlags(Qt.Widget) self.color_dialog.setOption(QColorDialog.NoButtons, True) self.color_dialog.setOption(QColorDialog.DontUseNativeDialog, True) self.button_box = QDialogButtonBox(self) self.button_box.setStandardButtons(QDialogButtonBox.Cancel | QDialogButtonBox.Ok) top_widget = QWidget(self) top_layout = QHBoxLayout(top_widget) top_layout.addWidget(self.icon_widget) top_layout.addWidget(self.color_dialog) layout = QVBoxLayout(self) layout.addWidget(top_widget) layout.addWidget(self.button_box) self.proxy_model = QSortFilterProxyModel(self) self.proxy_model.setSourceModel(self.icon_mngr.model) self.proxy_model.filterAcceptsRow = self._proxy_model_filter_accepts_row self.icon_list.setModel(self.proxy_model) self.setAttribute(Qt.WA_DeleteOnClose) self.connect_signals() def _proxy_model_filter_accepts_row(self, source_row, source_parent): """Overridden method to filter icons according to search terms. """ text = self.line_edit.text() if not text: return QSortFilterProxyModel.filterAcceptsRow( self.proxy_model, source_row, source_parent) searchterms = self.icon_mngr.model.index( source_row, 0, source_parent).data(Qt.UserRole + 1) return any([text in term for term in searchterms]) def connect_signals(self): """Connect signals to slots.""" self.line_edit.textEdited.connect(self.proxy_model.invalidateFilter) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) def set_data(self, data): icon_code, color_code = interpret_icon_id(data) self.icon_mngr.init_model() for i in range(self.proxy_model.rowCount()): index = self.proxy_model.index(i, 0) if index.data(Qt.UserRole) == icon_code: self.icon_list.setCurrentIndex(index) break self.color_dialog.setCurrentColor(QColor(color_code)) def data(self): icon_code = self.icon_list.currentIndex().data(Qt.UserRole) color_code = self.color_dialog.currentColor().rgb() return make_icon_id(icon_code, color_code)
class AuswahlDialog(QDialog): def __init__(self, title=None, parent=None): QDialog.__init__(self, parent) self.title = title self.listView = QListView() self.font = QFont("Arial", 14) self.okButton = QPushButton("OK") self.cancelButton = QPushButton("Abbrechen") self.model = QStandardItemModel() self.listView.setModel(self.model) self._selectedIndexes = "" self._createGui() def _createGui(self): hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(self.okButton) hbox.addWidget(self.cancelButton) vbox = QVBoxLayout(self) vbox.addWidget(self.listView, stretch=1) # vbox.addStretch(1) vbox.addLayout(hbox) self.okButton.setDefault(True) if self.title: self.setWindowTitle(self.title) else: self.setWindowTitle("Auswahl") self.okButton.clicked.connect(self.onAccepted) self.cancelButton.clicked.connect(self.reject) def appendItemList(self, itemlist: List[str]): for i in itemlist: self.appendItem(i, None) def appendItem(self, text: str, userdata: Any = None): item = CustomItem(text) if userdata: item.userdata = userdata item.setFont(self.font) self.model.appendRow(item) if self.model.rowCount() == 1: self.listView.setCurrentIndex(self.model.index(0, 0)) def onAccepted(self): self._selectedIndexes = self.listView.selectedIndexes() self.accept() def getSelectedIndexes(self): return self._selectedIndexes def getSelection(self) -> List[Tuple]: sel = self.getSelectedIndexes() l = list() for idx in sel: item: CustomItem = self.model.item(idx.row(), idx.column()) t = (item.text(), item.userdata) l.append(t) return l
class PipelineEditor(QGroupBox): def __init__(self, file_picker): super().__init__("Pipeline Editor") self.setLayout(QVBoxLayout()) self.file_picker = file_picker self.pipeline = Pipeline() self.pipelineView = QListView() self.pipelineView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.pipelineView.setModel(QStandardItemModel()) self.layout().addWidget(self.pipelineView) # === BUTTON CONTAINER === button_rows = QWidget() self.layout().addWidget(button_rows) button_rows_layout = QVBoxLayout(button_rows) button_rows_layout.setContentsMargins(0, 0, 0, 0) # === ROW 1 === row_1 = QWidget() button_rows_layout.addWidget(row_1) row_1_layout = QHBoxLayout(row_1) row_1_layout.setContentsMargins(0, 0, 0, 0) self.moveUpButton = QPushButton("Move Up") row_1_layout.addWidget(self.moveUpButton) self.moveUpButton.clicked.connect(self._move_up_listener) self.moveDownButton = QPushButton("Move Down") row_1_layout.addWidget(self.moveDownButton) self.moveDownButton.clicked.connect(self._move_down_listener) self.deleteButton = QPushButton("Delete") row_1_layout.addWidget(self.deleteButton) self.deleteButton.clicked.connect(self._delete_listener) # === ROW 2 === row_2 = QWidget() button_rows_layout.addWidget(row_2) row_2_layout = QHBoxLayout(row_2) row_2_layout.setContentsMargins(0, 0, 0, 0) self.applyButton = QPushButton("Apply Pipeline") row_2_layout.addWidget(self.applyButton) self.applyButton.clicked.connect(self._apply_pipeline_listener) self.update_pipeline_view() def update_pipeline_view(self): logging.debug(self.pipeline) model = self.pipelineView.model() model.clear() for t in range(self.pipeline.rowCount()): item = QStandardItem() item.setText(repr(self.pipeline.data(t))) model.appendRow(item) def _modify_transformation_listener(self): # TODO raise NotImplementedError def _move_up_listener(self): try: to_move = self.pipelineView.selectionModel().selectedIndexes( )[0].row() if to_move < 1: return self.pipeline.move_transformation_up(to_move) self.update_pipeline_view() self.pipelineView.setCurrentIndex(self.pipelineView.model().index( to_move - 1, 0)) except IndexError: return def _move_down_listener(self): try: to_move = self.pipelineView.selectionModel().selectedIndexes( )[0].row() if to_move > self.pipelineView.model().rowCount() - 2: return self.pipeline.move_transformation_down(to_move) self.update_pipeline_view() self.pipelineView.setCurrentIndex(self.pipelineView.model().index( to_move + 1, 0)) except IndexError: return def _delete_listener(self): try: to_delete = self.pipelineView.selectionModel().selectedIndexes( )[0].row() self.pipeline.remove_transformation(to_delete) self.update_pipeline_view() if to_delete > self.pipelineView.model().rowCount() - 1: self.pipelineView.setCurrentIndex( self.pipelineView.model().index(to_delete - 1, 0)) else: self.pipelineView.setCurrentIndex( self.pipelineView.model().index(to_delete, 0)) except IndexError: return def _apply_pipeline_listener(self): # Check that at least one transformation has been added to the pipeline if self.pipeline.rowCount() == 0: no_transformations_messagebox = QMessageBox(self) no_transformations_messagebox.setText( "ERROR: No transformations selected") no_transformations_messagebox.exec_() return file_sequence = self.file_picker.file_sequence.files # Check that at least one file has been added to the file sequence if len(file_sequence) == 0: no_files_messagebox = QMessageBox(self) no_files_messagebox.setText("ERROR: No files selected") no_files_messagebox.exec_() return transformed_sequence = self.pipeline.resolve(file_sequence) before_after = list(zip(file_sequence, transformed_sequence)) preview_text_lines = [] for rename in before_after: preview_text_lines.append(f"{rename[0].name} -> {rename[1].name}") preview_text = "\n".join(preview_text_lines) confirmation = QMessageBox(self) confirmation.setText("Are you sure you want to apply the pipeline?") confirmation.setDetailedText(preview_text) confirmation.setStandardButtons(QMessageBox.Yes | QMessageBox.No) confirmation.setDefaultButton(QMessageBox.No) ret = confirmation.exec_() if ret == int(QMessageBox.Yes): for rename in before_after: from_path = rename[0] to_path = rename[1] shutil.move(str(from_path), str(to_path)) self.file_picker.clear_file_list()
class Completer(QWidget): """docstring for ClassName Attributes: delegate (CompleterDelegate): the delegate use by the view model (CompleterModel): the model proxy_model (QSortFilterProxyModel ): the proxy model used to filter model panel (QLabel): The description widget view (QListView): the view Signals: activated (str): return the keyword selected """ activated = Signal(str) def __init__(self, parent=None): super().__init__(parent) self._target = None self._completion_prefix = "" self.setWindowFlag(Qt.Popup) self.setFocusPolicy(Qt.NoFocus) # create model self.model = CompleterModel() self.proxy_model = QSortFilterProxyModel() self.proxy_model.setSourceModel(self.model) self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive) # create delegate self.delegate = CompleterDelegate() # create view self.view = QListView() self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.view.setFocusPolicy(Qt.NoFocus) self.view.installEventFilter(self) self.view.setModel(self.proxy_model) self.view.setItemDelegate(self.delegate) self.view.setMinimumWidth(200) self.view.setUniformItemSizes(True) self.view.setSpacing(0) self.view.selectionModel().currentRowChanged.connect( self._on_row_changed) self.setFocusProxy(self.view) # create panel info self.panel = QLabel() self.panel.setAlignment(Qt.AlignTop) self.panel.setMinimumWidth(300) self.panel.setWordWrap(True) self.panel.setFrameShape(QFrame.StyledPanel) # Create layout vlayout = QHBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.setSpacing(0) vlayout.addWidget(self.view) vlayout.addWidget(self.panel) self.setLayout(vlayout) def set_target(self, target): """Set CodeEdit Args: target (CodeEdit): The CodeEdit """ self._target = target self.installEventFilter(self._target) def eventFilter(self, obj: QObject, event: QEvent) -> bool: """Filter event from CodeEdit and QListView Args: obj (QObject): Description event (QEvent): Description Returns: bool """ # Intercept CodeEdit event if obj == self._target: if event.type() == QEvent.FocusOut: # Ignore lost focus! return True else: obj.event(event) return True # Intercept QListView event if obj == self.view: # Redirect event to QTextExit if event.type() == QEvent.KeyPress and self._target: current = self.view.selectionModel().currentIndex() # emit signal when user press return if event.key() == Qt.Key_Return: word = current.data() self.activated.emit(word) self.hide() event.ignore() return True # use tab to move down/up in the list if event.key() == Qt.Key_Tab: if current.row() < self.proxy_model.rowCount() - 1: self.view.setCurrentIndex( self.proxy_model.index(current.row() + 1, 0)) if event.key() == Qt.Key_Backtab: if current.row() > 0: self.view.setCurrentIndex( self.proxy_model.index(current.row() - 1, 0)) # Route other key event to the target ! This make possible to write text when completer is visible self._target.event(event) return super().eventFilter(obj, event) def complete(self, rect: QRect): """Show completer as popup Args: rect (QRect): the area where to display the completer """ if self.proxy_model.rowCount() == 0: self.hide() return if self._target: pos = self._target.mapToGlobal(rect.bottomRight()) self.move(pos) self.setFocus() if not self.isVisible(): width = 400 #height = self.view.sizeHintForRow(0) * self.proxy_model.rowCount() + 3 # HACK.. TODO better ! #height = min(self._target.height() / 2, height) #self.resize(width, height) self.adjustSize() self.show() def set_completion_prefix(self, prefix: str): """Set prefix and filter model Args: prefix (str): A prefix keyword used to filter model """ self.view.clearSelection() self._completion_prefix = prefix self.proxy_model.setFilterRegularExpression( QRegularExpression(f"^{prefix}.*", QRegularExpression.CaseInsensitiveOption)) if self.proxy_model.rowCount() > 0: self.select_row(0) def select_row(self, row: int): """Select a row in the model Args: row (int): a row number """ index = self.proxy_model.index(row, 0) self.view.selectionModel().setCurrentIndex(index, QItemSelectionModel.Select) def completion_prefix(self) -> str: """getter of completion_prefix TODO: use getter / setter Returns: str: Return the completion_prefix """ return self._completion_prefix def hide(self): """Override from QWidget Hide the completer """ self.set_completion_prefix("") super().hide() def _on_row_changed(self, current: QModelIndex, previous: QModelIndex): """Slot received when user select a new item in the list. This is used to update the panel Args: current (QModelIndex): the selection index previous (QModelIndex): UNUSED """ description = current.data(Qt.ToolTipRole) self.panel.setText(description)