Tested and works fine on Ubuntu 25.10 (CachyOS [Arch] host). Note that if you have access to the AUR (Arch User Repository) there is a
PKGBUILD available for Bibleanaylzer (it also works fine on CachyOS/Arch).
Code: Select all
distrobox create --image ubuntu:25.10 --name bibleanalyzer --home ~/.bibleanalyzercontainer/
Supported tags on Docker hub are listed here:
https://hub.docker.com/_/ubuntu
For 25.10 the install command is:
Code: Select all
sudo apt install ./bibleanalyzer_5.6-1_all.deb python3-six -y
If you use a dark theme and it doesn't work well with BA you can change the theme using the following when launching /usr/bin/bibleanalyzer or /opt/bibleanalyzer/ba-run-py:
Code: Select all
env GTK_THEME=Adwaita bibleanalyzer
It can be placed in /usr/bin/bibleanalyzer:
Code: Select all
env GTK_THEME=Adwaita python3 /opt/bibleanalyzer/ba-run.py
Or when you update the .desktop file:
Code: Select all
env XDG_DATA_DIRS=/usr/share/ubuntu:/usr/share/gnome:/usr/local/share:/usr/share GTK_THEME=Adwaita python3 -u /opt/bibleanalyzer/ba-run.py"
The following is a UX hack for those that are interested and to keep a note for myself. It completely disables the autocomplete popups for input boxes because they don't work correctly on Linux platforms.
You'll need an editor installed to edit the following files so
or whichever editor you prefer. Change line 8 in /opt/bibleanalyzer/ba-run.py to the following (you may need to use sudo to edit the file):
Code: Select all
sys.path.insert(0, '/opt/bibleanalyzer/ba-565')
If you don't want to use sudo to edit the files in /opt/bibleanalyzer you can change the owner for the entire path
Code: Select all
chown -R $USER:$USER /opt/bibleanalyzer
Save, and close the file and then run the following (don't use sudo if you change the owner of the path):
Code: Select all
sudo unzip /opt/bibleanalyzer/ba-565.zip -d /opt/bibleanalyzer/ba-565
Then, replace /opt/bibleanalyzer/ba-565/autocomplete.py with the following (it will automatically detect when running on Linux and disable the autocomplete popups app-wide otherwise it's exactly the same as the original; note that the indentations/tabs are critical):
Code: Select all
# -*- coding: utf-8 -*-
__license__ = """Copyright (c) 2008-2010, Toni Ruža, All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE."""
__author__ = u"Toni Ruža <gmr.gaf@gmail.com>"
__url__ = "http://bitbucket.org/raz/wxautocompletectrl"
import wx
import wx.html
DISABLE_AUTOCOMPLETE = wx.Platform == "__WXGTK__"
class DisabledSuggestionsPopup(object):
Shown = False
def __init__(self):
self._suggestions = None
self._unformated_suggestions = None
self.inFilter = False
def __bool__(self):
return True
__nonzero__ = __bool__
def __getattr__(self, name):
if name == "Position":
return None
raise AttributeError(name)
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
def Hide(self):
pass
def DoHide(self):
pass
def DoShow(self):
pass
def ShowWithoutActivating(self):
pass
def IsShown(self):
return False
def IsActive(self):
return False
def SetSize(self, size):
pass
def SetSuggestions(self, suggestions, unformated_suggestions):
pass
def CursorUp(self):
pass
def CursorDown(self):
pass
def CursorHome(self):
pass
def CursorEnd(self):
pass
def GetSelectedSuggestion(self):
return None
def GetSuggestion(self, n):
return None
class SuggestionsPopup(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, style=wx.FRAME_NO_TASKBAR|wx.STAY_ON_TOP|wx.BORDER_NONE)
self._suggestions = self._listbox(self)
self._suggestions.SetItemCount(0)
self._unformated_suggestions = None
self._suggestions.SetSelectionBackground(wx.Colour(100, 100, 200))
self.mainApp = wx.GetApp().GetTopWindow()
self.inFilter = False
class _listbox(wx.html.HtmlListBox):
items = None
def OnGetItem(self, n):
return self.items[n]
def OnDrawBackground(self, dc, rect, item):
if item % 2 == 0:
bgCol = wx.Colour(240,240,255)
dc.SetBrush(wx.Brush(bgCol))
dc.SetPen(wx.Pen(bgCol))
dc.DrawRectangle(rect)
def SetSuggestions(self, suggestions, unformated_suggestions):
self._suggestions.items = suggestions
self._suggestions.SetItemCount(len(suggestions))
self._suggestions.SetSelection(0)
self._suggestions.Refresh()
self._unformated_suggestions = unformated_suggestions
def CursorUp(self):
selection = self._suggestions.GetSelection()
if selection > 0:
self._suggestions.SetSelection(selection - 1)
def CursorDown(self):
selection = self._suggestions.GetSelection()
last = self._suggestions.GetItemCount() - 1
if selection < last:
self._suggestions.SetSelection(selection + 1)
def CursorHome(self):
if self.IsShown():
self._suggestions.SetSelection(0)
def CursorEnd(self):
if self.IsShown():
self._suggestions.SetSelection(self._suggestions.GetItemCount() - 1)
def GetSelectedSuggestion(self):
#if self.inFilter:
return self._unformated_suggestions[self._suggestions.GetSelection()]
'''
else:
print 'else'
if self.mainApp.dctNB.dctPanel.ac.HasFocus():
print 'in dct', self._suggestions.GetSelection()
return self.mainApp.currentWL[self.mainApp.dctNB.dctPanel.ac.popup._suggestions.GetSelection()]
elif self.mainApp.cmtNB.cmtPanel.ac.HasFocus():
return self.mainApp.currentVL[self.mainApp.cmtNB.cmtPanel.ac.popup._suggestions.GetSelection()]
elif self.mainApp.noteEdit.ac.HasFocus():
return self.mainApp.currentVL[self.mainApp.noteEdit.ac.popup._suggestions.GetSelection()]
'''
def GetSuggestion(self, n):
#if self.inFilter:
return self._unformated_suggestions[n]
'''
else:
if self.mainApp.dctNB.dctPanel.ac.popup._suggestions.HasFocus():
return self.mainApp.currentWL[n]
elif self.mainApp.cmtNB.cmtPanel.ac.popup._suggestions.HasFocus() or \
self.mainApp.noteEdit.ac.popup._suggestions.HasFocus():
return self.mainApp.currentVL[n]
'''
def DoHide(self):
self.Hide()
def DoShow(self):
self.ShowWithoutActivating()
class AutocompleteTextCtrl(wx.ComboCtrl):
def __init__(self, parent, size=(160,-1), height=350, completer=None, multiline=False, frequency=25):
self.mainApp = wx.GetApp().GetTopWindow()
self.noFocusSel = 0
style = wx.TE_PROCESS_ENTER
if multiline:
style = style | wx.TE_MULTILINE
wx.ComboCtrl.__init__(self, parent, size=size, style=style)
self.height = height
self.frequency = frequency
if completer:
self.SetCompleter(completer)
self.queued_popup = False
self.skip_event = False
self.parent = parent.GetParent()
def DoSetPopupControl(self, popup):
pass
def OnButtonClick(self):
if DISABLE_AUTOCOMPLETE:
return
if self.parent == self.mainApp.dctNB:
self.mainApp.dctNB.dctPanel.OnDown()
elif self.parent == self.mainApp.cmtNB:
self.mainApp.cmtNB.cmtPanel.OnDown()
else:
self.mainApp.noteEdit.OnDown()
def SetCompleter(self, completer):
"""
Initializes the autocompletion. The 'completer' has to be a function
with one argument (the current value of the control, ie. the query)
and it has to return two lists: formated (html) and unformated
suggestions.
"""
self.completer = completer
if DISABLE_AUTOCOMPLETE:
self.popup = DisabledSuggestionsPopup()
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_SET_FOCUS, self.OnFocus)
return
self.popup = SuggestionsPopup(self.mainApp)
self.mainApp.Bind(wx.EVT_MOVE, self.OnMove)
self.Bind(wx.EVT_TEXT, self.OnTextUpdate)
self.Bind(wx.EVT_SIZE, self.OnSizeChange)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_SET_FOCUS, self.OnFocus)
self.popup._suggestions.Bind(wx.EVT_LEFT_UP, self.OnSuggestionClicked)
#self.Bind(wx.EVT_SET_FOCUS, self.OnFocus)
def OnFocus(self, event):
#print 'focus'
if not self.noFocusSel:
wx.CallAfter(self.SelectAll)
self.skip_event = False
self.noFocusSel = 0
#print 'on focus'
def AdjustPopupPosition(self):
if DISABLE_AUTOCOMPLETE:
return
self.popup.Position = self.ClientToScreen((-1, self.Size.height)).Get()
self.popup.SetSize((self.Size.width +25, self.height))
def OnMove(self, event):
try:
self.AdjustPopupPosition()
except:
#print 'autoctrl error'
pass
event.Skip()
def OnTextUpdate(self, event):
event.Skip()
if DISABLE_AUTOCOMPLETE:
return
if self.skip_event:
self.skip_event = False
elif not self.queued_popup:
wx.CallLater(self.frequency, self.AutoComplete)
self.queued_popup = True
def AutoComplete(self):
if DISABLE_AUTOCOMPLETE:
self.queued_popup = False
return
self.queued_popup = False
if self.Value:
formated, unformated = self.completer(self.Value.lower())
if len(formated) > 0:
self.popup.SetSuggestions(formated, unformated)
self.AdjustPopupPosition()
self.Unbind(wx.EVT_KILL_FOCUS)
if len(formated) < 16:
h = (len(formated)) * 24
self.popup.SetSize((self.Size.width +25, h))
self.popup.ShowWithoutActivating()
self.noFocusSel = 1
#self.SetFocus()
wx.CallAfter(self.Bind, wx.EVT_KILL_FOCUS, self.OnKillFocus)
#wx.CallAfter(self.mainApp.Raise)
else:
self.popup.Hide()
else:
self.popup.Hide()
def OnSizeChange(self, event):
if DISABLE_AUTOCOMPLETE:
event.Skip()
return
self.popup.Size = (self.Size[0], self.height)
event.Skip()
def OnKeyDown(self, event):
key = event.GetKeyCode()
#print 'in key down1', key
if DISABLE_AUTOCOMPLETE:
if key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
if self.mainApp.dctNB.dctPanel.ac.HasFocus():
self.mainApp.OnDctViewEntry()
return
elif self.mainApp.cmtNB.cmtPanel.ac.HasFocus():
self.mainApp.OnCmtViewEntry()
return
elif self.mainApp.noteEdit.ac.HasFocus():
self.mainApp.currentNoteRef = self.GetValue()
self.mainApp.OnNotesUpdate()
self.mainApp.noteSync = False
return
event.Skip()
return
if key == wx.WXK_UP:
self.popup.CursorUp()
return
elif key == wx.WXK_DOWN:
if not self.popup.Shown:
self.OnButtonClick()
self.popup.CursorDown()
return
elif key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) and self.popup.Shown:
self.skip_event = True
item = self.popup.GetSelectedSuggestion()
#print item, 'item'
self.ChangeValue(item)
self.SetInsertionPointEnd()
#wx.CallAfter(self.SetFocus)
self.popup.Hide()
self.noFocusSel = 0
if self.mainApp.dctNB.dctPanel.ac.HasFocus():
self.mainApp.OnDctViewEntry()
elif self.mainApp.cmtNB.cmtPanel.ac.HasFocus():
self.mainApp.OnCmtViewEntry()
elif self.mainApp.noteEdit.ac.HasFocus():
self.mainApp.currentNoteRef = item
self.mainApp.OnNotesUpdate()
self.mainApp.noteSync = False
return
elif key == wx.WXK_HOME:
self.popup.CursorHome()
elif key == wx.WXK_END:
self.popup.CursorEnd()
elif event.ControlDown() and chr(key).lower() == "a":
self.SelectAll()
elif key == wx.WXK_ESCAPE:
self.popup.Hide()
return
event.Skip()
def OnSuggestionClicked(self, event):
self.skip_event = True
n = self.popup._suggestions.HitTest(event.Position)
item = self.popup.GetSuggestion(n)
#print(item, 'item')
if not item:
print('No item')
return
self.ChangeValue(item)
#wx.CallAfter(self.SetFocus)
event.Skip()
self.popup.Hide()
self.noFocusSel = 0
if event.GetEventObject() == self.mainApp.dctNB.dctPanel.ac.popup._suggestions:
self.mainApp.OnDctViewEntry()
elif event.GetEventObject() == self.mainApp.cmtNB.cmtPanel.ac.popup._suggestions:
self.mainApp.OnCmtViewEntry(True)
elif event.GetEventObject() == self.mainApp.noteEdit.ac.popup._suggestions:
self.mainApp.OnSaveNotes()
syncState = self.mainApp.noteSync
self.mainApp.noteSync = True
#self.mainApp.currentNoteRef = item
self.mainApp.OnNotesUpdate()
self.mainApp.noteSync = syncState
def OnSuggestionKeyDown(self, event):
key = event.GetKeyCode()
#print 'in key down2', key
if key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
self.skip_event = True
item = self.popup.GetSelectedSuggestion()
self.ChangeValue(item)
#wx.CallAfter(self.SetFocus)
self.popup.Hide()
self.noFocusSel = 0
if self.mainApp.dctNB.dctPanel.ac.HasFocus():
self.mainApp.OnDctViewEntry()
elif self.mainApp.cmtNB.cmtPanel.ac.HasFocus():
self.mainApp.OnCmtViewEntry(True)
elif self.mainApp.noteEdit.ac.HasFocus():
self.mainApp.OnSaveNotes()
syncState = self.mainApp.noteSync
self.mainApp.noteSync = True
self.mainApp.currentNoteRef = item
self.mainApp.OnNotesUpdate()
self.mainApp.noteSync = syncState
event.Skip()
def OnKillFocus(self, event):
print ('in kill')
if not self.mainApp.FindFocus() or self.mainApp.FindFocus() == self:
return
if not self.popup.IsActive():
self.noFocusSel = 0
wx.CallAfter(self.popup.Hide)
event.Skip()
And replace /opt/bibleanalyzer/ba-565/autoCtrl.py with:
Code: Select all
# -*- coding: utf-8 -*-
__license__ = """Copyright (c) 2008-2010, Toni Ruža, All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE."""
__author__ = u"Toni Ruža <gmr.gaf@gmail.com>"
__url__ = "http://bitbucket.org/raz/wxautocompletectrl"
import wx
import wx.html
import re
import fixRef, validateCount
class SuggestionsPopup(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, style=wx.STAY_ON_TOP|wx.FRAME_NO_TASKBAR|wx.BORDER_NONE)
self._suggestions = self._listbox(self)
self._suggestions.SetSelectionBackground(wx.Colour(100, 100, 200))
self._suggestions.SetItemCount(0)
self._unformated_suggestions = None
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self._suggestions, 1, wx.EXPAND)
self.SetSizer(sizer)
self.Layout()
class _listbox(wx.html.HtmlListBox):
items = None
vsLstLen = 0
def OnGetItem(self, n):
return self.items[n]
def OnDrawSeparator(self, dc, rect, index):
if index != self.vsLstLen:
return
oldpen = dc.GetPen()
dc.SetPen(wx.Pen(wx.Colour(150,150,250)))
dc.DrawLine(rect.x, rect.y,
rect.x + rect.width -2,
rect.y)
rect.Deflate(0, 2)
dc.SetPen(oldpen)
def OnDrawBackground(self, dc, rect, item):
if item > self.vsLstLen and item % 2 == 0:
bgCol = wx.Colour(240,240,255)
dc.SetBrush(wx.Brush(bgCol))
dc.SetPen(wx.Pen(bgCol))
dc.DrawRectangle(rect)
elif item % 2 == 0:
bgCol = wx.Colour(255,240,240)
dc.SetBrush(wx.Brush(bgCol))
dc.SetPen(wx.Pen(bgCol))
dc.DrawRectangle(rect)
def SetSuggestions(self, suggestions, unformated_suggestions, vsLstLen, wdLstLen):
#print wdLstLen, 'wdLstLen'
self._suggestions.SetSize((220, wdLstLen*21))
self.SetClientSize(self._suggestions.GetSize())
self._suggestions.vsLstLen = vsLstLen
self._suggestions.items = suggestions
self._suggestions.SetItemCount(len(suggestions))
self._suggestions.SetSelection(0)
self._suggestions.Refresh()
self._unformated_suggestions = unformated_suggestions
def CursorUp(self):
selection = self._suggestions.GetSelection()
if selection > 0:
self._suggestions.SetSelection(selection - 1)
def CursorDown(self):
selection = self._suggestions.GetSelection()
last = self._suggestions.GetItemCount() - 1
if selection < last:
self._suggestions.SetSelection(selection + 1)
def CursorHome(self):
if self.IsShown():
self._suggestions.SetSelection(0)
def CursorEnd(self):
if self.IsShown():
self._suggestions.SetSelection(self._suggestions.GetItemCount() - 1)
def GetSelectedSuggestion(self):
sel = self._unformated_suggestions[self._suggestions.GetSelection()]
if type(sel) == tuple:
return sel[0]
else:
return sel
def GetSuggestion(self, n):
sel = self._unformated_suggestions[n]
print (n, sel)
if type(sel) == tuple:
return sel[0]
else:
return sel
class AutoTextCtrl(wx.TextCtrl):
def __init__(self, parent, size, height=300, vsCompleter=None, wdCompleter=None, multiline=False, frequency=100):
self.mainApp = wx.GetApp().GetTopWindow()
self.vsCount = validateCount.vsCount
self.chCount = validateCount.chCount
self.noFocusSel = 0
#print parent, 'main'
style = wx.TE_PROCESS_ENTER
if multiline:
style = style | wx.TE_MULTILINE
wx.TextCtrl.__init__(self, parent, value='', size=size, style=style)
self.height = height
self.frequency = frequency
if vsCompleter:
self.vsCompleter = vsCompleter
if wdCompleter:
self.SetCompleter(wdCompleter)
self.queued_popup = False
self.skip_event = False
self.Bind(wx.EVT_TEXT, self.OnTextUpdate)
self.Bind(wx.EVT_SIZE, self.OnSizeChange)
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_SET_FOCUS, self.OnFocus)
#self.Bind(wx.EVT_KILL_FOCUS, self.OnNoFocus)
def validate(self, ref):
#print ref, 'val'
def check(mo):
bk = mo.group(1)
ch = mo.group(2)
ch = re.sub(r'^0+(\d+)', r'\1', ch)
vs = mo.group(3)
if vs:
vs = re.sub(r'^0+(\d+)', r'\1', vs)
vs = re.sub(r'^0+', r'', vs)
if not vs:
vs = ''
if re.search(r'^0+', ch):
ch = 1
#print vs, 'vs'
if not int(ch) <= self.chCount[bk]:
#print mo.group(0), 'Bad chap.'
return '%s %s:%s' % (bk, self.chCount[bk], vs)
if vs and not int(vs) <= self.vsCount['%s %s:' % (bk, ch)]:
#print mo.group(0), 'Bad verse', self.vsCount['%s %s:' % (bk, ch)], 'max'
return '%s %s:%s' % (bk, ch, self.vsCount['%s %s:' % (bk, ch)])
return mo.group(0)
vRef = re.sub(r'(\w\w\w) (\d+):(\d+)?', check, ref)
vRef = re.sub(r'(\w\w\w) (\d+):(\d+)', check, vRef)
return vRef
def SetCompleter(self, completer):
"""
Initializes the autocompletion. The 'completer' has to be a function
with one argument (the current value of the control, ie. the query)
and it has to return two lists: formated (html) and unformated
suggestions.
"""
self.completer = completer
# On Linux (WXGTK) the autocomplete popup is unreliable and interferes
# with user interaction. Don't create or bind the popup there.
if wx.Platform == "__WXGTK__":
class _DisabledPopup(object):
Shown = False
def __init__(self):
class _Sugg(object):
def VirtualHitTest(self, y):
return 0
self._suggestions = _Sugg()
def SetSuggestions(self, *a, **k):
pass
def Hide(self, *a, **k):
pass
def ShowWithoutActivating(self, *a, **k):
pass
def CursorUp(self, *a, **k):
pass
def CursorDown(self, *a, **k):
pass
def CursorHome(self, *a, **k):
pass
def CursorEnd(self, *a, **k):
pass
def GetSelectedSuggestion(self, *a, **k):
return ''
def GetSuggestion(self, *a, **k):
return ''
def IsActive(self, *a, **k):
return False
self.popup = _DisabledPopup()
return
# Non-Linux: create functional popup
self.popup = SuggestionsPopup(self.mainApp)
self.popup._suggestions.Bind(wx.EVT_LEFT_UP, self.OnSuggestionClicked)
#self.popup._suggestions.Bind(wx.EVT_KEY_DOWN, self.OnSuggestionKeyDown)
self.mainApp.Bind(wx.EVT_MOVE, self.OnMove)
def AdjustPopupPosition(self):
self.popup.Position = self.ClientToScreen((-2, self.Size.height -1)).Get()
def OnFocus(self, event):
if not self.noFocusSel:
wx.CallAfter(self.SelectAll)
#wx.CallAfter(self.SelectAll)
self.skip_event = False
self.noFocusSel = 0
def OnNoFocus(self, event):
wx.CallAfter(self.SelectNone)
def OnMove(self, event):
self.AdjustPopupPosition()
event.Skip()
def OnTextUpdate(self, event):
event.Skip()
if not self.IsModified():
return
if re.search(r'^0+', self.Value):
return
self.preForm = ''
self.preUnform = ''
hit1 = re.search(r'^ ?(\d+[.:]\d+)', self.Value)
hit2 = re.search(r'^ ?(\d+[.:])(?! ?\D)', self.Value)
hit3 = re.search(r'^ ?(\d+)(?![.:])(?! ?\D)', self.Value)
fullBook = fixRef.fullTitle(self.mainApp.currentChap[:3])
if hit1:
#print 'hit1', hit1.group(1)
ref = hit1.group(1).replace('.', ':')
valRef = self.validate(u"%s %s" % (self.mainApp.currentChap[:3], ref))
valRef = re.search(r'(\d+:\d+)', valRef).group(1)
self.preForm = u'<font color="brown"><b>%s</b></font> %s' % (fullBook, valRef)
self.preUnform = u"%s %s" % (self.mainApp.currentChap[:3], valRef)
elif hit2:
#print 'hit2', hit2.group(1)
ref = hit2.group(1).replace('.', ':')
valRef = self.validate(u"%s %s" % (self.mainApp.currentChap[:3], ref))
valCh = re.search(r'(\d+):', valRef).group(1)
self.preForm = u'<font color="brown"><b>%s</b></font> %s' % (fullBook, valCh)
self.preUnform = u"%s %s" % (self.mainApp.currentChap[:3], valCh)
elif hit3:
#print 'hit3', hit3.group(1)
#vs = hit3.group(1)
chap = self.mainApp.currentChap
if not self.mainApp.currentChap.endswith(':'):
chap = self.mainApp.currentChap + ' 1:'
valRef = self.validate(u"%s%s" % (chap, hit3.group(1)))
valVs = re.search(r':(\d+)', valRef).group(1)
self.preForm = u'<font color="brown"><b>%s</b></font>%s%s' % (fullBook, self.mainApp.currentChap[3:], valVs)
self.preUnform = u"%s %s" % (self.mainApp.currentChap, valVs)
if self.skip_event:
self.skip_event = False
return
if not self.queued_popup:
wx.CallLater(self.frequency, self.AutoComplete)
#print 'set1'
self.queued_popup = True
def AutoComplete(self):
self.queued_popup = False
if self.Value:
value = self.Value.lower()
value = re.sub(r'([a-z])\. *', r'\1', self.Value, re.I)
refValue = re.sub(r'([123])([skctpj])', r'\1 \2', value, re.I)
try:
if re.match('\d', refValue) and len(refValue) > 1:
cap = refValue[2].upper()
refValue = refValue[:2] + cap + refValue[3:]
else:
refValue =refValue.capitalize()
except:
refValue =refValue.capitalize()
vsLstLen = 0
num = None
dig = re.search(r'\w ?(\d+[.:]?(?:\d+)?)', refValue)
if dig:
num = dig.group(1)
num = re.sub(r'^0+', '1', num)
num = re.sub(r'^0+(\d+)', r'\1', num)
num = num.replace(".", ":")
if not ':' in num:
num = num + ':'
refValue = re.sub(r'(\w) ?\d+[.:]?(?:\d+)?', r'\1', refValue)
vsFormated, vsUnformated = self.vsCompleter(refValue)
if num:
numList = []
for ref in vsUnformated:
valRef = self.validate(u"%s %s" % (fixRef.refMatch(ref), num))
valNum = re.match(r'\w\w\w (.+)', valRef).group(1)
numList.append(valNum)
vsFormated = ['%s %s' % (v, numList[id]) for id, v in enumerate(vsFormated)]
vsUnformated = ['%s %s' % (v, numList[id]) for id, v in enumerate(vsUnformated)]
formated, unformated = self.completer(self.Value)
if len(vsFormated) > 0:
vsLstLen = len(vsFormated)
formated = vsFormated[:len(vsFormated)] + formated
unformated = vsUnformated[:len(vsUnformated)] + unformated
if self.preForm:
formated.insert(0, self.preForm)
unformated.insert(0, self.preUnform)
vsLstLen +=1
if len(unformated) > 18:
formated = formated[:18]
unformated = unformated[:18]
if len(formated) > 0:
self.popup.SetSuggestions(formated, unformated, vsLstLen, len(unformated))
self.AdjustPopupPosition()
self.Unbind(wx.EVT_KILL_FOCUS)
self.popup.ShowWithoutActivating()
self.noFocusSel = 1
#self.SetFocus()
wx.CallAfter(self.Bind, wx.EVT_KILL_FOCUS, self.OnKillFocus)
#wx.CallAfter(self.mainApp.Raise)
else:
self.popup.Hide()
else:
self.popup.Hide()
def OnSizeChange(self, event):
self.popup.Size = (self.Size[0], self.height)
event.Skip()
def OnKeyDown(self, event):
key = event.GetKeyCode()
if key == wx.WXK_UP:
self.popup.CursorUp()
return
elif key == wx.WXK_DOWN:
self.popup.CursorDown()
return
elif key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) and self.popup.Shown:
self.skip_event = True
self.SetValue(self.popup.GetSelectedSuggestion())
self.SetInsertionPointEnd()
self.popup.Hide()
self.noFocusSel = 0
self.mainApp.OnSearch(None)
return
elif key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
self.skip_event = True
self.SetInsertionPointEnd()
self.mainApp.OnSearch(None)
return
elif key == wx.WXK_HOME:
self.popup.CursorHome()
elif key == wx.WXK_END:
self.popup.CursorEnd()
elif event.ControlDown() and chr(key).lower() == "a":
self.SelectAll()
elif key == wx.WXK_ESCAPE:
self.popup.Hide()
return
event.Skip()
def OnSuggestionClicked(self, event):
self.skip_event = True
n = self.popup._suggestions.VirtualHitTest(event.Position[1])
#print n, 'click'
self.Value = self.popup.GetSuggestion(n)
self.SetInsertionPointEnd()
self.popup.Hide()
self.noFocusSel = 0
self.mainApp.OnSearch(None)
event.Skip()
def OnSuggestionKeyDown(self, event):
key = event.GetKeyCode()
if key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
self.skip_event = True
self.SetValue(self.popup.GetSelectedSuggestion())
self.SetInsertionPointEnd()
self.popup.Hide()
self.noFocusSel = 0
self.mainApp.OnSearch(None)
event.Skip()
def OnKillFocus(self, event):
if not self.popup.IsActive():
self.noFocusSel = 0
wx.CallAfter(self.popup.Hide)
event.Skip()
If you have trouble installing the espeak-ng package because it conflicts with the espeak package required by Bibleanalyzer you can replace /opt/bibleanalyzer/ba-565/speech.py with the following version that will not bail out on startup if python-espeak is missing. Then, you can forcefully remove the espeak package (eg. pacman -Rdd espeak) which will allow you to both install espeak-ng and still run Bibleanalyzer.
Code: Select all
import wx, re, fixRef, dialogs
try:
from espeak import espeak
except ImportError:
espeak = None
#from AppKit import NSSpeechSynthesizer
#import Sapi5
books = {
'Gen':'Genesis', 'Exo':'Exodus', 'Lev':'Leviticus', 'Num':'Numbers', 'Deu':'Deuteronomy', 'Jos':'Joshua', 'Jdg':'Judges',
'Rth':'Ruth', '1Sa':'1 Samuel', '2Sa':'2 Samuel', '1Ki':'1 Kings', '2Ki':'2 Kings', '1Ch':'1 Chronicles',
'2Ch':'2 Chronicles', 'Ezr':'Ezra', 'Neh':'Nehemiah', 'Est':'Esther', 'Job':'Job', 'Psa':'Psalms', 'Pro':'Proverbs',
'Ecc':'Ecclesiastes', 'Son':'Song Solomon', 'Isa':'Isaiah', 'Jer':'Jeremiah', 'Lam':'Lamentations', 'Eze':'Ezekiel',
'Dan':'Daniel', 'Hos':'Hosea', 'Joe':'Joel', 'Amo':'Amos', 'Oba':'Obadiah', 'Jon':'Jonah', 'Mic':'Micah', 'Nah':'Nahum',
'Hab':'Habbakkuk', 'Zep':'Zephaniah', 'Hag':'Haggai', 'Zec':'Zechariah', 'Mal':'Malachi', 'Mat':'Matthew', 'Mar':'Mark',
'Luk':'Luke', 'Joh':'John', 'Act':'Acts', 'Rom':'Romans', '1Co':'1 Corinthians', '2Co':'2 Corinthians', 'Gal':'Galatians',
'Eph':'Ephesians', 'Phi':'Philippians', 'Col':'Colossians', '1Th':'1 Thessalonians', '2Th':'2 Thessalonians ',
'1Ti':'1 Timothy', '2Ti':'2 Timothy', 'Tit':'Titus', 'Phm':'Philemon', 'Heb':'Hebrews', 'Jam':'James', '1Pe':'1 Peter',
'2Pe':'2 Peter', '1Jo':'1 John', '2Jo':'2 John', '3Jo':'3 John', 'Jud':'Jude', 'Rev':'Revelation',}
class Text2Speech:
def __init__(self):
self.ttsSynth = espeak
if not self.ttsSynth:
self.voiceIDList = []
self.voices = []
return
self.voiceIDList = self.ttsSynth.list_voices()
self.voices = [x.name for x in self.voiceIDList]
#print self.voices
#print self.voiceIDList
#espeak.set_parameter(espeak.Parameter.Rate, <speed>)
def abbr2Full(self, match):
abbr = match.group(1)
try:
full = books[abbr]
except:
full = match.group(1)
return full + ' ' + match.group(2)
def speakTxt(self, txt, skipRef, voice = None):
if '<a hr' in txt:
txt = re.sub(r'<s href="bible://[^>]+>(.+?)</s>', r'', txt)
txt = re.sub(r'(<a href="embNote.+?">)(.+?)</a>', r'', txt)
txt = re.sub(r'_|<br>|<hr>', ' ', txt)
txt = re.sub(r' |&#\d+;|<fnc?>.+?</fnc?>|<.*?>', '', txt)
txt = re.sub(r'\bNT\b', 'New Testament', txt)
txt = re.sub(r'\bOT\b', 'Old Testament', txt)
txt = re.sub(r'([?!])', r'\1 ', txt)
if skipRef == 1:
txt = re.sub(r'\w\w\w \d+:\d+', '', txt)
else:
txt = re.sub(r'(\w\w\w) (\d+:[^ ]+)', self.abbr2Full, txt)
txt = re.sub(r'(\d+):(\d+)', r'\1 \2', txt)
#self.setVolume(level)
#self.setRate(rate)
if not self.ttsSynth:
return
if voice:
#print voice, 'in speech'
self.ttsSynth.set_voice(voice)
#try:
#self.ttsSynth.startSpeakingString_(txt)
#print txt
self.ttsSynth.synth(txt)
#except:
# return
#self.errorDialog()
def setVolume(self, level):
#set the speech volume percentage (0-100%)
if not self.ttsSynth:
return
self.ttsSynth.Volume(level)
def getVolume(self):
#set the speech volume percentage (0-100%)
if not self.ttsSynth:
return 0
return self.ttsSynth.Volume()
def setRate(self, rate):
#set the speech rate between -10 (slowest) and 10 (fastest), 0 is the default
if not self.ttsSynth:
return
self.ttsSynth.setRate_(rate)
def getRate(self):
#set the speech rate between -10 (slowest) and 10 (fastest), 0 is the default
if not self.ttsSynth:
return 0
return self.ttsSynth.Rate()
def IsSpeaking(self):
if not self.ttsSynth:
return False
return self.ttsSynth.is_playing()
def stop(self):
if not self.ttsSynth:
return
self.ttsSynth.cancel()
#speak = Text2Speech()
#speak.speak('this is a test of tts speaking', 1, 'english')