The Perfect BibleAnalyzer Experience on Any Distro
-
arcanemuse
- Posts: 6
- Joined: Sat Dec 31, 2022 5:13 pm
Re: The Perfect BibleAnalyzer Experience on Any Distro
I tried to use this on Mandriva. I got to the distrobox create line. First error it was complaining about crun. I installed that. Now it is saying a mount_program is required. 'overlay' is not supported over overlayfs. I have no idea what this means. I am an idiot so I can't fix it. I got as far as it being an issue with fuse-overlayfs which you can't get for Mandriva. I'm glad I'm trying this on a live usb and not an actual install. I always try to get bible analyzer working on a live usb first before I install. What's the point of installing the distro if the program won't work on it? I'm sort of stuck on Mint and MxLinux for the most part. Well, I'll be stuck on Mint only when they update to Debian 13 on MXLinux. BA won't run on Debian 13. I've tried a live usb already and actually updated a 12 install by mistake. I had to wipe my disk and reinstall everything to get Debian 12 back. Better to have a 2 year old distro that actually allows BA to run than a new one that won't run it. I have one laptop running Arch and I hate it but BA is in the user repos. You can just install the latest version. Every other machine will have to be on an old version of Debian or MX or Mint I guess. I won't use Ubuntu with their spyware and snap garbage. Your options as to a linux distro are pretty limited when the program you need only comes in .deb format. I was so hoping that this would actually work. I'd like to switch totally to Linux but I have a lot invested in Logos and Accordance and need either Mac or Windoze to run those. I don't have 2500 just lying around to buy a Mac so that means being stuck on Windoze.
-
arcanemuse
- Posts: 6
- Joined: Sat Dec 31, 2022 5:13 pm
Re: The Perfect BibleAnalyzer Experience on Any Distro
OK. I have a really thick head and I'm stubborn as a mule. I struck out on Mandriva but tried again on a live USB of Fedora 42 KDE. It took me a minute to find the shortcut in lost and found in the application menu but there it be. It works. Flawlessly. Thank you. What can I say? Mandriva is garbage apparently. Fedora did the job with no hassle. It simply worked when I put in the commands. I think I will be installing it as a keeper on this laptop. I've heard good things and it deserves a good spin around the block and a severe tire kicking. It has been years since I have had anything to do with an rpm based system. My last experience wasn't so good but that was a long time ago. I'm sure they have improved things over time.
-
wmcdannell
- Posts: 37
- Joined: Thu Oct 12, 2023 5:13 pm
Re: The Perfect BibleAnalyzer Experience on Any Distro
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).
Supported tags on Docker hub are listed here:
https://hub.docker.com/_/ubuntu
For 25.10 the install command is:
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:
It can be placed in /usr/bin/bibleanalyzer:
Or when you update the .desktop file:
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):
If you don't want to use sudo to edit the files in /opt/bibleanalyzer you can change the owner for the entire path
Save, and close the file and then run the following (don't use sudo if you change the owner of the path):
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):
And replace /opt/bibleanalyzer/ba-565/autoCtrl.py with:
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
distrobox create --image ubuntu:25.10 --name bibleanalyzer --home ~/.bibleanalyzercontainer/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 -yCode: Select all
env GTK_THEME=Adwaita bibleanalyzerCode: Select all
env GTK_THEME=Adwaita python3 /opt/bibleanalyzer/ba-run.pyCode: 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
Code: Select all
sudo apt install -y nanoCode: Select all
sys.path.insert(0, '/opt/bibleanalyzer/ba-565')
Code: Select all
chown -R $USER:$USER /opt/bibleanalyzer
Code: Select all
sudo unzip /opt/bibleanalyzer/ba-565.zip -d /opt/bibleanalyzer/ba-565Code: 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()
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()
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')