/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2000-2006 Keith Godfrey, Maxym Mykhalchuk, and Kim Bruning 2007 Zoltan Bartko 2008 Alex Buloichik, Didier Briel 2012 Martin Fleurke 2014 Alex Buloichik Piotr Kulik 2015 Aaron Madlon-Kay Home page: http://www.omegat.org/ Support center: http://groups.yahoo.com/group/OmegaT/ This file is part of OmegaT. OmegaT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmegaT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.omegat.gui.filelist; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Desktop; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import javax.swing.AbstractAction; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.RowSorter; import javax.swing.SortOrder; import javax.swing.SwingUtilities; import javax.swing.border.MatteBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.MenuKeyEvent; import javax.swing.event.MenuKeyListener; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.text.BadLocationException; import org.omegat.core.Core; import org.omegat.core.CoreEvents; import org.omegat.core.data.IProject; import org.omegat.core.data.IProject.FileInfo; import org.omegat.core.data.SourceTextEntry; import org.omegat.core.events.IEntryEventListener; import org.omegat.core.statistics.StatisticsInfo; import org.omegat.gui.main.MainWindow; import org.omegat.gui.main.ProjectUICommands; import org.omegat.util.Log; import org.omegat.util.OConsts; import org.omegat.util.OStrings; import org.omegat.util.Platform; import org.omegat.util.Preferences; import org.omegat.util.StreamUtil; import org.omegat.util.StringUtil; import org.omegat.util.gui.DataTableStyling; import org.omegat.util.gui.DragTargetOverlay; import org.omegat.util.gui.DragTargetOverlay.FileDropInfo; import org.omegat.util.gui.OSXIntegration; import org.omegat.util.gui.StaticUIUtils; import org.omegat.util.gui.TableColumnSizer; import org.omegat.util.gui.UIThreadsUtil; /** * Controller for showing all the files of the project. * * @author Keith Godfrey * @author Kim Bruning * @author Maxym Mykhalchuk * @author Henry Pijffers (henry.pijffers@saxnot.com) * @author Zoltan Bartko * @author Alex Buloichik (alex73mail@gmail.com) * @author Didier Briel * @author Martin Fleurke * @author Piotr Kulik * @author Aaron Madlon-Kay */ @SuppressWarnings("serial") public class ProjectFilesListController { private ProjectFilesList list; private FileInfoModel modelFiles; private AbstractTableModel modelTotal; private Sorter currentSorter; private TableFilterPanel filterPanel; private Font defaultFont; public ProjectFilesListController(MainWindow parent) { list = new ProjectFilesList(); if (Platform.isMacOSX()) { OSXIntegration.enableFullScreen(list); } createTableFiles(); createTableTotal(); TableColumnSizer colSizer = TableColumnSizer.autoSize(list.tableFiles, 0, true); colSizer.addColumnAdjustmentListener(e -> propagateTableColumns()); DragTargetOverlay.apply(list.tableFiles, new FileDropInfo(true) { @Override public String getImportDestination() { return Core.getProject().getProjectProperties().getSourceRoot(); } @Override public boolean canAcceptDrop() { return Core.getProject().isProjectLoaded(); } @Override public String getOverlayMessage() { return OStrings.getString("DND_ADD_SOURCE_FILE"); } @Override public boolean acceptFile(File path) { return true; } @Override public Component getComponentToOverlay() { return list.tablesInnerPanel; } }); defaultFont = list.tableFiles.getFont(); if (Preferences.isPreference(Preferences.PROJECT_FILES_USE_FONT)) { String fontName = Preferences.getPreference(Preferences.TF_SRC_FONT_NAME); int fontSize = Integer.parseInt(Preferences.getPreference(Preferences.TF_SRC_FONT_SIZE)); setFont(new Font(fontName, Font.PLAIN, fontSize)); } else { setFont(defaultFont); } list.tablesInnerPanel.setBorder(new JScrollPane().getBorder()); // set the position and size initWindowLayout(); list.m_addNewFileButton.addActionListener(e -> doImportSourceFiles()); list.m_wikiImportButton.addActionListener(e -> doWikiImport()); list.m_closeButton.addActionListener(e -> doCancel()); list.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { doCancel(); } @Override public void windowActivated(WindowEvent e) { propagateTableColumns(); } }); StaticUIUtils.setEscapeAction(list, new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { doCancel(); } }); CoreEvents.registerProjectChangeListener(eventType -> { switch (eventType) { case CLOSE: list.tableFiles.setModel(new DefaultTableModel()); list.tableFiles.repaint(); modelTotal.fireTableDataChanged(); list.setVisible(false); break; case LOAD: case CREATE: buildDisplay(Core.getProject().getProjectFiles()); if (!Preferences.isPreferenceDefault(Preferences.PROJECT_FILES_SHOW_ON_LOAD, true)) { break; } list.setVisible(true); SwingUtilities.invokeLater(() -> { list.toFront(); list.tableFiles.requestFocus(); }); break; default: // Nothing } }); CoreEvents.registerEntryEventListener(new IEntryEventListener() { @Override public void onNewFile(String activeFileName) { list.tableFiles.repaint(); list.tableTotal.repaint(); modelTotal.fireTableDataChanged(); } /** * Updates the number of translated segments only, does not rebuild the whole display. */ @Override public void onEntryActivated(SourceTextEntry newEntry) { UIThreadsUtil.mustBeSwingThread(); modelTotal.fireTableDataChanged(); } }); CoreEvents.registerFontChangedEventListener(newFont -> { if (!Preferences.isPreference(Preferences.PROJECT_FILES_USE_FONT)) { newFont = defaultFont; } setFont(newFont); }); list.tableFiles.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1 && e.getModifiersEx() == 0) { gotoFile(list.tableFiles.rowAtPoint(e.getPoint())); } } @Override public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) { doPopup(e.getPoint()); } } @Override public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { doPopup(e.getPoint()); } } private void doPopup(Point p) { int row = list.tableFiles.rowAtPoint(p); if (row != -1) { JPopupMenu popup = createContextMenuForRow(row); if (popup != null) { popup.show(list.tableFiles, p.x, p.y); } } } }); list.tableFiles.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { gotoFile(list.tableFiles.getSelectedRow()); e.consume(); } else if (isFiltering() && e.getKeyCode() == KeyEvent.VK_ESCAPE) { endFilter(); e.consume(); } else if (e.getKeyCode() == KeyEvent.VK_CONTEXT_MENU) { int row = list.tableFiles.getSelectedRow(); Point p = list.tableFiles.getCellRect(row, 0, false).getLocation(); JPopupMenu popup = createContextMenuForRow(row); if (popup != null) { popup.show(list.tableFiles, p.x, p.y); } e.consume(); } } }); list.tableFiles.getSelectionModel().addListSelectionListener(e -> updateButtonState()); list.tableFiles.addKeyListener(filterTrigger); list.tableTotal.addKeyListener(filterTrigger); list.btnUp.addKeyListener(filterTrigger); list.btnDown.addKeyListener(filterTrigger); list.btnFirst.addKeyListener(filterTrigger); list.btnLast.addKeyListener(filterTrigger); list.btnUp.addActionListener(moveAction); list.btnDown.addActionListener(moveAction); list.btnFirst.addActionListener(moveAction); list.btnLast.addActionListener(moveAction); } private void updateTitle() { int numFiles = currentSorter.getModelRowCount(); if (isFiltering()) { int showingFiles = currentSorter.getViewRowCount(); list.setTitle(StringUtil.format(OStrings.getString("PF_WINDOW_TITLE_FILTERED"), showingFiles, numFiles)); } else { list.setTitle(StringUtil.format(OStrings.getString("PF_WINDOW_TITLE"), numFiles)); } } private void updateButtonState() { boolean enabled = list.tableFiles.getSelectedRow() != -1; list.btnDown.setEnabled(enabled); list.btnFirst.setEnabled(enabled); list.btnLast.setEnabled(enabled); list.btnUp.setEnabled(enabled); } private JPopupMenu createContextMenuForRow(int row) { int[] rows; if (IntStream.of(list.tableFiles.getSelectedRows()).anyMatch(r -> r == row)) { // If clicked on selection, use selection rows = list.tableFiles.getSelectedRows(); } else { // Otherwise use the clicked row rows = new int[] { row }; } List<FileInfo> infos = IntStream.of(rows).map(list.tableFiles.getRowSorter()::convertRowIndexToModel) .mapToObj(modelFiles::getDataAtRow) .collect(Collectors.toList()); if (infos.isEmpty() || infos.stream().anyMatch(Objects::isNull)) { return null; } String sourceDir = Core.getProject().getProjectProperties().getSourceRoot(); String targetDir = Core.getProject().getProjectProperties().getTargetRoot(); JPopupMenu menu = new JPopupMenu(); addContextMenuItem(menu, true, infos.stream().map(i -> new File(sourceDir, i.filePath)).collect(Collectors.toList())); addContextMenuItem(menu, false, infos.stream().map(i -> new File(targetDir, Core.getProject().getTargetPathForSourceFile(i.filePath))) .collect(Collectors.toList())); return menu; } private void addContextMenuItem(JPopupMenu menu, boolean isSource, List<File> files) { long presentFiles = files.stream().filter(File::isFile).count(); String defaultTitle, modTitle; if (presentFiles > 1) { defaultTitle = StringUtil.format( OStrings.getString(isSource ? "PF_OPEN_SOURCE_FILES" : "PF_OPEN_TARGET_FILES"), presentFiles); modTitle = StringUtil.format(OStrings.getString(isSource ? "PF_OPEN_SOURCE_FILES" : "PF_OPEN_TARGET_FILES"), presentFiles); } else { defaultTitle = OStrings.getString(isSource ? "PF_OPEN_SOURCE_FILE" : "PF_OPEN_TARGET_FILE"); modTitle = OStrings.getString(isSource ? "PF_REVEAL_SOURCE_FILE" : "PF_REVEAL_TARGET_FILE"); } JMenuItem item = menu.add(defaultTitle); item.addActionListener(e -> { boolean openParent = (e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0; Stream<File> stream; if (openParent) { stream = files.stream().map(File::getParentFile).distinct().filter(File::isDirectory); } else { stream = files.stream().filter(File::isFile); } stream.forEach(f -> { try { Desktop.getDesktop().open(f); } catch (IOException ex) { Log.log(ex); } }); }); item.setEnabled(presentFiles > 0); item.addMenuKeyListener(new MenuKeyListener() { @Override public void menuKeyTyped(MenuKeyEvent e) { } @Override public void menuKeyReleased(MenuKeyEvent e) { if ((e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0 || e.getKeyCode() == KeyEvent.VK_META || e.getKeyCode() == KeyEvent.VK_CONTROL) { setText(defaultTitle); } } @Override public void menuKeyPressed(MenuKeyEvent e) { if ((e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0) { setText(modTitle); } } private void setText(String text) { item.setText(text); menu.pack(); } }); } private final KeyListener filterTrigger = new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { char c = e.getKeyChar(); if ((e.getModifiers() == 0 || e.getModifiers() == KeyEvent.SHIFT_MASK) && !Character.isWhitespace(c) && !Character.isISOControl(c)) { if (isFiltering()) { resumeFilter(e.getKeyChar()); } else { startFilter(e.getKeyChar()); } e.consume(); } } }; private void startFilter(char c) { if (isFiltering()) { throw new IllegalStateException("Already filtering!"); } filterPanel = new TableFilterPanel(); list.btnDown.setEnabled(false); list.btnUp.setEnabled(false); list.btnFirst.setEnabled(false); list.btnLast.setEnabled(false); list.tablesOuterPanel.add(filterPanel, BorderLayout.SOUTH); filterPanel.filterTextField.addActionListener(e -> gotoFile(list.tableFiles.getSelectedRow())); filterPanel.filterTextField.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { endFilter(); e.consume(); } else if (e.getKeyCode() == KeyEvent.VK_UP) { int selection = Math.max(0, list.tableFiles.getSelectedRow()); int total = list.tableFiles.getRowCount(); int up = (selection - 1 + total) % total; selectRow(up); e.consume(); } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { int selection = list.tableFiles.getSelectedRow(); int down = (selection + 1) % list.tableFiles.getRowCount(); selectRow(down); e.consume(); } } }); filterPanel.filterTextField.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { applyFilter(); } @Override public void removeUpdate(DocumentEvent e) { applyFilter(); } @Override public void changedUpdate(DocumentEvent e) { applyFilter(); } }); filterPanel.filterTextField.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { filterPanel.filterTextField.setCaretPosition(filterPanel.filterTextField.getText().length()); } }); filterPanel.filterCloseButton.addActionListener(e -> endFilter()); filterPanel.filterTextField.setText(Character.toString(c)); filterPanel.filterTextField.requestFocus(); list.validate(); list.repaint(); } private boolean isFiltering() { return filterPanel != null; } private void resumeFilter(char c) { if (!isFiltering()) { throw new IllegalStateException("Can't resume filtering when we're not filtering!"); } try { filterPanel.filterTextField.getDocument().insertString(filterPanel.filterTextField.getText().length(), Character.toString(c), null); filterPanel.filterTextField.requestFocus(); } catch (BadLocationException ex) { // Nothing } } private void applyFilter() { if (!isFiltering()) { throw new IllegalStateException("Can't apply filter when we're not filtering!"); } String quoted = Pattern.quote(filterPanel.filterTextField.getText()); Pattern findPattern = Pattern.compile(quoted, Pattern.CASE_INSENSITIVE); FilesTableColumn.FILE_NAME.setHighlightPattern(findPattern); Pattern matchPattern = Pattern.compile(".*" + quoted + ".*", Pattern.CASE_INSENSITIVE); currentSorter.setFilter(matchPattern); selectRow(0); } private void endFilter() { if (!isFiltering()) { throw new IllegalStateException("Can't end filtering when we're not filtering!"); } FilesTableColumn.FILE_NAME.setHighlightPattern(null); list.tablesOuterPanel.remove(filterPanel); list.btnDown.setEnabled(true); list.btnUp.setEnabled(true); list.btnFirst.setEnabled(true); list.btnLast.setEnabled(true); filterPanel = null; currentSorter.setFilter(null); list.tableFiles.requestFocus(); int currentRow = list.tableFiles.getSelectedRow(); list.tableFiles.scrollRectToVisible(list.tableFiles.getCellRect(currentRow, 0, true)); list.validate(); list.repaint(); } ActionListener moveAction = e -> { int[] selected = list.tableFiles.getSelectedRows(); if (selected.length == 0) { return; } int pos = selected[0]; int newPos; if (e.getSource() == list.btnUp) { newPos = pos - 1; } else if (e.getSource() == list.btnDown) { newPos = pos + 1; } else if (e.getSource() == list.btnFirst) { newPos = 0; } else if (e.getSource() == list.btnLast) { newPos = Integer.MAX_VALUE; } else { return; } pos = currentSorter.moveTo(selected, newPos); list.tableFiles.getSelectionModel().setSelectionInterval(pos, pos + selected.length - 1); }; public boolean isActive() { return list.isActive(); } public void setActive(boolean active) { if (active) { // moved current file selection here so it will be properly set on each activation list.setVisible(true); list.toFront(); SwingUtilities.invokeLater(() -> selectCurrentFile(Core.getProject().getProjectFiles())); } else { list.setVisible(false); } } /** * Selects current file on project files table */ private void selectCurrentFile(List<IProject.FileInfo> files) { // clear selection from possible previous multiple selections list.tableFiles.getSelectionModel().clearSelection(); String currentFile = Core.getEditor().getCurrentFile(); // set current file as default selection for (int i = 0; i < files.size(); i++) { if (files.get(i).filePath.equals(currentFile)) { int pos = list.tableFiles.convertRowIndexToView(i); selectRow(pos); break; } } list.tableFiles.requestFocus(); } /** * Select a row in tableFiles and make sure it's visible */ private void selectRow(int row) { list.tableFiles.getSelectionModel().setSelectionInterval(row, row); list.tableFiles.scrollRectToVisible(list.tableFiles.getCellRect(row, 0, true)); } /** * Loads/sets the position and size of the project files window. */ private void initWindowLayout() { // set default size and position Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); list.setBounds((screenSize.width - 640) / 2, (screenSize.height - 400) / 2, 640, 400); StaticUIUtils.persistGeometry(list, Preferences.PROJECT_FILES_WINDOW_GEOMETRY_PREFIX); } private void doCancel() { list.setVisible(false); } /** * Builds the table which lists all the project files. */ private void buildDisplay(List<IProject.FileInfo> files) { UIThreadsUtil.mustBeSwingThread(); String path; String statFileName = Core.getProject().getProjectProperties().getProjectInternal() + OConsts.STATS_FILENAME; File statFile = new File(statFileName); try { path = statFile.getCanonicalPath(); } catch (IOException ex) { path = statFile.getAbsolutePath(); } String statText = MessageFormat.format(OStrings.getString("PF_STAT_PATH"), path); list.statLabel.setText(statText); uiUpdateImportButtonStatus(); OSXIntegration.setProxyIcon(list.getRootPane(), new File(Core.getProject().getProjectProperties().getSourceRoot())); setTableFilesModel(files); updateTitle(); } private void createTableFiles() { DataTableStyling.applyColors(list.tableFiles); list.tableFiles.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); } private void propagateTableColumns() { // Set last column of tableTotal to match size of scrollbar. JScrollBar scrollbar = list.scrollFiles.getVerticalScrollBar(); int sbWidth = scrollbar == null || !scrollbar.isVisible() ? 0 : scrollbar.getWidth(); list.tableTotal.getColumnModel().getColumn(TotalsTableColumn.MARGIN.index).setPreferredWidth(sbWidth); // Propagate column sizes to totals table for (int i = 0; i < list.tableFiles.getColumnCount(); i++) { TableColumn srcCol = list.tableFiles.getColumnModel().getColumn(i); TableColumn trgCol = list.tableTotal.getColumnModel().getColumn(i); trgCol.setPreferredWidth(srcCol.getWidth()); } } enum FilesTableColumn { FILE_NAME(0, OStrings.getString("PF_FILENAME"), String.class, new DataTableStyling.PatternHighlightRenderer(false)), FILTER(1, OStrings.getString("PF_FILTERNAME"), String.class, DataTableStyling.getTextCellRenderer()), ENCODING(2, OStrings.getString("PF_ENCODING"), String.class, DataTableStyling.getTextCellRenderer()), SEGMENTS(3, OStrings.getString("PF_NUM_SEGMENTS"), Integer.class, DataTableStyling.getNumberCellRenderer()), UNIQUE_SEGMENTS(4, OStrings.getString("PF_NUM_UNIQUE_SEGMENTS"), Integer.class, DataTableStyling.getNumberCellRenderer()); private final int index; private final String label; private final Class<?> clazz; private final TableCellRenderer renderer; private FilesTableColumn(int index, String label, Class<?> clazz, TableCellRenderer renderer) { this.index = index; this.label = label; this.clazz = clazz; this.renderer = renderer; } static FilesTableColumn get(int index) { return values()[index]; } private void setHighlightPattern(Pattern pattern) { if (renderer instanceof DataTableStyling.PatternHighlightRenderer) { ((DataTableStyling.PatternHighlightRenderer) renderer).setPattern(pattern); } else { throw new UnsupportedOperationException("Column " + label + " doesn't support pattern highlights"); } } } private void setTableFilesModel(final List<IProject.FileInfo> files) { modelFiles = new FileInfoModel(files); list.tableFiles.setModel(modelFiles); TableColumnModel colModel = list.tableFiles.getColumnModel(); colModel.addColumnModelListener(new TableColumnModelListener() { @Override public void columnAdded(TableColumnModelEvent e) { } @Override public void columnMarginChanged(ChangeEvent e) { } @Override public void columnMoved(TableColumnModelEvent e) { // Propagate movement to tableTotal list.tableTotal.getColumnModel().moveColumn(e.getFromIndex(), e.getToIndex()); } @Override public void columnRemoved(TableColumnModelEvent e) { } @Override public void columnSelectionChanged(ListSelectionEvent e) { } }); for (FilesTableColumn col : FilesTableColumn.values()) { TableColumn tCol = colModel.getColumn(col.index); tCol.setCellRenderer(new CustomRenderer(files, col.renderer)); } currentSorter = new Sorter(files); currentSorter.addRowSorterListener(e -> updateTitle()); list.tableFiles.setRowSorter(currentSorter); } enum TotalsTableColumn { LABEL(0, String.class, DataTableStyling.getTextCellRenderer()) { @Override protected Object getValue(int row) { switch (row) { case 0: return OStrings.getString("GUI_PROJECT_TOTAL_SEGMENTS"); case 1: return OStrings.getString("GUI_PROJECT_UNIQUE_SEGMENTS"); case 2: return OStrings.getString("GUI_PROJECT_TRANSLATED"); default: throw new IllegalArgumentException(); } } }, EMPTY_1(1, String.class, DataTableStyling.getTextCellRenderer()), EMPTY_2(2, String.class, DataTableStyling.getTextCellRenderer()), EMPTY_3(3, Integer.class, DataTableStyling.getNumberCellRenderer()), VALUE(4, Integer.class, DataTableStyling.getNumberCellRenderer()) { @Override protected Object getValue(int row) { if (!Core.getProject().isProjectLoaded()) { return "-"; } StatisticsInfo stat = Core.getProject().getStatistics(); switch (row) { case 0: return stat.numberOfSegmentsTotal; case 1: return stat.numberOfUniqueSegments; case 2: return stat.numberofTranslatedSegments; default: throw new IllegalArgumentException(); } } }, MARGIN(5, String.class, new DataTableStyling.AlternatingHighlightRenderer().setDoHighlight(false)); private final int index; private final Class<?> clazz; private final TableCellRenderer renderer; private TotalsTableColumn(int index, Class<?> clazz, TableCellRenderer renderer) { this.index = index; this.clazz = clazz; this.renderer = renderer; } protected Object getValue(int row) { return ""; } static TotalsTableColumn get(int index) { return values()[index]; } } private void createTableTotal() { DataTableStyling.applyColors(list.tableTotal); list.tableTotal.setBorder(new MatteBorder(1, 0, 0, 0, DataTableStyling.COLOR_ALTERNATING_HILITE)); modelTotal = new AbstractTableModel() { @Override public Object getValueAt(int rowIndex, int columnIndex) { return TotalsTableColumn.get(columnIndex).getValue(rowIndex); } @Override public int getColumnCount() { return TotalsTableColumn.values().length; } @Override public Class<?> getColumnClass(int columnIndex) { return TotalsTableColumn.get(columnIndex).clazz; } @Override public int getRowCount() { return 3; } }; list.tableTotal.setModel(modelTotal); TableColumnModel colModel = list.tableTotal.getColumnModel(); for (TotalsTableColumn col : TotalsTableColumn.values()) { TableColumn tCol = colModel.getColumn(col.index); tCol.setCellRenderer(new CustomRenderer(null, col.renderer)); tCol.setMinWidth(0); } } /** * Imports the file/files/folder into project's source files. */ private void doImportSourceFiles() { ProjectUICommands.doPromptImportSourceFiles(); } private void doWikiImport() { ProjectUICommands.doWikiImport(); } /** Updates the Import Files button status. */ private void uiUpdateImportButtonStatus() { list.m_addNewFileButton.setEnabled(Core.getProject().isProjectLoaded()); list.m_wikiImportButton.setEnabled(Core.getProject().isProjectLoaded()); } private void gotoFile(int row) { if (!Core.getProject().isProjectLoaded()) { return; } if (row < 0) { return; } Cursor hourglassCursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR); Cursor oldCursor = list.getCursor(); list.setCursor(hourglassCursor); try { int modelRow = list.tableFiles.convertRowIndexToModel(row); Core.getEditor().gotoFile(modelRow); Core.getEditor().requestFocus(); } catch (IndexOutOfBoundsException ex) { // Data changed. } finally { list.setCursor(oldCursor); } } private static final Color COLOR_SPECIAL_FG = Color.BLACK; private static final Color COLOR_SPECIAL_BG = new Color(0xC8DDF2); /** * Render for table cells. */ private class CustomRenderer implements TableCellRenderer { private final List<IProject.FileInfo> files; private final TableCellRenderer childRenderer; public CustomRenderer(List<IProject.FileInfo> files, TableCellRenderer childRenderer) { this.files = files; this.childRenderer = childRenderer; } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = childRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (!isSelected && isSpecialHighlightRow(row)) { c.setForeground(COLOR_SPECIAL_FG); c.setBackground(COLOR_SPECIAL_BG); } return c; } private boolean isSpecialHighlightRow(int row) { if (files == null) { return false; } try { int modelRow = list.tableFiles.convertRowIndexToModel(row); IProject.FileInfo fi = files.get(modelRow); return fi.filePath.equals(Core.getEditor().getCurrentFile()); } catch (IndexOutOfBoundsException ex) { // data changed return false; } } } private void setFont(Font font) { DataTableStyling.applyFont(list.tableFiles, font); DataTableStyling.applyFont(list.tableTotal, font.deriveFont(Font.BOLD)); list.statLabel.setFont(font); } static class FileInfoModel extends AbstractTableModel { private final List<IProject.FileInfo> files; public FileInfoModel(List<IProject.FileInfo> files) { this.files = files; } @Override public Object getValueAt(int rowIndex, int columnIndex) { IProject.FileInfo fi; try { fi = files.get(rowIndex); } catch (IndexOutOfBoundsException ex) { // data changed return null; } switch (FilesTableColumn.get(columnIndex)) { case FILE_NAME: return fi.filePath; case FILTER: return fi.filterFileFormatName; case ENCODING: return fi.fileEncoding; case SEGMENTS: return fi.entries.size(); case UNIQUE_SEGMENTS: StatisticsInfo stat = Core.getProject().getStatistics(); return stat.uniqueCountsByFile.get(fi.filePath); default: throw new IllegalArgumentException(); } } @Override public int getColumnCount() { return FilesTableColumn.values().length; } @Override public int getRowCount() { return files.size(); } @Override public Class<?> getColumnClass(int columnIndex) { return FilesTableColumn.get(columnIndex).clazz; } @Override public String getColumnName(int column) { return FilesTableColumn.get(column).label; } public FileInfo getDataAtRow(int row) { return (row >= 0 && row < files.size()) ? files.get(row) : null; } } class Sorter extends RowSorter<FileInfoModel> { private final List<IProject.FileInfo> files; private SortKey sortKey = new SortKey(0, SortOrder.UNSORTED); private Integer[] modelToView; private List<Integer> viewToModel; private Pattern filter; public Sorter(final List<IProject.FileInfo> files) { this.files = files; init(); applyPrefs(); } private void init() { if (modelToView == null || modelToView.length != files.size()) { modelToView = new Integer[files.size()]; } int excluded = 0; for (int i = 0; i < modelToView.length; i++) { if (include(files.get(i))) { modelToView[i] = i - excluded; } else { modelToView[i] = -1; excluded++; } } viewToModel = new ArrayList<Integer>(modelToView.length - excluded); for (int i = 0, j = 0; i < modelToView.length; i++) { if (modelToView[i] != -1) { viewToModel.add(j++, i); } } } private void applyPrefs() { List<String> filenames = files.stream().map(fi -> fi.filePath) .sorted(StreamUtil.comparatorByList(Core.getProject().getSourceFilesOrder())) .collect(Collectors.toList()); Collections.sort(viewToModel, (o1, o2) -> { int pos1 = filenames.indexOf(files.get(o1).filePath); int pos2 = filenames.indexOf(files.get(o2).filePath); if (pos1 < pos2) { return -1; } else if (pos1 > pos2) { return 1; } else { return 0; } }); recalc(); } @Override public int getModelRowCount() { return files.size(); } @Override public int getViewRowCount() { return viewToModel.size(); } @Override public FileInfoModel getModel() { throw new RuntimeException("Not implemented"); } @Override public void modelStructureChanged() { } @Override public void allRowsChanged() { throw new RuntimeException("Not implemented"); } @Override public void rowsInserted(int firstRow, int endRow) { throw new RuntimeException("Not implemented"); } @Override public void rowsUpdated(int firstRow, int endRow) { throw new RuntimeException("Not implemented"); } @Override public void rowsUpdated(int firstRow, int endRow, int column) { } @Override public void rowsDeleted(int firstRow, int endRow) { throw new RuntimeException("Not implemented"); } @Override public List<? extends SortKey> getSortKeys() { return Arrays.asList(sortKey); } @Override public void setSortKeys(List<? extends javax.swing.RowSorter.SortKey> keys) { throw new RuntimeException("Not implemented"); } @Override public void toggleSortOrder(int column) { SortOrder order = SortOrder.ASCENDING; if (sortKey.getSortOrder() == SortOrder.ASCENDING) { order = SortOrder.DESCENDING; } sortKey = new SortKey(column, order); sort(); save(); } @Override public int convertRowIndexToModel(int index) { return viewToModel.get(index); } @Override public int convertRowIndexToView(int index) { return modelToView[index]; } void sort() { if (sortKey.getSortOrder() == SortOrder.UNSORTED) { applyPrefs(); return; } final StatisticsInfo stat = Core.getProject().getStatistics(); Collections.sort(viewToModel, (o1, o2) -> { IProject.FileInfo f1 = files.get(o1); IProject.FileInfo f2 = files.get(o2); int c = 0; switch (sortKey.getColumn()) { case 0: c = f1.filePath.compareToIgnoreCase(f2.filePath); break; case 1: c = f1.filterFileFormatName.compareToIgnoreCase(f2.filterFileFormatName); break; case 2: String fe1 = f1.fileEncoding == null ? "" : f1.fileEncoding; String fe2 = f2.fileEncoding == null ? "" : f2.fileEncoding; c = fe1.compareToIgnoreCase(fe2); break; case 3: int m1 = f1.entries.size(); int m2 = f2.entries.size(); c = m1 > m2 ? 1 : m1 < m2 ? -1 : 0; break; case 4: int n1 = stat.uniqueCountsByFile.get(f1.filePath); int n2 = stat.uniqueCountsByFile.get(f2.filePath); c = n1 > n2 ? 1 : n1 < n2 ? -1 : 0; break; } if (sortKey.getSortOrder() == SortOrder.DESCENDING) { c = -c; } return c; }); recalc(); } private void recalc() { for (int i = 0; i < viewToModel.size(); i++) { modelToView[viewToModel.get(i)] = i; } } public int moveTo(int[] selected, int newPos) { int[] temp = new int[selected.length]; int n = selected.length; for(int i = 0; i < selected.length; i++) { temp[i] = viewToModel.remove(selected[--n]); } newPos = Math.max(newPos, 0); newPos = Math.min(newPos, viewToModel.size()); for(int i = 0; i < temp.length; i++) { viewToModel.add(newPos, temp[i]); } recalc(); save(); list.tableFiles.scrollRectToVisible(list.tableFiles.getCellRect(newPos, 0, true) .union(list.tableFiles.getCellRect(newPos + temp.length, 0, true))); list.tableFiles.repaint(); sortKey = new SortKey(0, SortOrder.UNSORTED); list.tableFiles.getTableHeader().repaint(); return newPos; } private void save() { List<String> filenames = new ArrayList<String>(); for (Integer i : viewToModel) { String fn = files.get(i).filePath; filenames.add(fn); } Core.getProject().setSourceFilesOrder(filenames); } public void setFilter(Pattern pattern) { if (filter == pattern || pattern != null && pattern.equals(filter)) { return; } filter = pattern; int[] lastViewToModel = viewToModel.stream().mapToInt(Integer::intValue).toArray(); init(); sort(); fireRowSorterChanged(lastViewToModel); } private boolean include(IProject.FileInfo item) { if (filter == null) { return true; } return filter.matcher(item.filePath).matches(); } } }