2009-11-15

Making Windows a better place to be

Many developers including me are forced to use Windows at work due to various corporate reasons even when our work has nothing to do with Microsoft technologies. And we are all stuck with that terrible Command Prompt or a Power Shell which are crap compared to a proper terminal. However, there is a way to have a fully functional Unix terminal in Windows thanks to Cygwin and a patched version of Putty

I am doing this for several years and this is one of the first things I do in any Windows machine that I want to do some software development on, so it's time to come up with a little tutorial.

Setting up Cygwin and PuttyCyg

Getting Cygwin

Go to http://www.cygwin.com to download and run the setup.exe. Installation process is illustrated in screenshots below.
 


I suggest you installing cygwin directly to C:\cygwin as it's recommended. It's a significant part of your system and it is really worth the place in C:\ directly.



Cygwin offers "C:\Windows\System32" as a place for local packages, however I don't like that idea and keep it all under "C:\cygwin".


Some mirrors were failing for me with missing packages so I choose a respectable source and deal with slower download speed in exchange for reliability.



Choosing packages may take a while. You can go with default selection, however going through the long list is worth a while if you want to have goodies like vim, curl, ping, openssh, mc and so on.


Now go make yourself some coffee.


Don't add any icons or shortcuts because they open cygwin in that ugly command prompt we are about to get rid of.


Introducing Cygwin to your Windows

Now that you have the Cygwin, try it out by launching C:\cygwin\cygwin.bat. You will see something like this.


As you can see, a few files can be modified to configure your environment. Keep them in mind for future:
  • ~/.bashrc
  • ~/.bash_profile
  • ~/.inputrc

Now append these to the beginning of your Windows PATH environmental variable:

C:\cygwin\usr\local\bin;C:\cygwin\usr\bin;C:\cygwin\bin;



Run a fresh Command Prompt (if you are using Launchy or a similar program to run the cmd.exe, you have to restart it before the new PATH applies to programs you run.) and you will be able to do the following:


That's just a small benefit you get from cygwin, but it's handy to have unix commands anywhere in the system. If you think that cmd.exe is enough to enjoy unix commands, you are wrong. It fails to resize properly, it's hard to copy and paste. Compare using Putty with a remote machine to using Command Prompt on your local Windows box, It's like day and night.

Getting PuttyCyg

Get the latest puttycyg from http://code.google.com/p/puttycyg/. For the past year it's this one: http://puttycyg.googlecode.com/files/puttycyg-20090811.zip. You can put it in c:\cygwin\puttycyg to avoid a mixup with the original Putty if you use it.

Configuring PuttyCyg to access local cygwin

Create a shortcut to putty.exe from the puttycyg pack that you just extracted.


Run it to configure the local connection. Check "Cygterm" in Connection type and enter a minus (-) in Command.


Be sure to go to Window configuration and set Lines of scrollback to something more reasonable than the default 200.


Save the settings. Name the session "localhost".


Now click "Open" to test the configuration. You should see a working terminal.


You can now enhance the shortcut you created earlier to load the "localhost" session automatically. Just append "-load localhost" to the end of Target.


You're all set. A few resources to get you familiar with Cygwin:

2009-09-29

XML processing in Java

One of the things that most Java developers tackle on daily basis is dealing with XML. Despite the fact that XML is taking lots of criticism and new formats like YAML are emerging and becoming more popular, you cannot avoid XML it's too widespread and used everywhere. It's the main format for interchanging data across systems and even people. There is a great deal of fat books that show how to use various XML APIs and libraries to handle the beast with all it's standards and extensions. There are many solid tools that had been continuously developed for years by large communities (Xalan, Xerces, JDOM, DOM4J).

And still XML processing in Java is still a major pain in the ass.
I see two reasons for that: 
  1. XML is too bloated as a format. See the picture below (click to enlarge):
  2. Java libraries that deal with XML are bloated. It's natural because they simply try to implement the specifications

Let's say you have a Java application which receives some data in form of simple XML:

<?xml version="1.0" encoding="UTF-8"?>
<data>
  <entry id="1">entry number one</entry>
<entry id="2">entry number two</entry>
</data>
Your application has this class:

public class Entry {
private int id;
private String content;
//the usual setters and getters here
}
If you would want to parse this XML with Java, into Entry objects you would usually do something like this:

try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File("data.xml"));
NodeList nl = doc.getElementsByTagName("entry");
for (int i = 0; i < nl.getLength(); i++) {
Entry entry = new Entry();
entry.setId(Integer.parseInt(nl.item(i).getAttributes()
.getNamedItem("id").getNodeValue()));
entry.setContent(nl.item(i).getTextContent());
System.out.println(entry);
//do real stuff
}
}
catch (final Exception e) {
System.out.println("Failed parsing: " + e);
//do real handling
}
Expected output:

Entry:{id: 1; content: entry number one}
Entry:{id: 2; content: entry number two}
In Java 6 DocumentBuilderFactory.newInstance() will usually return an instance of this implementation: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl 
This is Xerces embedded into the JRE. What's wrong with that? First, it's a huge library with big memory footprint. It will be outdated in comparison with what you can get at the official homepage, so if you want to go for the latest version with all the bug fixes, you will have to add another megabyte of jars to your project, set a system property (javax.xml.parsers.DocumentBuilderFactory) to change the default implementation and hope your code works. Then you have to know DOM. You have to use an ugly for loop to iterate the results instead of doing it right (for (Node n : doc.getElementsByTagName("entry") { ... }).
Even though Java aims to be loosely coupled, you can use the API and switch implementations, you should keep in mind that API changes over time, and implementations work differently. I have seen legacy code where you can find sick things like DocumentBuilderFactoryImpl = (DocumentBuilderFactoryImpl) DocumentBuilderFactory.newInstance();, I have seen Axis failing to parse complex SOAP messages after switching to different, newer JDK, I have seen third party software vendors who start cursing when you change your web service implementation and your WSDL is generated with minor cosmetic differences (i.e.: xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" instead of previous version: xmlns:s="http://schemas.xmlsoap.org/wsdl/soap/"). In all these cases APIs and implementations failed to do what they were meant for. Of course, everything can be fixed, but it takes time and nerves, and these things are precious.
XML processing in Java is terrible, and the worst part is when you have to go through all this just to parse a simple piece of data. Why couldn't it be as simple as that:

for (XmlSlicer piece : XmlSlicer.cut(data).getTags("entry")) {
//each piece is: <entry id="...">...</entry>
Entry entry = new Entry();
entry.setId(Integer.parseInt(piece.getTagAttribute("entry", "id")));
entry.setContent(piece.get("entry").toString());
System.out.println(entry);   
//do real stuff
}
After being fed up with Java's great XML APIs and libraries I made a small tool for simple daily work with XML files.
The code above would work in XML Zen - a small and lightweight XML processing library that supports ~1% of what other XML processing libraries can do, however this 1% of functionality is what you use 90% of the time. There are no big APIs, just simple logic driven object oriented processing of XML strings. And it's just a little over 10Kb.
You can add XML Zen dependency with Maven, just set the dev.java.net repo first:

<repositories>
<repository>
  <id>maven2-repository.dev.java.net</id>
  <name>Java.net Repository for Maven</name>
  <url>http://download.java.net/maven/2</url>
  </repository>
  <!-- other repositories -->
</repositories>
Then the dependency:

<dependency>
    <groupId>com.googlecode.xmlzen<groupId>
    <artifactId>xmlzen</artifactId>
    <version>0.1.1</version>
</dependency>
That's it, you are ready to go. And when it comes to building XML and XML Zen is not enough for your needs, check out this great project: http://java.ociweb.com/mark/programming/WAX.html.

2009-06-17

A tool for unpacking multiple archives and other ramblings

I've always wondered why torrents are packed into multiple archives, sometimes even archives inside other archives. Anyway, I've got fed up with all the unpacking routines, especially after downloading several seasons of something that has each episode in an individual folder containing 20 rar or zip files. So, after one evening of coding this nightmare is now over.

Here is the screenshot of the stupidly named tool (click to visit project page):


It was also a good reason to try out new version of NetBeans. I still hate GUI builders, however for small "write and forget" kind of projects like Multi Unpacker it's a fairly good choice. However NetBeans is still slow and unresponsive in comparison with Eclipse.

So far Multi Unpacker is for Windows only, however it's a spare evening away from becoming cross-platform (and you are welcome to join the project). Too bad my MacBook broke down completely, so Macs will most probably not get any special treatment... This is also bad news for Hawkscope, unless someone is willing to donate me an old Mac? :)

2009-06-07

Cheatsheet: Unicode characters for buttons and GUI elements

Before drawing your own graphics for various GUI buttons, you could try finding a Unicode character that represents the thing you want to do. For instance, up/down arrows can be made with 25B2 (▲) and 25BC (▼).

Here are some pictures with Unicode characters that you can use to build GUIs. First a quick guide to using these:



Now, the cheatsheets:











These were captured from a tool named Korais.

You can download these images in a single PDF file: unicode.gui.cheatsheet.pdf

2009-04-27

Symbolic links in Windows

Surprisingly, all the good Windows features are hidden, undocumented and hard to find. It took me nearly a decade to accidentally find out that Windows has symbolic links. They are called NTFS Junction Points. However their support is limited to directory links, and the usage is a bit weird.

Oh, and you have to install Windows Resource Kit to get the functionality. You can download it from any of these locations:


The command you want is linkd. Let's take it for a spin.



The sandbox contains a directory named original with text.txt inside.

To create a symbolic link named symlinked that points to original, the command is linkd symlinked original. In POSIX it would be ln -s original symlinked.



When calling dir, symlinked shows as "junction". The other behavior is like a plain folder. In explorer you cannot tell the difference between the two.



To delete the symlink use rmdir, because del will attempt to remove the files from original directory.

2009-03-21

Gmail4J - Gmail API for Java



Seems that Google has no Gmail API available, so I made a small Java library called Gmail4J. The library is designed to be extensible, allowing various implementations. Currently the only available implementation allows getting new unread messages from their RSS feed. That's not much, but it's a start.

Here is the example code (updated to conform with Gmail4J 0.2):

GmailClient client = new RssGmailClient();
GmailConnection connection = new HttpGmailConnection("user", "pass".toCharArray());
client.setConnection(connection);
final List<GmailMessage> messages = client.getUnreadMessages();
for (GmailMessage message : messages) {
System.out.println(message);
}

Next implementation will probably be based on JavaMail IMAP functionality. It should be able to do more than getting unread messages.

2009-03-15

JAD Java Decompiler download mirror

As http://www.kpdus.com is no longer accessible, JAD Java Decompiler download is extremely hard to find. I've put up a mirror where you can get jad executable for Windows, Linux and Mac OS X: http://www.varaneckas.com/jad

Hope this helps those who are having a hard time finding a working JAD download.

2009-02-28

Hawkscope: Twitter plugin

The new release of Hawkscope has a Twitter plugin. Here's how you can install and use it:

Installing

First, go to Hawkscope Settings



Then go to Plugins tab and click Get Plugins



Hawkscope Plugins page will open in your Browser, click on twitter-1.0.jar to download it





Go to your download folder to find the plugin



Then go back to Hawkscope Settings Plugins tab and click Open in Plugin Location



A new Finder (or another file navigator) window will open. You will have to drag and drop twitter-1.0.jar from your downloads to Hawkscope plugins folder





Then, in Hawkscope Settings Plugins tab click Reload Plugins. You should see Twitter plugin in Available Plugins list



Close Hawkscope Settings window (click OK). Then if you open Hawkscope menu you will see a sad Twitter item. It's sad because there is no configuration.



Configuring

Go to Settings again. Your settings now has a Twitter tab



Enter your Twitter username and password. You can choose what elements to display. I chose not to see my own tweets. Click OK to apply your settings.



Your Twitter Hawkscope menu item is now enabled


Using

Click Tweet! to add a new Twitter status message



And you can also see more tweets or visit them in browser by clicking



Enjoy! And by the way, this plugin works on all operating systems that Hawkscope supports - Windows, Linux (Gnome) and Mac.

2009-02-13

Screw all GUI builders

Are you making your GUI with a builder? Do you like the generated code you get? I hate it. Even though I like the idea of building GUI with visual means (WYSIWYG), I can't stand the mess that code generators produce. In addition to that, there are more serious downsides:

  • You don't know how exactly the generated code works. You don't need to. You start not to care and GUI application development becomes a process of drawing and adding simple event handlers here and there.

  • Most GUI builders force you to use single class for single window, so generated classes tend to have thousands of lines of code.

  • Most GUI builders don't want you to modify the generated code. And if you do, they either break or rewrite your code.

  • GUI builders force you to use an IDE, mostly one you started coding with. So if you start with NetBeans, you most likely be forced to stay with it for the whole project.

  • The generated code is far from being optimal. It's not resize-friendly, not dynamic enough, it has many hard-coded values, refactoring is most likely impossible, because builder would not allow that.

So, why are you using GUI builders? Is it because you're doing GUI apps during your day job, you need fast results and you don't want to learn more than you have to? Or you just have no choice? That's reasonable, but when you have a choice, consider learning how Swing or SWT works, spend some time reading the API docs and examining the code - it's amazing how fast and dynamic your GUI building process can get when you finally get a clear understanding HOW to use all the widgets and layouts. Let me show you. Here's a window from Hawkscope app that I'm making in my spare time. It was generated with Jigloo GUI builder in Eclipse. First let's see how it looks:



The code (all comments removed):
package com.varaneckas.hawkscope.gui;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import com.cloudgarden.resource.SWTResourceManager;
import com.varaneckas.hawkscope.Version;
import com.varaneckas.hawkscope.cfg.ConfigurationFactory;
import com.varaneckas.hawkscope.util.IOUtils;
import com.varaneckas.hawkscope.util.IconFactory;
import com.varaneckas.hawkscope.util.OSUtils;

public class AboutWindow extends org.eclipse.swt.widgets.Dialog {

private Shell dialogShell;
private Canvas logoCanvas;
private Label appNameLabel;
private Label appSloganLabel;
private Label appVersion;
private Label appHomepageValue;
private Button copyReportButton;
private Button closeButton;
private Label environmentLabel;
private Text environmentTextArea;
private Label appHomepageLabel;
private Label appReleasedValue;
private Label appReleasedLabel;
private Label appVersionValue;

public AboutWindow(final Shell parent, final int style) {
super(parent, style);
}

public synchronized void open() {
if (dialogShell != null && !dialogShell.isDisposed()) {
dialogShell.setVisible(true);
dialogShell.forceFocus();
return;
}
final Shell parent = getParent();
dialogShell = new Shell(parent, SWT.DIALOG_TRIM
| SWT.APPLICATION_MODAL);
{
SWTResourceManager.registerResourceUser(dialogShell);
}
dialogShell.setImage(IconFactory.getInstance()
.getUncachedIcon("hawkscope16.png"));
dialogShell.setText("About");

dialogShell.setLayout(new FormLayout());
dialogShell.layout();
dialogShell.pack();
dialogShell.setSize(516, 322);
{
copyReportButton = new Button(dialogShell, SWT.PUSH | SWT.CENTER);
FormData copyReportButtonLData = new FormData();
copyReportButtonLData.width = 125;
copyReportButtonLData.height = 29;
copyReportButtonLData.left = new FormAttachment(0, 1000, 314);
copyReportButtonLData.top = new FormAttachment(0, 1000, 252);
copyReportButton.setLayoutData(copyReportButtonLData);
copyReportButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
IOUtils.copyToClipboard(Version.getEnvironmentReport());
}
});
copyReportButton.setText("Co&py to Clipboard");
OSUtils.adjustButton(copyReportButton);
}
{
closeButton = new Button(dialogShell, SWT.PUSH | SWT.CENTER);
FormData closeButtonLData = new FormData();
closeButtonLData.width = 47;
closeButtonLData.height = 29;
closeButtonLData.left = new FormAttachment(0, 1000, 451);
closeButtonLData.top = new FormAttachment(0, 1000, 252);
closeButton.setLayoutData(closeButtonLData);
closeButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
dialogShell.dispose();
}
});
closeButton.setText("&Close");
OSUtils.adjustButton(closeButton);
}
{
environmentLabel = new Label(dialogShell, SWT.NONE);
FormData environmentLabelLData = new FormData();
environmentLabelLData.width = 486;
environmentLabelLData.height = 17;
environmentLabelLData.left = new FormAttachment(0, 1000, 12);
environmentLabelLData.top = new FormAttachment(0, 1000, 127);
environmentLabel.setLayoutData(environmentLabelLData);
environmentLabel.setText("Environment");
environmentLabel.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
environmentTextArea = new Text(dialogShell, SWT.MULTI | SWT.WRAP
| SWT.V_SCROLL | SWT.BORDER);
FormData environmentTextAreaLData = new FormData();
environmentTextAreaLData.width = 468;
environmentTextAreaLData.height = 90;
environmentTextAreaLData.left = new FormAttachment(0, 1000, 12);
environmentTextAreaLData.top = new FormAttachment(0, 1000, 150);
environmentTextArea.setLayoutData(environmentTextAreaLData);
environmentTextArea.setText(Version.getSystemProperties());
environmentTextArea.setEditable(false);
}
{
appHomepageValue = new Label(dialogShell, SWT.NONE);
appHomepageValue.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
Program.launch(Version.HOMEPAGE);
}
});
appHomepageValue.setCursor(new Cursor(dialogShell.getDisplay(),
SWT.CURSOR_HAND));
appHomepageValue.setForeground(
new Color(dialogShell.getDisplay(), 0, 0, 255));
FormData appHomepageValueLData = new FormData();
appHomepageValueLData.width = 242;
appHomepageValueLData.height = 17;
appHomepageValueLData.left = new FormAttachment(0, 1000, 256);
appHomepageValueLData.top = new FormAttachment(0, 1000, 104);
appHomepageValue.setLayoutData(appHomepageValueLData);
appHomepageValue.setToolTipText("Click to open in browser");
appHomepageValue.setText(Version.HOMEPAGE);
}
{
appHomepageLabel = new Label(dialogShell, SWT.NONE);
FormData appHomepageLabelLData = new FormData();
appHomepageLabelLData.width = 94;
appHomepageLabelLData.height = 17;
appHomepageLabelLData.left = new FormAttachment(0, 1000, 156);
appHomepageLabelLData.top = new FormAttachment(0, 1000, 104);
appHomepageLabel.setLayoutData(appHomepageLabelLData);
appHomepageLabel.setText("Homepage:");
appHomepageLabel.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
appReleasedValue = new Label(dialogShell, SWT.NONE);
FormData appReleasedValueLData = new FormData();
appReleasedValueLData.width = 242;
appReleasedValueLData.height = 17;
appReleasedValueLData.left = new FormAttachment(0, 1000, 256);
appReleasedValueLData.top = new FormAttachment(0, 1000, 81);
appReleasedValue.setLayoutData(appReleasedValueLData);
appReleasedValue.setText(Version.VERSION_DATE);
}
{
appReleasedLabel = new Label(dialogShell, SWT.NONE);
FormData appReleasedLabelLData = new FormData();
appReleasedLabelLData.width = 77;
appReleasedLabelLData.height = 17;
appReleasedLabelLData.left = new FormAttachment(0, 1000, 156);
appReleasedLabelLData.top = new FormAttachment(0, 1000, 81);
appReleasedLabel.setLayoutData(appReleasedLabelLData);
appReleasedLabel.setText("Released:");
appReleasedLabel.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
appVersionValue = new Label(dialogShell, SWT.NONE);
FormData appVersionValueLData = new FormData();
appVersionValueLData.width = 242;
appVersionValueLData.height = 17;
appVersionValueLData.left = new FormAttachment(0, 1000, 256);
appVersionValueLData.top = new FormAttachment(0, 1000, 58);
appVersionValue.setLayoutData(appVersionValueLData);
if (Version.isUpdateAvailable() == null) {
appVersionValue.setText(Version.VERSION_NUMBER);
if (ConfigurationFactory.getConfigurationFactory()
.getConfiguration().checkForUpdates()) {
appVersionValue.setToolTipText("Could not get version information.");
}
} else {
if (Version.isUpdateAvailable()) {
appVersionValue.setForeground(new Color(dialogShell
.getDisplay(), 255, 0, 0));
appVersionValue.setText(Version.VERSION_NUMBER
+ " (Update Available!)");
appVersionValue.setToolTipText("Click to go to update " +
"download page");
appVersionValue.setCursor(new Cursor(dialogShell
.getDisplay(), SWT.CURSOR_HAND));
appVersionValue.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
Program.launch(Version.DOWNLOAD_URL);
dialogShell.dispose();
}
});
} else {
appVersionValue.setText(Version.VERSION_NUMBER);
appVersionValue.setToolTipText("Latest available version!");
appVersionValue.setForeground(new Color(dialogShell
.getDisplay(), 0, 128, 0));
}
}
}
{
appVersion = new Label(dialogShell, SWT.NONE);
FormData appVersionLData = new FormData();
appVersionLData.width = 77;
appVersionLData.height = 17;
appVersionLData.left = new FormAttachment(0, 1000, 156);
appVersionLData.top = new FormAttachment(0, 1000, 58);
appVersion.setLayoutData(appVersionLData);
appVersion.setText("Version:");
appVersion.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
appSloganLabel = new Label(dialogShell, SWT.WRAP);
FormData appSloganLabelLData = new FormData();
appSloganLabelLData.width = 342;
appSloganLabelLData.height = 17;
appSloganLabelLData.left = new FormAttachment(0, 1000, 156);
appSloganLabelLData.top = new FormAttachment(0, 1000, 35);
appSloganLabel.setLayoutData(appSloganLabelLData);
appSloganLabel.setText(Version.APP_SLOGAN);
}
{
appNameLabel = new Label(dialogShell, SWT.NONE);
FormData appNameLabelLData = new FormData();
appNameLabelLData.width = 342;
appNameLabelLData.height = 17;
appNameLabelLData.left = new FormAttachment(0, 1000, 156);
appNameLabelLData.top = new FormAttachment(0, 1000, 12);
appNameLabel.setLayoutData(appNameLabelLData);
appNameLabel.setText("Hawkscope");
appNameLabel.setFont(SWTResourceManager.getFont("Sans", 10, 1));
}
{
final FormData logoCanvasLData = new FormData();
logoCanvasLData.width = 114;
logoCanvasLData.height = 109;
logoCanvasLData.left = new FormAttachment(0, 1000, 12);
logoCanvasLData.top = new FormAttachment(0, 1000, 12);
logoCanvas = new Canvas(dialogShell, SWT.RESIZE);
logoCanvas.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
e.gc.drawImage(IconFactory.getInstance()
.getUncachedIcon("hawkscope128.png"), 0, 0, 128,
128, 0, 0, 114, 109);
}
});
logoCanvas.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
Program.launch(Version.HOMEPAGE);
}
});
logoCanvas.setCursor(new Cursor(dialogShell.getDisplay(),
SWT.CURSOR_HAND));
logoCanvas.setToolTipText("Click to visit Homepage");
logoCanvas.setLayoutData(logoCanvasLData);
}
dialogShell.setLocation(getParent().toDisplay(100, 100));
dialogShell.open();
Display display = dialogShell.getDisplay();
while (!dialogShell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
}

}


Now, a hand-rewritten version with no GUI builder:



The code:
package com.varaneckas.hawkscope.gui;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import com.varaneckas.hawkscope.Version;
import com.varaneckas.hawkscope.cfg.ConfigurationFactory;
import com.varaneckas.hawkscope.tray.TrayManager;
import com.varaneckas.hawkscope.util.IOUtils;
import com.varaneckas.hawkscope.util.IconFactory;

public class AboutShell {

private Shell shell;
private FormData layout;
private Font bold;
private Color red;
private Color green;
private Color blue;
private Cursor hand;
private Canvas logo;
private Label labelAppName;
private Label labelAppSlogan;
private Label labelVersion;
private Label labelReleased;
private Label labelHomePage;
private Label labelAppVersion;
private Label labelAppReleased;
private Label labelAppHomePage;
private Label labelEnvironment;
private Text textEnvironment;
private Button buttonCopyToClipboard;
private Button buttonClose;

public void open() {
if (shell != null && !shell.isDisposed()) {
shell.setVisible(true);
shell.forceFocus();
return;
}
createShell();
createResources();
createLogo();
createLabelAppName();
createLabelAppSlogan();
createLabelVersion();
createLabelReleased();
createLabelHomePage();
createLabelAppVersion();
createLabelAppReleased();
createLabelAppHomePage();
createLabelEnvironment();
createButtonClose();
createButtonCopyToClipboard();
createTextEnvironment();
shell.pack();
shell.open();
}

private void createResources() {
final FontData data = new FontData();
data.setHeight(10);
data.setStyle(SWT.BOLD);
bold = new Font(shell.getDisplay(), data);
red = new Color(shell.getDisplay(), 255, 0, 0);
green = new Color(shell.getDisplay(), 0, 128, 0);
blue = new Color(shell.getDisplay(), 0, 0, 255);
hand = new Cursor(shell.getDisplay(), SWT.CURSOR_HAND);
}

private void createShell() {
shell = new Shell(TrayManager.getInstance().getShell(), SWT.SHELL_TRIM);
final FormLayout layout = new FormLayout();
layout.spacing = 6;
layout.marginHeight = 12;
layout.marginWidth = 12;
shell.setLocation(shell.getParent().toDisplay(100, 100));
shell.setImage(IconFactory.getInstance()
.getUncachedIcon("hawkscope16.png"));
shell.setText("About");
shell.setLayout(layout);
shell.layout();
}

private FormData relativeTo(final Control top, final Control left) {
layout = new FormData();
layout.top = new FormAttachment(top);
layout.left = new FormAttachment(left);
return layout;
}

private FormData relativeToBottomRight(final Control right) {
layout = new FormData();
layout.bottom = new FormAttachment(100, 0);
if (right == null) {
layout.right = new FormAttachment(100, 0);
} else {
layout.right = new FormAttachment(right);
}
return layout;
}

private void createLogo() {
logo = new Canvas(shell, SWT.NONE);
logo.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
e.gc.drawImage(IconFactory.getInstance()
.getUncachedIcon("hawkscope128.png"), 0, 0);
}
});
logo.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
Program.launch(Version.HOMEPAGE);
}
});
logo.setCursor(hand);
logo.setToolTipText("Click to visit Homepage");
layout = relativeTo(null, null);
layout.width = 128;
layout.height = 128;
logo.setLayoutData(layout);
}

private void createLabelAppName() {
labelAppName = new Label(shell, SWT.NONE);
labelAppName.setText(Version.APP_NAME);
labelAppName.setLayoutData(relativeTo(null, logo));
labelAppName.setFont(bold);
}

private void createLabelAppSlogan() {
labelAppSlogan = new Label(shell, SWT.NONE);
labelAppSlogan.setLayoutData(relativeTo(labelAppName, logo));
labelAppSlogan.setText(Version.APP_SLOGAN);
}

private void createLabelVersion() {
labelVersion = new Label(shell, SWT.NONE);
labelVersion.setText("Version:");
labelVersion.setFont(bold);
labelVersion.setLayoutData(relativeTo(labelAppSlogan, logo));
}

private void createLabelAppVersion() {
labelAppVersion = new Label(shell, SWT.NONE);
labelAppVersion.setText(Version.VERSION_NUMBER);
labelAppVersion.setLayoutData(relativeTo(labelAppSlogan, labelHomePage));
updateLabelAppVersion();
}

private void updateLabelAppVersion() {
if (Version.isUpdateAvailable() == null) {
if (ConfigurationFactory.getConfigurationFactory()
.getConfiguration().checkForUpdates()) {
labelAppVersion.setToolTipText("Could not get version information.");
}
} else {
if (Version.isUpdateAvailable()) {
labelAppVersion.setForeground(red);
labelAppVersion.setText(Version.VERSION_NUMBER
+ " (Update Available!)");
labelAppVersion.setToolTipText("Click to go to update " +
"download page");
labelAppVersion.setCursor(hand);
labelAppVersion.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(final MouseEvent event) {
Program.launch(Version.DOWNLOAD_URL);
shell.dispose();
}
});
} else {
labelAppVersion.setText(Version.VERSION_NUMBER);
labelAppVersion.setToolTipText("Latest available version!");
labelAppVersion.setForeground(green);
}
}
}

private void createLabelReleased() {
labelReleased = new Label(shell, SWT.NONE);
labelReleased.setText("Released:");
labelReleased.setFont(bold);
labelReleased.setLayoutData(relativeTo(labelVersion, logo));
}

private void createLabelAppReleased() {
labelAppReleased = new Label(shell, SWT.NONE);
labelAppReleased.setText(Version.VERSION_DATE);
labelAppReleased.setLayoutData(relativeTo(labelVersion, labelHomePage));
}

private void createLabelHomePage() {
labelHomePage = new Label(shell, SWT.NONE);
labelHomePage.setText("Homepage:");
labelHomePage.setFont(bold);
labelHomePage.setLayoutData(relativeTo(labelReleased, logo));
}

private void createLabelAppHomePage() {
labelAppHomePage = new Label(shell, SWT.NONE);
labelAppHomePage.setText(Version.HOMEPAGE);
labelAppHomePage.setLayoutData(relativeTo(labelReleased, labelHomePage));
labelAppHomePage.setCursor(hand);
labelAppHomePage.setForeground(blue);
labelAppHomePage.setToolTipText("Click to open in browser");
labelAppHomePage.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(final MouseEvent event) {
Program.launch(Version.HOMEPAGE);
}
});
}

private void createLabelEnvironment() {
labelEnvironment = new Label(shell, SWT.NONE);
labelEnvironment.setText("Environment");
labelEnvironment.setFont(bold);
labelEnvironment.setLayoutData(relativeTo(logo, null));
}

private void createButtonClose() {
buttonClose = new Button(shell, SWT.PUSH);
buttonClose.setText("&Close");
buttonClose.setLayoutData(relativeToBottomRight(null));
buttonClose.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent event) {
shell.dispose();
}
});
}

private void createButtonCopyToClipboard() {
buttonCopyToClipboard = new Button(shell, SWT.PUSH);
buttonCopyToClipboard.setText("C&opy to Clipboard");
buttonCopyToClipboard.setLayoutData(relativeToBottomRight(buttonClose));
buttonCopyToClipboard.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent event) {
IOUtils.copyToClipboard(Version.getEnvironmentReport());
}
});
}

private void createTextEnvironment() {
textEnvironment = new Text(shell, SWT.MULTI | SWT.WRAP
| SWT.V_SCROLL | SWT.BORDER);
textEnvironment.setText(Version.getEnvironmentReport());
textEnvironment.setEditable(false);
layout = relativeTo(labelEnvironment, null);
layout.right = new FormAttachment(100, 0);
layout.bottom = new FormAttachment(buttonClose);
layout.width = 500;
layout.height = 150;
textEnvironment.setLayoutData(layout);
}

}


If you compare the two versions, handcoded one is superior in most aspects. The code is smaller, more readable and text editor friendly. The window can be resized, it is better looking - in generated code the logo image was scaled due to dragging inacuracy. And, believe it or not, I've spent less time creating the handcoded GUI version than "drawing" the automated one and then hacking it's generated code. Of course, if you know your tools well, you can be much more productive with a GUI builder, but I'll rather learn the low-level GUI API than some commercial third party product that treats you like parents treat their kids with LEGO.

No more GUI builders for me.

2009-01-28

Hawkscope on Mac. Finally.

Last week I have quit sleeping and did hell of a coding on Hawkscope. And there's a new version, with many significant changes and improvements. To name a few:

  • Works on Mac OS X! (Java 5, Tiger / Leopard)

  • Installer packages for Windows, Mac and Debian (Ubuntu).

  • Settings can finally be changed via Settings Window, like in most normal applications...

  • Speed! Especially noticeable when you have many network drives.

  • Check for updates. A new way to annoy the users!

  • Blacklist. Remove all the shit you don't want to see.



Download links:
hawkscope_0.4.0-1_i386.deb: Debian Package for i386 GTK Linux (Ubuntu)
hawkscope_0.4.0-1_amd64.deb: Debian Package for amd64 GTK Linux (Ubuntu)
Hawkscope-0.4.0.dmg: Mac OS X i386 Package (Tiger/Leopard)
hawkscope-0.4.0-installer.exe: Windows i386 Installer (XP/Vista)