/* * [BraceBalancer.java] * * Summary: Helps you find unbalanced braces, brackets and parentheses in any language. * * Copyright: (c) 2010-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2010-01-05 initial version. * 1.1 2011-01-06 add sound effects, fix bugs. * 1.2 2011-11-20 add prompts and sound effects to make handling very large documents less confusing. * 1.3 2012-03-23 choice of Balancer, export button */ package com.mindprod.bracebalancer; import com.mindprod.common18.Build; import com.mindprod.common18.CMPAboutJBox; import com.mindprod.common18.ClipboardPoker; import com.mindprod.common18.EIO; import com.mindprod.common18.FontFactory; import com.mindprod.common18.HybridJ; import com.mindprod.common18.JEButton; import com.mindprod.common18.Laf; import com.mindprod.common18.Misc; import com.mindprod.common18.Play; import com.mindprod.common18.ST; import com.mindprod.common18.VersionCheck; import com.mindprod.entities.EntifyStrings; import com.mindprod.fastcat.FastCat; import com.mindprod.hunkio.HunkIO; import javax.swing.DefaultComboBoxModel; import javax.swing.JApplet; import javax.swing.JComboBox; import javax.swing.JEditorPane; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.File; import java.io.IOException; import static java.lang.System.*; /** * Helps you find unbalanced braces, brackets and parentheses in any language. *
* This is both an Applet and a standalone application. * * @author Roedy Green, Canadian Mind Products * @version 1.3 2012-03-23 choice of Balancer, export button * @since 2010-12-27 */ public final class BraceBalancer extends JApplet implements ClipboardOwner, Runnable { /** * if true extra debug output. */ private static final boolean DEBUGGING = false; /** * height of Applet in pixels. Does not include bar at top, but does include menu bar. */ private static final int APPLET_HEIGHT = 900; /** * Width of Applet in pixels. */ private static final int APPLET_WIDTH = 758; private static final int FIRST_COPYRIGHT_YEAR = 2010; /** * DOCTYPE for generated HTML */ private static final String DOCTYPE = ""; /** * undisplayed copyright notice * * @noinspection UnusedDeclaration */ private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 2010-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; /** * CrLf or lf used for line endings */ private static final String lineSeparator = System.getProperty( "line.separator" ); /** * date this version released. */ private static final String RELEASE_DATE = "2012-03-23"; /** * want a mono font so code will align. */ private static final String stylesheet = "\n"; /** * Title of the Applet */ private static final String TITLE_STRING = "Brace Balancer"; /** * Version of the Applet */ private static final String VERSION_STRING = "1.3"; /** * background colour, pale green to match website */ private static final Color BACKGROUND_FOR_BODY = Build.BACKGROUND_FOR_BLENDING; /** * background where for data entry where user enters a value. */ private static final Color BACKGROUND_FOR_EDITABLE = Color.WHITE; /** * data entry text field colour */ private static final Color FOREGROUND_FOR_EDITABLE = Color.BLACK; private static final Color FOREGROUND_FOR_INSTRUCTIONS = new Color( 0x008000 ); private static final Color FOREGROUND_FOR_PROGRESS = new Color( 0x000080 ); /** * for titles */ private static final Color FOREGROUND_FOR_TITLE = new Color( 0xdc143c ); /** * title font */ private static final Font FONT_FOR_TITLE = FontFactory.build( "Dialog", Font.BOLD, 15 ); /** * for for title second line */ private static final Font FONT_FOR_TITLE2 = FontFactory.build( "Dialog", Font.PLAIN, 14 ); private static final Font INSTRUCTIONS_FONT = FontFactory.build( "Dialog", Font.PLAIN, 13 ); private static final Font PROGRESS_FONT = FontFactory.build( "Dialog", Font.PLAIN, 14 ); /** * which balancer the user selected. */ private Balancer balancer; /** * contentPane for the whole Applet */ private Container contentPane; /** * which syntactic elements to balance. */ private JComboBox" );
// do spanner for first line.
sb.append( buildSpanner( braceNesting, bracketNesting, parenthesisNesting, balancer ) );
for ( int i = 0; i < big.length(); i++ )
{
final char c = big.charAt( i );
switch ( c )
{
case '\r':
/* ignore, will be reinserted when see \n */
break;
case '\n':
sb.append( lineSeparator );
sb.append( buildSpanner( braceNesting, bracketNesting, parenthesisNesting, balancer ) );
break;
case '{':
if ( !inQuotes )
{
braceNesting++;
sb.append( newColour( braceNesting, bracketNesting, parenthesisNesting, balancer ) );
}
sb.append( c );
break;
case '}':
if ( !inQuotes )
{
sb.append( c );
braceNesting--;
sb.append( newColour( braceNesting, bracketNesting, parenthesisNesting, balancer ) );
}
else
{
sb.append( c );
}
break;
case '[':
if ( !inQuotes )
{
bracketNesting++;
sb.append( newColour( braceNesting, bracketNesting, parenthesisNesting, balancer ) );
}
sb.append( c );
break;
case ']':
if ( !inQuotes )
{
sb.append( c );
bracketNesting--;
sb.append( newColour( braceNesting, bracketNesting, parenthesisNesting, balancer ) );
}
else
{
sb.append( c );
}
break;
case '(':
if ( !inQuotes )
{
parenthesisNesting++;
sb.append( newColour( braceNesting, bracketNesting, parenthesisNesting, balancer ) );
}
sb.append( c );
break;
case ')':
if ( !inQuotes )
{
sb.append( c );
parenthesisNesting--;
sb.append( newColour( braceNesting, bracketNesting, parenthesisNesting, balancer ) );
}
else
{
sb.append( c );
}
break;
case '\'':
if ( !inDoubleQuotes && prevChar != '\\' )
{
inSingleQuotes = !inSingleQuotes;
inQuotes = inSingleQuotes || inDoubleQuotes;
}
sb.append( '\'' );
break;
case '\"':
if ( !inSingleQuotes && prevChar != '\\' )
{
inDoubleQuotes = !inDoubleQuotes;
inQuotes = inSingleQuotes || inDoubleQuotes;
}
sb.append( """ );
break;
default:
sb.append( EntifyStrings.toHTMLEntity( c ) );
}
prevChar = c;
}
sb.append( "
" );
return sb.toString();
}
/**
* hook up the listeners
*/
private void hookListeners()
{
paste.addActionListener( new ActionListener()
{
public void actionPerformed( final ActionEvent e )
{
colouriseAndDisplayClipboard();
}
} );
//
whatToBalance.addItemListener( new ItemListener()
{
/**
* Notice any change to one of the list box selectors
* @param e details of just what the user clicked.
*/
public void itemStateChanged( ItemEvent e )
{
if ( e.getStateChange() == ItemEvent.SELECTED )
{
recolouriseAndDisplayClipboard();
}
}
}
);
//
// what to do when user clicks the export button.
export.addActionListener( new ActionListener()
{
public void actionPerformed( ActionEvent e )
{
final JFileChooser fc = new JFileChooser();
// No filter. No suggested starting point.
switch ( fc.showOpenDialog( BraceBalancer.this ) )
{
case JFileChooser.APPROVE_OPTION:
final File file = fc.getSelectedFile();
fc.setFileSelectionMode( JFileChooser.FILES_ONLY );
try
{
HunkIO.writeEntireFile( file, formattedProgramText, HunkIO.UTF8 );
progress.setText( "Formatted program text exported as HTML to " + EIO.getCanOrAbsPath
( file ) );
}
catch ( IOException ioe )
{
progress.setText( "Unable to export formatted program" );
}
break;
case JFileChooser.CANCEL_OPTION:
case JFileChooser.ERROR_OPTION:
break;
default:
}
}
} );
}
/**
* layout fields in GridBag
*/
private void layoutComponents()
{
contentPane.setLayout( new GridBagLayout() );
// _____ 0_____________________________1_____2___----
// 0 Title------------------- ------title2---------- 0
// 1 whatToBalance 1
// 2 jep -------------------- -------------- ------- 1
// 3 instructions---------------------------export-- 2
// 4 progress-------------------------------paste--- 4
//
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( title,
new GridBagConstraints( 0,
0,
1,
1,
0.0,
0.0,
GridBagConstraints.WEST,
GridBagConstraints.NONE,
new Insets( 10, 10, 5, 5 ), // top, left, bottom, right
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( title2,
new GridBagConstraints( 1,
0,
2,
1,
0.0,
0.0,
GridBagConstraints.EAST,
GridBagConstraints.NONE,
new Insets( 10, 5, 5, 10 ), // top, left, bottom, right
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( whatToBalance,
new GridBagConstraints( 0,
1,
1,
1,
0.0,
0.0,
GridBagConstraints.WEST,
GridBagConstraints.NONE,
new Insets( 5, 10, 5, 5 ), // top, left, bottom, right
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( jepScrollPane,
new GridBagConstraints( 0,
2,
3,
1,
100.0,
90.0,
GridBagConstraints.CENTER,
GridBagConstraints.BOTH,
new Insets( 5, 10, 5, 10 ), // top, left, bottom, right
0,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( instructions,
new GridBagConstraints( 0,
3,
2,
2,
90.0,
5.0,
GridBagConstraints.WEST,
GridBagConstraints.BOTH,
new Insets( 5, 10, 10, 5 ),
40,
10 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( export,
new GridBagConstraints( 2,
3,
1,
1,
0.0,
0.0,
GridBagConstraints.EAST,
GridBagConstraints.NONE,
new Insets( 5, 5, 5, 10 ),
10,
10 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( paste,
new GridBagConstraints( 2,
4,
1,
1,
0.0,
0.0,
GridBagConstraints.EAST,
GridBagConstraints.NONE,
new Insets( 5, 5, 5, 10 ),
10,
10 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( progress,
new GridBagConstraints( 0,
5,
2,
1,
100.0,
0.0,
GridBagConstraints.WEST,
GridBagConstraints.BOTH,
new Insets( 5, 10, 10, 10 ), // top, left, bottom, right
40,
0 )
);
}
/**
* compute HTML to generate a new colour based on nesting depth
*
* @param braceNesting inside how many {}
* @param bracketNesting inside how many []
* @param parenthesisNesting inside how many ()
* @param balancer which balancer to display
*
* @return HTML to change the background colour
*/
private String newColour( int braceNesting, int bracketNesting, int parenthesisNesting, Balancer balancer )
{
if ( braceNesting > 5 )
{
braceNesting = 5;
}
if ( bracketNesting > 5 )
{
bracketNesting = 5;
}
if ( parenthesisNesting > 5 )
{
parenthesisNesting = 5;
}
final int r;
final int g;
final int b;
final int rgb;
switch ( balancer )
{
case ALL:
if ( braceNesting < 0 || bracketNesting < 0 || parenthesisNesting < 0 )
{
rgb = 0xffff00; /* bright yellow */
}
else
{
r = 255 - 24 * braceNesting;
g = 255 - 24 * bracketNesting;
b = 255 - 24 * parenthesisNesting;
rgb = r << 16 | g << 8 | b;
}
break;
case BRACE:
if ( braceNesting < 0 )
{
rgb = 0xffff00; /* bright yellow */
}
else if ( braceNesting == 0 )
{
rgb = 0xffffff;
}
else
{
int depth = 100 / Math.max( 1, maxBraceNesting ) * braceNesting;
r = 255; // deeper red as nest.
g = 255 - depth;
b = 255 - depth;
rgb = r << 16 | g << 8 | b;
}
break;
case BRACKET:
if ( bracketNesting < 0 )
{
rgb = 0xffff00; /* bright yellow */
}
else if ( bracketNesting == 0 )
{
rgb = 0xffffff;
}
else
{
int depth = 100 / Math.max( 1, maxBracketNesting ) * bracketNesting;
r = 255 - depth;
g = 255;
b = 255 - depth;
rgb = r << 16 | g << 8 | b;
}
break;
case PARENTHESIS:
if ( parenthesisNesting < 0 )
{
rgb = 0xffff00; /* bright yellow */
}
else if ( parenthesisNesting == 0 )
{
rgb = 0xffffff;
}
else
{
int depth = 100 / Math.max( 1, maxParenthesisNesting ) * parenthesisNesting;
r = 255 - depth;
g = 255 - depth;
b = 255;
rgb = r << 16 | g << 8 | b;
}
break;
default:
throw new IllegalArgumentException( "bad balancer enum " + balancer );
}
if ( rgb == prevrgb )
{
return "";
}
else
{
prevrgb = rgb;
return "";
}
}
/**
* recolourise the clipboard previously extracted with different balancer
*/
private void recolouriseAndDisplayClipboard()
{
final Balancer oldBalancer = balancer;
balancer = ( Balancer ) whatToBalance.getSelectedItem();
assert balancer != null : "null balancer";
if ( balancer != oldBalancer && rawClipboard != null && rawClipboard.length() > 0 )
{
progress.setText( "Colourising the clipboard. Please be patient..." );
new Thread( this ).start(); // start up run on a new thread to
}
}
/**
* Allow this Applet to run as as application as well.
*
* @param args command line arguments ignored.
*/
public static void main( String args[] )
{
HybridJ.fireup( new BraceBalancer(),
TITLE_STRING + " " + VERSION_STRING,
APPLET_WIDTH,
APPLET_HEIGHT );
} // end main
/**
* Called by the browser or Applet viewer to inform
* this Applet that it is being reclaimed and that it should destroy
* any resources that it has allocated.
*/
@Override
public void destroy()
{
balancer = null;
contentPane = null;
paste = null;
instructions = null;
jep = null;
jepScrollPane = null;
progress = null;
rawClipboard = null;
title = null;
title2 = null;
whatToBalance = null;
}
/**
* Called by the browser or Applet viewer to inform
* this Applet that it has been loaded into the system.
*/
@Override
public void init()
{
contentPane = getContentPane();
if ( !VersionCheck.isJavaVersionOK( 1, 7, 0, contentPane ) )
{
// abort
stop();
destroy();
}
buildMenu(); // also initial L&F
buildComponents();
layoutComponents();
hookListeners();
this.validate();
this.setVisible( true );
}
/**
* needed to let us use the clipboard
*/
public void lostOwnership( Clipboard clipboard, Transferable contents )
{
/* ignore */
}
/**
* on a separate thread, prepare contents of clipboard for display
*/
public void run()
{
// this is very quick
if ( !maxNestingDepthsComputed )
{
calcMaxNestingDepths( rawClipboard );
maxNestingDepthsComputed = true;
}
formattedProgramText = colouriseProgramText( rawClipboard, balancer );
progress.setText( "" );
// now we have prepared the html, render the results
SwingUtilities.invokeLater( new Runnable()
{
public void run()
{
try
{
// this takes quite a while
jep.setText( formattedProgramText );
Play.play( BraceBalancer.class, "sound/start.au" );
// after this is done, the repaint is fast.
}
catch ( Exception e )
{
Play.play( BraceBalancer.class, "sound/echo.au" );
err.println( "Unable to render that clipboard because of " + e.getMessage() );
err.println( "cooked: [" + formattedProgramText + "]" );
jep.setText( "Unable to render that clipboard" );
}
}
}
);
}
}