/*******************************************************************************
* Copyright (c) 2010 Yadu.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Yadu - initial API and implementation
******************************************************************************/
package code.google.restclient.ui;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import code.google.restclient.client.HitterClient;
import code.google.restclient.client.Validator;
import code.google.restclient.client.ViewRequest;
import code.google.restclient.client.ViewResponse;
import code.google.restclient.common.RCConstants;
import code.google.restclient.common.RCUtil;
import code.google.restclient.parse.Formatter;
/**
* @author Yaduvendra.Singh
*/
public class MainWindow {
private static final HitterClient client = new HitterClient();
private static final ViewRequest req = new ViewRequest();
private static final ViewResponse resp = new ViewResponse();
private static final String REQ_PROCESS_MSG = "processing...";
private static final String NO_REQ_PROCESS_MSG = ". .";
private static final String REQ_PROCESS_THREAD = "request-process";
private static volatile boolean abortRequest = false;
private static int TOTAL_HORIZ_SPAN = 4;
Thread reqProcThread; // Separate thread to process request
Display display;
Composite shellComposite;
// Widgets
Combo location; //TODO Go to location when Enter is pressed
ToolBar toolbar;
ToolItem itemGo, itemStop;
CCombo httpActionCombo;
SashForm sashForm, sashFormLeft;
Composite headerComposite, paramsComposite, bodyComposite, bottomComposite;
Label processingLabel, headerLabel, paramsLabel, bodyLabel;
StyledText headerText, paramsText, bodyText, reqPaneText, respPaneText;
Button textBodyButton, fileButton;
Color whiteColor, blackColor, greenColor, pinkColor, grayColor;
Browser browser;
AnimatedImage animImage;
public MainWindow() {}
public void render(Composite parent) {
shellComposite = parent;
display = parent.getDisplay();
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = TOTAL_HORIZ_SPAN;
shellComposite.setLayout(gridLayout);
createWidgets();
shellComposite.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
if ( animImage != null ) animImage.releaseThread();
releaseReqProcThread();
RCUtil.cleanUpRespFiles(); // cleanup temporary response files
// Clean up.
if ( animImage != null ) animImage.dispose();
if ( whiteColor != null ) whiteColor.dispose();
if ( blackColor != null ) blackColor.dispose();
if ( greenColor != null ) greenColor.dispose();
if ( pinkColor != null ) pinkColor.dispose();
if ( grayColor != null ) grayColor.dispose();
}
});
}
public void open() {
Display display = new Display();
MainWindow window = new MainWindow();
Shell shell = window.openShell(display);
while ( !shell.isDisposed() ) {
if ( !display.readAndDispatch() ) display.sleep();
}
display.dispose();
}
private Shell openShell(Display display) {
this.display = display;
shellComposite = new Shell(display);
Shell shell = shellComposite.getShell();
shell.setText(RCConstants.APP_DISPLAY_NAME);
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = TOTAL_HORIZ_SPAN;
shellComposite.setLayout(gridLayout);
ImageHelper.addImage(shell, "icons/logo.gif");
createWidgets();
shell.addShellListener(new ShellAdapter() {
@Override
public void shellClosed(ShellEvent e) {
if ( animImage != null ) animImage.releaseThread();
releaseReqProcThread();
e.doit = true;
RCUtil.cleanUpRespFiles(); // cleanup temporary response files
}
});
shellComposite.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
// Clean up.
if ( animImage != null ) animImage.dispose();
if ( whiteColor != null ) whiteColor.dispose();
if ( blackColor != null ) blackColor.dispose();
if ( greenColor != null ) greenColor.dispose();
if ( pinkColor != null ) pinkColor.dispose();
if ( grayColor != null ) grayColor.dispose();
}
});
shell.open();
return shell;
}
private void createWidgets() {
// Address bar combo using standard SWT
GridData data = new GridData();
location = new Combo(shellComposite, SWT.BORDER);
data = new GridData();
data.horizontalAlignment = SWT.FILL;
data.horizontalSpan = 1;
data.grabExcessHorizontalSpace = true;
location.setLayoutData(data);
// Tool bar buttons
toolbar = new ToolBar(shellComposite, SWT.NONE);
itemGo = new ToolItem(toolbar, SWT.PUSH);
itemGo.setToolTipText("Go");
ImageHelper.addImage(itemGo, "icons/go.gif", "&Go");
itemStop = new ToolItem(toolbar, SWT.PUSH);
itemStop.setToolTipText("Stop");
itemStop.setEnabled(false); // Initially disabled
ImageHelper.addImage(itemStop, "icons/stop.gif", "&Stop");
data = new GridData();
data.horizontalSpan = 1;
toolbar.setLayoutData(data);
processingLabel = new Label(shellComposite, SWT.NONE);
processingLabel.setText(NO_REQ_PROCESS_MSG);
data = new GridData();
data.horizontalSpan = 1;
processingLabel.setLayoutData(data);
/*
Canvas canvas = new Canvas(shellComposite, SWT.NONE);
data = new GridData();
data.horizontalSpan = 1;
canvas.setLayoutData(data);
animImage = new AnimatedImage(canvas, "icons/loader.gif");
animImage.drawImage(false);
*/
// Creating combo drop down using custom SWT
httpActionCombo = new CCombo(shellComposite, SWT.BORDER);
httpActionCombo.add("GET");
httpActionCombo.add("POST");
httpActionCombo.add("PUT");
httpActionCombo.add("DELETE");
httpActionCombo.add("HEAD");
httpActionCombo.add("OPTIONS");
httpActionCombo.add("TRACE");
httpActionCombo.select(0);
httpActionCombo.setEditable(false);
whiteColor = new Color(Display.getCurrent(), 255, 255, 255);
httpActionCombo.setBackground(whiteColor);
data = new GridData();
data.horizontalSpan = 1;
httpActionCombo.setLayoutData(data);
// Create sash form
sashForm = new SashForm(shellComposite, SWT.HORIZONTAL);
sashForm.SASH_WIDTH = 4; // Change the width of the sashes
sashForm.setBackground(shellComposite.getDisplay().getSystemColor(SWT.COLOR_WHITE)); // Change the color used to
// paint
// the sashes
data = new GridData();
data.horizontalAlignment = SWT.FILL;
data.verticalAlignment = SWT.FILL;
data.horizontalSpan = TOTAL_HORIZ_SPAN;
data.grabExcessHorizontalSpace = true;
data.grabExcessVerticalSpace = true;
sashForm.setLayoutData(data);
/* ** Create first inner SashForm ** */
sashFormLeft = new SashForm(sashForm, SWT.VERTICAL);
// Change the width of the sashes
sashFormLeft.SASH_WIDTH = 2;
// Change the color used to paint the sashes
sashFormLeft.setBackground(shellComposite.getDisplay().getSystemColor(SWT.COLOR_GRAY));
// Create styled texts on composites placed on left sash form
// Headers
headerComposite = new Composite(sashFormLeft, SWT.NONE);
headerComposite.setLayout(new GridLayout());
headerLabel = new Label(headerComposite, SWT.NONE);
headerLabel.setText("Headers");
headerText = new StyledText(headerComposite, SWT.V_SCROLL);
headerText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
headerText.setWordWrap(true);
headerText.setBackground(DecorHelper.COLOR.LIGHT_GREEN.getColor());
headerText.setFont(DecorHelper.getDefaultFont()); // set font
headerText.setText(RCConstants.HEADER_TEXT);
headerText.setStyleRange(DecorHelper.getGrayItalicStyle(RCConstants.HEADER_TEXT));
// Params
paramsComposite = new Composite(sashFormLeft, SWT.NONE);
paramsComposite.setLayout(new GridLayout());
paramsLabel = new Label(paramsComposite, SWT.NONE);
paramsLabel.setText("Params");
paramsText = new StyledText(paramsComposite, SWT.V_SCROLL);
paramsText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
paramsText.setWordWrap(true);
paramsText.setBackground(DecorHelper.COLOR.LIGHT_GREEN.getColor());
paramsText.setFont(DecorHelper.getDefaultFont()); // set font
paramsText.setText(RCConstants.PARAMS_TEXT);
paramsText.setStyleRange(DecorHelper.getGrayItalicStyle(RCConstants.PARAMS_TEXT));
// Body
bodyComposite = new Composite(sashFormLeft, SWT.NONE);
bodyComposite.setLayout(new GridLayout(3, false));
bodyLabel = new Label(bodyComposite, SWT.NONE);
bodyLabel.setText("Body");
data = new GridData();
data.horizontalAlignment = SWT.LEFT;
data.horizontalSpan = 1;
bodyLabel.setLayoutData(data);
textBodyButton = new Button(bodyComposite, SWT.CHECK);
textBodyButton.setText("&Use body text");
data = new GridData();
data.horizontalAlignment = SWT.RIGHT;
data.horizontalSpan = 1;
textBodyButton.setLayoutData(data);
fileButton = new Button(bodyComposite, SWT.PUSH);
fileButton.setText("&File");
data = new GridData();
data.horizontalAlignment = SWT.RIGHT;
data.horizontalSpan = 1;
fileButton.setLayoutData(data);
bodyText = new StyledText(bodyComposite, SWT.V_SCROLL);
data = new GridData(SWT.FILL, SWT.FILL, true, true);
data.horizontalSpan = 3;
bodyText.setLayoutData(data);
bodyText.setWordWrap(true);
bodyText.setEnabled(false);
bodyText.setBackground(DecorHelper.COLOR.LIGHT_PINK.getColor());
bodyText.setFont(DecorHelper.getDefaultFont()); // set font
bodyText.setText(RCConstants.BODY_TEXT);
bodyText.setStyleRange(DecorHelper.getGrayItalicStyle(RCConstants.BODY_TEXT));
/* ** First inner SashForm ends here ** */
// Request Pane
reqPaneText = new StyledText(sashForm, SWT.V_SCROLL); // | SWT.H_SCROLL);
reqPaneText.setWordWrap(true);
reqPaneText.setEditable(false);
reqPaneText.setBackground(DecorHelper.COLOR.LIGHT_YELLOW.getColor());
reqPaneText.setFont(DecorHelper.getDefaultFont()); // set font
reqPaneText.setText(RCConstants.REQUEST_DETAIL);
reqPaneText.setStyleRange(DecorHelper.getGrayItalicStyle(RCConstants.REQUEST_DETAIL));
// Response Pane
respPaneText = new StyledText(sashForm, SWT.V_SCROLL); // | SWT.H_SCROLL);
respPaneText.setWordWrap(true);
respPaneText.setEditable(false);
respPaneText.setBackground(DecorHelper.COLOR.LIGHT_YELLOW.getColor());
respPaneText.setFont(DecorHelper.getDefaultFont()); // set font
respPaneText.setText(RCConstants.RESPONSE_DETAIL);
respPaneText.setStyleRange(DecorHelper.getGrayItalicStyle(RCConstants.RESPONSE_DETAIL));
// Browser
try {
browser = new Browser(sashForm, SWT.NONE);
} catch ( SWTError e ) {
System.err.println("Could not instantiate Browser: " + e.getMessage());
return;
}
// TODO test text
browser.setText("<marquee behavior=\"alternate\" scrolldelay=\"600\"> Browser </marquee>");
data = new GridData();
data.horizontalAlignment = SWT.FILL;
data.verticalAlignment = SWT.FILL;
data.horizontalSpan = 1;
data.grabExcessHorizontalSpace = true;
data.grabExcessVerticalSpace = true;
browser.setLayoutData(data);
bottomComposite = new Composite(shellComposite, SWT.NONE);
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 3;
bottomComposite.setLayout(gridLayout);
addListeners();
}
/* **** Event Handling Starts Here **** */
private void addListeners() {
// Selection listener
SelectionListener selListener = getSelectionListener();
fileButton.addSelectionListener(selListener);
httpActionCombo.addSelectionListener(selListener);
itemGo.addSelectionListener(selListener);
itemStop.addSelectionListener(selListener);
textBodyButton.addSelectionListener(selListener);
// Key press and focus events
KeyListener keyListener = getTextKeyListener();
FocusListener focusListener = getTextFocusListener();
headerText.addKeyListener(keyListener);
headerText.addFocusListener(focusListener);
paramsText.addKeyListener(keyListener);
paramsText.addFocusListener(focusListener);
bodyText.addKeyListener(keyListener);
bodyText.addFocusListener(focusListener);
reqPaneText.addKeyListener(keyListener);
reqPaneText.addFocusListener(focusListener);
respPaneText.addKeyListener(keyListener);
respPaneText.addFocusListener(focusListener);
}
private SelectionListener getSelectionListener() {
return new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
if ( event.getSource().equals(fileButton) ) { // file button
FileDailogForm.openFileDialog(shellComposite.getShell(), req);
if ( !RCUtil.isEmpty(req.getFilePath()) ) {
httpActionCombo.select(1);
enableBody(bodyText, textBodyButton, false); // Disable body
}
}
if ( event.getSource().equals(httpActionCombo) ) { // enable body pane if POST is selected
boolean selection = RCUtil.isEntityEnclosingMethod(httpActionCombo.getText());
if ( selection ) req.setFilePath(null);
enableBody(bodyText, textBodyButton, selection);
}
if ( event.getSource().equals(itemGo) ) { // Go button
processRequest();
}
if ( event.getSource().equals(itemStop) ) { // Stop button
client.setAbort(true);
abortRequest = true;
switchGoStop(true);
}
if ( event.getSource().equals(textBodyButton) ) { // Use text body button
boolean selection = textBodyButton.getSelection();
if ( selection ) {
req.setFilePath(null);
httpActionCombo.select(1); // Select POST
} else httpActionCombo.select(0); // Select GET
enableBody(bodyText, textBodyButton, selection);
}
}
};
}
private KeyListener getTextKeyListener() { // CTRL+A (Select all)
return new KeyAdapter() {
@Override
public void keyPressed(KeyEvent event) {
StyledText textWidget = (StyledText) event.widget;
if ( event.stateMask == SWT.CTRL && event.keyCode == 97 ) {
textWidget.selectAll();
}
}
};
}
private FocusListener getTextFocusListener() { // Clear intro text
return new FocusAdapter() {
@Override
public void focusGained(FocusEvent event) {
StyledText textWidget = (StyledText) event.widget; // TextBox
String text = textWidget.getText();
if ( RCConstants.HEADER_TEXT.equals(text) || RCConstants.PARAMS_TEXT.equals(text) || RCConstants.BODY_TEXT.equals(text)
|| RCConstants.REQUEST_DETAIL.equals(text) || RCConstants.RESPONSE_DETAIL.equals(text) ) {
textWidget.setText("");
textWidget.setStyleRange(null);
}
}
};
}
/* **** Event Handling Ends Here **** */
/* **** Helper Methods **** */
private void addLocationUrl(Combo location, String newUrl) {
boolean urlFound = false;
for ( String url : location.getItems() ) {
if ( url.equals(newUrl) ) urlFound = true;
}
if ( !urlFound ) location.add(newUrl, 0);
}
private void processRequest() {
String url = Validator.validateUrl(location.getText());
location.setText(url); // Reflect validated url
if ( url != null ) {
// Prepare req
req.setUrl(url);
req.setMethod(httpActionCombo.getText());
req.setHeadersStr(headerText.getText());
req.setParamsStr(paramsText.getText());
if ( bodyText.getEnabled() && !RCConstants.BODY_TEXT.equals(bodyText.getText()) ) {
req.setBodyStr(bodyText.getText());
}
addLocationUrl(location, url); // Add new url to location drop down
// Clear previous data
reqPaneText.setText("");
respPaneText.setText("");
browser.setText("");
client.setAbort(false);
abortRequest = false;
// Hit url
reqProcThread = new Thread(REQ_PROCESS_THREAD) {
@Override
public void run() {
try {
switchGoStop(false);
client.hit(req, resp); // ** Hitting URL **
display.syncExec(new Runnable() { // execute as sync otherwise "invalid thread access" error
@Override
public void run() {
String hostUrl = req.getScheme() + "://" + req.getHost() + (req.getPort() == -1 ? "" : ":" + req.getPort());
// Populating request pane
populateReqPan(reqPaneText);
req.clear(); // Clear req object for next request
if ( !abortRequest ) {
// Populating response pane
populateRespPan(respPaneText);
// Populating browser
populateBrowser(browser, resp.getDisplayBodyPart(), resp.getBodyFilePath(), hostUrl);
}
resp.clear(); // Clear resp object for next response
}
});
} catch ( final Exception ex ) {
display.syncExec(new Runnable() {
@Override
public void run() {
populateReqPan(reqPaneText); // Populate request even if response failed with error
req.clear();
respPaneText.setText(ex.getMessage());
resp.clear();
}
});
} finally {
switchGoStop(true);
}
}
};
reqProcThread.start();
}
}
/**
* This must be called under dispose listener of its parent shellComposite to stop animation thread
*/
public void releaseReqProcThread() {
client.setAbort(true); // let thread end processing prematurely
if ( reqProcThread != null ) {
// wait for the thread to die before disposing the shellComposite.
while ( reqProcThread.isAlive() ) {
if ( !display.readAndDispatch() ) display.sleep();
}
}
}
private void populateReqPan(StyledText reqPanText) {
String headerPart = req.getDisplayHeaderPart();
String bodyPart = req.getDisplayBodyPart();
if ( headerPart == null ) headerPart = "";
if ( bodyPart == null ) bodyPart = "";
if ( !RCUtil.isEmpty(headerPart) ) {
if ( !RCUtil.isEmpty(bodyPart) ) {
String prettyBodyTxt = getPrettyTxt(bodyPart);
if ( !RCUtil.isEmpty(prettyBodyTxt) ) bodyPart = prettyBodyTxt;
StyleRange reqPanStyle = DecorHelper.getStyle(headerPart.length(), bodyPart.length() + 2, -1, SWT.COLOR_BLUE);
reqPanText.setText(headerPart + "\n\n" + bodyPart);
reqPanText.setStyleRange(reqPanStyle);
} else reqPanText.setText(headerPart);
}
}
private void populateRespPan(StyledText respPanText) {
String headerPart = resp.getDisplayHeaderPart();
String bodyPart = resp.getDisplayBodyPart();
if ( headerPart == null ) headerPart = "";
if ( bodyPart == null ) bodyPart = "";
if ( !RCUtil.isEmpty(headerPart) ) {
if ( !RCUtil.isEmpty(bodyPart) ) {
String prettyBodyTxt = getPrettyTxt(bodyPart);
if ( !RCUtil.isEmpty(prettyBodyTxt) ) bodyPart = prettyBodyTxt;
StyleRange respPanStyle = DecorHelper.getStyle(headerPart.length(), bodyPart.length() + 2, -1, SWT.COLOR_BLUE);
respPanText.setText(headerPart + "\n\n" + bodyPart);
respPanText.setStyleRange(respPanStyle);
} else respPanText.setText(headerPart);
}
}
private String getPrettyTxt(String text) {
// Pretty XML
if ( text.trim().startsWith("<?xml") ) return Formatter.getIndentedXml(text, 2);
// Pretty Json
if ( text.trim().startsWith("{") ) return Formatter.getIndentedJson(text, 2);
return "";
}
private void populateBrowser(Browser browser, String bodyPart, String filePath, String hostUrl) {
// First clear browser of any old content
browser.setUrl("");
browser.setText("");
// Populate browser
if ( !RCUtil.isEmpty(filePath) ) browser.setUrl("file:///" + filePath);
else {
String baseTag = "<base href=\"" + hostUrl + "\">";
if ( !RCUtil.isEmpty(bodyPart) ) {
bodyPart = bodyPart.replaceFirst("(?i)<head>", "<head>" + baseTag);
browser.setText(bodyPart);
}
}
}
private void switchGoStop(final boolean enableGo) {
display.syncExec(new Runnable() {
@Override
public void run() {
if ( itemGo != null && itemStop != null ) {
if ( enableGo ) {
itemGo.setEnabled(true);
itemStop.setEnabled(false);
processingLabel.setText(NO_REQ_PROCESS_MSG);
} else {
itemGo.setEnabled(false);
itemStop.setEnabled(true);
processingLabel.setText(REQ_PROCESS_MSG);
}
}
}
});
}
private void enableBody(StyledText bodyText, Button bodyTextCheck, boolean selection) {
if ( selection ) {
greenColor = DecorHelper.COLOR.LIGHT_GREEN.getColor();
blackColor = DecorHelper.getColor(SWT.COLOR_BLACK);
String text = bodyText.getText();
bodyText.setEnabled(true);
bodyText.setBackground(greenColor);
if ( !RCConstants.BODY_TEXT.equals(text) ) {
bodyText.setStyleRange(DecorHelper.getStyle(0, bodyText.getText().length(), SWT.NORMAL, blackColor));
}
bodyTextCheck.setSelection(true); // Enable body check box
} else {
pinkColor = DecorHelper.COLOR.LIGHT_PINK.getColor();
grayColor = DecorHelper.getColor(SWT.COLOR_GRAY);
bodyText.setEnabled(false);
bodyText.setStyleRange(DecorHelper.getStyle(0, bodyText.getText().length(), SWT.READ_ONLY, grayColor));
bodyText.setBackground(pinkColor);
bodyTextCheck.setSelection(false); // Disable body check box
}
}
public static void main(String[] args) {
new MainWindow().open();
}
}