diff options
author | Matthias Baumgartner <dev@igsor.net> | 2023-03-05 19:17:00 +0100 |
---|---|---|
committer | Matthias Baumgartner <dev@igsor.net> | 2023-03-05 19:17:00 +0100 |
commit | 5a325565f917c8b1d233d8e6373756c253400909 (patch) | |
tree | e6e0b475c7ab5c6a7ff4f0ea7ad1b08cecf05e68 /tagit/tiles | |
parent | e1e77797454ac747b293f589d8f2e0243173a419 (diff) | |
parent | 98e567933723c59d1d97b3a85e649cfdce514676 (diff) | |
download | tagit-0.23.03.tar.gz tagit-0.23.03.tar.bz2 tagit-0.23.03.zip |
Merge branch 'develop'v0.23.03
Diffstat (limited to 'tagit/tiles')
-rw-r--r-- | tagit/tiles/__init__.py | 57 | ||||
-rw-r--r-- | tagit/tiles/browser_tags.py | 108 | ||||
-rw-r--r-- | tagit/tiles/buttons.py | 50 | ||||
-rw-r--r-- | tagit/tiles/cursor_tags.py | 60 | ||||
-rw-r--r-- | tagit/tiles/decoration.kv | 87 | ||||
-rw-r--r-- | tagit/tiles/decoration.py | 77 | ||||
-rw-r--r-- | tagit/tiles/geo.py | 140 | ||||
-rw-r--r-- | tagit/tiles/info.py | 85 | ||||
-rw-r--r-- | tagit/tiles/selection_tags.py | 60 | ||||
-rw-r--r-- | tagit/tiles/tile.kv | 45 | ||||
-rw-r--r-- | tagit/tiles/tile.py | 85 |
11 files changed, 854 insertions, 0 deletions
diff --git a/tagit/tiles/__init__.py b/tagit/tiles/__init__.py new file mode 100644 index 0000000..f51ee2a --- /dev/null +++ b/tagit/tiles/__init__.py @@ -0,0 +1,57 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import typing + +# tagit imports +from tagit.utils.builder import BuilderBase + +# inner-module imports +from .browser_tags import BrowserTags +from .buttons import Buttons +from .cursor_tags import CursorTags +#from .entity_histogram import EntityHistogram +from .geo import Map +from .info import Info +#from .libsummary import LibSummary +#from .searchtree import Searchtree +from .selection_tags import SelectionTags +#from .suggested_tags import SuggestedTags +#from .tag_distribution import TagDistribution +#from .tag_histogram import TagHistogram +#from .tag_tree import TagTree +#from .tagcloud import Tagcloud +#from .venn import Venn + +# exports +__all__: typing.Sequence[str] = ( + 'TileBuilder', + ) + + +## code ## + +class TileBuilder(BuilderBase): + _factories = { + 'BrowserTags': BrowserTags, + 'Buttons': Buttons, + 'CursorTags': CursorTags, +# 'EntityHistogram': EntityHistogram, + 'Geo': Map, + 'Info': Info, +# 'LibSummary': LibSummary, +# 'Searchtree': Searchtree, + 'SelectionTags': SelectionTags, +# 'SuggestedTags': SuggestedTags, +# 'TagDistribution': TagDistribution, +# 'TagHistogram': TagHistogram, +# 'TagTree': TagTree, +# 'Tagcloud': Tagcloud, +# 'Venn': Venn, + } + +## EOF ## diff --git a/tagit/tiles/browser_tags.py b/tagit/tiles/browser_tags.py new file mode 100644 index 0000000..3a9c25f --- /dev/null +++ b/tagit/tiles/browser_tags.py @@ -0,0 +1,108 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +from functools import reduce +import operator + +# kivy imports +from kivy.lang import Builder + +# tagit imports +from tagit.widgets.browser import BrowserAwareMixin + +# inner-module imports +from .tile import TileWithLabel + +# exports +__all__ = ('BrowserTags', ) + + +## code ## + +# load kv +Builder.load_string(''' +<BrowserTags>: + title: 'Tags' + tooltip: 'Tags of displayed items.' +''') + +# classes +class BrowserTags(TileWithLabel, BrowserAwareMixin): + """Show tags of displayed items. Tags of selected items are highlighted.""" + + displayed = None + + def on_browser(self, sender, browser): + # remove old binding + if self.browser is not None: + self.browser.unbind(cursor=self.update) + self.browser.unbind(selection=self.update) + self.browser.unbind(items=self.on_items) + # add new binding + self.browser = browser + if self.browser is not None: + self.browser.bind(cursor=self.update) + self.browser.bind(selection=self.update) + self.browser.bind(items=self.on_items) + # populate displayed first, then update + self.on_items(browser, browser.items) + self.update() + + def __del__(self): + if self.browser is not None: + self.browser.unbind(cursor=self.update) + self.browser.unbind(selection=self.update) + self.browser.unbind(items=self.on_items) + self.browser = None + + def on_items(self, browser, items): + # unfold + items = browser.unfold(items) + # get tags + self.displayed = items.tag.label(node=False) + # update view + self.update() + + def update(self, *args): + if not self.visible: + self.text = '' + + elif self.displayed is None: + self.on_items(self.root.browser, self.root.browser.items) + # calls update again with not-None self.displayed + + else: + browser = self.root.browser + + # handle cursor + if browser.cursor is None: + cursor = set() + else: + cursor = browser.cursor.tag.label() + + # handle selection + if len(browser.selection) == 0: + selected = set() + else: + selection = reduce(operator.add, browser.selection) + selected = selection.tag.label(node=False) + + # assemble tag list + tags = [] + for tag in sorted(self.displayed | selected | cursor): + pretty = tag + if tag in cursor: + pretty = f'[b]{pretty}[/b]' # bold + if tag in selected: + pretty = f'[color=#415bCD]{pretty}[/color]' # blue color + + tags.append(pretty) + + # Apply prefix and display + self.text = ', '.join(tags) + +## EOF ## diff --git a/tagit/tiles/buttons.py b/tagit/tiles/buttons.py new file mode 100644 index 0000000..2a13911 --- /dev/null +++ b/tagit/tiles/buttons.py @@ -0,0 +1,50 @@ +""" + +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 .tile import Tile + +# exports +__all__ = ('Buttons', ) + +## code ## + +# load kv +# NOTE: ButtonDock doesn't need to be imported... why?! +Builder.load_string(''' +<Buttons>: + title: 'Actions' + tooltip: 'Some buttons' + + # content + slim: False + btns: btns + ButtonDock: + root: root.root + orientation: 'lr-tb' + id: btns + # space between childs + spacing: (5, 0) if root.slim else (30, 5) + # space between ButtonDock and its children + padding: (0, 0) if root.slim else (10, 5) +''') + +# classes +class Buttons(Tile): + """A container for buttons to trigger some action.""" + buttons = kp.ListProperty() + + def update(self): + if self.visible and len(self.btns.children) == 0: + self.btns.clear_widgets() + self.btns.populate(self.buttons) + +## EOF ## diff --git a/tagit/tiles/cursor_tags.py b/tagit/tiles/cursor_tags.py new file mode 100644 index 0000000..2ab2f30 --- /dev/null +++ b/tagit/tiles/cursor_tags.py @@ -0,0 +1,60 @@ +""" + +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 + +# tagit imports +from tagit.widgets.browser import BrowserAwareMixin + +# inner-module imports +from .tile import TileWithLabel + +# exports +__all__ = ('CursorTags', ) + + +## code ## + +# load kv +Builder.load_string(''' +<CursorTags>: + title: "Cursor's tags" + tooltip: 'Tags at the cursor' +''') + + +# classes +class CursorTags(TileWithLabel, BrowserAwareMixin): + """Show tags of cursor item.""" + + def on_browser(self, sender, browser): + # remove old binding + if self.browser is not None: + self.browser.unbind(cursor=self.update) + # add new binding + self.browser = browser + if self.browser is not None: + self.browser.bind(cursor=self.update) + self.update() + + def __del__(self): + if self.browser is not None: + self.browser.unbind(cursor=self.update) + self.browser = None + + def update(self, *args): + cursor = self.root.browser.cursor + if not self.visible or cursor is None: + # no cursor, nothing to do + self.text = '' + else: + tags = cursor.tag.label() + tags = {tag.title() for tag in tags} # nice display + tags = sorted(tags) + self.text = ', '.join(tags) + +## EOF ## diff --git a/tagit/tiles/decoration.kv b/tagit/tiles/decoration.kv new file mode 100644 index 0000000..ae7e49d --- /dev/null +++ b/tagit/tiles/decoration.kv @@ -0,0 +1,87 @@ + +# NOTE: +# TileDecoration assumes as *cbox* property that identifies the widget +# to which the main content will be added. + +<TileDecorationVanilla>: + cbox: cbox + + RelativeLayout: + id: cbox + +<TileDecorationBorder>: + cbox: cbox + + canvas.after: + # tile shadow + Color: + rgb: 0.2,0.2,0.2 + Line: + rectangle: self.x+5,self.y+5,self.width-10,self.height-10 + width: 2 + + Color: + rgb: 0.6,0.6,0.6 + Line: + rectangle: self.x+7,self.y+7,self.width-14,self.height-14 + width: 1 + + RelativeLayout: + id: cbox + pos: 15, 15 + size: root.width-30, root.height-30 + size_hint: None, None + +<TileDecorationFilledRectangle>: + cbox: cbox + + Label: + text: root.client.title + size: root.width, 20 + size_hint: None, None + pos: 0, root.height - self.height - 5 + + RelativeLayout: + id: cbox + pos: 5, 5 + size: root.width-10, root.height-30 + size_hint: None, None + + canvas.before: + Color: + rgba: 1,0,0,0.5 + Rectangle: + pos: 0, 0 + size: self.size + + +<TileDecorationRoundedBorder>: + cbox: cbox + + Label: + text: root.client.title + size: root.width, 20 + size_hint: None, None + pos: 0, root.height - self.height - 3 + + RelativeLayout: + id: cbox + pos: 5, 3 + size: root.width-10, root.height-30 + size_hint: None, None + + canvas.before: + Color: + rgb: 0xc5/256, 0xc9/256, 0xc7/256 + RoundedRectangle: + pos: self.pos + #radius: [10, 10, 10, 10] + size: self.size + Color: + rgb: 0x1c/256, 0x1b/256, 0x22/256 # FIXME: re-use from MainWindow? + RoundedRectangle: + pos: self.pos[0] + 2, self.pos[1] + 2 + radius: [5, 5, 5, 5] + size: self.size[0] - 4, self.size[1] - 4 + +## EOF ## diff --git a/tagit/tiles/decoration.py b/tagit/tiles/decoration.py new file mode 100644 index 0000000..c772f64 --- /dev/null +++ b/tagit/tiles/decoration.py @@ -0,0 +1,77 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import os +import typing + +# kivy imports +from kivy.lang import Builder +from kivy.uix.relativelayout import RelativeLayout +import kivy.properties as kp + +# exports +__all__: typing.Sequence[str] = ( + 'TileDecorationBorder', + 'TileDecorationRoundedBorder', + 'TileDecorationFilledRectangle', + 'TileDecorationVanilla', + ) + + +## code ## + +# load kv +Builder.load_file(os.path.join(os.path.dirname(__file__), 'decoration.kv')) + +# classes +class TileDecoration(RelativeLayout): + + cbox = kp.ObjectProperty(None) + client = kp.ObjectProperty(None) + + def __repr__(self): + return f'{self.__class__.__name__}({self.client})' + + def on_cbox(self, wx, cbox): + if cbox is not None and len(cbox.children) == 0: + cbox.add_widget(self.client) + + @property + def default_size(self): + return self.client.default_size + + +class TileDecorationVanilla(TileDecoration): + pass + + +class TileDecorationFilledRectangle(TileDecoration): + @property + def default_size(self): + width, height = self.client.default_size + width = None if width is None else width + 10 + height = None if height is None else height + 30 + return width, height + + +class TileDecorationBorder(TileDecoration): + @property + def default_size(self): + width, height = self.client.default_size + width = None if width is None else width + 30 + height = None if height is None else height + 30 + return width, height + +class TileDecorationRoundedBorder(TileDecoration): + @property + def default_size(self): + width, height = self.client.default_size + width = None if width is None else width + 30 + height = None if height is None else height + 30 + return width, height + +## EOF ## diff --git a/tagit/tiles/geo.py b/tagit/tiles/geo.py new file mode 100644 index 0000000..796a1c2 --- /dev/null +++ b/tagit/tiles/geo.py @@ -0,0 +1,140 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import os + +# kivy ipmorts +from kivy.lang import Builder +from kivy.uix.label import Label +import kivy.properties as kp + +# tagit imports +# NOTE: the following line segfaults: +# mapview.source.py:128:self.dp_tile_size = min(dp(self.tile_size), self.tile_size * 2) +# setting it to a static value (e.g. 256) works. +from tagit.external.kivy_garden.mapview import MapView, MapMarkerPopup +from tagit.utils import ns +from tagit.widgets.browser import BrowserAwareMixin + +# inner-module imports +from .tile import Tile + +# exports +__all__ = ('Map', ) + + +## code ## + +Builder.load_string(''' +<Map>: + # meta + title: "Map" + tooltip: 'Location of an item' + # content + map_: map_ + MapView: + id: map_ + zoom: 9 + +<MapLabel>: + size: self.texture_size + size_hint: None, None + padding: 5, 5 + font_size: '15sp' + + canvas.before: + # background + Color: + rgba: 0,0,0,0.6 + RoundedRectangle: + size: self.size + pos: self.pos + radius: [10] # keep in sync with the line's radius + # border + Color: + rgba: 0,0,0,1 + Line: + rounded_rectangle: self.x, self.y, self.width, self.height, 10 +''') + +class MapLabel(Label): + pass + +class Map(Tile, BrowserAwareMixin): + """Draw a map which indicates visible items' locations.""" + + # list of map markers + markers = kp.ListProperty() + + def on_browser(self, sender, browser): + """Bind to browser properties.""" + # remove old binding + if self.browser is not None: + self.browser.unbind(cursor=self.update) + self.browser.unbind(items=self.update_markers) + # add new binding + self.browser = browser + if self.browser is not None: + self.browser.bind(cursor=self.update) + self.browser.bind(items=self.update_markers) + # initial calls + self.update_markers() + self.update() + + def __del__(self): + if self.browser is not None: + self.browser.unbind(cursor=self.update) + self.browser.unbind(items=self.update_markers) + self.browser = None + + def update_markers(self, *args): + """Draw markers for all browser items.""" + # remove old markers + for mark in self.markers: + self.map_.remove_marker(mark) + self.markers.clear() + + # get view data + data = self.root.browser.unfold(self.root.browser.items).get( + ns.bse.filename, + ns.bse.latitude, + ns.bse.longitude, + node=True, + ) + + # draw new markers + for ent, vdict in data.items(): + if ns.bse.latitude not in vdict: + continue + if ns.bse.longitude not in vdict: + continue + # TODO: cluster points, one marker for multiple items + lat = vdict[ns.bse.latitude] + lon = vdict[ns.bse.longitude] + # create popup marker + mark = MapMarkerPopup(lat=lat, lon=lon) + text = vdict.get(ns.bse.filename, + ', '.join(os.path.basename(guid) for guid in ent.guids)) + mark.add_widget(MapLabel(text=text)) + # add marker + self.markers.append(mark) + self.map_.add_marker(mark) + + def update(self, *args): + """Focus the map on the cursor.""" + if not self.visible: + return + + cursor = self.root.browser.cursor + if cursor is not None: + coords = cursor.get(ns.bse.latitude, ns.bse.longitude) + if set(coords.keys()) == {ns.bse.latitude, ns.bse.longitude}: + lat = coords[ns.bse.latitude] + lon = coords[ns.bse.longitude] + self.map_.center_on(lat, lon) + +## EOF ## diff --git a/tagit/tiles/info.py b/tagit/tiles/info.py new file mode 100644 index 0000000..9555b35 --- /dev/null +++ b/tagit/tiles/info.py @@ -0,0 +1,85 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +from collections import OrderedDict + +# kivy imports +from kivy.lang import Builder + +# tagit imports +from tagit.utils import ttime, ns, magnitude_fmt +from tagit.widgets.browser import BrowserAwareMixin +from tagit.widgets.session import StorageAwareMixin + +# inner-module imports +from .tile import TileTabular + +# exports +__all__ = ('Info', ) + + +## code ## + +# load kv +Builder.load_string(''' +<Info>: + title: 'Item info' + tooltip: 'Key properties of the cursor item' + # assuming 7 info items + #default_size: None, 7*self.font_size + 6*5 + keywidth: min(75, self.width * 0.4) +''') + + +# classes +class Info(TileTabular, BrowserAwareMixin, StorageAwareMixin): + """Show essential attributes about the cursor.""" + + def on_root(self, wx, root): + BrowserAwareMixin.on_root(self, wx, root) + StorageAwareMixin.on_root(self, wx, root) + + def on_browser(self, sender, browser): + # remove old binding + if self.browser is not None: + self.browser.unbind(cursor=self.update) + # add new binding + self.browser = browser + if self.browser is not None: + self.browser.bind(cursor=self.update) + self.update() + + def __del__(self): + if self.browser is not None: + self.browser.unbind(cursor=self.update) + self.browser = None + + def on_predicate_modified(self, *args): + self.update() + + def update(self, *args): + cursor = self.root.browser.cursor + if not self.visible or cursor is None: + # invisible or no cursor, nothing to show + self.tabledata = OrderedDict({}) + + else: + preds = cursor.get( + ns.bsfs.Node().t_created, + ns.bse.filesize, + ns.bse.filename, + (ns.bse.tag, ns.bst.label), + ) + self.tabledata = OrderedDict({ + 'Date' : ttime.from_timestamp_utc( + preds.get(ns.bsfs.Node().t_created, ttime.timestamp_min)).strftime('%d.%m.%y %H:%M'), + 'Filesize' : magnitude_fmt(preds.get(ns.bse.filesize, 0)), + 'Filename' : preds.get(ns.bse.filename, 'n/a'), + 'Tags' : ', '.join(sorted(preds.get((ns.bse.tag, ns.bst.label), [' ']))), + }) + +## EOF ## diff --git a/tagit/tiles/selection_tags.py b/tagit/tiles/selection_tags.py new file mode 100644 index 0000000..7951cfe --- /dev/null +++ b/tagit/tiles/selection_tags.py @@ -0,0 +1,60 @@ +""" + +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 + +# tagit imports +from tagit.widgets.browser import BrowserAwareMixin + +# inner-module imports +from .tile import TileWithLabel + +# exports +__all__ = ('SelectionTags', ) + + +## code ## + +# load kv +Builder.load_string(''' +<SelectionTags>: + title: "Selection's tags" + default_size: None, 50 +''') + +# classes +class SelectionTags(TileWithLabel, BrowserAwareMixin): + """Show tags of selected items.""" + + def on_browser(self, sender, browser): + # remove old binding + if self.browser is not None: + self.browser.unbind(selection=self.update) + # add new binding + self.browser = browser + if self.browser is not None: + self.browser.bind(selection=self.update) + self.update() + + def __del__(self): + if self.browser is not None: + self.browser.unbind(selection=self.update) + self.browser = None + + def update(self, *args): + browser = self.root.browser + selection = browser.unfold(browser.selection) + if not self.visible or len(selection) == 0: + # nothing selected, nothing to do + self.text = '' + else: + tags = selection.tag.label(node=False) + tags = {tag.title() for tag in tags} # nice display + tags = sorted(tags) + self.text = ', '.join(tags) + +## EOF ## diff --git a/tagit/tiles/tile.kv b/tagit/tiles/tile.kv new file mode 100644 index 0000000..fcd9821 --- /dev/null +++ b/tagit/tiles/tile.kv @@ -0,0 +1,45 @@ + +<Tile>: + title: '' + tooltip: '' + default_size: None, None + +<TileTabular>: + # config + font_size: sp(15) + keywidth: 0.5 + + # table + rows: rows + TileTabularLine: + id: rows + orientation: 'tb-lr' + size_hint: 1, 1 + +<TileTabularLine@StackLayout>: + spacing: 10 + +<TileTabularRow>: + orientation: 'horizontal' + size_hint: 1, None + height: self.minimum_height + spacing: 10 + +<TileTabularCell>: + valign: 'top' + height: self.texture_size[1] + text_size: self.width, None + +<TileWithLabel>: + text: '' + text_align: 'left', 'top' + font_size: sp(15) + Label: + text: root.text + markup: True + text_size: root.size + font_size: root.font_size + halign: root.text_align[0] + valign: root.text_align[1] + +## EOF ## diff --git a/tagit/tiles/tile.py b/tagit/tiles/tile.py new file mode 100644 index 0000000..981d45b --- /dev/null +++ b/tagit/tiles/tile.py @@ -0,0 +1,85 @@ +""" + +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 +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.label import Label +from kivy.uix.relativelayout import RelativeLayout +import kivy.properties as kp + +# exports +__all__ = ('Tile', 'TileWithLabel', 'TileTabular') + + +## code ## + +# load kv +Builder.load_file(os.path.join(os.path.dirname(__file__), 'tile.kv')) + +# classes +class Tile(RelativeLayout): + visible = kp.BooleanProperty(False) + root = kp.ObjectProperty(None) + + def on_visible(self, wx, visible): + if visible: + self.update() + + def update(self, *args, **kwargs): + abstract() + + +class TileWithLabel(Tile): + pass + +class TileTabularRow(BoxLayout): + pass + +class TileTabularCell(Label): + pass + +class TileTabular(Tile): + + tabledata = kp.ObjectProperty() + keywidth = kp.NumericProperty(0.5) + + def on_tabledata(self, wx, data): + # set items + self.rows.clear_widgets() + for t_key, t_value in data.items(): + # row + row = TileTabularRow() + # left column (keys) + key = TileTabularCell( + text=t_key, + halign='right', + font_size = self.font_size, + size_hint=(None, 1), + width=self.width * self.keywidth if self.keywidth < 1 else self.keywidth, + ) + # right column (values) + value = TileTabularCell( + text=str(t_value), + halign='left', + font_size = self.font_size, + size_hint=(1, None), + ) + # adjust key's width and height dynamically. + # value's width and height are adjusted automatically + self.bind(width=lambda wx, width, key=key: setattr(key, 'width', + width * self.keywidth if self.keywidth < 1 else self.keywidth)) + key.bind(height=lambda wx, height, key=key: setattr(key, 'text_size', + (key.width, height))) + # add widgets + row.add_widget(key) + row.add_widget(value) + self.rows.add_widget(row) + +## EOF ## |