class NuggetListItemWidget(CustomScrollableListItem): def __init__(self, nugget_list_widget): super(NuggetListItemWidget, self).__init__(nugget_list_widget) self.nugget_list_widget = nugget_list_widget self.nugget = None self.setFixedHeight(40) self.setStyleSheet("background-color: white") self.layout = QHBoxLayout(self) self.layout.setContentsMargins(20, 0, 20, 0) self.layout.setSpacing(10) self.info_label = QLabel() self.info_label.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.info_label) self.left_split_label = QLabel("|") self.left_split_label.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.left_split_label) self.text_edit = QTextEdit() self.text_edit.setReadOnly(True) self.text_edit.setFrameStyle(0) self.text_edit.setFont(CODE_FONT) self.text_edit.setLineWrapMode(QTextEdit.LineWrapMode.FixedPixelWidth) self.text_edit.setLineWrapColumnOrWidth(10000) self.text_edit.setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.text_edit.setVerticalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.text_edit.setFixedHeight(30) self.text_edit.setText("") self.layout.addWidget(self.text_edit) self.right_split_label = QLabel("|") self.right_split_label.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.right_split_label) self.match_button = QPushButton() self.match_button.setIcon(QIcon("aset_ui/resources/correct.svg")) self.match_button.setFlat(True) self.match_button.clicked.connect(self._match_button_clicked) self.layout.addWidget(self.match_button) self.fix_button = QPushButton() self.fix_button.setIcon(QIcon("aset_ui/resources/incorrect.svg")) self.fix_button.setFlat(True) self.fix_button.clicked.connect(self._fix_button_clicked) self.layout.addWidget(self.fix_button) def update_item(self, item, params=None): self.nugget = item sentence = self.nugget[CachedContextSentenceSignal]["text"] start_char = self.nugget[CachedContextSentenceSignal]["start_char"] end_char = self.nugget[CachedContextSentenceSignal]["end_char"] self.text_edit.setText("") formatted_text = ( f"{' ' * (params - start_char)}{sentence[:start_char]}" f"<span style='background-color: #FFFF00'><b>{sentence[start_char:end_char]}</b></span>" f"{sentence[end_char:]}{' ' * 50}") self.text_edit.textCursor().insertHtml(formatted_text) scroll_cursor = QTextCursor(self.text_edit.document()) scroll_cursor.setPosition(params + 50) self.text_edit.setTextCursor(scroll_cursor) self.text_edit.ensureCursorVisible() self.info_label.setText( f"{str(round(self.nugget[CachedDistanceSignal], 2)).ljust(4)}") def _match_button_clicked(self): self.nugget_list_widget.interactive_matching_widget.main_window.give_feedback_task( { "message": "is-match", "nugget": self.nugget }) def _fix_button_clicked(self): self.nugget_list_widget.interactive_matching_widget.get_document_feedback( self.nugget) def enable_input(self): self.match_button.setEnabled(True) self.fix_button.setEnabled(True) def disable_input(self): self.match_button.setDisabled(True) self.fix_button.setDisabled(True)
class DocumentWidget(QWidget): def __init__(self, interactive_matching_widget): super(DocumentWidget, self).__init__(interactive_matching_widget) self.interactive_matching_widget = interactive_matching_widget self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(10) self.setLayout(self.layout) self.document = None self.current_nugget = None self.base_formatted_text = "" self.idx_mapper = {} self.nuggets_in_order = [] self.text_edit = QTextEdit() self.layout.addWidget(self.text_edit) self.text_edit.setReadOnly(True) self.text_edit.setFrameStyle(0) self.text_edit.setFont(CODE_FONT) self.text_edit.setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.text_edit.setText("") self.buttons_widget = QWidget() self.buttons_widget_layout = QHBoxLayout(self.buttons_widget) self.layout.addWidget(self.buttons_widget) self.left_button = QPushButton("Skip Left") self.left_button.setFont(BUTTON_FONT) self.left_button.clicked.connect(self._left_button_clicked) self.buttons_widget_layout.addWidget(self.left_button) self.right_button = QPushButton("Skip Right") self.right_button.setFont(BUTTON_FONT) self.right_button.clicked.connect(self._right_button_clicked) self.buttons_widget_layout.addWidget(self.right_button) self.match_button = QPushButton("Confirm Match") self.match_button.setFont(BUTTON_FONT) self.match_button.clicked.connect(self._match_button_clicked) self.buttons_widget_layout.addWidget(self.match_button) self.no_match_button = QPushButton("There Is No Match") self.no_match_button.setFont(BUTTON_FONT) self.no_match_button.clicked.connect(self._no_match_button_clicked) self.buttons_widget_layout.addWidget(self.no_match_button) def _left_button_clicked(self): idx = self.nuggets_in_order.index(self.current_nugget) if idx > 0: self.current_nugget = self.nuggets_in_order[idx - 1] self._highlight_current_nugget() def _right_button_clicked(self): idx = self.nuggets_in_order.index(self.current_nugget) if idx < len(self.nuggets_in_order) - 1: self.current_nugget = self.nuggets_in_order[idx + 1] self._highlight_current_nugget() def _match_button_clicked(self): self.interactive_matching_widget.main_window.give_feedback_task({ "message": "is-match", "nugget": self.current_nugget }) def _no_match_button_clicked(self): self.interactive_matching_widget.main_window.give_feedback_task({ "message": "no-match-in-document", "nugget": self.current_nugget }) def _highlight_current_nugget(self): mapped_start_char = self.idx_mapper[self.current_nugget.start_char] mapped_end_char = self.idx_mapper[self.current_nugget.end_char] formatted_text = ( f"{self.base_formatted_text[:mapped_start_char]}" f"<span style='background-color: #FFFF00'><b>" f"{self.base_formatted_text[mapped_start_char:mapped_end_char]}</span></b>" f"{self.base_formatted_text[mapped_end_char:]}") self.text_edit.setText("") self.text_edit.textCursor().insertHtml(formatted_text) def update_document(self, nugget): self.document = nugget.document self.current_nugget = nugget self.nuggets_in_order = list( sorted(self.document.nuggets, key=lambda x: x.start_char)) if self.nuggets_in_order != []: self.idx_mapper = {} char_list = [] end_chars = [] next_start_char = 0 next_nugget_idx = 0 for idx, char in enumerate(list(self.document.text)): if idx == next_start_char: if end_chars == []: char_list += list("<b>") end_chars.append( self.nuggets_in_order[next_nugget_idx].end_char) next_nugget_idx += 1 if next_nugget_idx < len(self.nuggets_in_order): next_start_char = self.nuggets_in_order[ next_nugget_idx].start_char else: next_start_char = -1 while idx in end_chars: end_chars.remove(idx) if end_chars == []: char_list += list("</b>") self.idx_mapper[idx] = len(char_list) char_list.append(char) self.base_formatted_text = "".join(char_list) else: self.idx_mapper = {} for idx in range(len(self.document.text)): self.idx_mapper[idx] = idx self.base_formatted_text = "" self._highlight_current_nugget() scroll_cursor = QTextCursor(self.text_edit.document()) scroll_cursor.setPosition(nugget.start_char) self.text_edit.setTextCursor(scroll_cursor) self.text_edit.ensureCursorVisible() def enable_input(self): self.left_button.setEnabled(True) self.right_button.setEnabled(True) self.match_button.setEnabled(True) self.no_match_button.setEnabled(True) def disable_input(self): self.left_button.setDisabled(True) self.right_button.setDisabled(True) self.match_button.setDisabled(True) self.no_match_button.setDisabled(True)
class LineNumberedTextEdit(QFrame): # # Inner class to LineNumberTextEdit # Defines the functionality responsible for numbering to the left of the # text edit area for each line of text. # class NumberBar(QWidget): def __init__(self, *args): QWidget.__init__(self, *args) self.edit = None # This is used to update the width of the control. # It is the highest line that is currently visibile. # self.highest_line = 0 def set_text_edit(self, edit): self.edit = edit def update(self, *args): # Updates the number bar width to display the current line numbers. # # The + 15 adds a bit of whitespace to the right of the line number. # width = int(log10(self.edit.document().blockCount()) + 1) * self.fontMetrics().averageCharWidth() + 5 if self.width() != width: self.setFixedWidth(width) QWidget.update(self, *args) def paintEvent(self, event): contents_y = self.edit.verticalScrollBar().value() page_bottom = contents_y + self.edit.viewport().height() font_metrics = self.fontMetrics() current_block = self.edit.document().findBlock(self.edit.textCursor().position()) painter = QPainter(self) line_count = 0 # Iterate over all text blocks in the document. # block = self.edit.document().begin() while block.isValid(): line_count += 1 # The top left position of the block in the document. # position = self.edit.document().documentLayout().blockBoundingRect(block).topLeft() # Check if the position of the block is out side of the visible # area. # if position.y() > page_bottom: break # We want the line number for the selected line to be bold. # bold = False if block == current_block: bold = True font = painter.font() font.setBold(True) painter.setFont(font) # Draw the line number right justified at the y position of the # line. 3 is a magic padding number. drawText(x, y, text). # painter.drawText(self.width() - len(str(line_count)) * font_metrics.averageCharWidth() - 5, round(position.y()) - contents_y + font_metrics.ascent(), str(line_count)) # Remove the bold style if it was set previously. # if bold: font = painter.font() font.setBold(False) painter.setFont(font) block = block.next() self.highest_line = line_count painter.end() QWidget.paintEvent(self, event) # # End inner class # def __init__(self, *args): QFrame.__init__(self, *args) self.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Sunken) self.edit = QTextEdit() self.edit.setFrameStyle(QFrame.Shape.NoFrame) self.edit.setAcceptRichText(False) self.edit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap) self.number_bar = self.NumberBar() self.number_bar.set_text_edit(self.edit) hbox = QHBoxLayout(self) hbox.setSpacing(0) hbox.setContentsMargins(10,0,0,0) hbox.addWidget(self.number_bar) hbox.addWidget(self.edit) self.edit.installEventFilter(self) self.edit.viewport().installEventFilter(self) def eventFilter(self, object, event): # Update the line numbers for all events on the text edit and the viewport. # This is easier than connecting all necessary singals. # if object in (self.edit, self.edit.viewport()): self.number_bar.update() return False return QFrame.eventFilter(object, event) def getTextEdit(self): return self.edit