-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathcustom_qwidget.py
355 lines (309 loc) · 14.1 KB
/
custom_qwidget.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 'zfb'
# time: 2022-05-03 18:02
import logging
from os.path import isdir, normpath, exists
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QPlainTextEdit, QDialog, QGridLayout,
QProgressBar, QPushButton, QRadioButton, QSizePolicy, QStyleFactory,
QFileDialog, QMessageBox, QLineEdit, QVBoxLayout, QHBoxLayout, QGroupBox)
from custom_formatter import CustomFormatter
from batch_work import BatchWork, scan_process
from os import makedirs, path
from datetime import datetime
from sql_helper import get_status, clean_files_table, clean_status_table
from utils import get_base_path
class QPlainTextEditLogger(QObject, logging.Handler):
'''自定义Qt控件,继承自QObject,初始化时创建QPlainTextEdit控件\n
用于实现QPlainTextEdit的日志输出功能
'''
# 信号量,负责实现外部进程更新UI进程的界面显示
# UI进程的界面显示只能在UI主进程里面进行,所以这里触发信号,并传递参数
# 前往信号对应的槽函数,进行处理
# 该信号用于触发和传递日志信息
new_record = pyqtSignal(object)
def __init__(self, parent=None):
super().__init__()
self.widget = QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.new_record.emit(msg)
class QDropLineEdit(QLineEdit):
'''用于实现拖放文件夹到编辑框的功能\n
自定义Qt控件,继承自QLineEdit
'''
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event):
md = event.mimeData()
if md.hasUrls():
files = [url.toLocalFile() for url in md.urls()]
# 只接受文件夹的拖放
files = [x for x in files if isdir(normpath(x))]
num = len(files)
if num > 1:
logging.warning(f"检测到多条数据,只读取第一个!")
elif num == 0:
logging.warning(f"请确保拖放文件夹而不是文件!")
return
self.setText(files[0])
event.acceptProposedAction()
class QrDetectDialog(QDialog):
'''Qt界面的主窗体,继承自QDialog
'''
def __init__(self, parent=None):
super(QrDetectDialog, self).__init__(parent)
# 用于写入日志到QPlainTextEdit
self.logger = QPlainTextEditLogger()
logging.getLogger().addHandler(self.logger)
# 设置日志的格式化类
self.logger.setFormatter(CustomFormatter())
# 设置日志的记录等级,低于该等级的日志将不会输出
logging.getLogger().setLevel(logging.DEBUG)
# 信号槽绑定,绑定在QPlainTextEditLogger创建的new_record信号到该函数
self.logger.new_record.connect(self.logger.widget.appendHtml)
self._loadThread = None
self.run_func = None
self.log_file = None
# 界面基本元素创建
self.createBottomLeftGroupBox()
self.createTopLeftGroupBox()
self.createControlGroupBox()
self.createRightGroupBox(self.logger.widget)
self.createProgressBar()
# 用于在pyqt5中的一个线程中,开启多进程
self.runButton.clicked.connect(self.batch_work)
self.pauseButton.clicked.connect(self.pause_batch_work)
self.resumeButton.clicked.connect(self.resume_batch_work)
self.stopButton.clicked.connect(self.stop_batch_work)
self.radioButton1.clicked.connect(self.disableCutPathStatus)
self.radioButton2.clicked.connect(self.clickCutRadioButton)
self.radioButton3.clicked.connect(self.clickDecodeRadioButton)
self.imgPathButton.clicked.connect(self.get_img_path)
self.cutPathButton.clicked.connect(self.get_cut_path)
# 主界面布局搭建,网格布局
mainLayout = QGridLayout()
# addWidget(QWidget, row: int, column: int, rowSpan: int, columnSpan: int)
mainLayout.addWidget(self.topLeftGroupBox, 1, 0)
mainLayout.addWidget(self.bottomLeftGroupBox, 0, 0)
mainLayout.addWidget(self.controlGroupBox, 2, 0)
mainLayout.addWidget(self.rightGroupBox, 0, 1, 3, 1)
mainLayout.addWidget(self.progressBar, 3, 0, 1, 2)
# 设置每一列的宽度比例,第0列的宽度为1;第1列的宽度为2
mainLayout.setColumnStretch(0, 1)
mainLayout.setColumnStretch(1, 2)
# 设置每一行的宽度比例,第0行的宽度为1;第1行的宽度为2
mainLayout.setRowStretch(0, 1)
mainLayout.setRowStretch(1, 1)
mainLayout.setRowStretch(2, 2)
self.setLayout(mainLayout)
self.setWindowTitle("图片二维码检测识别 github.com/zfb132/QrScan")
self.changeStyle('WindowsVista')
self.load_exists()
def load_exists(self):
self.is_first = True
res = get_status()
if res:
self.is_first = False
# 弹窗询问是否继续上次的操作
reply = QMessageBox.question(self, '提示', '检测到上次未完成的操作,是否继续?', QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
if reply == QMessageBox.No:
clean_files_table()
clean_status_table()
return
operation, img_path, cut_path = res
if operation == "delete":
self.radioButton1.setChecked(True)
self.radioButton2.setChecked(False)
self.radioButton3.setChecked(False)
elif operation == "cut":
self.radioButton2.setChecked(True)
self.radioButton1.setChecked(False)
self.radioButton3.setChecked(False)
elif operation == "decode":
self.radioButton3.setChecked(True)
self.radioButton1.setChecked(False)
self.radioButton2.setChecked(False)
if img_path:
self.imgPathTextBox.setText(img_path)
if cut_path:
self.cutPathTextBox.setText(cut_path)
logging.info(f"上次未完成的操作为:{operation}")
logging.info(f"图片路径为:{img_path}")
logging.info(f"保存路径为:{cut_path}")
def set_run_func(self, func):
self.run_func = func
def get_img_path(self):
directory = QFileDialog.getExistingDirectory(self, "选取图片所在的文件夹")
self.imgPathTextBox.setText(directory)
def get_cut_path(self):
directory = QFileDialog.getExistingDirectory(self, "选取保存结果的文件夹")
self.cutPathTextBox.setText(directory)
def set_log_file(self):
log_name = path.join(get_base_path(), "log", f"{datetime.now().strftime('%Y%m%d%H%M%S')}.txt")
try:
self.log_file = open(log_name, "w", encoding="utf-8")
logging.info(f"日志文件{log_name}创建成功")
except Exception as e:
logging.warning(f"日志文件{log_name}创建失败")
logging.warning(repr(e))
def batch_work(self):
# 运行按钮的响应函数
# 获取操作类型,cut还是delete
operation = 'cut'
if self.radioButton1.isChecked():
operation = 'delete'
if self.radioButton2.isChecked():
operation = 'cut'
if self.radioButton3.isChecked():
operation = 'decode'
c1 = self.imgPathTextBox.text().strip()
if not c1:
QMessageBox.warning(self, '警告', f'请先设置图片所在文件夹!', QMessageBox.Yes)
return
# 保证路径都是标准格式
img_path = normpath(c1)
if not exists(img_path):
QMessageBox.warning(self, '警告', f'不存在路径:{img_path}', QMessageBox.Yes)
return
cut_path = ""
if operation != 'delete':
c2 = self.cutPathTextBox.text().strip()
if not c2:
msg = "请先设置保存二维码图片的文件夹!"
if operation == 'decode':
msg = "请先设置保存二维码识别结果的文件夹!"
QMessageBox.warning(self, '警告', f'{msg}', QMessageBox.Yes)
return
cut_path = normpath(c2)
if not exists(cut_path):
QMessageBox.warning(self, '警告', f'不存在路径:{img_path}', QMessageBox.Yes)
return
# 设置默认日志
self.logger.widget.setPlainText("")
logging.critical("作者:zfb")
logging.critical("https://github.com/zfb132/QrScan")
# 创建线程
self._loadThread=QThread(parent=self)
self.set_log_file()
# vars = [1, 2, 3, 4, 5, 6]*6
vars = [img_path, cut_path, operation, self.log_file, self.is_first]
self.loadThread=BatchWork(vars, self.run_func)
# 为BatchWork类的两个信号绑定槽函数
self.loadThread.notifyProgress.connect(self.updateProgressBar)
self.loadThread.notifyStatus.connect(self.updateButtonStatus)
# 用于开辟新进程,不阻塞主界面
self.loadThread.moveToThread(self._loadThread)
self._loadThread.started.connect(self.loadThread.run)
self._loadThread.start()
def pause_batch_work(self):
if self.loadThread:
self.loadThread.event.clear()
logging.critical("暂停运行!")
self.resumeButton.setDisabled(False)
self.pauseButton.setDisabled(True)
def resume_batch_work(self):
if self.loadThread:
self.loadThread.event.set()
logging.critical("继续运行!")
self.resumeButton.setDisabled(True)
self.pauseButton.setDisabled(False)
def stop_batch_work(self):
if self.loadThread:
self.loadThread.pool.terminate()
self.loadThread.pool.join()
logging.critical("手动停止运行!")
self.runButton.setDisabled(False)
self.pauseButton.setDisabled(True)
self.resumeButton.setDisabled(True)
self.stopButton.setDisabled(True)
# if self._loadThread:
# self._loadThread.quit()
# self._loadThread.wait()
# self.imgPathTextBox.setText("AA")
def changeStyle(self, styleName):
QApplication.setStyle(QStyleFactory.create(styleName))
self.changePalette()
def changePalette(self):
QApplication.setPalette(QApplication.style().standardPalette())
def updateProgressBar(self, i):
self.progressBar.setValue(i)
def updateButtonStatus(self, status):
self.runButton.setDisabled(status)
self.pauseButton.setDisabled(not status)
self.resumeButton.setDisabled(True)
self.stopButton.setDisabled(not status)
if not status:
QMessageBox.information(self, '提示', '成功完成扫描!', QMessageBox.Yes)
def disableCutPathStatus(self):
self.cutPathButton.setDisabled(True)
self.cutPathTextBox.setDisabled(True)
def clickCutRadioButton(self):
self.cutPathButton.setDisabled(False)
self.cutPathTextBox.setDisabled(False)
self.cutPathButton.setText("选择剪切图片文件夹")
def clickDecodeRadioButton(self):
self.cutPathButton.setDisabled(False)
self.cutPathTextBox.setDisabled(False)
self.cutPathButton.setText("选择保存二维码识别结果文件夹")
def createBottomLeftGroupBox(self):
self.bottomLeftGroupBox = QGroupBox("包含二维码的图片操作")
self.radioButton1 = QRadioButton("删除")
self.radioButton2 = QRadioButton("剪切")
self.radioButton3 = QRadioButton("识别")
self.radioButton2.setChecked(True)
layout = QGridLayout()
layout.addWidget(self.radioButton1,0,0,1,1)
layout.addWidget(self.radioButton2,1,0,1,1)
layout.addWidget(self.radioButton3,0,1,1,1)
#layout.addStretch(1)
self.bottomLeftGroupBox.setLayout(layout)
def createTopLeftGroupBox(self):
self.topLeftGroupBox = QGroupBox("设置路径")
self.imgPathButton = QPushButton("选择原始图片文件夹")
self.imgPathButton.setDefault(True)
self.imgPathTextBox = QDropLineEdit("")
self.cutPathTextBox = QDropLineEdit("")
self.cutPathButton = QPushButton("选择剪切图片文件夹")
self.cutPathButton.setDefault(True)
layout = QVBoxLayout()
layout.addWidget(self.imgPathButton)
layout.addWidget(self.imgPathTextBox)
layout.addWidget(self.cutPathButton)
layout.addWidget(self.cutPathTextBox)
self.topLeftGroupBox.setLayout(layout)
def createControlGroupBox(self):
self.controlGroupBox = QGroupBox("控制按钮")
layout = QGridLayout()
self.runButton = QPushButton("启动")
self.runButton.setDefault(True)
self.pauseButton = QPushButton("暂停")
self.pauseButton.setDefault(False)
self.pauseButton.setDisabled(True)
self.resumeButton = QPushButton("继续")
self.resumeButton.setDefault(False)
self.resumeButton.setDisabled(True)
self.stopButton = QPushButton("停止")
self.stopButton.setDefault(False)
self.stopButton.setDisabled(True)
layout.addWidget(self.runButton, 0, 0)
layout.addWidget(self.pauseButton, 0, 1)
layout.addWidget(self.resumeButton, 1, 0)
layout.addWidget(self.stopButton, 1, 1)
self.controlGroupBox.setLayout(layout)
def createRightGroupBox(self, widget):
self.rightGroupBox = QGroupBox("运行日志")
layout = QHBoxLayout()
widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
widget.setMaximumBlockCount(10000)
widget.setPlainText("作者:zfb\nhttps://github.com/zfb132/QrScan")
layout.addWidget(widget)
#layout.addStretch(0)
self.rightGroupBox.setLayout(layout)
def createProgressBar(self):
self.progressBar = QProgressBar()
self.progressBar.setRange(0, 100)
self.progressBar.setValue(0)