AttrVals: TypeAlias = list[str]
AttrValsByVisibility: TypeAlias = dict[str, AttrVals]
ItemsAttrs: TypeAlias = dict[str, AttrValsByVisibility]
-Cache: TypeAlias = dict[str, dict[str, dict[str, str | float | int]]]
+CachedImg: TypeAlias = dict[str, str | float | int]
+Cache: TypeAlias = dict[str, dict[str, CachedImg]]
Bookmarks: TypeAlias = list[str]
Db: TypeAlias = Cache | Bookmarks
FilterInputs: TypeAlias = dict[str, str]
class JsonDb:
"""Representation of our simple .json DB files."""
+ _content: Db
def __init__(self, path: str) -> None:
self._path = path
- self._content: Db = {}
self._is_open = False
if not path_exists(path):
with open(path, 'w', encoding='utf8') as f:
json_dump(self._content, f)
self._close()
- def as_copy(self) -> Db:
+
+class BookmarksDb(JsonDb):
+ """Representation of Bookmarks DB files."""
+ _content: Bookmarks
+
+ def as_ref(self) -> Bookmarks:
+ """Return content at ._path as ref so that .write() stores changes."""
+ self._open()
+ return self._content
+
+ def as_copy(self) -> Bookmarks:
"""Return content at ._path for read-only purposes."""
self._open()
- dict_copy = self._content.copy()
+ copy = self._content.copy()
self._close()
- return dict_copy
+ return copy
+
+
+class CacheDb(JsonDb):
+ """Representation of Cache DB files."""
+ _content: Cache
- def as_ref(self) -> Db:
+ def as_ref(self) -> Cache:
"""Return content at ._path as ref so that .write() stores changes."""
self._open()
return self._content
parser.add_argument('-s', '--sort-order', default=SORT_DEFAULT)
opts = parser.parse_args()
self.img_dir_absolute = abspath(opts.directory)
- self.bookmarks_db = JsonDb(BOOKMARKS_PATH)
- self.cache_db = JsonDb(CACHE_PATH)
+ self.bookmarks_db = BookmarksDb(BOOKMARKS_PATH)
+ self.cache_db = CacheDb(CACHE_PATH)
self.sort_order = SorterAndFiltererOrder.from_suggestion(
opts.sort_order.split(','))
class SorterAndFilterer(GObject.GObject):
"""Sort order box representation of sorting/filtering attribute."""
widget: Gtk.Box
- label: Gtk.Label
def __init__(self, name: str) -> None:
super().__init__()
self.name = name
def setup_on_bind(self,
- widget: Gtk.Widget,
+ widget: Gtk.Box,
on_filter_activate: Callable,
filter_text: str,
- vals: dict[str, str],
+ vals: AttrValsByVisibility,
) -> None:
"""Set up SorterAndFilterer label, values listing, filter entry."""
self.widget = widget
# label
len_incl = len(vals['incl'])
- len_semi_total = len_incl + len(vals['semi'])
- len_total = len_semi_total + len(vals['excl'])
+ len_semi_total: int = len_incl + len(vals['semi'])
+ len_total: int = len_semi_total + len(vals['excl'])
title = f'{self.name} ({len_incl}/{len_semi_total}/{len_total}) '
self.widget.label.set_text(title)
# values listing
- vals_listed = [f'<b>{v}</b>' for v in vals['incl']]
+ vals_listed: list[str] = [f'<b>{v}</b>' for v in vals['incl']]
vals_listed += [f'<s>{v}</s>' for v in vals['semi']]
vals_listed += [f'<b><s>{v}</s></b>' for v in vals['excl']]
self.widget.values.set_text(', '.join(vals_listed))
@classmethod
def from_suggestion(cls, suggestion: list[str]) -> Self:
"""Create new, interpreting order of strings in suggestion."""
- names = [p.lower() for p in GEN_PARAMS] + ['bookmarked']
- order = []
+ names: list[str] = [p.lower() for p in GEN_PARAMS] + ['bookmarked']
+ order: list[SorterAndFilterer] = []
for name in names:
order += [SorterAndFilterer(name)]
- new_order = []
- do_reverse = '-' in suggestion
+ new_order: list[SorterAndFilterer] = []
+ do_reverse: bool = '-' in suggestion
for pattern in suggestion:
for sorter in [sorter for sorter in order
if sorter.name.startswith(pattern)]:
def switch_at(self, selected_idx: int, forward: bool) -> None:
"""Switch elements at selected_idx and its neighbor."""
- selected = self[selected_idx]
- other_idx = selected_idx + (1 if forward else -1)
- other = self[other_idx]
+ selected: SorterAndFilterer = self[selected_idx]
+ other_idx: int = selected_idx + (1 if forward else -1)
+ other: SorterAndFilterer = self[other_idx]
self._list[other_idx] = selected
self._list[selected_idx] = other
def __init__(self, path: str, name: str) -> None:
super().__init__()
self.name = name
- self.full_path = path_join(path, self.name)
+ self.full_path: str = path_join(path, self.name)
self.slot: GallerySlot
def __hash__(self) -> int:
- hashable_values = []
+ hashable_values: list[str | bool] = []
for attr_name in self._to_hash:
hashable_values += [getattr(self, attr_name)]
return hash(tuple(hashable_values))
super().__init__(path, name)
mtime = getmtime(self.full_path)
dt = datetime.fromtimestamp(mtime, tz=timezone.utc)
- iso8601_str = dt.isoformat(timespec='microseconds')
- self.last_mod_time = iso8601_str.replace('+00:00', 'Z')
+ iso8601_str: str = dt.isoformat(timespec='microseconds')
+ self.last_mod_time: str = iso8601_str.replace('+00:00', 'Z')
self.bookmarked = False
self.with_others = False
self.has_metadata = False
gen_params = GenParams.from_str(gen_params_as_str)
for k, v_ in gen_params.as_dict.items():
setattr(self, k, v_)
- cached = {}
+ cached: CachedImg = {}
for k in (k.lower() for k in GEN_PARAMS):
cached[k] = getattr(self, k)
cache[self.full_path] = {self.last_mod_time: cached}
"""Collect variable sizes shared among all GallerySlots."""
def __init__(self) -> None:
- self._margin = GALLERY_SLOT_MARGIN
+ self._margin: int = GALLERY_SLOT_MARGIN
assert 0 == self._margin % 2 # avoid ._margin != 2 * .side_margin
- self.side_margin = self._margin // 2
- self.size, self.size_sans_margins = -1, -1
+ self.side_margin: int = self._margin // 2
+ self.size: int = -1
+ self.size_sans_margins: int = -1
def set_size(self, size: int) -> None:
"""Not only set .size but also update .size_sans_margins."""
def update_widget(self, is_in_vp: bool) -> None:
"""(Un-)load slot, for Imgs if (not) is_in_vp, update CSS class."""
- new_content = None
+ new_content: Optional[Gtk.Image | Gtk.Label] = None
if isinstance(self.item, ImgItem):
if is_in_vp and not isinstance(self.item, Gtk.Image):
new_content = Gtk.Image.new_from_file(self.item.full_path)
def __init__(self,
box: Gtk.Box,
- # sort_order: list[SorterAndFilterer],
sort_order: SorterAndFiltererOrder,
request_update: Callable,
update_settings: Callable,
def on_filter_activate(entry: Gtk.Box) -> None:
entry.remove_css_class('temp')
- text = entry.get_buffer().get_text()
+ text: str = entry.get_buffer().get_text()
if '' != text.rstrip():
self.filter_inputs[sorter.name] = text
elif sorter.name in self.filter_inputs:
del self.filter_inputs[sorter.name]
self._filter_inputs_changed = True
- sorter = list_item.props.item
+ sorter: SorterAndFilterer = list_item.props.item
sorter.setup_on_bind(list_item.props.child, on_filter_activate,
self.filter_inputs.get(sorter.name, ''),
self._gallery_items_attrs[sorter.name])
self.update_box(tmp_sort_order, cur_idx + direction)
self._sort_sel.props.selected = cur_idx + direction
for i in range(self._store.get_n_items()):
- sort_item = self._store.get_item(i)
+ sort_item: SorterAndFilterer = self._store.get_item(i)
sort_item.widget.add_css_class('temp')
def update_box(self,
def __init__(self,
on_hit_item: Callable,
on_selection_change: Callable,
- bookmarks_db: JsonDb,
- cache_db: JsonDb
+ bookmarks_db: BookmarksDb,
+ cache_db: CacheDb
) -> None:
self._on_hit_item = on_hit_item
self._on_selection_change = on_selection_change
self._bookmarks_db, self._cache_db = bookmarks_db, cache_db
self._sort_order = SorterAndFiltererOrder([])
self._filter_inputs: FilterInputs = {}
- self._img_dir_path = None
+ self._img_dir_path = ''
self._shall_load = False
self._shall_build = False
self._viewport.set_scroll_to_focus(False) # prefer our own handling
def ensure_uptodate() -> bool:
- if self._img_dir_path is None:
+ if not self._img_dir_path:
return True
if self._shall_load:
self._load_directory()
def _load_directory(self) -> None:
"""Rewrite .dir_entries from ._img_dir_path, trigger rebuild."""
+ self._shall_load = False
+ self.dir_entries.clear()
+ bookmarks = self._bookmarks_db.as_copy()
+ cache = self._cache_db.as_ref()
- def read_directory(dir_path, make_parent=False):
+ def read_directory(dir_path: str, make_parent: bool = False) -> None:
if make_parent:
parent_dir = DirItem(abspath(path_join(dir_path, UPPER_DIR)),
UPPER_DIR, is_parent=True)
self.dir_entries += [parent_dir]
- dirs_to_enter, to_set_metadata_on = [], []
+ dirs_to_enter: list[str] = []
+ to_set_metadata_on: list[ImgItem] = []
dir_entries = list(listdir(dir_path))
for i, filename in enumerate(dir_entries):
msg = f'loading {dir_path}: entry {i+1}/{len(dir_entries)}'
print(f'{prefix}{i+1}/{len(dirs_to_enter)}')
read_directory(path)
- self._shall_load = False
- self.dir_entries = []
- bookmarks = self._bookmarks_db.as_copy()
- cache = self._cache_db.as_ref()
read_directory(self._img_dir_path, make_parent=True)
self._cache_db.write()
self.request_update(build=True)
def _build(self) -> None:
"""(Re-)build slot grid from .dir_entries, filters, layout settings."""
+ self._shall_build = False
+ old_selected_item: Optional[GalleryItem] = self.selected_item
def build_items_attrs() -> None:
self.items_attrs.clear()
items_attrs_tmp: ItemsAttrs = {}
for attr_name in (s.name for s in self._sort_order):
items_attrs_tmp[attr_name] = {'incl': [], 'excl': []}
- vals = set()
+ vals: set[str] = set()
for entry in [e for e in entries
if isinstance(e, ImgItem)]:
val = (getattr(entry, attr_name)
self.items_attrs[attr_name] = final_values
def filter_entries(items_attrs: ItemsAttrs) -> list[GalleryItem]:
- entries_filtered = []
+ entries_filtered: list[GalleryItem] = []
for entry in self.dir_entries:
if (not self._show_dirs) and isinstance(entry, DirItem):
continue
def build_grid(entries_filtered: list[GalleryItem]) -> None:
i_row_ref, i_slot_ref = [0], [0]
+ if self._grid.get_parent():
+ self._fixed_frame.remove(self._grid)
+ self._grid = Gtk.Grid()
+ if self._col_headers_grid.get_parent():
+ self._col_headers_frame.remove(self._col_headers_grid)
+ self._col_headers_grid = Gtk.Grid()
+ self.slots.clear()
+ self._fixed_frame.put(self._grid, 0, 0)
def build_rows_by_attrs(
remaining: list[tuple[str, AttrVals]],
build_rows_by_attrs(remaining[1:], items_of_attr_value,
ancestors + [(attr_name, attr_value)])
- if self._grid.get_parent():
- self._fixed_frame.remove(self._grid)
- self._grid = Gtk.Grid()
- if self._col_headers_grid.get_parent():
- self._col_headers_frame.remove(self._col_headers_grid)
- self._col_headers_grid = Gtk.Grid()
- self.slots = []
- self._fixed_frame.put(self._grid, 0, 0)
if self._by_1st:
self._show_dirs = False
- sort_attrs = []
+ sort_attrs: list[tuple[str, AttrVals]] = []
for sorter in reversed(self._sort_order):
- vals = self.items_attrs[sorter.name]['incl']
+ vals: AttrVals = self.items_attrs[sorter.name]['incl']
if len(vals) > 1:
sort_attrs += [(sorter.name, vals)]
if not sort_attrs:
- s_name = self._sort_order[0].name
+ s_name: str = self._sort_order[0].name
sort_attrs += [(s_name, self.items_attrs[s_name]['incl'])]
self._per_row = len(sort_attrs[-1][1])
build_rows_by_attrs(sort_attrs, entries_filtered, [])
self._col_headers_frame.put(self._col_headers_grid, 0, 0)
self._col_headers_grid.attach(Gtk.Box(), 0, 0, 1, 1)
- top_attr_name = sort_attrs[-1][0]
+ top_attr_name: str = sort_attrs[-1][0]
for i, val in enumerate(sort_attrs[-1][1]):
label = Gtk.Label(label=f'<b>{top_attr_name}</b>: {val}',
xalign=0,
self._col_headers_grid.attach(label, i + 1, 0, 1, 1)
else:
- dir_entries_filtered_sorted = sorted(
+ dir_entries_filtered_sorted: list[GalleryItem] = sorted(
entries_filtered, key=cmp_to_key(self._sort_cmp))
i_row, i_col = 0, 0
for i, item in enumerate(dir_entries_filtered_sorted):
i_col += 1
self.update_config_box()
- self._shall_build = False
- old_selected_item = self.selected_item
build_items_attrs()
entries_filtered = filter_entries(self.items_attrs)
build_grid(entries_filtered)
def _redraw_and_check_focus(self) -> None:
"""Draw gallery; possibly notice and first follow need to re-focus."""
- vp_width = (self._force_width if self._force_width
- else self._viewport.get_width())
- vp_height = (self._force_height if self._force_height
- else self._viewport.get_height())
+ vp_width: int = (self._force_width if self._force_width
+ else self._viewport.get_width())
+ vp_height: int = (self._force_height if self._force_height
+ else self._viewport.get_height())
self._force_width, self._force_height = 0, 0
- vp_scroll = self._viewport.get_vadjustment()
- vp_top = vp_scroll.get_value()
- vp_bottom = vp_top + vp_height
+ vp_scroll: Gtk.Adjustment = self._viewport.get_vadjustment()
+ vp_top: int = vp_scroll.get_value()
+ vp_bottom: int = vp_top + vp_height
side_offset, i_vlabels = 0, 0
if self._by_1st:
while True:
- widget = self._grid.get_child_at(i_vlabels, 0)
- if isinstance(widget, VerticalLabel):
- side_offset += widget.width
+ gal_widget: VerticalLabel | GalleryItem
+ gal_widget = self._grid.get_child_at(i_vlabels, 0)
+ if isinstance(gal_widget, VerticalLabel):
+ side_offset += gal_widget.width
else:
break
i_vlabels += 1
- max_slot_width = (vp_width - side_offset) // self._per_row
+ max_slot_width: int = (vp_width - side_offset) // self._per_row
self._slots_geometry.set_size(min(vp_height, max_slot_width))
if self._by_1st:
i_widgets = 0
while True:
- widget = self._col_headers_grid.get_child_at(i_widgets, 0)
+ head_widget: Gtk.Box | Gtk.Label | None
+ head_widget = self._col_headers_grid.get_child_at(i_widgets, 0)
if 0 == i_widgets:
- widget.set_size_request(side_offset, -1)
- elif isinstance(widget, Gtk.Label):
- widget.set_size_request(self._slots_geometry.size, -1)
+ head_widget.set_size_request(side_offset, -1)
+ elif isinstance(head_widget, Gtk.Label):
+ head_widget.set_size_request(self._slots_geometry.size, -1)
else:
break
i_widgets += 1
vp_bottom: int,
in_vp_greedy: bool = False
) -> tuple[bool, int, int]:
- slot_top = (idx // self._per_row) * self._slots_geometry.size
- slot_bottom = slot_top + self._slots_geometry.size
+ slot_top: int = (idx // self._per_row) * self._slots_geometry.size
+ slot_bottom: int = slot_top + self._slots_geometry.size
if in_vp_greedy:
in_vp = (slot_bottom >= vp_top and slot_top <= vp_bottom)
else:
vp_top: int,
vp_bottom: int
) -> bool:
- scroll_to_focus = self._shall_scroll_to_focus
+ scroll_to_focus: bool = self._shall_scroll_to_focus
self._shall_redraw, self._shall_scroll_to_focus = False, False
if scroll_to_focus:
in_vp, slot_top, slot_bottom = self._position_to_viewport(
class MainWindow(Gtk.Window):
"""Image browser app top-level window."""
- metadata: Gtk.TextBuffer
- prev_key: list
- topbar: Gtk.Label
def __init__(self, app: Application, **kwargs) -> None:
super().__init__(**kwargs)
self.app = app
-
- def init_navbar() -> Gtk.Box:
- navbar = Gtk.Box(orientation=OR_H)
- _add_button(navbar, 'sidebar', lambda _: self.toggle_side_box())
- self.topbar = Gtk.Label()
- navbar.append(self.topbar)
- return navbar
-
- def init_metadata_box() -> Gtk.TextView:
- text_view = Gtk.TextView(wrap_mode=Gtk.WrapMode.WORD_CHAR,
- editable=False)
- text_view.set_size_request(300, -1)
- self.metadata = text_view.get_buffer()
- return text_view
-
- def init_key_control() -> None:
- key_ctl = Gtk.EventControllerKey(
- propagation_phase=Gtk.PropagationPhase.CAPTURE)
- key_ctl.connect('key-pressed',
- lambda _, kval, _0, _1: self.handle_keypress(kval))
- self.add_controller(key_ctl)
- self.prev_key = [0]
-
- def setup_css() -> None:
- css_provider = Gtk.CssProvider()
- css_provider.load_from_data(CSS)
- Gtk.StyleContext.add_provider_for_display(
- self.get_display(), css_provider,
- Gtk.STYLE_PROVIDER_PRIORITY_USER)
-
self.gallery = Gallery(
on_hit_item=self.hit_gallery_item,
on_selection_change=self.update_metadata_on_gallery_selection,
bookmarks_db=self.app.bookmarks_db,
cache_db=self.app.cache_db)
+ config_box = Gtk.Box(orientation=OR_V)
+ self.conf = GalleryConfig(
+ sort_order=self.app.sort_order,
+ box=config_box,
+ request_update=self.gallery.request_update,
+ update_settings=self.gallery.update_settings,
+ items_attrs=self.gallery.items_attrs)
+ self.gallery.update_config_box = self.conf.update_box
+ metadata_textview = Gtk.TextView(wrap_mode=Gtk.WrapMode.WORD_CHAR,
+ editable=False)
+ self.metadata = metadata_textview.get_buffer()
+ self.idx_display = Gtk.Label()
- setup_css()
- viewer = Gtk.Box(orientation=OR_V)
- self.navbar = init_navbar()
- viewer.append(self.navbar)
- viewer.append(self.gallery.frame)
+ # layout: outer box, CSS, sizings
+ box_outer = Gtk.Box(orientation=OR_H)
+ self.set_child(box_outer)
+ css_provider = Gtk.CssProvider()
+ css_provider.load_from_data(CSS)
+ Gtk.StyleContext.add_provider_for_display(
+ self.get_display(), css_provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_USER)
+ metadata_textview.set_size_request(300, -1)
+ self.connect('notify::default-width', lambda _, __: self.on_resize())
+ self.connect('notify::default-height', lambda _, __: self.on_resize())
+
+ # layout: sidebar
self.side_box = Gtk.Notebook.new()
- self.side_box.append_page(init_metadata_box(),
+ self.side_box.append_page(metadata_textview,
Gtk.Label(label='metadata'))
- config_box = Gtk.Box(orientation=OR_V)
self.side_box.append_page(config_box, Gtk.Label(label='config'))
- box_outer = Gtk.Box(orientation=OR_H)
box_outer.append(self.side_box)
+
+ # layout: gallery viewer
+ viewer = Gtk.Box(orientation=OR_V)
+ self.navbar = Gtk.Box(orientation=OR_H)
+ _add_button(self.navbar, 'sidebar', lambda _: self.toggle_side_box())
+ self.navbar.append(self.idx_display)
+ viewer.append(self.navbar)
+ viewer.append(self.gallery.frame)
box_outer.append(viewer)
- self.props.child = box_outer
- self.connect('notify::default-width', lambda _, __: self.on_resize())
- self.connect('notify::default-height', lambda _, __: self.on_resize())
- init_key_control()
+ # init key and focus control
+ key_ctl = Gtk.EventControllerKey(
+ propagation_phase=Gtk.PropagationPhase.CAPTURE)
+ key_ctl.connect('key-pressed',
+ lambda _, kval, _0, _1: self.handle_keypress(kval))
+ self.add_controller(key_ctl)
+ self.prev_key_ref = [0]
self.connect('notify::focus-widget',
lambda _, __: self.on_focus_change())
- self.conf = GalleryConfig(
- sort_order=self.app.sort_order,
- box=config_box,
- request_update=self.gallery.request_update,
- update_settings=self.gallery.update_settings,
- items_attrs=self.gallery.items_attrs)
- self.gallery.update_config_box = self.conf.update_box
+
+ # only now we're ready for actually running the gallery
GLib.idle_add(lambda: self.gallery.update_settings(
img_dir_path=self.app.img_dir_absolute,
sort_order=self.conf.order.copy(),
def on_focus_change(self) -> None:
"""Handle reactions on focus changes in .gallery and .conf."""
- focused = self.get_focus()
+ focused: Optional[Gtk.Widget] = self.get_focus()
if not focused:
return
if isinstance(focused, GallerySlot):
if self.get_width() > 0: # So we don't call this on initial resize.
# NB: We .measure side_box because its width is changing, whereas
# for the unchanging navbar .get_height is sufficient.
- side_box_width = self.side_box.measure(OR_H, -1).natural
- default_size = self.get_default_size()
+ side_box_width: int = self.side_box.measure(OR_H, -1).natural
+ default_size: tuple[int, int] = self.get_default_size()
self.gallery.on_resize(default_size[0] - side_box_width,
default_size[1] - self.navbar.get_height())
if not isinstance(self.gallery.selected_item, ImgItem):
return
bookmarks = self.app.bookmarks_db.as_ref()
- assert isinstance(bookmarks, list)
if self.gallery.selected_item.bookmarked:
self.gallery.selected_item.bookmark(False)
bookmarks.remove(self.gallery.selected_item.full_path)
def hit_gallery_item(self) -> None:
"""If current file selection is directory, reload into that one."""
- selected = self.gallery.selected_item
+ selected: Optional[GalleryItem] = self.gallery.selected_item
if isinstance(selected, DirItem):
self.gallery.update_settings(img_dir_path=selected.full_path)
self.side_box.props.visible = not self.side_box.get_visible()
# Calculate new viewport directly, because GTK's respective viewport
# measurement happens too late for our needs.
- side_box_width = self.side_box.measure(OR_H, -1).natural
+ side_box_width: int = self.side_box.measure(OR_H, -1).natural
self.gallery.on_resize(self.get_width() - side_box_width)
def update_metadata_on_gallery_selection(self) -> None:
- """Update .metadata about individual file, .topbar also on idx/total"""
+ """Update .metadata about individual file, and .idx_display."""
self.metadata.set_text('')
- selected_item = self.gallery.selected_item
+ selected_item: Optional[GalleryItem] = self.gallery.selected_item
display_name = '(none)'
if selected_item:
if isinstance(selected_item, ImgItem):
display_name = selected_item.full_path
total = len([s for s in self.gallery.slots
if isinstance(s.item, (DirItem, ImgItem))])
- n_selected = self.gallery.selected_idx + 1
+ n_selected: int = self.gallery.selected_idx + 1
txt = f' {n_selected} of {total} – <b>{display_name}</b>'
- self.topbar.set_text(txt)
- self.topbar.set_use_markup(True)
+ self.idx_display.set_text(txt)
+ self.idx_display.set_use_markup(True)
def handle_keypress(self, keyval: int) -> bool:
"""Handle keys if not in Gtk.Entry, return True if key handling done"""
self.gallery.move_selection(None, -1, None)
elif Gdk.KEY_l == keyval:
self.gallery.move_selection(+1, None, None)
- elif Gdk.KEY_g == keyval and Gdk.KEY_g == self.prev_key[0]:
+ elif Gdk.KEY_g == keyval and Gdk.KEY_g == self.prev_key_ref[0]:
self.gallery.move_selection(None, None, -1)
elif Gdk.KEY_w == keyval:
self.conf.move_selection(-1)
elif Gdk.KEY_b == keyval:
self.bookmark()
else:
- self.prev_key[0] = keyval
+ self.prev_key_ref[0] = keyval
return False
return True