Adding a Spell Check to QGIS

(Or what to do on a rainy bank holiday in Glasgow)

This Monday was a local bank holiday in Glasgow (or at least the university) as a remnant of when the whole town took a train to Blackpool in the same two weeks so that the ship builders and steel works could stop in a coordinated fashion. As is required in the UK the weather was awful so I stayed in and being bored looked at my long list of possible projects. I picked one that has been kicking around on the list for a while adding a spell checker for QGIS. As a dyslexic I have spell checking turned on in nearly every program I enter text into including vim, InteliJ and my browser. So I have always felt that what QGIS really needed was a way to spell check maps before I printed them at A3 and put them on the wall.

Back in 2019 North Road wrote a iblog post about custom layout checks and ended it with a throw away comment “It’d even be possible to hook into one of the available Python spell checking libraries to write a spelling check!”. I came across this when I was trying to see if there was an easy way for my students (many of whom have English as a second language) to avoid handing in projects with glaring (i.e. I can see them) spelling errors in the title. So I stuck the link on my backlog, until the proverbial rainy day came along.

Implementation

Obviously I’m the last person who should be allowed to write spell checking software, but the joy of open source is that for things like this someone else has almost certainly already done it. So a quick duck-duck-go found me installing pyspellcheck which seemed like it would do what I want. It has a pretty easy interface in that once you’ve created a spell checker object, you can just pass in a list of words and it will return a list of (probably) misspelled words and a method to give the most likely correction and another method to give you list of other possibilities. Armed with this I could create a method to find and check all the text elements of a print layout.

@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
def layout_check_spelling(context, feedback):
    layout = context.layout
    results = []
    checker = SpellChecker()

    for i in layout.items():
        if isinstance(i, QgsLayoutItemLabel):
            text = i.currentText()
            tokens = [word.strip(string.punctuation) for word in text.split()]
            misspelled = checker.unknown(tokens)
            for word in misspelled:
                res = QgsValidityCheckResult()
                res.type = QgsValidityCheckResult.Warning
                res.title = 'Spelling Error?'
                template = f"""
                <strong>'{word}</strong>' may be misspelled, would
                '<strong>{checker.correction(word)}</strong>' be a better choice?
                """
                possibles = checker.candidates(word)
                if len(possibles) > 1:
                    template += """
                    Or one of:<br/>
                    <ul>
                    """
                    for t in possibles:
                        template += f"<li>{t}</li>\n"
                    template += '</ul>'
                res.detailedDescription = template
                results.append(res)
    return results

And in theory, that was that! But I’m pretty sure that my students (and everyone else) probably didn’t want to cut and paste that into the console every time they wanted to spell check a map. So, I looked at how to package this up for QGIS. I built a plugin (using the plugin builder tool), but then things got a little tricky as I can’t see any way for a plugin to add itself to the print layout rather than the main QGIS window (please let me know if it is possible), and it seemed unintuitive to make people press a button in one window to effect another one, besides the whole point of being a QgsAbstractValidityCheck was that the method is automatically run on print. So I didn’t need most of the plugin code or did I? On further thought I did, there is a need for some GUI as the user can pick which language they want to use in the spell check. pyspellcheck can spell check English, Spanish, French, Portuguese, German, Italian, Russian, Arabic, Basque, Latvian and Dutch (so if those are your language then please test this for me). I also thought that providing the option to supply a different to the default personal dictionary might be useful. So that made use of the dialog that pops up when you hit the plugin.

But it turns out you can’t register a class method as as a QgsAbstractValidityCheck since it gets confused when QGIS calls it later. So I had to move my checker method outside the plugin class. But then I couldn’t access the language and dictionary that was set in the GUI! Some more searching gave me the following code:

  _instance = plugins['qgis-spellcheck']
  checker = _instance.checker

Whereby I can pull out the named plugin and grab it’s spell checker, which was created in the plugin’s __init__ method. I seem to have a small issue that the user’s profile is not set when that runs which messes up where the personal dictionary is put (again if you know how to fix this let me know).

Future Work

Ideally, I’d like the spell checker to scan and highlight the text in the boxes as I typed but I fear that is beyond my understanding of the QGIS/Qt interface. Next highest on my wish list is for the list of spelling issues to be non-modal so I can cut and paste fixes into the text box, rather than having to memorise the correct spelling, close the window and then type it in (again answers on a github issue).

I’m sure all sorts of things will come up once people start using it, so as usual issues and PRs are welcome at https://github.com/ianturton/qgis-spellcheck.