# This file is a part of Lector, a Qt based ebook reader # Copyright (C) 2017-2019 BasioMeusPuga # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import json import logging import urllib.request from PyQt5 import QtWidgets, QtCore, QtGui logger = logging.getLogger(__name__) try: from PyQt5 import QtMultimedia multimedia_available = True except ImportError: error_string = 'QtMultimedia not found. Sounds will not play.' logger.error(error_string) multimedia_available = False from lector.resources import definitions class DefinitionsUI(QtWidgets.QDialog, definitions.Ui_Dialog): def __init__(self, parent): super(DefinitionsUI, self).__init__() self.setupUi(self) self._translate = QtCore.QCoreApplication.translate self.parent = parent self.previous_position = None self.setWindowFlags( QtCore.Qt.Popup | QtCore.Qt.FramelessWindowHint) radius = 15 path = QtGui.QPainterPath() path.addRoundedRect(QtCore.QRectF(self.rect()), radius, radius) try: mask = QtGui.QRegion(path.toFillPolygon().toPolygon()) self.setMask(mask) except TypeError: # Required for older versions of Qt pass self.definitionView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.app_id = 'bb7a91f9' self.app_key = 'fefacdf6775c347b52e9efa2efe642ef' self.root_url = 'https://od-api.oxforddictionaries.com:443/api/v1/inflections/' self.define_url = 'https://od-api.oxforddictionaries.com:443/api/v1/entries/' self.pronunciation_mp3 = None self.okButton.clicked.connect(self.hide) self.dialogBackground.clicked.connect(self.color_background) if multimedia_available: self.pronounceButton.clicked.connect(self.play_pronunciation) else: self.pronounceButton.setEnabled(False) def api_call(self, url, word): language = self.parent.settings['dictionary_language'] url = url + language + '/' + word.lower() req = urllib.request.Request(url) req.add_header('app_id', self.app_id) req.add_header('app_key', self.app_key) try: response = urllib.request.urlopen(req) if response.getcode() == 200: return_json = json.loads(response.read()) return return_json except Exception as e: this_error = f'API access error' logger.exception(this_error + f' {type(e).__name__} Arguments: {e.args}') self.parent.display_error_notification(None) return None def find_definition(self, word): word_root_json = self.api_call(self.root_url, word) if not word_root_json: logger.error('Word root json noped out: ' + word) self.set_text(word, None, None, True) return word_root = word_root_json['results'][0]['lexicalEntries'][0]['inflectionOf'][0]['id'] self.pronounceButton.setToolTip(f'Pronounce "{word_root}"') definition_json = self.api_call(self.define_url, word_root) if not definition_json: logger.error('Definition json noped out: ' + word_root) self.set_text(word, None, None, True) return definitions = {} for i in definition_json['results'][0]['lexicalEntries']: category = i['lexicalCategory'] try: self.pronunciation_mp3 = i['pronunciations'][0]['audioFile'] except KeyError: self.pronounceButton.setEnabled(False) this_sense = i['entries'][0]['senses'] for j in this_sense: try: this_definition = j['definitions'][0].capitalize() except KeyError: # The API also reports crossReferenceMarkers here this_definition = '<Not found>' try: definitions[category].add(this_definition) except KeyError: definitions[category] = set() definitions[category].add(this_definition) self.set_text(word, word_root, definitions) def set_text(self, word, word_root, definitions, nothing_found=False): html_string = '' # Word heading html_string += f'<h2><em><strong>{word}</strong></em></h2>\n' if nothing_found: nope_string = self._translate('DefinitionsUI', 'No definitions found in') language = self.parent.settings['dictionary_language'].upper() html_string += f'<p><em>{nope_string} {language}<em></p>\n' else: # Word root html_string += f'<p><em>Word root: <em>{word_root}</p>\n' # Definitions per category as an ordered list for i in definitions.items(): category = i[0] html_string += f'<p><strong>{category}</strong>:</p>\n<ol>\n' for j in i[1]: html_string += f'<li>{j}</li>\n' html_string += '</ol>\n' self.definitionView.setHtml(html_string) self.show() def color_background(self, set_initial=False): if set_initial: background = self.parent.settings['dialog_background'] else: self.previous_position = self.pos() self.parent.get_color() background = self.parent.settings['dialog_background'] # Calculate inverse color for the background so that # the text doesn't look blank r, g, b, alpha = background.getRgb() inv_average = 255 - (r + g + b) // 3 if 100 < inv_average < 150: inv_average = 255 foreground = QtGui.QColor( inv_average, inv_average, inv_average, alpha) self.setStyleSheet( "QDialog {{background-color: {0}}}".format(background.name())) self.definitionView.setStyleSheet( "QTextBrowser {{color:{0}; background-color: {1}}}".format( foreground.name(), background.name())) if not set_initial: self.show() def play_pronunciation(self): if not self.pronunciation_mp3 or not multimedia_available: return media_content = QtMultimedia.QMediaContent( QtCore.QUrl(self.pronunciation_mp3)) player = QtMultimedia.QMediaPlayer(self) player.setMedia(media_content) player.play() def showEvent(self, event): self.color_background(True) size = self.size() desktop_size = QtWidgets.QDesktopWidget().screenGeometry() top = (desktop_size.height() / 2) - (size.height() / 2) left = (desktop_size.width() / 2) - (size.width() / 2) self.move(left, top)