|
@@ -0,0 +1,439 @@
|
|
|
|
|
+package com.mes.print;
|
|
|
|
|
+
|
|
|
|
|
+import com.mes.print.model.FlowCardData;
|
|
|
|
|
+import com.mes.print.model.LabelData;
|
|
|
|
|
+
|
|
|
|
|
+import javax.swing.*;
|
|
|
|
|
+import javax.swing.border.EmptyBorder;
|
|
|
|
|
+import javax.swing.border.TitledBorder;
|
|
|
|
|
+import java.awt.*;
|
|
|
|
|
+import java.awt.event.ActionEvent;
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.PrintStream;
|
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * mes-print 模块独立测试 UI
|
|
|
|
|
+ *
|
|
|
|
|
+ * 直接 Run 这个类的 main:
|
|
|
|
|
+ * - 顶部:环境和当前配置
|
|
|
|
|
+ * - 中间:打印机类型/名称设置(保存到 config/print.properties)
|
|
|
|
|
+ * - 下方:流转卡 / 标签测试按钮
|
|
|
|
|
+ * - 底部:日志区(含 System.out / System.err 输出)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 依赖:
|
|
|
|
|
+ * - config/print.properties 在当前工作目录(会自动检测/创建)
|
|
|
|
|
+ * - 流转卡还需要 config/liuzhuanka_template.pdf
|
|
|
|
|
+ */
|
|
|
|
|
+public class PrintTest extends JFrame {
|
|
|
|
|
+
|
|
|
|
|
+ private JComboBox<String> typeBox;
|
|
|
|
|
+ private JComboBox<String> nameBox;
|
|
|
|
|
+ private JTextField cardSnField;
|
|
|
|
|
+ private JTextField cardSteelField;
|
|
|
|
|
+ private JTextField labelSnField;
|
|
|
|
|
+ private JTextField labelBarcodeField;
|
|
|
|
|
+ private JTextField labelQrcodeField;
|
|
|
|
|
+ private JTextField labelTitleField;
|
|
|
|
|
+ private JSpinner labelCopiesSpinner;
|
|
|
|
|
+ private JTextArea logArea;
|
|
|
|
|
+ private JLabel envLabel;
|
|
|
|
|
+
|
|
|
|
|
+ public PrintTest() {
|
|
|
|
|
+ super("mes-print 测试面板");
|
|
|
|
|
+ setDefaultCloseOperation(EXIT_ON_CLOSE);
|
|
|
|
|
+ setSize(720, 720);
|
|
|
|
|
+ setLocationRelativeTo(null);
|
|
|
|
|
+
|
|
|
|
|
+ JPanel root = new JPanel(new BorderLayout(8, 8));
|
|
|
|
|
+ root.setBorder(new EmptyBorder(10, 10, 10, 10));
|
|
|
|
|
+
|
|
|
|
|
+ root.add(buildTopPanel(), BorderLayout.NORTH);
|
|
|
|
|
+ root.add(buildCenterPanel(), BorderLayout.CENTER);
|
|
|
|
|
+ root.add(buildLogPanel(), BorderLayout.SOUTH);
|
|
|
|
|
+
|
|
|
|
|
+ setContentPane(root);
|
|
|
|
|
+
|
|
|
|
|
+ // 重定向 System.out / System.err 到日志区(保留原控制台)
|
|
|
|
|
+ redirectSystemStreams();
|
|
|
|
|
+
|
|
|
|
|
+ // 首次加载
|
|
|
|
|
+ refreshEnv();
|
|
|
|
|
+ refreshPrinterList();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============== UI 构建 ==============
|
|
|
|
|
+
|
|
|
|
|
+ private JPanel buildTopPanel() {
|
|
|
|
|
+ JPanel p = new JPanel(new BorderLayout(4, 4));
|
|
|
|
|
+ p.setBorder(new TitledBorder("环境 / 当前配置"));
|
|
|
|
|
+ envLabel = new JLabel("<html>加载中...</html>");
|
|
|
|
|
+ envLabel.setBorder(new EmptyBorder(4, 8, 4, 8));
|
|
|
|
|
+ p.add(envLabel, BorderLayout.CENTER);
|
|
|
|
|
+
|
|
|
|
|
+ JButton refresh = new JButton("刷新");
|
|
|
|
|
+ refresh.addActionListener(e -> {
|
|
|
|
|
+ refreshEnv();
|
|
|
|
|
+ refreshPrinterList();
|
|
|
|
|
+ log("刷新完成");
|
|
|
|
|
+ });
|
|
|
|
|
+ JPanel right = new JPanel();
|
|
|
|
|
+ right.add(refresh);
|
|
|
|
|
+ p.add(right, BorderLayout.EAST);
|
|
|
|
|
+ return p;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private JPanel buildCenterPanel() {
|
|
|
|
|
+ JPanel p = new JPanel();
|
|
|
|
|
+ p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
|
|
|
|
|
+
|
|
|
|
|
+ p.add(buildSettingPanel());
|
|
|
|
|
+ p.add(Box.createVerticalStrut(6));
|
|
|
|
|
+ p.add(buildCardPanel());
|
|
|
|
|
+ p.add(Box.createVerticalStrut(6));
|
|
|
|
|
+ p.add(buildLabelPanel());
|
|
|
|
|
+ return p;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private JPanel buildSettingPanel() {
|
|
|
|
|
+ JPanel p = new JPanel(new GridBagLayout());
|
|
|
|
|
+ p.setBorder(new TitledBorder("打印机设置"));
|
|
|
|
|
+ GridBagConstraints c = gbc();
|
|
|
|
|
+
|
|
|
|
|
+ typeBox = new JComboBox<>(new String[]{"hp", "tsc", "none"});
|
|
|
|
|
+ nameBox = new JComboBox<>();
|
|
|
|
|
+ nameBox.setEditable(true);
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 0; p.add(new JLabel("类型:"), c);
|
|
|
|
|
+ c.gridx = 1; c.weightx = 1; p.add(typeBox, c);
|
|
|
|
|
+ c.gridx = 2; c.weightx = 0;
|
|
|
|
|
+ JButton listBtn = new JButton("列出本机打印机");
|
|
|
|
|
+ listBtn.addActionListener(e -> refreshPrinterList());
|
|
|
|
|
+ p.add(listBtn, c);
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 1; c.weightx = 0; p.add(new JLabel("名称:"), c);
|
|
|
|
|
+ c.gridx = 1; c.gridwidth = 2; c.weightx = 1; p.add(nameBox, c);
|
|
|
|
|
+ c.gridwidth = 1;
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 2; c.gridwidth = 3; c.weightx = 1;
|
|
|
|
|
+ JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
|
|
|
|
+ JButton saveBtn = new JButton("保存到 print.properties");
|
|
|
|
|
+ saveBtn.addActionListener(this::onSaveConfig);
|
|
|
|
|
+ JButton reloadBtn = new JButton("Reload(不改文件)");
|
|
|
|
|
+ reloadBtn.addActionListener(e -> {
|
|
|
|
|
+ Printer.reload();
|
|
|
|
|
+ refreshEnv();
|
|
|
|
|
+ log("已 reload 配置");
|
|
|
|
|
+ });
|
|
|
|
|
+ JButton openDialogBtn = new JButton("打开自带 UI (PrinterSettingDialog)");
|
|
|
|
|
+ openDialogBtn.addActionListener(e -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 反射调用避免硬引用(jar 里的 dialog 可能签名有版本差异)
|
|
|
|
|
+ Class<?> cls = Class.forName("com.mes.print.ui.PrinterSettingDialog");
|
|
|
|
|
+ Object dlg = cls.getConstructor(java.awt.Frame.class).newInstance(this);
|
|
|
|
|
+ cls.getMethod("setVisible", boolean.class).invoke(dlg, true);
|
|
|
|
|
+ refreshEnv();
|
|
|
|
|
+ } catch (Throwable ex) {
|
|
|
|
|
+ log("[!] 无法打开 PrinterSettingDialog: " + ex.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ btns.add(saveBtn);
|
|
|
|
|
+ btns.add(reloadBtn);
|
|
|
|
|
+ btns.add(openDialogBtn);
|
|
|
|
|
+ p.add(btns, c);
|
|
|
|
|
+
|
|
|
|
|
+ return p;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private JPanel buildCardPanel() {
|
|
|
|
|
+ JPanel p = new JPanel(new GridBagLayout());
|
|
|
|
|
+ p.setBorder(new TitledBorder("测试流转卡(HP,走 PrintHelper.printFlowCard)"));
|
|
|
|
|
+ GridBagConstraints c = gbc();
|
|
|
|
|
+
|
|
|
|
|
+ cardSnField = new JTextField("+KB24IS3031T721000201");
|
|
|
|
|
+ cardSteelField = new JTextField("GY100000LX");
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 0; p.add(new JLabel("SN:"), c);
|
|
|
|
|
+ c.gridx = 1; c.weightx = 1; p.add(cardSnField, c);
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 1; c.weightx = 0; p.add(new JLabel("SteelSN:"), c);
|
|
|
|
|
+ c.gridx = 1; c.weightx = 1; p.add(cardSteelField, c);
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 2; c.gridwidth = 2; c.weightx = 1;
|
|
|
|
|
+ JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
|
|
|
|
+ JButton doIt = new JButton("打印流转卡");
|
|
|
|
|
+ doIt.addActionListener(this::onPrintCard);
|
|
|
|
|
+ btns.add(doIt);
|
|
|
|
|
+ p.add(btns, c);
|
|
|
|
|
+ return p;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private JPanel buildLabelPanel() {
|
|
|
|
|
+ JPanel p = new JPanel(new GridBagLayout());
|
|
|
|
|
+ p.setBorder(new TitledBorder("测试标签(TSC,走 PrintHelper.printLabel)"));
|
|
|
|
|
+ GridBagConstraints c = gbc();
|
|
|
|
|
+
|
|
|
|
|
+ labelTitleField = new JTextField("TEST LABEL");
|
|
|
|
|
+ labelSnField = new JTextField("TEST0001");
|
|
|
|
|
+ labelBarcodeField = new JTextField("TEST0001");
|
|
|
|
|
+ labelQrcodeField = new JTextField("TEST0001");
|
|
|
|
|
+ labelCopiesSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 99, 1));
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 0; p.add(new JLabel("标题:"), c);
|
|
|
|
|
+ c.gridx = 1; c.weightx = 1; p.add(labelTitleField, c);
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 1; c.weightx = 0; p.add(new JLabel("SN:"), c);
|
|
|
|
|
+ c.gridx = 1; c.weightx = 1; p.add(labelSnField, c);
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 2; c.weightx = 0; p.add(new JLabel("条码:"), c);
|
|
|
|
|
+ c.gridx = 1; c.weightx = 1; p.add(labelBarcodeField, c);
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 3; c.weightx = 0; p.add(new JLabel("二维码:"), c);
|
|
|
|
|
+ c.gridx = 1; c.weightx = 1; p.add(labelQrcodeField, c);
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 4; c.weightx = 0; p.add(new JLabel("份数:"), c);
|
|
|
|
|
+ c.gridx = 1; c.weightx = 1;
|
|
|
|
|
+ JPanel wrap = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
|
|
|
|
|
+ wrap.add(labelCopiesSpinner);
|
|
|
|
|
+ p.add(wrap, c);
|
|
|
|
|
+
|
|
|
|
|
+ c.gridx = 0; c.gridy = 5; c.gridwidth = 2; c.weightx = 1;
|
|
|
|
|
+ JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
|
|
|
|
+ JButton doIt = new JButton("打印标签");
|
|
|
|
|
+ doIt.addActionListener(this::onPrintLabel);
|
|
|
|
|
+ btns.add(doIt);
|
|
|
|
|
+ p.add(btns, c);
|
|
|
|
|
+ return p;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private JPanel buildLogPanel() {
|
|
|
|
|
+ JPanel p = new JPanel(new BorderLayout());
|
|
|
|
|
+ p.setBorder(new TitledBorder("日志"));
|
|
|
|
|
+ p.setPreferredSize(new Dimension(700, 220));
|
|
|
|
|
+
|
|
|
|
|
+ logArea = new JTextArea();
|
|
|
|
|
+ logArea.setEditable(false);
|
|
|
|
|
+ logArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
|
|
|
|
+
|
|
|
|
|
+ JScrollPane sp = new JScrollPane(logArea);
|
|
|
|
|
+ p.add(sp, BorderLayout.CENTER);
|
|
|
|
|
+
|
|
|
|
|
+ JPanel bar = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
|
|
|
|
+ JButton clr = new JButton("清空日志");
|
|
|
|
|
+ clr.addActionListener(e -> logArea.setText(""));
|
|
|
|
|
+ bar.add(clr);
|
|
|
|
|
+ p.add(bar, BorderLayout.SOUTH);
|
|
|
|
|
+ return p;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============== 事件处理 ==============
|
|
|
|
|
+
|
|
|
|
|
+ private void onSaveConfig(ActionEvent e) {
|
|
|
|
|
+ String type = (String) typeBox.getSelectedItem();
|
|
|
|
|
+ Object nameObj = nameBox.getSelectedItem();
|
|
|
|
|
+ String name = nameObj == null ? "" : nameObj.toString().trim();
|
|
|
|
|
+ try {
|
|
|
|
|
+ Printer.setPrinter(type, name);
|
|
|
|
|
+ log("已保存: type=" + type + " name=" + name);
|
|
|
|
|
+ refreshEnv();
|
|
|
|
|
+ } catch (Throwable ex) {
|
|
|
|
|
+ log("[!] 保存失败: " + ex.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void onPrintCard(ActionEvent e) {
|
|
|
|
|
+ String sn = cardSnField.getText().trim();
|
|
|
|
|
+ String steel = cardSteelField.getText().trim();
|
|
|
|
|
+ if (sn.isEmpty()) {
|
|
|
|
|
+ log("[!] SN 不能为空");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 提示模板来源(jar 里已内置,不存在会自动 fallback 到 classpath 资源)
|
|
|
|
|
+ File pdf = new File("config/liuzhuanka_template.pdf");
|
|
|
|
|
+ if (!pdf.exists()) {
|
|
|
|
|
+ log("[i] " + pdf.getAbsolutePath() + " 不存在,将使用 jar 内置模板");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 打印前预检打印机状态,脱机的话直接提示,不阻塞 UI
|
|
|
|
|
+ checkPrinterStatus();
|
|
|
|
|
+
|
|
|
|
|
+ log("[card] 开始 sn=" + sn + " steelSn=" + steel);
|
|
|
|
|
+ new SwingWorker<Boolean, Void>() {
|
|
|
|
|
+ @Override protected Boolean doInBackground() {
|
|
|
|
|
+ return PrintHelper.printFlowCard(sn, steel.isEmpty() ? null : steel);
|
|
|
|
|
+ }
|
|
|
|
|
+ @Override protected void done() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ log("[card] 结果: " + (get() ? "成功(任务已提交到打印队列)" : "失败"));
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ log("[card] 异常: " + ex.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }.execute();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 通过 javax.print API 查一下目标打印机的状态标志 */
|
|
|
|
|
+ private void checkPrinterStatus() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String name = Printer.getCurrentName();
|
|
|
|
|
+ javax.print.PrintService svc = null;
|
|
|
|
|
+ for (javax.print.PrintService s : java.awt.print.PrinterJob.lookupPrintServices()) {
|
|
|
|
|
+ if (name != null && !name.isEmpty()
|
|
|
|
|
+ && s.getName().toLowerCase().contains(name.toLowerCase())) {
|
|
|
|
|
+ svc = s;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (svc == null) {
|
|
|
|
|
+ log("[!] 未找到匹配 name=" + name + " 的打印机(先用「列出本机打印机」检查名字)");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ javax.print.attribute.standard.PrinterIsAcceptingJobs accept =
|
|
|
|
|
+ svc.getAttribute(javax.print.attribute.standard.PrinterIsAcceptingJobs.class);
|
|
|
|
|
+ javax.print.attribute.standard.PrinterState state =
|
|
|
|
|
+ svc.getAttribute(javax.print.attribute.standard.PrinterState.class);
|
|
|
|
|
+ javax.print.attribute.standard.PrinterStateReasons reasons =
|
|
|
|
|
+ svc.getAttribute(javax.print.attribute.standard.PrinterStateReasons.class);
|
|
|
|
|
+ javax.print.attribute.standard.QueuedJobCount queued =
|
|
|
|
|
+ svc.getAttribute(javax.print.attribute.standard.QueuedJobCount.class);
|
|
|
|
|
+
|
|
|
|
|
+ log("[state] 打印机=" + svc.getName()
|
|
|
|
|
+ + " 接受任务=" + accept
|
|
|
|
|
+ + " 状态=" + state
|
|
|
|
|
+ + " 队列中=" + (queued == null ? "?" : queued.getValue())
|
|
|
|
|
+ + " 原因=" + reasons);
|
|
|
|
|
+ if (accept != null
|
|
|
|
|
+ && accept == javax.print.attribute.standard.PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS) {
|
|
|
|
|
+ log("[!] 打印机当前拒绝接受任务(脱机/暂停/驱动异常)——请先在 Windows 里把它上线");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Throwable t) {
|
|
|
|
|
+ log("[!] 查打印机状态失败: " + t.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void onPrintLabel(ActionEvent e) {
|
|
|
|
|
+ // 直接用底层 Printer + LabelData,参数更清晰
|
|
|
|
|
+ LabelData d = new LabelData();
|
|
|
|
|
+ d.setTitle(labelTitleField.getText().trim());
|
|
|
|
|
+ d.setSn(labelSnField.getText().trim());
|
|
|
|
|
+ d.setBarcode(labelBarcodeField.getText().trim());
|
|
|
|
|
+ d.setQrcode(labelQrcodeField.getText().trim());
|
|
|
|
|
+ d.setCopies((Integer) labelCopiesSpinner.getValue());
|
|
|
|
|
+ if (d.getSn().isEmpty()) {
|
|
|
|
|
+ log("[!] SN 不能为空");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ log("[label] 开始 sn=" + d.getSn() + " copies=" + d.getCopies());
|
|
|
|
|
+ new SwingWorker<Void, Void>() {
|
|
|
|
|
+ @Override protected Void doInBackground() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Printer.printLabel(d);
|
|
|
|
|
+ log("[label] 已发送到打印机");
|
|
|
|
|
+ } catch (PrinterException ex) {
|
|
|
|
|
+ log("[label] 打印异常: " + ex.getMessage());
|
|
|
|
|
+ } catch (Throwable ex) {
|
|
|
|
|
+ log("[label] 异常: " + ex.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }.execute();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============== 辅助 ==============
|
|
|
|
|
+
|
|
|
|
|
+ private void refreshEnv() {
|
|
|
|
|
+ String type, name;
|
|
|
|
|
+ try { type = Printer.getCurrentType(); } catch (Throwable t) { type = "?(" + t.getMessage() + ")"; }
|
|
|
|
|
+ try { name = Printer.getCurrentName(); } catch (Throwable t) { name = "?"; }
|
|
|
|
|
+
|
|
|
|
|
+ File cfg = new File("config/print.properties");
|
|
|
|
|
+ File pdf = new File("config/liuzhuanka_template.pdf");
|
|
|
|
|
+
|
|
|
|
|
+ String pdfHint = pdf.exists() ? "存在=true" : "存在=false(将用 jar 内置模板)";
|
|
|
|
|
+ String html = "<html>"
|
|
|
|
|
+ + "工作目录: " + esc(new File(".").getAbsolutePath()) + "<br>"
|
|
|
|
|
+ + "Java 版本: " + esc(System.getProperty("java.version")) + "<br>"
|
|
|
|
|
+ + "配置文件: " + esc(cfg.getAbsolutePath()) + " 存在=" + cfg.exists() + "<br>"
|
|
|
|
|
+ + "流转卡模板: " + esc(pdf.getAbsolutePath()) + " " + pdfHint + "<br>"
|
|
|
|
|
+ + "当前配置: <b>type=" + esc(type) + " name=" + esc(name) + "</b>"
|
|
|
|
|
+ + "</html>";
|
|
|
|
|
+ envLabel.setText(html);
|
|
|
|
|
+
|
|
|
|
|
+ // 把当前配置同步到编辑控件(不触发保存)
|
|
|
|
|
+ if (typeBox != null && type != null) {
|
|
|
|
|
+ for (int i = 0; i < typeBox.getItemCount(); i++) {
|
|
|
|
|
+ if (type.equalsIgnoreCase(typeBox.getItemAt(i))) {
|
|
|
|
|
+ typeBox.setSelectedIndex(i);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (nameBox != null && name != null && !name.isEmpty()) {
|
|
|
|
|
+ nameBox.setSelectedItem(name);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void refreshPrinterList() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<String> names = Printer.listAvailablePrinters();
|
|
|
|
|
+ String cur = nameBox.getSelectedItem() == null ? "" : nameBox.getSelectedItem().toString();
|
|
|
|
|
+ nameBox.removeAllItems();
|
|
|
|
|
+ for (String n : names) nameBox.addItem(n);
|
|
|
|
|
+ if (!cur.isEmpty()) nameBox.setSelectedItem(cur);
|
|
|
|
|
+ log("发现打印机 " + names.size() + " 台");
|
|
|
|
|
+ } catch (Throwable t) {
|
|
|
|
|
+ log("[!] 列打印机失败: " + t.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void log(String msg) {
|
|
|
|
|
+ String line = new SimpleDateFormat("HH:mm:ss").format(new Date()) + " " + msg;
|
|
|
|
|
+ SwingUtilities.invokeLater(() -> {
|
|
|
|
|
+ logArea.append(line + "\n");
|
|
|
|
|
+ logArea.setCaretPosition(logArea.getDocument().getLength());
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 把 System.out / System.err 抄一份到日志区(保留原控制台输出) */
|
|
|
|
|
+ private void redirectSystemStreams() {
|
|
|
|
|
+ System.setOut(new PrintStream(new LoggingStream(System.out, false), true));
|
|
|
|
|
+ System.setErr(new PrintStream(new LoggingStream(System.err, true), true));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 按字节缓存,遇到换行才用平台默认编码 decode,避免中文两字节被拆成两个 char */
|
|
|
|
|
+ private class LoggingStream extends java.io.OutputStream {
|
|
|
|
|
+ private final java.io.OutputStream orig;
|
|
|
|
|
+ private final boolean err;
|
|
|
|
|
+ private final java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream(256);
|
|
|
|
|
+ LoggingStream(java.io.OutputStream orig, boolean err) { this.orig = orig; this.err = err; }
|
|
|
|
|
+ @Override public void write(int b) throws java.io.IOException {
|
|
|
|
|
+ orig.write(b);
|
|
|
|
|
+ if (b == '\n') {
|
|
|
|
|
+ String s = new String(buf.toByteArray()); // 平台默认编码
|
|
|
|
|
+ buf.reset();
|
|
|
|
|
+ log((err ? "[stderr] " : "") + s);
|
|
|
|
|
+ } else if (b != '\r') {
|
|
|
|
|
+ buf.write(b);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static GridBagConstraints gbc() {
|
|
|
|
|
+ GridBagConstraints c = new GridBagConstraints();
|
|
|
|
|
+ c.fill = GridBagConstraints.HORIZONTAL;
|
|
|
|
|
+ c.insets = new Insets(3, 6, 3, 6);
|
|
|
|
|
+ c.anchor = GridBagConstraints.WEST;
|
|
|
|
|
+ return c;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String esc(String s) {
|
|
|
|
|
+ if (s == null) return "";
|
|
|
|
|
+ return s.replace("&", "&").replace("<", "<").replace(">", ">");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static void main(String[] args) {
|
|
|
|
|
+ try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Throwable ignored) {}
|
|
|
|
|
+ SwingUtilities.invokeLater(() -> new PrintTest().setVisible(true));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|