$darkmode
DENOPTIM
VertexAsTwoDimStructureViewPanel.java
Go to the documentation of this file.
1/*
2 * DENOPTIM
3 * Copyright (C) 2020 Marco Foscato <marco.foscato@uib.no>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package denoptim.gui;
20
21import java.awt.BorderLayout;
22import java.awt.Color;
23import java.awt.Dimension;
24import java.awt.Graphics;
25import java.awt.Graphics2D;
26import java.awt.Rectangle;
27import java.awt.event.MouseEvent;
28import java.awt.event.MouseListener;
29import java.awt.event.MouseMotionListener;
30import java.awt.event.MouseWheelEvent;
31import java.awt.event.MouseWheelListener;
32import java.awt.geom.AffineTransform;
33import java.awt.geom.Point2D;
34import java.util.ArrayList;
35import java.util.HashMap;
36import java.util.List;
37import java.util.Map;
38
39import javax.swing.JLabel;
40import javax.swing.JPanel;
41import javax.swing.JScrollPane;
42import javax.swing.JSplitPane;
43import javax.swing.JTable;
44import javax.swing.event.TableModelEvent;
45import javax.swing.event.TableModelListener;
46import javax.swing.table.DefaultTableModel;
47import javax.swing.table.JTableHeader;
48
49import org.openscience.cdk.AtomRef;
50import org.openscience.cdk.PseudoAtom;
51import org.openscience.cdk.interfaces.IAtom;
52import org.openscience.cdk.interfaces.IAtomContainer;
53import org.openscience.cdk.interfaces.IBond;
54import org.openscience.cdk.layout.StructureDiagramGenerator;
55import org.openscience.cdk.renderer.AtomContainerRenderer;
56import org.openscience.cdk.renderer.RendererModel;
57import org.openscience.cdk.renderer.color.IAtomColorer;
58import org.openscience.cdk.renderer.font.AWTFontManager;
59import org.openscience.cdk.renderer.generators.BasicAtomGenerator;
60import org.openscience.cdk.renderer.generators.BasicBondGenerator;
61import org.openscience.cdk.renderer.generators.BasicSceneGenerator;
62import org.openscience.cdk.renderer.generators.IGenerator;
63import org.openscience.cdk.renderer.visitor.AWTDrawVisitor;
64
65import denoptim.graph.AttachmentPoint;
66import denoptim.graph.EmptyVertex;
67import denoptim.graph.Fragment;
68import denoptim.graph.Vertex;
69import denoptim.utils.MoleculeUtils;
70
71
79public class VertexAsTwoDimStructureViewPanel extends JSplitPane
80 implements IVertexAPSelection
81{
85 private static final long serialVersionUID = 1L;
86
90 private Vertex vertex;
91
95 protected Map<Integer, AttachmentPoint> mapAPs = null;
96
100 public boolean alteredAPData = false;
101
102 private JScrollPane viewPanel;
103 private JPanel twoDimView;
104 private JScrollPane tabPanel;
105 protected DefaultTableModel apTabModel;
106 protected JTable apTable;
107
108 // This is a global property of this instance
109 private boolean editableAPTable = false;
110 // this is vertex-specific, it does not overwrite editableAPTable, which is
111 // more general. The overall editablility is given by general && local.
112 private boolean vertexSpecificAPTabEditable = true;
113
114
115//-----------------------------------------------------------------------------
116
120 class TwoDimStructurePanel extends JPanel implements MouseWheelListener,
121 MouseListener, MouseMotionListener
122 {
126 private static final long serialVersionUID = 1L;
127
128 IAtomContainer mol;
129 AtomContainerRenderer renderer;
130
131 private double zoom = 1;
132 private boolean zoomingOrPanning = false;
133 AffineTransform initialTransform;
134 AffineTransform at;
138 double translateX = 0;
139 double translateY = 0;
140
142 {
143 StructureDiagramGenerator sdg = new StructureDiagramGenerator();
144 try
145 {
146 IAtomContainer extendedMol = MoleculeUtils.makeSameAs(
148 int apId = 0;
149 int numAtoms = extendedMol.getAtomCount();
151 {
152 extendedMol.addAtom(new PseudoAtom(""+(apId+1)));
153 extendedMol.addBond(ap.getAtomPositionNumber(),
154 apId+numAtoms, IBond.Order.SINGLE);
155 apId++;
156 }
157
158 sdg.setMolecule(extendedMol);
159 sdg.generateCoordinates();
160 } catch (Exception e)
161 {
162 e.printStackTrace();
163 renderer = null;
164 return;
165 }
166 this.mol = sdg.getMolecule();
167
168 // These generate the graphical components
169 List<IGenerator<IAtomContainer>> generators =
170 new ArrayList<IGenerator<IAtomContainer>>();
171 generators.add(new BasicSceneGenerator());
172 generators.add(new BasicBondGenerator());
173 generators.add(new AtomOrAPGenerator());
174 renderer = new AtomContainerRenderer(generators, new AWTFontManager());
175
176 addMouseWheelListener(this);
177 addMouseListener(this);
178 addMouseMotionListener(this);
179 }
180
181 public void paint(Graphics graphics)
182 {
183 if (renderer==null)
184 return;
185
186 renderer.setup(mol, new Rectangle(getWidth(), getHeight()));
187
188 // paint the background
189 graphics.setColor(Color.WHITE);
190 graphics.fillRect(0, 0, getWidth(), getHeight());
191
192 Graphics2D g2d = (Graphics2D) graphics;
193
194 AffineTransform saveTransform = g2d.getTransform();
195 at = new AffineTransform(saveTransform);
197 {
198 at.translate(getWidth()/2, getHeight()/2);
199 at.scale(zoom,zoom);
200 at.translate(-getWidth()/2, -getHeight()/2);
201 at.translate(translateX, translateY);
202
203 g2d.setTransform(at);
204 }
205
206 renderer.paint(mol, new AWTDrawVisitor(g2d));
207
209 {
210 // Is this needed? yes!
211 g2d.setTransform(saveTransform);
212 zoomingOrPanning = false;
213 }
214 }
215
216 @Override
217 public void mouseWheelMoved(MouseWheelEvent e)
218 {
219 if (e.getWheelRotation() < 0) {
220 zoom *= 1.1;
221 }
222 if (e.getWheelRotation() > 0) {
223 zoom /= 1.1;
224 }
225 zoomingOrPanning = true;
226 repaint();
227 }
228
229 @Override
230 public void mousePressed(MouseEvent e)
231 {
232 // first transform the mouse point to the pan and zoom
233 // coordinates
234 try {
235 XFormedPoint = at.inverseTransform(e.getPoint(), null);
236 }
237 catch (Exception te) {
238 te.printStackTrace();
239 }
240
241 // save the transformed starting point and the initial
242 // transform
243 referenceX = XFormedPoint.getX();
244 referenceY = XFormedPoint.getY();
246 }
247
248 @Override
249 public void mouseDragged(MouseEvent e)
250 {
251 try {
252 XFormedPoint = initialTransform.inverseTransform(e.getPoint(),
253 null);
254 }
255 catch (Exception te) {
256 te.printStackTrace();
257 }
258 double deltaX = XFormedPoint.getX() - referenceX;
259 double deltaY = XFormedPoint.getY() - referenceY;
260 referenceX = XFormedPoint.getX();
261 referenceY = XFormedPoint.getY();
262
263 translateX += deltaX;
264 translateY += deltaY;
265
266 // schedule a repaint.
267 zoomingOrPanning = true;
268 repaint();
269 }
270
271 @Override
272 public void mouseClicked(MouseEvent e) {}
273 @Override
274 public void mouseReleased(MouseEvent e) {}
275 @Override
276 public void mouseEntered(MouseEvent e) {}
277 @Override
278 public void mouseExited(MouseEvent e) {}
279 @Override
280 public void mouseMoved(MouseEvent e) {}
281 }
282
283//-----------------------------------------------------------------------------
284
285 class AtomOrAPGenerator extends BasicAtomGenerator
286 {
292 @Override
293 protected Color getAtomColor(IAtom atom, RendererModel model)
294 {
295 Color atomColor = model.get(AtomColor.class);
296 if ((Boolean) model.get(ColorByType.class))
297 {
298 boolean changeColor = false;
299 IAtom a = null;
300 if (atom instanceof AtomRef)
301 {
302 a = ((AtomRef) atom).deref();
303 if (a instanceof PseudoAtom)
304 changeColor = true;
305 } else if (atom instanceof PseudoAtom)
306 {
307 changeColor = true;
308 }
309 if (changeColor)
310 {
311 atomColor = new Color(0xFF00FF);
312 } else {
313 atomColor = ((IAtomColorer) model.get(AtomColorer.class))
314 .getAtomColor(atom);
315 }
316 }
317 return atomColor;
318 }
319 }
320
321//-----------------------------------------------------------------------------
322
328 public VertexAsTwoDimStructureViewPanel(boolean editableTable)
329 {
330 this(editableTable, 340);
331 }
332
333//-----------------------------------------------------------------------------
334
342 public VertexAsTwoDimStructureViewPanel(boolean editableTable,
343 int dividerPosition)
344 {
345 editableAPTable = editableTable;
346 initialize(dividerPosition);
347 }
348
349//-----------------------------------------------------------------------------
350
351 @SuppressWarnings("serial")
352 private void initialize(int dividerPosition)
353 {
354 this.setOrientation(JSplitPane.VERTICAL_SPLIT);
355 this.setOneTouchExpandable(true);
356 this.setDividerLocation(dividerPosition);
357 this.setResizeWeight(0.5);
358
359 // 2D mol Viewer
360 clearPanel();
361
362 // List of attachment points
363 apTabModel = new DefaultTableModel() {
364 @Override
365 public boolean isCellEditable(int row, int column) {
366 if (column == 0)
367 {
368 return false;
369 }
370 else
371 {
373 }
374 }
375 };
376 apTabModel.setColumnCount(2);
377 String column_names[]= {"<html><b>AP#</b></html>",
378 "<html><b>APClass</b></html>"};
379 apTabModel.setColumnIdentifiers(column_names);
380 apTable = new JTable(apTabModel);
381 apTable.putClientProperty("terminateEditOnFocusLost", true);
382 apTable.getColumnModel().getColumn(0).setMaxWidth(75);
383 apTable.setGridColor(Color.LIGHT_GRAY);
384 apTable.setPreferredScrollableViewportSize(apTable.getPreferredSize());
385 JTableHeader apTabHeader = apTable.getTableHeader();
386 apTabHeader.setPreferredSize(new Dimension(100, 20));
387 apTabModel.addTableModelListener(new PausableTableModelListener());
388 tabPanel = new JScrollPane(apTable);
389 this.setBottomComponent(tabPanel);
390 }
391
392//-----------------------------------------------------------------------------
393
394 public void setVertexSpecificEditableAPTable(boolean editable)
395 {
397 }
398
399//-----------------------------------------------------------------------------
400
402 {
404 clearAPTable();
405 vertex = v;
408 preSelectAPs();
409 }
410
411//-----------------------------------------------------------------------------
412
413 /*
414 * Structure here means the single-node-graph-like visual depiction of the
415 */
416 private void loadVertexStructure()
417 {
418 if ((vertex instanceof EmptyVertex)
419 || (vertex instanceof Fragment))
420 {
421 clearPanel();
423 }
424 }
425
426//-----------------------------------------------------------------------------
427
432 private void clearPanel()
433 {
434 twoDimView = new JPanel(new BorderLayout());
435 twoDimView.add(new JLabel("Not available"));
436 viewPanel = new JScrollPane(twoDimView);
437 this.setTopComponent(viewPanel);
438 }
439
440//-----------------------------------------------------------------------------
441
446 public boolean hasUnsavedAPEdits()
447 {
448 return alteredAPData;
449 }
450
451//-----------------------------------------------------------------------------
452
457 public void deprotectEdits()
458 {
459 alteredAPData = false;
460 }
461
462//-----------------------------------------------------------------------------
463
468 private void updateAPsMapAndTable()
469 {
470 clearAPTable();
471 mapAPs = new HashMap<Integer,AttachmentPoint>();
472
473 List<AttachmentPoint> lstAPs = vertex.getAttachmentPoints();
474 if (lstAPs.size() == 0)
475 {
476 return;
477 }
478
480 int arrId = 0;
481 for (AttachmentPoint ap : lstAPs)
482 {
483 arrId++;
484 apTabModel.addRow(new Object[]{arrId, ap.getAPClass()});
485 mapAPs.put(arrId,ap);
486 }
488 }
489
490//-----------------------------------------------------------------------------
491
492 private void preSelectAPs()
493 {
494 String PRESELPROP = GUIVertexSelector.PRESELECTEDAPSFIELD;
495 String PRESELPROPSEP = GUIVertexSelector.PRESELECTEDAPSFIELDSEP;
496
497 if (vertex.getProperty(PRESELPROP) == null)
498 {
499 return;
500 }
501
502 String prop = vertex.getProperty(PRESELPROP).toString();
503 String[] parts =prop.split(PRESELPROPSEP);
504
506 for (int i=0; i<parts.length; i++)
507 {
508 int apId = Integer.parseInt(parts[i]); //0-based
509 apTable.getSelectionModel().addSelectionInterval(apId, apId);
510 }
512 }
513
514//-----------------------------------------------------------------------------
515
519 public void clearAll()
520 {
521 clearAPTable();
522 }
523
524//-----------------------------------------------------------------------------
525
529 public void clearAPTable()
530 {
532 int initRowCount = apTabModel.getRowCount();
533 for (int i=0; i<initRowCount; i++)
534 {
535 //Always remove the first to avoid dealing with changing row ids
536 apTabModel.removeRow(0);
537 }
539 }
540
541//-----------------------------------------------------------------------------
542
547 public ArrayList<AttachmentPoint> getSelectedAPs()
548 {
549 ArrayList<AttachmentPoint> selected =
550 new ArrayList<AttachmentPoint>();
551
552 for (int rowId : apTable.getSelectedRows())
553 {
554 selected.add(mapAPs.get(apTable.getValueAt(rowId, 0)));
555 }
556 return selected;
557 }
558
559//-----------------------------------------------------------------------------
560
565 public ArrayList<Integer> getSelectedAPIDs()
566 {
567 ArrayList<Integer> selected = new ArrayList<Integer>();
568 for (int rowId : apTable.getSelectedRows())
569 {
570 selected.add(rowId);
571 }
572 return selected;
573 }
574
575//-----------------------------------------------------------------------------
576
577 private class PausableTableModelListener implements TableModelListener
578 {
579 private boolean isActive = false;
580
582 {};
583
584 @Override
585 public void tableChanged(TableModelEvent e)
586 {
587 if (isActive && !alteredAPData
588 && e.getType() == TableModelEvent.UPDATE)
589 {
590 alteredAPData = true;
591 firePropertyChange(IVertexAPSelection.APDATACHANGEEVENT, false,
592 true);
593 }
594 }
595
596 public void setActive(boolean var)
597 {
598 isActive = var;
599 }
600 }
601
602//-----------------------------------------------------------------------------
603
608 public void activateTabEditsListener(boolean var)
609 {
610 try
611 {
613 apTabModel.getTableModelListeners()[0];
614 l.setActive(var);
615 } catch (Throwable t) {
616 System.out.println("Bad attempt to contro listener: "
617 + t.getMessage());
618 System.out.println(t.getCause());
619 }
620 }
621
622//-----------------------------------------------------------------------------
623
624 @Override
625 public Map<Integer, AttachmentPoint> getMapOfAPsInTable()
626 {
627 return mapAPs;
628 }
629
630//-----------------------------------------------------------------------------
631
632 @Override
633 public DefaultTableModel getAPTableModel()
634 {
635 return apTabModel;
636 }
637
638//-----------------------------------------------------------------------------
639
640}
An attachment point (AP) is a possibility to attach a Vertex onto the vertex holding the AP (i....
An empty vertex has the behaviors of a vertex, but has no molecular structure.
Class representing a continuously connected portion of chemical object holding attachment points.
Definition: Fragment.java:61
A vertex is a data structure that has an identity and holds a list of AttachmentPoints.
Definition: Vertex.java:61
abstract List< AttachmentPoint > getAttachmentPoints()
Object getProperty(Object property)
Definition: Vertex.java:1136
abstract IAtomContainer getIAtomContainer()
A modal dialog with a viewer that understands the different types of DENOPTIM vertex and allows to se...
static final String PRESELECTEDAPSFIELD
Property used to pre-select APs.
static final String PRESELECTEDAPSFIELDSEP
Separator in property used to pre-select APs.
Color getAtomColor(IAtom atom, RendererModel model)
Chooses default colors from CDK2DAtomColor for all but PseudoAtoms which we expect to be placeholder ...
Panel dealing with the painting of 2D chemical representation.
A panel to visualize a vertex as two-dimensional chemical structure with attachment point table.
boolean hasUnsavedAPEdits()
Check for unsaved edits to the AP data.
VertexAsTwoDimStructureViewPanel(boolean editableTable)
Constructor that allows to specify whether the AP table is editable or not.
void clearPanel()
trashed the current panel displaying any 2D structure and replaces it with a new empty panel.
VertexAsTwoDimStructureViewPanel(boolean editableTable, int dividerPosition)
Constructor that allows to specify whether the AP table is editable or not.
void clearAll()
Removes the currently visualized molecule and AP table.
ArrayList< Integer > getSelectedAPIDs()
Identifies which attachment points are selected in the visualized table.
void deprotectEdits()
Overrides the flag signaling unsaved edits to saying that there are no altered data.
ArrayList< AttachmentPoint > getSelectedAPs()
Identifies which attachment points are selected in the visualized table.
void activateTabEditsListener(boolean var)
Allows to activate and deactivate the listener.
void clearAPTable()
Clears the table of attachment points.
Map< Integer, AttachmentPoint > mapAPs
Temporary list of attachment points of the current fragment.
boolean alteredAPData
Flag signaling that data about APs has been changed in the GUI.
void updateAPsMapAndTable()
Uses the AP of the Fragment to create a new map and table of APs.
Utilities for molecule conversion.
static IAtomContainer makeSameAs(IAtomContainer mol)
Constructs a copy of an atom container, i.e., a molecule that reflects the one given in the input arg...
Interface for all vertex viewers that intend to allow selection of attachment points.