Beispiel #1
0
    def __init__(self, *args, is_offline, **kwargs):
        super().__init__(*args, **kwargs)
        self.is_offline = is_offline
        self.online_languages = tesseract_download.get_downloadable_languages()

        # Translators: label of a list control containing bookmarks
        wx.StaticText(self, -1, _("Tesseract Languages"))
        listPanel = sc.SizedPanel(self)
        listPanel.SetSizerType("horizontal")
        listPanel.SetSizerProps(expand=True, align="center")
        self.tesseractLanguageList = ImmutableObjectListView(
            listPanel,
            wx.ID_ANY,
            style=wx.LC_REPORT | wx.SUNKEN_BORDER,
            size=(500, -1))
        self.btnPanel = btnPanel = sc.SizedPanel(self, -1)
        btnPanel.SetSizerType("horizontal")
        btnPanel.SetSizerProps(expand=True)
        if not self.is_offline:
            # Translators: text of a button to add a language to Tesseract OCR Engine (best quality model)
            self.addBestButton = wx.Button(btnPanel, wx.ID_ANY,
                                           _("Download &Best Model"))
            # Translators: text of a button to add a language to Tesseract OCR Engine (fastest model)
            self.addFastButton = wx.Button(btnPanel, wx.ID_ANY,
                                           _("Download &Fast Model"))
            self.Bind(wx.EVT_BUTTON, self.onAdd, self.addFastButton)
            self.Bind(wx.EVT_BUTTON, self.onAdd, self.addBestButton)
        else:
            # Translators: text of a button to remove a language from Tesseract OCR Engine
            self.removeButton = wx.Button(btnPanel, wx.ID_REMOVE, _("&Remove"))
            self.Bind(wx.EVT_BUTTON, self.onRemove, id=wx.ID_REMOVE)
        self.tesseractLanguageList.Bind(wx.EVT_SET_FOCUS, self.onListFocus,
                                        self.tesseractLanguageList)
Beispiel #2
0
class SearchResultsPage(sc.SizedPanel):
    def __init__(self, parent, search_results, list_label):
        super().__init__(parent, -1)
        column_spec = (
            ColumnDefn(_("Snippet"), "left", 255, operator.attrgetter("snippet")),
            ColumnDefn(
                _("Title"), "center", 255, operator.attrgetter("document_title")
            ),
            ColumnDefn(_("Page"), "right", 120, lambda ins: ins.page_index + 1),
        )
        wx.StaticText(self, -1, list_label)
        self.result_list = ImmutableObjectListView(self, -1, columns=column_spec)
        self.result_list.set_objects(search_results, set_focus=False)
        self.result_list.Bind(
            wx.EVT_LIST_ITEM_ACTIVATED, self.onItemActivated, self.result_list
        )

    def onItemActivated(self, event):
        selected_result = self.result_list.get_selected()
        page = selected_result.page_index
        position = Page.get_text_start_position(
            selected_result.page_id, selected_result.snippet
        )
        uri = selected_result.document.uri.create_copy(
            openner_args=dict(page=page, position=position)
        )
        # Translators: spoken message
        speech.announce("Openning document...")
        sounds.navigation.play()
        EBookReader.open_document_in_a_new_instance(uri)
 def addControls(self, parent):
     # Translators: header of a group of controls in a dialog to view user's comments/highlights
     filterBox = make_sized_static_box(parent, _("Filter By"))
     self.filterPanel = AnnotationFilterPanel(
         filterBox,
         -1,
         annotator=self.annotator,
         filter_callback=self.onFilter,
         filter_by_book=not self.reader.ready,
     )
     # Translators: header of a group of controls in a dialog to view user's comments/highlights
     sortBox = make_sized_static_box(parent, _("Sort By"))
     sortPanel = sc.SizedPanel(sortBox, -1)
     sortPanel.SetSizerType("horizontal")
     sortPanel.SetSizerProps(expand=True)
     sort_options = [
         # Translators: text of a toggle button to sort comments/highlights list
         (_("Date"), AnnotationSortCriteria.Date),
         # Translators: text of a toggle button to sort comments/highlights list
         (_("Page"), AnnotationSortCriteria.Page),
     ]
     if not self.reader.ready:
         # Translators: text of a toggle button to sort comments/highlights list
         sort_options.append((_("Book"), AnnotationSortCriteria.Book))
     for bt_label, srt_criteria in sort_options:
         tglButton = wx.ToggleButton(sortPanel, -1, bt_label)
         self._sort_toggles[tglButton] = srt_criteria
         self.Bind(wx.EVT_TOGGLEBUTTON, self.onSortToggle, tglButton)
     # Translators: text of a toggle button to sort comments/highlights list
     self.sortMethodToggle = wx.ToggleButton(sortPanel, -1, _("Ascending"))
     wx.StaticText(parent, -1, self.Title)
     self.itemsView = ImmutableObjectListView(
         parent, id=wx.ID_ANY, style=wx.LC_REPORT | wx.SUNKEN_BORDER
     )
     self.itemsView.SetSizerProps(expand=True)
     self.buttonPanel = sc.SizedPanel(parent, -1)
     self.buttonPanel.SetSizerType("horizontal")
     # Translators: text of a button in a dialog to view comments/highlights
     wx.Button(self.buttonPanel, wx.ID_PREVIEW, _("&View..."))
     if self.can_edit:
         # Translators: text of a button in a dialog to view comments/highlights
         wx.Button(self.buttonPanel, wx.ID_EDIT, _("&Edit..."))
         self.Bind(wx.EVT_BUTTON, self.onEdit, id=wx.ID_EDIT)
     # Translators: text of a button in a dialog to view comments/highlights
     wx.Button(self.buttonPanel, wx.ID_DELETE, _("&Delete..."))
     # Translators: text of a button in a dialog to view comments/highlights
     exportButton = wx.Button(self.buttonPanel, -1, _("E&xport..."))
     self.Bind(wx.EVT_TOGGLEBUTTON, self.onSortMethodToggle, self.sortMethodToggle)
     self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.onItemClick, self.itemsView)
     self.Bind(wx.EVT_LIST_KEY_DOWN, self.onKeyDown, self.itemsView)
     self.Bind(wx.EVT_BUTTON, self.onView, id=wx.ID_PREVIEW)
     self.Bind(wx.EVT_BUTTON, self.onDelete, id=wx.ID_DELETE)
     self.Bind(wx.EVT_BUTTON, self.onExport, exportButton)
     self.on_filter_and_sort_state_changed()
     self.set_items()
Beispiel #4
0
 def addControls(self, parent):
     parent.SetSizerType("vertical")
     column_spec = (
         # Translators: title of a list control colum
         ColumnDefn(_("Error"), "left", 255, operator.itemgetter(0)),
         # Translators: title of a list control colum
         ColumnDefn(_("File Name"), "center", 255, operator.itemgetter(1)),
         # Translators: title of a list control colum
         ColumnDefn(_("Title"), "right", 255, operator.itemgetter(2)),
     )
     # Translators: label of a list control showing file copy errors
     wx.StaticText(parent, -1, _("Errors"))
     result_list = ImmutableObjectListView(parent, -1, columns=column_spec)
     reason = _("Failed to copy document")
     result_list.set_objects([(reason, *i) for i in self.info], set_focus=True)
Beispiel #5
0
 def __init__(self, parent, search_results, list_label):
     super().__init__(parent, -1)
     column_spec = (
         ColumnDefn(_("Snippet"), "left", 255, operator.attrgetter("snippet")),
         ColumnDefn(
             _("Title"), "center", 255, operator.attrgetter("document_title")
         ),
         ColumnDefn(_("Page"), "right", 120, lambda ins: ins.page_index + 1),
     )
     wx.StaticText(self, -1, list_label)
     self.result_list = ImmutableObjectListView(self, -1, columns=column_spec)
     self.result_list.set_objects(search_results, set_focus=False)
     self.result_list.Bind(
         wx.EVT_LIST_ITEM_ACTIVATED, self.onItemActivated, self.result_list
     )
 def addControls(self, parent):
     # Translators: label of a list control containing bookmarks
     wx.StaticText(parent, -1, _("Saved Bookmarks"))
     self.annotationsListCtrl = ImmutableObjectListView(parent, wx.ID_ANY)
     # Translators: text of a button to remove bookmarks
     wx.Button(parent, wx.ID_DELETE, _("&Remove"))
     self.Bind(
         wx.EVT_LIST_ITEM_ACTIVATED, self.onItemClick, self.annotationsListCtrl
     )
     self.Bind(wx.EVT_LIST_KEY_DOWN, self.onKeyDown, self.annotationsListCtrl)
     self.Bind(
         wx.EVT_LIST_END_LABEL_EDIT, self.onEndLabelEdit, self.annotationsListCtrl
     )
     self.Bind(wx.EVT_BUTTON, self.onDelete, id=wx.ID_DELETE)
     wx.FindWindowById(wx.ID_DELETE).Enable(False)
     self._populate_list()
Beispiel #7
0
 def addControls(self, parent):
     self.elementTypeRadio = EnumRadioBox(
         parent,
         -1,
         label=("Element Type"),
         choice_enum=ElementKind,
         majorDimension=0,
         style=wx.RA_SPECIFY_COLS,
     )
     self.elementListViewLabel = wx.StaticText(parent, -1, _("Elements"))
     self.elementListView = ImmutableObjectListView(
         parent,
         wx.ID_ANY,
         columns=[
             ColumnDefn(_("Name"), "left", 255, "name"),
         ],
     )
     self.Bind(wx.EVT_RADIOBOX, self.onElementTypeRadioSelected,
               self.elementTypeRadio)
     self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.onListItemActivated,
               self.elementListView)
     self.ElementInfo = namedtuple("ElementInfo", "type name  text_range")
     self.__element_Info_cache = {}
     self.populate_element_list(self.elementTypeRadio.GetSelectedValue())
     self.set_listview_label()
class AnnotationWithContentDialog(SimpleDialog):
    """View, edit, and manage notes and quotes."""

    @classmethod
    def column_defn(cls):
        return (
            # Translators: the title of a column in the comments/highlights list
            ColumnDefn("Excerpt", "left", 250, lambda a: a.content[:20]),
            # Translators: the title of a column in the comments/highlights list
            ColumnDefn("Section", "left", 200, "section_title"),
            # Translators: the title of a column in the comments/highlights list
            ColumnDefn("Page", "center", 150, lambda anot: anot.page_number + 1),
            # Translators: the title of a column in the comments/highlights list
            ColumnDefn(
                "Added", "right", 200, lambda a: format_datetime(a.date_created)
            ),
        )

    def __init__(self, reader, annotator_cls, *args, can_edit=False, **kwargs):
        self.reader = reader
        self.annotator_cls = annotator_cls
        self.annotator = self.annotator_cls(reader)
        self.can_edit = can_edit
        self.service = wx.GetApp().service_handler.get_service("annotation")
        self._filter_and_sort_state = FilterAndSortState.create_default(self.annotator)
        self._sort_toggles = {}
        super().__init__(*args, **kwargs)

    def addControls(self, parent):
        # Translators: header of a group of controls in a dialog to view user's comments/highlights
        filterBox = make_sized_static_box(parent, _("Filter By"))
        self.filterPanel = AnnotationFilterPanel(
            filterBox,
            -1,
            annotator=self.annotator,
            filter_callback=self.onFilter,
            filter_by_book=not self.reader.ready,
        )
        # Translators: header of a group of controls in a dialog to view user's comments/highlights
        sortBox = make_sized_static_box(parent, _("Sort By"))
        sortPanel = sc.SizedPanel(sortBox, -1)
        sortPanel.SetSizerType("horizontal")
        sortPanel.SetSizerProps(expand=True)
        sort_options = [
            # Translators: text of a toggle button to sort comments/highlights list
            (_("Date"), AnnotationSortCriteria.Date),
            # Translators: text of a toggle button to sort comments/highlights list
            (_("Page"), AnnotationSortCriteria.Page),
        ]
        if not self.reader.ready:
            # Translators: text of a toggle button to sort comments/highlights list
            sort_options.append((_("Book"), AnnotationSortCriteria.Book))
        for bt_label, srt_criteria in sort_options:
            tglButton = wx.ToggleButton(sortPanel, -1, bt_label)
            self._sort_toggles[tglButton] = srt_criteria
            self.Bind(wx.EVT_TOGGLEBUTTON, self.onSortToggle, tglButton)
        # Translators: text of a toggle button to sort comments/highlights list
        self.sortMethodToggle = wx.ToggleButton(sortPanel, -1, _("Ascending"))
        wx.StaticText(parent, -1, self.Title)
        self.itemsView = ImmutableObjectListView(
            parent, id=wx.ID_ANY, style=wx.LC_REPORT | wx.SUNKEN_BORDER
        )
        self.itemsView.SetSizerProps(expand=True)
        self.buttonPanel = sc.SizedPanel(parent, -1)
        self.buttonPanel.SetSizerType("horizontal")
        # Translators: text of a button in a dialog to view comments/highlights
        wx.Button(self.buttonPanel, wx.ID_PREVIEW, _("&View..."))
        if self.can_edit:
            # Translators: text of a button in a dialog to view comments/highlights
            wx.Button(self.buttonPanel, wx.ID_EDIT, _("&Edit..."))
            self.Bind(wx.EVT_BUTTON, self.onEdit, id=wx.ID_EDIT)
        # Translators: text of a button in a dialog to view comments/highlights
        wx.Button(self.buttonPanel, wx.ID_DELETE, _("&Delete..."))
        # Translators: text of a button in a dialog to view comments/highlights
        exportButton = wx.Button(self.buttonPanel, -1, _("E&xport..."))
        self.Bind(wx.EVT_TOGGLEBUTTON, self.onSortMethodToggle, self.sortMethodToggle)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.onItemClick, self.itemsView)
        self.Bind(wx.EVT_LIST_KEY_DOWN, self.onKeyDown, self.itemsView)
        self.Bind(wx.EVT_BUTTON, self.onView, id=wx.ID_PREVIEW)
        self.Bind(wx.EVT_BUTTON, self.onDelete, id=wx.ID_DELETE)
        self.Bind(wx.EVT_BUTTON, self.onExport, exportButton)
        self.on_filter_and_sort_state_changed()
        self.set_items()

    def get_items(self):
        getter_func = (
            self.annotator.get_for_book if self.reader.ready else self.annotator.get_all
        )
        return getter_func(
            self._filter_and_sort_state.filter_criteria,
            self._filter_and_sort_state.sort_criteria,
            self._filter_and_sort_state.asc,
        )

    def set_items(self, items=None):
        items = items or self.get_items()
        self.itemsView.set_columns(self.column_defn())
        self.itemsView.set_objects(items)
        self.itemsView.SetFocusFromKbd()
        if not self.itemsView.ItemCount:
            self.buttonPanel.Disable()

    def on_filter_and_sort_state_changed(self):
        self.set_items(self.get_items())
        self.sortMethodToggle.SetValue(self._filter_and_sort_state.asc)
        for btn, sort_criteria in self._sort_toggles.items():
            if self._filter_and_sort_state.sort_criteria is sort_criteria:
                btn.SetValue(True)
            else:
                btn.SetValue(False)

    def onFilter(self, book_id, tag, section_title, content):
        self._filter_and_sort_state.filter_criteria = AnnotationFilterCriteria(
            book_id=book_id, tag=tag, section_title=section_title, content_snip=content
        )
        self.on_filter_and_sort_state_changed()

    def onSortToggle(self, event):
        if event.IsChecked():
            event.GetEventObject().SetValue(False)
            self._filter_and_sort_state.sort_criteria = self._sort_toggles[
                event.GetEventObject()
            ]
        self.on_filter_and_sort_state_changed()

    def onSortMethodToggle(self, event):
        self._filter_and_sort_state.asc = event.IsChecked()
        self.on_filter_and_sort_state_changed()

    def go_to_item(self, item):
        self.reader.go_to_page(item.page_number)

    def onItemClick(self, event):
        item = self.itemsView.get_selected()
        if item is not None:
            self.Close()
            self.go_to_item(item)

    def view_or_edit(self, is_viewing=True):
        item = self.itemsView.get_selected()
        if item is None:
            return wx.Bell()
        editable = self.can_edit and not is_viewing
        dlg = ViewAndEditAnnotationDialog(
            self,
            # Translators: title of a dialog to view or edit a single comment/highlight
            title=_("Editing") if editable else _("View"),
            annotation=item,
            editable=editable,
        )
        with dlg:
            return dlg.ShowModal()

    def onView(self, event):
        self.view_or_edit(is_viewing=True)

    def onDelete(self, event):
        item = self.itemsView.get_selected()
        if item is None:
            return
        if (
            wx.MessageBox(
                # Translators: content of a message asking the user if they want to delete a comment/highlight
                _(
                    "This action can not be reverted.\r\nAre you sure you want to remove this item?"
                ),
                # Translators: title of a message asking the user if they want to delete a bookmark
                _("Delete Annotation?"),
                parent=self,
                style=wx.YES_NO | wx.ICON_WARNING,
            )
            == wx.YES
        ):
            self.annotator.delete(item.id)
            self.filterPanel.update_choices()
            self.set_items(self.get_items())

    def onEdit(self, event):
        if not self.can_edit:
            return
        updates = self.view_or_edit(is_viewing=False)
        if updates:
            item = self.itemsView.get_selected()
            self.annotator.update(item.id, **updates)
            self.filterPanel.update_choices()
            self.set_items()

    def onKeyDown(self, event):
        item = self.itemsView.get_selected()
        if item is None:
            return
        kcode = event.GetKeyCode()
        if kcode == wx.WXK_DELETE:
            self.onDelete(event)
        elif kcode == wx.WXK_F6:
            self.filterPanel.SetFocus()
        elif kcode == wx.WXK_F2:
            self.edit_tags(item)
        elif wx.GetKeyState(wx.WXK_CONTROL) and (kcode == 67):
            try:
                if get_clipboard_text() != item.content:
                    copy_to_clipboard(item.content)
                    sounds.clipboard.play()
            except:
                log.exception(
                    "Failed to copy annotation text to the clipboard", evc_info=True
                )
        event.Skip()

    def edit_tags(self, item):
        new_tags = self.service.view.get_text_from_user(
            # Translators: title of a dialog that allows the user to edit the tag set of a comment/highlight
            title=_("Edit Tags"),
            # Translators: label of an edit control that allows the user to edit the tag set of a comment/highlight
            label=_("Tags"),
            value=" ".join(item.tags),
        )
        if new_tags is not None:
            self.annotator.update(item.id, tags=[t.strip() for t in new_tags.split()])
            self.filterPanel.update_choices()

    def onExport(self, event):
        items = tuple(self.get_items())
        # Translators: title of a dialog that allows the user to customize
        # how comments/highlights are exported
        with ExportNotesDialog(parent=self, title=_("Export Options")) as dlg:
            retval = dlg.ShowModal()
            if retval is None:
                return wx.Bell()
            renderer_cls, open_after_export, export_options = retval
            self.export_items(renderer_cls, items, export_options, open_after_export)

    def export_items(self, renderer_cls, items, export_options, open_after_export):
        renderer = renderer_cls(
            items, export_options, self._filter_and_sort_state.filter_criteria
        )
        resulting_file = renderer.render_to_file()
        if open_after_export:
            wx.LaunchDefaultApplication(resulting_file)
class BookmarksViewer(SimpleDialog):
    """A dialog to view the bookmarks of the current book."""

    def __init__(self, reader, annotator, *args, **kwargs):
        self.view = reader.view
        self.reader = reader
        self.annotator = annotator(self.reader)
        # Translators: label for unnamed bookmarks shown
        # when editing a single bookmark which has no name
        self._unamed_bookmark_title = _("[Unnamed Bookmark]")
        super().__init__(*args, **kwargs)

    def addControls(self, parent):
        # Translators: label of a list control containing bookmarks
        wx.StaticText(parent, -1, _("Saved Bookmarks"))
        self.annotationsListCtrl = ImmutableObjectListView(parent, wx.ID_ANY)
        # Translators: text of a button to remove bookmarks
        wx.Button(parent, wx.ID_DELETE, _("&Remove"))
        self.Bind(
            wx.EVT_LIST_ITEM_ACTIVATED, self.onItemClick, self.annotationsListCtrl
        )
        self.Bind(wx.EVT_LIST_KEY_DOWN, self.onKeyDown, self.annotationsListCtrl)
        self.Bind(
            wx.EVT_LIST_END_LABEL_EDIT, self.onEndLabelEdit, self.annotationsListCtrl
        )
        self.Bind(wx.EVT_BUTTON, self.onDelete, id=wx.ID_DELETE)
        wx.FindWindowById(wx.ID_DELETE).Enable(False)
        self._populate_list()

    def getButtons(self, parent):
        btnsizer = wx.StdDialogButtonSizer()
        # Translators: the label of a button to close a dialog
        btnsizer.AddButton(wx.Button(self, wx.ID_CANCEL, _("&Close")))
        btnsizer.Realize()
        return btnsizer

    def _populate_list(self, focus_target=0):
        annotations = self.annotator.get_for_book()
        column_defn = [
            ColumnDefn(
                # Translators: the title of a column in the bookmarks list
                _("Name"),
                "left",
                250,
                lambda bk: bk.title or self._unamed_bookmark_title,
            ),
            ColumnDefn(
                # Translators: the title of a column in the bookmarks list
                _("Page"),
                "center",
                150,
                lambda bk: bk.page_number + 1,
            ),
        ]
        if self.reader.document.has_toc_tree():
            # Translators: the title of a column in the bookmarks list
            column_defn.append(ColumnDefn(_("Section"), "left", 250, "section_title"))
        self.annotationsListCtrl.set_columns(column_defn)
        self.annotationsListCtrl.set_objects(annotations)
        self.FindWindowById(wx.ID_DELETE).Enable(len(annotations))

    def onItemClick(self, event):
        item = self.annotationsListCtrl.get_selected()
        if item is None:
            return
        self.reader.go_to_page(item.page_number)
        self.Close()
        wx.CallAfter(self.parent.contentTextCtrl.SetFocusFromKbd)
        wx.CallAfter(self.parent.contentTextCtrl.SetInsertionPoint, item.position)

    def onKeyDown(self, event):
        item = self.annotationsListCtrl.get_selected()
        if item is None:
            return
        selected_idx = self.annotationsListCtrl.GetFocusedItem()
        kcode = event.GetKeyCode()
        if kcode == wx.WXK_F2:
            editCtrl = self.annotationsListCtrl.EditLabel(selected_idx)
            if (
                self.annotationsListCtrl.GetItemText(selected_idx)
                == self._unamed_bookmark_title
            ):
                editCtrl.SetValue("")
        elif kcode == wx.WXK_DELETE:
            self.onDelete(event)
        event.Skip()

    def onEndLabelEdit(self, event):
        newTitle = event.GetLabel()
        if newTitle != self._unamed_bookmark_title:
            self.annotator.update(
                item_id=self.annotationsListCtrl.get_selected().id, title=newTitle
            )

    def onDelete(self, event):
        from . import AnnotationService

        item = self.annotationsListCtrl.get_selected()
        if item is None:
            return
        if (
            wx.MessageBox(
                # Translators: content of a message asking the user if they want to delete a bookmark
                _(
                    "This action can not be reverted.\r\nAre you sure you want to remove this bookmark?"
                ),
                # Translators: title of a message asking the user if they want to delete a bookmark
                _("Remove Bookmark?"),
                parent=self,
                style=wx.YES_NO | wx.ICON_WARNING,
            )
            == wx.YES
        ):
            page_number, pos = item.page_number, item.position
            self.annotator.delete(item.id)
            self._populate_list()
            if page_number == self.reader.current_page:
                AnnotationService.style_bookmark(self.Parent, pos, enable=False)
Beispiel #10
0
class TesseractLanguagePanel(sc.SizedPanel):
    def __init__(self, *args, is_offline, **kwargs):
        super().__init__(*args, **kwargs)
        self.is_offline = is_offline
        self.online_languages = tesseract_download.get_downloadable_languages()

        # Translators: label of a list control containing bookmarks
        wx.StaticText(self, -1, _("Tesseract Languages"))
        listPanel = sc.SizedPanel(self)
        listPanel.SetSizerType("horizontal")
        listPanel.SetSizerProps(expand=True, align="center")
        self.tesseractLanguageList = ImmutableObjectListView(
            listPanel,
            wx.ID_ANY,
            style=wx.LC_REPORT | wx.SUNKEN_BORDER,
            size=(500, -1))
        self.btnPanel = btnPanel = sc.SizedPanel(self, -1)
        btnPanel.SetSizerType("horizontal")
        btnPanel.SetSizerProps(expand=True)
        if not self.is_offline:
            # Translators: text of a button to add a language to Tesseract OCR Engine (best quality model)
            self.addBestButton = wx.Button(btnPanel, wx.ID_ANY,
                                           _("Download &Best Model"))
            # Translators: text of a button to add a language to Tesseract OCR Engine (fastest model)
            self.addFastButton = wx.Button(btnPanel, wx.ID_ANY,
                                           _("Download &Fast Model"))
            self.Bind(wx.EVT_BUTTON, self.onAdd, self.addFastButton)
            self.Bind(wx.EVT_BUTTON, self.onAdd, self.addBestButton)
        else:
            # Translators: text of a button to remove a language from Tesseract OCR Engine
            self.removeButton = wx.Button(btnPanel, wx.ID_REMOVE, _("&Remove"))
            self.Bind(wx.EVT_BUTTON, self.onRemove, id=wx.ID_REMOVE)
        self.tesseractLanguageList.Bind(wx.EVT_SET_FOCUS, self.onListFocus,
                                        self.tesseractLanguageList)

    def populate_list(self, set_focus=True):
        installed_languages = [(True, info) for info in sorted(
            TesseractOcrEngine.get_recognition_languages(),
            key=lambda l: l.english_name,
        )]
        if self.is_offline:
            languages = installed_languages
        else:
            languages = []
            added_locale_infos = set(l[1] for l in installed_languages)
            for lang in self.online_languages:
                loc_info = LocaleInfo.from_three_letter_code(lang)
                if loc_info in added_locale_infos:
                    continue
                else:
                    languages.append((False, loc_info))
                    added_locale_infos.add(loc_info)
        column_defn = [
            ColumnDefn(
                # Translators: the title of a column in the Tesseract language list
                _("Language"),
                "left",
                450,
                lambda lang: lang[1].description,
            ),
            ColumnDefn(
                # Translators: the title of a column in the Tesseract language list
                _("Installed"),
                "center",
                100,
                lambda lang: _("Yes") if lang[0] else _("No"),
            ),
        ]
        self.tesseractLanguageList.set_columns(column_defn)
        if set_focus:
            self.tesseractLanguageList.set_objects(languages, focus_item=0)
        else:
            self.tesseractLanguageList.set_objects(languages, set_focus=False)
        # Maintain the state of the list
        if not any(languages):
            if not self.is_offline:
                self.addBestButton.Enable(False)
                self.addFastButton.Enable(False)
            else:
                self.removeButton.Enable(False)
            self.btnPanel.Enable(False)

    def onAdd(self, event):
        if (selected := self.tesseractLanguageList.get_selected()) is None:
            return
        lang = selected[1]
        variant = "best" if event.GetEventObject(
        ) == self.addBestButton else "fast"
        lang_code = lang.given_locale_name
        if lang_code not in self.online_languages:
            log.debug(f"Could not find download info for language {lang_code}")
            return
        target_file = tesseract_download.get_language_path(lang_code)
        if target_file.exists():
            msg = wx.MessageBox(
                # Translators: content of a messagebox
                _("A version of the selected language model already exists.\n"
                  "Are you sure you want to replace it."),
                # Translators: title of a message box
                _("Confirm"),
                style=wx.YES_NO | wx.ICON_WARNING,
                parent=self,
            )
            if msg == wx.NO:
                return
        try:
            target_file.unlink(missing_ok=True)
        except:
            return
        progress_dlg = RobustProgressDialog(
            wx.GetApp().mainFrame,
            # Translators: title of a progress dialog
            _("Downloading Language"),
            # Translators: content of a progress dialog
            _("Getting download information..."),
            maxvalue=100,
            can_hide=True,
            can_abort=True,
        )
        threaded_worker.submit(
            tesseract_download.download_language,
            lang_code,
            variant,
            target_file,
            progress_dlg,
        ).add_done_callback(lambda future: wx.CallAfter(
            self._after_download_language, progress_dlg, future))