class MainAppliction(QApplication): def __init__(self, *argv): print('Raw cmdline:', argv) super(MainAppliction, self).__init__(*argv) self.args = {'source': argv[0][1]} params = map(lambda pair: tuple(pair.split(':')), argv[0][2:]) params = list(params) print('Params:', params) self.args['params'] = dict(params) print('Command-line:', self.args) self.web_view = QWebEngineView() self.authorizer = app_auth.Authorizer(self.web_view) self.extractor = app_extractor.Extractor(self.web_view) self.destination = app_destinations.destinations['local']() self.authorizer.auth_success.connect(self.on_auth_result) self.authorizer.auth_fail.connect(self.on_auth_result) # self.authorizer.auth_in_view.connect("""!!!<some button on_click here>!!!""") self.extractor.on_data.connect(self.on_extract_data) self.extractor.on_finish.connect(self.on_extract_finish) self.destination.auth({'dest_file': app_config.local_filename_templ}) self.attachments = [] @Core.pyqtSlot(dict, name='on_auth_result') def on_auth_result(self, result: dict): print(f'Received auth result: {result}') self.extractor.set_source(self.args['source']) self.extractor.set_parser_params(**self.args['params']) self.extractor.execute_in_view(self.web_view) @Core.pyqtSlot(list, name='on_extract_data') def on_extract_data(self, attachments: list): print('MainApplication: received more {}'.format(len(attachments))) self.attachments.extend(attachments) # ls = map(json.dumps, ls) # self.text_view.show() # doc = Gui.QTextDocument(self.text_view) # doc.setPlainText('\n'.join(ls)) # self.text_view.setDocument(doc) @Core.pyqtSlot(name='on_extract_finish') def on_extract_finish(self): print('MainApplication: attachment list transmitted') self.web_view.close() with open('dest/im_photos.json', 'w') as out: print(json.dumps(self.attachments), file=out) for counter, attach in enumerate(self.attachments): print('Saving {} / {}'.format(counter + 1, len(self.attachments))) self.destination.upload(attach) print('MainApplication: all files saved') self.quit()
class WebScraper(QApplication): def __init__(self, scraper=DummyScraper()): super().__init__([]) self._scraper = scraper self.view = QWebEngineView() self.view.loadFinished.connect(self._on_load_finished) def _get_headers(self): """Return the request headers dictionary from headers.json""" headers = {} try: with open('headers.json') as h: headers = json.load(h) except: pass finally: return headers def _callback(self, page_src): """Call the scrape function on page source and close the view""" self._scraper.scrape(page_src) self.view.close() def _on_load_finished(self, successfully): """Retrieve the page source a pass it to the default callback""" if successfully: self.view.page().toHtml(self._callback) else: print('Fail to load:', self.view.url().toString()) self.view.close() def load(self, url): """Load the url and sets the request headers""" request = QWebEngineHttpRequest(QUrl(url)) headers = self._get_headers() for h, v in headers.items(): request.setHeader(bytearray(str(h), 'utf-8'), bytearray(str(v), 'utf-8')) self.view.load(request)
class AwBrowser(QDialog): """ Customization and configuration of a web browser to run within Anki """ _parent = None _fields = [] _selectedListener = None _web = None _urlInfo = None def __init__(self, myParent): QDialog.__init__(self, myParent) self._parent = myParent self.setupUI() def setupUI(self): self.setWindowTitle('Anki :: Web Browser Addon') self.setGeometry(450, 200, 800, 450) self.setMinimumWidth (640) self.setMinimumHeight(450) mainLayout = QVBoxLayout() mainLayout.setContentsMargins(0,0,0,0) mainLayout.setSpacing(0) self.setLayout(mainLayout) topWidget = QtWidgets.QWidget(self) topWidget.setFixedHeight(50) topLayout = QtWidgets.QHBoxLayout(topWidget) topLayout.setObjectName("topLayout") lbSite = QtWidgets.QLabel(topWidget) lbSite.setObjectName("label") lbSite.setText("Website: ") topLayout.addWidget(lbSite) self._itAddress = QtWidgets.QLineEdit(topWidget) self._itAddress.setObjectName("itSite") topLayout.addWidget(self._itAddress) cbGo = QtWidgets.QCommandLinkButton(topWidget) cbGo.setObjectName("cbGo") cbGo.setFixedSize(30, 30) topLayout.addWidget(cbGo) # cbImport = QtWidgets.QCommandLinkButton(topWidget) # cbImport.setObjectName("cbImport") # cbImport.setFixedSize(30, 30) # topLayout.addWidget(cbImport) self._loadingBar = QtWidgets.QProgressBar(topWidget) self._loadingBar.setFixedWidth(100) self._loadingBar.setProperty("value", 100) self._loadingBar.setObjectName("loadingBar") topLayout.addWidget(self._loadingBar) mainLayout.addWidget(topWidget) self._web = QWebEngineView(self) self._web.contextMenuEvent = self.contextMenuEvent self._web.page().loadStarted.connect(self.onStartLoading) self._web.page().loadFinished.connect(self.onLoadFinish) self._web.page().loadProgress.connect(self.onProgress) self._web.page().urlChanged.connect(self.onPageChange) cbGo.clicked.connect(self._goToAddress) mainLayout.addWidget(self._web) if cfg.getConfig().browserAlwaysOnTop: self.setWindowFlags(Qt.WindowStaysOnTopHint) def formatTargetURL(self, website: str, query: str = ''): return website.format(urllib.parse.quote(query, encoding='utf8')) #encode('utf8', 'ignore') def open(self, website, query: str): """ Loads a given page with its replacing part with its query, and shows itself """ target = self.formatTargetURL(website, query) self._web.load(QUrl( target )) self._itAddress.setText(target) self.show() return self._web def unload(self): try: self._web.setHtml(BLANK_PAGE) except (RuntimeError) as err: pass def onClose(self): self._parent = None self._web.close() self.close() def onStartLoading(self): self._loadingBar.setProperty("value", 1) def onProgress(self, prog): self._loadingBar.setProperty("value", prog) def onLoadFinish(self, result): self._loadingBar.setProperty("value", 100) if not result: Feedback.showInfo('Error loading page') Feedback.log('Error on loading page! ', result) def _goToAddress(self): self._web.load(QUrl( self._itAddress.text() )) self._web.show() def onPageChange(self, url): self._itAddress.setText(url.toString()) def welcome(self): self._web.setHtml(WELCOME_PAGE) self.show() # ------------------------------------ Menu --------------------------------------- def _makeMenuAction(self, field, value, isLink): """ Creates correct operations for the context menu selection. Only with lambda, it would repeat only the last element """ return lambda: self._selectedListener.handleSelection(field, value, isLink) def contextMenuEvent(self, evt): """ Handles the context menu in the web view. Shows and handle options (from field list), only if in edit mode. """ if not (self._fields and self._selectedListener): return isLink = False value = None if self._web.selectedText(): isLink = False value = self._web.selectedText() else: if (self._web.page().contextMenuData().mediaType() == QWebEngineContextMenuData.MediaTypeImage and self._web.page().contextMenuData().mediaUrl()): isLink = True value = self._web.page().contextMenuData().mediaUrl() if not value: return self.createCtxMenu(value, isLink, evt) def createCtxMenu(self, value, isLink, evt): 'Creates and configures the menu itself' m = QMenu(self) sub = QMenu(Label.BROWSER_ASSIGN_TO, m) m.setTitle(Label.BROWSER_ASSIGN_TO) for index, label in self._fields.items(): act = QAction(label, m, triggered=self._makeMenuAction(index, value, isLink)) sub.addAction(act) m.addMenu(sub) action = m.exec_(self.mapToGlobal(evt.pos())) def load(self, qUrl): self._web.load(qUrl) # ----------------- getter / setter ------------------- def setFields(self, fList): self._fields = fList def setSelectionListener(self, value): self._selectedListener = value
class MainWin(QtWidgets.QWidget, Ui_Dialog): ALPHABET = ALPHABET URL = '' MODE = 'SEARCH' ASKING = 'WORD' WORD = None CODE = 0 MORE = 0 LENGTH = 60 CONTAIN = 0 COMMANDING = 1 WORDLIST_NUM = 0 HISTORY_NUM = 0 SEARCH_CONTENT = '' RELOAD_CONTENT = '%d-%d' % (ALPHABET.words[0].num, ALPHABET.words[-1].num) EMPTY_MODEL = QtGui.QStandardItemModel() def __init__(self): super(MainWin, self).__init__() self.setupUi(self) if IS_WINDOWS: x, y, width, height = self.button_show_wordlist.geometry().getRect( ) self.button_show_wordlist.setGeometry( QtCore.QRect(x + 5, y + 4, width - 10, height - 11)) x, y, width, height = self.button_quit.geometry().getRect() self.button_quit.setGeometry( QtCore.QRect(x + 6, y + 4, width - 10, height - 11)) x, y, width, height = self.button_help.geometry().getRect() self.button_help.setGeometry( QtCore.QRect(x + 6, y + 4, width - 10, height - 11)) x, y, width, height = self.button_clear.geometry().getRect() self.button_clear.setGeometry( QtCore.QRect(x + 8, y + 4, width - 14, height - 11)) x, y, width, height = self.combo_box_asking.geometry().getRect() self.combo_box_asking.setGeometry( QtCore.QRect(x + 3, y + 4, width - 6, height - 9)) x, y, width, height = self.combo_box_mode.geometry().getRect() self.combo_box_mode.setGeometry( QtCore.QRect(x + 3, y + 4, width - 6, height - 9)) x, y, width, height = self.button_reload.geometry().getRect() self.button_reload.setGeometry( QtCore.QRect(x + 8, y + 4, width - 10, height - 9)) self.initializing() self.wordlist_show() self.web_viewer = QWebEngineView() self.web_viewer.setObjectName('web_viewer') # self.channel = QWebChannel(self.web_viewer) # self.web_viewer.setGeometry(QtCore.QRect(800, 20, 360, 450)) self.button_show_wordlist.clicked.connect(self.wordlist_click) self.button_help.clicked.connect(self.show_help) self.button_clear.clicked.connect(self.clear) self.console.returnPressed.connect(self.console_operate) self.check_code.stateChanged.connect(self.code_update) self.check_more.stateChanged.connect(self.more_info) self.combo_box_asking.currentIndexChanged.connect(self.asking_update) self.combo_box_mode.currentIndexChanged.connect(self.mode_update) self.search_box.textChanged.connect(self.search) self.button_reload.clicked.connect(self.wordlist_reload) _translate = QCoreApplication.translate self.reload_selection_box.setPlaceholderText( _translate("Dialog", self.RELOAD_CONTENT)) self.show() def code_update(self): self.CODE = self.check_code.checkState() def mode_update(self): self.MODE = self.combo_box_mode.currentText() self.console_show_history.append('MODE changed into %s' % self.MODE) def asking_update(self): self.ASKING = self.combo_box_asking.currentText() self.console_show_history.append('ASKING changed into %s' % self.ASKING) def more_info(self): self.MORE = self.check_more.checkState() if self.MORE: # self.resize(1200, 562) if self.WORD is not None: self.URL = 'http://www.youdao.com/w/eng/%s' % self.WORD.word.lower( ) if self.URL: self.web_viewer.setUrl(QUrl(self.URL)) self.web_viewer.show() else: # self.resize(770, 562) self.web_viewer.close() pass def initializing(self): self.console_show_history.append('Initial mode is SEARCH MODE!') self.console_show_history.append('You can input word or num!') self.console_show_history.append( 'Input `help` for more commands available!') def search_words(self, content): result = [] try: temp = int(content) if self.CONTAIN: for each in self.ALPHABET.words: if content in '%03d' % each.num: result.append(each) return result else: for each in self.ALPHABET.words: if content == ('%03d' % each.num)[:len(content)]: result.append(each) return result except: content = content.lower() if self.CONTAIN: for each in self.ALPHABET.words: if content in each.word.lower(): result.append(each) return result else: for each in self.ALPHABET.words: if content == each.word[:len(content)].lower(): result.append(each) return result def search(self): self.SEARCH_CONTENT = self.search_box.text() if self.SEARCH_CONTENT: if self.button_show_wordlist.text() == 'HIDE': self.wordlist_show(self.search_words(self.SEARCH_CONTENT)) else: if self.button_show_wordlist.text() == 'HIDE': self.wordlist_show() def wordlist_reload(self): _translate = QCoreApplication.translate self.RELOAD_CONTENT = self.reload_selection_box.text() try: left, right = map(int, self.RELOAD_CONTENT.split('-')) left, right = min(left, right), max(left, right) READ_IN = read_in(PATH) self.ALPHABET = Alphabet([ Word(READ_IN.loc[i]) for i in range(len(READ_IN)) if left <= int(READ_IN.loc[i]['#']) <= right ]) del READ_IN self.wordlist_show() except: self.reload_selection_box.setText(_translate("Dialog", '')) def wordlist_click(self): content = self.button_show_wordlist.text() if content == 'SHOW': if self.SEARCH_CONTENT: self.wordlist_show(self.search_words(self.SEARCH_CONTENT)) else: self.wordlist_show() if content == 'HIDE': self.wordlist_hide() def wordlist_show(self, words=None): if words is None: words = self.ALPHABET.words wordlist_model = QtGui.QStandardItemModel(0, 2, self) wordlist_model.setHeaderData(0, Qt.Horizontal, '#') wordlist_model.setHeaderData(1, Qt.Horizontal, 'WORD') self.wordlist.setModel(wordlist_model) self.wordlist.setColumnWidth(0, 40) self.wordlist.setColumnWidth(1, 30) self.WORDLIST_NUM = 0 if words: for each in words: self.add_data(wordlist_model, '%03d' % each.num, each.word) _translate = QCoreApplication.translate self.button_show_wordlist.setText(_translate("Dialog", "HIDE")) def wordlist_hide(self): self.wordlist.setModel(self.EMPTY_MODEL) _translate = QCoreApplication.translate self.button_show_wordlist.setText(_translate("Dialog", "SHOW")) def add_data(self, model, num, word): model.insertRow(self.WORDLIST_NUM) model.setData(model.index(self.WORDLIST_NUM, 0), num) model.setData(model.index(self.WORDLIST_NUM, 1), word) self.WORDLIST_NUM += 1 def show_help(self): self.console_show_history.append(''.center(self.LENGTH, '-')) self.console_show_history.append('help'.ljust(7) + '\t->\t' + 'Help information') self.console_show_history.append('clear'.ljust(7) + '\t->\t' + 'Clear all the history') self.console_show_history.append('mode'.ljust(7) + '\t->\t' + 'Show or change the mode') self.console_show_history.append(''.ljust(7) + '\t\t Example: ' + 'mode random') self.console_show_history.append('show'.ljust(7) + '\t->\t' + 'Show the word list') self.console_show_history.append('hide'.ljust(7) + '\t->\t' + 'Hide the word list') self.console_show_history.append('code'.ljust(7) + '\t->\t' + 'Show more info') self.console_show_history.append('quit'.ljust(7) + '\t->\t' + 'Exit the program') self.console_show_history.append(''.center(self.LENGTH, '-')) def clear(self): self.HISTORY_NUM = 0 self.WORD = None self.console_show_history.clear() self.info_clear() self.label_info.setText('') def console_operate(self): def read(content): content = content.lower() if self.WORD is None: if content == 'help': self.show_help() elif content == 'clear': self.clear() elif content[:4] == 'mode': mode = str_process(content[4:]) if not mode: mode_change() elif mode in ['s', 'sear', 'search']: mode_change('SEARCH') elif mode in ['r', 'rand', 'random']: mode_change('RANDOM') elif mode in ['o', 'ord', 'order']: mode_change('ORDER') elif content == 'show': self.wordlist_show() self.console_show_history.append('Word list showed') elif content == 'hide': self.wordlist_hide() self.console_show_history.append('word list hid') elif content == 'quit': self.close() elif content[:6] == 'asking': asking = str_process(content[6:]) if not asking: asking_change() elif asking == 'word': asking_change('WORD') elif asking in ['def', 'definition']: asking_change('DEFINITION') elif asking in ['sam', 'sample']: asking_change('SAMPLE') elif asking in ['thes', 'thesaurus']: asking_change('THESAURUS') elif self.MODE == 'SEARCH': try: content = int(content) except: pass self.WORD = find_word(content) if self.WORD is None: self.console_show_history.append('Undefined') else: ask() elif self.MODE == 'RANDOM': # TODO self.WORD = self.ALPHABET.get_random() ask() elif self.MODE == 'ORDER': self.WORD = self.ALPHABET.get_next() ask() else: self.console_show_history.append('Undefined') else: if self.ASKING in ['WORD', 'SAMPLE', 'THESAURUS']: search_w(self.WORD, content) if self.ASKING == 'DEFINITION': search_q(self.WORD, content) self.WORD = None self.label_info.setText('') def ask(): self.info_clear() if self.ASKING == 'WORD': self.label_def_show.setText(self.WORD.html_definition()) self.label_info.setText('[Word required]') if self.ASKING == 'DEFINITION': self.label_word_show.setText(self.WORD.word) self.label_info.setText('Definition of [%s] required' % self.WORD.word) if self.ASKING == 'SAMPLE': self.label_samp_show.setText(self.WORD.html_sample_hollow()) self.label_info.setText('[Word required]') if self.ASKING == 'THESAURUS': thesaurus = self.WORD.get_random_html_thesaurus() self.label_thesaurus_show.setText(thesaurus) if thesaurus == 'thesaurus NOT available': self.label_info.setText('[Word required]') else: self.label_info.setText( '[Word has the same meaning of [%s] required]' % thesaurus) def asking_change(asking=''): if asking: self.ASKING = asking self.combo_box_asking.setCurrentIndex({ 'WORD': 0, 'DEFINITION': 1, 'SAMPLE': 2, 'THESAURUS': 3 }[asking]) else: self.console_show_history.append('ASKING now is %s' % self.ASKING) def mode_change(mode=''): if mode: self.MODE = mode self.combo_box_mode.setCurrentIndex({ 'SEARCH': 0, 'RANDOM': 1, 'ORDER': 2 }[mode]) else: self.console_show_history.append('MODE now is %s' % self.MODE) def search_q(word, definition): self.console_show_history.append(''.center(self.LENGTH, '-')) if str_process(word.definition) == str_process(definition): self.console_show_history.append( '<html>' '<span style=" font-weight:600; color:#ff0000;">' '√' '</span></html>') else: self.console_show_history.append( '<html>' '<span style=" font-weight:600; color:#ff0000;">' '×' '</span></html>') self.console_show_history.append( '↓ Your definition ↓\n%s' % definition) self.console_show_history.append( '%s\n↑ Real definition ↑' % word.definition) self.console_show_history.append(''.center(self.LENGTH, '-')) self.info_show(word) def search_w(word, w): self.console_show_history.append(''.center(self.LENGTH, '-')) if str_process(word.word) == str_process(w): self.console_show_history.append( '<html>' '<span style=" font-weight:600; color:#ff0000;">' '√' '</span></html>') else: self.console_show_history.append( '<html>' '<span style=" font-weight:600; color:#ff0000;">' '×' '</span></html>') self.console_show_history.append( '↓ Your word ↓\n%s' % w) self.console_show_history.append( '%s\n↑ Real word ↑' % word.word) self.console_show_history.append(''.center(self.LENGTH, '-')) self.info_show(word) self.HISTORY_NUM += 1 text = self.console.text() self.console.setText('') if self.CODE: self.console_show_history.append('[%d]>>> ' % self.HISTORY_NUM + text) read(text) def keyPressEvent(self, QKeyEvent): if QKeyEvent.key() == Qt.Key_Escape: self.close() def info_show(self, word): self.label_word_show.setText(word.word) self.label_def_show.setText(word.html_definition()) self.label_samp_show.setText(word.html_sample()) self.label_thesaurus_show.setText(word.html_thesaurus()) def info_clear(self): self.label_word_show.setText('') self.label_def_show.setText('') self.label_samp_show.setText('') self.label_thesaurus_show.setText('')
class MyWindow_v2(QtWidgets.QMainWindow): def __init__(self): super(MyWindow_v2, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) cur_dtime = datetime.now() self.fname = None self.current_date = str(cur_dtime.date()) list_theory = [ self.ui.theoryCombobox.itemText(i) for i in range(self.ui.theoryCombobox.count()) ] self.liquids = {item: [] for item in list_theory} self.current_index = 0 self.image_index = None self.__setupUI() self.result = [] @pyqtSlot() def __setupUI(self): # add WebView widget self.web = QWebView() self.web.setUrl(QUrl(THEORY)) self.ui.gridLayout_5.addWidget(self.web) # add setting widget self.Form = QtWidgets.QWidget() self.settings_ui = setting_ui() self.settings_ui.setupUi(self.Form) self.ui.gridLayout_5.addWidget(self.Form) self.Form.close() # add result widget self.Result = QtWidgets.QWidget() self.result_ui = result_ui() self.result_ui.setupUi(self.Result) self.ui.resultGridLayout.addWidget(self.Result) self.Result.close() # action menu clicked self.ui.actionSave_Result.triggered.connect(self.save_result) self.ui.actionOpenProject.triggered.connect(self.load_result) self.ui.actionNew.triggered.connect(self.new) self.ui.actionExit.triggered.connect(self.exit) self.ui.theoryButton.setEnabled(False) self.ui.theoryCombobox.setEnabled(False) self.ui.tempCombobox.setCurrentIndex(2) self.ui.theoryButton.clicked.connect(self.__theory_button_checked) self.ui.settingsButton.clicked.connect(self.__settings_button_checked) self.ui.measurmentButton.clicked.connect( self.__measurment_buttton_checked) self.ui.theoryCombobox.currentTextChanged.connect(self.__theory_chose) self.ui.tempCombobox.currentTextChanged.connect(self.__fullfill_table) self.settings_ui.calculateButton.clicked.connect( self.__collect_data_to_process) self.settings_ui.calculateButton.setEnabled(False) self.settings_ui.calculateButton.setStyleSheet( "color: rgb(255, 0, 0);") self.settings_ui.addliquidButton.clicked.connect( self.__add_liquid_button_clicked) self.result_ui.resultTable.setColumnCount(4) self.result_ui.resultTable.setHorizontalHeaderLabels( HORIZONTAL_HEADER_RESULT) self.result_ui.resultTable.setRowCount(5) @pyqtSlot() def __theory_button_checked(self): self.Form.close() self.Result.close() self.web.show() self.resize(1246, 564) self.ui.theoryCombobox.setEnabled(False) self.ui.theoryButton.setEnabled(False) self.ui.settingsButton.setEnabled(True) self.ui.measurmentButton.setEnabled(True) @pyqtSlot() def __settings_button_checked(self): self.Form.close() self.Result.close() # TODO Исправить этот костыль (web.show() => web.close()) self.web.show() self.resize(1246, 564) self.web.close() self.Form.show() self.ui.theoryCombobox.setEnabled(True) self.ui.theoryButton.setEnabled(True) self.ui.settingsButton.setEnabled(False) self.ui.measurmentButton.setEnabled(True) @pyqtSlot() def __measurment_buttton_checked(self): self.web.close() self.Form.show() self.Result.show() self.ui.theoryCombobox.setEnabled(True) self.ui.theoryButton.setEnabled(True) self.ui.settingsButton.setEnabled(True) self.ui.measurmentButton.setEnabled(False) @pyqtSlot() def __add_liquid_button_clicked(self): Dialog = QtWidgets.QDialog() ui = Ui_Dialog(LIQUIDS[self.ui.theoryCombobox.currentText()]) ui.setupUi(Dialog) Dialog.show() rsp = Dialog.exec_() if rsp == QtWidgets.QDialog.Accepted: self.liquids[self.ui.theoryCombobox.currentText()] = [] for index in range(ui.myList.count()): self.liquids[self.ui.theoryCombobox.currentText()].append( ui.myList.item(index).text()) self.__create_input_bar() self.__fullfill_table() else: pass @pyqtSlot() def __create_input_bar(self): self.lineedit_list = [] self.liquids_inuse_list = [] self.__delete_widgets_on_setting_layout() self.settings_ui.gridLayout.setColumnStretch(0, 1) self.settings_ui.gridLayout.setColumnStretch(1, 1) for index, liquid in enumerate( self.liquids[self.ui.theoryCombobox.currentText()]): new_label = QtWidgets.QLabel(text=liquid) new_label.setFixedHeight(30) new_label.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)) new_label.setAlignment(QtCore.Qt.AlignCenter) self.liquids_inuse_list.append(new_label) new_lineedit = QtWidgets.QLineEdit() new_lineedit.textChanged.connect(self.check_all) self.lineedit_list.append(new_lineedit) new_lineedit.setAlignment(QtCore.Qt.AlignCenter) reg_ex = QRegExp("[0-9]+.[0-9]{,3}") input_validator = QRegExpValidator(reg_ex, new_lineedit) new_lineedit.setValidator(input_validator) self.settings_ui.gridLayout_2.addWidget(new_label, index, 0) self.settings_ui.gridLayout_2.addWidget(new_lineedit, index, 1) @pyqtSlot() def __fullfill_table(self): self.__clear_table_on_setting_layout() sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum) self.settings_ui.tableWidget.setSizePolicy(sizePolicy) self.settings_ui.tableWidget.setColumnCount(4) self.settings_ui.tableWidget.verticalHeader().hide() self.settings_ui.tableWidget.setHorizontalHeaderLabels( HORIZONTAL_HEADER_SETTING) self.settings_ui.tableWidget.setColumnWidth(0, 150) self.settings_ui.tableWidget.setColumnWidth(1, 100) self.settings_ui.tableWidget.setColumnWidth(2, 100) self.settings_ui.tableWidget.setColumnWidth(3, 100) if self.liquids[self.ui.theoryCombobox.currentText()]: liquids = self.liquids[self.ui.theoryCombobox.currentText()] cur_temp = self.ui.tempCombobox.currentText()[:-1] self.settings_ui.tableWidget.setRowCount(len(liquids)) for index, liquid in enumerate(liquids): item0 = QTableWidgetItem(liquid) item0.setTextAlignment(QtCore.Qt.AlignCenter) item1 = QTableWidgetItem(str(TEMPERATURE[liquid][cur_temp][1])) item1.setTextAlignment(QtCore.Qt.AlignCenter) item2 = QTableWidgetItem(str(TEMPERATURE[liquid][cur_temp][0])) item2.setTextAlignment(QtCore.Qt.AlignCenter) item3 = QTableWidgetItem(str( TEMPERATURE[liquid][cur_temp][-1])) item3.setTextAlignment(QtCore.Qt.AlignCenter) self.settings_ui.tableWidget.setItem(index, 0, item0) self.settings_ui.tableWidget.setItem(index, 1, item1) self.settings_ui.tableWidget.setItem(index, 2, item2) self.settings_ui.tableWidget.setItem(index, 3, item3) @pyqtSlot() def __delete_widgets_on_setting_layout(self): for index in range(self.settings_ui.gridLayout_2.count()): self.settings_ui.gridLayout_2.itemAt(index).widget().deleteLater() @pyqtSlot() def __clear_table_on_setting_layout(self): self.settings_ui.tableWidget.clear() self.settings_ui.tableWidget.setRowCount(0) def __clear_table_on_result_layout(self): self.result_ui.resultTable.clear() self.result_ui.resultTable.setRowCount(0) @pyqtSlot() def __theory_chose(self): self.settings_ui.calculateButton.setStyleSheet( "color: rgb(255, 0, 0);") try: self.liquids[self.ui.theoryCombobox.currentText()] except Exception: self.liquids[self.ui.theoryCombobox.currentText()] = [] finally: self.__delete_widgets_on_setting_layout() self.__clear_table_on_setting_layout() self.__create_input_bar() self.__fullfill_table() def __collect_data_to_process(self): self.to_process = [] cur_temp = self.ui.tempCombobox.currentText()[:-1] method = self.ui.theoryCombobox.currentText() for index, line in enumerate(self.lineedit_list): polar = TEMPERATURE[ self.liquids_inuse_list[index].text()][cur_temp][1] dispersive = TEMPERATURE[ self.liquids_inuse_list[index].text()][cur_temp][0] if method == 'van-Oss': acid = TEMPERATURE[ self.liquids_inuse_list[index].text()][cur_temp][2] base = TEMPERATURE[ self.liquids_inuse_list[index].text()][cur_temp][3] self.to_process.append( (self.liquids_inuse_list[index].text(), float(line.text()), dispersive, polar, acid, base)) elif method == 'Owens-Wendt' or method == 'Fowkes': self.to_process.append((self.liquids_inuse_list[index].text(), float(line.text()), dispersive, polar)) else: self.to_process.append( (self.liquids_inuse_list[index].text(), float(line.text()), dispersive + polar)) self.image_index = 0 if self.image_index is None else self.image_index + 1 self.__process() def __process(self): math = Calculation(to_process=self.to_process, name=self.ui.theoryCombobox.currentText(), index=self.image_index) try: math.calculate() except ValueError as v_error: self._save_to_log(v_error) self.__raise_error() else: self.result.append(math.result) self._add_to_result_table() self._add_plot() def _save_to_log(self, *args): with open('error.log', mode='a+', encoding='utf8') as flog: line = f'{datetime.now()}:{[arg for arg in args]}\n' flog.write(line) @pyqtSlot() def __raise_error(self): msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText("Hello! It seems like something going wrong =( " "If you need details look errors.log file in the source ") msg.setInformativeText( "Result that we try to obtaine is bullsheety, check the data twice!" ) msg.setWindowTitle("Calculation Error") msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) retval = msg.exec_() @pyqtSlot() def _add_plot(self): if self.ui.theoryCombobox.currentText() != 'Fowkes': name = self.ui.theoryCombobox.currentText() pixmap = QPixmap( f'result/{self.current_date}_{name}({self.image_index}).png') self.result_ui.plotLabel.setPixmap(pixmap) else: self.result_ui.plotLabel.setText( f'No plot for this "{self.ui.theoryCombobox.currentText()}"method!' ) @pyqtSlot() def _add_to_result_table(self): align = QtCore.Qt.AlignCenter if len(self.result) > self.current_index: new_result = self.result[-1] self.result_ui.resultTable.setRowCount(len(self.result)) if len( self.result) > 4 else self.result_ui.resultTable.setRowCount(5) try: item0 = QTableWidgetItem(str(round(new_result[0], 3))) item1 = QTableWidgetItem(str(round(new_result[1], 3))) except Exception: item0 = QTableWidgetItem(str(new_result[0])) item1 = QTableWidgetItem(str(new_result[1])) finally: item0.setTextAlignment(align) self.result_ui.resultTable.setItem(self.current_index, 0, item0) item1.setTextAlignment(align) self.result_ui.resultTable.setItem(self.current_index, 1, item1) item2 = QTableWidgetItem(str(round(new_result[2], 3))) item2.setTextAlignment(align) self.result_ui.resultTable.setItem(self.current_index, 2, item2) item3 = QTableWidgetItem(str(new_result[3])) item3.setTextAlignment(align) self.result_ui.resultTable.setItem(self.current_index, 3, item3) self.current_index += 1 @pyqtSlot() def check_all(self): a = [line.text() for line in self.lineedit_list if line.text() != ''] if len(a) == self.settings_ui.gridLayout_2.count() // 2: self.settings_ui.calculateButton.setStyleSheet( "color: rgb(250, 120, 255);") self.settings_ui.calculateButton.setEnabled(True) else: self.settings_ui.calculateButton.setStyleSheet( "color: rgb(255, 0, 0);") self.settings_ui.calculateButton.setEnabled(False) @pyqtSlot() def save_result(self): ''' Save result of current measurment as .txt file ''' home_dir = os.getcwd() open_dir = f'{home_dir}/result' if not self.fname: self.fname = QFileDialog.getSaveFileName( self, 'Open file', open_dir, filter='Text files (*.txt)') try: with open(self.fname[0], 'a+') as f: for measurment in self.result: f.write('\n') for value in measurment: f.write(f'{str(value)},') except Exception as exc: print(exc) pass @pyqtSlot() def load_result(self): ''' Save result of current measurment as .txt file ''' result_dir = os.getcwd() self.fname = QFileDialog.getOpenFileNames(self, 'Open file', result_dir, filter='Text files (*.txt)') with open(self.fname[0][-1], 'r+') as f: result = [value[:-1] for value in f.readlines() if value != '\n'] for value in result: to_read = value.split(sep=',') self.result.append(to_read) if len( to_read) == 4 else self.result.append(to_read[:-1]) self.current_index = 0 self.__measurment_buttton_checked() self.__to_table() def __to_table(self): self.result_ui.resultTable.setColumnCount(4) self.result_ui.resultTable.setHorizontalHeaderLabels( HORIZONTAL_HEADER_RESULT) self.result_ui.resultTable.setRowCount(5) self.result_ui.resultTable.horizontalHeader().setStretchLastSection( True) align = QtCore.Qt.AlignCenter self.result_ui.resultTable.setRowCount(len(self.result)) for value in self.result: item0 = QTableWidgetItem(value[0][:4]) item1 = QTableWidgetItem(value[1][:4]) item2 = QTableWidgetItem(value[2][:4]) item3 = QTableWidgetItem(value[3]) item0.setTextAlignment(align) item1.setTextAlignment(align) item2.setTextAlignment(align) item3.setTextAlignment(align) self.result_ui.resultTable.setItem(self.current_index, 0, item0) self.result_ui.resultTable.setItem(self.current_index, 1, item1) self.result_ui.resultTable.setItem(self.current_index, 2, item2) self.result_ui.resultTable.setItem(self.current_index, 3, item3) self.current_index += 1 @pyqtSlot() def new(self): self.__delete_widgets_on_setting_layout() self.__clear_table_on_setting_layout() self.__clear_table_on_result_layout() self.result_ui.resultTable.setColumnCount(4) self.result_ui.resultTable.setHorizontalHeaderLabels( HORIZONTAL_HEADER_RESULT) self.result_ui.resultTable.setRowCount(5) self.result_ui.plotLabel.setText('Plot will be here') @pyqtSlot() def exit(self): self.save_result() self.close()
class Overlay(QtCore.QObject): def __init__(self, up, configFileName, name, url): super().__init__() self.configFileName = configFileName self.parent = up self.app = up.app self.url = url self.size = None self.name = name self.overlay = None self.settings = None self.position = None self.enabled = True self.showtitle = True self.mutedeaf = True self.showtitle = True def load(self): config = ConfigParser(interpolation=None) config.read(self.configFileName) self.posXL = config.getint(self.name, 'xl', fallback=0) self.posXR = config.getint(self.name, 'xr', fallback=200) self.posYT = config.getint(self.name, 'yt', fallback=50) self.posYB = config.getint(self.name, 'yb', fallback=450) self.right = config.getboolean(self.name, 'rightalign', fallback=False) self.mutedeaf = config.getboolean( self.name, 'mutedeaf', fallback=True) self.chatresize = config.getboolean( self.name, 'chatresize', fallback=True) self.screenName = config.get(self.name, 'screen', fallback='None') #self.url = config.get(self.name, 'url', fallback=None) self.enabled = config.getboolean(self.name, 'enabled', fallback=True) self.showtitle = config.getboolean(self.name, 'title', fallback=True) self.hideinactive = config.getboolean( self.name, 'hideinactive', fallback=True) self.chooseScreen() # TODO Check, is there a better logic location for this? if self.enabled: self.showOverlay() def moveOverlay(self): if self.overlay: self.overlay.resize(self.posXR-self.posXL, self.posYB-self.posYT) self.overlay.move(self.posXL + self.screenOffset.left(), self.posYT + self.screenOffset.top()) def on_url(self, url): if self.overlay: self.overlay.load(QtCore.QUrl(url)) self.url = url self.save() self.settings.close() self.settings = None def on_save_position(self, url): self.save() self.position.close() self.position = None @pyqtSlot() def save(self): config = ConfigParser(interpolation=None) config.read(self.configFileName) if not config.has_section(self.name): config.add_section(self.name) config.set(self.name, 'xl', '%d' % (self.posXL)) config.set(self.name, 'xr', '%d' % (self.posXR)) config.set(self.name, 'yt', '%d' % (self.posYT)) config.set(self.name, 'yb', '%d' % (self.posYB)) config.set(self.name, 'rightalign', '%d' % (int(self.right))) config.set(self.name, 'mutedeaf', '%d' % (int(self.mutedeaf))) config.set(self.name, 'chatresize', '%d' % (int(self.chatresize))) config.set(self.name, 'screen', self.screenName) config.set(self.name, 'enabled', '%d' % (int(self.enabled))) config.set(self.name, 'title', '%d' % (int(self.showtitle))) config.set(self.name, 'hideinactive', '%d' % (int(self.hideinactive))) if self.url: config.set(self.name, 'url', self.url) if ',' in self.url: self.parent.reinit() with open(self.configFileName, 'w') as file: config.write(file) @pyqtSlot() def on_click(self): self.runJS( "document.getElementsByClassName('source-url')[0].value;", self.on_url) @pyqtSlot() def skip_stream_button(self): skipIntro = "buttons = document.getElementsByTagName('button');for(i=0;i<buttons.length;i++){if(buttons[i].innerHTML=='Install for OBS'){buttons[i].click()}}" hideLogo = "document.getElementsByClassName('install-logo')[0].style.setProperty('display','none');" resizeContents = "document.getElementsByClassName('content')[0].style.setProperty('top','30px');" resizeHeader = "document.getElementsByClassName('header')[0].style.setProperty('height','35px');" hidePreview = "document.getElementsByClassName('config-link')[0].style.setProperty('height','300px');document.getElementsByClassName('config-link')[0].style.setProperty('overflow','hidden');" hideClose = "document.getElementsByClassName('close')[0].style.setProperty('display','none');" chooseVoice = "for( let button of document.getElementsByTagName('button')){ if(button.getAttribute('value') == 'voice'){ button.click(); } }" chooseChat = "for( let button of document.getElementsByTagName('button')){ if(button.getAttribute('value') == 'chat'){ button.click(); } }" infoListener = "if(typeof console.oldlog === 'undefined'){console.oldlog=console.log;}window.consoleCatchers=[];console.log = function(text,input){if(typeof input !== 'undefined'){window.consoleCatchers.forEach(function(item,index){item(input)})}else{console.oldlog(text);}};" catchGuild = "window.consoleCatchers.push(function(input){if(input.cmd == 'GET_GUILD'){window.guilds=input.data.id}})" catchChannel = "window.consoleCatchers.push(function(input){if(input.cmd == 'GET_CHANNELS'){window.channels = input.data.channels;}})" self.runJS(skipIntro) self.runJS(hideLogo) self.runJS(resizeContents) self.runJS(hidePreview) self.runJS(resizeHeader) self.runJS(hideClose) self.runJS(infoListener) self.runJS(catchGuild) self.runJS(catchChannel) if self.url: if 'overlay/voice' in self.url: self.runJS(chooseVoice) else: self.runJS(chooseChat) def enableConsoleCatcher(self): if self.overlay: tweak = "if(typeof console.oldlog === 'undefined'){console.oldlog=console.log;}window.consoleCatchers=[];console.log = function(text,input){if(typeof input !== 'undefined'){window.consoleCatchers.forEach(function(item,index){item(input)})}else{console.oldlog(text);}};" self.overlay.page().runJavaScript(tweak) def enableShowVoiceTitle(self): if self.overlay: tweak = "window.consoleCatchers.push(function(input){if(input.cmd == 'GET_CHANNEL'){chan=input.data.name;(function() { css = document.getElementById('title-css'); if (css == null) { css = document.createElement('style'); css.type='text/css'; css.id='title-css'; document.head.appendChild(css); } css.innerText='.voice-container:before{content:\"'+chan+'\";background:rgba(30, 33, 36, 0.95);padding:4px 6px;border-radius: 3px;}';})()}})" self.overlay.page().runJavaScript(tweak) def enableHideInactive(self): if self.overlay and self.url: if 'overlay/voice' in self.url: tweak = "document.getElementById('app-mount').style='display:none';window.consoleCatchers.push(function(input){if(input.cmd=='AUTHENTICATE'){console.error(input.data.user);window.iAm=input.data.user.username;}if(input.evt=='VOICE_STATE_CREATE' || input.evt=='VOICE_STATE_UPDATE'){if(input.data.nick.toUpperCase()==window.iAm.toUpperCase()){document.getElementById('app-mount').style='display:block';console.error('Showing '+chan)}}if(input.evt=='VOICE_STATE_DELETE'){if(input.data.nick.toUpperCase()==window.iAm.toUpperCase()){document.getElementById('app-mount').style='display:none';console.error('Hiding '+chan)}}});" self.overlay.page().runJavaScript(tweak) def enableMuteDeaf(self): if self.overlay: tweak = "window.consoleCatchers.push(function(input){if(input.evt == 'VOICE_STATE_UPDATE'){name=input.data.nick;uState = input.data.voice_state;muteicon = '';if(uState.self_mute || uState.mute){muteicon='<img src=\\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAABhMAAAYJQE8CCw1AAAAB3RJTUUH5AUGCx0VMm5EjgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAABzSURBVDjLxZIxCsAwCEW/oT1P7z93zZJjeIYMv0sCIaBoodTJDz6/JgJfBslOsns1xYONvK66JCeqAC4ALTz+dJvOo0lu/zS87p2C98IdHlq9Buo5D62h17amScMk78hBWXB/DUdP2fyBaINjJiJy4o94AM8J8ksz/MQjAAAAAElFTkSuQmCC\\' style=\\'height:0.9em;\\'>';}deaficon = '';if(uState.self_deaf || uState.deaf){deaficon='<img src=\\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAABhMAAAYJQE8CCw1AAAAB3RJTUUH5AUGCx077rhJQQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAACNSURBVDjLtZPNCcAgDIUboSs4iXTGLuI2XjpBz87g4fWiENr8iNBAQPR9ef7EbfsjAEQAN4A2UtCcGtyMzFxjwVlyBHAwTRFh52gqHDVnF+6L1XJ/w31cp7YvOX/0xlOJ254qYJ1ZLTAmPWeuDVxARDurfBFR8jovMLEKWxG6c1qB55pEuQOpE8vKz30AhEdNuXK0IugAAAAASUVORK5CYII=\\' style=\\'height:0.9em;\\'>';}spans = document.getElementsByTagName('span');for(i=0;i<spans.length;i++){if(spans[i].innerHTML.startsWith(name)){text = name + muteicon + deaficon;spans[i].innerHTML = text;}}}});" self.overlay.page().runJavaScript(tweak) def runJS(self, string, retFunc=None): if retFunc: self.settingWebView.page().runJavaScript(string, retFunc) else: self.settingWebView.page().runJavaScript(string) def applyTweaks(self): self.enableConsoleCatcher() if self.right: self.addCSS( 'cssrightalign', 'li.voice-state{ direction:rtl; }.avatar{ float:right !important; }.user{ display:flex; }.voice-container{margin-top:30px;}.voice-container:before{position:fixed;right:0px;top:0px;}') else: self.delCSS('cssrightalign') if self.showtitle: self.enableShowVoiceTitle() if self.mutedeaf: self.enableMuteDeaf() if self.hideinactive: self.enableHideInactive() if self.chatresize: self.addCSS( 'cssflexybox', 'div.chat-container { width: 100%; height: 100%; top: 0; left: 0; position: fixed; display: flex; flex-direction: column; } div.chat-container > .messages { box-sizing: border-box; width: 100%; flex: 1; }') else: self.delCSS('cssflexybox') def addCSS(self, name, css): if self.overlay: js = '(function() { css = document.getElementById(\'%s\'); if (css == null) { css = document.createElement(\'style\'); css.type=\'text/css\'; css.id=\'%s\'; document.head.appendChild(css); } css.innerText=\'%s\';})()' % (name, name, css) self.overlay.page().runJavaScript(js) def delCSS(self, name): if self.overlay: js = "(function() { css = document.getElementById('%s'); if(css!=null){ css.parentNode.removeChild(css);} })()" % ( name) self.overlay.page().runJavaScript(js) @pyqtSlot() def toggleEnabled(self, button=None): self.enabled = self.enabledButton.isChecked() if self.enabled: self.showOverlay() else: self.hideOverlay() @pyqtSlot() def toggleTitle(self, button=None): self.showtitle = self.showTitle.isChecked() if self.showtitle: self.enableShowVoiceTitle() @pyqtSlot() def toggleMuteDeaf(self, button=None): self.mutedeaf = self.muteDeaf.isChecked() if self.muteDeaf.isChecked(): self.enableMuteDeaf() @pyqtSlot() def toggleHideInactive(self, button=None): self.hideinactive = self.hideInactive.isChecked() if self.hideinactive: self.enableHideInactive() @pyqtSlot() def toggleChatResize(self, button=None): self.chatresize = self.chatResize.isChecked() self.applyTweaks() @pyqtSlot() def toggleRightAlign(self, button=None): self.right = self.rightAlign.isChecked() self.applyTweaks() @pyqtSlot() def changeValueFL(self): self.posXL = self.settingsDistanceFromLeft.value() self.moveOverlay() @pyqtSlot() def changeValueFR(self): self.posXR = self.settingsDistanceFromRight.value() self.moveOverlay() @pyqtSlot() def changeValueFT(self): self.posYT = self.settingsDistanceFromTop.value() self.moveOverlay() @pyqtSlot() def changeValueFB(self): self.posYB = self.settingsDistanceFromBottom.value() self.moveOverlay() def fillPositionWindowOptions(self): self.settingsDistanceFromLeft.valueChanged[int].connect( self.changeValueFL) self.settingsDistanceFromLeft.setMaximum(self.size.width()) self.settingsDistanceFromLeft.setValue(self.posXL) self.settingsDistanceFromRight.valueChanged[int].connect( self.changeValueFR) self.settingsDistanceFromRight.setMaximum(self.size.width()) self.settingsDistanceFromRight.setValue(self.posXR) self.settingsDistanceFromTop.valueChanged[int].connect( self.changeValueFT) self.settingsDistanceFromTop.setMaximum(self.size.height()) self.settingsDistanceFromTop.setInvertedAppearance(True) self.settingsDistanceFromTop.setValue(self.posYT) self.settingsDistanceFromBottom.valueChanged[int].connect( self.changeValueFB) self.settingsDistanceFromBottom.setMaximum(self.size.height()) self.settingsDistanceFromBottom.setInvertedAppearance(True) self.settingsDistanceFromBottom.setValue(self.posYB) def populateScreenList(self): self.ignoreScreenComboBox = True screenList = self.app.screens() self.settingsScreen.clear() for i, s in enumerate(screenList): self.settingsScreen.addItem(s.name()) if s.name() == self.screenName: self.settingsScreen.setCurrentIndex(i) self.ignoreScreenComboBox = False self.chooseScreen() def changeScreen(self, index): if not self.ignoreScreenComboBox: self.screenName = self.settingsScreen.currentText() self.chooseScreen() def chooseScreen(self): screen = None screenList = self.app.screens() logger.debug("Discovered screens: %r", [s.name() for s in screenList]) for s in screenList: if s.name() == self.screenName: screen = s logger.debug("Chose screen %s", screen.name()) break # The chosen screen is not in this list. Drop to primary else: screen = self.app.primaryScreen() logger.warning( "Chose screen %r as fallback because %r could not be matched", screen.name(), self.screenName) # Fill Info! self.size = screen.size() self.screenName = s.name() self.screenOffset = screen.availableGeometry() if self.position: self.settingsAspectRatio.updateScreen( self.size.width(), self.size.height()) self.fillPositionWindowOptions() self.screenShot(screen) self.moveOverlay() def showPosition(self): if self.position is not None: self.position.show() else: # Positional Settings Window self.position = QtWidgets.QWidget() self.position.setWindowTitle('Overlay %s Position' % (self.name)) self.positionbox = QtWidgets.QVBoxLayout() # Use a grid to lay out screen & sliders self.settingsGridWidget = QtWidgets.QWidget() self.settingsGrid = QtWidgets.QGridLayout() # Use the custom Aspect widget to keep the whole thing looking # as close to the user experience as possible self.settingsAspectRatio = AspectRatioWidget( self.settingsGridWidget) # Grid contents self.settingsPreview = ResizingImage() self.settingsPreview.setMinimumSize(1, 1) sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.settingsPreview.setSizePolicy(sizePolicy) self.settingsDistanceFromLeft = QtWidgets.QSlider( QtCore.Qt.Horizontal) self.settingsDistanceFromRight = QtWidgets.QSlider( QtCore.Qt.Horizontal) self.settingsDistanceFromTop = QtWidgets.QSlider( QtCore.Qt.Vertical) self.settingsDistanceFromBottom = QtWidgets.QSlider( QtCore.Qt.Vertical) # Screen chooser self.settingsScreen = QtWidgets.QComboBox() # self.position.setMinimumSize(600,600) # Save button self.settingSave = QtWidgets.QPushButton("Save") # Fill Screens, Choose the screen if config is set self.populateScreenList() self.settingSave.clicked.connect(self.on_save_position) self.settingsScreen.currentIndexChanged.connect(self.changeScreen) self.settingsGrid.addWidget(self.settingsPreview, 0, 0) self.settingsGrid.addWidget(self.settingsDistanceFromLeft, 1, 0) self.settingsGrid.addWidget(self.settingsDistanceFromRight, 2, 0) self.settingsGrid.addWidget(self.settingsDistanceFromTop, 0, 1) self.settingsGrid.addWidget(self.settingsDistanceFromBottom, 0, 2) self.settingsGridWidget.setLayout(self.settingsGrid) self.positionbox.addWidget(self.settingsScreen) self.positionbox.addWidget(self.settingsAspectRatio) self.position.setLayout(self.positionbox) self.positionbox.addWidget(self.settingSave) self.fillPositionWindowOptions() self.position.show() def showSettings(self): if self.settings is not None: self.settings.show() else: self.settings = QtWidgets.QWidget() self.settings.setWindowTitle('Overlay %s Layout' % (self.name)) self.settingsbox = QtWidgets.QVBoxLayout() self.settingWebView = QWebEngineView() self.rightAlign = QtWidgets.QCheckBox("Right Align") self.muteDeaf = QtWidgets.QCheckBox("Show mute and deafen") self.chatResize = QtWidgets.QCheckBox("Large chat box") self.showTitle = QtWidgets.QCheckBox("Show room title") self.hideInactive = QtWidgets.QCheckBox( "Hide voice channel when inactive") self.enabledButton = QtWidgets.QCheckBox("Enabled") self.settingTakeUrl = QtWidgets.QPushButton("Use this Room") self.settingTakeAllUrl = QtWidgets.QPushButton("Use all Rooms") self.settings.setMinimumSize(400, 400) self.settingTakeUrl.clicked.connect(self.on_click) self.settingTakeAllUrl.clicked.connect(self.getAllRooms) self.settingWebView.loadFinished.connect(self.skip_stream_button) self.rightAlign.stateChanged.connect(self.toggleRightAlign) self.rightAlign.setChecked(self.right) self.muteDeaf.stateChanged.connect(self.toggleMuteDeaf) self.muteDeaf.setChecked(self.mutedeaf) self.showTitle.stateChanged.connect(self.toggleTitle) self.showTitle.setChecked(self.showtitle) self.hideInactive.stateChanged.connect(self.toggleHideInactive) self.hideInactive.setChecked(self.hideinactive) self.enabledButton.stateChanged.connect(self.toggleEnabled) self.enabledButton.setChecked(self.enabled) self.chatResize.stateChanged.connect(self.toggleChatResize) self.chatResize.setChecked(self.chatresize) self.settingWebView.load(QtCore.QUrl( "https://streamkit.discord.com/overlay")) self.settingsbox.addWidget(self.settingWebView) self.settingsbox.addWidget(self.rightAlign) self.settingsbox.addWidget(self.muteDeaf) self.settingsbox.addWidget(self.chatResize) self.settingsbox.addWidget(self.showTitle) self.settingsbox.addWidget(self.hideInactive) self.settingsbox.addWidget(self.enabledButton) self.settingsbox.addWidget(self.settingTakeUrl) self.settingsbox.addWidget(self.settingTakeAllUrl) self.settings.setLayout(self.settingsbox) self.settings.show() def screenShot(self, screen): screenshot = screen.grabWindow(0) self.settingsPreview.setImage(screenshot) self.settingsPreview.setContentsMargins(0, 0, 0, 0) def showOverlay(self): if self.overlay: return self.overlay = QWebEngineView() self.overlay.page().setBackgroundColor(QtCore.Qt.transparent) self.overlay.setAttribute(QtCore.Qt.WA_TranslucentBackground, True) self.overlay.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, True) self.overlay.setWindowFlags( QtCore.Qt.X11BypassWindowManagerHint | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.WindowTransparentForInput | QtCore.Qt.WindowDoesNotAcceptFocus | QtCore.Qt.NoDropShadowWindowHint | QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowMinimizeButtonHint ) self.overlay.loadFinished.connect(self.applyTweaks) self.overlay.load(QtCore.QUrl(self.url)) self.overlay.setStyleSheet("background:transparent;") self.overlay.show() self.moveOverlay() def hideOverlay(self): if self.overlay: self.overlay.close() self.overlay = None def delete(self): self.hideOverlay() if self.settings: self.settings.close() if self.position: self.position.close() self.overlay = None def getAllRooms(self): getChannel = "[window.channels, window.guilds]" self.runJS(getChannel, self.gotAllRooms) def gotAllRooms(self, message): sep = '' url = '' for chan in message[0]: if chan['type'] == 2: url += sep+"https://streamkit.discord.com/overlay/voice/%s/%s?icon=true&online=true&logo=white&text_color=%%23ffffff&text_size=14&text_outline_color=%%23000000&text_outline_size=0&text_shadow_color=%%23000000&text_shadow_size=0&bg_color=%%231e2124&bg_opacity=0.95&bg_shadow_color=%%23000000&bg_shadow_size=0&limit_speaking=false&small_avatars=false&hide_names=false&fade_chat=0" % (message[1], chan[ 'id']) sep = ',' if self.overlay: self.overlay.load(QtCore.QUrl(url)) self.url = url self.save() self.settings.close() self.settings = None
class AwBrowser(QDialog): """ Customization and configuration of a web browser to run within Anki """ SINGLETON = None TITLE = 'Anki :: Web Browser Addon' _parent = None _fields = [] _selectionHandler = None _web = None _context = None _lastAssignedField = None infoList = [] providerList = [] def __init__(self, myParent): QDialog.__init__(self, None) self._parent = myParent self.setupUI() if myParent: def wrapClose(fn): def clozeBrowser(evt): self.close() fn(evt) return clozeBrowser myParent.closeEvent = wrapClose(myParent.closeEvent) def setupUI(self): self.setWindowTitle(AwBrowser.TITLE) self.setWindowFlags(Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint) self.setGeometry(450, 200, 800, 450) self.setMinimumWidth(640) self.setMinimumHeight(450) self.setStyleSheet(Style.DARK_BG) mainLayout = QVBoxLayout() mainLayout.setContentsMargins(0, 0, 0, 0) mainLayout.setSpacing(0) self.setLayout(mainLayout) self._web = QWebEngineView(self) self._web.contextMenuEvent = self.contextMenuEvent self._web.page().loadStarted.connect(self.onStartLoading) self._web.page().loadFinished.connect(self.onLoadFinish) self._web.page().loadProgress.connect(self.onProgress) self._web.page().urlChanged.connect(self.onPageChange) # -------------------- Top / toolbar ---------------------- navtbar = QToolBar("Navigation") navtbar.setIconSize(QSize(16, 16)) mainLayout.addWidget(navtbar) backBtn = QAction( QtGui.QIcon(os.path.join(CWD, 'assets', 'arrow-back.png')), "Back", self) backBtn.setStatusTip("Back to previous page") backBtn.triggered.connect(self._web.back) navtbar.addAction(backBtn) self.forwardBtn = QAction( QtGui.QIcon(os.path.join(CWD, 'assets', 'arrow-forward.png')), "Forward", self) self.forwardBtn.setStatusTip("Next visited page") self.forwardBtn.triggered.connect(self._web.forward) navtbar.addAction(self.forwardBtn) refreshBtn = QAction( QtGui.QIcon(os.path.join(CWD, 'assets', 'reload.png')), "Reload", self) refreshBtn.setStatusTip("Reload") refreshBtn.triggered.connect(self._web.reload) navtbar.addAction(refreshBtn) self.createProvidersMenu(navtbar) self._itAddress = QtWidgets.QLineEdit(self) self._itAddress.setObjectName("itSite") self._itAddress.setStyleSheet('background-color: #F5F5F5;') self._itAddress.returnPressed.connect(self._goToAddress) navtbar.addWidget(self._itAddress) cbGo = QAction(QtGui.QIcon(os.path.join(CWD, 'assets', 'go-icon.png')), "Go", self) cbGo.setObjectName("cbGo") navtbar.addAction(cbGo) cbGo.triggered.connect(self._goToAddress) self.stopBtn = QAction( QtGui.QIcon(os.path.join(CWD, 'assets', 'stop.png')), "Stop", self) self.stopBtn.setStatusTip("Stop loading") self.stopBtn.triggered.connect(self._web.stop) navtbar.addAction(self.stopBtn) # -------------------- Center ---------------------- mainLayout.addWidget(self._web) # -------------------- Bottom bar ---------------------- bottomWidget = QtWidgets.QWidget(self) bottomWidget.setFixedHeight(30) bottomLayout = QtWidgets.QHBoxLayout(bottomWidget) bottomLayout.setObjectName("bottomLayout") bottomWidget.setStyleSheet('color: #FFF;') lbSite = QtWidgets.QLabel(bottomWidget) lbSite.setObjectName("label") lbSite.setText("Context: ") lbSite.setFixedWidth(70) lbSite.setStyleSheet('font-weight: bold;') bottomLayout.addWidget(lbSite) self.ctxWidget = QtWidgets.QLabel(bottomWidget) self.ctxWidget.width = 300 self.ctxWidget.setStyleSheet('text-align: left;') bottomLayout.addWidget(self.ctxWidget) self._loadingBar = QtWidgets.QProgressBar(bottomWidget) self._loadingBar.setFixedWidth(100) self._loadingBar.setProperty("value", 100) self._loadingBar.setObjectName("loadingBar") bottomLayout.addWidget(self._loadingBar) mainLayout.addWidget(bottomWidget) if cfg.getConfig().browserAlwaysOnTop: self.setWindowFlags(Qt.WindowStaysOnTopHint) @classmethod def singleton(cls, parent): if not cls.SINGLETON: cls.SINGLETON = AwBrowser(parent) return cls.SINGLETON def formatTargetURL(self, website: str, query: str = ''): return website.format(urllib.parse.quote(query, encoding='utf8')) @exceptionHandler def open(self, website, query: str): """ Loads a given page with its replacing part with its query, and shows itself """ self._context = query self._updateContextWidget() target = self.formatTargetURL(website, query) self._web.load(QUrl(target)) self._itAddress.setText(target) self.show() self.raise_() return self._web def unload(self): try: self._web.setHtml(BLANK_PAGE) self._itAddress.setText('about:blank') except (RuntimeError) as err: pass def onClose(self): self._parent = None self._web.close() self.close() def onStartLoading(self): self.stopBtn.setEnabled(True) self._loadingBar.setProperty("value", 1) def onProgress(self, prog): self._loadingBar.setProperty("value", prog) def onLoadFinish(self, result): self.stopBtn.setDisabled(True) self._loadingBar.setProperty("value", 100) if not result: Feedback.log('No result on loading page! ') def _goToAddress(self): q = QUrl(self._itAddress.text()) if q.scheme() == "": q.setScheme("http") self._web.load(q) self._web.show() def onPageChange(self, url): if url and url.toString().startswith('http'): self._itAddress.setText(url.toString()) self.forwardBtn.setEnabled(self._web.history().canGoForward()) def welcome(self): self._web.setHtml(WELCOME_PAGE) self._itAddress.setText('about:blank') self.show() self.raise_() def _updateContextWidget(self): self.ctxWidget.setText(self._context) # --------------------------------------------------------------------------------- def createProvidersMenu(self, parentWidget): providerBtn = QAction( QtGui.QIcon(os.path.join(CWD, 'assets', 'gear-icon.png')), "Providers", parentWidget) providerBtn.setStatusTip("Search with Provider") providerBtn.triggered.connect( lambda: self.newProviderMenu(providerBtn)) parentWidget.addAction(providerBtn) def newProviderMenu(self, parentBtn): ctx = ProviderSelectionController() ctx.showCustomMenu(parentBtn.parentWidget(), self.reOpenSameQuery) @exceptionHandler def reOpenSameQuery(self, website): self.open(website, self._context) # ------------------------------------ Menu --------------------------------------- def _makeMenuAction(self, field, value, isLink): """ Creates correct operations for the context menu selection. Only with lambda, it would repeat only the last element """ def _processMenuSelection(): self._lastAssignedField = field self._selectionHandler(field, value, isLink) return _processMenuSelection def contextMenuEvent(self, evt): """ Handles the context menu in the web view. Shows and handle options (from field list), only if in edit mode. """ if not (self._fields and self._selectionHandler): return self.createInfoMenu(evt) isLink = False value = None if self._web.selectedText(): isLink = False value = self._web.selectedText() else: if (self._web.page().contextMenuData().mediaType() == QWebEngineContextMenuData.MediaTypeImage and self._web.page().contextMenuData().mediaUrl()): isLink = True value = self._web.page().contextMenuData().mediaUrl() Feedback.log('Link: ' + value.toString()) Feedback.log('toLocal: ' + value.toLocalFile()) if not self._checkSuffix(value): return if not value: Feedback.log('No value') return self.createInfoMenu(evt) if QApplication.keyboardModifiers() == Qt.ControlModifier: if self._assignToLastField(value, isLink): return self.createCtxMenu(value, isLink, evt) def _checkSuffix(self, value): if value and not value.toString().endswith( ("jpg", "jpeg", "png", "gif")): msgLink = value.toString() if len(value.toString()) < 80: msgLink = msgLink[:50] + '...' + msgLink[50:] answ = QMessageBox.question( self, 'Anki support', """This link may not be accepted by Anki: \n\n "%s" \n Usually the suffix should be one of (jpg, jpeg, png, gif). Try it anyway? """ % msgLink, QMessageBox.Yes | QMessageBox.No) if answ != QMessageBox.Yes: return False return True def createCtxMenu(self, value, isLink, evt): 'Creates and configures the menu itself' m = QMenu(self) m.addAction(QAction('Copy', m, triggered=lambda: self._copy(value))) m.addSeparator() labelAct = QAction(Label.BROWSER_ASSIGN_TO, m) labelAct.setDisabled(True) m.addAction(labelAct) # sub = QMenu(Label.BROWSER_ASSIGN_TO, m) m.setTitle(Label.BROWSER_ASSIGN_TO) for index, label in self._fields.items(): act = QAction(label, m, triggered=self._makeMenuAction(index, value, isLink)) m.addAction(act) # m.addMenu(sub) action = m.exec_(self.mapToGlobal(evt.pos())) def createInfoMenu(self, evt): 'Creates and configures a menu with only some information' m = QMenu(self) for item in self.infoList: act = QAction(item) act.setEnabled(False) m.addAction(act) action = m.exec_(self.mapToGlobal(evt.pos())) def _assignToLastField(self, value, isLink): 'Tries to set the new value to the same field used before, if set...' if self._lastAssignedField: if self._lastAssignedField in self._fields: self._selectionHandler(self._lastAssignedField, value, isLink) return True else: self._lastAssignedField = None return False def _copy(self, value): if not value: return clip = QApplication.clipboard() clip.setText(value if isinstance(value, str) else value.toString()) def load(self, qUrl): self._web.load(qUrl) # ----------------- getter / setter ------------------- def setFields(self, fList): self._fields = fList def setSelectionHandler(self, value): self._selectionHandler = value
class ChromiumBrowserTab(BrowserTab): def __init__(self, render_options, verbosity): super().__init__(render_options, verbosity) self.profile = QWebEngineProfile() # don't share cookies self.web_page = ChromiumWebPage(self.profile) self.web_view = QWebEngineView() self.web_view.setPage(self.web_page) self.web_view.setAttribute(Qt.WA_DeleteOnClose, True) # TODO: is it ok? :) # self.web_view.setAttribute(Qt.WA_DontShowOnScreen, True) # FIXME: required for screenshots? # Also, without .show() in JS window.innerWidth/innerHeight are zeros self.web_view.show() self._setup_webpage_events() self._set_default_webpage_options() self._html_d = None # ensure that default window size is not 640x480. self.set_viewport(defaults.VIEWPORT_SIZE) def _setup_webpage_events(self): self._load_finished = WrappedSignal(self.web_view.loadFinished) self._render_terminated = WrappedSignal(self.web_view.renderProcessTerminated) self.web_view.renderProcessTerminated.connect(self._on_render_terminated) self.web_view.loadFinished.connect(self._on_load_finished) # main_frame.urlChanged.connect(self._on_url_changed) # main_frame.javaScriptWindowObjectCleared.connect( # self._on_javascript_window_object_cleared) # self.logger.add_web_page(self.web_page) def _set_default_webpage_options(self): """ Set QWebPage options. TODO: allow to customize defaults. """ settings = self.web_page.settings() settings.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True) settings.setAttribute(QWebEngineSettings.JavascriptCanOpenWindows, False) settings.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True) # TODO: requires Qt 5.10 # settings.setAttribute(QWebEngineSettings.ShowScrollBars, False) # TODO # if self.visible: # settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) # TODO: options # self.set_js_enabled(True) # self.set_plugins_enabled(defaults.PLUGINS_ENABLED) # self.set_request_body_enabled(defaults.REQUEST_BODY_ENABLED) # self.set_response_body_enabled(defaults.RESPONSE_BODY_ENABLED) # self.set_indexeddb_enabled(defaults.INDEXEDDB_ENABLED) # self.set_webgl_enabled(defaults.WEBGL_ENABLED) # self.set_html5_media_enabled(defaults.HTML5_MEDIA_ENABLED) # self.set_media_source_enabled(defaults.MEDIA_SOURCE_ENABLED) def go(self, url, callback, errback): callback_id = self._load_finished.connect( self._on_content_ready, callback=callback, errback=errback, ) self.logger.log("callback %s is connected to loadFinished" % callback_id, min_level=3) self.web_view.load(QUrl(url)) @skip_if_closing def _on_content_ready(self, ok, callback, errback, callback_id): """ This method is called when a QWebEnginePage finishes loading. """ self.logger.log("loadFinished: disconnecting callback %s" % callback_id, min_level=3) self._load_finished.disconnect(callback_id) if ok: callback() else: error_info = RenderErrorInfo( type='Unknown', code=0, text="loadFinished ok=False", url=self.web_view.url().toString() ) errback(error_info) def _on_load_finished(self, ok): self.logger.log("loadFinished, ok=%s" % ok, min_level=2) def _on_render_terminated(self, status, code): status_details = RenderProcessTerminationStatus.get(status, 'unknown') self.logger.log("renderProcessTerminated: %s (%s), exit_code=%s" % ( status, status_details, code), min_level=1) def html(self): """ Return HTML of the current main frame """ self.logger.log("getting HTML", min_level=2) if self._html_d is not None: self.logger.log("HTML is already requested", min_level=1) return self._html_d self._html_d = defer.Deferred() self.web_view.page().toHtml(self._on_html_ready) return self._html_d def _on_html_ready(self, html): self.logger.log("HTML ready", min_level=2) self._html_d.callback(html) self._html_d = None def png(self, width=None, height=None, b64=False, render_all=False, scale_method=None, region=None): """ Return screenshot in PNG format """ # FIXME: move to base class self.logger.log( "Getting PNG: width=%s, height=%s, " "render_all=%s, scale_method=%s, region=%s" % (width, height, render_all, scale_method, region), min_level=2) if render_all: raise ValueError("render_all=True is not supported yet") image = self._get_image('PNG', width, height, render_all, scale_method, region=region) result = image.to_png() if b64: result = base64.b64encode(result).decode('utf-8') # self.store_har_timing("_onPngRendered") return result def jpeg(self, width=None, height=None, b64=False, render_all=False, scale_method=None, quality=None, region=None): """ Return screenshot in JPEG format. """ # FIXME: move to base class self.logger.log( "Getting JPEG: width=%s, height=%s, " "render_all=%s, scale_method=%s, quality=%s, region=%s" % (width, height, render_all, scale_method, quality, region), min_level=2) if render_all: raise ValueError("render_all=True is not supported yet") image = self._get_image('JPEG', width, height, render_all, scale_method, region=region) result = image.to_jpeg(quality=quality) if b64: result = base64.b64encode(result).decode('utf-8') # self.store_har_timing("_onJpegRendered") return result def _get_image(self, image_format, width, height, render_all, scale_method, region): renderer = QtChromiumScreenshotRenderer( self.web_page, self.logger, image_format, width=width, height=height, scale_method=scale_method, region=region) return renderer.render_qwebpage() def set_viewport(self, size, raise_if_empty=False): """ Set viewport size. If size is "full" viewport size is detected automatically. If can also be "<width>x<height>". FIXME: Currently the implementation just resizes the window, which causes Splash to crash on large sizes(?). Actully it is not changing the viewport. XXX: As an effect, this function changes window.outerWidth/outerHeight, while in Webkit implementation window.innerWidth/innerHeight is changed. """ if size == 'full': size = self.web_page.contentsSize() self.logger.log("Contents size: %s" % size, min_level=2) if size.isEmpty(): if raise_if_empty: raise RuntimeError("Cannot detect viewport size") else: size = defaults.VIEWPORT_SIZE self.logger.log("Viewport is empty, falling back to: %s" % size) if not isinstance(size, (QSize, QSizeF)): validate_size_str(size) size = parse_size(size) w, h = int(size.width()), int(size.height()) # XXX: it was crashing with large windows, but then the problem # seemed to go away. Need to keep an eye on it. # # FIXME: don't resize the window? # # FIXME: figure out exact limits # MAX_WIDTH = 1280 # MAX_HEIGHT = 1920 # # if w > MAX_WIDTH: # raise RuntimeError("Width {} > {} is currently prohibited".format( # w, MAX_WIDTH # )) # # if h > MAX_HEIGHT: # raise RuntimeError("Height {} > {} is currently prohibited".format( # h, MAX_HEIGHT # )) self.web_view.resize(w, h) # self._force_relayout() self.logger.log("viewport size is set to %sx%s" % (w, h), min_level=2) self.logger.log("real viewport size: %s" % self.web_view.size(), min_level=2) return w, h def stop_loading(self): self.logger.log("stop_loading", min_level=2) self.web_view.stop() @skip_if_closing def close(self): """ Destroy this tab """ super().close() self.web_view.stop() self.web_view.close() self.web_page.deleteLater() self.web_view.deleteLater()