def __init__(self, parent): super(KeywordEdit, self).__init__(parent) self.setAcceptRichText(False) self.highlighter = KeywordHighlighter(self.document()) self.setMouseTracking(True) self.last_edited = None
def __init__(self, parent=None): super(SearchMenu, self).__init__(parent) self.ui = Ui_SearchMenu() self.ui.setupUi(self) #self.setAttribute(QtCore.Qt.WA_DeleteOnClose) # Feels herpy. #self.ui.buttonBox.accepted.connect(self.open_clicked.emit) self.ui.shortcutFind = QShortcut(QKeySequence("Ctrl+F"), self) self.ui.shortcutFind.activated.connect(self.highlight_query) self.ui.btnFilterSelAll.clicked.connect(lambda: self.filterSetAll(True)) self.ui.btnFilterSelNone.clicked.connect(lambda: self.filterSetAll(False)) # Unchecked is easier to work with, # since it searches everything if nothing's given. self.filterSetAll(False) self.ui.treeResults.setHeaderLabel("Results (0)") self.ui.actionCopyPath = QtGui.QAction("Copy path", None, triggered = self.copyPath) self.ui.treeResults.addAction(self.ui.actionCopyPath) self.transHighlighter = KeywordHighlighter(self.ui.txtTranslated.document()) self.origHighlighter = KeywordHighlighter(self.ui.txtOriginal.document()) self.commHighlighter = KeywordHighlighter(self.ui.txtComments.document()) self.re_flags = re.UNICODE | re.MULTILINE
class KeywordEdit(QtGui.QTextEdit): def __init__(self, parent): super(KeywordEdit, self).__init__(parent) self.setAcceptRichText(False) self.highlighter = KeywordHighlighter(self.document()) self.setMouseTracking(True) self.last_edited = None #self.load_keywords() ############################################################################## ### @fn mouseMoveEvent(event) ### @desc So we can catch ToolTip events. ############################################################################## def mouseMoveEvent(self, event): tooltip, rect = self.get_tooltip(event.pos()) if not tooltip == "": QtGui.QToolTip.showText(event.globalPos(), tooltip, self, rect) else: QtGui.QToolTip.hideText() return super(KeywordEdit, self).mouseMoveEvent(event) ############################################################################## ### @fn words_at_pos(line, pos) ### @desc Figure out what keywords are at the specific position. ############################################################################## def words_at_pos(self, line, pos): words = [] for word in self.highlighter.matches[line]: if pos >= word[1] and pos < word[2]: words.append(word) return words ############################################################################## ### @fn get_tooltip(pos) ### @desc Returns the tooltip and the boundaries for it given the cursor pos. ############################################################################## def get_tooltip(self, pos): cursor_pos = self.document().documentLayout().hitTest( QtCore.QPointF(pos), Qt.Qt.ExactHit) if cursor_pos == -1: return "", QRect() cursor = QTextCursor(self.document()) cursor.setPosition(cursor_pos) col = cursor.positionInBlock() line = cursor.blockNumber() tooltip = "" rect = QRect() if line >= len(self.highlighter.matches): return "", QRect() match_start = 0 match_len = 0 matches = 0 #for word in self.highlighter.matches[line]: #if col >= word[1] and col < word[2]: for word in self.words_at_pos(line, col): keyword = word[0] # Go for the smallest region possible so we can update # as soon as the mouse falls out of range of one of the words. if word[1] > match_start: match_start = word[1] if word[2] - word[1] < match_len: match_len = word[2] - word[1] if matches > 0: tooltip += u"\n" if not keyword.section == "": tooltip += u"【%s】 " % keyword.section tooltip += u"%s ― %s" % (keyword.word, keyword.meaning) matches += 1 # Highlight our word, so we can get the rect. cursor.movePosition(QTextCursor.Start) for i in range(line): cursor.movePosition(QTextCursor.NextBlock) for i in range(match_start): cursor.movePosition(QTextCursor.NextCharacter) for i in range(match_len): cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) rect = self.cursorRect(cursor) return tooltip, rect ############################################################################## ### @fn contextMenuEvent(event) ### @desc Generates a context menu depending on what keywords are under the cursor. ############################################################################## def contextMenuEvent(self, event): menu = QtGui.QMenu() last_pos = event.pos() cursor = self.cursorForPosition(last_pos) pos = cursor.positionInBlock() line = cursor.blockNumber() keywords = self.words_at_pos(line, pos) if len(keywords) > 0: keyword_mapper = QSignalMapper(self) actions = [] for keyword in keywords: action_text = "Copy \"%s\"" % keyword[0].meaning actions.append(QtGui.QAction(action_text, None)) # We can only send strings with the signal mapper, so pickle our data. data = pickle.dumps(keyword[0]) data = QtCore.QString.fromAscii(data) self.connect(actions[-1], QtCore.SIGNAL("triggered()"), keyword_mapper, QtCore.SLOT("map()")) keyword_mapper.setMapping(actions[-1], data) self.connect(keyword_mapper, QtCore.SIGNAL("mapped(QString)"), self.copy_keyword) menu.addActions(actions) menu.addSeparator() default_menu = self.createStandardContextMenu() menu.addActions(default_menu.actions()) menu.exec_(event.globalPos()) ############################################################################## ### @fn copy_keyword(data) ### @desc Sends the "meaning" portion of a keyword to the clipboard. ############################################################################## def copy_keyword(self, data): # Since Qt's Signal Mapper will only accept a QString, we have to convert it # to a Python string in such a way that it won't try to actually encode/decode # any of the data, because nothing but the container changed when it turned # into a QString. keyword = pickle.loads(data.toAscii().data()) clipboard = QApplication.clipboard() clipboard.setText(keyword.meaning) ############################################################################## ### @fn load_keywords() ### @desc Checks to see if the terminology file has been changed since ### the last load, and if not, loads them. ############################################################################## def load_keywords(self): csv_edited = terminology.last_edited() if self.last_edited == csv_edited: return else: self.last_edited = csv_edited self.highlighter.clear_keywords() rows = terminology.load_csv() for row in rows: section = row['Section'].decode("UTF-8") word = row['Word'].decode("UTF-8") meaning = row['Meaning'].decode("UTF-8") term = terminology.Term(word, meaning, section) self.highlighter.add_keyword(term) self.highlighter.rehighlight() ############################################################################## ### @fn clear_keywords() ### @desc Bye bye. ############################################################################## def clear_keywords(self): self.highlighter.clear_keywords() self.highlighter.rehighlight() # So we force a reload. self.last_edited = -1 ### EOF ###
class SearchMenu(QtGui.QDialog): ### SIGNALS ### open_clicked = pyqtSignal() def __init__(self, parent=None): super(SearchMenu, self).__init__(parent) self.ui = Ui_SearchMenu() self.ui.setupUi(self) #self.setAttribute(QtCore.Qt.WA_DeleteOnClose) # Feels herpy. #self.ui.buttonBox.accepted.connect(self.open_clicked.emit) self.ui.shortcutFind = QShortcut(QKeySequence("Ctrl+F"), self) self.ui.shortcutFind.activated.connect(self.highlight_query) self.ui.btnFilterSelAll.clicked.connect(lambda: self.filterSetAll(True)) self.ui.btnFilterSelNone.clicked.connect(lambda: self.filterSetAll(False)) # Unchecked is easier to work with, # since it searches everything if nothing's given. self.filterSetAll(False) self.ui.treeResults.setHeaderLabel("Results (0)") self.ui.actionCopyPath = QtGui.QAction("Copy path", None, triggered = self.copyPath) self.ui.treeResults.addAction(self.ui.actionCopyPath) self.transHighlighter = KeywordHighlighter(self.ui.txtTranslated.document()) self.origHighlighter = KeywordHighlighter(self.ui.txtOriginal.document()) self.commHighlighter = KeywordHighlighter(self.ui.txtComments.document()) self.re_flags = re.UNICODE | re.MULTILINE ############################################################################## ### @fn copyPath() ### @desc Copies the path of the selected node to the clipboard. ############################################################################## def copyPath(self): node = self.ui.treeResults.currentItem() if not node == None: text = "{%s}" % tree.tree_item_to_path(node) clipboard = QApplication.clipboard() clipboard.setText(text) ############################################################################## ### @fn search() ### @desc Commences a search. ############################################################################## def search(self): text = common.qt_to_unicode(self.ui.txtQuery.text()) dir = common.editor_config.umdimage_dir if text == "": results = [] self.transHighlighter.clear_keywords() self.origHighlighter.clear_keywords() self.commHighlighter.clear_keywords() else: results = self.search_bar(text) self.transHighlighter.re_flags = self.re_flags self.origHighlighter.re_flags = self.re_flags self.commHighlighter.re_flags = self.re_flags if self.ui.chkAdvTrans.isChecked(): self.transHighlighter.set_keyword(Keyword(text)) if self.ui.chkAdvOrig.isChecked(): self.origHighlighter.set_keyword(Keyword(text)) if self.ui.chkAdvComments.isChecked(): self.commHighlighter.set_keyword(Keyword(text)) self.ui.treeResults.clear() self.ui.treeResults.setHeaderLabel("Results (%d)" % len(results)) if len(results) > 0: tree_items = [] for file in results: file = os.path.normpath(file) file = tree.consolidate_path(file) tree_item = tree.path_to_tree(file) tree_items.append(tree_item) tree_items = tree.consolidate_tree_items(tree_items) for item in tree_items: self.ui.treeResults.addTopLevelItem(item) self.ui.treeResults.expandAll() ############################################################################## ### @fn search_bar() ### @desc Search while displaying a progress bar. ############################################################################## def search_bar(self, query): matches = [] progress = QProgressDialog("", "Abort", 0, 50000, self) progress.setWindowTitle("Searching...") progress.setWindowModality(Qt.Qt.WindowModal) progress.setValue(0) width = self.width() height = self.height() x = self.x() y = self.y() self.re_flags = re.UNICODE | re.MULTILINE if not self.ui.chkAdvRegex.isChecked(): query = re.escape(query) if not self.ui.chkAdvCase.isChecked(): self.re_flags |= re.IGNORECASE if self.ui.chkAdvNewline.isChecked(): self.re_flags |= re.DOTALL query_re = re.compile(query, self.re_flags) dir_filter = common.qt_to_unicode(self.ui.txtFilterRe.text()) if dir_filter == "": filter_re = script_analytics.DEFAULT_FILTER else: filter_re = re.compile(dir_filter, re.IGNORECASE | re.DOTALL | re.UNICODE) self.search_flags = 0 if self.ui.chkAdvTrans.isChecked(): self.search_flags |= script_analytics.SEARCH_TRANSLATED if self.ui.chkAdvOrig.isChecked(): self.search_flags |= script_analytics.SEARCH_ORIGINAL if self.ui.chkAdvComments.isChecked(): self.search_flags |= script_analytics.SEARCH_COMMENTS if self.ui.chkAdvNoTags.isChecked(): self.search_flags |= script_analytics.SEARCH_NOTAGS matches = [] for i, total, filename, partial_results in script_analytics.SA.search_gen(query_re, filter_re, self.search_flags): if progress.wasCanceled(): break progress.setValue(i) progress.setMaximum(total) progress.setLabelText(filename) # Re-center the dialog. progress_w = progress.geometry().width() progress_h = progress.geometry().height() new_x = x + ((width - progress_w) / 2) new_y = y + ((height - progress_h) / 2) progress.move(new_x, new_y) matches.extend(partial_results) progress.close() return matches ############################################################################## ### @fn search_bar_old() ### @desc Search while displaying a progress bar. ############################################################################## def search_bar_old(self, dir, query): files = list_all_files(dir) progress = QProgressDialog("", "Abort", 0, len(files), self) progress.setWindowTitle("Searching...") progress.setWindowModality(Qt.Qt.WindowModal) progress.setValue(0) width = self.width() height = self.height() x = self.x() y = self.y() matches = [] if not self.ui.chkRegEx.isChecked(): query = re.escape(query) query_re = re.compile(query, re.IGNORECASE | re.DOTALL | re.UNICODE) for i, file in enumerate(files): if progress.wasCanceled(): break if i % 500 == 0: progress.setLabelText(file) # Re-center the dialog. progress_w = progress.geometry().width() progress_h = progress.geometry().height() new_x = x + ((width - progress_w) / 2) new_y = y + ((height - progress_h) / 2) progress.move(new_x, new_y) if os.path.splitext(file)[1] == ".txt": text = load_text(file) if not query_re.search(text) == None: matches.append(file) progress.setValue(i + 1) progress.close() return matches ############################################################################## ### @fn changedSearchFilter() ### @desc Triggered when the user clicks one of the search filter checkboxes. ############################################################################## def changedSearchFilter(self): PROLOGUE_RE = "e00" CH1_RE = "e01|mtb_s01" CH2_RE = "e02|mtb_s02" CH3_RE = "e03|mtb_s03" CH4_RE = "e04|mtb_s0[4-6]" CH5_RE = "e05|mtb_s07" CH6_RE = "e06|mtb_s(0[89]|10)" EPILOGUE_RE = "e07" FREETIME_RE = "^e08_00[1-9]|^e08_01[0-5]" SYS_RE = "^\d\d" MISC_RE = "^e08_000|^e08_016|^e08_020|^event|^voice" # If everything's checked, just leave the regex line blank # since blank means we'll search everything. all_checked = True for i in range(self.ui.layoutSearchFilter.count()): item = self.ui.layoutSearchFilter.itemAt(i) if item and not item.widget().isChecked(): all_checked = False break if all_checked: self.ui.txtFilterRe.clear() return # Otherwise, grab each of the individual checkbox statuses # and generate a regex from that. active_re = [] if self.ui.chkSearchPlg.isChecked(): active_re.append(PROLOGUE_RE) if self.ui.chkSearchCh1.isChecked(): active_re.append(CH1_RE) if self.ui.chkSearchCh2.isChecked(): active_re.append(CH2_RE) if self.ui.chkSearchCh3.isChecked(): active_re.append(CH3_RE) if self.ui.chkSearchCh4.isChecked(): active_re.append(CH4_RE) if self.ui.chkSearchCh5.isChecked(): active_re.append(CH5_RE) if self.ui.chkSearchCh6.isChecked(): active_re.append(CH6_RE) if self.ui.chkSearchEpg.isChecked(): active_re.append(EPILOGUE_RE) if self.ui.chkSearchFt.isChecked(): active_re.append(FREETIME_RE) if self.ui.chkSearchSys.isChecked(): active_re.append(SYS_RE) if self.ui.chkSearchEtc.isChecked(): active_re.append(MISC_RE) self.ui.txtFilterRe.setText("|".join(active_re)) ############################################################################## ### @fn filterSetAll(checked) ### @desc Triggered when the user clicks one of the select all/none buttons. ############################################################################## def filterSetAll(self, checked): if checked: state = Qt.Qt.Checked else: state = Qt.Qt.Unchecked for i in range(self.ui.layoutSearchFilter.count()): item = self.ui.layoutSearchFilter.itemAt(i) if item: item.widget().setCheckState(state) ############################################################################## ### @fn doubleClicked() ### @desc Triggered when the user double-clicks on an item in the tree. ############################################################################## def doubleClicked(self, item, column): if item.childCount() == 0: self.changedSelection(item, None) self.accept() ############################################################################## ### @fn changedSelection() ### @desc Triggered when the user selects something in the tree. ############################################################################## def changedSelection(self, current, prev): if current == None or current.childCount() != 0: return file = common.qt_to_unicode(current.text(0)) path = tree.tree_item_to_path(current.parent()) path = dir_tools.expand_dir(path) filename = os.path.join(common.editor_config.umdimage_dir, path, file) if not os.path.isfile(filename): self.ui.txtTranslated.setPlainText("Could not load \"%s\"." % file) self.ui.txtOriginal.setPlainText("") self.ui.txtComments.setPlainText("") return script_file = ScriptFile(filename) notags = self.search_flags & script_analytics.SEARCH_NOTAGS self.ui.txtTranslated.setPlainText(script_file.translated_notags if notags else script_file.translated) self.ui.txtOriginal.setPlainText(script_file.original_notags if notags else script_file.original) self.ui.txtComments.setPlainText(script_file.comments) ############################################################################## ### @fn highlight_query() ### @desc Highlights the query and gives the box focus. ### For use when the user presses Ctrl+F or in similar situations. ############################################################################## def highlight_query(self): self.ui.txtQuery.setFocus(Qt.Qt.OtherFocusReason) self.ui.txtQuery.selectAll() ############################################################################## ### @fn accept() ### @desc Overrides the OK button to make sure a folder is selected. ############################################################################## def accept(self): if self.ui.treeResults.currentItem() == None or self.ui.treeResults.currentItem().childCount() != 0: return self.open_clicked.emit() #super(SearchMenu, self).accept() ############################################################################## ### @fn show() ### @desc Overrides the show routine to place the cursor in the search box. ############################################################################## def show(self): self.highlight_query() super(SearchMenu, self).show()
class KeywordEdit(QtGui.QTextEdit): def __init__(self, parent): super(KeywordEdit, self).__init__(parent) self.setAcceptRichText(False) self.highlighter = KeywordHighlighter(self.document()) self.setMouseTracking(True) self.last_edited = None #self.load_keywords() ############################################################################## ### @fn mouseMoveEvent(event) ### @desc So we can catch ToolTip events. ############################################################################## def mouseMoveEvent(self, event): tooltip, rect = self.get_tooltip(event.pos()) if not tooltip == "": QtGui.QToolTip.showText(event.globalPos(), tooltip, self, rect) else: QtGui.QToolTip.hideText() return super(KeywordEdit, self).mouseMoveEvent(event) ############################################################################## ### @fn words_at_pos(line, pos) ### @desc Figure out what keywords are at the specific position. ############################################################################## def words_at_pos(self, line, pos): words = [] for word in self.highlighter.matches[line]: if pos >= word[1] and pos < word[2]: words.append(word) return words ############################################################################## ### @fn get_tooltip(pos) ### @desc Returns the tooltip and the boundaries for it given the cursor pos. ############################################################################## def get_tooltip(self, pos): cursor_pos = self.document().documentLayout().hitTest(QtCore.QPointF(pos), Qt.Qt.ExactHit) if cursor_pos == -1: return "", QRect() cursor = QTextCursor(self.document()) cursor.setPosition(cursor_pos) col = cursor.positionInBlock() line = cursor.blockNumber() tooltip = "" rect = QRect() if line >= len(self.highlighter.matches): return "", QRect() match_start = 0 match_len = 0 matches = 0 #for word in self.highlighter.matches[line]: #if col >= word[1] and col < word[2]: for word in self.words_at_pos(line, col): keyword = word[0] # Go for the smallest region possible so we can update # as soon as the mouse falls out of range of one of the words. if word[1] > match_start: match_start = word[1] if word[2] - word[1] < match_len: match_len = word[2] - word[1] if matches > 0: tooltip += u"\n" if not keyword.section == "": tooltip += u"【%s】 " % keyword.section tooltip += u"%s ― %s" % (keyword.word, keyword.meaning) matches += 1 # Highlight our word, so we can get the rect. cursor.movePosition(QTextCursor.Start) for i in range(line): cursor.movePosition(QTextCursor.NextBlock) for i in range(match_start): cursor.movePosition(QTextCursor.NextCharacter) for i in range(match_len): cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor) rect = self.cursorRect(cursor) return tooltip, rect ############################################################################## ### @fn contextMenuEvent(event) ### @desc Generates a context menu depending on what keywords are under the cursor. ############################################################################## def contextMenuEvent(self, event): menu = QtGui.QMenu() last_pos = event.pos() cursor = self.cursorForPosition(last_pos) pos = cursor.positionInBlock() line = cursor.blockNumber() keywords = self.words_at_pos(line, pos) if len(keywords) > 0: keyword_mapper = QSignalMapper(self) actions = [] for keyword in keywords: action_text = "Copy \"%s\"" % keyword[0].meaning actions.append(QtGui.QAction(action_text, None)) # We can only send strings with the signal mapper, so pickle our data. data = pickle.dumps(keyword[0]) data = QtCore.QString.fromAscii(data) self.connect(actions[-1], QtCore.SIGNAL("triggered()"), keyword_mapper, QtCore.SLOT("map()")) keyword_mapper.setMapping(actions[-1], data) self.connect(keyword_mapper, QtCore.SIGNAL("mapped(QString)"), self.copy_keyword) menu.addActions(actions) menu.addSeparator() default_menu = self.createStandardContextMenu() menu.addActions(default_menu.actions()) menu.exec_(event.globalPos()) ############################################################################## ### @fn copy_keyword(data) ### @desc Sends the "meaning" portion of a keyword to the clipboard. ############################################################################## def copy_keyword(self, data): # Since Qt's Signal Mapper will only accept a QString, we have to convert it # to a Python string in such a way that it won't try to actually encode/decode # any of the data, because nothing but the container changed when it turned # into a QString. keyword = pickle.loads(data.toAscii().data()) clipboard = QApplication.clipboard() clipboard.setText(keyword.meaning) ############################################################################## ### @fn load_keywords() ### @desc Checks to see if the terminology file has been changed since ### the last load, and if not, loads them. ############################################################################## def load_keywords(self): csv_edited = terminology.last_edited() if self.last_edited == csv_edited: return else: self.last_edited = csv_edited self.highlighter.clear_keywords() rows = terminology.load_csv() for row in rows: section = row['Section'].decode("UTF-8") word = row['Word'].decode("UTF-8") meaning = row['Meaning'].decode("UTF-8") term = terminology.Term(word, meaning, section) self.highlighter.add_keyword(term) self.highlighter.rehighlight() ############################################################################## ### @fn clear_keywords() ### @desc Bye bye. ############################################################################## def clear_keywords(self): self.highlighter.clear_keywords() self.highlighter.rehighlight() # So we force a reload. self.last_edited = -1 ### EOF ###