LabelPrint/main.py

970 lines
35 KiB
Python
Raw Normal View History

2024-04-01 16:51:26 +08:00
import os
import re
import io
import sys
import clr
import time
import json
import shutil
import sqlite3
import logging
import tempfile
import pywintypes
import hashlib
import win32com.client
import win32print
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QComboBox, QCheckBox, QLineEdit, QAction, QMenu, QMessageBox, QPushButton, QVBoxLayout, QHBoxLayout, QFileDialog
from PyQt5.QtCore import Qt, QCoreApplication
from PyQt5.QtGui import QPixmap, QIntValidator
from threading import RLock
sys.path.append(os.path.dirname(__file__))
clr.AddReference('BarTender')
try:
import BarTender
except Exception:
pass
def cp(src: str, dst: str):
"""
Support file or directory.
"""
return shutil.copytree(src, dst) and True if os.path.isdir(src) else shutil.copy(src, dst) and True
def mv(src: str, dst: str):
"""
Support file or directory.
"""
return shutil.move(src, dst) and True
def rm(src: str):
"""
Support file or directory.
"""
shutil.rmtree(src) if os.path.isdir(src) else os.remove(src)
return True
def rename(path: str, filename: str):
"""
Support file or directory.
"""
if filename.find('/') != -1:
raise Exception('File name, catalog name or scroll grammar incorrect grammar.')
return os.rename(path, os.path.dirname(path) + '/' + filename) or True
def exists(path: str):
"""
Support file or directory.
"""
return os.path.exists(path)
def mkdirs(path: str):
"""
Support multi-level directory.
"""
return os.makedirs(path)
def _fd(f):
return f.fileno() if hasattr(f, 'fileno') else f
if os.name == 'nt':
import msvcrt
from ctypes import (sizeof, c_ulong, c_void_p, c_int64, Structure, Union, POINTER, windll, byref)
from ctypes.wintypes import BOOL, DWORD, HANDLE
LOCK_SH = 0x0
LOCK_NB = 0x1
LOCK_EX = 0x2
LOCK_UN = 0x9
if sizeof(c_ulong) != sizeof(c_void_p):
ULONG_PTR = c_int64
else:
ULONG_PTR = c_ulong
PVOID = c_void_p
class _OFFSET(Structure):
_fields_ = [
('Offset', DWORD),
('OffsetHigh', DWORD)
]
class _OFFSET_UNION(Union):
_fields_ = [
('_offset', _OFFSET),
('Pointer', PVOID)
]
_anonymous_ = ['_offset']
class OVERLAPPED(Structure):
_fields_ = [
('Internal', ULONG_PTR),
('InternalHigh', ULONG_PTR),
('_offset_union', _OFFSET_UNION),
('hEvent', HANDLE)
]
_anonymous_ = ['_offset_union']
LPOVERLAPPED = POINTER(OVERLAPPED)
LockFileEx = windll.kernel32.LockFileEx
LockFileEx.restype = BOOL
LockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED]
UnlockFileEx = windll.kernel32.UnlockFileEx
UnlockFileEx.restype = BOOL
UnlockFileEx.argtypes = [HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED]
def flock(f, flags):
hfile = msvcrt.get_osfhandle(_fd(f))
overlapped = OVERLAPPED()
if flags == LOCK_UN:
ret = UnlockFileEx(
hfile,
0,
0,
0xFFFF0000,
byref(overlapped)
)
else:
ret = LockFileEx(
hfile,
flags,
0,
0,
0xFFFF0000,
byref(overlapped)
)
return bool(ret)
else:
try:
import fcntl
LOCK_SH = fcntl.LOCK_SH
LOCK_NB = fcntl.LOCK_NB
LOCK_EX = fcntl.LOCK_EX
LOCK_UN = fcntl.LOCK_UN
except (ImportError, AttributeError):
LOCK_EX = LOCK_SH = LOCK_NB = 0
def flock(f, flags):
return flags == LOCK_UN
else:
def flock(f, flags):
return fcntl.flock(_fd(f), flags) == 0
def getJson(file, data=None):
if os.path.exists(file):
try:
return json.loads(open(file=file, mode='r', encoding='utf-8').read())
except json.decoder.JSONDecodeError:
return data
return data
def putJson(file, data=None):
with open(file=file, mode='w', encoding='utf-8') as f:
flock(f, LOCK_EX)
res = f.write(json.dumps(data, indent=4, ensure_ascii=True))
flock(f, LOCK_UN)
return res
class SQLite3:
__conn__ = None
__curr__ = None
__data__ = None
def __init__(self, *args, **kwargs):
if args or kwargs:
self.connect(*args, **kwargs)
def _check_connection(self):
if self.__curr__ is None:
raise Exception('Connection does not exist.')
@staticmethod
def _sqlite_dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def connect(self, database):
if self.__conn__:
raise Exception('Already Connect.')
self.__conn__ = sqlite3.connect(database, timeout=10, check_same_thread=False)
self.__conn__.row_factory = self._sqlite_dict_factory
self.__curr__ = self.__conn__.cursor()
return self
def execute(self, *args, **kwargs):
self._check_connection()
self.__curr__.execute(*args, **kwargs)
self.__conn__.commit()
return self
def rownums(self):
self._check_connection()
return self.__curr__.rowcount
def fetchall(self):
self._check_connection()
return self.__curr__.fetchall()
def fetchone(self):
self._check_connection()
return self.__curr__.fetchone()
def close(self):
if self.__curr__ or self.__conn__:
self.__curr__.close()
self.__conn__.close()
self.__curr__ = None
self.__conn__ = None
return True
class Ditto:
def __init__(self, db_file, rlock=None, class_name='Ditto-Default', limit_time=None, limit_rows=None):
self.rLock = rlock or RLock()
if limit_time is not None and not limit_time >= 0: raise Exception('Range error.')
if limit_rows is not None and not limit_rows >= 0: raise Exception('Range error.')
self.class_name = class_name
self.limit_rows = limit_rows
self.limit_time = limit_time
try:
self.database = SQLite3(db_file)
self.init_database()
except sqlite3.DatabaseError:
self.database.close() and os.remove(db_file)
raise Exception('The database was damaged and has been reset.')
def init_database(self):
with self.rLock:
self.database.execute('''
CREATE TABLE IF NOT EXISTS "ditto" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"class" TEXT(64) COLLATE NOCASE,
"value" TEXT(256) COLLATE NOCASE,
"time" integer(12)
);
''')
def fresh(self):
with self.rLock:
if self.limit_time is not None: self.database.execute('''
DELETE FROM ditto WHERE class='%s' AND time < %s;
''' % (self.class_name, (int(time.time()) - self.limit_time)))
if self.limit_rows is not None: self.database.execute('''
DELETE FROM ditto WHERE id IN (SELECT id FROM (SELECT id FROM ditto WHERE class='%s' ORDER BY time DESC LIMIT %s,100));
''' % (self.class_name, self.limit_rows))
def count(self):
with self.rLock:
self.fresh()
self.database.execute('''
SELECT COUNT(1) AS rows FROM ditto WHERE class='%s';
''' % (self.class_name,))
return self.database.fetchone()['rows']
def clear(self):
with self.rLock:
self.fresh()
self.database.execute('''
DELETE FROM ditto WHERE class='%s';
''' % (self.class_name,))
return self.database.rownums()
def query(self, value):
with self.rLock:
self.fresh()
self.database.execute('''
SELECT COUNT(1) AS rows FROM ditto WHERE class='%s' AND value='%s';
''' % (self.class_name, value))
return self.database.fetchone()['rows']
def addit(self, value):
with self.rLock:
if self.query(value) != 0: return False
self.database.execute('''
INSERT INTO ditto (\"class\", \"value\", \"time\") VALUES ('%s', '%s', '%s');
''' % (self.class_name, value, int(time.time())))
self.fresh()
return True
class LoggerFileHandler:
def __init__(self, log_file: str, mode: str = 'a', level: str = None, fmt: str = None):
self.log, self.mod = log_file, mode
self.lev, self.fmt = level, fmt
self.sty = '%'
class LoggerConsHandler:
def __init__(self, level: str = None, fmt: str = None):
self.lev, self.fmt = level, fmt
self.sty = '%'
class Logger:
logger = None
levels = {
'CRITICAL': logging.CRITICAL,
'FATAL': logging.FATAL,
'ERROR': logging.ERROR,
'WARNING': logging.WARNING,
'WARN': logging.WARN,
'INFO': logging.INFO,
'DEBUG': logging.DEBUG,
'NOTSET': logging.NOTSET,
'D': logging.DEBUG,
'I': logging.INFO,
'W': logging.WARNING,
'E': logging.ERROR,
'F': logging.FATAL
}
default_format = '{asctime} - {name} - {levelname[0]}: {message}'
default_format_style = '{'
handler_list = []
def __init__(
self,
name: str = 'default',
default_level='DEBUG',
fh: LoggerFileHandler = None,
ch: LoggerConsHandler = None,
add_default_handler=False
):
ch = LoggerConsHandler() if add_default_handler and not ch else ch
if fh and not isinstance(fh, LoggerFileHandler):
raise TypeError('The parameter fh must be <LoggerFileHandler> type.')
if ch and not isinstance(ch, LoggerConsHandler):
raise TypeError('The parameter ch must be <LoggerConsHandler> type.')
self.logger = logging.getLogger(name)
self.logger.setLevel(self.levels[default_level])
if fh:
fhandler = logging.FileHandler(filename=fh.log, mode=fh.mod, encoding='utf-8')
self.handler_list.append(fhandler)
fhandler.setLevel(self.levels[fh.lev or default_level])
fh.fmt = fh.fmt or self.default_format
fh.sty = '{' if '%' not in fh.fmt else '%'
fhandler.setFormatter(logging.Formatter(fmt=fh.fmt, style=fh.sty))
self.logger.addHandler(fhandler)
if ch:
chandler = logging.StreamHandler()
self.handler_list.append(chandler)
chandler.setLevel(self.levels[ch.lev or default_level])
ch.fmt = ch.fmt or self.default_format
ch.sty = '{' if '%' not in ch.fmt else '%'
chandler.setFormatter(logging.Formatter(fmt=ch.fmt, style=ch.sty))
self.logger.addHandler(chandler)
self.d = self.logger.debug
self.i = self.logger.info
self.w = self.logger.warning
self.e = self.logger.error
self.f = self.logger.fatal
self.c = self.logger.critical
class Setting(dict):
def __init__(self, setting_file: str, setting_default: dict):
self.data_file = os.path.abspath(setting_file)
super().__init__(getJson(self.data_file, setting_default))
def __getitem__(self, item):
if item in self:
return super().__getitem__(item)
else:
return None
def __setitem__(self, key, value):
super().__setitem__(key, value)
putJson(self.data_file, self)
class BarTenderPrint:
def __init__(self, logger: Logger):
self.logger = logger
self.label = None
self.print = None
self.sheet = None
self.bt_app = BarTender.Application()
self.bt_app.Visible = False
self.bt_format = None
self.printer_list = [printer[2] for printer in win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL)][::-1]
self.printer_current_index = 0
self.template_source_primary = {}
self.logger.i('%s%s' % ('Init', ''))
def set_label(self, data: str):
self.label = os.path.abspath(data)
self.logger.i('%s%s' % ('Open the label template ', self.label))
for _ in range(2):
if (self.bt_format is not None) == 1:
self.bt_format.Close(BarTender.BtSaveOptions.btDoNotSaveChanges)
self.bt_format = self.bt_app.Formats.Open(self.label, False, '')
self.template_source_primary = {}
self.set_print(self.printer_current_index or 0)
self.set_sheet(self.sheet or 1)
def set_print(self, data: int):
self.printer_current_index = data
self.print = self.printer_list[self.printer_current_index]
self.logger.i('%s%s' % ('Printer set to ', self.print))
if (self.bt_format is not None) == 1:
self.bt_format.PrintSetup.Printer = self.print
def set_sheet(self, data: int):
self.sheet = int(data)
self.logger.i('%s%s' % ('Number of copies ', self.sheet))
if (self.bt_format is not None) == 1:
self.bt_format.PrintSetup.IdenticalCopiesOfLabel = self.sheet
def set_data_source(self, name, value):
if name not in self.template_source_primary.keys():
self.template_source_primary[name] = self.bt_format.GetNamedSubStringValue(name)
self.logger.i('%s%s' % ('Set data source ', '%s=%s' % (name, value)))
self.bt_format.SetNamedSubStringValue(name, value)
def start_printing(self, content: str):
for k, v in {'A': content}.items():
self.set_data_source(k, v)
if (self.bt_format is not None) == 1:
self.logger.i('%s%s' % ('Printing ', 'normal'))
self.bt_format.PrintOut(False, False)
def start_printing_template(self):
for k, v in self.template_source_primary.items():
self.set_data_source(k, v)
if (self.bt_format is not None) == 1:
self.logger.i('%s%s' % ('Printing ', 'template'))
self.bt_format.PrintOut(False, False)
def generate_preview(self):
path = os.path.abspath(os.path.join(tempfile.gettempdir(), '%s%s.png' % ('preview_', int(round(time.time() * 1000)))))
if (self.bt_format is not None) == 1:
self.logger.i('%s%s' % ('Generate preview to ', path))
self.bt_format.ExportToFile(path, 'PNG', BarTender.BtColors.btColors24Bit, BarTender.BtResolution.btResolutionPrinter, BarTender.BtSaveOptions.btDoNotSaveChanges)
return path
def quit(self):
2024-04-01 19:13:51 +08:00
try:
self.bt_format is not None and self.bt_format.Close(BarTender.BtSaveOptions.btDoNotSaveChanges)
self.bt_app is not None and self.bt_app.Quit(BarTender.BtSaveOptions.btDoNotSaveChanges)
except Exception:
pass
self.bt_format = None
self.bt_app = None
self.logger.i('%s%s' % ('Quit', ''))
2024-04-01 16:51:26 +08:00
class ScanVerify:
def __init__(self, logger, rule_file):
self.logger = logger
self.checker_list = [['', '.*']]
self.checker_list.extend(getJson(os.path.abspath(rule_file), []))
self.checker_current_index = 0
def set_check(self, data: int):
self.checker_current_index = data
self.logger.i('%s%s' % ('Checker set to ', self.checker_list[self.checker_current_index][0]))
def verify(self, content: str):
return bool(re.match(self.checker_list[self.checker_current_index][1], content))
class CustomLineEdit(QLineEdit):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setStyleSheet('font-size: 12px; font-family: \'Microsoft YaHei\'; color: #000000;')
def contextMenuEvent(self, event):
menu = QMenu(self)
action_c = menu.addAction('复制')
action_p = menu.addAction('粘贴')
action_c.triggered.connect(self.copy)
action_p.triggered.connect(self.paste)
menu.popup(self.mapToGlobal(event.pos()))
class CustomLineEditNoPopup(QLineEdit):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setStyleSheet('font-size: 12px; font-family: \'Microsoft YaHei\'; color: #656565;')
def contextMenuEvent(self, event):
pass
class CustomPushButton(QPushButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setStyleSheet('font-size: 12px; font-family: \'Microsoft YaHei\'; color: #0C0C0C;')
class CustomLabel(QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setStyleSheet('font-size: 12px; font-family: \'Microsoft YaHei\'; color: #0C0C0C; border: 1px solid #0C0C0C;')
class CustomComboBox(QComboBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setStyleSheet('font-size: 12px; font-family: \'Microsoft YaHei\'; color: #0C0C0C;')
class CustomCheckBox(QCheckBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setStyleSheet('font-size: 12px; font-family: \'Microsoft YaHei\'; color: #0C0C0C;')
class CustomHBoxLayout(QHBoxLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setAlignment(Qt.AlignTop | Qt.AlignLeft)
class CustomVBoxLayout(QVBoxLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setAlignment(Qt.AlignTop | Qt.AlignLeft)
class MainWindow(QMainWindow):
def __init__(self, logger: Logger):
super().__init__()
self.app_name = '标签打印'
self.app_version = ('1.0.1', '20240401', 'zhaoyafan', 'zhaoyafan@foxmail.com', 'https://www.fanscloud.net/')
self.logger = logger
self.setting = Setting(os.path.abspath(os.path.join(os.path.dirname(__file__), 'settings.json')), {})
self.bt = BarTenderPrint(logger=self.logger)
self.sv = ScanVerify(logger=self.logger, rule_file=os.path.abspath(os.path.join(os.path.dirname(__file__), 'rules.json')))
self.ditto = Ditto(db_file='%s.db' % (os.path.splitext(__file__)[0],), class_name='LabelPrint', limit_time=7776000, limit_rows=100000)
self.last_opened_template = ['', '']
self.input_convert_letter = 0
self.setWindowTitle(self.app_name)
self.setGeometry(0, 0, 600, 400)
self.statusBar()
self.menubar = self.menuBar()
filemenu = self.menubar.addMenu('文件')
self.blockRepeatAction = QAction('禁止重复打印', self, checkable=True)
self.clearRecordAction = QAction('清空打印记录', self)
filemenu.addActions(
[
self.blockRepeatAction,
self.clearRecordAction
]
)
moremenu = self.menubar.addMenu('更多')
self.designStateAction = QAction('编辑模式', self, checkable=True)
self.createShortAction = QAction('发送快捷方式到桌面', self)
self.aboutWindowAction = QAction('关于', self)
self.blockRepeatAction.triggered.connect(self.blockRepeatActionFunction)
self.clearRecordAction.triggered.connect(self.clearRecordActionFunction)
self.designStateAction.triggered.connect(self.designStateActionFunction)
self.createShortAction.triggered.connect(self.createShortActionFunction)
self.aboutWindowAction.triggered.connect(self.aboutWindowActionFunction)
moremenu.addActions(
[
self.designStateAction,
self.createShortAction,
self.aboutWindowAction
]
)
# 定义布局
m_layout_0 = CustomVBoxLayout()
l_layout_0 = CustomVBoxLayout()
l_layout_1 = CustomHBoxLayout()
l_layout_2 = CustomVBoxLayout()
r_layout_0 = CustomVBoxLayout()
r_layout_1 = CustomHBoxLayout()
r_layout_2 = CustomHBoxLayout()
r_layout_3 = CustomVBoxLayout()
l_layout_0.addLayout(l_layout_1)
l_layout_0.addLayout(l_layout_2)
r_layout_0.addLayout(r_layout_1)
r_layout_0.addLayout(r_layout_2)
r_layout_0.addLayout(r_layout_3)
m_layout_0.addLayout(l_layout_0)
m_layout_0.addLayout(r_layout_0)
# Select file
self.butSTP = CustomPushButton('选择文件')
self.butSTP.setFixedSize(82, 27)
self.butSTP.clicked.connect(self.on_open_template_file)
l_layout_1.addWidget(self.butSTP)
# Template name
self.edtNTP = CustomLineEditNoPopup('')
self.edtNTP.setReadOnly(True)
self.edtNTP.setFixedSize(280, 27)
l_layout_1.addWidget(self.edtNTP)
# Test printing
self.butTPT = CustomPushButton('测试打印')
self.butTPT.setFixedSize(82, 27)
self.butTPT.clicked.connect(self.on_test_print)
l_layout_1.addWidget(self.butTPT)
# Print preview
self.labPRV = CustomLabel()
self.labPRV.setAlignment(Qt.AlignCenter)
self.labPRV.setFixedSize(455, 256)
l_layout_2.addWidget(self.labPRV)
# Printer
self.labNPT = CustomLabel('打印机选择')
self.labNPT.setFixedSize(82, 27)
r_layout_1.addWidget(self.labNPT)
# Select printer
self.slcSPT = CustomComboBox()
self.slcSPT.addItems(self.bt.printer_list)
self.slcSPT.setFixedSize(233, 27)
self.slcSPT.currentIndexChanged.connect(self.on_printer_changed)
r_layout_1.addWidget(self.slcSPT)
# Number of copies
self.labPQT = CustomLabel('打印份数')
self.labPQT.setFixedSize(82, 27)
r_layout_1.addWidget(self.labPQT)
self.edtNQT = CustomLineEditNoPopup('1')
self.edtNQT.setFixedSize(40, 27)
self.edtNQT.textChanged.connect(self.on_copies_editing_changed)
validator = QIntValidator(1, 999)
self.edtNQT.setValidator(validator)
r_layout_1.addWidget(self.edtNQT)
# Input verification
self.labNVR = CustomLabel('扫描验证')
self.labNVR.setFixedSize(82, 27)
r_layout_2.addWidget(self.labNVR)
# Select input verification
self.slcSVR = CustomComboBox()
self.slcSVR.addItems([data[0] for data in self.sv.checker_list])
self.slcSVR.setFixedSize(233, 27)
self.slcSVR.currentIndexChanged.connect(self.on_checker_changed)
r_layout_2.addWidget(self.slcSVR)
# Convert letter
self.chkCVL = CustomCheckBox('强制转换大写')
self.chkCVL.setFixedSize(128, 27)
self.chkCVL.stateChanged.connect(self.on_convert_letter_changed)
r_layout_2.addWidget(self.chkCVL)
# Scan
self.edtSCN = CustomLineEdit('')
self.edtSCN.setStyleSheet('QLineEdit {font-size: 28px; font-family: \'Microsoft YaHei\'; color: #000000; background-color: #FFFFCC;}')
self.edtSCN.setFixedSize(455, 45)
r_layout_3.addWidget(self.edtSCN)
# Print button
self.butSPT = CustomPushButton('打印')
self.butSPT.setFixedSize(455, 45)
self.butSPT.clicked.connect(self.on_start_printing)
self.edtSCN.returnPressed.connect(self.butSPT.click)
r_layout_3.addWidget(self.butSPT)
# 显示界面
central_widget = QWidget()
central_widget.setLayout(m_layout_0)
self.setCentralWidget(central_widget)
self.setFixedSize(self.minimumSizeHint())
screen_rect = QApplication.desktop().availableGeometry()
window_rect = self.geometry()
self.move(int((screen_rect.width() - window_rect.width()) * 0.5), int((screen_rect.height() - window_rect.height()) * 0.5))
self.edtSCN.setFocus()
self.show()
self.load_setting()
def closeEvent(self, event):
self.bt.quit()
event.accept()
def load_setting(self):
# load blockRepeat
self.blockRepeatAction.setChecked(bool(self.setting['blockRepeat']))
# load printer
self.slcSPT.setCurrentIndex(next((index for index, item in enumerate(self.bt.printer_list) if item == self.setting['printer']), 0))
# load checker
self.slcSVR.setCurrentIndex(self.setting['checker'] or 0)
# load printCopies
self.edtNQT.setText(str(self.setting['printCopies'] or 1))
# load template
tempfile_last = self.setting['template']
if (tempfile_last and os.path.exists(tempfile_last)) == 1:
self.load_template(tempfile_last)
else:
self.setting['template'] = ''
def blockRepeatActionFunction(self, checked):
self.setting['blockRepeat'] = bool(checked)
def clearRecordActionFunction(self):
count = self.ditto.clear()
if (count > 0) == 1:
QMessageBox.information(self, '提示', '打印记录已经清空:%s' % (count,))
def designStateActionFunction(self, checked):
if (not self.bt.bt_app) == 1:
return None
if (checked is True) == 1:
t1 = '1.在编辑模式中您可以动态对模板进行修改;'
t2 = '2.保存模板将会覆盖原文件永久生效;'
t3 = '3.修改完成后手动关闭编辑模式;'
QMessageBox.information(self, '提示', '%s\n%s\n%s' % (t1, t2, t3))
self.bt.bt_app.Visible = bool(1)
else:
self.bt.bt_app.Visible = bool(0)
filename = self.last_opened_template[0]
if (filename == '' or not os.path.exists(filename)) == 1:
return None
last_md5 = self.last_opened_template[1]
temp_md5 = self.md5(open(self.last_opened_template[0], 'rb'))
QMessageBox.information(self, '提示', '模板文件已被修改:\n\n%s' % filename) if (last_md5 != temp_md5) == 1 else None
self.last_opened_template[1] = temp_md5
def createShortActionFunction(self):
target = '%s.exe' % (os.path.splitext(__file__)[0],)
shortcut_path = self.path_expandvars('%%USERPROFILE%%\\Desktop\\%s.lnk' % (self.app_name,))
if (os.path.exists(target)) == 1:
self.create_shortcut(target, shortcut_path, 1)
QMessageBox.information(self, '提示', '%s' % ('已成功发送快捷方式到桌面',))
def aboutWindowActionFunction(self):
_c = self.app_version
if (not _c) == 0:
QMessageBox.information(self, '关于', "" 'version: %s, build: %s, author: %s, email: %s, site: %s' % (_c[0], _c[1], _c[2], _c[3], '<a href=\'%s\'>%s</a>' % (_c[4], _c[4])))
@staticmethod
def md5(input_data):
if isinstance(input_data, bytes):
return hashlib.md5(input_data).hexdigest()
if isinstance(input_data, str):
return hashlib.md5(bytes(input_data, encoding='utf-8')).hexdigest()
md5_object = hashlib.md5()
while True:
data = input_data.read(io.DEFAULT_BUFFER_SIZE)
if data:
md5_object.update(data)
else:
return md5_object.hexdigest()
@staticmethod
def path_expandvars(path):
resolve = os.path.expandvars(path)
if (re.search('%[A-Za-z0-9_]+%', resolve)) is not None:
raise Exception('Variable analysis failed')
return resolve
@staticmethod
def create_shortcut(target, shortcut_path, run_as_admin):
shell = win32com.client.Dispatch('WScript.Shell')
try:
os.remove(shortcut_path)
except FileNotFoundError:
pass
shortcut = shell.CreateShortCut(shortcut_path)
shortcut.TargetPath = target
if shortcut_path.endswith('.lnk'):
shortcut.Arguments = ''
shortcut.WorkingDirectory = os.path.dirname(target)
if target.startswith('\\\\'):
shortcut.WorkingDirectory = ''
shortcut.save()
if shortcut_path.endswith('.lnk') and run_as_admin:
with open(shortcut_path, 'r+b') as f:
if os.path.isfile(shortcut_path):
f.seek(21, 0)
f.write(b'\x22\x00')
def set_preview(self, preview_image: str):
if (preview_image and os.path.exists(preview_image)) == 1:
pixmap = QPixmap(preview_image)
pixmap = pixmap.scaled(self.labPRV.size(), aspectRatioMode=Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
self.labPRV.setPixmap(pixmap)
if (tempfile.gettempdir() in preview_image) == 1:
rm(preview_image)
def set_template_name(self, name):
if (isinstance(name, str)) == 1:
self.edtNTP.setText(name)
def load_template(self, file):
try:
if (file != '' and os.path.exists(file)) == 1:
self.bt.set_label(file)
self.set_template_name(os.path.splitext(os.path.basename(file))[0])
self.set_preview(self.bt.generate_preview())
self.last_opened_template[0] = file
self.last_opened_template[1] = self.md5(open(file, 'rb'))
except pywintypes.com_error as e:
t = str(e.args[1])
m = str(e.args[2][2] if isinstance(e.args[2], tuple) else e.args[2])
if (len(t) > 5) == 1:
m = '%s\n\n%s' % (t, m)
t = '错误'
QMessageBox.critical(self, t, m)
except AttributeError:
QMessageBox.critical(self, '错误', '调用服务失败')
except Exception as e:
QMessageBox.critical(self, '错误', '%s' % e)
def on_open_template_file(self):
filename, _ = QFileDialog.getOpenFileName(self, '打开文件', os.path.dirname(self.last_opened_template[0]), 'BarTender 文档 (*.btw)')
if (filename and os.path.exists(filename)) == 1:
self.load_template(filename)
self.setting['template'] = filename
def on_test_print(self):
try:
if (self.bt.bt_format is not None) == 1:
self.bt.start_printing_template()
self.set_preview(self.bt.generate_preview())
return None
QMessageBox.critical(self, '错误', '请先加载模板文件')
except pywintypes.com_error as e:
t = str(e.args[1])
m = str(e.args[2][2] if isinstance(e.args[2], tuple) else e.args[2])
if (len(t) > 5) == 1:
m = '%s\n\n%s' % (t, m)
t = '错误'
QMessageBox.critical(self, t, m)
except AttributeError:
QMessageBox.critical(self, '错误', '调用服务失败')
except Exception as e:
QMessageBox.critical(self, '错误', '%s' % e)
def on_printer_changed(self, index):
try:
self.bt.set_print(index)
self.set_preview(self.bt.generate_preview())
self.setting['printer'] = str(self.bt.print)
except pywintypes.com_error as e:
t = str(e.args[1])
m = str(e.args[2][2] if isinstance(e.args[2], tuple) else e.args[2])
if (len(t) > 5) == 1:
m = '%s\n\n%s' % (t, m)
t = '错误'
QMessageBox.critical(self, t, m)
except AttributeError:
QMessageBox.critical(self, '错误', '调用服务失败')
except Exception as e:
QMessageBox.critical(self, '错误', '%s' % e)
def on_checker_changed(self, index):
try:
self.sv.set_check(index)
self.setting['checker'] = index
except pywintypes.com_error as e:
t = str(e.args[1])
m = str(e.args[2][2] if isinstance(e.args[2], tuple) else e.args[2])
if (len(t) > 5) == 1:
m = '%s\n\n%s' % (t, m)
t = '错误'
QMessageBox.critical(self, t, m)
except AttributeError:
QMessageBox.critical(self, '错误', '调用服务失败')
except Exception as e:
QMessageBox.critical(self, '错误', '%s' % e)
def on_copies_editing_changed(self, text):
try:
if (text == '' or int(text) == 0) == 1:
text = '1'
self.edtNQT.setText(text)
else:
copies = int(text)
self.bt.set_sheet(copies)
self.setting['printCopies'] = copies
except pywintypes.com_error as e:
t = str(e.args[1])
m = str(e.args[2][2] if isinstance(e.args[2], tuple) else e.args[2])
if (len(t) > 5) == 1:
m = '%s\n\n%s' % (t, m)
t = '错误'
QMessageBox.critical(self, t, m)
except AttributeError:
QMessageBox.critical(self, '错误', '调用服务失败')
except Exception as e:
QMessageBox.critical(self, '错误', '%s' % e)
def on_convert_letter_changed(self, checked):
if (checked == 2) == 1:
self.input_convert_letter = 1
else:
self.input_convert_letter = 0
def on_start_printing(self):
edit = self.edtSCN
if (self.input_convert_letter == 1) == 1:
text = edit.text().strip().upper()
else:
text = edit.text().strip()
edit.setText(text)
if (self.bt.bt_format is None) == 1:
edit.selectAll()
return None
if (text == '') == 1:
edit.selectAll()
return None
if (not self.sv.verify(text)) == 1:
QMessageBox.critical(self, '错误', '打印的内容不符合验证规则:\n\n%s' % (text,))
edit.selectAll()
return None
if (not self.ditto.addit(text) and self.setting['blockRepeat']) == 1:
QMessageBox.critical(self, '错误', '重复打印:\n\n%s' % (text,))
edit.selectAll()
return None
try:
self.bt.start_printing(text)
self.set_preview(self.bt.generate_preview())
edit.clear()
except pywintypes.com_error as e:
t = str(e.args[1])
m = str(e.args[2][2] if isinstance(e.args[2], tuple) else e.args[2])
if (len(t) > 5) == 1:
m = '%s\n\n%s' % (t, m)
t = '错误'
QMessageBox.critical(self, t, m)
except AttributeError:
QMessageBox.critical(self, '错误', '调用服务失败')
except Exception as e:
QMessageBox.critical(self, '错误', '%s' % e)
if __name__ == '__main__':
if (os.path.basename(__file__).lower().endswith('.int')) == 1:
QCoreApplication.addLibraryPath(os.path.abspath(os.path.join(os.path.dirname(__file__), 'site-packages/PyQt5/Qt5/plugins')))
f_lock = open(file='%s.lock' % (os.path.splitext(__file__)[0],), mode='w', encoding='utf-8')
if (not flock(f_lock, LOCK_EX | LOCK_NB)) == 1:
app = QApplication(sys.argv)
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText('程序已经在运行中')
msg.setWindowTitle('提示')
msg.setStandardButtons(QMessageBox.Ok)
msg.exec_()
app.exit(1)
sys.exit(1)
try:
win32com.client.Dispatch('BarTender.Application')
except pywintypes.com_error:
app = QApplication(sys.argv)
msg = QMessageBox()
msg.setIcon(QMessageBox.Critical)
msg.setText('初始化失败请检查BarTender是否已安装。')
msg.setWindowTitle('错误')
msg.setStandardButtons(QMessageBox.Ok)
msg.exec_()
app.exit(1)
sys.exit(1)
app = QApplication(sys.argv)
window_main = MainWindow(logger=Logger(name='main', fh=LoggerFileHandler(log_file='%s.log' % (os.path.splitext(__file__)[0],), mode='w'), ch=LoggerConsHandler()))
sys.exit(app.exec_())