diff options
author | Matthias Baumgartner <dev@igsor.net> | 2023-01-13 09:49:10 +0100 |
---|---|---|
committer | Matthias Baumgartner <dev@igsor.net> | 2023-01-13 09:49:10 +0100 |
commit | 9c366758665d9cfee7796ee45a8167a5412ae9ae (patch) | |
tree | b42e0a1fd4b1bd59fc31fad6267b83c2dc9a3a3b /tagit | |
parent | 8f2f697f7ed52b7e1c7a17411b2de526b6490691 (diff) | |
download | tagit-9c366758665d9cfee7796ee45a8167a5412ae9ae.tar.gz tagit-9c366758665d9cfee7796ee45a8167a5412ae9ae.tar.bz2 tagit-9c366758665d9cfee7796ee45a8167a5412ae9ae.zip |
filter early port, parsing adaptions
Diffstat (limited to 'tagit')
26 files changed, 1430 insertions, 152 deletions
diff --git a/tagit/actions/__init__.py b/tagit/actions/__init__.py index 034c4a1..c34cbe8 100644 --- a/tagit/actions/__init__.py +++ b/tagit/actions/__init__.py @@ -57,13 +57,13 @@ class ActionBuilder(BuilderBase): 'SelectInvert': browser.SelectInvert, 'Select': browser.Select, ## filter - #'AddToken': filter.AddToken, - #'RemoveToken': filter.RemoveToken, - #'SetToken': filter.SetToken, - #'EditToken': filter.EditToken, - #'GoBack': filter.GoBack, - #'GoForth': filter.GoForth, - #'JumpToToken': filter.JumpToToken, + 'AddToken': filter.AddToken, + 'RemoveToken': filter.RemoveToken, + 'SetToken': filter.SetToken, + 'EditToken': filter.EditToken, + 'GoBack': filter.GoBack, + 'GoForth': filter.GoForth, + 'JumpToToken': filter.JumpToToken, #'SearchByAddressOnce': filter.SearchByAddressOnce, #'SearchmodeSwitch': filter.SearchmodeSwitch, ## grouping diff --git a/tagit/actions/filter.py b/tagit/actions/filter.py index 3702879..e878952 100644 --- a/tagit/actions/filter.py +++ b/tagit/actions/filter.py @@ -12,14 +12,12 @@ from kivy.lang import Builder import kivy.properties as kp # tagit imports -from tagit import config -from tagit import dialogues -#from tagit.parsing import ParserError # FIXME: mb/port -#from tagit.parsing.search import ast_from_string, ast_to_string, ast # FIXME: mb/port -#from tagit.storage.base import ns # FIXME: mb/port -from tagit.utils import Frame -from tagit.widgets.bindings import Binding +from tagit import config, dialogues +from tagit.utils import errors, Frame +from tagit.widgets import Binding from tagit.widgets.filter import FilterAwareMixin +#from tagit.parsing.search import ast_to_string, ast # FIXME: mb/port +#from tagit.storage.base import ns # FIXME: mb/port # inner-module imports from .action import Action @@ -54,7 +52,7 @@ class SetToken(Action): with self.root.filter as filter: try: # parse filter into tokens - tokens = list(ast_from_string(text)) + tokens = list(self.root.session.filter_from_string(text)) # grab current frame filter.f_head.append(self.root.browser.frame) @@ -93,7 +91,10 @@ class AddToken(Action): def apply(self, token=None): if token is None: - sugg = {node.label for node in self.root.session.storage.all(ns.tagit.storage.base.Tag)} + #sugg = {node.label for node in self.root.session.storage.all(ns.tagit.storage.base.Tag)} + # FIXME: mb/port/bsfs + #sugg = set(self.root.session.storage.all(ns.bsfs.Tag).label()) + sugg = {'hello', 'world'} dlg = dialogues.TokenEdit(suggestions=sugg) dlg.bind(on_ok=lambda wx: self.add_from_string(wx.text)) dlg.open() @@ -104,8 +105,8 @@ class AddToken(Action): def add_from_string(self, text): try: - self.add_token(ast_from_string(text)) - except ParserError as e: + self.add_token(self.root.session.filter_from_string(text)) + except errors.ParserError as e: dialogues.Error(text=f'syntax error: {e}').open() def add_token(self, tokens): @@ -128,8 +129,10 @@ class EditToken(Action): text = kp.StringProperty('Edit token') def apply(self, token): - sugg = {node.label for node in self.root.session.storage.all(ns.tagit.storage.base.Tag)} - text = ast_to_string(token) + #sugg = {node.label for node in self.root.session.storage.all(ns.tagit.storage.base.Tag)} # FIXME: mb/port + sugg = {'hello', 'world'} # FIXME: mb/port + #text = ast_to_string(token) + text = 'hello world' dlg = dialogues.TokenEdit(text=text, suggestions=sugg) dlg.bind(on_ok=lambda obj: self.on_ok(token, obj)) dlg.open() @@ -137,8 +140,8 @@ class EditToken(Action): def on_ok(self, token, obj): with self.root.filter as filter: try: - tokens_from_text = ast_from_string(obj.text) - except ParserError as e: + tokens_from_text = self.root.session.filter_from_string(obj.text) # FIXME: mb/port + except errors.ParserError as e: dialogues.Error(text=f'Invalid token: {e}').open() return diff --git a/tagit/apps/port-config.yaml b/tagit/apps/port-config.yaml index 501eacd..d5c45e9 100644 --- a/tagit/apps/port-config.yaml +++ b/tagit/apps/port-config.yaml @@ -20,6 +20,7 @@ ui: - ShowDashboard #- AddTag #- EditTag + - AddToken #- CreateGroup #- DissolveGroup #- SelectAll @@ -30,6 +31,9 @@ ui: #- SelectSingle #- SelectMulti #- SelectRange + filter: + - AddToken + - EditToken context: app: - ShowSettings diff --git a/tagit/assets/icons/scalable/filter/add.svg b/tagit/assets/icons/scalable/filter/add.svg new file mode 100644 index 0000000..a544053 --- /dev/null +++ b/tagit/assets/icons/scalable/filter/add.svg @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="100mm" + height="100mm" + id="svg2" + version="1.1" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="add.svg" + inkscape:export-filename="../../kivy/filter/add.png" + inkscape:export-xdpi="7.6199999" + inkscape:export-ydpi="7.6199999"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.979899" + inkscape:cx="106.95662" + inkscape:cy="169.42756" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + inkscape:snap-global="true" + inkscape:snap-bbox="true" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1151" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + inkscape:pagecheckerboard="true" + units="mm" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-bbox-midpoints="true" + inkscape:object-paths="true" + inkscape:snap-intersection-paths="true" + inkscape:snap-smooth-nodes="true" + inkscape:snap-midpoints="true" + inkscape:snap-object-midpoints="true" + inkscape:snap-center="true" + inkscape:snap-text-baseline="true" + inkscape:snap-page="true" + inkscape:lockguides="false"> + <sodipodi:guide + position="188.97638,188.97638" + orientation="0,1" + id="guide1099" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="188.97638,188.97638" + orientation="1,0" + id="guide1101" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-167.28122,-322.85977)"> + <path + style="fill:none;stroke:#c8c8c8;stroke-width:29.81036186;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 182.82699,337.92736 123.49426,144.97182 v 203.0089 L 406.37348,630.06527 V 482.89684 L 529.86773,337.92736 H 182.82699" + id="path851" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/tagit/assets/icons/scalable/filter/address.svg b/tagit/assets/icons/scalable/filter/address.svg new file mode 100644 index 0000000..de30faa --- /dev/null +++ b/tagit/assets/icons/scalable/filter/address.svg @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="100mm" + height="100mm" + id="svg2" + version="1.1" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="address.svg" + inkscape:export-filename="../../kivy/filter/address.png" + inkscape:export-xdpi="7.6199999" + inkscape:export-ydpi="7.6199999"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.9899495" + inkscape:cx="8.0179354" + inkscape:cy="311.11216" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + inkscape:snap-global="true" + inkscape:snap-bbox="true" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1151" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + inkscape:pagecheckerboard="true" + units="mm" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-bbox-midpoints="true" + inkscape:object-paths="true" + inkscape:snap-intersection-paths="true" + inkscape:snap-smooth-nodes="true" + inkscape:snap-midpoints="true" + inkscape:snap-object-midpoints="true" + inkscape:snap-center="true" + inkscape:snap-text-baseline="true" + inkscape:snap-page="true" + inkscape:lockguides="false"> + <sodipodi:guide + position="188.97638,188.97638" + orientation="0,1" + id="guide1099" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="188.97638,188.97638" + orientation="1,0" + id="guide1101" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-167.28122,-322.85977)"> + <rect + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:17.85590553;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" + id="rect817" + width="360.09683" + height="220.61481" + x="176.20917" + y="401.52875" /> + <path + style="fill:none;stroke:#c8c8c8;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 364.4772,427.97815 v 173.777" + id="path833" + inkscape:connector-curvature="0" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:52.13865662px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.30346632" + x="195.92052" + y="571.07178" + id="text837"><tspan + sodipodi:role="line" + id="tspan835" + x="195.92052" + y="571.07178" + style="font-size:173.79553223px;fill:#ffffff;fill-opacity:1;stroke-width:1.30346632"><tspan + style="fill:#e6e6e6;fill-opacity:1;stroke-width:1.30346632" + id="tspan843">fo</tspan><tspan + style="fill:#828282;fill-opacity:1;stroke-width:1.30346632" + id="tspan841">o</tspan></tspan></text> + </g> +</svg> diff --git a/tagit/assets/icons/scalable/filter/go_back.svg b/tagit/assets/icons/scalable/filter/go_back.svg new file mode 100644 index 0000000..a972c87 --- /dev/null +++ b/tagit/assets/icons/scalable/filter/go_back.svg @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="100mm" + height="100mm" + id="svg2" + version="1.1" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="go_back.svg" + inkscape:export-filename="../../kivy/filter/go_back.png" + inkscape:export-xdpi="7.6199999" + inkscape:export-ydpi="7.6199999"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="2.8" + inkscape:cx="265.05952" + inkscape:cy="147.00905" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + inkscape:snap-global="true" + inkscape:snap-bbox="true" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1031" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + inkscape:pagecheckerboard="true" + units="mm" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-bbox-midpoints="true" + inkscape:object-paths="true" + inkscape:snap-intersection-paths="true" + inkscape:snap-smooth-nodes="true" + inkscape:snap-midpoints="true" + inkscape:snap-object-midpoints="true" + inkscape:snap-center="true" + inkscape:snap-text-baseline="true" + inkscape:snap-page="true" + inkscape:lockguides="false"> + <sodipodi:guide + orientation="0,1" + position="13.637059,643.40404" + id="guide3788" + inkscape:locked="false" /> + <sodipodi:guide + position="188.97638,188.97638" + orientation="0,1" + id="guide1099" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="188.97638,188.97638" + orientation="1,0" + id="guide1101" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="233.588,370" + orientation="1,0" + id="guide1107" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="144.36496,311.42857" + orientation="1,0" + id="guide1109" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="-77.142857,144.36496" + orientation="0,1" + id="guide1111" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="5.000315,233.58779" + orientation="0,1" + id="guide1113" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-167.28122,-322.85977)"> + <g + id="g883" + transform="matrix(-1,0,0,1,711.07945,-5.2302858e-6)"> + <path + inkscape:transform-center-y="-2.0036887e-06" + inkscape:transform-center-x="-64.791235" + d="m 543.79823,511.83615 -107.19675,61.89008 -107.19675,61.89007 0,-123.78015 0,-123.78014 107.19676,61.89007 z" + inkscape:randomized="0" + inkscape:rounded="0" + inkscape:flatsided="false" + sodipodi:arg2="1.0471976" + sodipodi:arg1="0" + sodipodi:r2="71.4645" + sodipodi:r1="142.929" + sodipodi:cy="511.83615" + sodipodi:cx="400.86923" + sodipodi:sides="3" + id="path2995" + style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:1.56328595;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + sodipodi:type="star" /> + <rect + y="462.98917" + x="165.84547" + height="97.693985" + width="231.62929" + id="rect4750" + style="opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:2.08440304;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4.16880612, 2.08440306;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + </g> + </g> +</svg> diff --git a/tagit/assets/icons/scalable/filter/go_forth.svg b/tagit/assets/icons/scalable/filter/go_forth.svg new file mode 100644 index 0000000..f4a246d --- /dev/null +++ b/tagit/assets/icons/scalable/filter/go_forth.svg @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="100mm" + height="100mm" + id="svg2" + version="1.1" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="go_forth.svg" + inkscape:export-filename="../../kivy/filter/go_forth.png" + inkscape:export-xdpi="7.6199999" + inkscape:export-ydpi="7.6199999"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="-59.38416" + inkscape:cy="86.716973" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + inkscape:snap-global="true" + inkscape:snap-bbox="true" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1031" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + inkscape:pagecheckerboard="true" + units="mm" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-bbox-midpoints="true" + inkscape:object-paths="true" + inkscape:snap-intersection-paths="true" + inkscape:snap-smooth-nodes="true" + inkscape:snap-midpoints="true" + inkscape:snap-object-midpoints="true" + inkscape:snap-center="true" + inkscape:snap-text-baseline="true" + inkscape:snap-page="true" + inkscape:lockguides="false"> + <sodipodi:guide + orientation="0,1" + position="13.637059,643.40404" + id="guide3788" + inkscape:locked="false" /> + <sodipodi:guide + position="188.97638,188.97638" + orientation="0,1" + id="guide1099" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="188.97638,188.97638" + orientation="1,0" + id="guide1101" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="233.588,370" + orientation="1,0" + id="guide1107" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="144.36496,311.42857" + orientation="1,0" + id="guide1109" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="-77.142857,144.36496" + orientation="0,1" + id="guide1111" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="5.000315,233.58779" + orientation="0,1" + id="guide1113" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-167.28122,-322.85977)"> + <path + sodipodi:type="star" + style="fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:1.56328595;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path2995" + sodipodi:sides="3" + sodipodi:cx="400.86923" + sodipodi:cy="511.83615" + sodipodi:r1="142.929" + sodipodi:r2="71.4645" + sodipodi:arg1="0" + sodipodi:arg2="1.0471976" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 543.79823,511.83615 -107.19675,61.89008 -107.19675,61.89007 0,-123.78015 0,-123.78014 107.19676,61.89007 z" + inkscape:transform-center-x="-64.791235" + inkscape:transform-center-y="-2.0036887e-06" /> + <rect + style="opacity:1;fill:#c8c8c8;fill-opacity:1;stroke:none;stroke-width:2.07793283;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4.15586594, 2.07793297;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" + id="rect4750" + width="230.19354" + height="97.693985" + x="167.28122" + y="462.98917" /> + </g> +</svg> diff --git a/tagit/assets/icons/scalable/filter/shingles.svg b/tagit/assets/icons/scalable/filter/shingles.svg new file mode 100644 index 0000000..a07b6ea --- /dev/null +++ b/tagit/assets/icons/scalable/filter/shingles.svg @@ -0,0 +1,217 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="100mm" + height="100mm" + id="svg2" + version="1.1" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="shingles.svg" + inkscape:export-filename="../../kivy/filter/shingles.png" + inkscape:export-xdpi="7.6199999" + inkscape:export-ydpi="7.6199999"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.98994949" + inkscape:cx="61.463033" + inkscape:cy="211.38181" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + showguides="true" + inkscape:guide-bbox="true" + inkscape:snap-global="true" + inkscape:snap-bbox="true" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1151" + inkscape:window-x="0" + inkscape:window-y="25" + inkscape:window-maximized="1" + inkscape:pagecheckerboard="true" + units="mm" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-bbox-midpoints="true" + inkscape:object-paths="true" + inkscape:snap-intersection-paths="true" + inkscape:snap-smooth-nodes="true" + inkscape:snap-midpoints="true" + inkscape:snap-object-midpoints="true" + inkscape:snap-center="true" + inkscape:snap-text-baseline="true" + inkscape:snap-page="true" + inkscape:lockguides="false"> + <sodipodi:guide + position="188.97638,188.97638" + orientation="0,1" + id="guide1099" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + <sodipodi:guide + position="188.97638,188.97638" + orientation="1,0" + id="guide1101" + inkscape:locked="false" + inkscape:label="" + inkscape:color="rgb(0,0,255)" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-167.28122,-322.85977)"> + <path + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" + d="m 762.61553,105.82518 v 80.22827 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-80.41973 z" + id="rect888-6-2-0-0" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccc" /> + <g + id="g1084" + transform="translate(139.39556,1.9249742)"> + <g + transform="matrix(1.0298281,0,0,1.0298281,-320.57448,-232.49785)" + id="g1010-6" /> + </g> + <g + id="g1077" + transform="translate(-135.36045,-275.77165)"> + <g + transform="matrix(1.0298281,0,0,1.0298281,-106.16686,-28.823189)" + id="g1066" /> + </g> + <g + id="g1077-9-8" + transform="translate(-235.90303,68.096044)"> + <g + transform="matrix(1.0298281,0,0,1.0298281,-106.16686,-28.823189)" + id="g1066-2-9" /> + </g> + <g + id="g1370" + transform="matrix(1.2737193,0,0,1.2737193,-97.514563,-83.896439)"> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3" + d="m 175.74036,319.44885 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-2" + d="m 296.44726,319.44885 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-9" + d="m 417.15416,319.44885 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.09517,-56.03559 l 0.527,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-3" + d="m 235.82505,415.6943 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-1" + d="m 356.53195,415.6943 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-94" + d="m 477.23885,415.6943 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09517,56.03559 33.1897,3.4e-4 60.0954,-25.0878 60.0952,-56.03559 l 0.527,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-7" + d="m 115.11815,415.6943 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-8" + d="m 175.20284,511.93975 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-4" + d="m 295.90974,511.93975 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-5" + d="m 416.35313,512.03548 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18966,3.4e-4 60.09539,-25.0878 60.09519,-56.03559 l 0.527,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-0" + d="m 114.58063,608.1852 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-36" + d="m 235.02402,608.28093 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90553,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09538,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-10" + d="m 355.73092,608.28093 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.09519,56.03559 33.18967,3.4e-4 60.09537,-25.0878 60.0952,-56.03559 l 0.52701,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + <path + sodipodi:nodetypes="cccccc" + inkscape:connector-curvature="0" + id="rect888-6-2-3-6" + d="m 476.43782,608.28093 v 40.0184 c -0.003,0.0639 -0.007,0.12764 -0.0105,0.19146 -1.8e-4,30.94779 26.90552,56.03593 60.0952,56.03559 33.1897,3.4e-4 60.0954,-25.0878 60.0952,-56.03559 l 0.527,-40.20986" + style="opacity:1;fill:none;fill-opacity:1;stroke:#c8c8c8;stroke-width:15.81019115;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> + </g> + <path + style="fill:none;stroke:#c8c8c8;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 126.32932,322.99173 H 587.5694" + id="path1391" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/tagit/dialogues/__init__.py b/tagit/dialogues/__init__.py index bee5bf4..d7792cb 100644 --- a/tagit/dialogues/__init__.py +++ b/tagit/dialogues/__init__.py @@ -18,11 +18,11 @@ import typing # inner-module imports ##from .spash import Splash -#from .autoinput import AutoTextInput +from .autoinput import AutoTextInput #from .console import Console #from .dir_creator import DirCreator #from .dir_picker import DirPicker -#from .error import Error +from .error import Error #from .export import Export #from .file_creator import FileCreator #from .file_picker import FilePicker @@ -32,8 +32,8 @@ import typing #from .path_picker import PathPicker #from .progress import Progress #from .project import Project -#from .simple_input import SimpleInput -#from .stoken import TokenEdit +from .simple_input import SimpleInput +from .stoken import TokenEdit #from .yesno import YesNo # exports @@ -41,7 +41,7 @@ __all__: typing.Sequence[str] = ( #'Console', #'DirCreator', #'DirPicker', - #'Error', + 'Error', #'Export', #'FileCreator', #'FilePicker', @@ -51,8 +51,8 @@ __all__: typing.Sequence[str] = ( #'PathPicker', #'Progress', #'Project', - #'SimpleInput', - #'TokenEdit', + 'SimpleInput', + 'TokenEdit', #'YesNo', ) diff --git a/tagit/dialogues/autoinput.py b/tagit/dialogues/autoinput.py new file mode 100644 index 0000000..a036ed4 --- /dev/null +++ b/tagit/dialogues/autoinput.py @@ -0,0 +1,73 @@ +"""This is a simple example of how to use suggestion text. + +In this example you setup a word_list at the begining. In this case +'the the quick brown fox jumps over the lazy old dog'. This list along +with any new word written word in the textinput is available as a +suggestion when you are typing. You can press tab to auto complete the text. + +Based on & thanks to akshayaurora: + https://gist.github.com/akshayaurora/fa5a68980af585e355668e5adce5f98b + +Part of the tagit module. +A copy of the license is provided with the project. +Modifications authored by: Matthias Baumgartner, 2022 +""" +# standard imports +from bisect import bisect + +# kivy imports +from kivy.uix.textinput import TextInput +import kivy.properties as kp + +# exports +__all__ = ('AutoTextInput', ) + + +## code ## + +class AutoTextInput(TextInput): + + sep = kp.StringProperty(',') + suffix = kp.StringProperty(' ') + vocabulary = kp.ListProperty() + + def on_suggestion_text(self, wx, value): + if not value: + return + + super(AutoTextInput, self).on_suggestion_text(wx, value) + + def keyboard_on_key_down(self, window, keycode, text, modifiers): + if self.suggestion_text and keycode[1] == 'tab': # complete suggestion_text + self.insert_text(self.suggestion_text + self.sep + self.suffix) + self.suggestion_text = '' + return True + return super(AutoTextInput, self).keyboard_on_key_down(window, keycode, text, modifiers) + + def on_text(self, wx, value): + # include all current text from textinput into the word list + # the kind of behavior sublime text has + + # what's on the current line + temp = value[:value.rfind(self.sep)].split(self.sep) + temp = [s.strip() for s in temp] + # combine with static vocabulary + wordlist = sorted(set(self.vocabulary + temp)) + + # get prefix + prefix = value[value.rfind(self.sep)+1:].strip() + if not prefix: + return + + # binary search on (sorted) wordlist + pos = bisect(wordlist, prefix) + + # check if matching string found + if pos == len(wordlist) or not wordlist[pos].startswith(prefix): + self.suggestion_text = '' + return + + # fetch suffix from wordlist + self.suggestion_text = wordlist[pos][len(prefix):] + +## EOF ## diff --git a/tagit/dialogues/dialogue.kv b/tagit/dialogues/dialogue.kv new file mode 100644 index 0000000..e23f0db --- /dev/null +++ b/tagit/dialogues/dialogue.kv @@ -0,0 +1,114 @@ +#:import get_root tagit.utils.get_root +# FIXME: remove need for get_root + +<-Dialogue>: + auto_dismiss: True + ok_on_enter: True + +<DialogueContentBase>: + + orientation: 'vertical' + padding: '12dp' + size_hint: 0.66, None + height: self.minimum_height + + canvas: + # mask main window + Color: + rgba: 0,0,0, 0.7 * self.parent._anim_alpha + Rectangle: + size: self.parent._window.size if self.parent._window else (0, 0) + + # solid background color + Color: + rgb: 1, 1, 1 + BorderImage: + source: self.parent.background + border: self.parent.border + pos: self.pos + size: self.size + + +<DialogueContentNoTitle@DialogueContentBase>: + # nothing to do + +<DialogueContentTitle@DialogueContentBase>: + title: '' + title_color: 1,1,1,1 + + Label: + text: root.title + size_hint_y: None + height: self.texture_size[1] + dp(16) + text_size: self.width - dp(16), None + font_size: '16sp' + color: root.title_color + bold: True + halign: 'center' + valing: 'middle' + + canvas.before: + # Background + Color: + rgb: 0.2, 0.2, 0.2 + Rectangle: + size: self.size + pos: self.pos + + # top border + #Color: + # rgb: 0.5, 0.5, 0.5 + #Line: + # points: self.x, self.y + self.height, self.x + self.width, self.y + self.height + # width: 2 + + # bottom border + #Color: + # rgb: 0.5, 0.5, 0.5 + #Line: + # points: self.x, self.y, self.x + self.width, self.y + # width: 2 + + # small space + Label: + size_hint_y: None + height: 12 + + +<DialogueButtons>: + orientation: 'vertical' + size_hint_y: None + height: dp(48+8) + + # small space + Label: + size_hint_y: None + height: dp(8) + + # here come the buttons + + +<DialogueButtons_One@DialogueButtons>: + ok_text: 'OK' + + Button: + text: root.ok_text + on_press: get_root(self).ok() + + +<DialogueButtons_Two@DialogueButtons>: + cancel_text: 'Cancel' + ok_text: 'OK' + ok_enabled: True + + BoxLayout: + orientation: 'horizontal' + Button: + text: root.cancel_text + on_press: get_root(self).cancel() + Button: + text: root.ok_text + on_press: get_root(self).ok() + disabled: not root.ok_enabled + +## EOF ## diff --git a/tagit/dialogues/dialogue.py b/tagit/dialogues/dialogue.py new file mode 100644 index 0000000..1aa0e9a --- /dev/null +++ b/tagit/dialogues/dialogue.py @@ -0,0 +1,108 @@ +"""Popup dialogue. + +Rougly based on code from https://gist.github.com/kived/742397a80d61e6be225a +by Ryan Pessa. The license is provided in the source folder. + +Part of the tagit module. +A copy of the license is provided with the project. +Modifications authored by: Matthias Baumgartner, 2022 +""" +# standard imports +import os + +# kivy imports +from kivy.lang import Builder +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.popup import Popup +import kivy.properties as kp + +# exports +__all__ = ('Dialogue', ) + + +## code ## + +# Load kv +Builder.load_file(os.path.join(os.path.dirname(__file__), 'dialogue.kv')) + +# classes +class Dialogue(Popup): + """Popup dialogue base class. + + Use like below: + + >>> dlg = Dialogue() + >>> dlg.bind(on_ok=....) + >>> dlg.open() + + """ + + ok_on_enter = kp.BooleanProperty() + + __events__ = ('on_ok', 'on_cancel') + + def __init__(self, *args, **kwargs): + super(Dialogue, self).__init__(*args, **kwargs) + from kivy.core.window import Window + # assumes that the first widget created controls the keyboard + #Window.children[-1].request_exclusive_keyboard() + # Alternatively, you can bind a function (self._on_keyboard) to on_keyboard + # which returns True. This stops the event from being processed by the main + # window. + # However, this still does not keep the 'enter' from 'on_text_validate' from + # being processed by the main window. + Window.bind(on_keyboard=self._on_keyboard) + # By binding to on_key_down, the <enter> key can trigger the ok action. + # This also prevents the enter event to be processed by the main window, + # unlike the 'on_text_validate' of TextInput. + Window.bind(on_key_down=self._key_down) + + def _on_keyboard(self, *args, **kwargs): + # block events from processing in the main window + return True + + def _key_down(self, instance, key, scancode, codepoint, modifiers): + if key == 13 and self.ok_on_enter: + self.ok() + return True + # must not stop other events such that ctrl up/down reach the browser + + def ok(self): + """User pressed the OK button.""" + self.dispatch('on_ok') + self.dismiss() + + def cancel(self): + """User pressed the Cancel button.""" + self.dispatch('on_cancel') + self.dismiss() + + def on_dismiss(self): + from kivy.core.window import Window + # assumes that the first widget created controls the keyboard + #Window.children[-1].release_exclusive_keyboard() + Window.unbind(on_keyboard=self._on_keyboard) + Window.unbind(on_key_down=self._key_down) + super(Dialogue, self).on_dismiss() + + def on_ok(self): + """Event prototype.""" + pass + + def on_cancel(self): + """Event prototype.""" + pass + +# helper classes + +# content bases +class DialogueContentBase(BoxLayout): pass +class DialogueContentTitle(DialogueContentBase): pass +class DialogueContentNoTitle(DialogueContentBase): pass + +# buttons +class DialogueButtons(BoxLayout): pass +class DialogueButtons_One(DialogueButtons): pass +class DialogueButtons_Two(DialogueButtons): pass + +## EOF ## diff --git a/tagit/dialogues/error.py b/tagit/dialogues/error.py new file mode 100644 index 0000000..d93f853 --- /dev/null +++ b/tagit/dialogues/error.py @@ -0,0 +1,45 @@ +"""Dialogue to show an error message. + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# kivy imports +from kivy.lang import Builder +import kivy.properties as kp + +# inner-module imports +from .dialogue import Dialogue + +# exports +__all__ = ('Error', ) + + +## code ## + +# load kv +Builder.load_string(''' +<Error>: + text: '' + ok_on_enter: False + + DialogueContentNoTitle: + + Label: + markup: True + text: root.text + size_hint_y: None + color: 1, 0, 0, 1 + height: self.texture_size[1] + dp(16) + text_size: self.width - dp(16), None + halign: 'center' + + DialogueButtons_One: +''') + +# classes +class Error(Dialogue): + """Error message.""" + text = kp.StringProperty('') + +## EOF ## diff --git a/tagit/dialogues/license.t b/tagit/dialogues/license.t new file mode 100644 index 0000000..bbd2830 --- /dev/null +++ b/tagit/dialogues/license.t @@ -0,0 +1,33 @@ + +The dialogues are based on the following code: + +https://gist.github.com/kived/742397a80d61e6be225a + +It ships with license, provided below: + +>>> The following license shall apply to all Public Gists owned by account. It +>>> shall never apply to any Secret Gists, for which no license of any sort is +>>> granted. +>>> +>>> Copyright (c) 2015- Ryan Pessa +>>> +>>> Permission is hereby granted, free of charge, to any person obtaining a copy +>>> of this software and associated documentation files (the "Software"), to deal +>>> in the Software without restriction, including without limitation the rights +>>> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +>>> copies of the Software, and to permit persons to whom the Software is +>>> furnished to do so, subject to the following conditions: +>>> +>>> The above copyright notice and this permission notice shall be included in +>>> all copies or substantial portions of the Software. +>>> +>>> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +>>> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +>>> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +>>> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +>>> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +>>> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +>>> THE SOFTWARE. + +Code modification are subject to the license of the tagit software. + diff --git a/tagit/dialogues/simple_input.kv b/tagit/dialogues/simple_input.kv new file mode 100644 index 0000000..b7deb9c --- /dev/null +++ b/tagit/dialogues/simple_input.kv @@ -0,0 +1,30 @@ + +#:import AutoTextInput tagit.dialogues + +<SimpleInput>: + text: '' + ok_on_enter: True + cancel_on_defocus: True + + DialogueContentNoTitle: + + #AutoTextInput: + TextInput: + vocabulary: root.suggestions + sep: root.suggestion_sep + suffix: root.suggestion_suffix + focus: True + text: root.text + size_hint_y: None + multiline: False + height: self.minimum_height + text_size: self.width - dp(16), None + halign: 'center' + + on_text: root.text = self.text + on_focus: root.on_text_focus(*args) + #on_text_validate: root.ok() # handled via the ok_on_enter mechanism + + DialogueButtons_Two: + +## EOF ## diff --git a/tagit/dialogues/simple_input.py b/tagit/dialogues/simple_input.py new file mode 100644 index 0000000..d7cc69f --- /dev/null +++ b/tagit/dialogues/simple_input.py @@ -0,0 +1,55 @@ +"""Dialogue with a single-line text input field. + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import os + +# kivy imports +from kivy.lang import Builder +import kivy.properties as kp + +# inner-module imports +from .dialogue import Dialogue + +# exports +__all__ = ('SimpleInput', ) + + +## code ## + +# load kv +Builder.load_file(os.path.join(os.path.dirname(__file__), 'simple_input.kv')) + +# classes +class SimpleInput(Dialogue): + """Dialogue with a single-line text input field. + + Pass the default text as **text**. + + >>> SimpleInput(text='Hello world').open() + + In case of touch events, they need to be inhibited to change the focus. + + >>> FocusBehavior.ignored_touch.append(touch) + + """ + + # Defocus problem: + # Buttons defocus when on_press, but on_release is ok. + # Touch events must be blocked via FocusBehavior + + text = kp.StringProperty('') + cancel_on_defocus = kp.BooleanProperty(True) + suggestions = kp.ListProperty() + suggestion_sep = kp.StringProperty(',') + suggestion_suffix = kp.StringProperty(' ') + + + def on_text_focus(self, instance, focus): + if not focus and self.cancel_on_defocus: + self.dismiss() + +## EOF ## diff --git a/tagit/dialogues/stoken.py b/tagit/dialogues/stoken.py new file mode 100644 index 0000000..6e5427a --- /dev/null +++ b/tagit/dialogues/stoken.py @@ -0,0 +1,40 @@ +"""Search token editor + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 + +""" +# kivy imports +from kivy.lang import Builder +import kivy.properties as kp + +# inner-module imports +from .simple_input import SimpleInput + +# exports +__all__ = ('TokenEdit', ) + + +## code ## + +# Load kv +Builder.load_string(''' +#:import AutoTextInput tagit.dialogues + +<TokenEdit>: + text: '' + ok_on_enter: True + cancel_on_defocus: True +''') + +# classes +class TokenEdit(SimpleInput): + """Search token editor + """ + # TODO: Currently this is no different than SimpleInput. + # It should be extend to specify the type and getting help + # with editing ranges and alternative selection. + pass + +## EOF ## diff --git a/tagit/parsing/__init__.py b/tagit/parsing/__init__.py index 1c431a4..0070bf9 100644 --- a/tagit/parsing/__init__.py +++ b/tagit/parsing/__init__.py @@ -6,14 +6,14 @@ Author: Matthias Baumgartner, 2022 """ # inner-module imports from .datefmt import parse_datetime -from .search import ast_from_string -from .sort import sort_from_string +from .filter import Filter +from .sort import Sort # exports __all__ = ( - 'ast_from_string', + 'Filter', + 'Sort', 'parse_datetime', - 'sort_from_string', ) ## EOF ## diff --git a/tagit/parsing/search.py b/tagit/parsing/filter.py index 10d0e7c..ea8df51 100644 --- a/tagit/parsing/search.py +++ b/tagit/parsing/filter.py @@ -1,7 +1,7 @@ """User-specified search query parsing. >>> q = "has mime / tag in (november, october) / ! Apfel / time < 10.10.2004 / iso in (100, 200)" ->>> ast = ast_from_string(q) +>>> ast = filter_from_string(q) Part of the tagit module. A copy of the license is provided with the project. @@ -14,37 +14,29 @@ from datetime import datetime from pyparsing import CaselessKeyword, Combine, Group, Optional, Or, Word, delimitedList, nums, oneOf, ParseException, Literal, QuotedString, alphanums, alphas8bit, punc8bit # tagit imports -from tagit.utils import errors, ttime +from tagit.utils import bsfs, errors, ns, ttime +from tagit.utils.bsfs import ast # inner-module imports -from . import datefmt - -# exports -__all__ = ( - 'ast_from_string', - ) +from .datefmt import parse_datetime # constants SEARCH_DELIM = '/' VALUE_DELIM = ',' -DEFAULT_PREDICATE = 'tag' +# exports +__all__ = ( + 'Filter', + ) -## code ## -class SearchParser(): +## code ## - # valid predicates per type - _PREDICATES_CATEGORICAL = None - _PREDICATES_CONTINUOUS = None - _PREDICATES_DATETIME = None +class Filter(): # parsers - _CATEGORICAL = None - _CONTINUOUS = None - _EXISTENCE = None + _DATETIME_PREDICATES = None _QUERY = None - _TAG = None def __init__(self, schema: bsfs.schema.Schema): self.schema = schema @@ -61,9 +53,6 @@ class SearchParser(): def build_parser(self): """ """ - # The *predicate* argument is for compatibility with predicate listener. - # It's not actually used here. - # valid predicates per type, as supplied by tagit.library # FIXME: # * range / type constraints @@ -79,9 +68,21 @@ class SearchParser(): > Target: Entity (allow others?) -> rfds:domain > Require: searchable as specified in backend AND user-searchable as specified in frontend """ - self._PREDICATES_CATEGORICAL = self.schema.predicates(searchable=True, range=self.schema.tm.categorical) # FIXME! - self._PREDICATES_CONTINUOUS = self.schema.predicates(searchable=True, range=self.schema.tm.numerical) # FIXME! - self._PREDICATES_DATETIME = self.schema.predicates(searchable=True, range=self.schema.tm.datetime) # FIXME! + # all relevant predicates + predicates = {pred for pred in self.schema.predicates() if pred.domain <= self.schema.node(ns.bsfs.Entity)} + # filter through accept/reject lists + ... # FIXME + # shortcuts + self._abb2uri = {pred.uri.fragment: pred.uri for pred in predicates} # FIXME: tie-breaking for duplicates + self._uri2abb = {uri: fragment for fragment, uri in self._abb2uri.items()} + # all predicates + _PREDICATES = {self._uri2abb[pred.uri] for pred in predicates} + # numeric predicates + _PREDICATES_NUMERIC = {self._uri2abb[pred.uri] for pred in predicates if isinstance(pred.range, bsfs.schema.Literal) and pred.range <= self.schema.literal(ns.bsfs.Number)} # FIXME: type check might become unnecessary + # datetime predicates + self._DATETIME_PREDICATES = {pred.uri for pred in predicates if isinstance(pred.range, bsfs.schema.Literal) and pred.range <= self.schema.literal(ns.bsfs.Time)} # FIXME: type check might become unnecessary + _PREDICATES_DATETIME = {self._uri2abb[pred] for pred in self._DATETIME_PREDICATES} + # terminal symbols number = Group(Optional(oneOf('- +')) \ @@ -93,11 +94,11 @@ class SearchParser(): # FIXME: Non-ascii characters # predicates - predicate = Or([CaselessKeyword(p) for p in self._PREDICATES_CATEGORICAL]).setResultsName( + predicate = Or([CaselessKeyword(p) for p in _PREDICATES]).setResultsName( 'predicate') - date_predicate = Or([CaselessKeyword(p) for p in self._PREDICATES_DATETIME]).setResultsName( + date_predicate = Or([CaselessKeyword(p) for p in _PREDICATES_DATETIME]).setResultsName( 'predicate') - num_predicate = Or([CaselessKeyword(p) for p in self._PREDICATES_CONTINUOUS]).setResultsName( + num_predicate = Or([CaselessKeyword(p) for p in _PREDICATES_NUMERIC]).setResultsName( 'predicate') # existence @@ -106,7 +107,7 @@ class SearchParser(): PREDICATE := [predicate] """ op = (CaselessKeyword('has') ^ CaselessKeyword('has no') ^ CaselessKeyword('has not')).setResultsName('op') - self._EXISTENCE = Group(op + predicate).setResultsName('existence') + _EXISTENCE = Group(op + predicate).setResultsName('existence') # continuous @@ -127,7 +128,7 @@ class SearchParser(): bclose = oneOf(') ] [').setResultsName('bclose') bopen = oneOf('( [ ]').setResultsName('bopen') op = Or([':', '=', 'in']).setResultsName('op') - datefmt = datefmt.parse_datetime.DATETIME + datefmt = parse_datetime.DATETIME rngn = num_predicate + op + bopen + number('lo') + rsepn + number('hi') + bclose ^ \ num_predicate + op + bopen + rsepn + number('hi') + bclose ^ \ num_predicate + op + bopen + number('lo') + rsepn + bclose @@ -143,7 +144,7 @@ class SearchParser(): datefmt('vleft') + cmp('cleft') + date_predicate ^ \ datefmt('vleft') + cmp('cleft') + date_predicate + cmp('cright') + datefmt('vright') # combined - self._CONTINUOUS = Group( + _CONTINUOUS = Group( Group(eqn).setResultsName('eq') ^ Group(eqd).setResultsName('eq') ^ Group(rngn).setResultsName('range') ^ \ @@ -161,7 +162,7 @@ class SearchParser(): """ op = (CaselessKeyword('in') ^ CaselessKeyword('not in') ^ ':' ^ '=' ^ '!=' ^ '~' ^ '!~').setResultsName('op') value = delimitedList(words, delim=VALUE_DELIM).setResultsName('value') - self._CATEGORICAL = Group(predicate + op + ('(' + value + ')' | value) ).setResultsName('categorical') + _CATEGORICAL = Group(predicate + op + ('(' + value + ')' | value) ).setResultsName('categorical') # tag shortcuts @@ -173,35 +174,17 @@ class SearchParser(): """ op = oneOf('! ~ !~').setResultsName('op') value = delimitedList(words, delim=VALUE_DELIM).setResultsName('value') - self._TAG = Group(Optional(op) + '(' + value + ')' ^ Optional(op) + value).setResultsName('tag') + _TAG = Group(Optional(op) + '(' + value + ')' ^ Optional(op) + value).setResultsName('tag') # overall query """ QUERY := QUERY / QUERY | EXPR """ - self._QUERY = delimitedList(self._EXISTENCE | self._CONTINUOUS | self._CATEGORICAL | self._TAG, delim=SEARCH_DELIM) + self._QUERY = delimitedList(_EXISTENCE | _CONTINUOUS | _CATEGORICAL | _TAG, delim=SEARCH_DELIM) return self - def __del__(self): - if self._QUERY is not None: # remove listener - try: - self.predicates.ignore(self.build_parser) - except ImportError: - # The import fails if python is shutting down. - # In that case, the ignore becomes unnecessary anyway. - pass - def __call__(self, search): - # FIXME: mb/port/parsing - #if self._QUERY is None: - # # parsers were not initialized yet - # self.build_parser() - # # attach listener to receive future updates - # self.predicates.listen(self.build_parser) - # # FIXME: Additional filters would be handy - # #self.predicates.listen(self.build_parser, self.predicates.scope.library) - try: parsed = self._QUERY.parseString(search, parseAll=True) except ParseException as e: @@ -211,61 +194,58 @@ class SearchParser(): tokens = [] for exp in parsed: if exp.getName() == 'existence': + pred = self._abb2uri[exp.predicate.lower()] if 'op' not in exp: # prevented by grammar raise errors.ParserError('Missing operator', exp) elif exp.op == 'has': - cond = ast.Existence() + tok = ast.filter.Has(pred) elif exp.op in ('has no', 'has not'): - cond = ast.Inexistence() + tok = ast.filter.Not(ast.filter.Has(pred)) else: # prevented by grammar raise errors.ParserError('Invalid operator ({})'.format(exp.op), exp) - - tokens.append( - ast.Token(exp.predicate.lower(), cond)) + tokens.append(tok) elif exp.getName() == 'categorical': + pred = self._abb2uri[exp.predicate.lower()] + approx = False values = [s.strip() for s in exp.value] if 'op' not in exp: # prevented by grammar raise errors.ParserError('Missing operator', exp) - elif exp.op in (':', '=', 'in'): - cond = ast.SetInclude(values) - elif exp.op in ('!=', 'not in'): - cond = ast.SetExclude(values) - elif exp.op == '~': - cond = ast.SetInclude(values, approximate=True) - elif exp.op == '!~': - cond = ast.SetExclude(values, approximate=True) + if exp.op in ('~' '!~'): + approx = True + if exp.op in (':', '=', '~', 'in'): + tok = ast.filter.Any(pred, ast.filter.Includes(*values, approx=approx)) + elif exp.op in ('!=', '!~', 'not in'): + tok = ast.filter.All(pred, ast.filter.Excludes(*values, approx=approx)) else: # prevented by grammar raise errors.ParserError('Invalid operator ({})'.format(exp.op), exp) - - tokens.append( - ast.Token(exp.predicate.lower(), cond)) + tokens.append(tok) elif exp.getName() == 'tag': values = [s.strip() for s in exp.value] if 'op' not in exp: - cond = ast.SetInclude(values) + outer = ast.filter.Any + cond = ast.filter.Includes(*values) elif exp.op == '~': - cond = ast.SetInclude(values, approximate=True) + outer = ast.filter.Any + cond = ast.filter.Includes(*values, approx=True) elif exp.op == '!': - cond = ast.SetExclude(values) + outer = ast.filter.All + cond = ast.filter.Excludes(*values) elif exp.op == '!~': - cond = ast.SetExclude(values, approximate=True) + outer = ast.filter.All + cond = ast.filter.Excludes(*values, approx=True) else: # prevented by grammar raise errors.ParserError('Invalid operator ({})'.format(exp.op), exp) + tokens.append(outer(ns.bse.tag, ast.filter.Any(ns.bst.label, cond))) - tokens.append( - ast.Token(DEFAULT_PREDICATE, cond)) - - elif exp.getName() == 'continuous': - + elif exp.getName() == 'continuous': # FIXME: simplify and adapt bsfs.query.ast.filter.Between accordingly! lo, hi = None, None lo_inc, hi_inc = False, False predicate = None - if 'eq' in exp: # equation style - predicate = exp.eq.predicate.lower() + predicate = self._abb2uri[exp.eq.predicate.lower()] if ('>' in exp.eq.cleft and '<' in exp.eq.cright) or \ ('<' in exp.eq.cleft and '>' in exp.eq.cright) or \ @@ -294,7 +274,7 @@ class SearchParser(): lo_inc = hi_inc = True elif 'range' in exp: # value in [lo:hi] - predicate = exp.range.predicate.lower() + predicate = self._abb2uri[exp.range.predicate.lower()] if 'lo' in exp.range: lo = exp.range.lo @@ -307,7 +287,7 @@ class SearchParser(): raise errors.ParserError('Expression is neither a range nor an equation', exp) # interpret values - if predicate in set([p.lower() for p in self._PREDICATES_DATETIME]): + if predicate in self._DATETIME_PREDICATES: # turn into datetime lo, lfmt = datefmt.guess_datetime(lo) if lo is not None else (None, None) @@ -357,7 +337,8 @@ class SearchParser(): raise errors.ParserError('Lower bound must not exceed upper bound', (lo, hi)) tokens.append( - ast.Token(predicate, ast.TimeRange(lo, hi, lo_inc, hi_inc))) + ast.filter.Any(predicate, + ast.filter.Between(lo, hi, not lo_inc, not hi_inc))) else: # date specification # Check consistency @@ -368,7 +349,8 @@ class SearchParser(): raise errors.ParserError('Lower bound must not exceed upper bound', (lo, hi)) tokens.append( - ast.Token(predicate, ast.Datetime(lo, hi, lo_inc, hi_inc))) + ast.filter.Any(predicate, + ast.filter.Between(lo, hi, not lo_inc, not hi_inc))) else: # number predicate @@ -379,27 +361,14 @@ class SearchParser(): if not (lo < hi or (lo == hi and lo_inc and hi_inc)): raise errors.ParserError('Lower bound must not exceed upper bound', (lo, hi)) + # FIXME: mb/port: Three times the same code... optimize tokens.append( - ast.Token(predicate, ast.Continuous(lo, hi, lo_inc, hi_inc))) + ast.filter.Any(predicate, + ast.filter.Between(lo, hi, not lo_inc, not hi_inc))) else: # prevented by grammar raise errors.ParserError('Invalid expression', exp) - return ast.AND(tokens) - - - -"""Default SearchParser instance. - -To produce an ast, call - ->>> ast_from_string(search) - -Convenience shortcut for - ->>> SearchParser().parse(search) - -""" -ast_from_string = SearchParser(predicates) + return ast.filter.And(tokens) ## EOF ## diff --git a/tagit/parsing/sort.py b/tagit/parsing/sort.py index 8950613..75fa36c 100644 --- a/tagit/parsing/sort.py +++ b/tagit/parsing/sort.py @@ -12,13 +12,13 @@ from tagit.utils import errors, Struct # exports __all__ = ( - 'sort_from_string', + 'Sort', ) ## code ## -class SortParser(): +class Sort(): """Sort parser. A sort string can be as simple as a predicate, but also allows @@ -176,17 +176,4 @@ class SortParser(): else: return ast.Order(*tokens) -"""Default SortParser instance. - -To produce an ast, call - ->>> sort_from_string(sort) - -Convenience shortcut for - ->>> SortParser().parse(sort) - -""" -sort_from_string = SortParser(sortkeys) - ## EOF ## diff --git a/tagit/utils/__init__.py b/tagit/utils/__init__.py index 3f09078..16dcd4d 100644 --- a/tagit/utils/__init__.py +++ b/tagit/utils/__init__.py @@ -9,6 +9,7 @@ import typing # inner-module imports from . import bsfs +from . import namespaces as ns from . import time as ttime from .frame import Frame from .shared import * # FIXME: port properly diff --git a/tagit/utils/bsfs.py b/tagit/utils/bsfs.py index 0ab90a9..d80efe0 100644 --- a/tagit/utils/bsfs.py +++ b/tagit/utils/bsfs.py @@ -8,8 +8,16 @@ Author: Matthias Baumgartner, 2022 import typing # bsfs imports +from bsfs import schema, Open +from bsfs.query import ast +from bsfs.namespace import Namespace # exports -__all__: typing.Sequence[str] = [] +__all__: typing.Sequence[str] = ( + 'Namespace', + 'Open', + 'ast', + 'schema', + ) ## EOF ## diff --git a/tagit/utils/namespaces.py b/tagit/utils/namespaces.py new file mode 100644 index 0000000..dd26eef --- /dev/null +++ b/tagit/utils/namespaces.py @@ -0,0 +1,30 @@ +"""Default namespaces used throughout tagit. + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import typing + +# inner-module imports +from . import bsfs as _bsfs + +# constants +bse = _bsfs.Namespace('http://bsfs.ai/schema/Entity') +bsfs = _bsfs.Namespace('http://bsfs.ai/schema', fsep='/') +bsm = _bsfs.Namespace('http://bsfs.ai/schema/Meta') +bst = _bsfs.Namespace('http://bsfs.ai/schema/Tag') +xsd = _bsfs.Namespace('http://www.w3.org/2001/XMLSchema') + +# export +__all__: typing.Sequence[str] = ( + 'bse', + 'bsfs', + 'bsm', + 'xsd', + ) + +## EOF ## + + diff --git a/tagit/utils/shared.py b/tagit/utils/shared.py index 0d496ed..b5ab421 100644 --- a/tagit/utils/shared.py +++ b/tagit/utils/shared.py @@ -28,6 +28,7 @@ __all__: typing.Sequence[str] = ( 'is_list', 'magnitude_fmt', 'truncate_dir', + 'get_root', ) @@ -140,4 +141,15 @@ def fileopen(pth): except KeyError: warnings.warn('Unknown platform {}'.format(platform.system())) + +def get_root(obj): + """Traverse the widget tree upwards until the root is found.""" + while obj.parent is not None and obj.parent != obj.parent.parent: + if hasattr(obj, 'root') and obj.root is not None: + return obj.root + + obj = obj.parent + + return obj + ## EOF ## diff --git a/tagit/widgets/filter.py b/tagit/widgets/filter.py index 0152737..332ad34 100644 --- a/tagit/widgets/filter.py +++ b/tagit/widgets/filter.py @@ -23,7 +23,7 @@ import kivy.properties as kp # tagit imports from tagit import config #from tagit.parsing.search import ast, ast_to_string # FIXME: mb/port -from tagit.utils import errors +from tagit.utils import bsfs, errors # inner-module imports from .session import ConfigAwareMixin @@ -108,6 +108,10 @@ class Filter(BoxLayout, ConfigAwareMixin): ## exposed methods def get_query(self): + query = bsfs.ast.filter.And(self.t_head[:]) if len(self.t_head) > 0 else None + sort = None + return query, sort + # FIXME: mb/port.parsing query = ast.AND(self.t_head[:]) if len(self.t_head) else None # sort order is always set to False so that changing the sort order # won't trigger a new query which can be very expensive. The sort @@ -116,6 +120,8 @@ class Filter(BoxLayout, ConfigAwareMixin): return query, sort def abbreviate(self, token): + return 'T' + # FIXME: mb/port/parsing if token.predicate() == 'tag': return ','.join(list(token.condition())) elif token.predicate() == 'entity': @@ -162,8 +168,9 @@ class Filter(BoxLayout, ConfigAwareMixin): if self.changed: self.redraw() # issue search - if self.run_search: - self.root.trigger('Search') + # FIXME: mb/port/parsing + #if self.run_search: + # self.root.trigger('Search') def redraw(self): self.tokens.clear_widgets() diff --git a/tagit/widgets/session.py b/tagit/widgets/session.py index a7c7355..ca8c595 100644 --- a/tagit/widgets/session.py +++ b/tagit/widgets/session.py @@ -14,6 +14,7 @@ from kivy.uix.widget import Widget import kivy.properties as kp # tagit imports +from tagit import parsing from tagit.config.loader import load_settings #from tagit.storage.broker import Broker # FIXME: mb/port #from tagit.storage.loader import load_broker, load_log # FIXME: mb/port @@ -38,6 +39,9 @@ class Session(Widget): self.cfg = cfg self.storage = storage self.log = log + # derived members + self.filter_from_string = parsing.Filter(self.storage.schema) + #self.sort_from_string = parsing.Sort(self.storage.schema) # FIXME: mb/port/parsing def __enter__(self): return self |