"""Filter controller.
Part of the tagit module.
A copy of the license is provided with the project.
Author: Matthias Baumgartner, 2016
"""
# IMPORTS
import json
import re
# INNER-MODULE IMPORTS
from controller import DataController
from ..basics import unique, split, string_types
from ..bindings import Binding
from browser import TAGS_SEPERATOR
from ..token import Token_Tag, Token_Include, Token_Exclude, token_factory
# EXPORTS
__all__ = ('CFilter', 'CFilterToken')
## CODE ##
class CFilterToken(DataController): pass # Just for get_root and parent
[docs]class CFilter(DataController):
def __init__(self, widget, model, settings, parent=None):
super(CFilter, self).__init__(widget, model, settings, parent)
self.active = []
self.inactive = []
# Database update
self.parent.bind(on_images_change=self.apply)
self.parent.bind(on_show_selected=self.selected_show)
self.parent.bind(on_remove_selected=self.selected_remove)
# Keyboard bindings
self.get_root().bind(on_keyboard=self.on_keyboard)
def __del__(self):
# Release bindings
self.get_root().unbind(on_keyboard=self.on_keyboard)
self.parent.unbind(on_images_change=self.apply)
self.parent.unbind(on_show_selected=self.selected_show)
self.parent.unbind(on_remove_selected=self.selected_remove)
[docs] def save(self, path):
"""Save the current filter to *path*.
"""
import json
fh = open(path, 'w')
json.dump((self.active, self.inactive), fh)
[docs] def load(self, path):
"""Load the filter from *path*.
"""
import json
self.active, self.inactive = json.load(open(path))
self.redraw()
self.apply()
def num_tokens(self):
return len(self.active)
###########################################################################
# WIDGET CONTROL #
###########################################################################
[docs] def redraw(self):
"""Update the widget."""
tokens_raw = self.active + self.inactive
self.widget.redraw(tokens_raw, len(self.active))
###########################################################################
# GETTERS #
###########################################################################
def get_tokens(self):
return self.active
def get_tags(self):
return filter(Token_Tag.match, self.active)
[docs] def apply(self):
"""Run the filter.
"""
# query the filter from the database
query_result = self.model.query(self.active)
# Propagate the result via the parent
self.parent.dispatch('on_results_change', self, query_result)
return False # Don't prevent other listeners to run.
###########################################################################
# OPERATIONS #
###########################################################################
[docs] def go_back(self, nSteps=1):
"""Remove the last *nSteps* tokens.
"""
self.inactive = self.active[-nSteps:] + self.inactive
self.active = self.active[:-nSteps]
self.redraw()
self.apply()
[docs] def go_forth(self, nSteps=1):
"""Re-add the next *nSteps* tokens.
"""
self.active = self.active + self.inactive[:nSteps]
self.inactive = self.inactive[nSteps:]
self.redraw()
self.apply()
[docs] def remove_token(self, token):
"""Remove a *token* from the token list.
"""
if token in self.active:
self.active.remove(token)
self.redraw()
self.apply()
if token in self.inactive:
self.inactive.remove(token)
self.redraw()
[docs] def add_token_tag(self, text):
"""Add a token for tag *text*."""
self.add_token(Token_Tag(text))
[docs] def add_variable_token(self, text):
"""Add a token and allow its type to be specified."""
parts = re.match('^!\s*(\w+)\s*:\s*(.*)$', text)
if parts is not None:
type_, args = parts.groups()
token = token_factory(type_, args)
self.add_token(token)
else:
self.add_token_tag(text)
[docs] def add_token(self, token):
"""Add a token *text*.
"""
if token not in self.active:
self.active.append(token)
self.inactive = []
self.redraw()
self.apply()
[docs] def edit_token(self, token, data):
"""Change *token* to *text*.
"""
if token in self.active:
if token.from_string(data) not in self.active:
# add to active
token.update(data)
# delete from inactive
if token in self.inactive:
self.inactive.remove(token)
self.redraw()
self.apply()
elif token in self.inactive:
if token.from_string(data) not in self.inactive:
# add to inactive
token.update(data)
# delete from active
post = lambda: True
if token in self.active:
self.active.remove(token)
post = self.apply
self.redraw()
post()
###########################################################################
# EVENTS #
###########################################################################
def selected_show(self, selection):
self.add_token(Token_Include(selection))
def selected_remove(self, selection):
self.add_token(Token_Exclude(selection))
###########################################################################
# KEYBINDINGS #
###########################################################################
[docs] def on_keyboard(self, wx, evt):
"""Handle filter keyboard events.
"""
if Binding.check(evt, self.settings.trace('bindings', 'filter', 'redraw ', Binding.simple(286))): # F5
self.apply()
return True
elif Binding.check(evt, self.settings.trace('bindings', 'filter', 'add_token ', Binding.simple(107, Binding.CTRL, Binding.REST))): # Ctrl + k
return self.request_add_token()
elif Binding.check(evt, self.settings.trace('bindings', 'filter', 'edit_filter', Binding.simple(108, Binding.CTRL, Binding.REST))): # Ctrl + l
return self.request_editor()
elif Binding.check(evt, self.settings.trace('bindings', 'filter', 'go_back ', Binding.multi((Binding.BACKSPACE, Binding.CTRL, Binding.REST), (Binding.LEFT, Binding.ALT, Binding.REST)))):
self.go_back(1)
return True
elif Binding.check(evt, self.settings.trace('bindings', 'filter', 'go_forth ', Binding.simple(Binding.RIGHT, Binding.ALT, Binding.REST))):
self.go_forth(1)
return True
return False
###########################################################################
# MOUSE EVENTS #
###########################################################################
[docs] def request_apply(self):
"""Apply the filter.
"""
self.apply()
return True
[docs] def request_add_token(self):
"""Add a token.
"""
self.widget.token_dialogue('', self.add_variable_token)
return True
[docs] def request_remove_token(self, token):
"""Remove token *token*.
"""
if token in self.active + self.inactive:
self.remove_token(token)
return True
return False
[docs] def request_edit_token(self, token, newtext=''):
"""Change *token*.
If *newtext* is empty, a dialogue will be requested.
Otherwise, *token* is changed to *newtext*.
"""
if token not in self.active + self.inactive:
return False
if newtext != '':
self.edit_token(token, newtext)
else:
self.widget.token_dialogue(token.edit(), lambda txt: self.edit_token(token, txt))
return True
def request_editor(self):
active, inactive = self.active, self.inactive
active = filter(Token_Tag.match, active)
inactive = filter(Token_Tag.match, inactive)
def on_ok(text):
# Remove tag tokens
for tok in active: self.active.remove(tok)
for tok in inactive: self.inactive.remove(tok)
# Add new tokens to the rear
for tok in text.split(TAGS_SEPERATOR):
tok = tok.strip()
if len(tok) > 0:
self.add_token_tag(tok)
def on_cancel(text):
self.redraw()
self.widget.show_editor((TAGS_SEPERATOR + ' ').join(map(lambda t: t.edit(), active)), on_ok, on_cancel)
## EOF ##