<commit_before>/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2006, University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.gui2;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.LinkedList;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.tree.TreePath;

import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.I18N;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.cloud.Cloud;
import edu.umd.cs.findbugs.cloud.Cloud.BugFilingStatus;
import edu.umd.cs.findbugs.util.LaunchBrowser;

/**
 * @author pugh
 */
public class CommentsArea {

	private JTextArea userCommentsText = new JTextArea();
	private JTextArea reportText = new JTextArea();

	private Color userCommentsTextUnenabledColor;

	private JComboBox designationComboBox;

	private ArrayList<String> designationKeys;

	private JButton fileBug; 

	LinkedList<String> prevCommentsList = new LinkedList<String>();

	final static private int prevCommentsMaxSize = 10;

	private JComboBox prevCommentsComboBox; 

	private boolean dontShowAnnotationConfirmation = false;

	private boolean changed;
	final MainFrame frame;

	CommentsArea(MainFrame frame) {
		this.frame = frame;
	}

	/**
	 * Create center panel that holds the user input combo boxes and TextArea.
	 */
	JPanel createCommentsInputPanel() {
		BugCollection bc = getMainFrame().bugCollection;
		
		Cloud cloud = bc == null ? null : bc.getCloud();
		
		JPanel centerPanel = new JPanel();
		GridBagLayout layout = new GridBagLayout();

		centerPanel.setLayout(layout);

		userCommentsText.getDocument().addDocumentListener(
				new DocumentListener() {

					public void insertUpdate(DocumentEvent e) {
						setProjectChanged(true);
						changed = true;
					}

					public void removeUpdate(DocumentEvent e) {
						setProjectChanged(true);
						changed = true;
					}

					public void changedUpdate(DocumentEvent e) {
						changed = true;
					}

				});

		userCommentsTextUnenabledColor = centerPanel.getBackground();

		userCommentsText.setLineWrap(true);
		userCommentsText
				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.enter_comments", "Enter your comments about this bug here"));
		userCommentsText.setWrapStyleWord(true);
		userCommentsText.setEnabled(false);
		userCommentsText.setBackground(userCommentsTextUnenabledColor);
		JScrollPane commentsScrollP = new JScrollPane(userCommentsText);
		
		reportText.setLineWrap(true);
		reportText
				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.report", "Information about the bug here"));
		reportText.setWrapStyleWord(true);
		reportText.setEditable(false);
		
		JScrollPane reportScrollP = new JScrollPane(reportText);
		fileBug = new JButton(BugFilingStatus.FILE_BUG.toString());
		fileBug.setEnabled(false);
		fileBug.setToolTipText("Click to file bug for this issue");
		fileBug.addActionListener(new ActionListener(){

			public void actionPerformed(ActionEvent e) {
				if (frame.currentSelectedBugLeaf != null) {
					saveComments();
					BugInstance bug = frame.currentSelectedBugLeaf.getBug();
					Cloud cloud = getMainFrame().bugCollection.getCloud();
					if (!cloud.supportsBugLinks())
						return;
					URL u = cloud.getBugLink(bug);
					if (u != null) {
						if (LaunchBrowser.showDocument(u)) {
							cloud.bugFiled(bug, null);
							getMainFrame().syncBugInformation();
						}
					}
				}
	            
            }});
		
		 prevCommentsComboBox = new JComboBox();
		prevCommentsComboBox.setEnabled(false);
		prevCommentsComboBox
				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.reuse_comments", "Use this to reuse a previous textual comment for this bug"));
		prevCommentsComboBox.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED
						&& prevCommentsComboBox.getSelectedIndex() != 0) {
					setCurrentUserCommentsText(getCurrentPrevCommentsSelection());

					prevCommentsComboBox.setSelectedIndex(0);
				}
			}
		});

		designationComboBox = new JComboBox();
		designationKeys = new ArrayList<String>();

		designationComboBox.setEnabled(false);
		designationComboBox
				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.select_designation", "Select a user designation for this bug"));
		designationComboBox.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (frame.userInputEnabled
						&& e.getStateChange() == ItemEvent.SELECTED) {
					if (frame.currentSelectedBugLeaf == null)
						setDesignationNonLeaf(designationComboBox
								.getSelectedItem().toString());
					else if (!alreadySelected())
						setDesignation(designationComboBox.getSelectedItem()
								.toString());
				}
			}

			/*
			 * Checks to see if the designation is already selected as that.
			 * This was created because it was found the itemStateChanged method
			 * is called when the combo box is set when a bug is clicked.
			 */
			private boolean alreadySelected() {
				return designationKeys.get(
						designationComboBox.getSelectedIndex()).equals(
						frame.currentSelectedBugLeaf.getBug()
								.getUserDesignationKey());
			}
		});

		designationKeys.add("");
		designationComboBox.addItem("");
		for (String s : I18N.instance().getUserDesignationKeys(true)) {
			designationKeys.add(s);
			designationComboBox.addItem(Sortables.DESIGNATION.formatValue(s));
		}
		setUnknownDesignation();

		//JPanel comments = new JPanel();
		// comments.setLayout(new FlowLayout(FlowLayout.LEFT));
		// comments.add(designationComboBox);
		// comments.add(whoWhen);
		
		GridBagConstraints c = new GridBagConstraints();
		c.gridx = 0;
		c.gridy = 0;
		c.fill=GridBagConstraints.HORIZONTAL;
		c.weightx = 0;
		centerPanel.add(new JLabel("Classify:"), c);
		
		c.gridx++;
		c.fill=GridBagConstraints.HORIZONTAL;
		c.weightx = 1;
		
		centerPanel.add(designationComboBox, c);
		
		
		c.gridx = 0;
		c.gridy++;
		c.weightx = 1;
		c.weighty = 2;
		c.gridwidth = 2;
		c.fill=GridBagConstraints.BOTH;
		centerPanel.add(commentsScrollP, c);
		
		if (cloud != null && cloud.supportsCloudReports()) {
			c.gridy++;
			c.weightx = 1;
			c.weighty = 1;
			c.fill=GridBagConstraints.BOTH;
		
			centerPanel.add(reportScrollP, c);
		}
		
		if (cloud != null && cloud.supportsBugLinks()) {
			c.gridy++;
			c.weightx = 0;
			c.weighty = 0;
			c.fill = GridBagConstraints.HORIZONTAL;
			centerPanel.add(fileBug, c);
		}
		
		return centerPanel;
	}

	void setUnknownDesignation() {
		assert designationComboBox.getItemCount() == designationKeys.size();
		designationComboBox.setSelectedIndex(0); // WARNING: this is hard
													// coded in here.
	}

	/**
	 * Sets the user comment panel to whether or not it is enabled. If isEnabled
	 * is false will clear the user comments text pane.
	 * 
	 * @param isEnabled
	 */
	void setUserCommentInputEnable(final boolean isEnabled) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				setUserCommentInputEnableFromSwingThread(isEnabled);
			}
		});
	}

	/**
	 * Sets the user comment panel to whether or not it is enabled. If isEnabled
	 * is false will clear the user comments text pane.
	 * 
	 * @param isEnabled
	 */
	void setUserCommentInputEnableFromSwingThread(final boolean isEnabled) {
		frame.userInputEnabled = isEnabled;
		if (!isEnabled) {
//			This so if already saved doesn't make it seem project changed
			boolean b = frame.getProjectChanged();
			userCommentsText.setText("");
			// WARNING: this is hard coded in here, but needed
			// so when not enabled shows default setting of designation
			setUnknownDesignation();
			userCommentsText.setBackground(userCommentsTextUnenabledColor);
			setProjectChanged(b);
		} else
			userCommentsText.setBackground(Color.WHITE);
		userCommentsText.setEnabled(isEnabled);
		prevCommentsComboBox.setEnabled(isEnabled);
		designationComboBox.setEnabled(isEnabled);
	}


	SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd");
	/**
	 * Updates comments tab. Takes node passed and sets the designation and
	 * comments.
	 * 
	 * @param node
	 */
	void updateCommentsFromLeafInformation(final BugLeafNode node) {
		SwingUtilities.invokeLater(new Runnable() {

			public void run() {
				//This so if already saved doesn't make it seem project changed
				boolean b = frame.getProjectChanged();
				BugInstance bug = node.getBug();
				Cloud plugin = getCloud();

				if (plugin.supportsBugLinks()) {
					BugFilingStatus status = plugin.getBugLinkStatus(bug);
					fileBug.setText(status.toString());
					fileBug.setEnabled(status.linkEnabled());
				} else {
					fileBug.setEnabled(false);
				}
				if (!plugin.canStoreUserAnnotation(bug)) {
					designationComboBox.setSelectedIndex(0);
					setCurrentUserCommentsText("");
					reportText.setText("Issue not persisted to database");
					setUserCommentInputEnableFromSwingThread(false);
				} else {
					setCurrentUserCommentsText(bug.getAnnotationText());
					if (plugin.supportsCloudReports()) {
						String report = plugin.getCloudReport(bug);
						reportText.setText(report);
					}
					designationComboBox.setSelectedIndex(designationKeys.indexOf(bug.getUserDesignationKey()));
					setUserCommentInputEnableFromSwingThread(plugin.canStoreUserAnnotation(bug));
				}
				changed = false;
				setProjectChanged(b);
			}
		});
	}

	void updateCommentsFromNonLeafInformation(final BugAspects theAspects) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				//This so if already saved doesn't make it seem project changed
				boolean b = frame.getProjectChanged();
				updateCommentsFromNonLeafInformationFromSwingThread(theAspects);
				setUserCommentInputEnableFromSwingThread(true);
				changed = false;
				setProjectChanged(b);
			}
		});
	}

	/**
	 * Saves the current comments to the BugLeafNode passed in. If the passed in
	 * node's annotation is already equal to the current user comment then will
	 * not do anything so setProjectedChanged is not made true. Will also add
	 * the comment if it is new to the previous comments list.
	 * 
	 * @param node
	 */
	private void saveCommentsToBug(BugLeafNode node) {
		if (node == null)
			return;

		String comments = getCurrentUserCommentsText();
		final BugInstance bug = node.getBug();
		if (bug.getAnnotationText().equals(comments))
			return;

		bug.setAnnotationText(comments, MainFrame.getInstance().bugCollection);
		setProjectChanged(true);
		changed = false;
		addToPrevComments(comments);
	}

	private boolean confirmAnnotation() {

		String[] options = { edu.umd.cs.findbugs.L10N.getLocalString("dlg.yes_btn", "Yes"), edu.umd.cs.findbugs.L10N.getLocalString("dlg.no_btn", "No"), edu.umd.cs.findbugs.L10N.getLocalString("dlg.yes_dont_ask_btn", "Yes, and don't ask me this again")};
		if (dontShowAnnotationConfirmation)
			return true;
		int choice = JOptionPane
				.showOptionDialog(
						frame,
						edu.umd.cs.findbugs.L10N.getLocalString("dlg.changing_text_lbl", "Changing this text box will overwrite the annotations associated with all bugs in this folder and subfolders. Are you sure?"),
						edu.umd.cs.findbugs.L10N.getLocalString("dlg.annotation_change_ttl", "Annotation Change"), JOptionPane.DEFAULT_OPTION,
						JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
		switch (choice) {
		case 0:
			return true;
		case 1:
			return false;
		case 2:
			dontShowAnnotationConfirmation = true;
			return true;
		default:
			return true;
		}

	}

	private void saveCommentsToNonLeaf(BugAspects aspects) {
		if (aspects == null)
			return;
		if (!changed) return;
		String newComment = getCurrentUserCommentsText();
		if (newComment.equals(getNonLeafCommentsText(aspects)))
			return;
		else if (confirmAnnotation()) {
			BugSet filteredSet = aspects
					.getMatchingBugs(BugSet.getMainBugSet());
			for (BugLeafNode nextNode : filteredSet) {
				saveCommentsToBug(nextNode);
			}
		}
		changed = false;

	}

	/**
	 * Saves comments to the current selected bug.
	 * 
	 */

	public void saveComments() {
		saveComments(frame.currentSelectedBugLeaf,
				frame.currentSelectedBugAspects);
	}

	public void saveComments(BugLeafNode theNode, BugAspects theAspects) {
		if (theNode != null)
			saveCommentsToBug(theNode);
		else
			saveCommentsToNonLeaf(theAspects);
	}

	/**
	 * Deletes the list have already. Then loads from list. Will load from the
	 * list until run out of room in the prevCommentsList.
	 * 
	 * @param list
	 */
	void loadPrevCommentsList(String[] list) {
		int count = 0;
		for (String str : list) {
			if (str.equals(""))
				count++;
		}

		String[] ary = new String[list.length - count];
		int j = 0;
		for (String str : list) {
			if (!str.equals("")) {
				ary[j] = str;
				j++;
			}
		}

		String[] temp;
		prevCommentsList = new LinkedList<String>();
		if ((ary.length) > prevCommentsMaxSize) {
			temp = new String[prevCommentsMaxSize];
			for (int i = 0; i < temp.length && i < ary.length; i++)
				temp[i] = ary[i];
		} else {
			temp = new String[ary.length];
			for (int i = 0; i < ary.length; i++)
				temp[i] = ary[i];
		}

		for (String str : temp)
			prevCommentsList.add(str);

		resetPrevCommentsComboBox();
	}

	/**
	 * Adds the comment into the list. If the comment is already in the list
	 * then simply moves to the front. If the list is too big when adding the
	 * comment then deletes the last comment on the list.
	 * 
	 * @param comment
	 */
	private void addToPrevComments(String comment) {
		if (comment.equals(""))
			return;

		if (prevCommentsList.contains(comment)) {
			int index = prevCommentsList.indexOf(comment);
			if (index == 0)
				return;
			prevCommentsList.remove(index);
		}

		prevCommentsList.addFirst(comment);

		while (prevCommentsList.size() > prevCommentsMaxSize)
			prevCommentsList.removeLast();

		resetPrevCommentsComboBox();
	}

	/**
	 * Removes all items in the comboBox for previous comments. Then refills it
	 * using prevCommentsList.
	 * 
	 */
	private void resetPrevCommentsComboBox() {
		prevCommentsComboBox.removeAllItems();

		prevCommentsComboBox.addItem("");

		for (String str : prevCommentsList) {
			if (str.length() < 20)
				prevCommentsComboBox.addItem(str);
			else
				prevCommentsComboBox.addItem(str.substring(0, 17) + "...");
		}
	}

	/**
	 * Returns the text in the current user comments textArea.
	 * 
	 * @return
	 */
	private String getCurrentUserCommentsText() {
		return userCommentsText.getText();
	}

	/**
	 * Sets the current user comments text area to comment.
	 * 
	 * @param comment
	 */
	private void setCurrentUserCommentsText(String comment) {
		changed = true;
		userCommentsText.setText(comment);
	}

	/**
	 * Returns the current selected previous comments. Returns as an object.
	 */
	private String getCurrentPrevCommentsSelection() {
		return prevCommentsList
				.get(prevCommentsComboBox.getSelectedIndex() - 1);
	}

	void addDesignationItem(JMenu menu, final String menuName, int keyEvent) {
		JMenuItem toggleItem = new JMenuItem(menuName);

		toggleItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if (frame.currentSelectedBugLeaf == null)
					setDesignationNonLeaf(menuName);
				else
					setDesignation(menuName);
			}
		});
		MainFrame.attachAcceleratorKey(toggleItem, keyEvent);
		menu.add(toggleItem);
	}

	void setDesignation(String designationName) {
		if (frame.currentSelectedBugLeaf == null)
			return;
		String designationKey = convertDesignationNameToDesignationKey(designationName);
		if (designationKey == null)
			return;
		BugCollection bugCollection = MainFrame.getInstance().bugCollection;
		BugInstance bug = frame.currentSelectedBugLeaf.getBug();
		String oldValue = bug.getUserDesignationKey();
		if (designationKey.equals(oldValue))
			return;
		Cloud plugin = bugCollection != null? bugCollection.getCloud() : null;
		if (plugin != null && designationKey.equals("I_WILL_FIX")) {
			String claimedBy = plugin.claimedBy(bug);
			if (claimedBy != null && !plugin.getUser().equals(claimedBy)) {
				int result = JOptionPane.showConfirmDialog(null, 
						claimedBy + " has already said they will fix this issue\n"
						+ "Do you want to also be listed as fixing this issue?\n"
						+ "If so, please coordinate with " + claimedBy,
					 "Issue already claimed", JOptionPane.YES_NO_CANCEL_OPTION);
				if (result == JOptionPane.CANCEL_OPTION)
					return;
				if (result != JOptionPane.YES_OPTION)
					designationKey = "MUST_FIX";	
			}
		}
		if (changeDesignationOfBug(frame.currentSelectedBugLeaf, designationKey)){
			if (plugin != null && plugin.supportsCloudReports()) {
				String report = plugin.getCloudReport(bug);<commit_msg>Check to see if plugin supports claims<commit_after>/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2006, University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.gui2;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.LinkedList;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.tree.TreePath;

import edu.umd.cs.findbugs.BugCollection;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.I18N;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.cloud.Cloud;
import edu.umd.cs.findbugs.cloud.Cloud.BugFilingStatus;
import edu.umd.cs.findbugs.util.LaunchBrowser;

/**
 * @author pugh
 */
public class CommentsArea {

	private JTextArea userCommentsText = new JTextArea();
	private JTextArea reportText = new JTextArea();

	private Color userCommentsTextUnenabledColor;

	private JComboBox designationComboBox;

	private ArrayList<String> designationKeys;

	private JButton fileBug; 

	LinkedList<String> prevCommentsList = new LinkedList<String>();

	final static private int prevCommentsMaxSize = 10;

	private JComboBox prevCommentsComboBox; 

	private boolean dontShowAnnotationConfirmation = false;

	private boolean changed;
	final MainFrame frame;

	CommentsArea(MainFrame frame) {
		this.frame = frame;
	}

	/**
	 * Create center panel that holds the user input combo boxes and TextArea.
	 */
	JPanel createCommentsInputPanel() {
		BugCollection bc = getMainFrame().bugCollection;
		
		Cloud cloud = bc == null ? null : bc.getCloud();
		
		JPanel centerPanel = new JPanel();
		GridBagLayout layout = new GridBagLayout();

		centerPanel.setLayout(layout);

		userCommentsText.getDocument().addDocumentListener(
				new DocumentListener() {

					public void insertUpdate(DocumentEvent e) {
						setProjectChanged(true);
						changed = true;
					}

					public void removeUpdate(DocumentEvent e) {
						setProjectChanged(true);
						changed = true;
					}

					public void changedUpdate(DocumentEvent e) {
						changed = true;
					}

				});

		userCommentsTextUnenabledColor = centerPanel.getBackground();

		userCommentsText.setLineWrap(true);
		userCommentsText
				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.enter_comments", "Enter your comments about this bug here"));
		userCommentsText.setWrapStyleWord(true);
		userCommentsText.setEnabled(false);
		userCommentsText.setBackground(userCommentsTextUnenabledColor);
		JScrollPane commentsScrollP = new JScrollPane(userCommentsText);
		
		reportText.setLineWrap(true);
		reportText
				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.report", "Information about the bug here"));
		reportText.setWrapStyleWord(true);
		reportText.setEditable(false);
		
		JScrollPane reportScrollP = new JScrollPane(reportText);
		fileBug = new JButton(BugFilingStatus.FILE_BUG.toString());
		fileBug.setEnabled(false);
		fileBug.setToolTipText("Click to file bug for this issue");
		fileBug.addActionListener(new ActionListener(){

			public void actionPerformed(ActionEvent e) {
				if (frame.currentSelectedBugLeaf != null) {
					saveComments();
					BugInstance bug = frame.currentSelectedBugLeaf.getBug();
					Cloud cloud = getMainFrame().bugCollection.getCloud();
					if (!cloud.supportsBugLinks())
						return;
					URL u = cloud.getBugLink(bug);
					if (u != null) {
						if (LaunchBrowser.showDocument(u)) {
							cloud.bugFiled(bug, null);
							getMainFrame().syncBugInformation();
						}
					}
				}
	            
            }});
		
		 prevCommentsComboBox = new JComboBox();
		prevCommentsComboBox.setEnabled(false);
		prevCommentsComboBox
				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.reuse_comments", "Use this to reuse a previous textual comment for this bug"));
		prevCommentsComboBox.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED
						&& prevCommentsComboBox.getSelectedIndex() != 0) {
					setCurrentUserCommentsText(getCurrentPrevCommentsSelection());

					prevCommentsComboBox.setSelectedIndex(0);
				}
			}
		});

		designationComboBox = new JComboBox();
		designationKeys = new ArrayList<String>();

		designationComboBox.setEnabled(false);
		designationComboBox
				.setToolTipText(edu.umd.cs.findbugs.L10N.getLocalString("tooltip.select_designation", "Select a user designation for this bug"));
		designationComboBox.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent e) {
				if (frame.userInputEnabled
						&& e.getStateChange() == ItemEvent.SELECTED) {
					if (frame.currentSelectedBugLeaf == null)
						setDesignationNonLeaf(designationComboBox
								.getSelectedItem().toString());
					else if (!alreadySelected())
						setDesignation(designationComboBox.getSelectedItem()
								.toString());
				}
			}

			/*
			 * Checks to see if the designation is already selected as that.
			 * This was created because it was found the itemStateChanged method
			 * is called when the combo box is set when a bug is clicked.
			 */
			private boolean alreadySelected() {
				return designationKeys.get(
						designationComboBox.getSelectedIndex()).equals(
						frame.currentSelectedBugLeaf.getBug()
								.getUserDesignationKey());
			}
		});

		designationKeys.add("");
		designationComboBox.addItem("");
		for (String s : I18N.instance().getUserDesignationKeys(true)) {
			designationKeys.add(s);
			designationComboBox.addItem(Sortables.DESIGNATION.formatValue(s));
		}
		setUnknownDesignation();

		//JPanel comments = new JPanel();
		// comments.setLayout(new FlowLayout(FlowLayout.LEFT));
		// comments.add(designationComboBox);
		// comments.add(whoWhen);
		
		GridBagConstraints c = new GridBagConstraints();
		c.gridx = 0;
		c.gridy = 0;
		c.fill=GridBagConstraints.HORIZONTAL;
		c.weightx = 0;
		centerPanel.add(new JLabel("Classify:"), c);
		
		c.gridx++;
		c.fill=GridBagConstraints.HORIZONTAL;
		c.weightx = 1;
		
		centerPanel.add(designationComboBox, c);
		
		
		c.gridx = 0;
		c.gridy++;
		c.weightx = 1;
		c.weighty = 2;
		c.gridwidth = 2;
		c.fill=GridBagConstraints.BOTH;
		centerPanel.add(commentsScrollP, c);
		
		if (cloud != null && cloud.supportsCloudReports()) {
			c.gridy++;
			c.weightx = 1;
			c.weighty = 1;
			c.fill=GridBagConstraints.BOTH;
		
			centerPanel.add(reportScrollP, c);
		}
		
		if (cloud != null && cloud.supportsBugLinks()) {
			c.gridy++;
			c.weightx = 0;
			c.weighty = 0;
			c.fill = GridBagConstraints.HORIZONTAL;
			centerPanel.add(fileBug, c);
		}
		
		return centerPanel;
	}

	void setUnknownDesignation() {
		assert designationComboBox.getItemCount() == designationKeys.size();
		designationComboBox.setSelectedIndex(0); // WARNING: this is hard
													// coded in here.
	}

	/**
	 * Sets the user comment panel to whether or not it is enabled. If isEnabled
	 * is false will clear the user comments text pane.
	 * 
	 * @param isEnabled
	 */
	void setUserCommentInputEnable(final boolean isEnabled) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				setUserCommentInputEnableFromSwingThread(isEnabled);
			}
		});
	}

	/**
	 * Sets the user comment panel to whether or not it is enabled. If isEnabled
	 * is false will clear the user comments text pane.
	 * 
	 * @param isEnabled
	 */
	void setUserCommentInputEnableFromSwingThread(final boolean isEnabled) {
		frame.userInputEnabled = isEnabled;
		if (!isEnabled) {
//			This so if already saved doesn't make it seem project changed
			boolean b = frame.getProjectChanged();
			userCommentsText.setText("");
			// WARNING: this is hard coded in here, but needed
			// so when not enabled shows default setting of designation
			setUnknownDesignation();
			userCommentsText.setBackground(userCommentsTextUnenabledColor);
			setProjectChanged(b);
		} else
			userCommentsText.setBackground(Color.WHITE);
		userCommentsText.setEnabled(isEnabled);
		prevCommentsComboBox.setEnabled(isEnabled);
		designationComboBox.setEnabled(isEnabled);
	}


	SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd");
	/**
	 * Updates comments tab. Takes node passed and sets the designation and
	 * comments.
	 * 
	 * @param node
	 */
	void updateCommentsFromLeafInformation(final BugLeafNode node) {
		SwingUtilities.invokeLater(new Runnable() {

			public void run() {
				//This so if already saved doesn't make it seem project changed
				boolean b = frame.getProjectChanged();
				BugInstance bug = node.getBug();
				Cloud plugin = getCloud();

				if (plugin.supportsBugLinks()) {
					BugFilingStatus status = plugin.getBugLinkStatus(bug);
					fileBug.setText(status.toString());
					fileBug.setEnabled(status.linkEnabled());
				} else {
					fileBug.setEnabled(false);
				}
				if (!plugin.canStoreUserAnnotation(bug)) {
					designationComboBox.setSelectedIndex(0);
					setCurrentUserCommentsText("");
					reportText.setText("Issue not persisted to database");
					setUserCommentInputEnableFromSwingThread(false);
				} else {
					setCurrentUserCommentsText(bug.getAnnotationText());
					if (plugin.supportsCloudReports()) {
						String report = plugin.getCloudReport(bug);
						reportText.setText(report);
					}
					designationComboBox.setSelectedIndex(designationKeys.indexOf(bug.getUserDesignationKey()));
					setUserCommentInputEnableFromSwingThread(plugin.canStoreUserAnnotation(bug));
				}
				changed = false;
				setProjectChanged(b);
			}
		});
	}

	void updateCommentsFromNonLeafInformation(final BugAspects theAspects) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				//This so if already saved doesn't make it seem project changed
				boolean b = frame.getProjectChanged();
				updateCommentsFromNonLeafInformationFromSwingThread(theAspects);
				setUserCommentInputEnableFromSwingThread(true);
				changed = false;
				setProjectChanged(b);
			}
		});
	}

	/**
	 * Saves the current comments to the BugLeafNode passed in. If the passed in
	 * node's annotation is already equal to the current user comment then will
	 * not do anything so setProjectedChanged is not made true. Will also add
	 * the comment if it is new to the previous comments list.
	 * 
	 * @param node
	 */
	private void saveCommentsToBug(BugLeafNode node) {
		if (node == null)
			return;

		String comments = getCurrentUserCommentsText();
		final BugInstance bug = node.getBug();
		if (bug.getAnnotationText().equals(comments))
			return;

		bug.setAnnotationText(comments, MainFrame.getInstance().bugCollection);
		setProjectChanged(true);
		changed = false;
		addToPrevComments(comments);
	}

	private boolean confirmAnnotation() {

		String[] options = { edu.umd.cs.findbugs.L10N.getLocalString("dlg.yes_btn", "Yes"), edu.umd.cs.findbugs.L10N.getLocalString("dlg.no_btn", "No"), edu.umd.cs.findbugs.L10N.getLocalString("dlg.yes_dont_ask_btn", "Yes, and don't ask me this again")};
		if (dontShowAnnotationConfirmation)
			return true;
		int choice = JOptionPane
				.showOptionDialog(
						frame,
						edu.umd.cs.findbugs.L10N.getLocalString("dlg.changing_text_lbl", "Changing this text box will overwrite the annotations associated with all bugs in this folder and subfolders. Are you sure?"),
						edu.umd.cs.findbugs.L10N.getLocalString("dlg.annotation_change_ttl", "Annotation Change"), JOptionPane.DEFAULT_OPTION,
						JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
		switch (choice) {
		case 0:
			return true;
		case 1:
			return false;
		case 2:
			dontShowAnnotationConfirmation = true;
			return true;
		default:
			return true;
		}

	}

	private void saveCommentsToNonLeaf(BugAspects aspects) {
		if (aspects == null)
			return;
		if (!changed) return;
		String newComment = getCurrentUserCommentsText();
		if (newComment.equals(getNonLeafCommentsText(aspects)))
			return;
		else if (confirmAnnotation()) {
			BugSet filteredSet = aspects
					.getMatchingBugs(BugSet.getMainBugSet());
			for (BugLeafNode nextNode : filteredSet) {
				saveCommentsToBug(nextNode);
			}
		}
		changed = false;

	}

	/**
	 * Saves comments to the current selected bug.
	 * 
	 */

	public void saveComments() {
		saveComments(frame.currentSelectedBugLeaf,
				frame.currentSelectedBugAspects);
	}

	public void saveComments(BugLeafNode theNode, BugAspects theAspects) {
		if (theNode != null)
			saveCommentsToBug(theNode);
		else
			saveCommentsToNonLeaf(theAspects);
	}

	/**
	 * Deletes the list have already. Then loads from list. Will load from the
	 * list until run out of room in the prevCommentsList.
	 * 
	 * @param list
	 */
	void loadPrevCommentsList(String[] list) {
		int count = 0;
		for (String str : list) {
			if (str.equals(""))
				count++;
		}

		String[] ary = new String[list.length - count];
		int j = 0;
		for (String str : list) {
			if (!str.equals("")) {
				ary[j] = str;
				j++;
			}
		}

		String[] temp;
		prevCommentsList = new LinkedList<String>();
		if ((ary.length) > prevCommentsMaxSize) {
			temp = new String[prevCommentsMaxSize];
			for (int i = 0; i < temp.length && i < ary.length; i++)
				temp[i] = ary[i];
		} else {
			temp = new String[ary.length];
			for (int i = 0; i < ary.length; i++)
				temp[i] = ary[i];
		}

		for (String str : temp)
			prevCommentsList.add(str);

		resetPrevCommentsComboBox();
	}

	/**
	 * Adds the comment into the list. If the comment is already in the list
	 * then simply moves to the front. If the list is too big when adding the
	 * comment then deletes the last comment on the list.
	 * 
	 * @param comment
	 */
	private void addToPrevComments(String comment) {
		if (comment.equals(""))
			return;

		if (prevCommentsList.contains(comment)) {
			int index = prevCommentsList.indexOf(comment);
			if (index == 0)
				return;
			prevCommentsList.remove(index);
		}

		prevCommentsList.addFirst(comment);

		while (prevCommentsList.size() > prevCommentsMaxSize)
			prevCommentsList.removeLast();

		resetPrevCommentsComboBox();
	}

	/**
	 * Removes all items in the comboBox for previous comments. Then refills it
	 * using prevCommentsList.
	 * 
	 */
	private void resetPrevCommentsComboBox() {
		prevCommentsComboBox.removeAllItems();

		prevCommentsComboBox.addItem("");

		for (String str : prevCommentsList) {
			if (str.length() < 20)
				prevCommentsComboBox.addItem(str);
			else
				prevCommentsComboBox.addItem(str.substring(0, 17) + "...");
		}
	}

	/**
	 * Returns the text in the current user comments textArea.
	 * 
	 * @return
	 */
	private String getCurrentUserCommentsText() {
		return userCommentsText.getText();
	}

	/**
	 * Sets the current user comments text area to comment.
	 * 
	 * @param comment
	 */
	private void setCurrentUserCommentsText(String comment) {
		changed = true;
		userCommentsText.setText(comment);
	}

	/**
	 * Returns the current selected previous comments. Returns as an object.
	 */
	private String getCurrentPrevCommentsSelection() {
		return prevCommentsList
				.get(prevCommentsComboBox.getSelectedIndex() - 1);
	}

	void addDesignationItem(JMenu menu, final String menuName, int keyEvent) {
		JMenuItem toggleItem = new JMenuItem(menuName);

		toggleItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if (frame.currentSelectedBugLeaf == null)
					setDesignationNonLeaf(menuName);
				else
					setDesignation(menuName);
			}
		});
		MainFrame.attachAcceleratorKey(toggleItem, keyEvent);
		menu.add(toggleItem);
	}

	void setDesignation(String designationName) {
		if (frame.currentSelectedBugLeaf == null)
			return;
		String designationKey = convertDesignationNameToDesignationKey(designationName);
		if (designationKey == null)
			return;
		BugCollection bugCollection = MainFrame.getInstance().bugCollection;
		BugInstance bug = frame.currentSelectedBugLeaf.getBug();
		String oldValue = bug.getUserDesignationKey();
		if (designationKey.equals(oldValue))
			return;
		Cloud plugin = bugCollection != null? bugCollection.getCloud() : null;
		if (plugin != null && designationKey.equals("I_WILL_FIX") && plugin.supportsClaims()) {
			String claimedBy = plugin.claimedBy(bug);
			if (claimedBy != null && !plugin.getUser().equals(claimedBy)) {
				int result = JOptionPane.showConfirmDialog(null, 
						claimedBy + " has already said they will fix this issue\n"
						+ "Do you want to also be listed as fixing this issue?\n"
						+ "If so, please coordinate with " + claimedBy,
					 "Issue already claimed", JOptionPane.YES_NO_CANCEL_OPTION);
				if (result == JOptionPane.CANCEL_OPTION)
					return;
				if (result != JOptionPane.YES_OPTION)
					designationKey = "MUST_FIX";	
			}
		}
		if (changeDesignationOfBug(frame.currentSelectedBugLeaf, designationKey)){
			if (plugin != null && plugin.supportsCloudReports()) {
				String report = plugin.getCloudReport(bug);
{'fraction_non_alphanumeric': 0.10497100248550124, 'fraction_numerical': 0.004252968793151063, 'mean_word_length': 4.008437067773167, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7248230', 'n_tokens_mistral': 13113, 'n_tokens_neox': 10778, 'n_words': 2710}