def run(self): debug('translate running...') (accessible_window, gdk_window) = active_window() if not accessible_window: debug('No active window. Do nothing.') return debug('active: %s' % accessible_window) qingfanyi.styles.init() debug('taking snapshot') snapshot = Snapshot(accessible_window, gdk_window) snapshot_matcher = SnapshotMatcher(snapshot, self.dic) debug('creating translate window') translate_win = TranslateWindow(snapshot, snapshot_matcher) translate_win.show() snapshot_matcher.start() PopupManager(translate_win) # nested loop to make run() blocking translate_win.connect('hide', lambda *_: Gtk.main_quit()) Gtk.main()
def run_in_other_thread(self): self.condvar.acquire() GLib.idle_add(self.run_in_this_thread) self.condvar.wait() debug('run in other thread done') if self.error: raise self.error
def _pinyin_to_diacritic(word): # last character should be the tone try: tone = int(word[-1]) except ValueError: # must be a special case... return word w = word[0:-1] w = w.replace('u:', u'ü') # https://en.wikipedia.org/wiki/Pinyin # # "the tone mark should always be placed by the order - a, o, e, i, u, ü, with the only exception being iu, # where the tone mark is placed on the u instead" chars = [u'iu'] chars.extend([c for c in u'aoeiuü']) indexes = [(c, w.lower().find(c)) for c in chars] indexes = [(c, i) for (c, i) in indexes if i != -1] if not indexes: # Weird, a word without vowels? debug('vowel-less word? %s' % word) return word (c, index) = min(indexes, key=lambda (c, i): i) dia = _TONE_DIACRITIC[tone] offset = len(c) w = w[0:index+offset] + dia + w[index+offset:] return w
def maybe_fail(): if attempts <= 0: debug(' cannot find active window after several attempts') return _guess_active_window() debug(' try again to find active window') time.sleep(0.1) return active_window(attempts - 1)
def accept_match(match): # Only accept the match if all of its rects are within our geometry for rect in match.rects: if not rect_within(self.snapshot.geometry, rect): debug('rejecting match outside of window: %s' % match) return False return True
def _match_next(self): if self._stop: return False matches = [] out = True begin = time.time() def should_stop(): return time.time() - begin > BATCH_TIME while True: try: if self._cursor is None: self._cursor = Cursor(*next(self._texts)) self._cursor = self._cursor_next(self._cursor, matches) except StopIteration: debug('no more text to match') out = False break if should_stop(): break self.emit('matches-found', matches) return out
def add_matches(self, sender, matches): def accept_match(match): # Only accept the match if all of its rects are within our geometry for rect in match.rects: if not rect_within(self.snapshot.geometry, rect): debug('rejecting match outside of window: %s' % match) return False return True matches = [m for m in matches if accept_match(m)] if not matches: self.unmatch_count += 1 # If we previously have found something, and now we repeatedly cannot match # anything, then tell the sender to stop - probably it's wasting time # processing text far off the screen if self.navigator.matches and self.unmatch_count > 5: debug( 'sender keeps sending offscreen stuff. asking it to stop.') sender.stop() return self.unmatch_count = 0 self.navigator.add_matches(matches) self.update_pixbuf_for_matches(matches)
def add_matches(self, sender, matches): def accept_match(match): # Only accept the match if all of its rects are within our geometry for rect in match.rects: if not rect_within(self.snapshot.geometry, rect): debug('rejecting match outside of window: %s' % match) return False return True matches = filter(accept_match, matches) if not matches: self.unmatch_count += 1 # If we previously have found something, and now we repeatedly cannot match # anything, then tell the sender to stop - probably it's wasting time # processing text far off the screen if self.navigator.matches and self.unmatch_count > 5: debug('sender keeps sending offscreen stuff. asking it to stop.') sender.stop() return self.unmatch_count = 0 self.navigator.add_matches(matches) self.update_pixbuf_for_matches(matches)
def stop_now(sig, *_): stop.append(sig) activate_queue.close() debug('stop due to signal %s' % sig) signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGUSR1, signal.SIG_DFL)
def _pinyin_to_diacritic(word): # last character should be the tone try: tone = int(word[-1]) except ValueError: # must be a special case... return word w = word[0:-1] w = w.replace('u:', u'ü') # https://en.wikipedia.org/wiki/Pinyin # # "the tone mark should always be placed by the order - a, o, e, i, u, ü, with the only exception being iu, # where the tone mark is placed on the u instead" chars = [u'iu'] chars.extend([c for c in u'aoeiuü']) indexes = [(c, w.lower().find(c)) for c in chars] indexes = [(c, i) for (c, i) in indexes if i != -1] if not indexes: # Weird, a word without vowels? debug('vowel-less word? %s' % word) return word (c, index) = min(indexes, key=lambda x: x[1]) dia = _TONE_DIACRITIC[tone] offset = len(c) w = w[0:index + offset] + dia + w[index + offset:] return w
def __init__(self, parent, match): Gtk.Window.__init__(self, Gtk.WindowType.POPUP) self.set_name('popup_window') # FIXME: any way to do this with CSS? Can't make it work... self.set_border_width(5) self.set_size_request(80, -1) layout = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) _add_labels(layout, match) layout.show() self.add(layout) # Initially set a position near the match (at least so it's on the right monitor); # init_position is expected to make this better later (x, y, _, _) = match.rects[0] self.move(x, y) debug('should popup for %s' % match) def do_init_position(*_): self.init_position(parent, match) self.connect('size-allocate', do_init_position)
def accept_match(match): # Only accept the match if all of its rects are within our geometry for rect in match.rects: if not rect_within(self.snapshot.geometry, rect): debug('rejecting match outside of window: %s' % match) return False return True
def _add_labels(layout, match): debug('add labels') def add(l): l.show() l.set_alignment(0, 0) layout.pack_start(l, True, False, 0) # https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat text_label = Gtk.Label(match.text) text_label.set_name('match') add(text_label) for record in match.records: sep = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL) sep.show() layout.pack_start(sep, True, False, 0) label = Gtk.Label(record.pinyin) label.set_name('main_pinyin') add(label) for en in record.en_US: label = Gtk.Label(u'• %s' % en) label.set_name('en_US') add(label)
def _on_shortcut(event, queue): debug('shortcut %s' % event) try: queue.put_nowait(event) except Queue.Full: # this means earlier shortcut is still being processed debug(' dropped shortcut')
def init_position(self, parent, match): self_w = self.get_allocated_width() self_h = self.get_allocated_height() screen = Gdk.Screen.get_default() monitor = screen.get_monitor_at_window(parent.get_window()) debug('monitor %d' % monitor) monitor_rect = screen.get_monitor_geometry(monitor) (root_x, root_y, root_w, root_h) = (monitor_rect.x, monitor_rect.y, monitor_rect.width, monitor_rect.height) (mx, my, mw, mh) = match.rects[0] def adjust_x(val): debug('adjust x %s: root (%s .. %s)' % (val, root_x, root_x + root_w)) if self_w >= root_w: return val while val + self_w > root_x + root_w: val -= 1 while val < root_x: val += 1 debug('adjust to %s' % val) return val # First try below. (x, y) = mx + mw/4, my + mh + 3 if y + self_h < root_y + root_h: self.move(adjust_x(x), y) return # Then above. (x, y) = mx + mw/4, my - self_h - 3 self.move(adjust_x(x), y)
def _on_shortcut(event, queue): debug('shortcut %s' % event) try: queue.put_nowait(event) except Full: # this means earlier shortcut is still being processed debug(' dropped shortcut')
def _match_next(self): if self._stop: return False matches = [] out = True begin = time.time() def should_stop(): return time.time() - begin > BATCH_TIME while True: try: if self._cursor is None: self._cursor = Cursor(*self._texts.next()) self._cursor = self._cursor_next(self._cursor, matches) except StopIteration: debug('no more text to match') out = False break if should_stop(): break self.emit('matches-found', matches) return out
def __init__(self, parent, match): Gtk.Window.__init__(self, Gtk.WindowType.POPUP) self.set_name('popup_window') # FIXME: any way to do this with CSS? Can't make it work... self.set_border_width(5) self.set_size_request(80, -1) layout = Gtk.Box.new(Gtk.Orientation.VERTICAL, 4) _add_labels(layout, match) layout.show() self.add(layout) # Initially set a position near the match (at least so it's on the right monitor); # init_position is expected to make this better later (x, y, _, _) = match.rects[0] self.move(x, y) debug('should popup for %s' % match) def do_init_position(*_): self.init_position(parent, match) self.connect('size-allocate', do_init_position)
def stop_now(sig, *_): stop.append(sig) activate_queue.close() debug('stop due to signal %s' % sig) signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGUSR1, signal.SIG_DFL)
def invert(self, rect): (x, y, w, h) = rect sub = self.pixbuf.new_subpixbuf(*rect).copy() pix = sub.get_pixels() alpha = sub.get_has_alpha() istride = sub.get_rowstride() bytes_per_pixel = 4 if alpha else 3 ostride = w * bytes_per_pixel bytes_total = ostride * h npix = bytearray(bytes_total) for i in range(h): for j in range(0, w * bytes_per_pixel, bytes_per_pixel): npix[i * ostride + j] = 255 - ord(pix[i * istride + j]) npix[i * ostride + j + 1] = 255 - ord(pix[i * istride + j + 1]) npix[i * ostride + j + 2] = 255 - ord(pix[i * istride + j + 2]) if alpha: npix[i * ostride + j + 3] = 255 bytes = GLib.Bytes.new(npix) debug('have bytes: %s for rect: %s' % (bytes.get_size(), rect)) sub = GdkPixbuf.Pixbuf.new_from_bytes( bytes, GdkPixbuf.Colorspace.RGB, alpha, 8, w, h, ostride ) sub.copy_area(0, 0, w, h, self.pixbuf, x, y) self.img.set_from_pixbuf(self.pixbuf)
def init_position(self, parent, match): self_w = self.get_allocated_width() self_h = self.get_allocated_height() screen = Gdk.Screen.get_default() monitor = screen.get_monitor_at_window(parent.get_window()) debug('monitor %d' % monitor) monitor_rect = screen.get_monitor_geometry(monitor) (root_x, root_y, root_w, root_h) = (monitor_rect.x, monitor_rect.y, monitor_rect.width, monitor_rect.height) (mx, my, mw, mh) = match.rects[0] def adjust_x(val): debug('adjust x %s: root (%s .. %s)' % (val, root_x, root_x + root_w)) if self_w >= root_w: return val while val + self_w > root_x + root_w: val -= 1 while val < root_x: val += 1 debug('adjust to %s' % val) return val # First try below. (x, y) = mx + mw / 4, my + mh + 3 if y + self_h < root_y + root_h: self.move(adjust_x(x), y) return # Then above. (x, y) = mx + mw / 4, my - self_h - 3 self.move(adjust_x(x), y)
def run(self): debug('translate running...') (accessible_window, gdk_window) = active_window() if not accessible_window: debug('No active window. Do nothing.') return debug('active: %s' % accessible_window) qingfanyi.styles.init() debug('taking snapshot') snapshot = Snapshot(accessible_window, gdk_window) snapshot_matcher = SnapshotMatcher(snapshot, self.dic) debug('creating translate window') translate_win = TranslateWindow(snapshot, snapshot_matcher) translate_win.show() snapshot_matcher.start() PopupManager(translate_win) # nested loop to make run() blocking translate_win.connect('hide', lambda *_: Gtk.main_quit()) Gtk.main()
def invert(self, rect): (x, y, w, h) = rect sub = self.pixbuf.new_subpixbuf(*rect).copy() pix = sub.get_pixels() alpha = sub.get_has_alpha() istride = sub.get_rowstride() bytes_per_pixel = 4 if alpha else 3 ostride = w * bytes_per_pixel bytes_total = ostride * h npix = bytearray(bytes_total) for i in range(h): for j in range(0, w * bytes_per_pixel, bytes_per_pixel): npix[i * ostride + j] = 255 - pix[i * istride + j] npix[i * ostride + j + 1] = 255 - pix[i * istride + j + 1] npix[i * ostride + j + 2] = 255 - pix[i * istride + j + 2] if alpha: npix[i * ostride + j + 3] = 255 bytes = GLib.Bytes.new(npix) debug('have bytes: %s for rect: %s' % (bytes.get_size(), rect)) sub = GdkPixbuf.Pixbuf.new_from_bytes(bytes, GdkPixbuf.Colorspace.RGB, alpha, 8, w, h, ostride) sub.copy_area(0, 0, w, h, self.pixbuf, x, y) self.img.set_from_pixbuf(self.pixbuf)
def maybe_fail(): if attempts <= 0: debug(' cannot find active window after several attempts') return _guess_active_window() debug(' try again to find active window') time.sleep(0.1) return active_window(attempts - 1)
def run_in_other_thread(self): self.condvar.acquire() GLib.idle_add(self.run_in_this_thread) self.condvar.wait() debug('run in other thread done') if self.error: raise self.error
def _start_keybind_process(queue): debug('keybind process starting') import qingfanyi.process.keybind try: qingfanyi.process.keybind.run(queue) except: os.kill(os.getppid(), signal.SIGUSR1) raise
def _atspi_windows(): desktop = pyatspi.Registry.getDesktop(0) for app in desktop: try: for window in app: yield window except GLib.Error as e: debug('error from app %s: %s' % (app, e))
def _atspi_windows(): desktop = pyatspi.Registry.getDesktop(0) for app in desktop: try: for window in app: yield window except GLib.Error as e: debug('error from app %s: %s' % (app, e))
def _start_keybind_process(queue): debug('keybind process starting') import qingfanyi.process.keybind try: qingfanyi.process.keybind.run(queue) except: os.kill(os.getppid(), signal.SIGUSR1) raise
def on_current_match_changed(self, _sender, prev_match, match): debug('match has changed from %s to %s' % (prev_match, match)) if prev_match: self.invert_for_match(prev_match) if match: self.invert_for_match(match) self.emit('lookup-requested', match)
def on_current_match_changed(self, _sender, prev_match, match): debug('match has changed from %s to %s' % (prev_match, match)) if prev_match: self.invert_for_match(prev_match) if match: self.invert_for_match(match) self.emit('lookup-requested', match)
def step(self): self.index -= 1 while not may_contain_chinese(self.current_text[0]) and not self.finished: self.index -= 1 if self.finished: return extents = self.text_object.getCharacterExtents(self.index, 0) debug('get rect for index %d == %s' % (self.index, extents)) self.rects[self.index] = extents
def _atspi_active_windows(): """ :return: all windows AT-SPI claims are active (there can be more than one) """ for window in _atspi_windows(): state = window.getState() debug(' window %s state %s' % (window, state.states)) if state.contains(pyatspi.STATE_ACTIVE): debug(' ACTIVE') yield window
def adjust_x(val): debug('adjust x %s: root (%s .. %s)' % (val, root_x, root_x + root_w)) if self_w >= root_w: return val while val + self_w > root_x + root_w: val -= 1 while val < root_x: val += 1 debug('adjust to %s' % val) return val
def _atspi_active_windows(): """ :return: all windows AT-SPI claims are active (there can be more than one) """ for window in _atspi_windows(): state = window.getState() debug(' window %s state %s' % (window, state.states)) if state.contains(pyatspi.STATE_ACTIVE): debug(' ACTIVE') yield window
def adjust_x(val): debug('adjust x %s: root (%s .. %s)' % (val, root_x, root_x + root_w)) if self_w >= root_w: return val while val + self_w > root_x + root_w: val -= 1 while val < root_x: val += 1 debug('adjust to %s' % val) return val
def step(self): self.index -= 1 while not may_contain_chinese( self.current_text[0]) and not self.finished: self.index -= 1 if self.finished: return extents = self.text_object.getCharacterExtents(self.index, 0) debug('get rect for index %d == %s' % (self.index, extents)) self.rects[self.index] = extents
def __init__(self, accessible_window, gdk_window): (_, _, w, h) = gdk_window.get_geometry() (_, x, y) = gdk_window.get_origin() self.geometry = (x, y, w, h) debug('taking snapshot of %s' % gdk_window) self.pixbuf = Gdk.pixbuf_get_from_window(gdk_window, 0, 0, w, h) if not self.pixbuf: raise IOError('Could not get pixbuf from active window') self.accessible_window = accessible_window self.texts = _extract_texts(accessible_window)
def collect_chinese(accessible_object, _): text_object = get_text_object(accessible_object) if not text_object: return text = text_object.getText(0, -1) # NOTE: what if app is not using utf-8? debug('TEXT: %s' % text) if not may_contain_chinese(text): return out.append((text, text_object, accessible_object))
def __init__(self, accessible_window, gdk_window): (_, _, w, h) = gdk_window.get_geometry() (_, x, y) = gdk_window.get_origin() self.geometry = (x, y, w, h) debug('taking snapshot of %s' % gdk_window) self.pixbuf = Gdk.pixbuf_get_from_window(gdk_window, 0, 0, w, h) if not self.pixbuf: raise IOError('Could not get pixbuf from active window') self.accessible_window = accessible_window self.texts = _extract_texts(accessible_window)
def collect_chinese(accessible_object, _): text_object = get_text_object(accessible_object) if not text_object: return text = text_object.getText(0, -1) # NOTE: what if app is not using utf-8? text = unicode(text, 'utf-8') debug('TEXT: %s' % text) if not may_contain_chinese(text): return out.append((text, text_object, accessible_object))
def on_key_pressed(self, widget, event): debug('key pressed: %s' % ((event.keyval, event.string),)) key = event.keyval if key == Gdk.KEY_Left: self.navigator.navigate_offset(-1) elif key == Gdk.KEY_Right: self.navigator.navigate_offset(1) elif key == Gdk.KEY_Up: self.navigator.navigate_offset(-len(self.matches) / 10) elif key == Gdk.KEY_Down: self.navigator.navigate_offset(len(self.matches) / 10) elif event.string: self.destroy()
def from_line(line): out = Record() m = Record._CEDICT_PATTERN.match(line) if not m: raise ValueError("Not a valid CEDICT-formatted record: %s" % line) out.zh_TW = m.group(1) out.zh_CN = m.group(2) out.pinyin_num = m.group(3) out.en_US_num = m.group(4).split('/') debug('type of en_US_num: %s' % type(out.en_US_num[0])) return out
def from_line(line): out = Record() m = Record._CEDICT_PATTERN.match(line) if not m: raise ValueError("Not a valid CEDICT-formatted record: %s" % line) out.zh_TW = m.group(1) out.zh_CN = m.group(2) out.pinyin_num = m.group(3) out.en_US_num = m.group(4).split('/') debug('type of en_US_num: %s' % type(out.en_US_num[0])) return out
def ensure_index_built(index_filename=qingfanyi.dict.INDEX_FILENAME, dict_filename=qingfanyi.dict.DICT_FILENAME): if os.path.exists(index_filename): debug('Index is already built.') return print('Building index on first use.') resource_name = 'data/cedict_1_0_ts_utf-8_mdbg.txt' stream = resource_stream('qingfanyi', resource_name) assert stream build(resource_name, stream, logger=print, out_dict_filename=dict_filename, out_index_filename=index_filename)
def _cursor_next(self, cursor, matches): cursor.step() text = cursor.current_text debug('cursor idx %d, match text: %s' % (cursor.index, text)) if cursor.finished: return prefixes = self._dic.prefixes(text) for pref in prefixes: debug('prefix: %s' % pref) records = self._dic[pref] rects = cursor.current_rects(len(pref)) rects = join_rects(rects) matches.append(Match(pref, records, rects)) return cursor
def on_key_pressed(self, widget, event): debug('key pressed: %s' % ((event.keyval, event.string), )) key = event.keyval if key == Gdk.KEY_Left: self.navigator.navigate_offset(-1) elif key == Gdk.KEY_Right: self.navigator.navigate_offset(1) elif key == Gdk.KEY_Up: self.navigator.navigate_offset( int(-len(self.navigator.matches) / 10)) elif key == Gdk.KEY_Down: self.navigator.navigate_offset( int(len(self.navigator.matches) / 10)) elif event.string: self.destroy()
def ensure_index_built(index_filename=qingfanyi.dict.INDEX_FILENAME, dict_filename=qingfanyi.dict.DICT_FILENAME): if os.path.exists(index_filename): debug('Index is already built.') return print('Building index on first use.') resource_name = 'data/cedict_1_0_ts_utf-8_mdbg.txt' stream = resource_stream('qingfanyi', resource_name) assert stream build(resource_name, stream, logger=print, out_dict_filename=dict_filename, out_index_filename=index_filename)
def _cursor_next(self, cursor, matches): cursor.step() text = cursor.current_text debug('cursor idx %d, match text: %s' % (cursor.index, text)) if cursor.finished: return prefixes = self._dic.prefixes(text) for pref in prefixes: debug('prefix: %s' % pref) records = self._dic[pref] rects = cursor.current_rects(len(pref)) rects = join_rects(rects) matches.append(Match(pref, records, rects)) return cursor
def run(): ensure_index_built() activate_queue = Queue(1) keybind_process = Process(target=_start_keybind_process, args=(activate_queue,)) keybind_process.start() translate_pool = Pool(processes=1, initializer=_init_translate_process, maxtasksperchild=1) stop = [] def stop_now(sig, *_): stop.append(sig) activate_queue.close() debug('stop due to signal %s' % sig) signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGUSR1, signal.SIG_DFL) signal.signal(signal.SIGTERM, stop_now) signal.signal(signal.SIGINT, stop_now) signal.signal(signal.SIGUSR1, stop_now) while not stop: got = None try: got = activate_queue.get() except: if not stop: raise debug('parent got: %s' % got) if not got: break debug('invoke translate') try: translate_pool.apply(_run_translate_process) except StandardError as e: debug('failed: %s' % e) if stop[0] == signal.SIGUSR1: # keybind child signaled an error keybind_process.join(10) os._exit(7) debug('exiting normally') keybind_process.terminate() # FIXME: this always hangs. Why? # That's why we use _exit instead. #translate_pool.terminate() os._exit(0)
def run(): ensure_index_built() activate_queue = Queue(1) keybind_process = Process(target=_start_keybind_process, args=(activate_queue,)) keybind_process.start() translate_pool = Pool(processes=1, initializer=_init_translate_process, maxtasksperchild=1) stop = [] def stop_now(sig, *_): stop.append(sig) activate_queue.close() debug('stop due to signal %s' % sig) signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGUSR1, signal.SIG_DFL) signal.signal(signal.SIGTERM, stop_now) signal.signal(signal.SIGINT, stop_now) signal.signal(signal.SIGUSR1, stop_now) while not stop: got = None try: got = activate_queue.get() except: if not stop: raise debug('parent got: %s' % got) if not got: break debug('invoke translate') try: translate_pool.apply(_run_translate_process) except Exception as e: debug('failed: %s' % e) if stop[0] == signal.SIGUSR1: # keybind child signaled an error keybind_process.join(10) os._exit(7) debug('exiting normally') keybind_process.terminate() # FIXME: this always hangs. Why? # That's why we use _exit instead. #translate_pool.terminate() os._exit(0)
def visit_visible(root, callback, level=0): """ Visit every visible object in a hierarchy and invoke a provided callback. :param root: an AtSpi.Accessible object :param callback: invoked for each visible object with two parameters: the object, and the distance from root (e.g. 0 == root, 1 == child of root, 2 == grandchild ...) """ debug('%s%s' % (' ' * level, root)) if not _is_showing(root): debug('%s PRUNE' % (' ' * level)) return callback(root, level) for child in root: visit_visible(child, callback, level + 1)
def join_rects(rects): """ Given a list of rects for character extents, which must be ordered left-to-right and top-to-bottom, attempts to return a smaller list of larger rects covering the same area. Rects will be joined left-to-right, but not top-to-bottom, i.e. rects will not be extended over a line break. For example, if given rects for text like this: 你好吗 ... the 3 rects for each character will be joined to 1, but if given rects for text like this: 你好 吗 ... the 3 rects for each character will be joined to 1 for the first line and 1 for the second line, returning 2 rects total, neither spanning the line break. """ i = 0 end = len(rects) out = [] current_rect = None while i < end: this_rect = rects[i] debug('index %d rect %s' % (i, this_rect)) if current_rect: # Can we extend the current_rect to cover this_rect without increasing # its height? (curr_x, curr_y, curr_w, curr_h) = current_rect (this_x, this_y, this_w, this_h) = this_rect if curr_y == this_y and curr_h == this_h: # Yes. Keep extending current rect. debug(' extending') current_rect = rect_extend(current_rect, this_rect) else: # No. Save current rect and start a new one. debug(' new rect') out.append(current_rect) current_rect = this_rect else: current_rect = this_rect i += 1 debug(' end') out.append(current_rect) return out
def join_rects(rects): """ Given a list of rects for character extents, which must be ordered left-to-right and top-to-bottom, attempts to return a smaller list of larger rects covering the same area. Rects will be joined left-to-right, but not top-to-bottom, i.e. rects will not be extended over a line break. For example, if given rects for text like this: 你好吗 ... the 3 rects for each character will be joined to 1, but if given rects for text like this: 你好 吗 ... the 3 rects for each character will be joined to 1 for the first line and 1 for the second line, returning 2 rects total, neither spanning the line break. """ i = 0 end = len(rects) out = [] current_rect = None while i < end: this_rect = rects[i] debug('index %d rect %s' % (i, this_rect)) if current_rect: # Can we extend the current_rect to cover this_rect without increasing # its height? (curr_x, curr_y, curr_w, curr_h) = current_rect (this_x, this_y, this_w, this_h) = this_rect if curr_y == this_y and curr_h == this_h: # Yes. Keep extending current rect. debug(' extending') current_rect = rect_extend(current_rect, this_rect) else: # No. Save current rect and start a new one. debug(' new rect') out.append(current_rect) current_rect = this_rect else: current_rect = this_rect i += 1 debug(' end') out.append(current_rect) return out
def add_matches(self, matches): debug('BEGIN add %d matches' % len(matches)) # Retain the current match if there is one. current = self.current_match self.matches.extend(matches) self.matches.sort(key=_match_sort_key) if current: # Update current_match_index to new correct value. # It could be anywhere from its previous value up to +len(matches). for i in range(self.current_match_index, self.current_match_index + len(matches) + 1): if self.matches[i] is current: self.current_match_index = i break debug('END add matches') pass
def add_matches(self, matches): debug('BEGIN add %d matches' % len(matches)) # Retain the current match if there is one. current = self.current_match self.matches.extend(matches) self.matches.sort(key=_match_sort_key) if current: # Update current_match_index to new correct value. # It could be anywhere from its previous value up to +len(matches). for i in range(self.current_match_index, self.current_match_index + len(matches) + 1): if self.matches[i] is current: self.current_match_index = i break debug('END add matches') pass
def update_pixbuf_for_matches(self, matches): debug('BEGIN redraw...') (window_x, window_y, width, height) = self.snapshot.geometry copy_pb = [] current_match = self.navigator.current_match # Note: could improve to only process current_match if it overlaps. # But time savings are probably negligible. if current_match: matches.append(current_match) for m in matches: for (x, y, w, h) in m.rects: x -= window_x y -= window_y rect = (x, y, w, h) sub = self.snapshot.pixbuf.new_subpixbuf(*rect) if sub: sub = sub.copy() copy_pb.append((sub, rect)) for (pb, rect) in copy_pb: (x, y, w, h) = rect pb.copy_area(0, 0, w, h, self.pixbuf, x, y) if current_match: debug('inverting current match - %s' % current_match) self.invert_for_match(current_match) self.img.set_from_pixbuf(self.pixbuf) debug('END redraw')
def update_pixbuf_for_matches(self, matches): debug('BEGIN redraw...') (window_x, window_y, width, height) = self.snapshot.geometry copy_pb = [] current_match = self.navigator.current_match # Note: could improve to only process current_match if it overlaps. # But time savings are probably negligible. if current_match: matches.append(current_match) for m in matches: for (x, y, w, h) in m.rects: x -= window_x y -= window_y rect = (x, y, w, h) sub = self.snapshot.pixbuf.new_subpixbuf(*rect) if sub: sub = sub.copy() copy_pb.append((sub, rect)) for (pb, rect) in copy_pb: (x, y, w, h) = rect pb.copy_area(0, 0, w, h, self.pixbuf, x, y) if current_match: debug('inverting current match - %s' % current_match) self.invert_for_match(current_match) self.img.set_from_pixbuf(self.pixbuf) debug('END redraw')
def navigate_offset(self, offset): if not self.matches: return idx = prev_idx = self.current_match_index if idx is None: if offset == 1: idx = 0 else: idx = -1 else: idx += offset size = len(self.matches) while idx >= size: idx -= size while idx < 0: idx += size debug('navigate from %s by %s gives %s' % (prev_idx, offset, idx)) self.set_current_match(idx, self.matches[idx])