class PyCalcUi(QMainWindow): """PyCalc's View (GUI).""" def __init__(self): """View initializer.""" super().__init__() # Set some main window's properties self.setWindowTitle("PyCalc") self.setFixedSize(235, 235) # Set the central widget and the general layout self.generalLayout = QVBoxLayout() self._centralWidget = QWidget(self) self.setCentralWidget(self._centralWidget) self._centralWidget.setLayout(self.generalLayout) # Create the display and the buttons self._createDisplay() self._createButtons() def _createDisplay(self): """Create the display.""" # Create the display widget self.display = QLineEdit() # Set some display's properties self.display.setFixedHeight(35) self.display.setAlignment(Qt.AlignmentFlag.AlignRight) self.display.setReadOnly(True) # Add the display to the general layout self.generalLayout.addWidget(self.display) def _createButtons(self): """Create the buttons.""" self.buttons = {} buttonsLayout = QGridLayout() # Button text | position on the QGridLayout buttons = { "7": (0, 0), "8": (0, 1), "9": (0, 2), "/": (0, 3), "C": (0, 4), "4": (1, 0), "5": (1, 1), "6": (1, 2), "*": (1, 3), "(": (1, 4), "1": (2, 0), "2": (2, 1), "3": (2, 2), "-": (2, 3), ")": (2, 4), "0": (3, 0), "00": (3, 1), ".": (3, 2), "+": (3, 3), "=": (3, 4), } # Create the buttons and add them to the grid layout for btnText, pos in buttons.items(): self.buttons[btnText] = QPushButton(btnText) self.buttons[btnText].setFixedSize(40, 40) buttonsLayout.addWidget(self.buttons[btnText], pos[0], pos[1]) # Add buttonsLayout to the general layout self.generalLayout.addLayout(buttonsLayout) def setDisplayText(self, text): """Set display's text.""" self.display.setText(text) self.display.setFocus() def displayText(self): """Get display's text.""" return self.display.text() def clearDisplay(self): """Clear the display.""" self.setDisplayText("")
class B23Download(QWidget): def __init__(self): super(B23Download, self).__init__() # setup some flags self.is_fetching = False self.is_downloading = False # default output path basepath = os.path.dirname(os.path.abspath(__file__)) path = os.path.join(basepath, "videos") self.output_path = path # setup some window specific things self.setWindowTitle("Bilibili Favorite Downloader") self.setWindowIcon(QIcon("images/icon_bilibili.ico")) self.setFixedSize(705, 343) # parent layout main_layout = QVBoxLayout() main_layout.setContentsMargins(15, 15, 15, 10) self.setLayout(main_layout) # top bar layout top_layout = QHBoxLayout() # detail section mid_main_layout = QHBoxLayout() mid_right_layout = QVBoxLayout() # download section bottom_main_layout = QHBoxLayout() bottom_right_layout = QVBoxLayout() # output path link button self.output_btn = QPushButton("📂 Output Path") self.output_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.output_btn.setToolTip(self.output_path) self.output_btn.clicked.connect(self.set_output_path) # status bar self.status_bar = QStatusBar() # message box self.message_box = QMessageBox() # setting up widgets self.url_edit = QLineEdit() self.url_edit.setPlaceholderText("🔍 Enter or paste favorite URL...") self.get_btn = QPushButton("Get") self.get_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.get_btn.clicked.connect(self.get_details) # thumbnail pixmap = QPixmap("images/placeholder.png") self.thumb = QLabel() self.thumb.setFixedSize(250, 141) self.thumb.setScaledContents(True) self.thumb.setPixmap(pixmap) # detail widgets self.title = QLabel("Title: ") self.author = QLabel("Author: ") self.length = QLabel("Videos: ") self.publish_date = QLabel("Published: ") # progress bar self.progress_bar = QProgressBar() # download options self.download_btn = QPushButton(" Download Videos ") self.download_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.download_btn.clicked.connect(self.get_content) self.download_btn.setEnabled(False) self.download_btn.setShortcut("Ctrl+Return") self.download_btn.setMinimumWidth(200) # add widgets and layouts top_layout.addWidget(self.url_edit) top_layout.addWidget(self.get_btn) # detail section mid_right_layout.addWidget(self.title) mid_right_layout.addWidget(self.author) mid_right_layout.addWidget(self.length) mid_right_layout.addWidget(self.publish_date) mid_main_layout.addWidget(self.thumb) mid_main_layout.addSpacing(20) mid_main_layout.addLayout(mid_right_layout) # download section bottom_right_layout.addWidget(self.download_btn) bottom_main_layout.addWidget(self.progress_bar) bottom_main_layout.addSpacing(10) bottom_main_layout.addLayout(bottom_right_layout) # status bar self.status_bar.setSizeGripEnabled(False) self.status_bar.addPermanentWidget(self.output_btn) # add content to parent layout main_layout.addLayout(top_layout) main_layout.addSpacing(20) main_layout.addLayout(mid_main_layout) main_layout.addSpacing(5) main_layout.addLayout(bottom_main_layout) main_layout.addWidget(self.status_bar) # set output path slot def set_output_path(self): # update the output path path = str( QFileDialog.getExistingDirectory(self, "Select Output Directory")) if path: self.output_path = path # update tooltip self.output_btn.setToolTip(path) # get button slot def get_details(self): text = self.url_edit.text().strip() if not text: return if text.find("fid") < 0: self.message_box.warning( self, "Error", ("Input a correct favorite URL!\n" "For example: https://space.bilibili.com/xxx/favlist?fid=xxx..." ), ) return if self.get_btn.text() == "Get": self.get_btn.setText("Stop") # indicate progress bar as busy self.progress_bar.setRange(0, 0) # set fetching flag self.is_fetching = True # setup a worker thread to keep UI responsive self.media_id = text.split("fid=")[-1].split("&")[0] self.worker = WorkerThread(self.media_id) self.worker.start() # catch the finished signal self.worker.finished.connect(self.finished_slot) # catch the response signal self.worker.worker_response.connect(self.response_slot) # catch the error signal self.worker.worker_err_response.connect(self.err_slot) elif self.get_btn.text() == "Stop": if self.is_fetching: # stop worker thread self.worker.terminate() # set back the get_btn text self.get_btn.setText("Get") elif self.is_downloading: # stop download thread self.download_thread.terminate() # show the warning message_box self.message_box.information( self, "Interrupted", "Download interrupted!\nThe process was aborted while the file was being downloaded... ", ) # reset progress bar self.progress_bar.reset() # download options slot def get_content(self): if self.is_fetching: # show the warning message self.message_box.critical( self, "Error", "Please wait!\nWait while the details are being fetched... ", ) else: # disable the download options self.download_btn.setDisabled(True) # set downloading flag self.is_downloading = True # set button to stop self.get_btn.setText("Stop") self.download_thread = DownloadThread( self.media_id, self.media_counts, self.first_page_medias, self.output_path, ) # start the thread self.download_thread.start() # catch the finished signal self.download_thread.finished.connect(self.download_finished_slot) # catch the response signal self.download_thread.download_response.connect( self.download_response_slot) # catch the complete signal self.download_thread.download_complete.connect( self.download_complete_slot) # catch the error signal self.download_thread.download_err.connect(self.download_err_slot) # handling enter key for get/stop button def keyPressEvent(self, event): self.url_edit.setFocus() if (event.key() == Qt.Key.Key_Enter.value or event.key() == Qt.Key.Key_Return.value): self.get_details() # finished slot def finished_slot(self): # remove progress bar busy indication self.progress_bar.setRange(0, 100) # unset fetching flag self.is_fetching = False # response slot def response_slot(self, res): # set back the button text self.get_btn.setText("Get") # set the actual thumbnail of requested video self.thumb.setPixmap(res.thumb_img) # slice the title if it is more than the limit if len(res.title) > 50: self.title.setText(f"Title: {res.title[:50]}...") else: self.title.setText(f"Title: {res.title}") # cache first page medias self.first_page_medias = res.medias self.media_counts = res.media_counts # set leftover details self.author.setText(f"Author: {res.author}") self.length.setText(f"Videos: {res.media_counts}") self.publish_date.setText( f'Published: {time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(res.publish_date))}' ) self.download_btn.setDisabled(False) # error slot def err_slot(self): # show the warning message self.message_box.warning( self, "Warning", "Something went wrong!\nProbably a broken link or some restricted content... ", ) # set back the button text self.get_btn.setText("Get") # download finished slot def download_finished_slot(self): # set back the button text self.get_btn.setText("Get") # now enable the download options self.download_btn.setDisabled(False) # unset downloading flag self.is_downloading = False # reset pogress bar self.progress_bar.reset() # download response slot def download_response_slot(self, per): # update progress bar self.progress_bar.setValue(per) # adjust the font color to maintain the contrast if per > 52: self.progress_bar.setStyleSheet("QProgressBar { color: #fff }") else: self.progress_bar.setStyleSheet("QProgressBar { color: #000 }") # download complete slot def download_complete_slot(self, location): # use native separators location = QDir.toNativeSeparators(location) # show the success message if (self.message_box.information( self, "Downloaded", f"Download complete!\nFile was successfully downloaded to :\n{location}\n\nOpen the downloaded file now ?", QMessageBox.StandardButtons.Open, QMessageBox.StandardButtons.Cancel, ) is QMessageBox.StandardButtons.Open): subprocess.Popen(f"explorer /select,{location}") # download error slot def download_err_slot(self): # show the error message self.message_box.critical( self, "Error", "Error!\nSomething unusual happened and was unable to download...", )
class RenameDialog(QDialog): out = pyqtSignal(object) def __init__(self, parent=None): super(RenameDialog, self).__init__(parent) self.infos = [] self.min_width = 400 self.initUI() self.update_text() self.setStyleSheet(dialog_qss_style) def set_values(self, infos=None): self.infos = infos or [] self.update_text() # 更新界面 def initUI(self): self.setWindowIcon(QIcon(SRC_DIR + "desc.ico")) self.lb_name = QLabel() self.lb_name.setText("文件夹名:") self.lb_name.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) self.tx_name = QLineEdit() self.lb_desc = QLabel() self.tx_desc = QTextEdit() self.lb_desc.setText("描 述:") self.lb_desc.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) self.buttonBox = QDialogButtonBox() self.buttonBox.setOrientation(Qt.Orientation.Horizontal) self.buttonBox.setStandardButtons( QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText("确定") self.buttonBox.button( QDialogButtonBox.StandardButton.Cancel).setText("取消") self.grid = QGridLayout() self.grid.setSpacing(10) self.grid.addWidget(self.lb_name, 1, 0) self.grid.addWidget(self.tx_name, 1, 1) self.grid.addWidget(self.lb_desc, 2, 0) self.grid.addWidget(self.tx_desc, 2, 1, 5, 1) self.grid.addWidget(self.buttonBox, 7, 1, 1, 1) self.setLayout(self.grid) self.buttonBox.accepted.connect(self.btn_ok) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) def update_text(self): self.tx_desc.setFocus() num = len(self.infos) if num == 1: self.lb_name.setVisible(True) self.tx_name.setVisible(True) infos = self.infos[0] self.buttonBox.button( QDialogButtonBox.StandardButton.Ok).setToolTip("") # 去除新建文件夹影响 self.buttonBox.button( QDialogButtonBox.StandardButton.Ok).setEnabled( True) # 去除新建文件夹影响 self.setWindowTitle("修改文件夹名与描述") self.tx_name.setText(str(infos.name)) if infos.desc: self.tx_desc.setText(str(infos.desc)) self.tx_desc.setToolTip('原描述:' + str(infos.desc)) else: self.tx_desc.setText("") self.tx_desc.setToolTip('') self.tx_desc.setPlaceholderText("无") self.min_width = len(str(infos.name)) * 8 if infos.is_file: self.setWindowTitle("修改文件描述") self.tx_name.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.tx_name.setReadOnly(True) else: self.tx_name.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.tx_name.setReadOnly(False) self.tx_name.setFocus() elif num > 1: self.lb_name.setVisible(False) self.tx_name.setVisible(False) self.setWindowTitle(f"批量修改{num}个文件(夹)的描述") self.tx_desc.setText('') self.tx_desc.setPlaceholderText("建议160字数以内。") else: self.setWindowTitle("新建文件夹") self.tx_name.setText("") self.buttonBox.button( QDialogButtonBox.StandardButton.Ok).setEnabled(False) self.buttonBox.button( QDialogButtonBox.StandardButton.Ok).setToolTip("请先输入文件名!") self.tx_name.textChanged.connect(self.slot_new_ok_btn) self.tx_name.setPlaceholderText("不支持空格,如有会被自动替换成 _") self.tx_name.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.tx_name.setReadOnly(False) self.tx_desc.setPlaceholderText("可选项,建议160字数以内。") self.tx_name.setFocus() if self.min_width < 400: self.min_width = 400 self.resize(self.min_width, 200) def slot_new_ok_btn(self): """新建文件夹槽函数""" self.buttonBox.button( QDialogButtonBox.StandardButton.Ok).setEnabled(True) self.buttonBox.button( QDialogButtonBox.StandardButton.Ok).setToolTip("") def btn_ok(self): new_name = self.tx_name.text() new_des = self.tx_desc.toPlainText() info_len = len(self.infos) if info_len == 0: # 在 work_id 新建文件夹 if new_name: self.out.emit(("new", new_name, new_des)) elif info_len == 1: if new_name != self.infos[0].name or new_des != self.infos[0].desc: self.infos[0].new_des = new_des self.infos[0].new_name = new_name self.out.emit(("change", self.infos)) else: if new_des: for infos in self.infos: infos.new_des = new_des self.out.emit(("change", self.infos))