Java tutorial
/** * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This Source Code Form is "Incompatible With Secondary Licenses", as * defined by the Mozilla Public License, v. 2.0. */ package utybo.branchingstorytree.swing.editor; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import org.apache.commons.lang.StringEscapeUtils; import org.pushingpixels.substance.api.renderer.SubstanceDefaultListCellRenderer; import net.miginfocom.swing.MigLayout; import utybo.branchingstorytree.api.BSTException; import utybo.branchingstorytree.api.story.LogicalNode; import utybo.branchingstorytree.api.story.StoryNode; import utybo.branchingstorytree.api.story.TextNode; import utybo.branchingstorytree.api.story.VirtualNode; import utybo.branchingstorytree.swing.Icons; import utybo.branchingstorytree.swing.Messagers; import utybo.branchingstorytree.swing.OpenBSTGUI; import utybo.branchingstorytree.swing.editor.StorySingleNodeEditor.Status; import utybo.branchingstorytree.swing.utils.AlphanumComparator; import utybo.branchingstorytree.swing.utils.Lang; import utybo.branchingstorytree.swing.visuals.JScrollablePanel; import utybo.branchingstorytree.swing.visuals.JScrollablePanel.ScrollableSizeHint; @SuppressWarnings("serial") public class StoryNodesEditor extends JPanel implements EditorControl<Collection<StoryNode>> { private ListListModel<StorySingleNodeEditor> list; private JList<StorySingleNodeEditor> jlist; private JPanel container; public StoryNodesEditor() { setLayout(new MigLayout("", "[:33%:300px][grow 150]", "[grow][]")); JScrollPane scrollPane = new JScrollPane(); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); add(scrollPane, "cell 0 0,grow"); jlist = new JList<>(); jlist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); jlist.setCellRenderer(new SubstanceDefaultListCellRenderer() { @Override public Component getListCellRendererComponent(@SuppressWarnings("rawtypes") JList list, Object o, int index, boolean isSelected, boolean cellHasFocus) { JLabel label = (JLabel) super.getListCellRendererComponent(list, o, index, isSelected, cellHasFocus); if (o instanceof StorySingleNodeEditor) { if (((StorySingleNodeEditor) o).getStatus() == Status.ERROR) label.setForeground(Color.RED.darker()); if (o instanceof StoryLogicalNodeEditor) label.setIcon(new ImageIcon(Icons.getImage("LogicalNode", 16))); else if (o instanceof StoryTextNodeEditor) label.setIcon(new ImageIcon(Icons.getImage("TextNode", 16))); else if (o instanceof StoryVirtualNodeEditor) label.setIcon(new ImageIcon(Icons.getImage("VirtualNode", 16))); } return label; } }); jlist.addListSelectionListener(e -> { if (jlist.getSelectedValue() instanceof JComponent) { container.removeAll(); container.add(jlist.getSelectedValue()); container.revalidate(); container.repaint(); } }); JScrollablePanel pan = new JScrollablePanel(new BorderLayout(0, 0)); jlist.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { scrollPane.revalidate(); pan.revalidate(); jlist.revalidate(); revalidate(); repaint(); } }); pan.setScrollableWidth(ScrollableSizeHint.FIT); pan.setScrollableHeight(ScrollableSizeHint.STRETCH); pan.add(jlist, BorderLayout.CENTER); scrollPane.setViewportView(pan); container = new JPanel(); add(container, "cell 1 0,grow"); container.setLayout(new BorderLayout(0, 0)); container.add(new JPanel(), BorderLayout.CENTER); // TODO JPanel panel = new JPanel(); add(panel, "cell 0 1 2 1,alignx leading,growy"); JButton btnAddNode = new JButton(Lang.get("editor.panel.add"), new ImageIcon(Icons.getImage("Add Subnode", 16))); btnAddNode.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { createMenu().show(btnAddNode, e.getX(), e.getY()); } }); panel.add(btnAddNode); JButton btnRemoveNode = new JButton(Lang.get("editor.panel.remove"), new ImageIcon(Icons.getImage("Delete Subnode", 16))); btnRemoveNode.addActionListener(e -> removeNode()); panel.add(btnRemoveNode); } private JPopupMenu createMenu() { JPopupMenu createMenu = new JPopupMenu(); createMenu.add(new AbstractAction(Lang.get("editor.panel.text"), // new ImageIcon(Icons.getImage("TextNode", 16))) { @Override public void actionPerformed(ActionEvent e) { addNode(new StoryTextNodeEditor(StoryNodesEditor.this)); } }); createMenu.add(new AbstractAction(Lang.get("editor.panel.virtual"), new ImageIcon(Icons.getImage("VirtualNode", 16))) { @Override public void actionPerformed(ActionEvent e) { addNode(new StoryVirtualNodeEditor(StoryNodesEditor.this)); } }); createMenu.add(new AbstractAction(Lang.get("editor.panel.logical"), new ImageIcon(Icons.getImage("LogicalNode", 16))) { @Override public void actionPerformed(ActionEvent e) { addNode(new StoryLogicalNodeEditor(StoryNodesEditor.this)); } }); return createMenu; } private void removeNode() { StorySingleNodeEditor ssne = jlist.getSelectedValue(); if (ssne == null) { Messagers.showMessage(OpenBSTGUI.getInstance(), Lang.get("editor.panel.noselected"), Messagers.TYPE_ERROR); return; } else { int i = Messagers.showConfirm(OpenBSTGUI.getInstance(), "<html>" + Lang.get("editor.panel.removeconfirm").replace("$i", ssne.getIdentifier()) .replace("$s", StringEscapeUtils.escapeHtml(ssne.getSummary())), Messagers.OPTIONS_YES_NO); if (i == Messagers.OPTION_YES) { int index = list.indexOf(ssne); list.remove(ssne); if (index < list.size()) jlist.setSelectedIndex(index); else if (index - 1 < list.size()) jlist.setSelectedIndex(index - 1); else if (list.size() > 0) jlist.setSelectedIndex(0); else container.removeAll(); // TODO } } } protected void addNode(StorySingleNodeEditor node) { int id = -1; for (int i = 1; i < Integer.MAX_VALUE; i++) { boolean freeSpot = true; for (StorySingleNodeEditor ssne : list) { if (ssne.getRawIntegerId() == i) { freeSpot = false; break; } } if (freeSpot == true) { id = i; break; } } node.setInitialIntegerId(id); list.addSorted(node, (o1, o2) -> new AlphanumComparator().compare(o1.getIdentifier(), o2.getIdentifier())); jlist.setSelectedValue(node, true); jlist.revalidate(); jlist.repaint(); } @Override public void importFrom(Collection<StoryNode> from) throws BSTException { list = new ListListModel<>(new ArrayList<>()); for (StoryNode sn : from) { if (sn instanceof TextNode) { StoryTextNodeEditor stne = new StoryTextNodeEditor(this); stne.importFrom((TextNode) sn); list.add(stne); } else if (sn instanceof VirtualNode) { StoryVirtualNodeEditor svne = new StoryVirtualNodeEditor(this); svne.importFrom((VirtualNode) sn); list.add(svne); } else if (sn instanceof LogicalNode) { StoryLogicalNodeEditor slne = new StoryLogicalNodeEditor(this); slne.importFrom((LogicalNode) sn); list.add(slne); } } list.sort((o1, o2) -> new AlphanumComparator().compare(o1.getIdentifier(), o2.getIdentifier())); jlist.setModel(list); checkAllAvailable(); if (list.size() > 0) jlist.setSelectedIndex(0); } @Override public Collection<StoryNode> exportToObject() { // TODO Auto-generated method stub return null; } @Override public String exportToString() throws BSTException { StringBuilder sb = new StringBuilder(); for (StorySingleNodeEditor editor : list) { sb.append(((EditorControl<?>) editor).exportToString()); sb.append("\n\n"); } return sb.toString(); } @Override public void updateUI() { super.updateUI(); if (list != null) for (StorySingleNodeEditor ssne : list) ssne.updateUI(); } public void refreshList() { jlist.repaint(); } public void checkAllAvailable() { ArrayList<StorySingleNodeEditor> seen = new ArrayList<>(); // Because the algorithm used can mark errors twice we use a set to ensure // it only contains each errored element once LinkedHashSet<StorySingleNodeEditor> toMarkAsError = new LinkedHashSet<>(); for (StorySingleNodeEditor ssne : list) { for (StorySingleNodeEditor other : seen) if (other.matchesId(ssne)) { toMarkAsError.add(ssne); toMarkAsError.add(other); break; } seen.add(ssne); } // Turn "seen" into the "everything is ok" list by removing all the errors seen.removeAll(toMarkAsError); seen.forEach(ssne -> { ssne.setStatus(Status.OK); ssne.getId().notifyOk(); }); toMarkAsError.forEach(ssne -> { ssne.setStatus(Status.ERROR); ssne.getId().notifyError(); }); jlist.repaint(); } }