// License: GPL. For details, see LICENSE file.
package org.wikipedia.gui;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.SwingWorker;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.search.SearchAction;
import org.openstreetmap.josm.command.ChangePropertyCommand;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Tag;
import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
import org.openstreetmap.josm.data.preferences.StringProperty;
import org.openstreetmap.josm.gui.SideButton;
import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.LanguageInfo;
import org.openstreetmap.josm.tools.OpenBrowser;
import org.wikipedia.WikipediaApp;
import org.wikipedia.actions.FetchWikidataAction;
import org.wikipedia.data.WikipediaEntry;
public class WikipediaToggleDialog extends ToggleDialog implements ActiveLayerChangeListener, DataSetListenerAdapter.Listener {
public WikipediaToggleDialog() {
super(tr("Wikipedia"), "wikipedia", tr("Fetch Wikipedia articles with coordinates"), null, 150);
createLayout(list, true, Arrays.asList(
new SideButton(new WikipediaLoadCoordinatesAction(false)),
new SideButton(new WikipediaLoadCoordinatesAction(true)),
new SideButton(new WikipediaLoadCategoryAction()),
new SideButton(new PasteWikipediaArticlesAction()),
new SideButton(new AddWikipediaTagAction(list)),
new SideButton(new WikipediaSettingsAction(), false)));
updateTitle();
}
/** A string describing the context (use-case) for determining the dialog title */
String titleContext = null;
static final StringProperty wikipediaLang = new StringProperty("wikipedia.lang", LanguageInfo.getJOSMLocaleCode().substring(0, 2));
final Set<String> articles = new HashSet<>();
final DefaultListModel<WikipediaEntry> model = new DefaultListModel<>();
final JList<WikipediaEntry> list = new JList<WikipediaEntry>(model) {
{
setToolTipText(tr("Double click on item to search for object with article name (and center coordinate)"));
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2 && getSelectedValue() != null && Main.getLayerManager().getEditDataSet() != null) {
final WikipediaEntry entry = getSelectedValue();
if (entry.coordinate != null) {
BoundingXYVisitor bbox = new BoundingXYVisitor();
bbox.visit(entry.coordinate);
Main.map.mapView.zoomTo(bbox);
}
final String search = entry.getSearchText().replaceAll("\\(.*\\)", "");
SearchAction.search(search, SearchAction.SearchMode.replace);
}
}
});
setCellRenderer(new DefaultListCellRenderer() {
@Override
public JLabel getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
final WikipediaEntry entry = (WikipediaEntry) value;
final String labelText = "<html>" + entry.getLabelText();
final JLabel label = (JLabel) super.getListCellRendererComponent(list, labelText, index, isSelected, cellHasFocus);
if (entry.getWiwosmStatus() != null && entry.getWiwosmStatus()) {
label.setIcon(ImageProvider.getIfAvailable("misc", "grey_check"));
label.setToolTipText(/* I18n: WIWOSM server already links Wikipedia article to object/s */ tr("Available via WIWOSM server"));
} else if (articles.contains(entry.article)) {
label.setIcon(ImageProvider.getIfAvailable("misc", "green_check"));
label.setToolTipText(/* I18n: object/s from dataset contain link to Wikipedia article */ tr("Available in local dataset"));
} else {
label.setToolTipText(tr("Not linked yet"));
}
return label;
}
});
final JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(new OpenWikipediaArticleAction());
popupMenu.add(new ZoomToWikipediaArticleAction());
setComponentPopupMenu(popupMenu);
}
};
private void updateTitle() {
final String lang = getLanguageOfFirstItem();
final String host = WikipediaApp.forLanguage(lang).getSiteUrl().split("/+")[1];
if (titleContext == null) {
setTitle(host);
} else {
setTitle(tr("{0}: {1}", host, titleContext));
}
}
private String getLanguageOfFirstItem() {
try {
return list.getModel().getElementAt(0).lang;
} catch (ArrayIndexOutOfBoundsException ignore) {
return wikipediaLang.get();
}
}
class WikipediaLoadCoordinatesAction extends AbstractAction {
private final boolean wikidata;
public WikipediaLoadCoordinatesAction(boolean wikidata) {
super(wikidata ? tr("Wikidata") : tr("Coordinates"));
this.wikidata = wikidata;
new ImageProvider("dialogs", wikidata ? "wikidata" : "wikipedia").getResource().attachImageIcon(this, true);
putValue(SHORT_DESCRIPTION, wikidata
? tr("Fetches all coordinates from Wikidata in the current view")
: tr("Fetches all coordinates from Wikipedia in the current view"));
}
@Override
public void actionPerformed(ActionEvent e) {
try {
// determine bbox
final LatLon min = Main.map.mapView.getLatLon(0, Main.map.mapView.getHeight());
final LatLon max = Main.map.mapView.getLatLon(Main.map.mapView.getWidth(), 0);
// add entries to list model
titleContext = tr("coordinates");
updateTitle();
new UpdateWikipediaArticlesSwingWorker() {
@Override
List<WikipediaEntry> getEntries() {
return WikipediaApp.forLanguage(wikidata ? "wikidata" : wikipediaLang.get())
.getEntriesFromCoordinates(min, max);
}
}.execute();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
abstract class UpdateWikipediaArticlesSwingWorker extends SwingWorker<Void, WikipediaEntry> {
abstract List<WikipediaEntry> getEntries();
@Override
protected Void doInBackground() throws Exception {
final List<WikipediaEntry> entries = getEntries();
entries.sort(null);
publish(entries.toArray(new WikipediaEntry[entries.size()]));
WikipediaApp.partitionList(entries, 20).forEach(chunk -> {
WikipediaApp.forLanguage(chunk.get(0).lang).updateWIWOSMStatus(chunk);
list.repaint();
});
return null;
}
@Override
protected void process(List<WikipediaEntry> chunks) {
model.clear();
chunks.forEach(model::addElement);
updateTitle();
updateWikipediaArticles();
}
}
class WikipediaLoadCategoryAction extends AbstractAction {
public WikipediaLoadCategoryAction() {
super(tr("Category"));
new ImageProvider("data", "sequence").getResource().attachImageIcon(this, true);
putValue(SHORT_DESCRIPTION, tr("Fetches a list of all Wikipedia articles of a category"));
}
@Override
public void actionPerformed(ActionEvent e) {
final WikipediaCategorySearchDialog categorySearchDialog = WikipediaCategorySearchDialog.getInstance();
categorySearchDialog.showDialog();
if (categorySearchDialog.getValue() != 1) {
return;
}
final String category = categorySearchDialog.getCategory();
if (category == null) {
return;
}
titleContext = category;
updateTitle();
new UpdateWikipediaArticlesSwingWorker() {
@Override
List<WikipediaEntry> getEntries() {
return WikipediaApp.forLanguage(wikipediaLang.get())
.getEntriesFromCategory(category, Main.pref.getInteger("wikipedia.depth", 3));
}
}.execute();
}
}
class PasteWikipediaArticlesAction extends AbstractAction {
public PasteWikipediaArticlesAction() {
super(tr("Clipboard"));
new ImageProvider("paste").getResource().attachImageIcon(this, true);
putValue(SHORT_DESCRIPTION, tr("Pastes Wikipedia articles from the system clipboard"));
}
@Override
public void actionPerformed(ActionEvent e) {
titleContext = tr("clipboard");
updateTitle();
new UpdateWikipediaArticlesSwingWorker() {
@Override
List<WikipediaEntry> getEntries() {
return WikipediaApp.getEntriesFromClipboard(wikipediaLang.get());
}
}.execute();
}
}
class OpenWikipediaArticleAction extends AbstractAction {
public OpenWikipediaArticleAction() {
super(tr("Open Article"));
new ImageProvider("browser").getResource().attachImageIcon(this);
putValue(SHORT_DESCRIPTION, tr("Opens the Wikipedia article of the selected item in a browser"));
}
@Override
public void actionPerformed(ActionEvent e) {
if (list.getSelectedValue() != null) {
final String url = list.getSelectedValue().getBrowserUrl();
Main.info("Wikipedia: opening " + url);
OpenBrowser.displayUrl(url);
}
}
}
class WikipediaSettingsAction extends AbstractAction {
public WikipediaSettingsAction() {
super(tr("Language"));
new ImageProvider("dialogs/settings").getResource().attachImageIcon(this, true);
putValue(SHORT_DESCRIPTION, tr("Sets the default language for the Wikipedia articles"));
}
@Override
public void actionPerformed(ActionEvent e) {
String lang = JOptionPane.showInputDialog(
Main.parent,
tr("Enter the Wikipedia language"),
wikipediaLang.get());
if (lang != null) {
wikipediaLang.put(lang);
updateTitle();
updateWikipediaArticles();
}
}
}
static class AddWikipediaTagAction extends AbstractAction {
private final JList<WikipediaEntry> list;
public AddWikipediaTagAction(JList<WikipediaEntry> list) {
super(tr("Add Tag"));
this.list = list;
new ImageProvider("pastetags").getResource().attachImageIcon(this, true);
putValue(SHORT_DESCRIPTION, tr("Adds a ''wikipedia'' tag corresponding to this article to the selected objects"));
}
@Override
public void actionPerformed(ActionEvent e) {
addTag(list.getSelectedValue());
}
static void addTag(WikipediaEntry entry) {
if (entry == null) {
return;
}
addTag(entry.createWikipediaTag());
}
static void addTag(Tag tag) {
if (tag == null) {
return;
}
final Collection<OsmPrimitive> selected = Main.getLayerManager().getEditDataSet().getSelected();
if (!GuiUtils.confirmOverwrite(tag.getKey(), tag.getValue(), selected)) {
return;
}
ChangePropertyCommand cmd = new ChangePropertyCommand(
selected,
tag.getKey(), tag.getValue());
Main.main.undoRedo.add(cmd);
Main.worker.submit(new FetchWikidataAction.Fetcher(selected));
}
}
class ZoomToWikipediaArticleAction extends AbstractAction {
ZoomToWikipediaArticleAction() {
super(tr("Zoom to selection"));
new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this);
putValue(SHORT_DESCRIPTION, tr("Zoom to selection"));
}
@Override
public void actionPerformed(ActionEvent e) {
final WikipediaEntry entry = list.getSelectedValue();
if (entry == null) {
return;
}
final LatLon latLon = entry.coordinate != null
? entry.coordinate
: WikipediaApp.forLanguage(entry.lang).getCoordinateForArticle(entry.article);
if (latLon == null) {
return;
}
Main.map.mapView.zoomTo(latLon);
}
}
protected void updateWikipediaArticles() {
final String language = getLanguageOfFirstItem();
articles.clear();
if (Main.main != null && Main.getLayerManager().getEditDataSet() != null) {
Main.getLayerManager().getEditDataSet().allPrimitives().stream()
.flatMap(p -> WikipediaApp.forLanguage(language).getWikipediaArticles(p))
.forEach(articles::add);
}
}
private final DataSetListenerAdapter dataChangedAdapter = new DataSetListenerAdapter(this);
@Override
public void showNotify() {
DatasetEventManager.getInstance().addDatasetListener(dataChangedAdapter, FireMode.IN_EDT_CONSOLIDATED);
Main.getLayerManager().addActiveLayerChangeListener(this);
updateWikipediaArticles();
}
@Override
public void hideNotify() {
DatasetEventManager.getInstance().removeDatasetListener(dataChangedAdapter);
Main.getLayerManager().removeActiveLayerChangeListener(this);
articles.clear();
}
@Override
public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
updateWikipediaArticles();
list.repaint();
}
@Override
public void processDatasetEvent(AbstractDatasetChangedEvent event) {
updateWikipediaArticles();
list.repaint();
}
}