/*
* [MASMBalancer.java]
*
* Summary: Helps you find unbalanced proc/endp segment/ends if/endif in MASM.
*
* Copyright: (c) 2011-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 2011-01-03 initial version.
* 1.1 2011-01-06 add sound effects, handle clipboard without final nl.
* 1.2 2011-02-07 allow individual balancing, also depth bars.
* 1.3 2012-03-23 support if1, if2, ifdef, ifndef, ifb, ifnb, export button
*/
package com.mindprod.masmbalancer;
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.VersionCheck;
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 proc/endp segment/ends if/endif in MASM.
*
* This is both an Applet and a standalone application.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.3 2012-03-23 support if1, if2, ifdef, ifndef, ifb, ifnb, export button
* @since 2011-01-03
*/
public final class MASMBalancer extends JApplet implements ClipboardOwner, Runnable
{
// togo: Masm JPrep
// togo: why does need final Cr?
/**
* 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 = 2011;
/**
* undisplayed copyright notice
*
* @noinspection UnusedDeclaration
*/
private static final String EMBEDDED_COPYRIGHT =
"Copyright: (c) 2011-2017 Roedy Green, Canadian Mind Products, http://mindprod.com";
/**
* date this version released.
*/
private static final String RELEASE_DATE = "2012-03-23";
/**
* Title of the Applet
*/
private static final String TITLE_STRING = "MASM 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 whatToBalance;
/**
* invoke colourising.
*/
private JEButton export;
/**
* invoke colourising.
*/
private JEButton paste;
/**
* display instructions for use
*/
private JEditorPane instructions;
/**
* where we display the code with unbalanced delimiter
*/
private JEditorPane jep;
/**
* let user know progress of colorising
*/
private JLabel progress;
/**
* name of app
*/
private JLabel title;
/**
* build and version
*/
private JLabel title2;
/**
* scrollpane to hold the JEditorPane
*/
private JScrollPane jepScrollPane;
/**
* html-css formatted program text
*/
private String formattedProgramText;
/**
* clipboard contents before we colourise it
*/
private String rawClipboard;
/**
* build all the Swing components.
*/
private void buildComponents()
{
contentPane.setBackground( BACKGROUND_FOR_BODY );
title = new JLabel( TITLE_STRING + " " + VERSION_STRING );
title.setFont( FONT_FOR_TITLE );
title.setForeground( FOREGROUND_FOR_TITLE );
title2 = new JLabel(
"released:" +
RELEASE_DATE +
" build:" +
Build.BUILD_NUMBER
);
title2.setFont( FONT_FOR_TITLE2 );
title2.setForeground( FOREGROUND_FOR_TITLE );
whatToBalance = new JComboBox<>();
// turn off the write-in feature.
whatToBalance.setEditable( false );
whatToBalance.setModel( new DefaultComboBoxModel<>( Balancer.values() ) );
whatToBalance.setSelectedItem( Balancer.ALL );
balancer = Balancer.ALL;
jep = new JEditorPane();
jep.setEditable( false );
jep.setContentType( "text/html" );
jep.setForeground( FOREGROUND_FOR_EDITABLE );
jep.setBackground( BACKGROUND_FOR_EDITABLE );
jep.setMargin( new Insets( 5, 5, 5, 5 ) );
jepScrollPane = new JScrollPane( jep,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
final FastCat sb = new FastCat( 6 );
// cannot use any css, HTML 3.2 only
sb.append(
""
);
sb.append( "" );
sb.append( "- Copy MASM source code with possibly unbalanced segment/ends, "
+ "macro/endm, proc/endp or if/endif into the clipboard from your editor or IDE. In Windows use " +
"Ctrl-C.
\n" );
sb.append( "- Then click Paste to paste it into this program.
\n" );
sb.append( "- Anomalies in the background colours of the operators should "
+ "help you quickly find the error.
\n" );
sb.append( "" );
final String instructionsHTML = sb.toString();
instructions = new JEditorPane();
instructions.setContentType( "text/html" );
instructions.setEditable( false );
instructions.setMargin( new Insets( 5, 5, 5, 5 ) );
instructions.setFont( INSTRUCTIONS_FONT );
instructions.setForeground( FOREGROUND_FOR_INSTRUCTIONS );
try
{
instructions.setText( instructionsHTML );
}
catch ( Exception e )
{
Play.play( MASMBalancer.class, "sound/echo.au" );
err.println( "Because of Sun bug, unable to display instructions, please exit browser and restart" );
e.printStackTrace( err );
err.println( instructionsHTML );
}
paste = new JEButton( "Paste" );
progress = new JLabel();
progress.setFont( PROGRESS_FONT );
progress.setForeground( FOREGROUND_FOR_PROGRESS );
export = new JEButton( "Export" );
}
/**
* build a menu with Look & Feel and About across the top
*/
private void buildMenu()
{
// turn on anti-alias
System.setProperty( "swing.aatext", "true" );
final JMenuBar menubar = new JMenuBar();
setJMenuBar( menubar );
final JMenu lafMenu = Laf.buildLookAndFeelMenu();
if ( lafMenu != null )
{
menubar.add( lafMenu );
}
final JMenu menuHelp = new JMenu( "Help" );
menubar.add( menuHelp );
final JMenuItem aboutItem = new JMenuItem( "About" );
menuHelp.add( aboutItem );
aboutItem.addActionListener( new ActionListener()
{
public void actionPerformed( ActionEvent e )
{
// open about frame
new CMPAboutJBox( Misc.getParentFrame( MASMBalancer.this ),
TITLE_STRING,
VERSION_STRING,
"Helps you balance mismatched proc/endp segment/ends if/endif in MASM source code.",
"",
"freeware",
RELEASE_DATE,
FIRST_COPYRIGHT_YEAR,
"Roedy Green",
"MASMBALANCER",
"1.8" );
}
} );
}
/**
* colourise backgrounds according to the nesting depth
*/
private void colouriseAndDisplayClipboard()
{
progress.setText( "Collecting the clipboard. Please be patient..." );
// contents of the clipboard before processing.
rawClipboard = ClipboardPoker.getClip( this );
if ( rawClipboard != null && rawClipboard.length() > 0 )
{
progress.setText( "Colourising the clipboard. Please be patient..." );
Play.play( MASMBalancer.class, "sound/cork.au" );
// fire up run on separate thread to parse the clipboard and render it
// needs cast even though whatToBalance is generic
balancer = ( Balancer ) whatToBalance.getSelectedItem();
new Thread( this ).start(); // start up run on a new thread.
}
else
{
progress.setText( "empty clipboard..." );
Play.play( MASMBalancer.class, "sound/echo.au" );
err.println( "empty clipboard" );
}
}
/**
* 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( MASMBalancer.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 -------------------- -------------- ------- 2
// 3 instructions---------------------------export-- 3
// 4 -------------------------------------- paste ---4
// 5 progress--------------------------------------- 5
//
// 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,
100.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,
0.0,
GridBagConstraints.WEST,
GridBagConstraints.BOTH,
new Insets( 5, 10, 5, 5 ), // top, left, bottom, right
40,
0 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( export,
new GridBagConstraints( 2,
3,
1,
1,
10.0,
0.0,
GridBagConstraints.EAST,
GridBagConstraints.NONE,
new Insets( 5, 5, 5, 10 ), // top, left, bottom, right
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,
10.0,
0.0,
GridBagConstraints.EAST,
GridBagConstraints.NONE,
new Insets( 5, 5, 5, 10 ), // top, left, bottom, right
10,
10 )
);
// x y w h wtx wty anchor fill T L B R padx pady
contentPane.add( progress,
new GridBagConstraints( 0,
5,
3,
1,
100.0,
0.0,
GridBagConstraints.WEST,
GridBagConstraints.BOTH,
new Insets( 5, 10, 10, 5 ), // top, left, bottom, right
40,
0 )
);
}
/**
* recolourise the clipboard previously extracted with different balancer
*/
private void recolouriseAndDisplayClipboard()
{
final Balancer oldBalancer = balancer;
balancer = ( Balancer ) whatToBalance.getSelectedItem();
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 MASMBalancer(),
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;
instructions = null;
jep = null;
jepScrollPane = null;
paste = 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.
* On entry rawClipboard contains clipboard.
*/
public void run()
{
formattedProgramText = MASMState.colourise( rawClipboard, balancer );
// now we have prepared the html, render the results
SwingUtilities.invokeLater( new Runnable()
{
public void run()
{
try
{
jep.setText( formattedProgramText );
Play.play( MASMBalancer.class, "sound/start.au" );
progress.setText( "" );
}
catch ( Exception e )
{
Play.play( MASMBalancer.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" );
}
}
}
);
}
}