I Was a Teenage Thought PolicemanI remember an early database, MacLion, which was a bad port of a DOS application, right down to the 24-by-80 monospaced scrolling text window. Boy, it was ugly. It eventually lost in the marketplace. Apple also spent a lot of time working with major DOS application vendors to get them to “get it” about the graphic user interface. Lotus received a lot of personal attention from Apple for their Jazz product, and later 1-2-3 for Mac.
But the folklore that has come down through the years is that Apple defended the purity of the interface by punishing the developers who built applications that broke the rules. And that’s just not true. The rules were vague; they were revised several times over the first five years; we broke the rules ourselves (starting early, with MacPaint); and to tell you the truth, we were so desperate for software that we even put that ugly, DOSish MacLion on our poster of the first 100 apps.
The truth is that the punishment for inconsistency came from the Mac community itself. Magazine reviewers and pundits were the first to appreciate the consistency and simplicity of Mac applications, especially in contrast with the growing mess in the DOS world. Influential users and purchasers followed suit. Programs with inconsistent interfaces did suffer; but they suffered at the hands of the marketplace, not of a dictatorial Apple.
Swing:
jEdit
Limewire
NetBeans
SWT:
Eclipse
Azureus
RSSOwl
Cocoa:
Cyberduck
Mac OS X 10.3 (Panther) or later
Be careful. A lot of Mac users still use Java 1.3 by default.
The Macintosh computer sports the most jumbled-up, inconsistent, confusing interface ever made, with the exception of those of all the other computers.Tog on Interface
Run application
How many things are wrong?
How many things are right?
Most menu items are in the right place with the right names
Menu shortcuts are mostly correct
Command keys use command, not Ctrl
Window minimization
Something goes in the dock
Menu bar is not on top of screen
About jEdit is in the Help menu, not the Application Menu
Where is the application menu?
Generic dock icon
Doesn't start in home directory
File/Exit
final static boolean thisIsAMac = System.getProperty("mrj.version") != null;
Tog on InterfaceUsers believe in the menu bar. The menu bar tells them where they are: in the safe, protective environment of the Macintosh, where consistency reigns.
The menu bar is the most constant object in the Macintosh. When it disappears, non-computer-oriented users assume they, the users, have moved, navigated, to a different planet, a world where all the rules may have changed. They are no longer within the familiar Macintosh world. And their only known way back, Quit on the menu bar, has been stripped away.
I have seen, during user testing, the very real fear etched on the face of users when the menu bar disappears. I have watched them literally panic as they realize they are trapped in a strange world.
The menu bar goes on top of the screen
Menu Order from left to right:
Apple
Application
File
Edit
...
Help
public static void main(String[] args) throws QTException {
System.setProperty("apple.laf.useScreenMenuBar", "true");
System.setProperty("com.apple.eawt.CocoaComponent.CompatibilityMode", "false");
// Need an empty hidden frame just for menu bar
final PlayerFrame hidden = new PlayerFrame(true);
// first frame is just for menu bar
EventQueue.invokeLater(new Runnable() {
public void run() {
hidden.show();
}
});
}
Typically contains About AppName, Preferences, Services, Hide AppName, Hide Others, Show All, and Quit AppName
Most of this is handled for you by the OS. You do not have to set this menu up yourself.
You can plug-in your own code using the Application
and ApplicationAdapter
classes and the ApplicationListener
interface.
package com.apple.eawt;
public class Application {
public Application();
public void addApplicationListener(ApplicationListener listener);
public void removeApplicationListener(ApplicationListener listener);
public void setEnabledPreferencesMenu(boolean enable);
public boolean getEnabledPreferencesMenu();
public static java.awt.Point getMouseLocationOnScreen();
// New methods in Java 5
public static Application getApplication();
public boolean isPreferencesMenuItemPresent();
public void addPreferencesMenuItem();
public void removePreferencesMenuItem();
public void removeAboutMenuItem();
public boolean isAboutMenuItemPresent();
public void addAboutMenuItem();
public boolean getEnabledAboutMenu();
public void setEnabledAboutMenu(boolean enable);
}
Most work is done through one or more ApplicationListeners you install:
package com.apple.eawt;
public interface ApplicationListener extends java.util.EventListener {
public void handleAbout(ApplicationEvent event);
public void handleOpenApplication(ApplicationEvent event);
public void handleOpenFile(ApplicationEvent event);
public void handlePreferences(ApplicationEvent event);
public void handlePrintFile(ApplicationEvent event);
public void handleQuit(ApplicationEvent event);
}
Handle menu items and standard Apple events (e.g. Finder drag and drop)
There is an ApplicationAdapter
convenience class
public void handleQuit(ApplicationEvent event) {
Iterator iterator = WindowList.INSTANCE.iterator();
while (iterator.hasNext()) {
Frame next = (Frame) iterator.next();
next.setVisible(false);
next.dispose();
}
RecentFileList.INSTANCE.storeRecentFiles();
System.exit(0);
}
public class MacOSHandler extends Application {
private Dialog about;
public MacOSHandler(PlayerFrame frame) {
about = new AboutDialog(frame);
addApplicationListener(new AboutBoxHandler());
}
class AboutBoxHandler extends ApplicationAdapter {
public void handleAbout(ApplicationEvent event) {
EventQueue.invokeLater(new Runnable() {
public void run() {
about.setVisible(true);
}
});
event.setHandled(true);
}
}
}
package com.apple.eawt;
public class ApplicationEvent {
public String getFilename();
public boolean isHandled()
public void setHandled(boolean state)
}
WordPad Menus:
File:
Edit:
View
Insert
Format:
Help:
TextEdit Menus:
Apple
TextEdit
File
Edit
Format
Window
Help
//File -> Exit.
if (!thisIsAMac) {
new MenuItem(menu, SWT.SEPARATOR);
//File -> Exit.
subItem = new MenuItem(menu, SWT.NULL);
subItem.setText(resAddressBook.getString("Exit"));
subItem.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
shell.close();
}
});
}
"New" menu item is normally first
It normally doesn't say "New Foo" unless there's also a "New Bar"
Ditto for Open
Normally "File/New" implies a new document and window.
Single window interfaces should not be used for multidocument applications (and most applications should be multidocument)
Command-N implies "File/New" which implies a new document and window.
The Edit menu always begins like this:
Edit Undo ⌘Z Redo ⇧⌘Z ------------- Cut ⌘X Copy ⌘C Paste ⌘V Delete (or Clear) ------------- Application specific items go here
Necessary fixes:
Add Delete or Clear item
About menu goes in the Application menu, not in the Help menu
Help is bound to Help key
Creating a new document should not close the current document.
Opening an existing document should not close the current document.
There should be a File/Close menu item
Closing the last window should not exit the application or hide the menu bar.
Single window applications need different metaphors
Set up a special menu bar to be used when no windows are open.
Add this to your first frame.
Set its size to zero.
Set its location to negative coordinates.
Display the frame.
PlayerFrame(boolean invisible) {
super("");
this.setUndecorated(true);
this.setSize(0, 0);
CONTROL_BAR_HEIGHT = 0;
JMenuBar menubar = new PlayerMenuBar(null);
this.setJMenuBar(menubar);
this.pack();
}
Be careful if you have any bring to Front/Send to Back/Move Forward/Move backward functionality
The smallest size necessary to display the content or larger, up to the full size of the screen, minus the dock and the menu bar
Can be saved with document if user modifies it.
The Hack: Launch from a class in the default package with the right name:
public class MacAddressBook {
public static void main(String[] args) {
com.elharo.swt.examples.AddressBook.main(args);
}
}
The correct way: specified in the Info.plist file
Setup when you export an app from Eclipse, or build with JarBundler in Ant
Demo: Inspect the plist
Command-space bar: Spotlight
Command-tab: switch applications
Command-option-Esc
Command-control-Eject
Control-Function key
F9, F10, F11: Exposé
F12: Eject/Dashboard
It probably only has one button, but may have more:
Checking for mouse buttons:
public class Mouse {
public static void main(String args[]) {;
System.out.println(java.awt.MouseInfo.getNumberOfButtons());
}
}
Control-click fakes a second mouse button if necessary.
Regardless, don't put anything in a context menu that can't be reached from the regular menus.
Never assume the platform default encoding
Specify the encoding
Prefer UTF-8
Avoid FileWriter
/FileReader
and always specify the encoding for
OutputStreamWriter
/OutputStreamReader
Mac text files tend to end in carriage return (\r) rather than \n or \r\n
When writing a custom, non-text non-XML format, don't rely on this.
Never use println()
to output a line break.
Treat .app bundles as files. Don't traverse. Especially don't show their contents to users.
.DS_Store
don't show to user
don't transfer or archive
Avoid hidden files in the home directory (.cvspass, etc.)
Don't store them in the application; multiple users may need them
Put them in ~/Library/ApplicationName directory along with any support files
Or ~/Library/Preferences/ for plist files only
From Java ~ is System.getProperty("user.home")
package com.apple.eio;
public class FileManager {
public static void setFileTypeAndCreator(String filename, int type, int creator) throws IOException
public static void setFileType(String filename, int type) throws IOException
public static void setFileCreator(String filename, int creator) throws IOException
public static int getFileType(String filename) throws IOException
public static int getFileCreator(String filename) throws IOException
public static String findFolder(int folderType) throws FileNotFoundException
public static String findFolder(short domain, int folderType) throws FileNotFoundException
public static String findFolder(short domain, int folderType, boolean createIfNeeded) throws FileNotFoundException
public static void openURL(String url) throws IOException
public static String getResource(String resourceName) throws FileNotFoundException
public static String getResource(String resourceName, String subDirName) throws FileNotFoundException
}
Eclipse: File/Export... Mac OS X Application Bundle
Choosing a name
Choosing an Icon
Creator Codes:
Four ASCII letters
Not all lower case
Users never see this
Registering a Creator Code: http://developer.apple.com/datatype/creatorcode.html
Mac applications tend to be distributed as disk image files.
Disk images are created with the Disk Utility tool.
Disk images can be compressed; no need for a separate zip step
Demo installing from a disk image
Each disk image contains ideally a single application that can be dragged into the Applications folder.
Demo making a disk image
Apple Installer can do more complicated operations:
Check prerequisites
Show README
Require acceptance of license agreement
Require authorization
Require restart
Run special purpose shell scripts
PackageMaker creates the Install script; /Developer/Applications/Utilities
Free for non-profits/non-commercial/internal enterprise
$250-$1500 for commercial distribution depending on volume
Incompatible with open source licenses
QuickTime and QuickTime for Java are available for Windows and Mac (not Linux)
Handles playback and editing of various media formats (MP3, JPEG, MOV, MPEG, etc.)
But incomplete
Doesn't play well with SWT
com.apple.cocoa.*
packages provide incomplete
support for the native Cocoa API
Non standard "JavaDoc"
Never well supported; soon to be abandoned?
com.apple.cocoa.foundation.NSSelector
lets you reflect to invoke any native Mac OS X method
Suppose you have this Cocoa method:
void doSomething(String str, int i) { }
Use this Java code to invoke it:
NSSelector sel = new NSSelector(
"doSomething", new Class[] {String.class, int.class} );
Foo f = new Foo();
sel.invoke(f, new Object[] { "hi", new Integer(42) });
Functionality:
Get and set file types and creator codes.
Find special folders defined by the OS.
Find applications by creator code.
Find resource files in application bundles.
Open URLs in the user's favorite browser.
Handle the Apple events Open Application, Reopen Application, Quit Application, Open Document, and Print Document.
Get the name of the startup disk.
Launch applications as well as open documents, either by the application that created them or by one of your choice.
Mostly designed for Swing/AWT but the non GUI pieces work (or can be copied to work) with SWT
Stubbed on other non-Mac platforms
Open Source, Artistic License
Swing Look and Feel that fiuuxes a lot of little inconsistencies with the Mac UI
LGPL or BSD
FileMaker | MacLion |
Quark XPress | FrameMaker |
Excel | Lotus 1-2-3 |
Word | WordPerfect |
Microsoft Word 5 | Microsoft Word 6 |
iTunes | WinAmp |
Adobe Illustrator | CorelDraw |
MacroMedia Director-->ShockWave-->Flash | VRML, Java |
This presentation: http://www.cafeaulait.org/slides/sd2006west/macifying/
Apple Human Interface Guidelines: http://developer.apple.com/documentation/UserExperience/Conceptual/OSXHIGuidelines/
java-dev mailing list: http://lists.apple.com/mailman/listinfo/java-dev