Commit 27ea4b08 authored by Ayush Mittal's avatar Ayush Mittal Committed by Alexandru Băluț

Allow searching actions

Fixes #2315
parent 29abee1f
Pipeline #154372 failed with stages
in 26 seconds
# -*- coding: utf-8 -*-
# Pitivi video editor
# Copyright (c) 2019, Ayush Mittal <ayush.mittal9398@gmail.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, see <http://www.gnu.org/licenses/>.
from gettext import gettext as _
from gi.repository import Gdk
from gi.repository import Gio
from gi.repository import Gtk
from pitivi.utils.ui import clear_styles
from pitivi.utils.ui import gtk_style_context_get_color
from pitivi.utils.ui import PADDING
class ActionSearchBar(Gtk.Window):
def __init__(self, app):
Gtk.Window.__init__(self)
self.app = app
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_size_request(600, 50)
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=PADDING)
self.set_decorated(False)
self.add(self.vbox)
self.entry = Gtk.SearchEntry()
self.results_window = Gtk.ScrolledWindow()
self.results_window.props.can_focus = False
clear_styles(self.entry)
self.setup_results_window()
self.entry.props.placeholder_text = _("Search Action")
self.entry.connect("key-press-event", self._entry_key_press_event_cb)
self.entry.connect("changed", self._entry_changed_cb)
self.treeview.connect("row-activated", self._treeview_row_activated_cb)
self.vbox.pack_start(self.entry, True, True, 0)
self.vbox.pack_start(self.results_window, True, True, 0)
def setup_results_window(self):
self.list_model = Gtk.ListStore(str, int, Gdk.ModifierType, Gio.SimpleAction, object, bool)
self.model_filter = self.list_model.filter_new()
disable_groups = ["medialibrary"]
style_context = self.entry.get_style_context()
color_insensitive = gtk_style_context_get_color(style_context, Gtk.StateFlags.INSENSITIVE)
for group in self.app.shortcuts.group_actions:
if group not in disable_groups:
for action, title, action_object in self.app.shortcuts.group_actions[group]:
accelerator_parsed = Gtk.accelerator_parse(self.app.get_accels_for_action(action)[0])
disabled = not action_object.props.enabled
self.list_model.append([title,
accelerator_parsed.accelerator_key,
accelerator_parsed.accelerator_mods,
action_object,
title.lower().split(" "),
disabled])
self.model_filter.set_visible_func(self.filter_func)
self.treeview = Gtk.TreeView.new_with_model(self.model_filter)
self.treeview.props.headers_visible = False
self.treeview.props.enable_search = False
self.treeview.props.can_focus = False
text_renderer = Gtk.CellRendererText()
text_renderer.props.foreground_rgba = color_insensitive
description_column = Gtk.TreeViewColumn("Description", text_renderer, text=0, foreground_set=5)
description_column.set_fixed_width(400)
self.treeview.append_column(description_column)
accel_renderer = Gtk.CellRendererAccel()
# The default is Gtk.CellRendererAccelMode.GTK, but with that one
# accelerator "Left" appears as "Invalid" for some reason.
accel_renderer.props.accel_mode = Gtk.CellRendererAccelMode.OTHER
accel_renderer.props.foreground_rgba = color_insensitive
accel_renderer.props.foreground_set = True
shortcut_column = Gtk.TreeViewColumn("Shortcut", accel_renderer, accel_key=1, accel_mods=2)
self.treeview.append_column(shortcut_column)
self.__select_row(self.model_filter.get_iter_first())
self.results_window.add(self.treeview)
self.results_window.props.min_content_height = 300
def filter_func(self, model, row, data):
text = self.entry.get_text()
if text:
self.results_window.show()
all_found = True
for keyword in text.lower().split(" "):
found = False
for term in model[row][4]:
if term.startswith(keyword):
found = True
break
if not found:
all_found = False
return all_found
else:
return True
def do_focus_out_event(self, event):
self.destroy()
return True
def _treeview_row_activated_cb(self, treeview, path, col):
self.__activate_selected_action()
def __activate_selected_action(self):
model, row_iter = self.treeview.get_selection().get_selected()
if not row_iter:
# No row is selected, possibly because there are no rows.
return
action_object, = model.get(row_iter, 3)
action_object.activate()
self.destroy()
def _entry_changed_cb(self, entry):
self.model_filter.refilter()
# Make sure a row is always selected.
self.__select_row(self.model_filter.get_iter_first())
def __select_row(self, row_iter):
if not row_iter:
return
self.treeview.get_selection().select_iter(row_iter)
row_path = self.model_filter.get_path(row_iter)
self.treeview.scroll_to_cell(row_path, None, False, 0, 0)
def _entry_key_press_event_cb(self, entry, event):
if event.keyval == Gdk.KEY_Escape:
self.destroy()
return True
if event.keyval == Gdk.KEY_Return:
self.__activate_selected_action()
return True
if event.keyval == Gdk.KEY_Up:
selection = self.treeview.get_selection()
model, row_iter = selection.get_selected()
if row_iter:
self.__select_row(model.iter_previous(row_iter))
return True
if event.keyval == Gdk.KEY_Down:
selection = self.treeview.get_selection()
model, row_iter = selection.get_selected()
if row_iter:
self.__select_row(model.iter_next(row_iter))
return True
# Let the default handler process this event.
return False
......@@ -161,25 +161,26 @@ class Pitivi(Gtk.Application, Loggable):
self.undo_action = Gio.SimpleAction.new("undo", None)
self.undo_action.connect("activate", self._undo_cb)
self.add_action(self.undo_action)
self.shortcuts.add("app.undo", ["<Primary>z"],
self.shortcuts.add("app.undo", ["<Primary>z"], self.undo_action,
_("Undo the most recent action"))
self.redo_action = Gio.SimpleAction.new("redo", None)
self.redo_action.connect("activate", self._redo_cb)
self.add_action(self.redo_action)
self.shortcuts.add("app.redo", ["<Primary><Shift>z"],
self.shortcuts.add("app.redo", ["<Primary><Shift>z"], self.redo_action,
_("Redo the most recent action"))
self.quit_action = Gio.SimpleAction.new("quit", None)
self.quit_action.connect("activate", self._quit_cb)
self.add_action(self.quit_action)
self.shortcuts.add("app.quit", ["<Primary>q"], _("Quit"))
self.shortcuts.add("app.quit", ["<Primary>q"], self.quit_action, _("Quit"))
self.show_shortcuts_action = Gio.SimpleAction.new("shortcuts_window", None)
self.show_shortcuts_action.connect("activate", self._show_shortcuts_cb)
self.add_action(self.show_shortcuts_action)
self.shortcuts.add("app.shortcuts_window",
["<Primary>F1", "<Primary>question"],
self.show_shortcuts_action,
_("Show the Shortcuts Window"))
def do_activate(self):
......
......@@ -393,7 +393,7 @@ class PreferencesDialog(Loggable):
index = 0
for group in shortcuts_manager.groups:
actions = shortcuts_manager.group_actions[group]
for action, title in actions:
for action, title, _ in actions:
item = ModelItem(self.app, action, title, group)
self.list_store.append(item)
self.action_ids[action] = index
......
......@@ -351,7 +351,7 @@ class EditorPerspective(Perspective, Loggable):
self.save_action = Gio.SimpleAction.new("save", None)
self.save_action.connect("activate", self.__save_project_cb)
group.add_action(self.save_action)
self.app.shortcuts.add("editor.save", ["<Primary>s"],
self.app.shortcuts.add("editor.save", ["<Primary>s"], self.save_action,
_("Save the current project"), group="win")
self.save_button.set_action_name("editor.save")
......@@ -359,6 +359,7 @@ class EditorPerspective(Perspective, Loggable):
self.save_as_action.connect("activate", self.__save_project_as_cb)
group.add_action(self.save_as_action)
self.app.shortcuts.add("editor.save-as", ["<Primary><Shift>s"],
self.save_as_action,
_("Save the current project as"), group="win")
self.revert_to_saved_action = Gio.SimpleAction.new("revert-to-saved", None)
......@@ -381,6 +382,7 @@ class EditorPerspective(Perspective, Loggable):
self.import_asset_action.connect("activate", self.__import_asset_cb)
group.add_action(self.import_asset_action)
self.app.shortcuts.add("editor.import-asset", ["<Primary>i"],
self.import_asset_action,
_("Add media files to your project"), group="win")
def __import_asset_cb(self, unused_action, unused_param):
......
......@@ -275,12 +275,14 @@ class GreeterPerspective(Perspective):
self.new_project_action.connect("activate", self.__new_project_cb)
group.add_action(self.new_project_action)
self.app.shortcuts.add("greeter.new-project", ["<Primary>n"],
self.new_project_action,
_("Create a new project"), group="win")
self.open_project_action = Gio.SimpleAction.new("open-project", None)
self.open_project_action.connect("activate", self.__open_project_cb)
group.add_action(self.open_project_action)
self.app.shortcuts.add("greeter.open-project", ["<Primary>o"],
self.open_project_action,
_("Open a project"), group="win")
@staticmethod
......
......@@ -191,24 +191,28 @@ class MainWindow(Gtk.ApplicationWindow, Loggable):
self.help_action = Gio.SimpleAction.new("help", None)
self.help_action.connect("activate", self.__user_manual_cb)
self.add_action(self.help_action)
self.app.shortcuts.add("win.help", ["F1"], _("Help"), group="app")
self.app.shortcuts.add("win.help", ["F1"], self.help_action,
_("Help"), group="app")
self.about_action = Gio.SimpleAction.new("about", None)
self.about_action.connect("activate", self.__about_cb)
self.add_action(self.about_action)
self.app.shortcuts.add("win.about", ["<Primary><Shift>a"],
self.about_action,
_("About"), group="app")
self.main_menu_action = Gio.SimpleAction.new("menu-button", None)
self.main_menu_action.connect("activate", self.__menu_cb)
self.add_action(self.main_menu_action)
self.app.shortcuts.add("win.menu-button", ["F10"],
self.main_menu_action,
_("Show the menu button content"), group="app")
self.preferences_action = Gio.SimpleAction.new("preferences", None)
self.preferences_action.connect("activate", self.__preferences_cb)
self.add_action(self.preferences_action)
self.app.shortcuts.add("win.preferences", ["<Primary>comma"],
self.preferences_action,
_("Preferences"), group="app")
@staticmethod
......
......@@ -661,12 +661,14 @@ class MediaLibraryWidget(Gtk.Box, Loggable):
self.remove_assets_action.connect("activate", self._remove_assets_cb)
actions_group.add_action(self.remove_assets_action)
self.app.shortcuts.add("medialibrary.remove-assets", ["<Primary>Delete"],
self.remove_assets_action,
_("Remove the selected assets"))
self.insert_at_end_action = Gio.SimpleAction.new("insert-assets-at-end", None)
self.insert_at_end_action.connect("activate", self._insert_end_cb)
actions_group.add_action(self.insert_at_end_action)
self.app.shortcuts.add("medialibrary.insert-assets-at-end", ["Insert"],
self.insert_at_end_action,
_("Insert selected assets at the end of the timeline"))
self._update_actions()
......
......@@ -69,11 +69,11 @@ class ShortcutsManager(GObject.Object):
"""
with open(self.config_path, "w") as conf_file:
for unused_group_id, actions in self.group_actions.items():
for action, unused_title in actions:
for action, unused_title, unused_action_object in actions:
accels = ",".join(self.app.get_accels_for_action(action))
conf_file.write(action + ":" + accels + "\n")
def add(self, action, accelerators, title, group=None):
def add(self, action, accelerators, action_object, title, group=None):
"""Adds an action to be displayed.
Args:
......@@ -83,6 +83,7 @@ class ShortcutsManager(GObject.Object):
the action. They are set as the accelerators of the action
only if no accelerators have been loaded from the config file
initially, when the current manager instance has been created.
action_object (Gio.SimpleAction): The object of the action.
title (str): The title of the action.
group (Optional[str]): The group id registered with `register_group`
to be used instead of that extracted from `action`.
......@@ -93,7 +94,7 @@ class ShortcutsManager(GObject.Object):
self.app.set_accels_for_action(action, accelerators)
action_prefix = group or action.split(".")[0]
self.group_actions[action_prefix].append((action, title))
self.group_actions[action_prefix].append((action, title, action_object))
def set(self, action, accelerators):
"""Sets accelerators for a shortcut.
......@@ -136,7 +137,7 @@ class ShortcutsManager(GObject.Object):
"""
group_name = action.split(".")[0]
for group in {group_name, "app", "win"}:
for neighbor_action, unused_title in self.group_actions[group]:
for neighbor_action, unused_title, unused_action_object in self.group_actions[group]:
if neighbor_action == action:
continue
for accel in self.app.get_accels_for_action(neighbor_action):
......@@ -202,7 +203,7 @@ class ShortcutsWindow(Gtk.ShortcutsWindow):
group = Gtk.ShortcutsGroup(title=self.app.shortcuts.group_titles[group_id])
group.show()
for action, title in actions:
for action, title, _ in actions:
# Show only the first accelerator which is the main one.
# Don't bother with the others, to keep the dialog pretty.
try:
......
......@@ -24,6 +24,7 @@ from gi.repository import GLib
from gi.repository import Gst
from gi.repository import Gtk
from pitivi.action_search_bar import ActionSearchBar
from pitivi.autoaligner import AlignmentProgressDialog
from pitivi.autoaligner import AutoAligner
from pitivi.configure import get_ui_dir
......@@ -1649,59 +1650,75 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
self.toolbar.insert_action_group("timeline", group)
self.app.shortcuts.register_group("timeline", _("Timeline"), position=30)
# Action Search Bar.
self.action_search = Gio.SimpleAction.new("action-search", None)
self.action_search.connect("activate", self.__action_search_cb)
group.add_action(self.action_search)
self.app.shortcuts.add("timeline.action-search", ["slash"],
self.action_search, _("Action Search"))
# Clips actions.
self.delete_action = Gio.SimpleAction.new("delete-selected-clips", None)
self.delete_action.connect("activate", self._delete_selected)
group.add_action(self.delete_action)
self.app.shortcuts.add("timeline.delete-selected-clips", ["Delete"],
self.delete_action,
_("Delete selected clips"))
self.delete_and_shift_action = Gio.SimpleAction.new("delete-selected-clips-and-shift", None)
self.delete_and_shift_action.connect("activate", self._delete_selected_and_shift)
group.add_action(self.delete_and_shift_action)
self.app.shortcuts.add("timeline.delete-selected-clips-and-shift", ["<Shift>Delete"],
self.delete_and_shift_action,
_("Delete selected clips and shift following ones"))
self.group_action = Gio.SimpleAction.new("group-selected-clips", None)
self.group_action.connect("activate", self._group_selected_cb)
group.add_action(self.group_action)
self.app.shortcuts.add("timeline.group-selected-clips", ["<Primary>g"],
self.group_action,
_("Group selected clips together"))
self.ungroup_action = Gio.SimpleAction.new("ungroup-selected-clips", None)
self.ungroup_action.connect("activate", self._ungroup_selected_cb)
group.add_action(self.ungroup_action)
self.app.shortcuts.add("timeline.ungroup-selected-clips", ["<Primary><Shift>g"],
self.ungroup_action,
_("Ungroup selected clips"))
self.copy_action = Gio.SimpleAction.new("copy-selected-clips", None)
self.copy_action.connect("activate", self.__copy_clips_cb)
group.add_action(self.copy_action)
self.app.shortcuts.add("timeline.copy-selected-clips", ["<Primary>c"],
self.copy_action,
_("Copy selected clips"))
self.paste_action = Gio.SimpleAction.new("paste-clips", None)
self.paste_action.connect("activate", self.__paste_clips_cb)
group.add_action(self.paste_action)
self.app.shortcuts.add("timeline.paste-clips", ["<Primary>v"],
self.paste_action,
_("Paste selected clips"))
self.add_layer_action = Gio.SimpleAction.new("add-layer", None)
self.add_layer_action.connect("activate", self.__add_layer_cb)
group.add_action(self.add_layer_action)
self.app.shortcuts.add("timeline.add-layer", ["<Primary>n"],
self.add_layer_action,
_("Add layer"))
self.seek_forward_clip_action = Gio.SimpleAction.new("seek-forward-clip", None)
self.seek_forward_clip_action.connect("activate", self._seek_forward_clip_cb)
group.add_action(self.seek_forward_clip_action)
self.app.shortcuts.add("timeline.seek-forward-clip", ["<Primary>Right"],
self.seek_forward_clip_action,
_("Seeks to the first clip edge after the playhead."))
self.seek_backward_clip_action = Gio.SimpleAction.new("seek-backward-clip", None)
self.seek_backward_clip_action.connect("activate", self._seek_backward_clip_cb)
group.add_action(self.seek_backward_clip_action)
self.app.shortcuts.add("timeline.seek-backward-clip", ["<Primary>Left"],
self.seek_backward_clip_action,
_("Seeks to the first clip edge before the playhead."))
if in_devel():
......@@ -1714,13 +1731,14 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
self.split_action.connect("activate", self._split_cb)
group.add_action(self.split_action)
self.split_action.set_enabled(True)
self.app.shortcuts.add("timeline.split-clips", ["s"],
self.app.shortcuts.add("timeline.split-clips", ["s"], self.split_action,
_("Split the clip at the position"))
self.keyframe_action = Gio.SimpleAction.new("keyframe-selected-clips", None)
self.keyframe_action.connect("activate", self._keyframe_cb)
group.add_action(self.keyframe_action)
self.app.shortcuts.add("timeline.keyframe-selected-clips", ["k"],
self.keyframe_action,
_("Add keyframe to the keyframe curve of selected clip"))
navigation_group = Gio.SimpleActionGroup()
......@@ -1733,35 +1751,43 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
navigation_group.add_action(self.zoom_in_action)
self.app.shortcuts.add("navigation.zoom-in",
["<Primary>plus", "<Primary>KP_Add", "<Primary>equal"],
self.zoom_in_action,
_("Zoom in"))
self.zoom_out_action = Gio.SimpleAction.new("zoom-out", None)
self.zoom_out_action.connect("activate", self._zoom_out_cb)
navigation_group.add_action(self.zoom_out_action)
self.app.shortcuts.add("navigation.zoom-out", ["<Primary>minus", "<Primary>KP_Subtract"],
self.app.shortcuts.add("navigation.zoom-out",
["<Primary>minus", "<Primary>KP_Subtract"],
self.zoom_out_action,
_("Zoom out"))
self.zoom_fit_action = Gio.SimpleAction.new("zoom-fit", None)
self.zoom_fit_action.connect("activate", self._zoom_fit_cb)
navigation_group.add_action(self.zoom_fit_action)
self.app.shortcuts.add("navigation.zoom-fit", ["<Primary>0", "<Primary>KP_0"],
self.app.shortcuts.add("navigation.zoom-fit",
["<Primary>0", "<Primary>KP_0"],
self.zoom_fit_action,
_("Adjust zoom to fit the project to the window"))
self.play_action = Gio.SimpleAction.new("play", None)
self.play_action.connect("activate", self._play_pause_cb)
navigation_group.add_action(self.play_action)
self.app.shortcuts.add("navigation.play", ["space"], _("Play"))
self.app.shortcuts.add("navigation.play", ["space"],
self.play_action, _("Play"))
self.backward_one_frame_action = Gio.SimpleAction.new("backward_one_frame", None)
self.backward_one_frame_action.connect("activate", self._seek_backward_one_frame_cb)
navigation_group.add_action(self.backward_one_frame_action)
self.app.shortcuts.add("navigation.backward_one_frame", ["Left"],
self.backward_one_frame_action,
_("Seek backward one frame"))
self.forward_one_frame_action = Gio.SimpleAction.new("forward_one_frame", None)
self.forward_one_frame_action.connect("activate", self._seek_forward_one_frame_cb)
navigation_group.add_action(self.forward_one_frame_action)
self.app.shortcuts.add("navigation.forward_one_frame", ["Right"],
self.forward_one_frame_action,
_("Seek forward one frame"))
self.backward_one_second_action = Gio.SimpleAction.new("backward_one_second", None)
......@@ -1769,6 +1795,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
navigation_group.add_action(self.backward_one_second_action)
self.app.shortcuts.add("navigation.backward_one_second",
["<Shift>Left"],
self.backward_one_second_action,
_("Seek backward one second"))
self.forward_one_second_action = Gio.SimpleAction.new("forward_one_second", None)
......@@ -1776,6 +1803,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
navigation_group.add_action(self.forward_one_second_action)
self.app.shortcuts.add("navigation.forward_one_second",
["<Shift>Right"],
self.forward_one_second_action,
_("Seek forward one second"))
self.update_actions()
......@@ -2114,3 +2142,9 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable):
def _gaplessmode_toggled_cb(self, unused_action, unused_parameter):
self._settings.timelineAutoRipple = self.gapless_button.get_active()
self.info("Automatic ripple: %s", self._settings.timelineAutoRipple)
def __action_search_cb(self, unused_action, unused_param):
win = ActionSearchBar(self.app)
win.set_transient_for(self.app.gui)
win.show_all()
......@@ -143,7 +143,7 @@ class Console(GObject.GObject, Peas.Activatable):
open_action = Gio.SimpleAction.new("open_console", None)
open_action.connect("activate", self.__menu_item_activate_cb)
self.app.add_action(open_action)
self.app.shortcuts.add("app.open_console", ["<Primary>d"], _("Developer Console"))
self.app.shortcuts.add("app.open_console", ["<Primary>d"], open_action, _("Developer Console"))
self._setup_dialog()
self.add_menu_item()
......
# -*- coding: utf-8 -*-
# Pitivi video editor
# Copyright (c) 2019, Ayush Mittal <ayush.mittal9398@gmail.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, see <http://www.gnu.org/licenses/>.
"""Test for Action Search Bar."""
from gi.repository import Gio
from pitivi.action_search_bar import ActionSearchBar
from pitivi.shortcuts import ShortcutsManager
from tests import common
class TestActionSearchBar(common.TestCase):
"""Tests for ActionSearchBar."""
def test_action_search(self):
app = common.create_pitivi()
shortcut_manager = ShortcutsManager(app)
shortcut_manager.register_group("alpha_group", "One", position=10)
shortcut_manager.register_group("beta_group", "Two", position=20)
action = Gio.SimpleAction.new("remove-effect", None)
shortcut_manager.add("alpha_group.first", ["<Primary>A"], action, "First action")
shortcut_manager.add("alpha_group.second", ["<Primary>B"], action, "Second action")
shortcut_manager.add("beta_group.first", ["<Primary>C"], action, "First beta action")
app.shortcuts = shortcut_manager
action_search_bar = ActionSearchBar(app)
# When entry is empty initially.
all_vals = ["First action", "Second action", "First beta action"]
self.assertEqual(self.result_vals(action_search_bar), all_vals)
# When entry is "First".
self.assertEqual(self.result_vals(action_search_bar, "First action"), ["First action", "First beta action"])
# When entry is "Second".
self.assertEqual(self.result_vals(action_search_bar, "Second"), ["Second action"])
def result_vals(self, action_search_bar, entry=''):
action_search_bar.entry.set_text(entry)
result_actions = []
row_iter = action_search_bar.model_filter.get_iter_first()
while row_iter is not None:
result_actions.append(action_search_bar.model_filter.get_value(row_iter, 0))