• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jiangxincode / ApkToolBoxGUI / #1428

21 Apr 2026 03:41PM UTC coverage: 3.42% (-0.03%) from 3.452%
#1428

push

jiangxincode
feat: add export to Excel functionality for result panels

- Add ExcelExporter utility class with XSSFWorkbook-based .xlsx export
- Add right-click context menu 'Export to Excel' in PdfStatPanel
- Add right-click context menu 'Export to Excel' in PdfFinderPanel
- Add right-click context menu 'Export to Excel' in WordStatPanel
- Add right-click context menu 'Export to Excel' in DuplicateSearchPanel

0 of 69 new or added lines in 5 files covered. (0.0%)

4 existing lines in 4 files now uncovered.

250 of 7311 relevant lines covered (3.42%)

0.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/edu/jiangxin/apktoolbox/pdf/stat/PdfStatPanel.java
1
package edu.jiangxin.apktoolbox.pdf.stat;
2

3
import edu.jiangxin.apktoolbox.pdf.PdfUtils;
4
import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
5
import edu.jiangxin.apktoolbox.swing.extend.FileListPanel;
6
import edu.jiangxin.apktoolbox.utils.Constants;
7
import edu.jiangxin.apktoolbox.utils.DateUtils;
8
import edu.jiangxin.apktoolbox.utils.ExcelExporter;
9
import edu.jiangxin.apktoolbox.utils.FileUtils;
10

11
import javax.swing.*;
12
import javax.swing.table.DefaultTableModel;
13
import java.awt.*;
14
import java.awt.event.ActionEvent;
15
import java.awt.event.ActionListener;
16
import java.awt.event.MouseAdapter;
17
import java.awt.event.MouseEvent;
18
import java.io.File;
19
import java.io.Serial;
20
import java.util.*;
21
import java.util.List;
22
import java.util.concurrent.ExecutorService;
23
import java.util.concurrent.Executors;
24
import java.util.concurrent.Future;
25
import java.util.concurrent.atomic.AtomicInteger;
26

27
public class PdfStatPanel extends EasyPanel {
×
28

29
    @Serial
30
    private static final long serialVersionUID = 1L;
31

32
    private JTabbedPane tabbedPane;
33

34
    private JPanel mainPanel;
35

36
    private FileListPanel fileListPanel;
37

38
    private JRadioButton pageCountRadioButton;
39

40
    private JCheckBox isRecursiveSearched;
41

42
    private JPanel resultPanel;
43

44
    private JTable resultTable;
45

46
    private DefaultTableModel resultTableModel;
47

48
    private JLabel statInfoLabel;
49

50
    private JButton statButton;
51
    private JButton cancelButton;
52

53
    private JProgressBar progressBar;
54

55
    private transient SearchThread searchThread;
56

57
    private long totalFileSize = 0;
×
58

59
    private int totalPageCount = 0;
×
60

61
    private transient final List<Vector<Object>> resultFileList = new ArrayList<>();
×
62

63

64
    @Override
65
    public void initUI() {
66
        tabbedPane = new JTabbedPane();
×
67
        add(tabbedPane);
×
68

69
        createMainPanel();
×
70
        tabbedPane.addTab("Option", null, mainPanel, "Show Stat Options");
×
71

72
        createResultPanel();
×
73
        tabbedPane.addTab("Result", null, resultPanel, "Show Stat Result");
×
74
    }
×
75

76
    private void createMainPanel() {
77
        mainPanel = new JPanel();
×
78
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
×
79

80
        fileListPanel = new FileListPanel();
×
81
        fileListPanel.initialize();
×
82

83
        JPanel checkOptionPanel = new JPanel();
×
84
        checkOptionPanel.setLayout(new BoxLayout(checkOptionPanel, BoxLayout.X_AXIS));
×
85
        checkOptionPanel.setBorder(BorderFactory.createTitledBorder("Check Options"));
×
86

87
        ButtonGroup buttonGroup = new ButtonGroup();
×
88

89
        pageCountRadioButton = new JRadioButton("统计页数");
×
90
        pageCountRadioButton.setSelected(true);
×
91
        pageCountRadioButton.setEnabled(false);
×
92
        buttonGroup.add(pageCountRadioButton);
×
93

94
        JPanel typePanel = new JPanel();
×
95
        typePanel.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));
×
96
        typePanel.add(pageCountRadioButton);
×
97

98
        checkOptionPanel.add(typePanel);
×
99
        checkOptionPanel.add(Box.createHorizontalGlue());
×
100

101
        JPanel searchOptionPanel = new JPanel();
×
102
        searchOptionPanel.setLayout(new BoxLayout(searchOptionPanel, BoxLayout.X_AXIS));
×
103
        searchOptionPanel.setBorder(BorderFactory.createTitledBorder("Stat Options"));
×
104

105
        isRecursiveSearched = new JCheckBox("Recursive");
×
106
        isRecursiveSearched.setSelected(true);
×
107
        searchOptionPanel.add(isRecursiveSearched);
×
108
        searchOptionPanel.add(Box.createHorizontalGlue());
×
109

110
        JPanel operationPanel = new JPanel();
×
111
        operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
×
112
        operationPanel.setBorder(BorderFactory.createTitledBorder("Operations"));
×
113

114
        JPanel buttonPanel = new JPanel();
×
115
        buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
×
116

117
        statButton = new JButton("Stat");
×
118
        cancelButton = new JButton("Cancel");
×
119
        cancelButton.setEnabled(false);
×
120
        statButton.addActionListener(new OperationButtonActionListener());
×
121
        cancelButton.addActionListener(new OperationButtonActionListener());
×
122
        operationPanel.add(statButton);
×
123
        operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
124
        operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
125
        operationPanel.add(cancelButton);
×
126
        operationPanel.add(Box.createHorizontalGlue());
×
127

128
        progressBar = new JProgressBar();
×
129
        progressBar.setStringPainted(true);
×
130
        progressBar.setString("Ready");
×
131

132
        mainPanel.add(fileListPanel);
×
133
        mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
134
        mainPanel.add(checkOptionPanel);
×
135
        mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
136
        mainPanel.add(searchOptionPanel);
×
137
        mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
138
        mainPanel.add(operationPanel);
×
139
        mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
140
        mainPanel.add(progressBar);
×
141
    }
×
142

143
    private void createResultPanel() {
144
        resultPanel = new JPanel();
×
145
        resultPanel.setLayout(new BoxLayout(resultPanel, BoxLayout.Y_AXIS));
×
146

147
        resultTableModel = new PdfFilesTableModel(new Vector<>(), PdfFilesConstants.COLUMN_NAMES);
×
148
        resultTable = new JTable(resultTableModel);
×
149
        resultTable.setDefaultRenderer(Vector.class, new PdfFilesTableCellRenderer());
×
150
        for (int i = 0; i < resultTable.getColumnCount(); i++) {
×
151
            resultTable.getColumn(resultTable.getColumnName(i)).setCellRenderer(new PdfFilesTableCellRenderer());
×
152
        }
153
        resultTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
×
NEW
154
        resultTable.addMouseListener(new MouseAdapter() {
×
155
            @Override
156
            public void mouseReleased(MouseEvent e) {
NEW
157
                if (e.isPopupTrigger() && e.getComponent() instanceof JTable) {
×
NEW
158
                    JPopupMenu popupmenu = new JPopupMenu();
×
NEW
159
                    JMenuItem exportMenuItem = new JMenuItem("导出到 Excel");
×
NEW
160
                    exportMenuItem.addActionListener(ev ->
×
NEW
161
                        ExcelExporter.export(resultTableModel, "pdf_stat_export.xlsx", PdfStatPanel.this));
×
NEW
162
                    popupmenu.add(exportMenuItem);
×
NEW
163
                    popupmenu.show(e.getComponent(), e.getX(), e.getY());
×
164
                }
NEW
165
            }
×
166
        });
UNCOV
167
        JScrollPane scrollPane = new JScrollPane(resultTable);
×
168

169
        JPanel statInfoPanel = new JPanel();
×
170
        statInfoPanel.setLayout(new BoxLayout(statInfoPanel, BoxLayout.X_AXIS));
×
171
        statInfoLabel = new JLabel("");
×
172
        statInfoPanel.add(statInfoLabel);
×
173
        statInfoPanel.add(Box.createHorizontalGlue());
×
174

175
        resultPanel.add(scrollPane);
×
176
        resultPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
177
        resultPanel.add(statInfoPanel);
×
178
    }
×
179

180
    private void processFile(File file) {
181
        if (pageCountRadioButton.isSelected()) {
×
182
            Vector<Object> fileVector = getRowVector(file);
×
183
            resultFileList.add(fileVector);
×
184
        } else {
×
185
            logger.error("Invalid option selected");
×
186
        }
187
    }
×
188

189
    class OperationButtonActionListener implements ActionListener {
×
190
        @Override
191
        public void actionPerformed(ActionEvent e) {
192
            Object source = e.getSource();
×
193
            if (source.equals(statButton)) {
×
194
                statButton.setEnabled(false);
×
195
                cancelButton.setEnabled(true);
×
196
                searchThread = new SearchThread(isRecursiveSearched.isSelected());
×
197
                searchThread.start();
×
198
            } else if (source.equals(cancelButton)) {
×
199
                statButton.setEnabled(true);
×
200
                cancelButton.setEnabled(false);
×
201
                if (searchThread.isAlive()) {
×
202
                    searchThread.interrupt();
×
203
                    searchThread.executorService.shutdownNow();
×
204
                }
205
            }
206

207
        }
×
208
    }
209

210
    private void showResult() {
211
        SwingUtilities.invokeLater(() -> {
×
212
            int index = 0;
×
213
            for (Vector<Object> file : resultFileList) {
×
214
                file.add(0, ++index);
×
215
                resultTableModel.addRow(file);
×
216
            }
×
217
            tabbedPane.setSelectedIndex(1);
×
218
            statInfoLabel.setText("Page Count: " + totalPageCount + ", Total Size: " + FileUtils.sizeOfInHumanFormat(totalFileSize));
×
219
        });
×
220
    }
×
221

222
    private Vector<Object> getRowVector(File file) {
223
        Vector<Object> rowData = new Vector<>();
×
224
        rowData.add(file.getParent());
×
225
        rowData.add(file.getName());
×
226
        totalFileSize += file.length();
×
227
        rowData.add(FileUtils.sizeOfInHumanFormat(file));
×
228
        rowData.add(DateUtils.millisecondToHumanFormat(file.lastModified()));
×
229
        int pageCount = PdfUtils.getPageCount(file);
×
230
        totalPageCount += pageCount;
×
231
        rowData.add(pageCount);
×
232
        return rowData;
×
233
    }
234

235
    class SearchThread extends Thread {
236
        public final ExecutorService executorService;
237
        private final AtomicInteger processedFiles = new AtomicInteger(0);
×
238
        private int totalFiles = 0;
×
239
        private final boolean isRecursiveSearched;
240

241
        public SearchThread(boolean isRecursiveSearched) {
×
242
            super();
×
243
            this.isRecursiveSearched = isRecursiveSearched;
×
244
            this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
×
245

246
            resultFileList.clear();
×
247
            totalFileSize = 0;
×
248
            totalPageCount = 0;
×
249

250
            SwingUtilities.invokeLater(() -> {
×
251
                progressBar.setValue(0);
×
252
                progressBar.setString("Starting search...");
×
253
                resultTableModel.setRowCount(0);
×
254
                statInfoLabel.setText("");
×
255
            });
×
256
        }
×
257

258
        @Override
259
        public void run() {
260
            try {
261
                List<File> fileList = fileListPanel.getFileList();
×
262
                Set<File> fileSet = new TreeSet<>();
×
263
                String[] extensions = new String[]{"pdf", "PDF"};
×
264
                for (File file : fileList) {
×
265
                    fileSet.addAll(FileUtils.listFiles(file, extensions, isRecursiveSearched));
×
266
                }
×
267

268
                List<Future<?>> futures = new ArrayList<>();
×
269
                totalFiles = fileSet.size();
×
270
                updateProgress();
×
271

272
                for (File file : fileSet) {
×
273
                    if (currentThread().isInterrupted()) {
×
274
                        return;
×
275
                    }
276
                    futures.add(executorService.submit(() -> {
×
277
                        if (currentThread().isInterrupted()) {
×
278
                            return null;
×
279
                        }
280
                        processFile(file);
×
281
                        incrementProcessedFiles();
×
282
                        return null;
×
283
                    }));
284
                }
×
285

286
                // Wait for all tasks to complete
287
                for (Future<?> future : futures) {
×
288
                    try {
289
                        future.get();
×
290
                    } catch (InterruptedException e) {
×
291
                        logger.error("Search interrupted", e);
×
292
                        currentThread().interrupt(); // Restore interrupted status
×
293
                        return;
×
294
                    }
×
295
                }
×
296

297
                showResult();
×
298
            } catch (Exception e) {
×
299
                logger.error("Search failed", e);
×
300
                SwingUtilities.invokeLater(() -> progressBar.setString("Search failed"));
×
301
            } finally {
302
                executorService.shutdown();
×
303
                SwingUtilities.invokeLater(() -> {
×
304
                    statButton.setEnabled(true);
×
305
                    cancelButton.setEnabled(false);
×
306
                });
×
307
            }
308
        }
×
309

310
        private void incrementProcessedFiles() {
311
            processedFiles.incrementAndGet();
×
312
            updateProgress();
×
313
        }
×
314

315
        private void updateProgress() {
316
            if (totalFiles > 0) {
×
317
                SwingUtilities.invokeLater(() -> {
×
318
                    int processed = processedFiles.get();
×
319
                    int percentage = (int) (processed * 100.0 / totalFiles);
×
320
                    progressBar.setValue(percentage);
×
321
                    progressBar.setString(String.format("Processing: %d/%d files (%d%%)", processed, totalFiles, percentage));
×
322
                });
×
323
            }
324
        }
×
325
    }
326
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc