The Perfect BibleAnalyzer Experience on Any Distro

The place to discuss the Linux/Ubuntu edition
arcanemuse
Posts: 6
Joined: Sat Dec 31, 2022 5:13 pm

Re: The Perfect BibleAnalyzer Experience on Any Distro

Post by arcanemuse »

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

Post by arcanemuse »

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

Post by wmcdannell »

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

Code: Select all

sudo apt install -y nano
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'&nbsp;|&#\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')	

Post Reply