gt: 					setText("  " + functionInfo.getName());
context: package org.cytoscape.view.table.internal.equation;
  
  import static java.util.Comparator.comparing;
  import static java.util.Comparator.naturalOrder;
  import static java.util.Comparator.nullsFirst;
  
  import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Component;
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.event.ActionEvent;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.List;
  import java.util.SortedMap;
  import java.util.TreeMap;
  
  import javax.swing.AbstractAction;
  import javax.swing.DefaultListCellRenderer;
  import javax.swing.JDialog;
  import javax.swing.JFrame;
  import javax.swing.JList;
  import javax.swing.JPopupMenu;
  import javax.swing.WindowConstants;
  import javax.swing.event.AncestorEvent;
  import javax.swing.event.AncestorListener;
  import javax.swing.event.HyperlinkEvent.EventType;
  
  import org.cytoscape.application.swing.CyColumnPresentationManager;
  import org.cytoscape.application.swing.CySwingApplication;
  import org.cytoscape.equations.Equation;
  import org.cytoscape.equations.EquationCompiler;
  import org.cytoscape.equations.EquationParser;
  import org.cytoscape.equations.Function;
  import org.cytoscape.model.CyColumn;
  import org.cytoscape.model.CyNetwork;
  import org.cytoscape.model.CyRow;
  import org.cytoscape.model.CyTable;
  import org.cytoscape.service.util.CyServiceRegistrar;
  import org.cytoscape.view.table.internal.impl.BrowserTable;
  import org.cytoscape.view.table.internal.impl.BrowserTableModel;
  import org.cytoscape.view.table.internal.util.TableBrowserUtil;
  
  /*
   * #%L
   * Cytoscape Table Presentation Impl (table-presentation-impl)
   * $Id:$
   * $HeadURL:$
   * %%
   * Copyright (C) 2010 - 2021 The Cytoscape Consortium
   * %%
   * This program 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 program 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 General Lesser Public License for more details.
   * 
   * You should have received a copy of the GNU General Lesser Public 
   * License along with this program.  If not, see
   * <http://www.gnu.org/licenses/lgpl-2.1.html>.
   * #L%
   */
  
  /**
   * This mediator is not a singleton, there is one instance per BrowserTable.
   */
  public class EquationEditorMediator {
  	
  	public enum ApplyScope {
  		CURRENT_CELL("Current cell only"),
  		CURRENT_SELECTION("Current selection"),
  		ENTIRE_COLUMN("Entire column");
  
  		private final String text;
  		ApplyScope(String text) { this.text = text; }
  		@Override public String toString() { return text; }
  		
  		public static ApplyScope[] values(boolean includeSelected) {
  			if(includeSelected)
  				return values();
  			return new ApplyScope[] { ApplyScope.CURRENT_CELL, ApplyScope.ENTIRE_COLUMN };
  		}
  	}
  	
  	
  	private final CyServiceRegistrar registrar;
  	private final BrowserTable browserTable;
  	private final EquationEditorPanel builderPanel;
  	
  	// The selected column/row can change as the BrowserTable reacts to events, so we need to store these values up front.
  	private final CyColumn column;
  	private final int currentRowIndex;
  	
  	
  	private EquationEditorMediator(CyColumn column, BrowserTable browserTable, EquationEditorPanel builderPanel, CyServiceRegistrar registrar) {
  		this.browserTable = browserTable;
  		this.registrar = registrar;
  		this.builderPanel = builderPanel;
  		this.column = column;
  		this.currentRowIndex = browserTable.convertRowIndexToModel(browserTable.getSelectedRow());
  	}
  	
  	public static void openEquationEditorDialog(BrowserTable browserTable, String equation, CyServiceRegistrar registrar) {
  		JFrame parent = registrar.getService(CySwingApplication.class).getJFrame();
  		JDialog dialog = new JDialog(parent);
  		
  		BrowserTableModel tableModel = browserTable.getBrowserTableModel();
  		int cellCol = browserTable.convertColumnIndexToModel(browserTable.getSelectedColumn());
  		CyColumn column =  tableModel.getCyColumn(cellCol);
  		
  		var builderPanel = new EquationEditorPanel(registrar, browserTable);
  		
  		EquationEditorMediator mediator = new EquationEditorMediator(column, browserTable, builderPanel, registrar);
  		mediator.wireTogether(dialog);
  		
  		String attribName = column.getName();
  		
  		dialog.setTitle("Formula Builder for " + attribName);
  		dialog.setModal(true);
  		dialog.getContentPane().setLayout(new BorderLayout());
  		dialog.getContentPane().add(builderPanel, BorderLayout.CENTER);
  		dialog.setPreferredSize(new Dimension(600, 600)); 
  		dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
  		dialog.pack();
  		dialog.setLocationRelativeTo(parent);
  		
  		builderPanel.getSyntaxPanel().getSyntaxTextArea().addAncestorListener(new RequestFocusListener());
  		builderPanel.getSyntaxPanel().setText(equation);
  		
  		dialog.setVisible(true);
  	}
  	
  	private static class RequestFocusListener implements AncestorListener {
  		public void ancestorAdded(AncestorEvent e) {
  			var c = e.getComponent();
  			c.requestFocusInWindow();
  			c.removeAncestorListener(this);
  		}
  		public void ancestorRemoved(AncestorEvent event) { }
  		public void ancestorMoved(AncestorEvent event) { }
  	}
  	
  	
  	private void wireTogether(JDialog dialog) {
  		initializeTutorialList();
  		initializeFunctionList();
  		initializeAttributeList();
  		
  		builderPanel.getCloseButton().addActionListener(e -> dialog.dispose());
  		
  		SyntaxAreaPanel syntaxPanel = builderPanel.getSyntaxPanel();
  		syntaxPanel.getApplyButton().addActionListener(e -> handleApply(true));
  		syntaxPanel.getEvalButton().addActionListener(e -> handleApply(false));
  		
  		builderPanel.getInfoPanel().getTextArea().addHyperlinkListener(e -> {
  			if(e.getEventType() == EventType.ACTIVATED) {
  				handleInsert();	
  			}
  		});
  	}
  	
  	
  	private void initializeTutorialList() {
  		builderPanel.getTutorialPanel().addElements(TutorialItems.getTutorialItems());
  		
  		JList<String> list = builderPanel.getTutorialPanel().getList();
  		list.addListSelectionListener(e -> {
  			String item = list.getSelectedValue();
  			if(item != null) {
  				builderPanel.getAttributePanel().clearSelection();
  				builderPanel.getFunctionPanel().clearSelection();
  				String docs = TutorialItems.getTutorialDocs(item);
  				builderPanel.getInfoPanel().setText(docs);
  			}
  		});
  	}
  	
  	
  	private void initializeFunctionList() {
  		initializeFunctionListContents();
  		initializeFunctionListComponents();
  	}
  	
  	
  	private void initializeFunctionListContents() {
  		EquationParser equationParser = registrar.getService(EquationParser.class);
  		
  		SortedMap<String,SortedMap<String,Function>> categories = new TreeMap<>();
  		
  		for(Function f : equationParser.getRegisteredFunctions()) {
  			for(String category : getCategoriesFor(f)) {
  				var functions = categories.computeIfAbsent(category, k -> new TreeMap<>());
  				functions.put(f.getName(), f);
  			}
  		}
  		
  		ItemListPanel<FunctionInfo> functionPanel = builderPanel.getFunctionPanel();
  		
  		List<String> topCategories = Arrays.asList("Numeric", "Text", "List", "Logic", "Network");
  		
  		// First put the top categories at the top
  		for(String categoryName : topCategories) {
  			var functions = categories.remove(categoryName);
  			if(functions != null) {
  				functionPanel.addElement(FunctionInfo.category(categoryName));
  				
  				for(var functionEntry : functions.entrySet()) {
  					Function f = functionEntry.getValue();
  					functionPanel.addElement(FunctionInfo.function(f));
  				}
  			}
  		}
  		
  		// Now add the rest
  		for(var categoryEntry : categories.entrySet()) {
  			String categoryName = categoryEntry.getKey();
  			var functions = categoryEntry.getValue();
  			
  			functionPanel.addElement(FunctionInfo.category(categoryName));
  			
  			for(var functionEntry : functions.entrySet()) {
  				Function f = functionEntry.getValue();
  				functionPanel.addElement(FunctionInfo.function(f));
  			}
  		}
  	}
  	
  	
  	private static String[] getCategoriesFor(Function f) {
  		String categoryList = f.getCategoryName();
  		if(categoryList == null || categoryList.isBlank()) {
  			return new String[] { "Other" };
  		}
  		String[] split = categoryList.split(",");
  		if(split == null) {
  			return new String[] { "Other" };
  		}
  		return split;
  	}
  	
  	
  	@SuppressWarnings("serial")
  	private void initializeFunctionListComponents() {
  		ItemListPanel<FunctionInfo> functionPanel = builderPanel.getFunctionPanel();
  		JList<FunctionInfo> list = functionPanel.getList();
  		
  		list.setCellRenderer(new DefaultListCellRenderer() {
  			public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
  				super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
  				
  				FunctionInfo functionInfo = (FunctionInfo) value;
  				if(functionInfo.isCategoryHeader()) {
  					Font font = getFont();
  					Font bold = font.deriveFont(Font.BOLD);
  					setFont(bold);
  					setText(functionInfo.getName());
  					setBackground(slightlyDarker(getBackground()));
  				} else {
  