re-engineer translation filters
Resolves #77 Resolves #101 Resolves #122 Resolves #127
This commit is contained in:
parent
8ce09a6a5b
commit
e8236b272b
10
CHANGELOG.md
10
CHANGELOG.md
@ -3,6 +3,16 @@
|
|||||||
# easy-i18n Changelog
|
# easy-i18n Changelog
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Duplicate translation values filter
|
||||||
|
- Multiple translation filters can be used together
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Reengineered how translation filters are applied internally
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Exception during batch delete
|
||||||
|
- Translation filters keep their status across updates
|
||||||
|
|
||||||
## [4.0.0]
|
## [4.0.0]
|
||||||
### BREAKING CHANGES
|
### BREAKING CHANGES
|
||||||
|
@ -2,8 +2,8 @@ package de.marhali.easyi18n;
|
|||||||
|
|
||||||
import de.marhali.easyi18n.model.bus.BusListener;
|
import de.marhali.easyi18n.model.bus.BusListener;
|
||||||
import de.marhali.easyi18n.model.TranslationData;
|
import de.marhali.easyi18n.model.TranslationData;
|
||||||
|
|
||||||
import de.marhali.easyi18n.model.KeyPath;
|
import de.marhali.easyi18n.model.KeyPath;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -11,7 +11,8 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data-bus which is used to distribute changes regarding translations or ui tools to the participating components.
|
* Eventbus which is mandatory to distribute changes to the participating components.
|
||||||
|
* For user interface related components the {@link FilteredDataBus} has a builtin solution to apply all relevant filters.
|
||||||
* @author marhali
|
* @author marhali
|
||||||
*/
|
*/
|
||||||
public class DataBus {
|
public class DataBus {
|
||||||
@ -38,8 +39,13 @@ public class DataBus {
|
|||||||
public BusListener propagate() {
|
public BusListener propagate() {
|
||||||
return new BusListener() {
|
return new BusListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onUpdateData(@NotNull TranslationData data) {
|
public void onFilterDuplicate(boolean filter) {
|
||||||
listener.forEach(li -> li.onUpdateData(data));
|
listener.forEach(li -> li.onFilterDuplicate(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFilterIncomplete(boolean filter) {
|
||||||
|
listener.forEach(li -> li.onFilterIncomplete(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -53,8 +59,8 @@ public class DataBus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFilterMissingTranslations(boolean filter) {
|
public void onUpdateData(@NotNull TranslationData data) {
|
||||||
listener.forEach(li -> li.onFilterMissingTranslations(filter));
|
listener.forEach(li -> li.onUpdateData(data));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
193
src/main/java/de/marhali/easyi18n/FilteredDataBus.java
Normal file
193
src/main/java/de/marhali/easyi18n/FilteredDataBus.java
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package de.marhali.easyi18n;
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
|
||||||
|
import de.marhali.easyi18n.model.KeyPath;
|
||||||
|
import de.marhali.easyi18n.model.TranslationData;
|
||||||
|
import de.marhali.easyi18n.model.TranslationNode;
|
||||||
|
import de.marhali.easyi18n.model.TranslationValue;
|
||||||
|
import de.marhali.easyi18n.model.bus.BusListener;
|
||||||
|
import de.marhali.easyi18n.model.bus.ExpandAllListener;
|
||||||
|
import de.marhali.easyi18n.model.bus.FilteredBusListener;
|
||||||
|
import de.marhali.easyi18n.settings.ProjectSettingsService;
|
||||||
|
import de.marhali.easyi18n.settings.ProjectSettingsState;
|
||||||
|
import de.marhali.easyi18n.util.KeyPathConverter;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI related eventbus. Uses the {@link BusListener} by {@link DataBus} under the hood.
|
||||||
|
* User-Interface components (e.g. tabs) use this component by implementing {@link FilteredBusListener}.
|
||||||
|
* @author marhali
|
||||||
|
*/
|
||||||
|
public class FilteredDataBus implements BusListener {
|
||||||
|
|
||||||
|
private final Project project;
|
||||||
|
private final Set<FilteredBusListener> listener;
|
||||||
|
|
||||||
|
private TranslationData data;
|
||||||
|
private ProjectSettingsState settings;
|
||||||
|
private boolean filterDuplicate;
|
||||||
|
private boolean filterIncomplete;
|
||||||
|
private String searchQuery;
|
||||||
|
private KeyPath focusKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new project specific UI eventbus.
|
||||||
|
* @param project Associated project
|
||||||
|
*/
|
||||||
|
public FilteredDataBus(@NotNull Project project) {
|
||||||
|
this.project = project;
|
||||||
|
this.listener = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a participant to the event bus. Every participant needs to be added manually.
|
||||||
|
* @param listener Bus listener
|
||||||
|
*/
|
||||||
|
public void addListener(FilteredBusListener listener) {
|
||||||
|
this.listener.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFilterDuplicate(boolean filter) {
|
||||||
|
this.filterDuplicate = filter;
|
||||||
|
this.processAndPropagate();
|
||||||
|
fire(ExpandAllListener::onExpandAll);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFilterIncomplete(boolean filter) {
|
||||||
|
this.filterIncomplete = filter;
|
||||||
|
this.processAndPropagate();
|
||||||
|
fire(ExpandAllListener::onExpandAll);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFocusKey(@NotNull KeyPath key) {
|
||||||
|
this.focusKey = key;
|
||||||
|
fire(li -> li.onFocusKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSearchQuery(@Nullable String query) {
|
||||||
|
this.searchQuery = query == null ? null : query.toLowerCase();
|
||||||
|
this.processAndPropagate();
|
||||||
|
fire(ExpandAllListener::onExpandAll);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdateData(@NotNull TranslationData data) {
|
||||||
|
this.data = data;
|
||||||
|
this.settings = ProjectSettingsService.get(this.project).getState();
|
||||||
|
processAndPropagate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter translations based on supplied filters and propagate changes
|
||||||
|
* to all registered participants.
|
||||||
|
* Internally creates a shallow copy of the cached translations and
|
||||||
|
* removes any that does not apply with the configured filters.
|
||||||
|
*/
|
||||||
|
private void processAndPropagate() {
|
||||||
|
TranslationData shadow = new TranslationData(
|
||||||
|
this.data.getLocales(), new TranslationNode(this.data.isSorting()));
|
||||||
|
|
||||||
|
for (KeyPath key : this.data.getFullKeys()) {
|
||||||
|
TranslationValue value = this.data.getTranslation(key);
|
||||||
|
assert value != null;
|
||||||
|
|
||||||
|
// We create a shallow copy of the current translation instance
|
||||||
|
// and remove every translation that does not conform with the configured filter/s
|
||||||
|
shadow.setTranslation(key, value);
|
||||||
|
|
||||||
|
// filter incomplete translations
|
||||||
|
if(filterIncomplete) {
|
||||||
|
if(!isIncomplete(value)) {
|
||||||
|
shadow.setTranslation(key, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter duplicate values
|
||||||
|
if(filterDuplicate) {
|
||||||
|
if(!isDuplicate(value)) {
|
||||||
|
shadow.setTranslation(key, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// full-text-search
|
||||||
|
if(searchQuery != null) {
|
||||||
|
if(!isSearched(key, value)) {
|
||||||
|
shadow.setTranslation(key, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fire(li -> {
|
||||||
|
li.onUpdateData(shadow);
|
||||||
|
|
||||||
|
if(focusKey != null) {
|
||||||
|
li.onFocusKey(focusKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param li Notify all registered participants about an fired event.
|
||||||
|
*/
|
||||||
|
private void fire(@NotNull Consumer<FilteredBusListener> li) {
|
||||||
|
listener.forEach(li);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter translations with missing translation values for any locale
|
||||||
|
*/
|
||||||
|
private boolean isIncomplete(@NotNull TranslationValue value) {
|
||||||
|
return this.data.getLocales().size() != value.getLocaleContents().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter duplicate translation values
|
||||||
|
*/
|
||||||
|
private boolean isDuplicate(@NotNull TranslationValue value) {
|
||||||
|
Collection<String> contents = value.getLocaleContents();
|
||||||
|
|
||||||
|
for (KeyPath currentKey : this.data.getFullKeys()) {
|
||||||
|
TranslationValue currentValue = this.data.getTranslation(currentKey);
|
||||||
|
assert currentValue != null;
|
||||||
|
|
||||||
|
for (String currentContent : currentValue.getLocaleContents()) {
|
||||||
|
if(contents.contains(currentContent)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter by search query
|
||||||
|
*/
|
||||||
|
private boolean isSearched(@NotNull KeyPath key, @NotNull TranslationValue value) {
|
||||||
|
String concatKey = new KeyPathConverter(settings).toString(key).toLowerCase();
|
||||||
|
|
||||||
|
if(searchQuery.contains(concatKey) || concatKey.contains(searchQuery)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String localeContent : value.getLocaleContents()) {
|
||||||
|
if(localeContent.toLowerCase().contains(searchQuery)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ public class InstanceManager {
|
|||||||
|
|
||||||
private final DataStore store;
|
private final DataStore store;
|
||||||
private final DataBus bus;
|
private final DataBus bus;
|
||||||
|
private final FilteredDataBus uiBus;
|
||||||
|
|
||||||
public static InstanceManager get(@NotNull Project project) {
|
public static InstanceManager get(@NotNull Project project) {
|
||||||
InstanceManager instance = INSTANCES.get(project);
|
InstanceManager instance = INSTANCES.get(project);
|
||||||
@ -35,6 +36,10 @@ public class InstanceManager {
|
|||||||
private InstanceManager(@NotNull Project project) {
|
private InstanceManager(@NotNull Project project) {
|
||||||
this.store = new DataStore(project);
|
this.store = new DataStore(project);
|
||||||
this.bus = new DataBus();
|
this.bus = new DataBus();
|
||||||
|
this.uiBus = new FilteredDataBus(project);
|
||||||
|
|
||||||
|
// Register ui eventbus on top of the normal eventbus
|
||||||
|
this.bus.addListener(this.uiBus);
|
||||||
|
|
||||||
// Load data after first initialization
|
// Load data after first initialization
|
||||||
ApplicationManager.getApplication().invokeLater(() -> {
|
ApplicationManager.getApplication().invokeLater(() -> {
|
||||||
@ -48,10 +53,20 @@ public class InstanceManager {
|
|||||||
return this.store;
|
return this.store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary eventbus.
|
||||||
|
*/
|
||||||
public DataBus bus() {
|
public DataBus bus() {
|
||||||
return this.bus;
|
return this.bus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI optimized eventbus with builtin filter logic.
|
||||||
|
*/
|
||||||
|
public FilteredDataBus uiBus() {
|
||||||
|
return this.uiBus;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reloads the plugin instance. Unsaved cached data will be deleted.
|
* Reloads the plugin instance. Unsaved cached data will be deleted.
|
||||||
* Fetches data from persistence layer and notifies all endpoints via {@link DataBus}.
|
* Fetches data from persistence layer and notifies all endpoints via {@link DataBus}.
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package de.marhali.easyi18n.action;
|
||||||
|
|
||||||
|
import com.intellij.icons.AllIcons;
|
||||||
|
import com.intellij.openapi.actionSystem.AnAction;
|
||||||
|
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import de.marhali.easyi18n.InstanceManager;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to toggle duplicate translation values filter.
|
||||||
|
* @author marhali
|
||||||
|
*/
|
||||||
|
public class FilterDuplicateAction extends AnAction {
|
||||||
|
// TODO: Custom icon to differentiate between incomplete and duplicate filter
|
||||||
|
public FilterDuplicateAction() {
|
||||||
|
super(ResourceBundle.getBundle("messages").getString("action.filter.duplicate"),
|
||||||
|
null, AllIcons.General.ShowWarning);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||||
|
Project project = Objects.requireNonNull(e.getProject());
|
||||||
|
boolean enable = e.getPresentation().getIcon() == AllIcons.General.ShowWarning;
|
||||||
|
e.getPresentation().setIcon(enable ? AllIcons.General.Warning : AllIcons.General.ShowWarning);
|
||||||
|
InstanceManager.get(project).bus().propagate().onFilterDuplicate(enable);
|
||||||
|
}
|
||||||
|
}
|
@ -14,9 +14,10 @@ import java.util.ResourceBundle;
|
|||||||
* Action which toggles translation filter on missing values.
|
* Action which toggles translation filter on missing values.
|
||||||
* @author marhali
|
* @author marhali
|
||||||
*/
|
*/
|
||||||
public class FilterMissingTranslationsAction extends AnAction {
|
public class FilterIncompleteAction extends AnAction {
|
||||||
public FilterMissingTranslationsAction() {
|
// TODO: Custom icon to differentiate between incomplete and duplicate filter
|
||||||
super(ResourceBundle.getBundle("messages").getString("action.toggle-missing"),
|
public FilterIncompleteAction() {
|
||||||
|
super(ResourceBundle.getBundle("messages").getString("action.filter.incomplete"),
|
||||||
null, AllIcons.General.ShowWarning);
|
null, AllIcons.General.ShowWarning);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,6 +26,6 @@ public class FilterMissingTranslationsAction extends AnAction {
|
|||||||
Project project = Objects.requireNonNull(e.getProject());
|
Project project = Objects.requireNonNull(e.getProject());
|
||||||
boolean enable = e.getPresentation().getIcon() == AllIcons.General.ShowWarning;
|
boolean enable = e.getPresentation().getIcon() == AllIcons.General.ShowWarning;
|
||||||
e.getPresentation().setIcon(enable ? AllIcons.General.Warning : AllIcons.General.ShowWarning);
|
e.getPresentation().setIcon(enable ? AllIcons.General.Warning : AllIcons.General.ShowWarning);
|
||||||
InstanceManager.get(project).bus().propagate().onFilterMissingTranslations(enable);
|
InstanceManager.get(project).bus().propagate().onFilterIncomplete(enable);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package de.marhali.easyi18n.model.bus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single event listener.
|
||||||
|
* @see #onExpandAll()
|
||||||
|
* @author marhali
|
||||||
|
*/
|
||||||
|
public interface ExpandAllListener {
|
||||||
|
/**
|
||||||
|
* Action to expand all nodes
|
||||||
|
*/
|
||||||
|
void onExpandAll();
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package de.marhali.easyi18n.model.bus;
|
||||||
|
|
||||||
|
import de.marhali.easyi18n.FilteredDataBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to replicate the state of {@link FilteredDataBus} to underlying components.
|
||||||
|
* @author marhali
|
||||||
|
*/
|
||||||
|
public interface FilteredBusListener extends UpdateDataListener, FocusKeyListener, ExpandAllListener {}
|
@ -27,6 +27,7 @@ public class TranslatorToolWindowFactory implements ToolWindowFactory, DumbAware
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
|
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
|
||||||
|
InstanceManager manager = InstanceManager.get(project);
|
||||||
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
|
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
|
||||||
|
|
||||||
// Translations tree view
|
// Translations tree view
|
||||||
@ -46,19 +47,19 @@ public class TranslatorToolWindowFactory implements ToolWindowFactory, DumbAware
|
|||||||
// ToolWindow Actions (Can be used for every view)
|
// ToolWindow Actions (Can be used for every view)
|
||||||
List<AnAction> actions = new ArrayList<>();
|
List<AnAction> actions = new ArrayList<>();
|
||||||
actions.add(new AddAction());
|
actions.add(new AddAction());
|
||||||
actions.add(new FilterMissingTranslationsAction());
|
actions.add(new FilterIncompleteAction());
|
||||||
|
actions.add(new FilterDuplicateAction());
|
||||||
actions.add(new ReloadAction());
|
actions.add(new ReloadAction());
|
||||||
actions.add(new SettingsAction());
|
actions.add(new SettingsAction());
|
||||||
actions.add(new SearchAction((query) -> InstanceManager.get(project).bus().propagate().onSearchQuery(query)));
|
actions.add(new SearchAction((query) -> manager.bus().propagate().onSearchQuery(query)));
|
||||||
toolWindow.setTitleActions(actions);
|
toolWindow.setTitleActions(actions);
|
||||||
|
|
||||||
// Initialize Window Manager
|
// Initialize Window Manager
|
||||||
WindowManager.getInstance().initialize(toolWindow, treeView, tableView);
|
WindowManager.getInstance().initialize(toolWindow, treeView, tableView);
|
||||||
|
|
||||||
// Synchronize ui with underlying data
|
// Synchronize ui with underlying data
|
||||||
InstanceManager manager = InstanceManager.get(project);
|
manager.uiBus().addListener(treeView);
|
||||||
manager.bus().addListener(treeView);
|
manager.uiBus().addListener(tableView);
|
||||||
manager.bus().addListener(tableView);
|
|
||||||
manager.bus().propagate().onUpdateData(manager.store().getData());
|
manager.bus().propagate().onUpdateData(manager.store().getData());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,33 +11,30 @@ import de.marhali.easyi18n.listener.DeleteKeyListener;
|
|||||||
import de.marhali.easyi18n.listener.PopupClickListener;
|
import de.marhali.easyi18n.listener.PopupClickListener;
|
||||||
import de.marhali.easyi18n.model.TranslationData;
|
import de.marhali.easyi18n.model.TranslationData;
|
||||||
import de.marhali.easyi18n.model.action.TranslationDelete;
|
import de.marhali.easyi18n.model.action.TranslationDelete;
|
||||||
import de.marhali.easyi18n.model.bus.BusListener;
|
|
||||||
import de.marhali.easyi18n.model.KeyPath;
|
import de.marhali.easyi18n.model.KeyPath;
|
||||||
import de.marhali.easyi18n.model.Translation;
|
import de.marhali.easyi18n.model.Translation;
|
||||||
import de.marhali.easyi18n.model.TranslationValue;
|
import de.marhali.easyi18n.model.TranslationValue;
|
||||||
|
import de.marhali.easyi18n.model.bus.FilteredBusListener;
|
||||||
import de.marhali.easyi18n.renderer.TableRenderer;
|
import de.marhali.easyi18n.renderer.TableRenderer;
|
||||||
import de.marhali.easyi18n.tabs.mapper.TableModelMapper;
|
import de.marhali.easyi18n.tabs.mapper.TableModelMapper;
|
||||||
import de.marhali.easyi18n.util.KeyPathConverter;
|
import de.marhali.easyi18n.util.KeyPathConverter;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.ResourceBundle;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows translation state as table.
|
* Shows translation state as table.
|
||||||
*
|
|
||||||
* @author marhali
|
* @author marhali
|
||||||
*/
|
*/
|
||||||
public class TableView implements BusListener {
|
public class TableView implements FilteredBusListener {
|
||||||
|
|
||||||
private final JBTable table;
|
private final JBTable table;
|
||||||
|
|
||||||
private final Project project;
|
private final Project project;
|
||||||
|
|
||||||
private TableModelMapper currentMapper;
|
|
||||||
private KeyPathConverter converter;
|
private KeyPathConverter converter;
|
||||||
|
|
||||||
private JPanel rootPanel;
|
private JPanel rootPanel;
|
||||||
@ -70,12 +67,14 @@ public class TableView implements BusListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void deleteSelectedRows() {
|
private void deleteSelectedRows() {
|
||||||
for (int selectedRow : table.getSelectedRows()) {
|
Set<KeyPath> batchDelete = new HashSet<>();
|
||||||
KeyPath fullPath = this.converter.fromString(String.valueOf(table.getValueAt(selectedRow, 0)));
|
|
||||||
|
|
||||||
InstanceManager.get(project).processUpdate(
|
for (int selectedRow : table.getSelectedRows()) {
|
||||||
new TranslationDelete(new Translation(fullPath, null))
|
batchDelete.add(this.converter.fromString(String.valueOf(table.getValueAt(selectedRow, 0))));
|
||||||
);
|
}
|
||||||
|
|
||||||
|
for (KeyPath key : batchDelete) {
|
||||||
|
InstanceManager.get(project).processUpdate(new TranslationDelete(new Translation(key, null)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +82,7 @@ public class TableView implements BusListener {
|
|||||||
public void onUpdateData(@NotNull TranslationData data) {
|
public void onUpdateData(@NotNull TranslationData data) {
|
||||||
this.converter = new KeyPathConverter(project);
|
this.converter = new KeyPathConverter(project);
|
||||||
|
|
||||||
table.setModel(this.currentMapper = new TableModelMapper(data, this.converter, update ->
|
table.setModel(new TableModelMapper(data, this.converter, update ->
|
||||||
InstanceManager.get(project).processUpdate(update)));
|
InstanceManager.get(project).processUpdate(update)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,19 +104,8 @@ public class TableView implements BusListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSearchQuery(@Nullable String query) {
|
public void onExpandAll() {
|
||||||
if (this.currentMapper != null) {
|
// table-view never collapse any rows
|
||||||
this.currentMapper.onSearchQuery(query);
|
|
||||||
this.table.updateUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFilterMissingTranslations(boolean filter) {
|
|
||||||
if(this.currentMapper != null) {
|
|
||||||
this.currentMapper.onFilterMissingTranslations(filter);
|
|
||||||
this.table.updateUI();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public JPanel getRootPanel() {
|
public JPanel getRootPanel() {
|
||||||
|
@ -13,7 +13,6 @@ import de.marhali.easyi18n.dialog.EditDialog;
|
|||||||
import de.marhali.easyi18n.listener.ReturnKeyListener;
|
import de.marhali.easyi18n.listener.ReturnKeyListener;
|
||||||
import de.marhali.easyi18n.model.TranslationData;
|
import de.marhali.easyi18n.model.TranslationData;
|
||||||
import de.marhali.easyi18n.model.action.TranslationDelete;
|
import de.marhali.easyi18n.model.action.TranslationDelete;
|
||||||
import de.marhali.easyi18n.model.bus.BusListener;
|
|
||||||
import de.marhali.easyi18n.action.treeview.CollapseTreeViewAction;
|
import de.marhali.easyi18n.action.treeview.CollapseTreeViewAction;
|
||||||
import de.marhali.easyi18n.action.treeview.ExpandTreeViewAction;
|
import de.marhali.easyi18n.action.treeview.ExpandTreeViewAction;
|
||||||
import de.marhali.easyi18n.listener.DeleteKeyListener;
|
import de.marhali.easyi18n.listener.DeleteKeyListener;
|
||||||
@ -21,6 +20,7 @@ import de.marhali.easyi18n.listener.PopupClickListener;
|
|||||||
import de.marhali.easyi18n.model.KeyPath;
|
import de.marhali.easyi18n.model.KeyPath;
|
||||||
import de.marhali.easyi18n.model.Translation;
|
import de.marhali.easyi18n.model.Translation;
|
||||||
import de.marhali.easyi18n.model.TranslationValue;
|
import de.marhali.easyi18n.model.TranslationValue;
|
||||||
|
import de.marhali.easyi18n.model.bus.FilteredBusListener;
|
||||||
import de.marhali.easyi18n.renderer.TreeRenderer;
|
import de.marhali.easyi18n.renderer.TreeRenderer;
|
||||||
import de.marhali.easyi18n.settings.ProjectSettingsService;
|
import de.marhali.easyi18n.settings.ProjectSettingsService;
|
||||||
import de.marhali.easyi18n.tabs.mapper.TreeModelMapper;
|
import de.marhali.easyi18n.tabs.mapper.TreeModelMapper;
|
||||||
@ -32,16 +32,13 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
import javax.swing.tree.TreePath;
|
import javax.swing.tree.TreePath;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show translation state as tree.
|
* Show translation state as tree.
|
||||||
*
|
|
||||||
* @author marhali
|
* @author marhali
|
||||||
*/
|
*/
|
||||||
public class TreeView implements BusListener {
|
public class TreeView implements FilteredBusListener {
|
||||||
|
|
||||||
private final Tree tree;
|
private final Tree tree;
|
||||||
|
|
||||||
@ -71,8 +68,8 @@ public class TreeView implements BusListener {
|
|||||||
private void placeActions() {
|
private void placeActions() {
|
||||||
DefaultActionGroup group = new DefaultActionGroup("TranslationsGroup", false);
|
DefaultActionGroup group = new DefaultActionGroup("TranslationsGroup", false);
|
||||||
|
|
||||||
ExpandTreeViewAction expand = new ExpandTreeViewAction(this::expandAll);
|
ExpandTreeViewAction expand = new ExpandTreeViewAction(this::onExpandAll);
|
||||||
CollapseTreeViewAction collapse = new CollapseTreeViewAction(this::collapseAll);
|
CollapseTreeViewAction collapse = new CollapseTreeViewAction(this::onCollapseAll);
|
||||||
|
|
||||||
group.add(collapse);
|
group.add(collapse);
|
||||||
group.add(expand);
|
group.add(expand);
|
||||||
@ -117,24 +114,6 @@ public class TreeView implements BusListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSearchQuery(@Nullable String query) {
|
|
||||||
if (this.currentMapper != null) {
|
|
||||||
this.currentMapper.onSearchQuery(query);
|
|
||||||
this.expandAll();
|
|
||||||
this.tree.updateUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFilterMissingTranslations(boolean filter) {
|
|
||||||
if (this.currentMapper != null) {
|
|
||||||
this.currentMapper.onFilterMissingTranslations(filter);
|
|
||||||
this.expandAll();
|
|
||||||
this.tree.updateUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showEditPopup(@Nullable TreePath path) {
|
private void showEditPopup(@Nullable TreePath path) {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
return;
|
return;
|
||||||
@ -157,28 +136,30 @@ public class TreeView implements BusListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void deleteSelectedNodes() {
|
private void deleteSelectedNodes() {
|
||||||
TreePath[] paths = tree.getSelectionPaths();
|
TreePath[] selection = tree.getSelectionPaths();
|
||||||
|
Set<KeyPath> batchDelete = new HashSet<>();
|
||||||
|
|
||||||
if (paths == null) {
|
if(selection == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TreePath path : tree.getSelectionPaths()) {
|
for (TreePath path : selection) {
|
||||||
KeyPath fullPath = TreeUtil.getFullPath(path);
|
batchDelete.add(TreeUtil.getFullPath(path));
|
||||||
|
}
|
||||||
|
|
||||||
InstanceManager.get(project).processUpdate(
|
for (KeyPath key : batchDelete) {
|
||||||
new TranslationDelete(new Translation(fullPath, null))
|
InstanceManager.get(project).processUpdate(new TranslationDelete(new Translation(key, null)));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expandAll() {
|
@Override
|
||||||
|
public void onExpandAll() {
|
||||||
for (int i = 0; i < tree.getRowCount(); i++) {
|
for (int i = 0; i < tree.getRowCount(); i++) {
|
||||||
tree.expandRow(i);
|
tree.expandRow(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void collapseAll() {
|
public void onCollapseAll() {
|
||||||
for (int i = 0; i < tree.getRowCount(); i++) {
|
for (int i = 0; i < tree.getRowCount(); i++) {
|
||||||
tree.collapseRow(i);
|
tree.collapseRow(i);
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,6 @@ package de.marhali.easyi18n.tabs.mapper;
|
|||||||
|
|
||||||
import de.marhali.easyi18n.model.TranslationData;
|
import de.marhali.easyi18n.model.TranslationData;
|
||||||
import de.marhali.easyi18n.model.action.TranslationUpdate;
|
import de.marhali.easyi18n.model.action.TranslationUpdate;
|
||||||
import de.marhali.easyi18n.model.bus.FilterIncompleteListener;
|
|
||||||
import de.marhali.easyi18n.model.bus.SearchQueryListener;
|
|
||||||
import de.marhali.easyi18n.model.KeyPath;
|
import de.marhali.easyi18n.model.KeyPath;
|
||||||
import de.marhali.easyi18n.model.Translation;
|
import de.marhali.easyi18n.model.Translation;
|
||||||
import de.marhali.easyi18n.model.TranslationValue;
|
import de.marhali.easyi18n.model.TranslationValue;
|
||||||
@ -11,7 +9,6 @@ import de.marhali.easyi18n.util.KeyPathConverter;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nls;
|
import org.jetbrains.annotations.Nls;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import javax.swing.event.TableModelListener;
|
import javax.swing.event.TableModelListener;
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
@ -23,13 +20,13 @@ import java.util.function.Consumer;
|
|||||||
* Mapping {@link TranslationData} to {@link TableModel}.
|
* Mapping {@link TranslationData} to {@link TableModel}.
|
||||||
* @author marhali
|
* @author marhali
|
||||||
*/
|
*/
|
||||||
public class TableModelMapper implements TableModel, SearchQueryListener, FilterIncompleteListener {
|
public class TableModelMapper implements TableModel {
|
||||||
|
|
||||||
private final @NotNull TranslationData data;
|
private final @NotNull TranslationData data;
|
||||||
private final @NotNull KeyPathConverter converter;
|
private final @NotNull KeyPathConverter converter;
|
||||||
|
|
||||||
private final @NotNull List<String> locales;
|
private final @NotNull List<String> locales;
|
||||||
private @NotNull List<KeyPath> fullKeys;
|
private final @NotNull List<KeyPath> fullKeys;
|
||||||
|
|
||||||
private final @NotNull Consumer<TranslationUpdate> updater;
|
private final @NotNull Consumer<TranslationUpdate> updater;
|
||||||
|
|
||||||
@ -44,49 +41,6 @@ public class TableModelMapper implements TableModel, SearchQueryListener, Filter
|
|||||||
this.updater = updater;
|
this.updater = updater;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSearchQuery(@Nullable String query) {
|
|
||||||
if(query == null) { // Reset
|
|
||||||
this.fullKeys = new ArrayList<>(this.data.getFullKeys());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
query = query.toLowerCase();
|
|
||||||
List<KeyPath> matches = new ArrayList<>();
|
|
||||||
|
|
||||||
for(KeyPath key : this.data.getFullKeys()) {
|
|
||||||
if(this.converter.toString(key).toLowerCase().contains(query)) {
|
|
||||||
matches.add(key);
|
|
||||||
} else {
|
|
||||||
for(String content : this.data.getTranslation(key).getLocaleContents()) {
|
|
||||||
if(content.toLowerCase().contains(query)) {
|
|
||||||
matches.add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fullKeys = matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFilterMissingTranslations(boolean filter) {
|
|
||||||
if(!filter) { // Reset
|
|
||||||
this.fullKeys = new ArrayList<>(this.data.getFullKeys());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<KeyPath> matches = new ArrayList<>();
|
|
||||||
|
|
||||||
for(KeyPath key : this.data.getFullKeys()) {
|
|
||||||
if(this.data.getTranslation(key).getLocaleContents().size() != this.locales.size()) {
|
|
||||||
matches.add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fullKeys = matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return this.fullKeys.size();
|
return this.fullKeys.size();
|
||||||
|
@ -5,12 +5,8 @@ import com.intellij.ui.JBColor;
|
|||||||
|
|
||||||
import de.marhali.easyi18n.model.TranslationData;
|
import de.marhali.easyi18n.model.TranslationData;
|
||||||
import de.marhali.easyi18n.model.TranslationNode;
|
import de.marhali.easyi18n.model.TranslationNode;
|
||||||
import de.marhali.easyi18n.model.bus.FilterIncompleteListener;
|
|
||||||
import de.marhali.easyi18n.model.bus.SearchQueryListener;
|
|
||||||
import de.marhali.easyi18n.model.KeyPath;
|
import de.marhali.easyi18n.model.KeyPath;
|
||||||
import de.marhali.easyi18n.model.TranslationValue;
|
|
||||||
import de.marhali.easyi18n.settings.ProjectSettings;
|
import de.marhali.easyi18n.settings.ProjectSettings;
|
||||||
import de.marhali.easyi18n.util.KeyPathConverter;
|
|
||||||
import de.marhali.easyi18n.util.UiUtil;
|
import de.marhali.easyi18n.util.UiUtil;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -25,17 +21,15 @@ import java.util.Map;
|
|||||||
* Mapping {@link TranslationData} to {@link TreeModel}.
|
* Mapping {@link TranslationData} to {@link TreeModel}.
|
||||||
* @author marhali
|
* @author marhali
|
||||||
*/
|
*/
|
||||||
public class TreeModelMapper extends DefaultTreeModel implements SearchQueryListener, FilterIncompleteListener {
|
public class TreeModelMapper extends DefaultTreeModel {
|
||||||
|
|
||||||
private final TranslationData data;
|
private final TranslationData data;
|
||||||
private final KeyPathConverter converter;
|
|
||||||
private final ProjectSettings state;
|
private final ProjectSettings state;
|
||||||
|
|
||||||
public TreeModelMapper(TranslationData data, ProjectSettings state) {
|
public TreeModelMapper(TranslationData data, ProjectSettings state) {
|
||||||
super(null);
|
super(null);
|
||||||
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.converter = new KeyPathConverter(state);
|
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
||||||
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
|
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
|
||||||
@ -43,65 +37,7 @@ public class TreeModelMapper extends DefaultTreeModel implements SearchQueryList
|
|||||||
super.setRoot(rootNode);
|
super.setRoot(rootNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSearchQuery(@Nullable String query) {
|
|
||||||
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
|
|
||||||
TranslationData shadow = new TranslationData(this.state.isSorting());
|
|
||||||
|
|
||||||
if(query == null) { // Reset
|
|
||||||
this.generateNodes(rootNode, this.data.getRootNode());
|
|
||||||
super.setRoot(rootNode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
query = query.toLowerCase();
|
|
||||||
|
|
||||||
for(KeyPath currentKey : this.data.getFullKeys()) {
|
|
||||||
TranslationValue translation = this.data.getTranslation(currentKey);
|
|
||||||
String loweredKey = this.converter.toString(currentKey).toLowerCase();
|
|
||||||
|
|
||||||
if(query.contains(loweredKey) || loweredKey.contains(query)) {
|
|
||||||
shadow.setTranslation(currentKey, translation);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(String currentContent : translation.getLocaleContents()) {
|
|
||||||
if(currentContent.toLowerCase().contains(query)) {
|
|
||||||
shadow.setTranslation(currentKey, translation);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.generateNodes(rootNode, shadow.getRootNode());
|
|
||||||
super.setRoot(rootNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFilterMissingTranslations(boolean filter) {
|
|
||||||
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
|
|
||||||
TranslationData shadow = new TranslationData(this.state.isSorting());
|
|
||||||
|
|
||||||
if(!filter) { // Reset
|
|
||||||
this.generateNodes(rootNode, this.data.getRootNode());
|
|
||||||
super.setRoot(rootNode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(KeyPath currentKey : this.data.getFullKeys()) {
|
|
||||||
TranslationValue translation = this.data.getTranslation(currentKey);
|
|
||||||
|
|
||||||
if(translation.getLocaleContents().size() != this.data.getLocales().size()) {
|
|
||||||
shadow.setTranslation(currentKey, translation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.generateNodes(rootNode, shadow.getRootNode());
|
|
||||||
super.setRoot(rootNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param parent Parent tree node
|
* @param parent Parent tree node
|
||||||
* @param translationNode Layer of translation node to write to tree
|
* @param translationNode Layer of translation node to write to tree
|
||||||
* @return true if children nodes misses any translation values
|
* @return true if children nodes misses any translation values
|
||||||
|
@ -6,7 +6,8 @@ view.table.title=Table View
|
|||||||
view.empty=No translations found. Click on settings to specify a locales directory or reload
|
view.empty=No translations found. Click on settings to specify a locales directory or reload
|
||||||
action.add=Add Translation
|
action.add=Add Translation
|
||||||
action.edit=Edit Translation
|
action.edit=Edit Translation
|
||||||
action.toggle-missing=Toggle missing translations
|
action.filter.incomplete=Filter Incomplete Translations
|
||||||
|
action.filter.duplicate=Filter Duplicate Translation Values
|
||||||
action.reload=Reload From Disk
|
action.reload=Reload From Disk
|
||||||
action.settings=Settings
|
action.settings=Settings
|
||||||
action.search=Search...
|
action.search=Search...
|
||||||
|
Loading…
x
Reference in New Issue
Block a user