aboutsummaryrefslogtreecommitdiffstats
path: root/tagit/dialogues
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2023-03-05 19:17:00 +0100
committerMatthias Baumgartner <dev@igsor.net>2023-03-05 19:17:00 +0100
commit5a325565f917c8b1d233d8e6373756c253400909 (patch)
treee6e0b475c7ab5c6a7ff4f0ea7ad1b08cecf05e68 /tagit/dialogues
parente1e77797454ac747b293f589d8f2e0243173a419 (diff)
parent98e567933723c59d1d97b3a85e649cfdce514676 (diff)
downloadtagit-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/dialogues')
-rw-r--r--tagit/dialogues/__init__.py42
-rw-r--r--tagit/dialogues/autoinput.py73
-rw-r--r--tagit/dialogues/console.kv30
-rw-r--r--tagit/dialogues/console.py37
-rw-r--r--tagit/dialogues/dialogue.kv77
-rw-r--r--tagit/dialogues/dialogue.py108
-rw-r--r--tagit/dialogues/error.py45
-rw-r--r--tagit/dialogues/file_picker.py39
-rw-r--r--tagit/dialogues/license.t33
-rw-r--r--tagit/dialogues/message.py47
-rw-r--r--tagit/dialogues/numeric_input.py72
-rw-r--r--tagit/dialogues/path_picker.kv27
-rw-r--r--tagit/dialogues/path_picker.py41
-rw-r--r--tagit/dialogues/simple_input.kv30
-rw-r--r--tagit/dialogues/simple_input.py55
-rw-r--r--tagit/dialogues/stoken.py40
16 files changed, 796 insertions, 0 deletions
diff --git a/tagit/dialogues/__init__.py b/tagit/dialogues/__init__.py
new file mode 100644
index 0000000..beed253
--- /dev/null
+++ b/tagit/dialogues/__init__.py
@@ -0,0 +1,42 @@
+"""Popup dialogues.
+
+A dialogue can be opened from the main application.
+It appears on top of the application and prevent its use until the dialogue
+is closed. A dialogue contains buttons whose presses can be captured.
+
+>>> dlg = LabelDialogue(text='Hello world')
+>>> dlg.bind(on_ok=...)
+>>> dlg.bind(on_cancel=...)
+>>> dlg.open()
+
+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 .autoinput import AutoTextInput
+from .console import Console
+from .error import Error
+from .file_picker import FilePicker
+from .message import Message
+from .numeric_input import NumericInput
+from .path_picker import PathPicker
+from .simple_input import SimpleInput
+from .stoken import TokenEdit
+
+# exports
+__all__: typing.Sequence[str] = (
+ 'Console',
+ 'Error',
+ 'FilePicker',
+ 'Message',
+ 'NumericInput',
+ 'PathPicker',
+ 'SimpleInput',
+ 'TokenEdit',
+ )
+
+## EOF ##
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/console.kv b/tagit/dialogues/console.kv
new file mode 100644
index 0000000..b68227d
--- /dev/null
+++ b/tagit/dialogues/console.kv
@@ -0,0 +1,30 @@
+
+<Console>:
+ text: ''
+ title: 'tagit log console'
+ ok_on_enter: False
+ init_at_bottom: True
+
+ DialogueContentTitle:
+ title: root.title
+ size_hint_y: 0.6
+
+ ScrollView:
+ scroll_y: 0 if root.init_at_bottom else 1
+
+ Label:
+ text: root.text
+ size_hint_y: None
+ height: self.texture_size[1]
+ text_size: self.width, None
+ bold: True
+ font_name: resource_find('DejaVuSansMono.ttf') # monospace font
+ markup: True
+ multiline: True
+ halign: 'left'
+
+ DialogueButtons_One:
+ ok_text: "close"
+ # FIXME: Inform that this action will not terminate the underlying process
+
+## EOF ##
diff --git a/tagit/dialogues/console.py b/tagit/dialogues/console.py
new file mode 100644
index 0000000..282b378
--- /dev/null
+++ b/tagit/dialogues/console.py
@@ -0,0 +1,37 @@
+"""
+
+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.clock import mainthread
+from kivy.lang import Builder
+import kivy.properties as kp
+
+# inner-module imports
+from .dialogue import Dialogue
+
+# exports
+__all__ = ('Console', )
+
+
+## code ##
+
+# load kv
+Builder.load_file(os.path.join(os.path.dirname(__file__), 'console.kv'))
+
+# classes
+class Console(Dialogue):
+ """Dialogue with console output."""
+
+ text = kp.StringProperty('')
+
+ @mainthread
+ def update(self, sender, text):
+ self.text = '\n'.join(text)
+
+## EOF ##
diff --git a/tagit/dialogues/dialogue.kv b/tagit/dialogues/dialogue.kv
new file mode 100644
index 0000000..e2cab66
--- /dev/null
+++ b/tagit/dialogues/dialogue.kv
@@ -0,0 +1,77 @@
+#: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
+
+
+<DialogueContentNoTitle@DialogueContentBase>:
+ # nothing to do
+
+
+<DialogueTitle@Label>:
+
+<DialogueContentTitle@DialogueContentBase>:
+ title: ''
+ title_color: 1,1,1,1
+
+ DialogueTitle:
+ text: root.title
+ size_hint_y: None
+ height: self.texture_size[1] + dp(16)
+ text_size: self.width - dp(16), None
+ color: root.title_color
+
+ # small space
+ Label:
+ size_hint_y: None
+ height: 12
+
+
+
+<DialogueButton@Button>:
+
+<DialogueButtonRow>:
+ 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@DialogueButtonRow>:
+ ok_text: 'OK'
+
+ DialogueButton:
+ text: root.ok_text
+ on_press: get_root(self).ok()
+
+
+<DialogueButtons_Two@DialogueButtonRow>:
+ cancel_text: 'Cancel'
+ ok_text: 'OK'
+ ok_enabled: True
+
+ BoxLayout:
+ orientation: 'horizontal'
+ DialogueButton:
+ text: root.cancel_text
+ on_press: get_root(self).cancel()
+ DialogueButton:
+ 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..bf72a28
--- /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 DialogueButtonRow(BoxLayout): pass
+class DialogueButtons_One(DialogueButtonRow): pass
+class DialogueButtons_Two(DialogueButtonRow): 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/file_picker.py b/tagit/dialogues/file_picker.py
new file mode 100644
index 0000000..283adb6
--- /dev/null
+++ b/tagit/dialogues/file_picker.py
@@ -0,0 +1,39 @@
+"""Dialogue to pick a file.
+
+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
+
+# inner-module imports
+from .path_picker import PathPicker
+from .error import Error
+
+# exports
+__all__ = ('FilePicker', )
+
+
+## code ##
+
+# load kv
+Builder.load_string('''
+<FilePicker>:
+ dirselect: False
+ title: 'Please select a file'
+''')
+
+# classes
+class FilePicker(PathPicker):
+ """Dialogue with a file browser to select a file."""
+ def ok(self):
+ if not os.path.exists(self.path) or not os.path.isfile(self.path):
+ Error(text='Please select a file').open()
+ else:
+ super(FilePicker, self).ok()
+
+## 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/message.py b/tagit/dialogues/message.py
new file mode 100644
index 0000000..ab67180
--- /dev/null
+++ b/tagit/dialogues/message.py
@@ -0,0 +1,47 @@
+"""Dialogue to show some 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__ = ('Message', )
+
+
+## code ##
+
+# load kv
+Builder.load_string('''
+<Message>:
+ text: ''
+ align: 'center'
+ textcolor: 1,1,1,1
+
+ DialogueContentNoTitle:
+ Label:
+ text: root.text
+ size_hint_y: None
+ color: root.textcolor
+ height: self.texture_size[1] + dp(16)
+ text_size: self.width - dp(16), None
+ halign: root.align
+ markup: True
+
+ DialogueButtons_One:
+''')
+
+# classes
+class Message(Dialogue):
+ """Dialogue to show a text message."""
+ text = kp.StringProperty()
+ align = kp.StringProperty()
+ textcolor: kp.ListProperty()
+
+## EOF ##
diff --git a/tagit/dialogues/numeric_input.py b/tagit/dialogues/numeric_input.py
new file mode 100644
index 0000000..63491d2
--- /dev/null
+++ b/tagit/dialogues/numeric_input.py
@@ -0,0 +1,72 @@
+"""Dialogue with a slider.
+
+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__ = ('NumericInput', )
+
+
+## code ##
+
+# load kv
+Builder.load_string('''
+
+<NumericInput>:
+ value: int(slider.value)
+ init_value: 0
+
+ DialogueContentNoTitle:
+
+ BoxLayout:
+ orientation: 'horizontal'
+ size_hint_y: None
+ height: 50
+
+ Label:
+ text: str(root.lo)
+ size_hint_x: None
+ width: self.texture_size[0]
+
+ Slider:
+ id: slider
+ orientation: 'horizontal'
+ min: root.lo
+ max: root.hi
+ step: 1
+ value: root.init_value
+
+ Label:
+ text: str(root.hi)
+ size_hint_x: None
+ width: self.texture_size[0]
+
+ Label:
+ text: str(root.value)
+ size_hint: 1, None
+ height: self.texture_size[1]
+ halign: 'center'
+
+ DialogueButtons_Two:
+
+''')
+
+# classes
+class NumericInput(Dialogue):
+ """Dialogue with a slider."""
+
+ lo = kp.NumericProperty(0)
+ hi = kp.NumericProperty(100)
+ value = kp.NumericProperty(0)
+ init_value = kp.NumericProperty(0)
+
+
+## EOF ##
diff --git a/tagit/dialogues/path_picker.kv b/tagit/dialogues/path_picker.kv
new file mode 100644
index 0000000..1837b80
--- /dev/null
+++ b/tagit/dialogues/path_picker.kv
@@ -0,0 +1,27 @@
+#:import join os.path.join
+#:import pwd os.path.curdir
+
+<PathPicker>:
+ path: ''
+ title: 'Please select a file or directory'
+ filters: []
+ dirselect: True
+
+ DialogueContentTitle:
+ title: root.title
+ size_hint_y: 0.8
+
+ FileChooserListView:
+ text_size: self.width - dp(16), None
+ halign: 'center'
+ dirselect: root.dirselect
+ path: pwd
+ filters: root.filters
+
+ on_selection: root.path = join(self.path, self.selection[0]); buttons.ok_enabled = True
+
+ DialogueButtons_Two:
+ id: buttons
+ ok_enabled: False
+
+## EOF ##
diff --git a/tagit/dialogues/path_picker.py b/tagit/dialogues/path_picker.py
new file mode 100644
index 0000000..25bbf32
--- /dev/null
+++ b/tagit/dialogues/path_picker.py
@@ -0,0 +1,41 @@
+"""Dialogue to pick a file or directory.
+
+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
+from .error import Error
+
+# exports
+__all__ = ('PathPicker', )
+
+
+## code ##
+
+# load kv
+Builder.load_file(os.path.join(os.path.dirname(__file__), 'path_picker.kv'))
+
+# classes
+class PathPicker(Dialogue):
+ """Dialogue with a file browser to select a file or directory."""
+
+ title = kp.StringProperty('')
+ path = kp.StringProperty('')
+ filters = kp.ListProperty()
+
+ def ok(self):
+ if not os.path.exists(self.path):
+ Error(text='Please select a file or directory').open()
+ else:
+ super(PathPicker, self).ok()
+
+## EOF ##
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 ##