def UiComponents(self): button = QPushButton("Close Window", self) button.setGeometry(QRect(100, 100, 111, 50)) button.setIcon(QtGui.QIcon("home.png")) button.setIconSize(QSize(40, 40)) button.setToolTip("This Is Click Me Button") button.clicked.connect(ClickMe)
def initUI(self): QToolTip.setFont(QFont('SansSerif', 10)) self.setToolTip('This is a <b>QWidget</b> widget') btn = QPushButton('Button', self) btn.setToolTip('This is a <b>QPushButton</b> widget') btn.resize(btn.sizeHint()) btn.move(50, 50) self.setGeometry(300, 300, 300, 200) self.setWindowTitle('Tooltips') self.show()
def __init__(self): app = QApplication(sys.argv) super().__init__() self.setWindowTitle('PyQt6 Message Box') self.setGeometry(100, 100, 320, 200) # # Create a button that when clicked will pop up our dialog. # Connect our function on_click so that it is called when # the button is clicked. # button = QPushButton('Click to Open Message Box', self) button.setToolTip('This opens an example message box') button.clicked.connect(self.on_click) # # Add the button to the center of our simple window. # widget = QWidget() layout = QHBoxLayout(widget) layout.addWidget(button) self.setCentralWidget(widget) self.show() sys.exit(app.exec())
def __init__(self): app = QApplication(sys.argv) super().__init__() self.setWindowTitle('PyQt6 simple button example') self.setGeometry(100, 100, 360, 140) button = QPushButton('PyQt6 Push Button', self) button.setToolTip('Click button to change window status message.') # # Not too sure about setting the fixed size of the button. # But I did that to keep the button from stretching across the width of # the window. Must be a way to have the button fit the size of it's text. # button.clicked.connect(self.on_click) button.setFixedSize(150, 30) widget = QWidget() layout = QHBoxLayout(widget) layout.addWidget(button) self.setCentralWidget(widget) self.statusBar().showMessage('Operational') self.show() sys.exit(app.exec())
class Window(QDialog): def __init__(self): super().__init__() self.title = "PyQt6 Window" self.top = 100 self.left = 100 self.width = 400 self.height = 300 self.InitWindow() def InitWindow(self): self.setWindowIcon(QtGui.QIcon("home.png")) self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.createLayout() vbox = QVBoxLayout() vbox.addWidget(self.groupBox) self.setLayout(vbox) self.show() def createLayout(self): self.groupBox = QGroupBox("What Is Your Favorite Sport?") hboxlayout = QHBoxLayout() self.button = QPushButton("Football", self) self.button.setIcon(QtGui.QIcon("home.png")) self.button.setIconSize(QSize(40, 40)) self.button.setToolTip("This Is Click Me Button") self.button.setMinimumHeight(40) hboxlayout.addWidget(self.button) self.button1 = QPushButton("Cricket", self) self.button1.setIcon(QtGui.QIcon("home.png")) self.button1.setIconSize(QSize(40, 40)) self.button1.setMinimumHeight(40) self.button1.setToolTip("This Is Click Me Button") hboxlayout.addWidget(self.button1) self.button2 = QPushButton("Tennis", self) self.button2.setIcon(QtGui.QIcon("home.png")) self.button2.setIconSize(QSize(40, 40)) self.button2.setMinimumHeight(40) self.button2.setToolTip("This Is Click Me Button") hboxlayout.addWidget(self.button2) self.groupBox.setLayout(hboxlayout)
def __init__(self): super().__init__() self.logger = logging.getLogger(__name__) formatter = logging.Formatter( fmt="%(asctime)s-%(levelname)s-%(message)s", datefmt="%Y-%m-%d %H:%M:%S") filehandler = logging.FileHandler(filename="logs.log", mode="w", encoding="utf-8") handler = QLogger(update_signal=self.update_signal) handler.setLevel(logging.INFO) filehandler.setLevel(logging.INFO) self.logger.setLevel(logging.INFO) if os.path.exists("config.json") == False: self.gen_conf() with open(file="config.json", mode="r", encoding="utf-8") as conf_reader: conf = json.loads(conf_reader.read()) debug = bool(conf["debug"]) if debug == True: handler.setLevel(logging.DEBUG) filehandler.setLevel(logging.DEBUG) self.logger.setLevel(logging.DEBUG) handler.setFormatter(formatter) filehandler.setFormatter(formatter) self.logger.addHandler(handler) self.logger.addHandler(filehandler) self.logger.debug("当前调试状态:%s" % debug) self.resize(1024, 768) self.setWindowOpacity(0.9) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.setWindowFlag(Qt.WindowFlags.FramelessWindowHint) self.setAutoFillBackground(True) self.work = Work(show_qr_signal=self.show_qr_signal, finish_signal=self.finish_signal, close_qr_signal=self.close_qr_signal) self.work_thread = QThread() self.work.moveToThread(self.work_thread) self.main_layout = QGridLayout() self.setLayout(self.main_layout) self.title = QLabel("ChinaUniOnlineGUI") self.title.setStyleSheet( "QLabel{border:none;border-radius:5px;background:transparent;color:#9AD3BC;font-size:60px;}" ) self.title.setAlignment(Qt.Alignment.AlignCenter) handler.widget.setStyleSheet( "QPlainTextEdit{font-family:Microsoft YaHei;background:#F3EAC2;border:none;border-radius:5px;}QScrollBar:vertical,QScrollBar::handle:vertical{background:#F3EAC2;border:none;border-radius:8px;width:16px;}QScrollBar::handle:vertical:hover{background:#F5B461;}QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical{background:#FFFDF9;border:none;border-radius:8px;width:16px;}QScrollBar::down-arrow:vertical,QScrollBar::up-arrow:vertical{background:#F5B461;border:none;border-radius:8px;width:16px;height:16px;}QScrollBar::sub-line:vertical,QScrollBar::add-line:vertical{background:transparent;border:none;}" ) self.control = QVBoxLayout() self.control_close = QPushButton() self.control_close.setToolTip("关闭") self.control_close.setStyleSheet( "QPushButton{background:#FFE3ED;border-radius:5px;border:none;}QPushButton:hover{background:#EC524B;}" ) self.contron_max = QPushButton() if self.isMaximized() == False: self.contron_max.setToolTip("最大化") else: self.contron_max.setToolTip("还原") self.contron_max.setStyleSheet( "QPushButton{background:#FFFDF9;border-radius:5px;border:none;}QPushButton:hover{background:#F5B461;}" ) self.control_min = QPushButton() self.control_min.setToolTip("最小化") self.control_min.setStyleSheet( "QPushButton{background:#BEEBE9;border-radius:5px;border:none;}QPushButton:hover{background:#F3EAC2;}" ) self.start_button = QPushButton("开始(&S)") self.start_button.setStyleSheet( "QPushButton{background:#9BE3DE;border:none;border-radius:5px;font-size:20px;font-family:DengXian;}QPushButton:hover{background:#9AD3BC;}" ) self.start_button.setToolTip("开始") self.start_button.setFixedSize(120, 60) self.start_button.setDefault(True) setting_button = QPushButton("设置") setting_button.setToolTip("设置") setting_button.setFixedSize(60, 60) setting_button.setStyleSheet( "QPushButton{background:#9BE3DE;border:none;border-radius:5px;font-size:20px;font-family:DengXian;}QPushButton:hover{background:#9AD3BC;}" ) setting_button.clicked.connect(self.setting_callback) start = QHBoxLayout() start.addWidget(self.start_button, 2) start.addWidget(setting_button, 1) self.control_close.clicked.connect(self.close) self.control_min.clicked.connect(self.min_callback) self.contron_max.clicked.connect(self.max_callback) self.start_button.clicked.connect(self.start_callback) self.work_thread.started.connect(self.work.start) self.finish_signal.connect(self.finish_callback) self.close_qr_signal.connect(self.close_qr) self.control.addWidget(self.control_min) self.control.addWidget(self.contron_max) self.control.addWidget(self.control_close) self.main_layout.addLayout(self.control, 0, 0) self.main_layout.addWidget(self.title, 0, 1) self.main_layout.addLayout(start, 0, 2) self.main_layout.addWidget(handler.widget, 1, 1) self.update_signal.connect(handler.widget.appendPlainText) handler.widget.textChanged.connect(handler.scroll_widget_to_bottom) self.show_qr_signal.connect(self.show_qr) self.logger.debug("已初始化UI")
class UI(QWidget): update_signal = pyqtSignal(str) show_qr_signal = pyqtSignal(bytes) finish_signal = pyqtSignal() close_qr_signal = pyqtSignal() def __init__(self): super().__init__() self.logger = logging.getLogger(__name__) formatter = logging.Formatter( fmt="%(asctime)s-%(levelname)s-%(message)s", datefmt="%Y-%m-%d %H:%M:%S") filehandler = logging.FileHandler(filename="logs.log", mode="w", encoding="utf-8") handler = QLogger(update_signal=self.update_signal) handler.setLevel(logging.INFO) filehandler.setLevel(logging.INFO) self.logger.setLevel(logging.INFO) if os.path.exists("config.json") == False: self.gen_conf() with open(file="config.json", mode="r", encoding="utf-8") as conf_reader: conf = json.loads(conf_reader.read()) debug = bool(conf["debug"]) if debug == True: handler.setLevel(logging.DEBUG) filehandler.setLevel(logging.DEBUG) self.logger.setLevel(logging.DEBUG) handler.setFormatter(formatter) filehandler.setFormatter(formatter) self.logger.addHandler(handler) self.logger.addHandler(filehandler) self.logger.debug("当前调试状态:%s" % debug) self.resize(1024, 768) self.setWindowOpacity(0.9) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.setWindowFlag(Qt.WindowFlags.FramelessWindowHint) self.setAutoFillBackground(True) self.work = Work(show_qr_signal=self.show_qr_signal, finish_signal=self.finish_signal, close_qr_signal=self.close_qr_signal) self.work_thread = QThread() self.work.moveToThread(self.work_thread) self.main_layout = QGridLayout() self.setLayout(self.main_layout) self.title = QLabel("ChinaUniOnlineGUI") self.title.setStyleSheet( "QLabel{border:none;border-radius:5px;background:transparent;color:#9AD3BC;font-size:60px;}" ) self.title.setAlignment(Qt.Alignment.AlignCenter) handler.widget.setStyleSheet( "QPlainTextEdit{font-family:Microsoft YaHei;background:#F3EAC2;border:none;border-radius:5px;}QScrollBar:vertical,QScrollBar::handle:vertical{background:#F3EAC2;border:none;border-radius:8px;width:16px;}QScrollBar::handle:vertical:hover{background:#F5B461;}QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical{background:#FFFDF9;border:none;border-radius:8px;width:16px;}QScrollBar::down-arrow:vertical,QScrollBar::up-arrow:vertical{background:#F5B461;border:none;border-radius:8px;width:16px;height:16px;}QScrollBar::sub-line:vertical,QScrollBar::add-line:vertical{background:transparent;border:none;}" ) self.control = QVBoxLayout() self.control_close = QPushButton() self.control_close.setToolTip("关闭") self.control_close.setStyleSheet( "QPushButton{background:#FFE3ED;border-radius:5px;border:none;}QPushButton:hover{background:#EC524B;}" ) self.contron_max = QPushButton() if self.isMaximized() == False: self.contron_max.setToolTip("最大化") else: self.contron_max.setToolTip("还原") self.contron_max.setStyleSheet( "QPushButton{background:#FFFDF9;border-radius:5px;border:none;}QPushButton:hover{background:#F5B461;}" ) self.control_min = QPushButton() self.control_min.setToolTip("最小化") self.control_min.setStyleSheet( "QPushButton{background:#BEEBE9;border-radius:5px;border:none;}QPushButton:hover{background:#F3EAC2;}" ) self.start_button = QPushButton("开始(&S)") self.start_button.setStyleSheet( "QPushButton{background:#9BE3DE;border:none;border-radius:5px;font-size:20px;font-family:DengXian;}QPushButton:hover{background:#9AD3BC;}" ) self.start_button.setToolTip("开始") self.start_button.setFixedSize(120, 60) self.start_button.setDefault(True) setting_button = QPushButton("设置") setting_button.setToolTip("设置") setting_button.setFixedSize(60, 60) setting_button.setStyleSheet( "QPushButton{background:#9BE3DE;border:none;border-radius:5px;font-size:20px;font-family:DengXian;}QPushButton:hover{background:#9AD3BC;}" ) setting_button.clicked.connect(self.setting_callback) start = QHBoxLayout() start.addWidget(self.start_button, 2) start.addWidget(setting_button, 1) self.control_close.clicked.connect(self.close) self.control_min.clicked.connect(self.min_callback) self.contron_max.clicked.connect(self.max_callback) self.start_button.clicked.connect(self.start_callback) self.work_thread.started.connect(self.work.start) self.finish_signal.connect(self.finish_callback) self.close_qr_signal.connect(self.close_qr) self.control.addWidget(self.control_min) self.control.addWidget(self.contron_max) self.control.addWidget(self.control_close) self.main_layout.addLayout(self.control, 0, 0) self.main_layout.addWidget(self.title, 0, 1) self.main_layout.addLayout(start, 0, 2) self.main_layout.addWidget(handler.widget, 1, 1) self.update_signal.connect(handler.widget.appendPlainText) handler.widget.textChanged.connect(handler.scroll_widget_to_bottom) self.show_qr_signal.connect(self.show_qr) self.logger.debug("已初始化UI") def min_callback(self): if self.isMinimized() == False: self.showMinimized() def max_callback(self): if self.isMaximized() == False: self.showMaximized() self.contron_max.setToolTip("还原") else: self.showNormal() self.contron_max.setToolTip("最大化") def start_callback(self): self.start_time = time.time() self.work_thread.start() self.start_button.setEnabled(False) self.start_button.setText("执行中...") def finish_callback(self): self.start_button.setEnabled(True) self.start_button.setText("开始") passed_time = time.time() - self.start_time mins, secs = divmod(passed_time, 60) hours, mins = divmod(mins, 60) self.logger.info("执行完成,共计用时 {:0>2d}:{:0>2d}:{:0>2d}".format( int(hours), int(mins), int(secs))) def show_qr(self, qr: bytes): title_label = QLabel("请使用微信扫描小程序码完成登陆") title_label.setStyleSheet( "QLabel{color:#ffe3ed;border:none;background-color:transparent;border-radius:5px;}" ) title_label.setAlignment(Qt.Alignment.AlignCenter) title_label.setFixedHeight(20) qr_label = QLabel() pixmap = QPixmap() pixmap.loadFromData(qr) qr_label.setPixmap(pixmap) qr_label.setStyleSheet( "QLabel{color:#ffe3ed;border:none;background-color:transparent;border-radius:5px;}" ) layout_ = QVBoxLayout() layout_.addWidget(title_label, 1) layout_.addWidget(qr_label, 9) self.qr_dialog = QWidget(self) self.qr_dialog.setLayout(layout_) self.main_layout.addWidget(self.qr_dialog, 1, 1, Qt.Alignment.AlignCenter) self.qr_dialog.show() def close_qr(self): self.qr_dialog.close() def setting_callback(self): setting = SettingWindow(parent=self) setting.setStyleSheet( "QDialog{border:none;border-radius:5px;background:#F3EAC2;}") setting.show() def gen_conf(self): default_conf = { "debug": False, "hero": { "title": "英雄篇", "enabled": True, "times": 1 }, "revival": { "title": "复兴篇", "enabled": True, "times": 1 }, "creation": { "title": "创新篇", "enabled": True, "times": 1 }, "belief": { "title": "信念篇", "enabled": True, "times": 1 }, "limit_time": { "title": "限时赛", "enabled": True, "times": 1 }, "rob": { "title": "抢十赛", "enabled": True, "times": 1 } } with open(file="config.json", mode="w", encoding="utf-8") as conf_writer: conf_writer.write( json.dumps(default_conf, indent=4, sort_keys=True, ensure_ascii=False)) self.logger.info("已生成默认配置文件") def mousePressEvent(self, event: QMouseEvent): self.logger.debug("触发鼠标按压事件") super().mousePressEvent(event) self.setFocus() self.m_flag = True if event.button() == Qt.MouseButtons.LeftButton and self.isMaximized( ) == False and self.hasFocus() == True: self.old_pos = event.globalPosition() #获取鼠标相对窗口的位置 self.logger.debug("已获取鼠标位置") self.setCursor(QtGui.QCursor( Qt.CursorShape.SizeAllCursor)) #更改鼠标图标 def mouseMoveEvent(self, event: QMouseEvent): self.logger.debug("触发鼠标移动事件") super().mouseMoveEvent(event) if self.m_flag == True: delta_x = int(event.globalPosition().x() - self.old_pos.x()) delta_y = int(event.globalPosition().y() - self.old_pos.y()) self.move(self.x() + delta_x, self.y() + delta_y) #更改窗口位置 self.logger.debug("已更改窗口位置") self.old_pos = event.globalPosition() def mouseReleaseEvent(self, event: QMouseEvent): self.logger.debug("触发鼠标释放事件") super().mouseReleaseEvent(event) self.m_flag = False self.setCursor(QtGui.QCursor(Qt.CursorShape.ArrowCursor))
def __init__(self, parent: QWidget): super().__init__() self.logger = logging.getLogger(__name__) with open(file="config.json", mode="r", encoding="utf-8") as conf_reader: self.conf = json.loads(conf_reader.read()) self.logger.debug("初始化设置界面。设置内容:%s" % self.conf) layout = QGridLayout() self.setLayout(layout) self.setModal(True) self.setParent(parent) self.resize(400, 300) title = QLabel("设置") title.setStyleSheet( "QLabel{border:none;border-radius:5px;background:transparent;color:#9AD3BC;font-size:20px;}" ) title.setAlignment(Qt.Alignment.AlignCenter) layout.addWidget(title, 0, 1, Qt.Alignment.AlignCenter) control_close = QPushButton() control_close.setStyleSheet( "QPushButton{background:#FFE3ED;border-radius:5px;border:none;}QPushButton:hover{background:#EC524B;}" ) control_close.setToolTip("关闭") control_close.setFixedHeight(20) control_close.clicked.connect(self.close_callback) layout.addWidget(control_close, 0, 0) debug_check = QCheckBox("调试模式") debug_check.setChecked(self.conf["debug"]) debug_check.setToolTip("单击切换开关状态") debug_check.setStyleSheet( "QCheckBox::indicator{width:10px;height:10px;border:none;border-radius:5px;background:#9BE3DE;}QCheckBox::indicator:unchecked{background:#BEEBE9;}QCheckBox::indicator:unchecked:hover{background:#9AD3BC;}QCheckBox::indicator:checked{background:#95E1D3;}QCheckBox::indicator:checked:hover{background:#98DED9;}" ) self.content = QGridLayout() (x, y) = self.show_setting(conf=self.conf, layout=self.content) # 返回content的最后一个元素的x,y proxy = QGroupBox() proxy.setObjectName("proxy") proxy_layout = QVBoxLayout() proxy_label = QLabel("代理地址:") proxy_label.setStyleSheet( "QLabel{background:transparent;border:none;}") proxy_input = EnhancedEdit() proxy_input.setText(self.conf["proxy"]) proxy_input.setToolTip("格式为协议://IP:端口,留空保持直连") proxy_input.setStyleSheet( "QLineEdit{border:1px solid #F3EAC2;border-radius:5px;background:transparent;}QLineEdit:hover{border:1px solid #F5B461;}" ) proxy_layout.addWidget(proxy_label) proxy_layout.addWidget(proxy_input) proxy.setLayout(proxy_layout) proxy.setStyleSheet("QGroupBox{border-radius:5px;}") proxy.setToolTip("代理设置") if y + 1 >= 3: y_ = 0 x_ = x + 1 else: y_ = y + 1 x_ = x self.content.addWidget(proxy, x_, y_) self.content.addWidget(debug_check) layout.addLayout(self.content, 1, 1)
class YTdownloader(QWidget): def __init__(self): super().__init__() # setup some flags self.isFetching = False self.isDownloading = False # default output path self.outputPath = f'{QDir.homePath()}/videos' # setup some window specific things self.setWindowTitle('YouTube Downloader') self.setWindowIcon(QIcon('assets/yt-icon.ico')) self.setFixedSize(705, 343) # parent layout layout = QVBoxLayout() layout.setContentsMargins(15, 15, 15, 10) self.setLayout(layout) # top bar layout topBar = QHBoxLayout() # detail section detailSec = QHBoxLayout() metaSec = QVBoxLayout() # download section downloadSec = QHBoxLayout() downloadBtn = QVBoxLayout() # output path link button self.outputBtn = QPushButton('📂 Output Path') self.outputBtn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.outputBtn.setToolTip(self.outputPath) self.outputBtn.clicked.connect(self.setOutputPath) # status bar self.statusBar = QStatusBar() # message box self.message = QMessageBox() # setting up widgets self.urlBox = QLineEdit() self.urlBox.setFocusPolicy(Qt.FocusPolicy.ClickFocus or Qt.FocusPolicy.NoFocus) self.urlBox.setPlaceholderText('🔍 Enter or paste video URL...') self.button = QPushButton('Get') self.button.setDefault(True) self.button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.button.clicked.connect(self.getDetails) # thumbnail pixmap = QPixmap('assets\placeholder.jpg') 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('Duration: ') self.publish_date = QLabel('Published: ') # progress bar self.progress_bar = QProgressBar() # download options self.download = QComboBox() self.download.setPlaceholderText('Download Video') self.download.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.download.activated.connect(lambda: self.getContent(0)) self.download.setEnabled(False) # download audio button self.download_audio = QPushButton('Download Audio') self.download_audio.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.download_audio.clicked.connect(lambda: self.getContent(1)) self.download_audio.setEnabled(False) # add widgets and layouts topBar.addWidget(self.urlBox) topBar.addWidget(self.button) # detail section metaSec.addWidget(self.title) metaSec.addWidget(self.author) metaSec.addWidget(self.length) metaSec.addWidget(self.publish_date) detailSec.addWidget(self.thumb) detailSec.addSpacing(20) detailSec.addLayout(metaSec) # download section downloadBtn.addWidget(self.download) downloadBtn.addWidget(self.download_audio) downloadSec.addWidget(self.progress_bar) downloadSec.addSpacing(10) downloadSec.addLayout(downloadBtn) # status bar self.statusBar.setSizeGripEnabled(False) self.statusBar.addPermanentWidget(self.outputBtn) # add content to parent layout layout.addLayout(topBar) layout.addSpacing(20) layout.addLayout(detailSec) layout.addSpacing(5) layout.addLayout(downloadSec) layout.addWidget(self.statusBar) # setup a connection thread to keep checking internet connectivity self.connection = ConnectionThread() self.connection.start() # catch the connection response signal self.connection.con_response.connect(self.connection_slot) # connection slot def connection_slot(self, status): curMsg = self.statusBar.currentMessage() # connection succeeded if status: if curMsg == '🔴 Disconnected': self.statusBar.showMessage('🟢 Connection restored!', 3000) elif curMsg != '🟢 Connected': self.statusBar.showMessage('🟢 Connected') # connection failed elif curMsg == '🟢 Connected': self.statusBar.showMessage('🔴 Connection interrupted!', 3000) elif curMsg != '🔴 Disconnected': self.statusBar.showMessage('🔴 Disconnected') # set output path slot def setOutputPath(self): # update the output path path = str(QFileDialog.getExistingDirectory(self, "Select Output Directory")) if path: self.outputPath = path # update tooltip self.outputBtn.setToolTip(path) # get button slot def getDetails(self): curMsg = self.statusBar.currentMessage() if curMsg == '🔴 Disconnected' or curMsg == '🔴 Connection interrupted!': self.message.critical( self, 'Error', 'Connection failed!\nAre you sure you\'re connected to the internet ? ' ) elif self.button.text() == 'Get': self.button.setText('Stop') # indicate progress bar as busy self.progress_bar.setRange(0, 0) # set fetching flag self.isFetching = True # setup a worker thread to keep UI responsive self.worker = WorkerThread(self.urlBox.text()) 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.button.text() == 'Stop': if self.isFetching: # stop worker thread self.worker.terminate() # set back the button text self.button.setText('Get') elif self.isDownloading: # stop download thread self.download_thread.terminate() # show the warning message self.message.information( self, 'Interrupted', 'Download interrupted!\nThe process was aborted while the file was being downloaded... ' ) # reset pogress bar self.progress_bar.reset() # download options slot def getContent(self, id): if self.isFetching: # show the warning message self.message.warning( self, 'Warning', 'Please wait!\nWait while the details are being fetched... ' ) else: # disable the download options self.download.setDisabled(True) self.download_audio.setDisabled(True) # set downloading flag self.isDownloading = True # set button to stop self.button.setText('Stop') # setup download thread if id == 0: self.download_thread = DownloadThread(self.yt, self.download.currentText()[:4], self.outputPath) else: self.download_thread = DownloadThread(self.yt, 'audio', self.outputPath) # 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) # finished slot def finished_slot(self): # remove progress bar busy indication self.progress_bar.setRange(0, 100) # unset fetching flag self.isFetching = False # response slot def response_slot(self, res): # set back the button text self.button.setText('Get') # save the yt object for speeding up download self.yt = res[0] # set the actual thumbnail of requested video self.thumb.setPixmap(res[1]) # slice the title if it is more than the limit if len(res[2]) > 50: self.title.setText(f'Title: {res[2][:50]}...') else: self.title.setText(f'Title: {res[2]}') # set leftover details self.author.setText(f'Author: {res[3]}') self.length.setText(f'Duration: {timedelta(seconds=res[4])}') self.publish_date.setText(f'Published: {res[5].strftime("%d/%m/%Y")}') # clear any previous items if any self.download.clear() # add resolutions as items to the download button and enable them self.download.addItems([item for item in res[6]]) self.download.setDisabled(False) self.download_audio.setDisabled(False) # error slot def err_slot(self): # show the warning message self.message.warning( self, 'Warning', 'Something went wrong!\nProbably a broken link or some restricted content... ' ) # set back the button text self.button.setText('Get') # download finished slot def download_finished_slot(self): # set back the button text self.button.setText('Get') # now enable the download options self.download.setDisabled(False) self.download_audio.setDisabled(False) # unset downloading flag self.isDownloading = 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.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.critical( self, 'Error', 'Error!\nSomething unusual happened and was unable to download...' )
class AttributeWidget(CustomScrollableListItem): def __init__(self, document_base_viewer): super(AttributeWidget, self).__init__(document_base_viewer) self.document_base_viewer = document_base_viewer self.attribute = None self.setFixedHeight(40) self.setStyleSheet("background-color: white") self.layout = QHBoxLayout(self) self.layout.setContentsMargins(20, 0, 20, 0) self.layout.setSpacing(40) self.attribute_name = QLabel() self.attribute_name.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.attribute_name, alignment=Qt.AlignmentFlag.AlignLeft) self.num_matched = QLabel("matches: -") self.num_matched.setFont(CODE_FONT) self.layout.addWidget(self.num_matched, alignment=Qt.AlignmentFlag.AlignLeft) self.buttons_widget = QWidget() self.buttons_layout = QHBoxLayout(self.buttons_widget) self.buttons_layout.setContentsMargins(0, 0, 0, 0) self.buttons_layout.setSpacing(10) self.layout.addWidget(self.buttons_widget, alignment=Qt.AlignmentFlag.AlignRight) self.forget_matches_button = QPushButton() self.forget_matches_button.setIcon(QIcon("aset_ui/resources/redo.svg")) self.forget_matches_button.setToolTip( "Forget matches for this attribute.") self.forget_matches_button.setFlat(True) self.forget_matches_button.clicked.connect( self._forget_matches_button_clicked) self.buttons_layout.addWidget(self.forget_matches_button) self.remove_button = QPushButton() self.remove_button.setIcon(QIcon("aset_ui/resources/trash.svg")) self.remove_button.setToolTip("Remove this attribute.") self.remove_button.setFlat(True) self.remove_button.clicked.connect(self._remove_button_clicked) self.buttons_layout.addWidget(self.remove_button) def update_item(self, item, params=None): self.attribute = item if len(params.attributes) == 0: max_attribute_name_len = 10 else: max_attribute_name_len = max( len(attribute.name) for attribute in params.attributes) self.attribute_name.setText(self.attribute.name + ( " " * (max_attribute_name_len - len(self.attribute.name)))) mappings_in_some_documents = False no_mappings_in_some_documents = False num_matches = 0 for document in params.documents: if self.attribute.name in document.attribute_mappings.keys(): mappings_in_some_documents = True if document.attribute_mappings[self.attribute.name] != []: num_matches += 1 else: no_mappings_in_some_documents = True if not mappings_in_some_documents and no_mappings_in_some_documents: self.num_matched.setText("not matched yet") elif mappings_in_some_documents and no_mappings_in_some_documents: self.num_matched.setText("only partly matched") else: self.num_matched.setText(f"matches: {num_matches}") def enable_input(self): self.forget_matches_button.setEnabled(True) self.remove_button.setEnabled(True) def disable_input(self): self.forget_matches_button.setDisabled(True) self.remove_button.setDisabled(True) def _forget_matches_button_clicked(self): self.document_base_viewer.main_window.forget_matches_for_attribute_with_given_name_task( self.attribute.name) def _remove_button_clicked(self): self.document_base_viewer.main_window.remove_attribute_with_given_name_task( self.attribute.name)
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 AboutDialog(QDialog): check_update = pyqtSignal(str, bool) def __init__(self, parent=None): super(AboutDialog, self).__init__(parent) self._ver = '' self._github = 'https://github.com/rachpt/lanzou-gui' self._api_url = 'https://github.com/zaxtyson/LanZouCloud-API' self._gitee = 'https://gitee.com/rachpt/lanzou-gui' self._home_page = 'https://rachpt.cn/lanzou-gui/' self.initUI() self.setStyleSheet(others_style) def set_values(self, version): self._ver = version self.lb_name_text.setText(f"v{version} (点击检查更新)") # 更新版本 def show_update(self, ver, msg): self.lb_new_ver = QLabel("新版") # 检测新版 self.lb_new_ver_msg = QLabel() self.lb_new_ver_msg.setOpenExternalLinks(True) self.lb_new_ver_msg.setWordWrap(True) if ver != '0': self.lb_name_text.setText(f"{self._ver} ➡ {ver}") self.lb_new_ver_msg.setText(msg) self.lb_new_ver_msg.setMinimumWidth(700) if self.form.rowCount() < 5: self.form.insertRow(1, self.lb_new_ver, self.lb_new_ver_msg) def initUI(self): self.setWindowTitle("关于 lanzou-gui") about = f'本项目使用PyQt6实现图形界面,可以完成蓝奏云的大部分功能<br/> \ 得益于 <a href="{self._api_url}">API</a> 的功能,可以间接突破单文件最大 100MB 的限制,同时增加了批量上传/下载的功能<br/> \ Python 依赖见<a href="{self._github }/blob/master/requirements.txt">requirements.txt</a>,\ <a href="{self._github}/releases">releases</a> 有打包好了的 Windows 可执行程序,但可能不是最新的' project_url = f'<a href="{self._home_page}">主页</a> | <a href="{self._github}">repo</a> | \ <a href="{self._gitee}">mirror repo</a>' self.logo = QLabel() # logo self.logo.setPixmap(QPixmap(SRC_DIR + "logo2.gif")) self.logo.setStyleSheet("background-color:rgb(255,255,255);") self.logo.setAlignment(Qt.AlignmentFlag.AlignCenter) self.lb_qt_ver = QLabel("依赖") # QT 版本 self.lb_qt_text = QLabel( f"QT: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}") # QT 版本 self.lb_name = QLabel("版本") # 版本 self.lb_name_text = QPushButton("") # 版本 self.lb_name_text.setToolTip("点击检查更新") ver_style = "QPushButton {border:none; background:transparent;font-weight:bold;color:blue;}" self.lb_name_text.setStyleSheet(ver_style) self.lb_name_text.clicked.connect( lambda: self.check_update.emit(self._ver, True)) self.lb_about = QLabel("关于") # about self.lb_about_text = QLabel() self.lb_about_text.setText(about) self.lb_about_text.setOpenExternalLinks(True) self.lb_author = QLabel("作者") # author self.lb_author_mail = QLabel( "<a href='mailto:[email protected]'>rachpt</a>") self.lb_author_mail.setOpenExternalLinks(True) self.lb_update = QLabel("项目") # 更新 self.lb_update_url = QLabel(project_url) self.lb_update_url.setOpenExternalLinks(True) self.buttonBox = QDialogButtonBox() self.buttonBox.setOrientation(Qt.Orientation.Horizontal) self.buttonBox.setStandardButtons( QDialogButtonBox.StandardButton.Close) self.buttonBox.button( QDialogButtonBox.StandardButton.Close).setText("关闭") self.buttonBox.rejected.connect(self.reject) self.buttonBox.setStyleSheet(btn_style) self.recommend = QLabel( "<br />大文件推荐使用 <a href='https://github.com/Aruelius/cloud189'>cloud189-cli</a>" ) self.recommend.setOpenExternalLinks(True) self.line = QLine(QPoint(), QPoint(550, 0)) self.lb_line = QLabel('<html><hr /></html>') vbox = QVBoxLayout() vbox.addWidget(self.logo) vbox.addStretch(1) self.form = QFormLayout() self.form.setLabelAlignment(Qt.AlignmentFlag.AlignRight) self.form.setFormAlignment(Qt.AlignmentFlag.AlignLeft) self.form.setHorizontalSpacing(40) self.form.setVerticalSpacing(15) self.form.addRow(self.lb_qt_ver, self.lb_qt_text) self.form.addRow(self.lb_name, self.lb_name_text) self.form.addRow(self.lb_update, self.lb_update_url) self.form.addRow(self.lb_author, self.lb_author_mail) self.form.addRow(self.lb_about, self.lb_about_text) self.form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy. AllNonFixedFieldsGrow) # 覆盖MacOS的默认样式 vbox.addLayout(self.form) vbox.addStretch(1) vbox.addWidget(self.recommend) vbox.addWidget(self.lb_line) donate = QLabel() donate.setText("<b>捐助我</b> 如果你愿意") donate.setAlignment(Qt.AlignmentFlag.AlignCenter) hbox = QHBoxLayout() hbox.addStretch(2) for it in ["wechat", "alipay", "qqpay"]: lb = QLabel() lb.setPixmap(QPixmap(SRC_DIR + f"{it}.jpg")) hbox.addWidget(lb) hbox.addStretch(1) hbox.addWidget(self.buttonBox) vbox.addWidget(donate) vbox.addLayout(hbox) self.setLayout(vbox) self.setMinimumWidth(720) def paintEvent(self, event): QDialog.paintEvent(self, event) if not self.line.isNull(): painter = QPainter(self) pen = QPen(Qt.GlobalColor.red, 3) painter.setPen(pen) painter.drawLine(self.line)
class LoginDialog(QDialog): """登录对话框""" clicked_ok = pyqtSignal() def __init__(self, config): super().__init__() self._cwd = os.getcwd() self._config = config self._cookie_assister = 'login_assister.exe' self._user = "" self._pwd = "" self._cookie = {} self._del_user = "" self.initUI() self.setStyleSheet(dialog_qss_style) self.setMinimumWidth(560) self.name_ed.setFocus() # 信号 self.name_ed.textChanged.connect(self.set_user) self.pwd_ed.textChanged.connect(self.set_pwd) self.cookie_ed.textChanged.connect(self.set_cookie) def update_selection(self, user): """显示已经保存的登录用户信息""" user_info = self._config.get_user_info(user) if user_info: self._user = user_info[0] self._pwd = user_info[1] self._cookie = user_info[2] # 更新控件显示内容 self.name_ed.setText(self._user) self.pwd_ed.setText(self._pwd) try: text = ";".join([f'{k}={v}' for k, v in self._cookie.items()]) except: text = '' self.cookie_ed.setPlainText(text) def initUI(self): self.setWindowTitle("登录蓝奏云") self.setWindowIcon(QIcon(SRC_DIR + "login.ico")) logo = QLabel() logo.setPixmap(QPixmap(SRC_DIR + "logo3.gif")) logo.setStyleSheet("background-color:rgb(0,153,255);") logo.setAlignment(Qt.AlignmentFlag.AlignCenter) self.tabs = QTabWidget() self.auto_tab = QWidget() self.hand_tab = QWidget() # Add tabs self.tabs.addTab(self.auto_tab,"自动获取Cookie") self.tabs.addTab(self.hand_tab,"手动输入Cookie") self.auto_get_cookie_ok = AutoResizingTextEdit("🔶点击👇自动获取浏览器登录信息👇") self.auto_get_cookie_ok.setReadOnly(True) self.auto_get_cookie_btn = QPushButton("自动读取浏览器登录信息") auto_cookie_notice = '支持浏览器:Chrome, Chromium, Opera, Edge, Firefox' self.auto_get_cookie_btn.setToolTip(auto_cookie_notice) self.auto_get_cookie_btn.clicked.connect(self.call_auto_get_cookie) self.auto_get_cookie_btn.setStyleSheet("QPushButton {min-width: 210px;max-width: 210px;}") self.name_lb = QLabel("&U 用户") self.name_lb.setAlignment(Qt.AlignmentFlag.AlignCenter) self.name_ed = QLineEdit() self.name_lb.setBuddy(self.name_ed) self.pwd_lb = QLabel("&P 密码") self.pwd_lb.setAlignment(Qt.AlignmentFlag.AlignCenter) self.pwd_ed = QLineEdit() self.pwd_ed.setEchoMode(QLineEdit.EchoMode.Password) self.pwd_lb.setBuddy(self.pwd_ed) self.cookie_lb = QLabel("&Cookie") self.cookie_ed = QTextEdit() notice = "由于滑动验证的存在,需要输入cookie,cookie请使用浏览器获取\n" + \ "cookie会保存在本地,下次使用。其格式如下:\n ylogin=value1; phpdisk_info=value2" self.cookie_ed.setPlaceholderText(notice) self.cookie_lb.setBuddy(self.cookie_ed) self.show_input_cookie_btn = QPushButton("显示Cookie输入框") self.show_input_cookie_btn.setToolTip(notice) self.show_input_cookie_btn.setStyleSheet("QPushButton {min-width: 110px;max-width: 110px;}") self.show_input_cookie_btn.clicked.connect(self.change_show_input_cookie) self.ok_btn = QPushButton("登录") self.ok_btn.clicked.connect(self.change_ok_btn) self.cancel_btn = QPushButton("取消") self.cancel_btn.clicked.connect(self.change_cancel_btn) lb_line_1 = QLabel() lb_line_1.setText('<html><hr />切换用户</html>') lb_line_2 = QLabel() lb_line_2.setText('<html><hr /></html>') self.form = QFormLayout() self.form.setLabelAlignment(Qt.AlignmentFlag.AlignRight) self.form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow) # 覆盖MacOS的默认样式 self.form.addRow(self.name_lb, self.name_ed) self.form.addRow(self.pwd_lb, self.pwd_ed) if is_windows: def set_assister_path(): """设置辅助登录程序路径""" assister_path = QFileDialog.getOpenFileName(self, "选择辅助登录程序路径", self._cwd, "EXE Files (*.exe)") if not assister_path[0]: return None assister_path = os.path.normpath(assister_path[0]) # windows backslash if assister_path == self._cookie_assister: return None self.assister_ed.setText(assister_path) self._cookie_assister = assister_path self.assister_lb = QLabel("登录辅助程序") self.assister_lb.setAlignment(Qt.AlignmentFlag.AlignCenter) self.assister_ed = MyLineEdit(self) self.assister_ed.setText(self._cookie_assister) self.assister_ed.clicked.connect(set_assister_path) self.assister_lb.setBuddy(self.assister_ed) self.form.addRow(self.assister_lb, self.assister_ed) hbox = QHBoxLayout() hbox.addWidget(self.show_input_cookie_btn) hbox.addStretch(1) hbox.addWidget(self.ok_btn) hbox.addWidget(self.cancel_btn) user_box = QHBoxLayout() self.user_num = 0 self.user_btns = {} for user in self._config.users_name: user = str(user) # TODO: 可能需要删掉 self.user_btns[user] = QDoublePushButton(user) self.user_btns[user].setStyleSheet("QPushButton {border:none;}") if user == self._config.name: self.user_btns[user].setStyleSheet("QPushButton {background-color:rgb(0,153,2);}") self.tabs.setCurrentIndex(1) self.user_btns[user].setToolTip(f"点击选中,双击切换至用户:{user}") self.user_btns[user].doubleClicked.connect(self.choose_user) self.user_btns[user].clicked.connect(self.delete_chose_user) user_box.addWidget(self.user_btns[user]) self.user_num += 1 user_box.addStretch(1) self.layout = QVBoxLayout(self) self.layout.addWidget(logo) vbox = QVBoxLayout() if self._config.name: vbox.addWidget(lb_line_1) user_box.setAlignment(Qt.AlignmentFlag.AlignCenter) vbox.addLayout(user_box) vbox.addWidget(lb_line_2) if self.user_num > 1: self.del_user_btn = QPushButton("删除账户") self.del_user_btn.setIcon(QIcon(SRC_DIR + "delete.ico")) self.del_user_btn.setStyleSheet("QPushButton {min-width: 180px;max-width: 180px;}") self.del_user_btn.clicked.connect(self.call_del_chose_user) vbox.addWidget(self.del_user_btn) else: self.del_user_btn = None vbox.addStretch(1) vbox.addLayout(self.form) vbox.addStretch(1) vbox.addLayout(hbox) vbox.setAlignment(Qt.AlignmentFlag.AlignCenter) self.hand_tab.setLayout(vbox) auto_cookie_vbox = QVBoxLayout() auto_cookie_vbox.addWidget(self.auto_get_cookie_ok) auto_cookie_vbox.addWidget(self.auto_get_cookie_btn) auto_cookie_vbox.setAlignment(Qt.AlignmentFlag.AlignCenter) self.auto_tab.setLayout(auto_cookie_vbox) self.layout.addWidget(self.tabs) self.setLayout(self.layout) self.update_selection(self._config.name) def call_del_chose_user(self): if self._del_user: if self._del_user != self._config.name: self.user_num -= 1 self._config.del_user(self._del_user) self.user_btns[self._del_user].close() self._del_user = "" if self.user_num <= 1: self.del_user_btn.close() self.del_user_btn = None return else: title = '不能删除' msg = '不能删除当前登录账户,请先切换用户!' else: title = '请选择账户' msg = '请单击选择需要删除的账户\n\n注意不能删除当前账户(绿色)' message_box = QMessageBox(self) message_box.setIcon(QMessageBox.Icon.Critical) message_box.setStyleSheet(btn_style) message_box.setWindowTitle(title) message_box.setText(msg) message_box.setStandardButtons(QMessageBox.StandardButton.Close) buttonC = message_box.button(QMessageBox.StandardButton.Close) buttonC.setText('关闭') message_box.exec() def delete_chose_user(self): """更改单击选中需要删除的用户""" user = str(self.sender().text()) self._del_user = user if self.del_user_btn: self.del_user_btn.setText(f"删除 <{user}>") def choose_user(self): """切换用户""" user = self.sender().text() if user != self._config.name: self.ok_btn.setText("切换用户") else: self.ok_btn.setText("登录") self.update_selection(user) def change_show_input_cookie(self): row_c = 4 if is_windows else 3 if self.form.rowCount() < row_c: self.org_height = self.height() self.form.addRow(self.cookie_lb, self.cookie_ed) self.show_input_cookie_btn.setText("隐藏Cookie输入框") self.change_height = None self.adjustSize() else: if not self.change_height: self.change_height = self.height() if self.cookie_ed.isVisible(): self.cookie_lb.setVisible(False) self.cookie_ed.setVisible(False) self.show_input_cookie_btn.setText("显示Cookie输入框") start_height, end_height = self.change_height, self.org_height else: self.cookie_lb.setVisible(True) self.cookie_ed.setVisible(True) self.show_input_cookie_btn.setText("隐藏Cookie输入框") start_height, end_height = self.org_height, self.change_height gm = self.geometry() x, y = gm.x(), gm.y() wd = self.width() self.animation = QPropertyAnimation(self, b'geometry') self.animation.setDuration(400) self.animation.setStartValue(QRect(x, y, wd, start_height)) self.animation.setEndValue(QRect(x, y, wd, end_height)) self.animation.start() def set_user(self, user): self._user = user if not user: return None if user not in self._config.users_name: self.ok_btn.setText("添加用户") self.cookie_ed.setPlainText("") elif user != self._config.name: self.update_selection(user) self.ok_btn.setText("切换用户") else: self.update_selection(user) self.ok_btn.setText("登录") def set_pwd(self, pwd): if self._user in self._config.users_name: user_info = self._config.get_user_info(self._user) if pwd and pwd != user_info[1]: # 改变密码,cookie作废 self.cookie_ed.setPlainText("") self._cookie = None if not pwd: # 输入空密码,表示删除对pwd的存储,并使用以前的cookie self._cookie = user_info[2] try: text = ";".join([f'{k}={v}' for k, v in self._cookie.items()]) except: text = '' self.cookie_ed.setPlainText(text) self._pwd = pwd def set_cookie(self): cookies = self.cookie_ed.toPlainText() if cookies: try: self._cookie = {kv.split("=")[0].strip(" "): kv.split("=")[1].strip(" ") for kv in cookies.split(";") if kv.strip(" ") } except: self._cookie = None def change_cancel_btn(self): self.update_selection(self._config.name) self.close() def change_ok_btn(self): if self._user and self._pwd: if self._user not in self._config.users_name: self._cookie = None if self._cookie: up_info = {"name": self._user, "pwd": self._pwd, "cookie": self._cookie, "work_id": -1} if self.ok_btn.text() == "切换用户": self._config.change_user(self._user) else: self._config.set_infos(up_info) self.clicked_ok.emit() self.close() elif USE_WEB_ENG: self.web = LoginWindow(self._user, self._pwd) self.web.cookie.connect(self.get_cookie_by_web) self.web.setWindowModality(Qt.WindowModality.ApplicationModal) self.web.exec() elif os.path.isfile(self._cookie_assister): try: result = os.popen(f'{self._cookie_assister} {self._user} {self._pwd}') cookie = result.read() try: self._cookie = {kv.split("=")[0].strip(" "): kv.split("=")[1].strip(" ") for kv in cookie.split(";")} except: self._cookie = None if not self._cookie: return None up_info = {"name": self._user, "pwd": self._pwd, "cookie": self._cookie, "work_id": -1} self._config.set_infos(up_info) self.clicked_ok.emit() self.close() except: pass else: title = '请使用 Cookie 登录或是选择 登录辅助程序' msg = '没有输入 Cookie,或者没有找到登录辅助程序!\n\n' + \ '推荐使用浏览器获取 cookie 填入 cookie 输入框\n\n' + \ '如果不嫌文件体积大,请下载登录辅助程序:\n' + \ 'https://github.com/rachpt/lanzou-gui/releases' message_box = QMessageBox(self) message_box.setIcon(QMessageBox.Icon.Critical) message_box.setStyleSheet(btn_style) message_box.setWindowTitle(title) message_box.setText(msg) message_box.setStandardButtons(QMessageBox.StandardButton.Close) buttonC = message_box.button(QMessageBox.StandardButton.Close) buttonC.setText('关闭') message_box.exec() def get_cookie_by_web(self, cookie): """使用辅助登录程序槽函数""" self._cookie = cookie self._close_dialog() def call_auto_get_cookie(self): """自动读取浏览器cookie槽函数""" try: self._cookie = get_cookie_from_browser() except Exception as e: logger.error(f"Browser_cookie3 Error: {e}") self.auto_get_cookie_ok.setPlainText(f"❌获取失败,错误信息\n{e}") else: if self._cookie: self._user = self._pwd = '' self.auto_get_cookie_ok.setPlainText("✅获取成功即将登录……") QTimer.singleShot(2000, self._close_dialog) else: self.auto_get_cookie_ok.setPlainText("❌获取失败\n请提前使用支持的浏览器登录蓝奏云,读取前完全退出浏览器!\n支持的浏览器与顺序:\nchrome, chromium, opera, edge, firefox") def _close_dialog(self): """关闭对话框""" up_info = {"name": self._user, "pwd": self._pwd, "cookie": self._cookie} self._config.set_infos(up_info) self.clicked_ok.emit() self.close()
class UploadDialog(QDialog): """文件上传对话框""" new_infos = pyqtSignal(object) def __init__(self, user_home): super().__init__() self.cwd = user_home self._folder_id = -1 self._folder_name = "LanZouCloud" self.set_pwd = False self.set_desc = False self.pwd = '' self.desc = '' self.allow_big_file = False self.max_size = 100 self.selected = [] self.initUI() self.set_size() self.setStyleSheet(dialog_qss_style) def set_pwd_desc_bigfile(self, settings): self.set_pwd = settings["set_pwd"] self.set_desc = settings["set_desc"] self.pwd = settings["pwd"] self.desc = settings["desc"] self.allow_big_file = settings["allow_big_file"] self.max_size = settings["max_size"] if self.allow_big_file: self.btn_chooseMultiFile.setToolTip("") else: self.btn_chooseMultiFile.setToolTip(f"文件大小上限 {self.max_size}MB") def set_values(self, folder_name, folder_id, files): self.setWindowTitle("上传文件至 ➩ " + str(folder_name)) self._folder_id = folder_id self._folder_name = folder_name if files: self.selected = files self.show_selected() self.exec() def initUI(self): self.setWindowTitle("上传文件") self.setWindowIcon(QIcon(SRC_DIR + "upload.ico")) self.logo = QLabel() self.logo.setPixmap(QPixmap(SRC_DIR + "logo3.gif")) self.logo.setStyleSheet("background-color:rgb(0,153,255);") self.logo.setAlignment(Qt.AlignmentFlag.AlignCenter) # btn 1 self.btn_chooseDir = QPushButton("选择文件夹", self) self.btn_chooseDir.setObjectName("btn_chooseDir") self.btn_chooseDir.setObjectName("btn_chooseDir") self.btn_chooseDir.setIcon(QIcon(SRC_DIR + "folder.gif")) # btn 2 self.btn_chooseMultiFile = QPushButton("选择多文件", self) self.btn_chooseDir.setObjectName("btn_chooseMultiFile") self.btn_chooseMultiFile.setObjectName("btn_chooseMultiFile") self.btn_chooseMultiFile.setIcon(QIcon(SRC_DIR + "file.ico")) # btn 3 self.btn_deleteSelect = QPushButton("移除", self) self.btn_deleteSelect.setObjectName("btn_deleteSelect") self.btn_deleteSelect.setIcon(QIcon(SRC_DIR + "delete.ico")) self.btn_deleteSelect.setToolTip("按 Delete 移除选中文件") # 列表 self.list_view = MyListView() self.list_view.drop_files.connect(self.add_drop_files) self.list_view.setViewMode(QListView.ViewMode.ListMode) self.slm = QStandardItem() self.model = QStandardItemModel() self.list_view.setModel(self.model) self.model.removeRows(0, self.model.rowCount()) # 清除旧的选择 self.list_view.setEditTriggers( QAbstractItemView.EditTrigger.NoEditTriggers) self.list_view.setSelectionBehavior( QAbstractItemView.SelectionBehavior.SelectRows) self.list_view.setSelectionMode( QAbstractItemView.SelectionMode.ExtendedSelection) 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("取消") vbox = QVBoxLayout() hbox_head = QHBoxLayout() hbox_button = QHBoxLayout() hbox_head.addWidget(self.btn_chooseDir) hbox_head.addStretch(1) hbox_head.addWidget(self.btn_chooseMultiFile) hbox_button.addWidget(self.btn_deleteSelect) hbox_button.addStretch(1) hbox_button.addWidget(self.buttonBox) vbox.addWidget(self.logo) vbox.addLayout(hbox_head) vbox.addWidget(self.list_view) vbox.addLayout(hbox_button) self.setLayout(vbox) self.setMinimumWidth(350) # 设置信号 self.btn_chooseDir.clicked.connect(self.slot_btn_chooseDir) self.btn_chooseMultiFile.clicked.connect(self.slot_btn_chooseMultiFile) self.btn_deleteSelect.clicked.connect(self.slot_btn_deleteSelect) self.buttonBox.accepted.connect(self.slot_btn_ok) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.clear_old) self.buttonBox.rejected.connect(self.reject) def set_size(self): if self.selected: h = 18 if len(self.selected) > 18 else 10 w = 40 for i in self.selected: i_len = len(i) if i_len > 100: w = 100 break if i_len > w: w = i_len self.resize(120 + w * 7, h * 30) else: self.resize(400, 300) def clear_old(self): self.selected = [] self.model.removeRows(0, self.model.rowCount()) self.set_size() def show_selected(self): self.model.removeRows(0, self.model.rowCount()) for item in self.selected: if os.path.isfile(item): self.model.appendRow( QStandardItem(QIcon(SRC_DIR + "file.ico"), item)) else: self.model.appendRow( QStandardItem(QIcon(SRC_DIR + "folder.gif"), item)) self.set_size() def backslash(self): """Windows backslash""" tasks = {} for item in self.selected: url = os.path.normpath(item) total_size = 0 total_file = 0 if os.path.isfile(url): total_size = os.path.getsize(url) if not total_size: continue # 空文件无法上传 total_file += 1 else: for filename in os.listdir(url): file_path = os.path.join(url, filename) if not os.path.isfile(file_path): continue # 跳过子文件夹 total_size += os.path.getsize(file_path) total_file += 1 tasks[url] = UpJob(url=url, fid=self._folder_id, folder=self._folder_name, pwd=self.pwd if self.set_pwd else None, desc=self.desc if self.set_desc else None, total_size=total_size, total_file=total_file) return tasks def slot_btn_ok(self): tasks = self.backslash() if self.selected: self.new_infos.emit(tasks) self.clear_old() def slot_btn_deleteSelect(self): _indexes = self.list_view.selectionModel().selection().indexes() if not _indexes: return indexes = [] for i in _indexes: # 获取所选行号 indexes.append(i.row()) indexes = set(indexes) for i in sorted(indexes, reverse=True): self.selected.remove(self.model.item(i, 0).text()) self.model.removeRow(i) self.set_size() def add_drop_files(self, files): for item in files: if item not in self.selected: self.selected.append(item) self.show_selected() def slot_btn_chooseDir(self): dir_choose = QFileDialog.getExistingDirectory(self, "选择文件夹", self.cwd) # 起始路径 if dir_choose == "": return if dir_choose not in self.selected: self.selected.append(dir_choose) self.cwd = os.path.dirname(dir_choose) self.show_selected() def slot_btn_chooseMultiFile(self): files, _ = QFileDialog.getOpenFileNames(self, "选择多文件", self.cwd, "All Files (*)") if len(files) == 0: return for _file in files: if _file not in self.selected: if os.path.getsize(_file) <= self.max_size * 1048576: self.selected.append(_file) elif self.allow_big_file: self.selected.append(_file) self.show_selected() def keyPressEvent(self, e): if e.key() == Qt.Key.Key_Delete: # delete self.slot_btn_deleteSelect()