/* * Software Name : ATK * * Copyright (C) 2007 - 2012 France Télécom * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------------------------------------------------------------ * File Name : ComparatorFrame.java * * Created : 04/06/2009 * Author(s) : France Telecom */ package com.orange.atk.compUI; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.Observable; import java.util.Observer; import javax.imageio.ImageIO; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.ProgressMonitor; import javax.swing.UIManager; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.apache.log4j.xml.DOMConfigurator; import com.orange.atk.compModel.ComparaisonCouple; import com.orange.atk.compModel.DirectoryFileFilter; import com.orange.atk.compModel.ImageFileMask; import com.orange.atk.compModel.Mask; import com.orange.atk.compModel.Model; import com.orange.atk.compModel.ProgressListener; import com.orange.atk.platform.Platform; /** * This class uses a very simple, naive similarity algorithm to compare an image * with an other one. */ public class ComparatorFrame extends JFrame implements Observer, ProgressListener { /** * */ private static final long serialVersionUID = 1L; private static final String VERSION = "ScreenShot Comparator - Beta 2.1"; private static String basePath="src/images/"; private JPanel mainPanel; private JMenuItem jmiPrintToPDF; private JLabel jlbLeft; private JLabel jlbRight; private JButton jbRight; private JButton jbLeft; private JButton jbRightDifferent; private JButton jbLeftDifferent; private JButton jbPass; private JButton jbFail; private JLabel jlnumberTotalFail; private JLabel jlbCounter; private MyDisplayJAI dispLeft; private MyDisplayJAI dispRight; private MyJTextArea taComment; private MyJTextArea taRefScDesc; private JButton jbRecalcul; private ProgressMonitor progressBar; //Variables used for the mask (for event click) private Date maskTimeLastEvent; private int maskLastX; private int maskLastY; //Variables used for the mask (for event drop) private int moussePressedX; private int moussePressedY; private int maskLeftX; private int maskTopY; private int maskRightX; private int maskBottomY; private int activeCell; private ListMask listMask; private JScrollPane scrollPaneMask; Box topBox; private static Color colorOk = new Color(100,200,50);//green; private static Color colorKo = new Color(250,150,50);//orange; private MyJTextArea taSumary; private JMenuItem jmiChangeDir; private JMenuItem jmiAbout; private JMenu jmZoom; private int currentIndex=0; private ArrayList<ComparaisonCouple> couples; private Model model; private JLabel jlbMask; private Double zoom = 1.0; private int zoomLevel = Mask.getCELL_HALF_SIZE(); private int taCommentDefaultWidth = 0; private int taRefScDescDefaultWidth = 0; private ComparatorFrame comp; /** * The constructor, which creates the GUI. * @param model */ public ComparatorFrame(Model model) throws IOException { super("ScreenShot Comparator"); UIManager.put("OptionPane.cancelButtonText", "Close"); UIManager.put("ProgressMonitor.progressText", "Comparison progress ..."); comp = this; // If comparison succeeds for all images, print report and exit this.model = model; couples=model.getCouplesComparaison(); if (model.getNbFail()==0){ int ret= JOptionPane.showConfirmDialog(this, "All screenshot are similar.\nDo you want to print the PDF report?", "Report", JOptionPane.OK_CANCEL_OPTION); if (ret==JOptionPane.OK_OPTION){ printReport(); } //return; } //Initialize the variables maskTimeLastEvent = new Date(); maskLastX = -1; maskLastY = -1; maskLeftX = -1; maskTopY = -1; maskRightX = -1; maskBottomY = -1; activeCell = -1; //------------ Creates ScreenshotComparator UI --------------// //MENU BAR // File Menu JMenuBar jmb =new JMenuBar(); JMenu jmFile= new JMenu("File"); jmiChangeDir =new JMenuItem("Change Image Directories"); jmiChangeDir.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { changeDirectories(); } }); jmFile.add(jmiChangeDir); jmFile.add(new JSeparator()); jmiPrintToPDF=new JMenuItem("Print Report in PDF "); jmiPrintToPDF.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { printReport(); } }); jmFile.add(jmiPrintToPDF); jmb.add(jmFile); // Zoom Menu jmZoom= new JMenu("Zoom"); JMenuItem jmiNoZoom = new JMenuItem("No Zoom"); jmiNoZoom.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { noZoom(); } }); jmZoom.add(jmiNoZoom); JMenuItem jmiZoomIn = new JMenuItem("Zoom In"); jmiZoomIn.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { zoomIn(); } }); jmZoom.add(jmiZoomIn); JMenuItem jmiZoomOut = new JMenuItem("Zoom Out"); jmiZoomOut.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { zoomOut(); } }); jmZoom.add(jmiZoomOut); jmb.add(jmZoom); // Help Menu JMenu jmHelp= new JMenu("Help"); jmiAbout = new JMenuItem("About ScreenShot Comparator ..."); jmiAbout.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { about(); } }); jmHelp.add(jmiAbout); jmb.add(Box.createHorizontalGlue()); jmb.add(jmHelp); this.setJMenuBar(jmb); // CONTENT PANEL mainPanel = new JPanel(); mainPanel.setLayout(new GridBagLayout()); //Default constraints //Top left with no insets or a 0.1 weight (few move on resizing) GridBagConstraints gbc = new GridBagConstraints( 0,0, //gridx, gridy 1,1, //gridwidth, gridheight 0,0, //weightx, weighty GridBagConstraints.CENTER, // anchor GridBagConstraints.NONE, // FILL new Insets(1,1,1,1), // padding top, left, bottom, right 0,0); //ipadx, ipady //-----------------------------------------------// // Information //-----------------------------------------------// topBox = Box.createHorizontalBox(); updateNumberTotalFail(); jlnumberTotalFail.setFont(new Font(null,1,20)); gbc.gridwidth=3; gbc.gridx=0; gbc.anchor=GridBagConstraints.CENTER; topBox.add(jlnumberTotalFail); topBox.setOpaque(true); topBox.setMinimumSize(getMaximumSize()); if(couples.get(currentIndex).isPass()){ topBox.setBackground(colorOk); } else{ topBox.setBackground(colorKo); } mainPanel.add(topBox,gbc); taSumary=new MyJTextArea(3,10); taSumary.setFont(new Font(null,1,15)); taSumary.setEnabled(false); taSumary.setBorder(BorderFactory.createLineBorder(Color.black)); taSumary.setBackground(Color.GRAY); taSumary.setText(model.getNbFail()+ " FAILED\n"+ (model.getNbImages()-model.getNbFail())+ " PASSED\n"+ model.getNbImages()+ " TOTAL"); gbc.gridx++; gbc.anchor=GridBagConstraints.EAST; mainPanel.add(taSumary,gbc); //------------------ 1ST LINE ------------------// // Contains labels for the images and the masks //-----------------------------------------------// gbc.gridy++; // Reference image labels Box jpTopL = Box.createVerticalBox(); JLabel jlbRef=new JLabel("Reference Screen Shot" ); jlbLeft=new JLabel(couples.get(currentIndex).getImgRefId()); jpTopL.add(jlbRef); jpTopL.add(jlbLeft); gbc.anchor=GridBagConstraints.CENTER; gbc.gridwidth=1; gbc.gridx=0; mainPanel.add(jpTopL, gbc); jlbCounter = new JLabel((currentIndex+1)+" / "+model.getNbImages()); jlbCounter.setFont(new Font(null,1,16)); gbc.gridwidth=3; gbc.gridx=0; mainPanel.add(jlbCounter, gbc); gbc.gridwidth=1; // Test image labels Box jpTopR = Box.createVerticalBox(); JLabel jlbTest=new JLabel("Test Screen Shot"); jlbRight=new JLabel(couples.get(currentIndex).getImgTest().getName()); jpTopR.add(jlbTest); jpTopR.add(jlbRight); gbc.anchor = GridBagConstraints.CENTER; gbc.gridx=2; mainPanel.add(jpTopR,gbc); // Mask labels Box jpTopM = Box.createVerticalBox(); jpTopM.add(new JLabel("List of mask") ); String imgRefId = couples.get(currentIndex).getImgRefId(); int numberOfMask = model.getRefImage(imgRefId).getMaskListId().size(); String message = "there "; message+=numberOfMask; if(numberOfMask<=1) message += " mask is currently selected"; else message += " masks are currently selected"; jlbMask = new JLabel(message); jpTopM.add(jlbMask); gbc.gridx=3; mainPanel.add(jpTopM, gbc); //------------------ 2ND LINE ------------------// // Contains the images and the list of mask //-----------------------------------------------// // Reference image dispLeft=new MyDisplayJAI(couples.get(currentIndex), model, false); dispLeft.setName("dispLeft"); dispLeft.setBorder(BorderFactory.createLineBorder(Color.black)); dispLeft.addListener(this); gbc.anchor=GridBagConstraints.CENTER; gbc.gridx=0; gbc.gridy++; mainPanel.add(dispLeft,gbc); // Test image dispRight=new MyDisplayJAI(couples.get(currentIndex), model,true); dispRight.setName("dispRight"); dispRight.setBorder(BorderFactory.createLineBorder(Color.black)); dispRight.addListener(this); gbc.insets=new Insets(0,0,0,5); gbc.gridx=2; mainPanel.add(dispRight,gbc); // List of masks listMask = new ListMask(model.getListMask(),this,model.getMaskWidth(),model.getMaskHeight()); //Now create a scrollpane; scrollPaneMask = new JScrollPane(); //Make the listBox with Checkboxes look like a rowheader. //This will place the component on the left corner of the scrollpane scrollPaneMask.setRowHeaderView(listMask.getListCheckBox()); //Now, make the listbox with actual descriptions as the main view scrollPaneMask.setViewportView(listMask.getListLabel()); scrollPaneMask.setPreferredSize(new Dimension(300, model.getImageHeight())); scrollPaneMask.setBorder(BorderFactory.createLineBorder(Color.black)); gbc.gridx=3; mainPanel.add(scrollPaneMask,gbc); //------------------ 3RD LINE ------------------// // Arrow buttons, number of images // and Combobox for mask //-----------------------------------------------// // Left arrow buttons Box jpButtonsLeft = Box.createHorizontalBox(); Box jpcolorInformation = Box.createVerticalBox(); int sizeIcon = 15; JLabel colorSelected = new JLabel("Mask selected"); BufferedImage imageSelected = new BufferedImage(sizeIcon,sizeIcon, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = (Graphics2D) imageSelected.getGraphics(); g2d.setColor(MyDisplayJAI.colorMaskSelected); g2d.fillRect(0,0,sizeIcon,sizeIcon); colorSelected.setIcon(new ImageIcon(imageSelected)); jpcolorInformation.add(colorSelected); JLabel colorMask = new JLabel("Sum of the masks"); BufferedImage imageSum = new BufferedImage(sizeIcon,sizeIcon, BufferedImage.TYPE_INT_ARGB); g2d = (Graphics2D) imageSum.getGraphics(); g2d.setColor(MyDisplayJAI.colorSumMask); g2d.fillRect(0,0,sizeIcon,sizeIcon); colorMask.setIcon(new ImageIcon(imageSum)); jpcolorInformation.add(colorMask); jpcolorInformation.setPreferredSize(new Dimension(150, 20)); jpButtonsLeft.add(jpcolorInformation); jbLeftDifferent = new JButton(new ImageIcon(new File("res/tango/back_red.png").toURI().toURL())); jbLeftDifferent.setName("jbleftdifferent"); jbLeftDifferent.setToolTipText("Go to the previous different images"); jbLeftDifferent.setPreferredSize(new Dimension(40,40)); jbLeftDifferent.setSize(40,40); jbLeftDifferent.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { previousDifferent(); } }); jpButtonsLeft.add(jbLeftDifferent); jbLeft = new JButton(new ImageIcon(new File("res/tango/back.png").toURI().toURL())); jbLeft.setName("jbleft"); jbLeft.setToolTipText("Go to the previous images"); jbLeft.setPreferredSize(new Dimension(40,40)); jbLeft.setSize(40,40); jbLeft.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { previous(); } }); jpButtonsLeft.add(jbLeft); gbc.gridx=0; gbc.gridy++; gbc.insets=new Insets(5,10,0,10); gbc.anchor=GridBagConstraints.CENTER; mainPanel.add(jpButtonsLeft,gbc); // Right arrow buttons Box jpButtonsRight = Box.createHorizontalBox(); jbRight = new JButton(new ImageIcon(new File("res/tango/forward.png").toURI().toURL())); jbRight.setName("jbright"); jbRight.setToolTipText("Go to the next images"); jbRight.setPreferredSize(new Dimension(40,40)); jbRight.setSize(40,40); jbRight.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { next(); } }); jpButtonsRight.add(jbRight); jbRightDifferent = new JButton(new ImageIcon(new File("res/tango/forward_red.png").toURI().toURL())); jbRightDifferent.setName("jbrightdifferent"); jbRightDifferent.setToolTipText("Go to the next different images"); jbRightDifferent.setPreferredSize(new Dimension(40,40)); jbRightDifferent.setSize(40,40); jbRightDifferent.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { nextDifferent(); } }); jpButtonsRight.add(jbRightDifferent); JLabel colorSimilar = new JLabel("Similar part"); BufferedImage imageSimilar = new BufferedImage(sizeIcon+20,sizeIcon, BufferedImage.TYPE_INT_ARGB); g2d = (Graphics2D) imageSimilar.getGraphics(); g2d.setColor(MyDisplayJAI.colorSimilarPart); g2d.fillRect(20,0,sizeIcon,sizeIcon); colorSimilar.setIcon(new ImageIcon(imageSimilar)); jpButtonsRight.add(colorSimilar); gbc.anchor=GridBagConstraints.WEST; gbc.gridx=2; mainPanel.add(jpButtonsRight,gbc); // Mask : Button ADD JButton addMask = new JButton("Add a mask"); addMask.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { addMaskAction(); } }); // Mask : Button REMOVE gbc.gridx=3; JButton removeMask = new JButton("Remove a mask"); removeMask.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { removeMaskAction(); } }); // Mask : Inverse the mask JButton inverseMask = new JButton("Inverse a mask"); inverseMask.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { inverseMaskAction(); } }); Box jpMaskButton = Box.createHorizontalBox(); jpMaskButton.add(addMask); jpMaskButton.add(removeMask); jpMaskButton.add(inverseMask); gbc.gridx=3; gbc.anchor=GridBagConstraints.CENTER; mainPanel.add(jpMaskButton,gbc); //------------------ 4TH LINE ------------------// // Action buttons //-----------------------------------------------// gbc.gridy++; //Action buttons : Pass jbPass=new JButton("Pass"); jbPass.setName("jbPass"); jbPass.setSize(new Dimension(100, 50)); jbPass.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { pass(); } }); gbc.gridx=0; gbc.anchor=GridBagConstraints.EAST; mainPanel.add(jbPass,gbc); //Action buttons : Fail jbFail= new JButton("Fail"); jbFail.setName("jbFail"); jbFail.setSize(new Dimension(100, 50)); jbFail.setToolTipText("Please Insert Comment Before Click Fail"); jbFail.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fail(); } }); gbc.gridx=2; gbc.anchor=GridBagConstraints.WEST; mainPanel.add(jbFail,gbc); //Action buttons : Recompute differences jbRecalcul=new JButton("Recompute differences"); jbRecalcul.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { if (progressBar!=null) progressBar.close(); progressBar = null; progressBar = new ProgressMonitor(comp, null,"",0,101); progressBar.setMillisToPopup(0); jbRecalcul.setEnabled(false); pack(); Thread t = new Thread() { public void run() { recompute(); } }; t.start(); } }); gbc.gridx=3; gbc.anchor=GridBagConstraints.CENTER; mainPanel.add(jbRecalcul,gbc); //-------------- 6TH and 7TH LINE --------------// // Comments //-----------------------------------------------// JLabel refScTitle = new JLabel("Ref. screenshot description:"); gbc.gridx=0; gbc.gridy++; gbc.gridwidth=1; gbc.anchor=GridBagConstraints.WEST; mainPanel.add(refScTitle,gbc); taRefScDesc=new MyJTextArea(); taRefScDesc.setRows(4); taRefScDesc.setVisible(true); taRefScDesc.setEditable(true); taRefScDesc.setLineWrap(true); CompoundBorder innerCompound = new CompoundBorder(new EmptyBorder(3, 3, 3, 3), new EmptyBorder(0,0,0,0)); CompoundBorder outerCompound = new CompoundBorder(new LineBorder(Color.DARK_GRAY, 1), innerCompound); taRefScDesc.setBorder(outerCompound); gbc.gridy++; gbc.insets=new Insets(3,3,3,3); gbc.fill=GridBagConstraints.BOTH; mainPanel.add(taRefScDesc,gbc); taRefScDesc.addFocusListener(new FocusListener() { public void focusGained(FocusEvent e) { } public void focusLost(FocusEvent e) { refScDescChanged(); } }); JLabel commentTitle = new JLabel("Comment about Failed test screenshot:"); gbc.gridx=1; gbc.gridy--; gbc.gridwidth=3; mainPanel.add(commentTitle,gbc); taComment=new MyJTextArea(); taComment.setRows(4); taComment.setVisible(true); taComment.setEditable(true); taComment.setLineWrap(true); taComment.setBorder(outerCompound); if(couples.get(currentIndex).isPass()){ taComment.setEnabled(false); taComment.setBackground(Color.GRAY); } else{ taComment.setEnabled(true); taComment.setBackground(Color.WHITE); } taComment.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent arg0) { //commentChanged(); } public void insertUpdate(DocumentEvent arg0) { commentChanged(); } public void removeUpdate(DocumentEvent arg0) { commentChanged(); } }); gbc.gridy++; gbc.insets=new Insets(3,3,3,3); gbc.fill=GridBagConstraints.BOTH; mainPanel.add(taComment,gbc); // Add Scrollpanel setContentPane(new JScrollPane(mainPanel)); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); pack(); setVisible(true); update(this.getGraphics()); } public void setProgressValue(int value) { progressBar.setProgress(value); if (value==100) { jbRecalcul.setEnabled(true); } } public void setNbFailed(int value) { progressBar.setNote(value+" / "+model.getNbImages()+" image(s) failed"); } /************************************** MENU ACTIONS ********************************************/ /****************** File Menu ********************/ // "Change Image Directories" Menu Item action protected void changeDirectories() { ChangeDirectoriesDialog cdd = new ChangeDirectoriesDialog(null, model); if(cdd.getAction()==JOptionPane.OK_OPTION){ model.setDirectories(cdd.getRefPath(), cdd.getTestPath()); } } // "Print PDF Report" Menu Item action protected void printReport() { //First we save the report model.saveReport(); //Then we print it in PDF model.printPDFReport(); int ret= JOptionPane.showConfirmDialog(this, "The PDF report has been printed. Click OK and it will be open." , "PDF printed", JOptionPane.OK_CANCEL_OPTION); if (ret==JOptionPane.OK_OPTION){ try { java.awt.Desktop.getDesktop().open(new File(model.getTestDirectory()+Platform.FILE_SEPARATOR+model.getPdfReportName())); if (model.getNbFail()<=0){ this.setVisible(false); this.dispose(); } } catch (IOException e) { e.printStackTrace(); } } } /****************** Zoom Menu ********************/ protected void noZoom() { zoom = 1.0; zoomLevel = Mask.getCELL_HALF_SIZE(); zoomUI(zoom); jmZoom.setText("Zoom"); zoomTA(zoom); update(this.getGraphics()); pack(); } protected void zoomIn() { if ((zoomLevel + 1) < 2 * Mask.getCELL_HALF_SIZE()) { zoomLevel++; zoom = (double) zoomLevel / (double) Mask.getCELL_HALF_SIZE(); zoomUI(zoom); if (!zoom.equals(1.0)) jmZoom.setText("Zoom ["+ (int)(100*zoom) + "%]"); else jmZoom.setText("Zoom"); zoomTA(zoom); update(this.getGraphics()); pack(); } } protected void zoomOut() { if ((zoomLevel - 1) > 0) { zoomLevel--; zoom = (double) zoomLevel / (double) Mask.getCELL_HALF_SIZE(); zoomUI(zoom); if (!zoom.equals(1.0)) jmZoom.setText("Zoom ["+ (int)(100*zoom) + "%]"); else jmZoom.setText("Zoom"); zoomTA(zoom); update(this.getGraphics()); pack(); } } private void zoomTA(double zoom) { if (this.taCommentDefaultWidth==0) { taCommentDefaultWidth = taComment.getWidth(); taRefScDescDefaultWidth = taRefScDesc.getWidth(); } //taRefScDesc.setSize((int) (taRefScDescDefaultWidth*zoom), taRefScDesc.getHeight()); Dimension taRefScDescDim = new Dimension((int) (taRefScDescDefaultWidth*zoom), taRefScDesc.getHeight()); taRefScDesc.setPreferredSize(taRefScDescDim); taRefScDesc.setMaximumSize(taRefScDescDim); Dimension taCommentDim = new Dimension((int) (taCommentDefaultWidth*zoom), taComment.getHeight()); taComment.setPreferredSize(taCommentDim); taComment.setMaximumSize(taCommentDim); } private void zoomUI(double zoom) { dispLeft.setZoom(zoom); dispRight.setZoom(zoom); scrollPaneMask.setPreferredSize(new Dimension(300, (int) (model.getImageHeight()*zoom))); } /****************** Help Menu ********************/ protected void about() { new AboutDialog(this,VERSION); } /** * called when the Pass Button is pressed */ protected void pass() { couples.get(currentIndex).setComment( taComment.getText()); couples.get(currentIndex).setPass(Model.MANUALLY_PASS); model.saveReport(); next(); } /** * called when the Fail Button is pressed */ protected void fail() { couples.get(currentIndex).setComment( taComment.getText()); couples.get(currentIndex).setPass(Model.FAIL); model.saveReport(); next(); } /** * called when the previous button is pressed */ protected void previous() { if (currentIndex>0) currentIndex--; update(this.getGraphics()); } /** * called when the previousDifferent button is pressed */ protected void previousDifferent() { int previousIndex = currentIndex; while (previousIndex>0){ previousIndex--; if(!couples.get(previousIndex).isPass()){ currentIndex = previousIndex; update(this.getGraphics()); return; } } return; } /** * called when Next, Pass of Fail Button is pressed. * Goes to the next couple of image */ protected void next() { if(currentIndex<getBiggestIndex()) currentIndex++; update(this.getGraphics()); } /** * called when NextDifferent button is pressed. * Goes the next different couple */ protected void nextDifferent() { int nextIndex = currentIndex; while (nextIndex<getBiggestIndex()){ nextIndex++; if(!couples.get(nextIndex).isPass()){ currentIndex = nextIndex; update(this.getGraphics()); return; } } return; } /** * Action to add a mask when the button is clicked */ private void addMaskAction() { String name =JOptionPane.showInputDialog( "Please, type of the name of the new mask"); if(name==null) return; Mask mask = createMask(name); Object[] options = {"Set only to the current images", "Set for all"}; int option =JOptionPane.showOptionDialog(null, "Do you want to set the new mask to all " + "or only the current images?", "Set to all masks?", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); if(option == 0) getCurrentImgRef().addMask(mask.getId()); else{ for(String image : model.getListRefImage()) model.getRefImage(image).addMask(mask.getId()); } update(this.getGraphics()); } /** * Create a mask and add it in the list * @param label * @return new mask */ private Mask createMask(String label) { Integer nextId = 0; for(Integer Id : model.getListKeysetMask()){ if(nextId<Id) nextId = Id; } nextId++; Mask mask = new Mask(label,nextId,listMask.getMaskWidth(),listMask.getMaskHeight()); model.addRefMask(mask); listMask.add(mask); listMask.setSelectedMask(mask); return mask; } /** * Action to remove a mask when the button is clicked */ private void removeMaskAction() { Mask mask = listMask.getSelectedMask(); if(mask == null){ JOptionPane.showMessageDialog(null, "You must select a mask from the list."); return; } int value = JOptionPane.showConfirmDialog(null, "Do you really want to delete the mask \""+mask+"\" ?", "Delete the mask?", JOptionPane.OK_CANCEL_OPTION); if(value == JOptionPane.OK_OPTION){ //We remove the mask for every image for(String image : model.getListRefImage()) model.getRefImage(image).removeMask(mask.getId()); //We remove the mask from the list of reference mask model.removeRefMask(mask); //We remove the mask from the UI with the list of masks listMask.removeMask(mask); scrollPaneMask.repaint(); model.saveMaskAssociations(); } } /** * Inverse a mask */ private void inverseMaskAction() { Mask mask = listMask.getSelectedMask(); if(mask == null){ JOptionPane.showMessageDialog(null, "You must select a mask from the list."); return; } for(int x=0;x<listMask.getMaskWidth();x++){ for(int y=0;y<listMask.getMaskHeight();y++){ mask.setCell(x, y, !mask.getCell(x, y)); } } model.saveMaskAssociations(); scrollPaneMask.repaint(); update(this.getGraphics()); } /** * Return the biggest possible index for image */ int getBiggestIndex(){ return (model.getNbImages()-1); } /** * called on changing in taComment */ protected void commentChanged() { couples.get(currentIndex).setComment(taComment.getText()); } /** * called on changing in refScDescComment */ protected void refScDescChanged() { model.setRefScDescription(couples.get(currentIndex).getImgRefId(), taRefScDesc.getText()); } /** * Add/remove a cell into/from the mask */ protected void addZone(MouseEvent arg0) { Mask activeMask = (Mask)listMask.getListLabel().getSelectedValue(); int x = moussePressedX; int y = moussePressedY; Date newDate = new Date(); //We check the same square already get an event in the last 1000ms //if it's the case, it will be ignored if((x==maskLastX)&&(y==maskLastY)){ if((newDate.getTime()-maskTimeLastEvent.getTime())<1000){ maskTimeLastEvent = new Date(); return; } } activeMask.setCell(x, y, !activeMask.getCell(x, y)); update(this.getGraphics()); model.saveMaskAssociations(); //Update the values maskTimeLastEvent = new Date(); maskLastX = x; maskLastY = y; } /** * Add/remove a zone which is dragged into/from the mask */ protected void addZoneDragged(MouseEvent arg0) { Mask activeMask = (Mask)listMask.getListLabel().getSelectedValue(); int x0 = arg0.getX()/((int)(2*Mask.getCELL_HALF_SIZE()*zoom)); int y0 = arg0.getY()/((int)(2*Mask.getCELL_HALF_SIZE()*zoom)); Boolean valueCell = false; if(activeCell==1){ valueCell = true; } //We clear the zone first (in case the zone is reduced for(int x=maskLeftX; x<=maskRightX; x++){ for(int y=maskTopY; y<=maskBottomY; y++){ if((y<model.getMaskHeight())&&(x<model.getMaskWidth())) activeMask.setCell(x, y, !valueCell); } } if(moussePressedX < x0){ maskLeftX = moussePressedX; maskRightX = x0; }else{ maskLeftX = x0; maskRightX = moussePressedX; } if(moussePressedY < y0){ maskTopY = moussePressedY; maskBottomY = y0; }else{ maskTopY = y0; maskBottomY = moussePressedY; } for(int x=maskLeftX; x<=maskRightX; x++){ for(int y=maskTopY; y<=maskBottomY; y++){ if((y<model.getMaskHeight())&&(x<model.getMaskWidth())) activeMask.setCell(x, y, valueCell); } } update(this.getGraphics()); model.saveMaskAssociations(); //Update the values maskTimeLastEvent = new Date(); maskLastX = x0; maskLastY = y0; } /** * Add/remove a line into/from the mask */ protected void addLine(MouseEvent arg0) { Mask activeMask = (Mask)listMask.getListLabel().getSelectedValue(); int x0 = moussePressedX; int y = moussePressedY; Boolean active = !activeMask.getCell(x0, y); Date newDate = new Date(); //For the doubleclick, we get an event with a click before //we need to inverse the value to ignore the first click if((x0==maskLastX)&&(y==maskLastY)){ if((newDate.getTime()-maskTimeLastEvent.getTime())<500){ maskTimeLastEvent = new Date(); active = !active; } } for(int x = 0; x< listMask.getMaskWidth(); x++) activeMask.setCell(x, y, active); update(this.getGraphics()); model.saveMaskAssociations(); } /** * Function called from when the mouse is pressed on an image * @param arg0 */ public void mousePressed(MouseEvent arg0) { moussePressedX = arg0.getX()/((int)(2*Mask.getCELL_HALF_SIZE()*zoom)); moussePressedY = arg0.getY()/((int)(2*Mask.getCELL_HALF_SIZE()*zoom)); maskLeftX = moussePressedX; maskTopY = moussePressedY; maskRightX = maskLeftX; maskBottomY = maskTopY; Mask activeMask = (Mask)listMask.getListLabel().getSelectedValue(); if(activeMask == null){ addMaskAction(); return; }else{ //We check the box (to be sure the selected mask is selected for this image) listMask.checkSelectedMask(activeMask); if(!activeMask.getCell(maskLeftX,maskTopY)) activeCell = 1; else activeCell = 0; } } /** * Function called from when the mouse is released on an image * @param arg0 */ public void mouseReleased(MouseEvent arg0) { maskLeftX = -1; maskTopY = -1; maskRightX = -1; maskBottomY = -1; } /** * Show information about the cell (give the list of masks which contain the cell * @param arg0 * @param display */ void showInfoZone(MouseEvent arg0, MyDisplayJAI display) { int x = moussePressedX; int y = moussePressedY; String message; if(!couples.get(currentIndex).getMaskSum().getCell(x, y)) message = "The cell x="+x+" - y="+y+" isn't in any mask."; else{ ArrayList<Integer> listMaskId = couples.get(currentIndex).getMaskList(); message = "The cell x="+x+" - y="+y+" is in "; String listMask = ""; int numberMask = 0; for(Integer Id : listMaskId){ if(listMaskId.contains(Id)){ if(model.getRefMask(Id).getCell(x, y)){ listMask+=" - "+model.getRefMask(Id)+"\n"; numberMask++; } } } message +=numberMask; if(numberMask==1) message+=" mask :\n"+listMask; else message+=" masks :\n"+listMask; } JOptionPane.showMessageDialog(display, message); } protected void recompute() { int nbFailed=0; nbFailed= model.recomputeAll(); this.setNbFailed(nbFailed); update(this.getGraphics()); } /** * Update the main line */ private void updateNumberTotalFail() { String numberTotalFail; // Image index/total number numberTotalFail = "Screenshot "+(currentIndex+1)+" "; // Result numberTotalFail += couples.get(currentIndex).getPass(); if(null==jlnumberTotalFail) jlnumberTotalFail=new JLabel(numberTotalFail); else jlnumberTotalFail.setText(numberTotalFail); } /** * This method update the UI */ @Override public void update(Graphics arg0) { try { updateNumberTotalFail(); couples.get(currentIndex).updateDifWithMask(); jlbCounter.setText((currentIndex+1)+" / "+model.getNbImages()); jlbLeft.setText(couples.get(currentIndex).getImgRefId()); BufferedImage imageL=(BufferedImage) ImageIO.read(model.getRefImage(couples.get(currentIndex).getImgRefId()).getImage()); dispLeft.set(imageL, couples.get(currentIndex) ); dispLeft.setActiveMask(listMask.getSelectedMask()); dispLeft.setSize(imageL.getWidth(),imageL.getHeight()); dispLeft.repaint(); jlbRight.setText(couples.get(currentIndex).getImgTest().getName()); BufferedImage imageR=(BufferedImage) ImageIO.read(couples.get(currentIndex).getImgTest()); dispRight.set(imageR, couples.get(currentIndex)); dispRight.setSize(imageR.getWidth(),imageR.getHeight()); dispRight.repaint(); String imgRefId = couples.get(currentIndex).getImgRefId(); int numberOfMask = model.getRefImage(imgRefId).getMaskListId().size(); String message = ""+numberOfMask; if(numberOfMask<=1) message += " mask is currently selected"; else message += " masks are currently selected"; jlbMask.setText(message); listMask.setListCheckBox(couples.get(currentIndex).getImgRef().getMaskListId()); taSumary.setText(model.getNbFail()+ " FAILED\n"+ (model.getNbImages()-model.getNbFail())+ " PASSED\n"+ model.getNbImages()+ " TOTAL"); taRefScDesc.setText(model.getRefScDescription(couples.get(currentIndex).getImgRefId())); taComment.setText(couples.get(currentIndex).getComment()); if(couples.get(currentIndex).isPass()){ //resultCompare.setIcon(new ImageIcon("res/tango/ok.png")); taComment.setEnabled(false); taComment.setBackground(Color.GRAY); topBox.setBackground(colorOk); } else{ //resultCompare.setIcon(new ImageIcon("res/tango/ko.png")); taComment.setEnabled(true); taComment.setBackground(Color.WHITE); topBox.setBackground(colorKo); } } catch (IOException e) { e.printStackTrace(); } } public ImageFileMask getCurrentImgRef() { return model.getRefImage(couples.get(currentIndex).getImgRefId()); } public Model getModel() { return model; } public void update(Observable arg0, Object arg1) { update(this.getGraphics()); } /** * The entry point for the application, which opens a file with an image that * will be used as reference and starts the application. */ public static void main(String[] args) throws Exception { DOMConfigurator.configure("log4j.xml"); JFileChooser fc = new JFileChooser(basePath); fc.setFileFilter(new DirectoryFileFilter()); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int res = fc.showDialog(null,"Open as reference directory"); // We have an image! if (res == JFileChooser.APPROVE_OPTION) { File fileRef = fc.getSelectedFile(); res =fc.showDialog(null,"Open as test directory"); if (res == JFileChooser.APPROVE_OPTION) { File fileTest=fc.getSelectedFile(); Model model =new Model(fileRef.getPath(), fileTest.getPath()); new ComparatorFrame(model); } } // Oops! else { JOptionPane.showMessageDialog(null, "You must select one directory to be the reference.", "Aborting...", JOptionPane.WARNING_MESSAGE); } } }