• 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/word/stat/WordStatPanel.java
1
package edu.jiangxin.apktoolbox.word.stat;
2

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

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

26
public class WordStatPanel extends EasyPanel {
×
27

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

31
    private JTabbedPane tabbedPane;
32

33
    private JPanel mainPanel;
34

35
    private FileListPanel fileListPanel;
36

37
    private JCheckBox isRecursiveSearched;
38

39
    private JPanel resultPanel;
40

41
    private JTable resultTable;
42

43
    private DefaultTableModel resultTableModel;
44

45
    private JLabel statInfoLabel;
46

47
    private JButton statButton;
48
    private JButton cancelButton;
49

50
    private JProgressBar progressBar;
51

52
    private transient SearchThread searchThread;
53

54
    private long totalFileSize = 0;
×
55

56
    private int totalPageCount = 0;
×
57

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

60
    @Override
61
    public void initUI() {
62
        tabbedPane = new JTabbedPane();
×
63
        add(tabbedPane);
×
64

65
        createMainPanel();
×
66
        tabbedPane.addTab("Option", null, mainPanel, "Show Stat Options");
×
67

68
        createResultPanel();
×
69
        tabbedPane.addTab("Result", null, resultPanel, "Show Stat Result");
×
70
    }
×
71

72
    private void createMainPanel() {
73
        mainPanel = new JPanel();
×
74
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
×
75

76
        fileListPanel = new FileListPanel();
×
77
        fileListPanel.initialize();
×
78

79
        JPanel searchOptionPanel = new JPanel();
×
80
        searchOptionPanel.setLayout(new BoxLayout(searchOptionPanel, BoxLayout.X_AXIS));
×
81
        searchOptionPanel.setBorder(BorderFactory.createTitledBorder("Stat Options"));
×
82

83
        isRecursiveSearched = new JCheckBox("Recursive");
×
84
        isRecursiveSearched.setSelected(true);
×
85
        searchOptionPanel.add(isRecursiveSearched);
×
86
        searchOptionPanel.add(Box.createHorizontalGlue());
×
87

88
        JPanel operationPanel = new JPanel();
×
89
        operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
×
90
        operationPanel.setBorder(BorderFactory.createTitledBorder("Operations"));
×
91

92
        statButton = new JButton("Stat");
×
93
        cancelButton = new JButton("Cancel");
×
94
        cancelButton.setEnabled(false);
×
95
        statButton.addActionListener(new OperationButtonActionListener());
×
96
        cancelButton.addActionListener(new OperationButtonActionListener());
×
97
        operationPanel.add(statButton);
×
98
        operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
99
        operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
100
        operationPanel.add(cancelButton);
×
101
        operationPanel.add(Box.createHorizontalGlue());
×
102

103
        progressBar = new JProgressBar();
×
104
        progressBar.setStringPainted(true);
×
105
        progressBar.setString("Ready");
×
106

107
        mainPanel.add(fileListPanel);
×
108
        mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
109
        mainPanel.add(searchOptionPanel);
×
110
        mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
111
        mainPanel.add(operationPanel);
×
112
        mainPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
113
        mainPanel.add(progressBar);
×
114
    }
×
115

116
    private void createResultPanel() {
117
        resultPanel = new JPanel();
×
118
        resultPanel.setLayout(new BoxLayout(resultPanel, BoxLayout.Y_AXIS));
×
119

120
        resultTableModel = new WordFilesTableModel(new Vector<>(), WordFilesConstants.COLUMN_NAMES);
×
121
        resultTable = new JTable(resultTableModel);
×
122
        resultTable.setDefaultRenderer(Vector.class, new WordFilesTableCellRenderer());
×
123
        for (int i = 0; i < resultTable.getColumnCount(); i++) {
×
124
            resultTable.getColumn(resultTable.getColumnName(i)).setCellRenderer(new WordFilesTableCellRenderer());
×
125
        }
126
        resultTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
×
NEW
127
        resultTable.addMouseListener(new MouseAdapter() {
×
128
            @Override
129
            public void mouseReleased(MouseEvent e) {
NEW
130
                if (e.isPopupTrigger() && e.getComponent() instanceof JTable) {
×
NEW
131
                    JPopupMenu popupmenu = new JPopupMenu();
×
NEW
132
                    JMenuItem exportMenuItem = new JMenuItem("导出到 Excel");
×
NEW
133
                    exportMenuItem.addActionListener(ev ->
×
NEW
134
                        ExcelExporter.export(resultTableModel, "word_stat_export.xlsx", WordStatPanel.this));
×
NEW
135
                    popupmenu.add(exportMenuItem);
×
NEW
136
                    popupmenu.show(e.getComponent(), e.getX(), e.getY());
×
137
                }
NEW
138
            }
×
139
        });
UNCOV
140
        JScrollPane scrollPane = new JScrollPane(resultTable);
×
141

142
        JPanel statInfoPanel = new JPanel();
×
143
        statInfoPanel.setLayout(new BoxLayout(statInfoPanel, BoxLayout.X_AXIS));
×
144
        statInfoLabel = new JLabel("");
×
145
        statInfoPanel.add(statInfoLabel);
×
146
        statInfoPanel.add(Box.createHorizontalGlue());
×
147

148
        resultPanel.add(scrollPane);
×
149
        resultPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
150
        resultPanel.add(statInfoPanel);
×
151
    }
×
152

153
    private void processFile(File file) {
154
        Vector<Object> fileVector = getRowVector(file);
×
155
        resultFileList.add(fileVector);
×
156
    }
×
157

158
    class OperationButtonActionListener implements ActionListener {
×
159
        @Override
160
        public void actionPerformed(ActionEvent e) {
161
            Object source = e.getSource();
×
162
            if (source.equals(statButton)) {
×
163
                if (fileListPanel.getFileList().isEmpty()) {
×
164
                    return;
×
165
                }
166
                statButton.setEnabled(false);
×
167
                cancelButton.setEnabled(true);
×
168
                searchThread = new SearchThread(isRecursiveSearched.isSelected());
×
169
                searchThread.start();
×
170
            } else if (source.equals(cancelButton)) {
×
171
                statButton.setEnabled(true);
×
172
                cancelButton.setEnabled(false);
×
173
                if (searchThread.isAlive()) {
×
174
                    searchThread.interrupt();
×
175
                    searchThread.executorService.shutdownNow();
×
176
                }
177
            }
178
        }
×
179
    }
180

181
    private void showResult() {
182
        SwingUtilities.invokeLater(() -> {
×
183
            int index = 0;
×
184
            for (Vector<Object> file : resultFileList) {
×
185
                file.add(0, ++index);
×
186
                resultTableModel.addRow(file);
×
187
            }
×
188
            tabbedPane.setSelectedIndex(1);
×
189
            statInfoLabel.setText("Page Count: " + totalPageCount + ", Total Size: " + FileUtils.sizeOfInHumanFormat(totalFileSize));
×
190
        });
×
191
    }
×
192

193
    private Vector<Object> getRowVector(File file) {
194
        Vector<Object> rowData = new Vector<>();
×
195
        rowData.add(file.getParent());
×
196
        rowData.add(file.getName());
×
197
        totalFileSize += file.length();
×
198
        rowData.add(FileUtils.sizeOfInHumanFormat(file));
×
199
        rowData.add(DateUtils.millisecondToHumanFormat(file.lastModified()));
×
200
        int pageCount = WordUtils.getPageCount(file);
×
201
        totalPageCount += pageCount;
×
202
        rowData.add(pageCount);
×
203
        return rowData;
×
204
    }
205

206
    class SearchThread extends Thread {
207
        public final ExecutorService executorService;
208
        private final AtomicInteger processedFiles = new AtomicInteger(0);
×
209
        private int totalFiles = 0;
×
210
        private final boolean isRecursiveSearched;
211

212
        public SearchThread(boolean isRecursiveSearched) {
×
213
            super();
×
214
            this.isRecursiveSearched = isRecursiveSearched;
×
215
            this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
×
216

217
            resultFileList.clear();
×
218
            totalFileSize = 0;
×
219
            totalPageCount = 0;
×
220

221
            SwingUtilities.invokeLater(() -> {
×
222
                progressBar.setValue(0);
×
223
                progressBar.setString("Starting search...");
×
224
                resultTableModel.setRowCount(0);
×
225
                statInfoLabel.setText("");
×
226
            });
×
227
        }
×
228

229
        @Override
230
        public void run() {
231
            try {
232
                List<File> fileList = fileListPanel.getFileList();
×
233
                Set<File> fileSet = new TreeSet<>();
×
234
                String[] extensions = new String[]{"doc", "DOC", "docx", "DOCX"};
×
235
                for (File file : fileList) {
×
236
                    fileSet.addAll(FileUtils.listFiles(file, extensions, isRecursiveSearched));
×
237
                }
×
238

239
                List<Future<?>> futures = new ArrayList<>();
×
240
                totalFiles = fileSet.size();
×
241
                updateProgress();
×
242

243
                for (File file : fileSet) {
×
244
                    if (currentThread().isInterrupted()) {
×
245
                        return;
×
246
                    }
247
                    futures.add(executorService.submit(() -> {
×
248
                        if (currentThread().isInterrupted()) {
×
249
                            return null;
×
250
                        }
251
                        processFile(file);
×
252
                        incrementProcessedFiles();
×
253
                        return null;
×
254
                    }));
255
                }
×
256

257
                // Wait for all tasks to complete
258
                for (Future<?> future : futures) {
×
259
                    try {
260
                        future.get();
×
261
                    } catch (InterruptedException e) {
×
262
                        logger.error("Search interrupted", e);
×
263
                        currentThread().interrupt();
×
264
                        return;
×
265
                    }
×
266
                }
×
267

268
                showResult();
×
269
            } catch (Exception e) {
×
270
                logger.error("Search failed", e);
×
271
                SwingUtilities.invokeLater(() -> progressBar.setString("Search failed"));
×
272
            } finally {
273
                executorService.shutdown();
×
274
                SwingUtilities.invokeLater(() -> {
×
275
                    statButton.setEnabled(true);
×
276
                    cancelButton.setEnabled(false);
×
277
                });
×
278
            }
279
        }
×
280

281
        private void incrementProcessedFiles() {
282
            processedFiles.incrementAndGet();
×
283
            updateProgress();
×
284
        }
×
285

286
        private void updateProgress() {
287
            if (totalFiles > 0) {
×
288
                SwingUtilities.invokeLater(() -> {
×
289
                    int processed = processedFiles.get();
×
290
                    int percentage = (int) (processed * 100.0 / totalFiles);
×
291
                    progressBar.setValue(percentage);
×
292
                    progressBar.setString(String.format("Processing: %d/%d files (%d%%)", processed, totalFiles, percentage));
×
293
                });
×
294
            }
295
        }
×
296
    }
297
}
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