aboutsummaryrefslogtreecommitdiffstats
path: root/tagit/dialogues
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2023-01-13 09:49:10 +0100
committerMatthias Baumgartner <dev@igsor.net>2023-01-13 09:49:10 +0100
commit9c366758665d9cfee7796ee45a8167a5412ae9ae (patch)
treeb42e0a1fd4b1bd59fc31fad6267b83c2dc9a3a3b /tagit/dialogues
parent8f2f697f7ed52b7e1c7a17411b2de526b6490691 (diff)
downloadtagit-9c366758665d9cfee7796ee45a8167a5412ae9ae.tar.gz
tagit-9c366758665d9cfee7796ee45a8167a5412ae9ae.tar.bz2
tagit-9c366758665d9cfee7796ee45a8167a5412ae9ae.zip
filter early port, parsing adaptions
Diffstat (limited to 'tagit/dialogues')
-rw-r--r--tagit/dialogues/__init__.py14
-rw-r--r--tagit/dialogues/autoinput.py73
-rw-r--r--tagit/dialogues/dialogue.kv114
-rw-r--r--tagit/dialogues/dialogue.py108
-rw-r--r--tagit/dialogues/error.py45
-rw-r--r--tagit/dialogues/license.t33
-rw-r--r--tagit/dialogues/simple_input.kv30
-rw-r--r--tagit/dialogues/simple_input.py55
-rw-r--r--tagit/dialogues/stoken.py40
9 files changed, 505 insertions, 7 deletions
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 ##