class MainWindow(QMainWindow): def __init__(self): super().__init__() # tag::tabWidget[] self.tabs = QTabWidget() self.tabs.setDocumentMode(True) self.tabs.tabBarDoubleClicked.connect(self.tab_open_doubleclick) self.tabs.currentChanged.connect(self.current_tab_changed) self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.close_current_tab) self.setCentralWidget(self.tabs) # end::tabWidget[] navtb = QToolBar("Navigation") navtb.setIconSize(QSize(16, 16)) self.addToolBar(navtb) back_btn = QAction(QIcon(os.path.join("icons", "arrow-180.png")), "Back", self) back_btn.setStatusTip("Back to previous page") back_btn.triggered.connect(lambda: self.tabs.currentWidget().back()) navtb.addAction(back_btn) next_btn = QAction(QIcon(os.path.join("icons", "arrow-000.png")), "Forward", self) next_btn.setStatusTip("Forward to next page") next_btn.triggered.connect(lambda: self.tabs.currentWidget().forward()) navtb.addAction(next_btn) reload_btn = QAction( QIcon(os.path.join("icons", "arrow-circle-315.png")), "Reload", self) reload_btn.setStatusTip("Reload page") reload_btn.triggered.connect( lambda: self.tabs.currentWidget().reload()) navtb.addAction(reload_btn) home_btn = QAction(QIcon(os.path.join("icons", "home.png")), "Home", self) home_btn.setStatusTip("Go home") home_btn.triggered.connect(self.navigate_home) navtb.addAction(home_btn) navtb.addSeparator() self.httpsicon = QLabel() # Yes, really! self.httpsicon.setPixmap( QPixmap(os.path.join("icons", "lock-nossl.png"))) navtb.addWidget(self.httpsicon) self.urlbar = QLineEdit() self.urlbar.returnPressed.connect(self.navigate_to_url) navtb.addWidget(self.urlbar) stop_btn = QAction(QIcon(os.path.join("icons", "cross-circle.png")), "Stop", self) stop_btn.setStatusTip("Stop loading current page") stop_btn.triggered.connect(lambda: self.tabs.currentWidget().stop()) navtb.addAction(stop_btn) self.menuBar().setNativeMenuBar(False) self.statusBar() file_menu = self.menuBar().addMenu("&File") new_tab_action = QAction( QIcon(os.path.join("icons", "ui-tab--plus.png")), "New Tab", self) new_tab_action.setStatusTip("Open a new tab") new_tab_action.triggered.connect(lambda _: self.add_new_tab()) file_menu.addAction(new_tab_action) open_file_action = QAction( QIcon(os.path.join("icons", "disk--arrow.png")), "Open file...", self) open_file_action.setStatusTip("Open from file") open_file_action.triggered.connect(self.open_file) file_menu.addAction(open_file_action) save_file_action = QAction( QIcon(os.path.join("icons", "disk--pencil.png")), "Save Page As...", self) save_file_action.setStatusTip("Save current page to file") save_file_action.triggered.connect(self.save_file) file_menu.addAction(save_file_action) print_action = QAction(QIcon(os.path.join("icons", "printer.png")), "Print...", self) print_action.setStatusTip("Print current page") print_action.triggered.connect(self.print_page) file_menu.addAction(print_action) # Create our system printer instance. self.printer = QPrinter() help_menu = self.menuBar().addMenu("&Help") about_action = QAction( QIcon(os.path.join("icons", "question.png")), "About Mozzarella Ashbadger", self, ) about_action.setStatusTip( "Find out more about Mozzarella Ashbadger") # Hungry! about_action.triggered.connect(self.about) help_menu.addAction(about_action) navigate_mozarella_action = QAction( QIcon(os.path.join("icons", "lifebuoy.png")), "Mozzarella Ashbadger Homepage", self, ) navigate_mozarella_action.setStatusTip( "Go to Mozzarella Ashbadger Homepage") navigate_mozarella_action.triggered.connect(self.navigate_mozarella) help_menu.addAction(navigate_mozarella_action) self.add_new_tab(QUrl("http://www.google.com"), "Homepage") self.show() self.setWindowTitle("Mozzarella Ashbadger") self.setWindowIcon(QIcon(os.path.join("icons", "ma-icon-64.png"))) # tag::addNewTab[] def add_new_tab(self, qurl=None, label="Blank"): if qurl is None: qurl = QUrl("") browser = QWebEngineView() browser.setUrl(qurl) i = self.tabs.addTab(browser, label) self.tabs.setCurrentIndex(i) # end::addNewTab[] # tag::addNewTabSignals[] # More difficult! We only want to update the url when it's from the # correct tab browser.urlChanged.connect( lambda qurl, browser=browser: self.update_urlbar(qurl, browser)) browser.loadFinished.connect(lambda _, i=i, browser=browser: self.tabs. setTabText(i, browser.page().title())) # end::addNewTabSignals[] # tag::tabWidgetSlots[] def tab_open_doubleclick(self, i): if i == -1: # No tab under the click self.add_new_tab() def current_tab_changed(self, i): qurl = self.tabs.currentWidget().url() self.update_urlbar(qurl, self.tabs.currentWidget()) self.update_title(self.tabs.currentWidget()) def close_current_tab(self, i): if self.tabs.count() < 2: return self.tabs.removeTab(i) # end::tabWidgetSlots[] def update_title(self, browser): if browser != self.tabs.currentWidget(): # If this signal is not from the current tab, ignore return title = self.tabs.currentWidget().page().title() self.setWindowTitle("%s - Mozzarella Ashbadger" % title) def navigate_mozarella(self): self.tabs.currentWidget().setUrl(QUrl("https://www.udemy.com/522076")) def about(self): dlg = AboutDialog() dlg.exec_() def open_file(self): filename, _ = QFileDialog.getOpenFileName( self, "Open file", "", "Hypertext Markup Language (*.htm *.html);;" "All files (*.*)", ) if filename: with open(filename, "r") as f: html = f.read() self.tabs.currentWidget().setHtml(html) self.urlbar.setText(filename) def save_file(self): filename, _ = QFileDialog.getSaveFileName( self, "Save Page As", "", "Hypertext Markup Language (*.htm *html);;" "All files (*.*)", ) if filename: # Define callback method to handle the write. def writer(html): with open(filename, "w") as f: f.write(html) self.tabs.currentWidget().page().toHtml(writer) def print_page(self): page = self.tabs.currentWidget().page() def callback(*args): pass dlg = QPrintDialog(self.printer) dlg.accepted.connect(callback) if dlg.exec_() == QDialog.Accepted: page.print(self.printer, callback) def navigate_home(self): self.tabs.currentWidget().setUrl(QUrl("http://www.google.com")) def navigate_to_url(self): # Does not receive the Url q = QUrl(self.urlbar.text()) if q.scheme() == "": q.setScheme("http") self.tabs.currentWidget().setUrl(q) # tag::updateURLbar[] def update_urlbar(self, q, browser=None): if browser != self.tabs.currentWidget(): # If this signal is not from the current tab, ignore return if q.scheme() == "https": # Secure padlock icon self.httpsicon.setPixmap( QPixmap(os.path.join("icons", "lock-ssl.png"))) else: # Insecure padlock icon self.httpsicon.setPixmap( QPixmap(os.path.join("icons", "lock-nossl.png"))) self.urlbar.setText(q.toString()) self.urlbar.setCursorPosition(0)
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.browser = QWebEngineView() self.browser.setUrl(QUrl("http://google.com")) self.setCentralWidget(self.browser) # tag::navigationSignals[] self.browser.urlChanged.connect(self.update_urlbar) self.browser.loadFinished.connect(self.update_title) # end::navigationSignals[] # tag::navigation1[] navtb = QToolBar("Navigation") navtb.setIconSize(QSize(16, 16)) self.addToolBar(navtb) back_btn = QAction(QIcon(os.path.join("icons", "arrow-180.png")), "Back", self) back_btn.setStatusTip("Back to previous page") back_btn.triggered.connect(self.browser.back) navtb.addAction(back_btn) # end::navigation1[] # tag::navigation2[] next_btn = QAction(QIcon(os.path.join("icons", "arrow-000.png")), "Forward", self) next_btn.setStatusTip("Forward to next page") next_btn.triggered.connect(self.browser.forward) navtb.addAction(next_btn) reload_btn = QAction( QIcon(os.path.join("icons", "arrow-circle-315.png")), "Reload", self) reload_btn.setStatusTip("Reload page") reload_btn.triggered.connect(self.browser.reload) navtb.addAction(reload_btn) home_btn = QAction(QIcon(os.path.join("icons", "home.png")), "Home", self) home_btn.setStatusTip("Go home") home_btn.triggered.connect(self.navigate_home) navtb.addAction(home_btn) # end::navigation2[] navtb.addSeparator() # tag::navigation3[] self.httpsicon = QLabel() # Yes, really! self.httpsicon.setPixmap( QPixmap(os.path.join("icons", "lock-nossl.png"))) navtb.addWidget(self.httpsicon) self.urlbar = QLineEdit() self.urlbar.returnPressed.connect(self.navigate_to_url) navtb.addWidget(self.urlbar) stop_btn = QAction(QIcon(os.path.join("icons", "cross-circle.png")), "Stop", self) stop_btn.setStatusTip("Stop loading current page") stop_btn.triggered.connect(self.browser.stop) navtb.addAction(stop_btn) # end::navigation3[] self.menuBar().setNativeMenuBar(False) self.statusBar() # tag::menuFile[] file_menu = self.menuBar().addMenu("&File") open_file_action = QAction( QIcon(os.path.join("icons", "disk--arrow.png")), "Open file...", self) open_file_action.setStatusTip("Open from file") open_file_action.triggered.connect(self.open_file) file_menu.addAction(open_file_action) save_file_action = QAction( QIcon(os.path.join("icons", "disk--pencil.png")), "Save Page As...", self) save_file_action.setStatusTip("Save current page to file") save_file_action.triggered.connect(self.save_file) file_menu.addAction(save_file_action) # end::menuFile[] # tag::menuPrint[] print_action = QAction(QIcon(os.path.join("icons", "printer.png")), "Print...", self) print_action.setStatusTip("Print current page") print_action.triggered.connect(self.print_page) file_menu.addAction(print_action) # Create our system printer instance. self.printer = QPrinter() # end::menuPrint[] # tag::menuHelp[] help_menu = self.menuBar().addMenu("&Help") about_action = QAction( QIcon(os.path.join("icons", "question.png")), "About Mozzarella Ashbadger", self, ) about_action.setStatusTip( "Find out more about Mozzarella Ashbadger") # Hungry! about_action.triggered.connect(self.about) help_menu.addAction(about_action) navigate_mozzarella_action = QAction( QIcon(os.path.join("icons", "lifebuoy.png")), "Mozzarella Ashbadger Homepage", self, ) navigate_mozzarella_action.setStatusTip( "Go to Mozzarella Ashbadger Homepage") navigate_mozzarella_action.triggered.connect(self.navigate_mozzarella) help_menu.addAction(navigate_mozzarella_action) # end::menuHelp[] self.show() self.setWindowIcon(QIcon(os.path.join("icons", "ma-icon-64.png"))) # tag::navigationTitle[] def update_title(self): title = self.browser.page().title() self.setWindowTitle("%s - Mozzarella Ashbadger" % title) # end::navigationTitle[] # tag::menuHelpfn[] def navigate_mozzarella(self): self.browser.setUrl(QUrl("https://www.learnpyqt.com/")) def about(self): dlg = AboutDialog() dlg.exec_() # end::menuHelpfn[] # tag::menuFilefnOpen[] def open_file(self): filename, _ = QFileDialog.getOpenFileName( self, "Open file", "", "Hypertext Markup Language (*.htm *.html);;" "All files (*.*)", ) if filename: with open(filename, "r") as f: html = f.read() self.browser.setHtml(html) self.urlbar.setText(filename) # end::menuFilefnOpen[] # tag::menuFilefnSave[] def save_file(self): filename, _ = QFileDialog.getSaveFileName( self, "Save Page As", "", "Hypertext Markup Language (*.htm *html);;" "All files (*.*)", ) if filename: # Define callback method to handle the write. def writer(html): with open(filename, "w") as f: f.write(html) self.browser.page().toHtml(writer) # end::menuFilefnSave[] # tag::menuPrintfn[] def print_page(self): page = self.browser.page() def callback(*args): pass dlg = QPrintDialog(self.printer) dlg.accepted.connect(callback) if dlg.exec_() == QDialog.Accepted: page.print(self.printer, callback) # end::menuPrintfn[] # tag::navigationHome[] def navigate_home(self): self.browser.setUrl(QUrl("http://www.google.com")) # end::navigationHome[] # tag::navigationURL[] def navigate_to_url(self): # Does not receive the Url q = QUrl(self.urlbar.text()) if q.scheme() == "": q.setScheme("http") self.browser.setUrl(q) # end::navigationURL[] # tag::navigationURLBar[] def update_urlbar(self, q): if q.scheme() == "https": # Secure padlock icon self.httpsicon.setPixmap( QPixmap(os.path.join("icons", "lock-ssl.png"))) else: # Insecure padlock icon self.httpsicon.setPixmap( QPixmap(os.path.join("icons", "lock-nossl.png"))) self.urlbar.setText(q.toString()) self.urlbar.setCursorPosition(0)
class ConfigDialog(QDialog): config_changed = Signal() def __init__(self, requester, config, parent=None): super().__init__(parent) self.requester = requester self.requester.pin_needed.connect(self.input_pin) self.requester.umi_made.connect(self.write_umi) self.requester.msg_passed.connect(self.error_msg) self.config = config self.lbl_id = QLabel('계정명') self.lbl_id.setAlignment(Qt.AlignCenter) self.lbl_pw = QLabel('비밀번호') self.lbl_pw.setAlignment(Qt.AlignCenter) self.lbl_umi = QLabel('umi 쿠키') self.lbl_umi.setAlignment(Qt.AlignCenter) self.lbl_ua = QLabel('유저 에이전트') self.lbl_ua.setAlignment(Qt.AlignCenter) self.lbl_delay = QLabel('저속 간격') self.lbl_delay.setAlignment(Qt.AlignCenter) self.lbl_msg = QLabel('') self.line_id = QLineEdit() self.line_pw = QLineEdit() self.line_pw.setEchoMode(QLineEdit.PasswordEchoOnEdit) self.line_umi = QLineEdit() self.line_umi.setPlaceholderText('로그인 시 자동 입력') self.line_ua = QLineEdit() self.line_delay = QDoubleSpinBox() self.line_delay.setMinimum(3) self.line_delay.setDecimals(1) self.line_delay.setSuffix('초') self.line_delay.setSingleStep(0.1) self.btn_save = NPButton('저장', 10, self) self.btn_save.clicked.connect(self.save) self.btn_cancel = NPButton('취소', 10, self) self.btn_cancel.clicked.connect(self.reject) self.btn_get_umi = NPButton('로그인', 10, self) self.btn_get_umi.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.MinimumExpanding) self.btn_get_umi.clicked.connect(self.get_umi) grid = QGridLayout() grid.addWidget(self.lbl_id, 0, 0, 1, 1) grid.addWidget(self.line_id, 0, 1, 1, 6) grid.addWidget(self.lbl_pw, 1, 0, 1, 1) grid.addWidget(self.line_pw, 1, 1, 1, 6) grid.addWidget(self.btn_get_umi, 0, 7, 2, 2) grid.addWidget(self.lbl_umi, 2, 0, 1, 1) grid.addWidget(self.line_umi, 2, 1, 1, 8) grid.addWidget(self.lbl_ua, 3, 0, 1, 1) grid.addWidget(self.line_ua, 3, 1, 1, 8) grid.addWidget(self.lbl_delay, 4, 0, 1, 1) grid.addWidget(self.line_delay, 4, 1, 1, 8) grid.addWidget(self.lbl_msg, 5, 0, 1, 4) grid.addWidget(self.btn_save, 5, 5, 1, 2) grid.addWidget(self.btn_cancel, 5, 7, 1, 2) self.setLayout(grid) self.input_dialog = InputDialog(self) self.input_dialog.input.setInputMask('999999') self.setWindowTitle('개인정보') self.setWindowIcon(QIcon('icon.png')) self.setStyleSheet('font: 10pt \'맑은 고딕\'') self.setWindowFlag(Qt.WindowContextHelpButtonHint, False) self.c_login, self.c_work = {}, {} @Slot(str) def error_msg(self, t): self.lbl_msg.setText(t) def load(self): self.line_id.setText(self.config.c['login']['ID']) self.line_pw.setText(self.config.c['login']['PW']) self.line_umi.setText(self.config.c['login']['UMI']) self.line_ua.setText(self.config.c['login']['UA']) self.line_ua.setCursorPosition(0) self.line_delay.setValue(float(self.config.c['work']['DELAY'])) self.lbl_msg.clear() ok = self.exec_() if ok == QDialog.Accepted: self.config_changed.emit() def save(self): self.config.save(login={ 'ID': self.line_id.text().strip(), 'PW': self.line_pw.text().strip(), 'UMI': self.line_umi.text().strip(), 'UA': self.line_ua.text().strip() }, delay=self.line_delay.value()) self.accept() def get_umi(self): self.lbl_msg.setText('로그인 시도...') self.line_umi.clear() self.input_dialog.input.clear() self.requester.init_login(self.line_id.text().strip(), self.line_pw.text().strip()) @Slot(str) def write_umi(self, umi): self.line_umi.setText(umi) @Slot(str) def input_pin(self, mail): pin, ok = self.input_dialog.get_text('로그인 PIN 입력', f'이메일({mail})로 전송된 PIN을 입력해주세요.') if ok: if pin: self.requester.typed_pin = pin else: self.requester.typed_pin = 'nothing' else: self.requester.typed_pin = 'deny'
class PiecesPlayer(QWidget): """ main widget of application (used as widget inside PiecesMainWindow) """ def __init__(self, parent): """ standard constructor: set up class variables, ui elements and layout """ # TODO: split current piece info into separate lineedits for title, album name and length # TODO: make time changeable by clicking next to the slider (not only # by dragging the slider) # TODO: add "about" action to open info dialog in new "help" menu # TODO: add option to loop current piece (?) # TODO: more documentation # TODO: add some "whole piece time remaining" indicator? (complicated) # TODO: implement a playlist of pieces that can be edited and enable # going back to the previous piece (also un- and re-shuffling?) # TODO: implement debug dialog as menu action (if needed) if not isinstance(parent, PiecesMainWindow): raise ValueError('Parent widget must be a PiecesMainWindow') super(PiecesPlayer, self).__init__(parent=parent) # -- declare and setup variables for storing information -- # various data self._set_str = '' # string of currently loaded directory sets self._pieces = {} # {<piece1>: [<files piece1 consists of>], ...} self._playlist = [] # list of keys of self._pieces (determines order) self._shuffled = True # needed for (maybe) reshuffling when looping # doc for self._history: # key: timestamp ('HH:MM:SS'), # value: info_str of piece that started playing at that time self._history = {} self._status = 'Paused' self._current_piece = {'title': '', 'files': [], 'play_next': 0} self._default_volume = 60 # in percent from 0 - 100 self._volume_before_muted = self._default_volume # set to true by self.__event_movement_ended and used by self.__update self._skip_to_next = False # vlc-related variables self._vlc_instance = VLCInstance() self._vlc_mediaplayer = self._vlc_instance.media_player_new() self._vlc_mediaplayer.audio_set_volume(self._default_volume) self._vlc_medium = None self._vlc_events = self._vlc_mediaplayer.event_manager() # -- create and setup ui elements -- # buttons self._btn_play_pause = QPushButton(QIcon(get_icon_path('play')), '') self._btn_previous = QPushButton(QIcon(get_icon_path('previous')), '') self._btn_next = QPushButton(QIcon(get_icon_path('next')), '') self._btn_volume = QPushButton(QIcon(get_icon_path('volume-high')), '') self._btn_loop = QPushButton(QIcon(get_icon_path('loop')), '') self._btn_loop.setCheckable(True) self._btn_play_pause.clicked.connect(self.__action_play_pause) self._btn_previous.clicked.connect(self.__action_previous) self._btn_next.clicked.connect(self.__action_next) self._btn_volume.clicked.connect(self.__action_volume_clicked) # labels self._lbl_current_piece = QLabel('Current piece:') self._lbl_movements = QLabel('Movements:') self._lbl_time_played = QLabel('00:00') self._lbl_time_left = QLabel('-00:00') self._lbl_volume = QLabel('100%') # needed so that everything has the same position # independent of the number of digits of volume self._lbl_volume.setMinimumWidth(55) # sliders self._slider_time = QSlider(Qt.Horizontal) self._slider_volume = QSlider(Qt.Horizontal) self._slider_time.sliderReleased.connect( self.__event_time_changed_by_user) self._slider_volume.valueChanged.connect(self.__event_volume_changed) self._slider_time.setRange(0, 100) self._slider_volume.setRange(0, 100) self._slider_volume.setValue(self._default_volume) self._slider_volume.setMinimumWidth(100) # other elements self._checkbox_loop_playlist = QCheckBox('Loop playlist') self._lineedit_current_piece = QLineEdit() self._lineedit_current_piece.setReadOnly(True) self._lineedit_current_piece.textChanged.connect( self.__event_piece_text_changed) self._listwidget_movements = QListWidget() self._listwidget_movements.itemClicked.connect( self.__event_movement_selected) # -- create layout and insert ui elements-- self._layout = QVBoxLayout(self) # row 0 (name of current piece) self._layout_piece_name = QHBoxLayout() self._layout_piece_name.addWidget(self._lbl_current_piece) self._layout_piece_name.addWidget(self._lineedit_current_piece) self._layout.addLayout(self._layout_piece_name) # rows 1 - 5 (movements of current piece) self._layout.addWidget(self._lbl_movements) self._layout.addWidget(self._listwidget_movements) # row 6 (time) self._layout_time = QHBoxLayout() self._layout_time.addWidget(self._lbl_time_played) self._layout_time.addWidget(self._slider_time) self._layout_time.addWidget(self._lbl_time_left) self._layout.addLayout(self._layout_time) # row 7 (buttons and volume) self._layout_buttons_and_volume = QHBoxLayout() self._layout_buttons_and_volume.addWidget(self._btn_play_pause) self._layout_buttons_and_volume.addWidget(self._btn_previous) self._layout_buttons_and_volume.addWidget(self._btn_next) self._layout_buttons_and_volume.addWidget(self._btn_loop) self._layout_buttons_and_volume.addSpacing(40) # distance between loop and volume buttons: min. 40, but stretchable self._layout_buttons_and_volume.addStretch() self._layout_buttons_and_volume.addWidget(self._btn_volume) self._layout_buttons_and_volume.addWidget(self._slider_volume) self._layout_buttons_and_volume.addWidget(self._lbl_volume) self._layout.addLayout(self._layout_buttons_and_volume) # -- setup hotkeys -- self._KEY_CODES_PLAY_PAUSE = [269025044] self._KEY_CODES_NEXT = [269025047] self._KEY_CODES_PREVIOUS = [269025046] self._keyboard_listener = keyboard.Listener(on_press=self.__on_press) self._keyboard_listener.start() QShortcut(QKeySequence('Space'), self, self.__action_play_pause) # -- various setup -- self._timer = QTimer(self) self._timer.timeout.connect(self.__update) self._timer.start(100) # update every 100ms self.setMinimumWidth(900) self.setMinimumHeight(400) # get directory set(s) input and set up self._pieces # (exec_ means we'll wait for the user input before continuing) DirectorySetChooseDialog(self, self.set_pieces_and_playlist).exec_() # skip to next movement / next piece when current one has ended self._vlc_events.event_attach(VLCEventType.MediaPlayerEndReached, self.__event_movement_ended) def __action_next(self): """ switches to next file in self._current_piece['files'] or to the next piece, if the current piece has ended """ reset_pause_after_current = False # current movement is last of the current piece if self._current_piece['play_next'] == -1: if len(self._playlist) == 0: # reached end of playlist if self._btn_loop.isChecked(): self._playlist = list(self._pieces.keys()) if self._shuffled: shuffle(self._playlist) return if self._status == 'Playing': self.__action_play_pause() self._current_piece['title'] = '' self._current_piece['files'] = [] self._current_piece['play_next'] = -1 self._lineedit_current_piece.setText('') self.__update_movement_list() self.parentWidget().update_status_bar( self._status, 'End of playlist reached.') return else: if self.parentWidget().get_exit_after_current(): self.parentWidget().exit() if self.parentWidget().get_pause_after_current(): self.__action_play_pause() reset_pause_after_current = True # reset of the menu action will be at the end of this # function, or else we won't stay paused self._current_piece['title'] = self._playlist.pop(0) self._current_piece['files'] = [ p[1:-1] for p in self._pieces[self._current_piece['title']] ] # some pieces only have one movement self._current_piece['play_next'] = \ 1 if len(self._current_piece['files']) > 1 else -1 self.__update_vlc_medium(0) self._lineedit_current_piece.setText( create_info_str(self._current_piece['title'], self._current_piece['files'])) self.__update_movement_list() self._history[datetime.now().strftime('%H:%M:%S')] = \ self._lineedit_current_piece.text() else: self.__update_vlc_medium(self._current_piece['play_next']) # next is last movement if self._current_piece['play_next'] == \ len(self._current_piece['files']) - 1: self._current_piece['play_next'] = -1 else: # there are at least two movements of current piece left self._current_piece['play_next'] += 1 if self._status == 'Paused' and not reset_pause_after_current: self.__action_play_pause() elif reset_pause_after_current: self.parentWidget().set_pause_after_current(False) else: self._vlc_mediaplayer.play() self.parentWidget().update_status_bar( self._status, f'{len(self._pieces) - len(self._playlist)}/{len(self._pieces)}') def __action_play_pause(self): """ (gets called when self._btn_play_pause is clicked) toggles playing/pausing music and updates everything as needed """ # don't do anything now (maybe end of playlist reached?) if self._current_piece['title'] == '': return if self._status == 'Paused': if not self._vlc_medium: self.__action_next() self._vlc_mediaplayer.play() self._btn_play_pause.setIcon(QIcon(get_icon_path('pause'))) self._status = 'Playing' else: self._vlc_mediaplayer.pause() self._btn_play_pause.setIcon(QIcon(get_icon_path('play'))) self._status = 'Paused' self.parentWidget().update_status_bar( self._status, f'{len(self._pieces) - len(self._playlist)}/{len(self._pieces)}') def __action_previous(self): """ (called when self._btn_previous ist clicked) goes back one movement of the current piece, if possible (cannot go back to previous piece) """ # can't go back to previous piece, but current one has no or one movement if len(self._current_piece['files']) <= 1: pass # currently playing first movement, so nothing to do as well elif self._current_piece['play_next'] == 1: pass else: # we can go back one movement # currently at last movement if self._current_piece['play_next'] == -1: # set play_next to last movement self._current_piece['play_next'] = \ len(self._current_piece['files']) - 1 else: # currently before last movement # set play_next to current movement self._current_piece['play_next'] -= 1 self._vlc_mediaplayer.stop() self.__update_vlc_medium(self._current_piece['play_next'] - 1) self._vlc_mediaplayer.play() def __action_volume_clicked(self): """ (called when self._btn_volume is clicked) (un)mutes volume """ if self._slider_volume.value() == 0: # unmute volume self._slider_volume.setValue(self._volume_before_muted) else: # mute volume self._volume_before_muted = self._slider_volume.value() self._slider_volume.setValue(0) def __event_movement_ended(self, event): """ (called when self._vlc_media_player emits a MediaPlayerEndReached event) sets self._skip_to_next to True so the next self.__update call will trigger self.__action_next """ self._skip_to_next = True def __event_movement_selected(self): """ (called when self._listwidget_movements emits itemClicked) skips to the newly selected movement """ index = self._listwidget_movements.indexFromItem( self._listwidget_movements.currentItem()).row() # user selected a movement different from the current one if index != self.__get_current_movement_index(): self._current_piece['play_next'] = index self.__action_next() def __event_piece_text_changed(self): """ (called when self._lineedit_current_piece emits textChanged) ensures that the user sees the beginning of the text in self._lineedit_current_piece (if text is too long, the end will be cut off and the user must scroll manually to see it) """ self._lineedit_current_piece.setCursorPosition(0) def __event_volume_changed(self): """ (called when value of self._slider_volume changes) updates text of self._lbl_volume to new value of self._slider_value and sets icon of self._btn_volume to a fitting one """ volume = self._slider_volume.value() self._lbl_volume.setText(f'{volume}%') if volume == 0: self._btn_volume.setIcon(QIcon(get_icon_path('volume-muted'))) elif volume < 34: self._btn_volume.setIcon(QIcon(get_icon_path('volume-low'))) elif volume < 67: self._btn_volume.setIcon(QIcon(get_icon_path('volume-medium'))) else: self._btn_volume.setIcon(QIcon(get_icon_path('volume-high'))) self._vlc_mediaplayer.audio_set_volume(volume) def __event_time_changed_by_user(self): """ (called when user releases self._slider_time) synchronizes self._vlc_mediaplayer's position to the new value of self._slider_time """ self._vlc_mediaplayer.set_position(self._slider_time.value() / 100) def __get_current_movement_index(self): """ returns the index of the current movement in self._current_piece['files'] """ play_next = self._current_piece['play_next'] if play_next == -1: return len(self._current_piece['files']) - 1 else: return play_next - 1 def __on_press(self, key): """ (called by self._keyboard_listener when a key is pressed) looks up key code corresponding to key and calls the appropriate action function """ try: # key is not always of the same type (why would it be?!) key_code = key.vk except AttributeError: key_code = key.value.vk if key_code in self._KEY_CODES_PLAY_PAUSE: self.__action_play_pause() elif key_code in self._KEY_CODES_NEXT: self.__action_next() elif key_code in self._KEY_CODES_PREVIOUS: self.__action_previous() def __update_movement_list(self): """ removes all items currently in self._listwidget_movements and adds everything in self._current_piece['files] """ # TODO: use ID3 title instead of filename while self._listwidget_movements.count() > 0: self._listwidget_movements.takeItem(0) files = self._current_piece['files'] if os_name == 'nt': # windows paths look different than posix paths # remove path to file, title number and .mp3 ending files = [i[i.rfind('\\') + 3:-4] for i in files] else: files = [i[i.rfind('/') + 4:-4] for i in files] self._listwidget_movements.addItems(files) def __update(self): """ (periodically called when self._timer emits timeout signal) updates various ui elements""" # -- select currently playing movement in self._listwidget_movements -- if self._listwidget_movements.count() > 0: self._listwidget_movements.item( self.__get_current_movement_index()).setSelected(True) # -- update text of self._lbl_time_played and self._lbl_time_left, # if necessary -- if self._vlc_medium: try: time_played = self._vlc_mediaplayer.get_time() medium_duration = self._vlc_medium.get_duration() # other values don't make sense (but do occur) if (time_played >= 0) and (time_played <= medium_duration): self._lbl_time_played.setText( get_time_str_from_ms(time_played)) else: self._lbl_time_played.setText(get_time_str_from_ms(0)) self._lbl_time_left.setText( f'-{get_time_str_from_ms(medium_duration - time_played)}') except OSError: # don't know why that occurs sometimes pass # -- update value of self._slider_time -- # don't reset slider to current position if user is dragging it if not self._slider_time.isSliderDown(): try: self._slider_time.setValue( self._vlc_mediaplayer.get_position() * 100) except OSError: # don't know why that occurs sometimes pass if self._skip_to_next: self._skip_to_next = False self.__action_next() def __update_vlc_medium(self, files_index): old_medium = self._vlc_medium self._vlc_medium = self._vlc_instance.media_new( self._current_piece['files'][files_index]) self._vlc_medium.parse() self._vlc_mediaplayer.set_media(self._vlc_medium) if old_medium: # only release if not None old_medium.release() def get_history(self): """ getter function for parent widget """ return self._history def get_set_str(self): """ getter function for parent widget """ return self._set_str if self._set_str != '' \ else 'No directory set loaded.' def set_pieces_and_playlist(self, pieces, playlist, set_str, shuffled): """ needed so that DirectorySetChooseDialog can set our self._pieces and self._playlist """ # just to be sure if isinstance(pieces, dict) and isinstance(playlist, list): self._vlc_mediaplayer.stop() self._set_str = set_str self._pieces = pieces self._playlist = playlist self._shuffled = shuffled self._current_piece['title'] = self._playlist.pop(0) self._current_piece['files'] = [ p.replace('"', '') for p in self._pieces[self._current_piece['title']] ] self._current_piece['play_next'] = \ 1 if len(self._current_piece['files']) > 1 else -1 self._lineedit_current_piece.setText( create_info_str(self._current_piece['title'], self._current_piece['files'])) self.__update_movement_list() self.__update_vlc_medium(0) self._history[datetime.now().strftime('%H:%M:%S')] = \ self._lineedit_current_piece.text() def exit(self): """ exits cleanly """ try: # don't know why that occurs sometimes self._vlc_mediaplayer.stop() self._vlc_mediaplayer.release() self._vlc_instance.release() except OSError: pass self._keyboard_listener.stop()
class CommandWidget(TabWidgetExtension, QWidget): """Output for running queue""" # log state __log = False insertTextSignal = Signal(str, dict) updateCommandSignal = Signal(str) cliButtonsSateSignal = Signal(bool) cliValidateSignal = Signal(bool) resetSignal = Signal() def __init__(self, parent=None, proxyModel=None, controlQueue=None, log=None): super(CommandWidget, self).__init__(parent=parent, tabWidgetChild=self) self.__log = log self.__output = None self.__rename = None self.__tab = None # self.oCommand = MKVCommand() self.algorithm = None self.oCommand = MKVCommandParser() self.controlQueue = controlQueue self.parent = parent self.proxyModel = proxyModel self.model = proxyModel.sourceModel() self.outputWindow = QOutputTextWidget(self) self.log = log self._initControls() self._initUI() self._initHelper() def _initControls(self): # # command line # self.frmCmdLine = QFormLayout() btnPasteClipboard = QPushButtonWidget( Text.txt0164, function=lambda: qtRunFunctionInThread(self.pasteClipboard), margins=" ", toolTip=Text.txt0165, ) self.cmdLine = QLineEdit() self.cmdLine.setValidator( ValidateCommand(self, self.cliValidateSignal, log=self.log) ) self.frmCmdLine.addRow(btnPasteClipboard, self.cmdLine) self.frmCmdLine.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) self.command = QWidget() self.command.setLayout(self.frmCmdLine) # # Button group definition # self.btnGroup = QGroupBox() self.btnGrid = QGridLayout() btnAddCommand = QPushButtonWidget( Text.txt0160, function=lambda: self.addCommand(JobStatus.Waiting), margins=" ", toolTip=Text.txt0161, ) btnRename = QPushButtonWidget( Text.txt0182, function=self.parent.renameWidget.setAsCurrentTab, margins=" ", toolTip=Text.txt0183, ) btnAddQueue = QPushButtonWidget( Text.txt0166, function=lambda: self.addCommand(JobStatus.AddToQueue), margins=" ", toolTip=Text.txt0167, ) btnStartQueue = QPushButtonWidget( Text.txt0126, function=self.parent.jobsQueue.run, margins=" ", toolTip=Text.txt0169, ) btnAnalysis = QPushButtonWidget( Text.txt0170, function=lambda: qtRunFunctionInThread( runAnalysis, command=self.cmdLine.text(), output=self.output, log=self.log, ), margins=" ", toolTip=Text.txt0171, ) btnShowCommands = QPushButtonWidget( Text.txt0172, function=lambda: qtRunFunctionInThread( showCommands, output=self.output, command=self.cmdLine.text(), oCommand=self.oCommand, log=self.log, ), margins=" ", toolTip=Text.txt0173, ) btnCheckFiles = QPushButtonWidget( Text.txt0174, function=lambda: qtRunFunctionInThread( checkFiles, output=self.output, command=self.cmdLine.text(), oCommand=self.oCommand, log=self.log, ), margins=" ", toolTip=Text.txt0175, ) btnClear = QPushButtonWidget( Text.txt0176, function=self.clearOutputWindow, margins=" ", toolTip=Text.txt0177, ) btnReset = QPushButtonWidget( Text.txt0178, function=self.reset, margins=" ", toolTip=Text.txt0179, ) self.btnGrid.addWidget(btnAddCommand, 0, 0) self.btnGrid.addWidget(btnRename, 0, 1) self.btnGrid.addWidget(btnAddQueue, 1, 0) self.btnGrid.addWidget(btnStartQueue, 1, 1) self.btnGrid.addWidget(HorizontalLine(), 2, 0, 1, 2) self.btnGrid.addWidget(btnAnalysis, 3, 0) self.btnGrid.addWidget(btnShowCommands, 3, 1) self.btnGrid.addWidget(btnCheckFiles, 4, 0) self.btnGrid.addWidget(HorizontalLine(), 5, 0, 1, 2) self.btnGrid.addWidget(btnClear, 6, 0) self.btnGrid.addWidget(btnReset, 6, 1) self.btnGroup.setLayout(self.btnGrid) self.btnGroupBox = QGroupBox() self.btnHBox = QHBoxLayout() self.lblAlgorithm = QLabelWidget( Text.txt0094, textSuffix=": ", ) self.rbZero = QRadioButton("0", self) self.rbOne = QRadioButton("1", self) self.rbTwo = QRadioButton("2", self) btnDefaultAlgorithm = QPushButtonWidget( Text.txt0092, function=self.setDefaultAlgorithm, margins=" ", toolTip=Text.txt0093, ) self.radioButtons = [self.rbZero, self.rbOne, self.rbTwo] self.btnHBox.addWidget(self.lblAlgorithm) self.btnHBox.addWidget(self.rbZero) self.btnHBox.addWidget(self.rbOne) self.btnHBox.addWidget(self.rbTwo) self.btnHBox.addWidget(btnDefaultAlgorithm) self.btnGroupBox.setLayout(self.btnHBox) def _initUI(self): grid = QGridLayout() grid.addWidget(self.command, 0, 0, 1, 2) grid.addWidget(self.btnGroupBox, 1, 0) grid.addWidget(self.btnGroup, 2, 0) grid.addWidget(self.outputWindow, 2, 1, 10, 1) self.setLayout(grid) def _initHelper(self): # # Signal interconnections # # local button state connect to related state self.parent.jobsQueue.addQueueItemSignal.connect( lambda: self.jobStartQueueState(True) ) self.parent.jobsQueue.queueEmptiedSignal.connect( lambda: self.jobStartQueueState(False) ) # job related self.parent.jobsQueue.runJobs.startSignal.connect(lambda: self.jobStatus(True)) self.parent.jobsQueue.runJobs.finishedSignal.connect( lambda: self.jobStatus(False) ) # map insertText signal to outputWidget one self.insertText = self.outputWindow.insertTextSignal # command self.updateCommandSignal.connect(self.updateCommand) self.cliButtonsSateSignal.connect(self.cliButtonsState) self.cliValidateSignal.connect(self.cliValidate) # # button state # # Command related # self.frmCmdLine.itemAt(0, QFormLayout.LabelRole).widget().setEnabled(False) self.cliButtonsState(False) self.btnGrid.itemAt(_Button.ANALYSIS).widget().setEnabled(False) # Clear buttons related self.btnGrid.itemAt(_Button.CLEAR).widget().setEnabled(False) self.btnGrid.itemAt(_Button.RESET).widget().setEnabled(False) # connect text windows textChanged to clearButtonState function self.outputWindow.textChanged.connect(self.clearButtonState) # connect command line textChanged to analysisButtonState function self.cmdLine.textChanged.connect(self.analysisButtonState) # Job Queue related self.btnGrid.itemAt(_Button.STARTQUEUE).widget().setEnabled(False) # Job Added to Queue self.parent.jobsQueue.addQueueItemSignal.connect(self.printJobIDAdded) # # Misc # self.cmdLine.setClearButtonEnabled(True) # button at end of line to clear it # Algorithm radio buttons self.rbZero.toggled.connect(lambda: self.toggledRadioButton(self.rbZero)) self.rbOne.toggled.connect(lambda: self.toggledRadioButton(self.rbOne)) self.rbTwo.toggled.connect(lambda: self.toggledRadioButton(self.rbTwo)) self.setDefaultAlgorithm() @classmethod def classLog(cls, setLogging=None): """ get/set logging at class level every class instance will log unless overwritten Args: setLogging (bool): - True class will log - False turn off logging - None returns current Value Returns: bool: returns the current value set """ if setLogging is not None: if isinstance(setLogging, bool): cls.__log = setLogging return cls.__log @property def log(self): """ class property can be used to override the class global logging setting Returns: bool: True if logging is enable False otherwise """ if self.__log is not None: return self.__log return CommandWidget.classLog() @log.setter def log(self, value): """set instance log variable""" if isinstance(value, bool) or value is None: self.__log = value # No variable used so for now use class log ValidateCommand.classLog(value) self.outputWindow.log = value @property def output(self): return self.__output @output.setter def output(self, value): self.__output = value @property def rename(self): return self.__rename @rename.setter def rename(self, value): if isinstance(value, object): self.__rename = value @Slot(list) def applyRename(self, renameFiles): if self.oCommand: self.oCommand.renameOutputFiles(renameFiles) @Slot(bool) def cliButtonsState(self, validateOK): """ cliButtonsState change enabled status for buttons related with command line Args: validateOK (bool): True to enable, False to disable """ for b in [ _Button.ADDCOMMAND, _Button.RENAME, _Button.ADDQUEUE, _Button.SHOWCOMMANDS, _Button.CHECKFILES, ]: button = self.btnGrid.itemAt(b).widget() button.setEnabled(validateOK) @Slot(bool) def cliValidate(self, validateOK): """ cliValidate Slot used by ValidateCommnad Args: validateOK (bool): True if command line is Ok. False otherwise. """ if validateOK: self.output.command.emit( "Command looks ok.\n", {LineOutput.AppendEnd: True} ) else: if self.cmdLine.text() != "": self.output.command.emit("Bad command.\n", {LineOutput.AppendEnd: True}) self.cliButtonsState(validateOK) self.updateObjCommnad(validateOK) @Slot(bool) def jobStartQueueState(self, state): if state and not isThreadRunning(config.WORKERTHREADNAME): self.btnGrid.itemAt(_Button.STARTQUEUE).widget().setEnabled(True) else: self.btnGrid.itemAt(_Button.STARTQUEUE).widget().setEnabled(False) @Slot(bool) def updateObjCommnad(self, valid): """Update the command object""" if valid: self.oCommand.command = self.cmdLine.text() if self.rename is not None: self.rename.setFilesSignal.emit(self.oCommand) self.rename.applyFileRenameSignal.connect(self.applyRename) else: self.oCommand.command = "" if self.rename is not None: self.rename.clear() @Slot(str) def updateCommand(self, command): """Update command input widget""" self.cmdLine.clear() self.cmdLine.setText(command) self.cmdLine.setCursorPosition(0) @Slot(int) def updateAlgorithm(self, algorithm): if 0 <= algorithm < len(self.radioButtons): self.radioButtons[algorithm].setChecked(True) @Slot(bool) def jobStatus(self, running): """ jobStatus receive Signals for job start/end Args: running (bool): True if job started. False if ended. """ if running: self.jobStartQueueState(False) palette = QPalette() color = checkColor( QColor(42, 130, 218), config.data.get(config.ConfigKey.DarkMode) ) palette.setColor(QPalette.WindowText, color) self.parent.jobsLabel.setPalette(palette) else: palette = QPalette() color = checkColor(None, config.data.get(config.ConfigKey.DarkMode)) palette.setColor(QPalette.WindowText, color) self.parent.jobsLabel.setPalette(palette) def addCommand(self, status): """ addCommand add command row in jobs table Args: status (JobStatus): Status for job to be added should be either JobStatus.Waiting or JobStatus.AddToQueue """ totalJobs = self.model.rowCount() command = self.cmdLine.text() # [cell value, tooltip, obj] data = [ ["", "", self.algorithm], [status, "Status code", None], [command, command, self.oCommand], ] self.model.insertRows(totalJobs, 1, data=data) self.cmdLine.clear() def analysisButtonState(self): """Set clear button state""" if self.cmdLine.text() != "": self.btnGrid.itemAt(_Button.ANALYSIS).widget().setEnabled(True) else: self.btnGrid.itemAt(_Button.ANALYSIS).widget().setEnabled(False) def clearButtonState(self): """Set clear button state""" if self.outputWindow.toPlainText() != "": self.btnGrid.itemAt(_Button.CLEAR).widget().setEnabled(True) else: self.btnGrid.itemAt(_Button.CLEAR).widget().setEnabled(False) def clearOutputWindow(self): """ clearOutputWindow clear the command output window """ language = config.data.get(config.ConfigKey.Language) bAnswer = False # Clear output window? title = "Clear output" msg = "¿" if language == "es" else "" msg += "Clear output window" + "?" bAnswer = yesNoDialog(self, msg, title) if bAnswer: self.outputWindow.clear() def printJobIDAdded(self, index): jobID = self.model.dataset[index.row(), index.column()] self.output.command.emit( f"Job: {jobID} added to Queue...\n", {LineOutput.AppendEnd: True} ) def pasteClipboard(self): """Paste clipboard to command QLineEdit""" clip = QApplication.clipboard().text() if clip: self.output.command.emit( "Checking command...\n", {LineOutput.AppendEnd: True} ) self.update() self.updateCommandSignal.emit(clip) def reset(self): """ reset program status """ language = config.data.get(config.ConfigKey.Language) if not isThreadRunning(config.WORKERTHREADNAME): language = config.data.get(config.ConfigKey.Language) bAnswer = False # Clear output window? title = "Reset" msg = "¿" if language == "es" else "" msg += "Reset Application" + "?" bAnswer = yesNoDialog(self, msg, title) if bAnswer: self.cmdLine.clear() self.outputWindow.clear() self.output.jobOutput.clear() self.output.errorOutput.clear() self.resetSignal.emit() else: messageBox(self, "Reset", "Jobs are running..") def resetButtonState(self): """Set clear button state""" if self.output.jobOutput.toPlainText() != "": self.btnGrid.itemAt(_Button.RESET).widget().setEnabled(True) else: self.btnGrid.itemAt(_Button.RESET).widget().setEnabled(False) def setDefaultAlgorithm(self): # # Algorithm # if config.data.get(config.ConfigKey.Algorithm) is not None: currentAlgorithm = config.data.get(config.ConfigKey.Algorithm) self.radioButtons[currentAlgorithm].setChecked(True) def setLanguage(self): """ setLanguage language use in buttons/labels to be called by MainWindow """ for index in range(self.frmCmdLine.rowCount()): widget = self.frmCmdLine.itemAt(index, QFormLayout.LabelRole).widget() if isinstance(widget, QPushButtonWidget): widget.setLanguage() # widget.setText(" " + _(widget.originalText) + " ") # widget.setToolTip(_(widget.toolTip)) for index in range(self.btnHBox.count()): widget = self.btnHBox.itemAt(index).widget() if isinstance( widget, ( QLabelWidget, QPushButtonWidget, ), ): widget.setLanguage() for index in range(self.btnGrid.count()): widget = self.btnGrid.itemAt(index).widget() if isinstance(widget, QPushButtonWidget): widget.setLanguage() # widget.setText(" " + _(widget.originalText) + " ") # widget.setToolTip(_(widget.toolTip)) def toggledRadioButton(self, rButton): for index, rb in enumerate(self.radioButtons): if rb.isChecked(): self.algorithm = index
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.browser = QWebEngineView() self.browser.setUrl(QUrl("http://google.com")) self.browser.urlChanged.connect(self.update_urlbar) self.browser.loadFinished.connect(self.update_title) self.setCentralWidget(self.browser) self.status = QStatusBar() self.setStatusBar(self.status) navtb = QToolBar("Navigation") navtb.setIconSize(QSize(16, 16)) self.addToolBar(navtb) back_btn = QAction(QIcon(os.path.join('images', 'arrow-180.png')), "Back", self) back_btn.setStatusTip("Back to previous page") back_btn.triggered.connect(self.browser.back) navtb.addAction(back_btn) next_btn = QAction(QIcon(os.path.join('images', 'arrow-000.png')), "Forward", self) next_btn.setStatusTip("Forward to next page") next_btn.triggered.connect(self.browser.forward) navtb.addAction(next_btn) reload_btn = QAction( QIcon(os.path.join('images', 'arrow-circle-315.png')), "Reload", self) reload_btn.setStatusTip("Reload page") reload_btn.triggered.connect(self.browser.reload) navtb.addAction(reload_btn) home_btn = QAction(QIcon(os.path.join('images', 'home.png')), "Home", self) home_btn.setStatusTip("Go home") home_btn.triggered.connect(self.navigate_home) navtb.addAction(home_btn) navtb.addSeparator() self.httpsicon = QLabel() # Yes, really! self.httpsicon.setPixmap( QPixmap(os.path.join('images', 'lock-nossl.png'))) navtb.addWidget(self.httpsicon) self.urlbar = QLineEdit() self.urlbar.returnPressed.connect(self.navigate_to_url) navtb.addWidget(self.urlbar) stop_btn = QAction(QIcon(os.path.join('images', 'cross-circle.png')), "Stop", self) stop_btn.setStatusTip("Stop loading current page") stop_btn.triggered.connect(self.browser.stop) navtb.addAction(stop_btn) # Uncomment to disable native menubar on Mac # self.menuBar().setNativeMenuBar(False) file_menu = self.menuBar().addMenu("&File") open_file_action = QAction( QIcon(os.path.join('images', 'disk--arrow.png')), "Open file...", self) open_file_action.setStatusTip("Open from file") open_file_action.triggered.connect(self.open_file) file_menu.addAction(open_file_action) save_file_action = QAction( QIcon(os.path.join('images', 'disk--pencil.png')), "Save Page As...", self) save_file_action.setStatusTip("Save current page to file") save_file_action.triggered.connect(self.save_file) file_menu.addAction(save_file_action) print_action = QAction(QIcon(os.path.join('images', 'printer.png')), "Print...", self) print_action.setStatusTip("Print current page") print_action.triggered.connect(self.print_page) file_menu.addAction(print_action) help_menu = self.menuBar().addMenu("&Help") about_action = QAction(QIcon(os.path.join('images', 'question.png')), "About MooseAche", self) about_action.setStatusTip("Find out more about MooseAche") # Hungry! about_action.triggered.connect(self.about) help_menu.addAction(about_action) navigate_mozarella_action = QAction( QIcon(os.path.join('images', 'lifebuoy.png')), "MooseAche Homepage", self) navigate_mozarella_action.setStatusTip("Go to MooseAche Homepage") navigate_mozarella_action.triggered.connect(self.navigate_mozarella) help_menu.addAction(navigate_mozarella_action) self.show() self.setWindowIcon(QIcon(os.path.join('images', 'ma-icon-64.png'))) def update_title(self): title = self.browser.page().title() self.setWindowTitle("%s - MooseAche" % title) def navigate_mozarella(self): self.browser.setUrl(QUrl("https://www.udemy.com/522076")) def about(self): dlg = AboutDialog() dlg.exec_() def open_file(self): filename, _ = QFileDialog.getOpenFileName( self, "Open file", "", "Hypertext Markup Language (*.htm *.html);;" "All files (*.*)") if filename: with open(filename, 'r') as f: html = f.read() self.browser.setHtml(html) self.urlbar.setText(filename) def save_file(self): filename, _ = QFileDialog.getSaveFileName( self, "Save Page As", "", "Hypertext Markup Language (*.htm *html);;" "All files (*.*)") if filename: html = self.browser.page().toHtml() with open(filename, 'w') as f: f.write(html) def print_page(self): dlg = QPrintPreviewDialog() dlg.paintRequested.connect(self.browser.print_) dlg.exec_() def navigate_home(self): self.browser.setUrl(QUrl("http://www.google.com")) def navigate_to_url(self): # Does not receive the Url q = QUrl(self.urlbar.text()) if q.scheme() == "": q.setScheme("http") self.browser.setUrl(q) def update_urlbar(self, q): if q.scheme() == 'https': # Secure padlock icon self.httpsicon.setPixmap( QPixmap(os.path.join('images', 'lock-ssl.png'))) else: # Insecure padlock icon self.httpsicon.setPixmap( QPixmap(os.path.join('images', 'lock-nossl.png'))) self.urlbar.setText(q.toString()) self.urlbar.setCursorPosition(0)