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()

Post Reply