null User manual

null  User manual
Eclipse Rich Client Platform: Designing, Coding, and Packaging Java™ Applications
By Jeff McAffer, Jean-Michel Lemieux
...............................................
Publisher: Addison Wesley Professional
Pub Date: October 17, 2005
ISBN: 0-321-33461-2
Pages: 552
Table of Contents | Index
Build Powerful, Cross-Platform Rich Client Applications
Eclipse is more than a state-of-the-art IDE: its Rich Client Platform (RCP) plug-ins form an
outstanding foundation for any desktop application, from chat applications to enterprise
software front-ends. In Eclipse Rich Client Platform, two leaders of the Eclipse RCP project
show exactly how to leverage Eclipse for rapid, efficient, cross-platform desktop development.
In addition to explaining the power of Eclipse as a desktop application development platform,
the authors walk step-by-step through developing a fully featured, branded RCP application.
They introduce a wide range of techniques, including developing pluggable and dynamically
extensible systems, using third-party code libraries, and packaging applications for diverse
environments. You'll build, refine, and refactor a complete prototype; customize the user
interface; add Help and Update features; and build, brand, and ship the finished software.
For every Java developer, regardless of previous Eclipse experience
Thoroughly covers Eclipse 3.1's new RCP features and its extensive new tools for
designing, coding, and packaging RCP applications
Presents techniques for branding and customizing the look and feel of RCP applications
Shows how to overcome the challenges and "rough edges" of RCP development
Discusses the similarities and differences between RCP and conventional plug-in
development
Includes an overview of OSGi, the base execution framework for Eclipse
If you want to develop and deploy world-class Java applications with rich, native GUIs, and
use Eclipse RCPget this book.
CD-ROM contains the Eclipse 3.1 SDK, Eclipse 3.1 RCP SDK, and Eclipse 3.1 RCP Delta Pack
appropriate for Windows, Linux, and Mac OS X. It also contains all the code samples
developed in the book.
© Copyright Pearson Education. All rights reserved.
Eclipse Rich Client Platform: Designing, Coding, and Packaging Java™ Applications
By Jeff McAffer, Jean-Michel Lemieux
...............................................
Publisher: Addison Wesley Professional
Pub Date: October 17, 2005
ISBN: 0-321-33461-2
Pages: 552
Table of Contents | Index
Copyright
The Eclipse Series
Titles in the Eclipse Series
Foreword by John Weigand
Foreword by Jeff Norris
Acknowledgments
Preface
About this Book
Audience
Sample Code
Conventions
Feedback
Part I: Introduction
Chapter 1. Eclipse as a Rich Client Platform
Section 1.1. Eclipse
Section 1.2. The Eclipse Rich Client Platform
Section 1.3. Eclipse RCP Over the Years
Section 1.4. Uses of RCP
Section 1.5. Summary
Chapter 2. Eclipse RCP Concepts
Section 2.1. A Community of Plug-ins
Section 2.2. Inside Plug-ins
Section 2.3. Putting a System Together
Section 2.4. OSGi Framework
Section 2.5. The Runtime
Section 2.6. SWT
Section 2.7. JFace
Section 2.8. UI Workbench
Section 2.9. Summary
Part II: RCP by Example
Chapter 3. Tutorial Introduction
Section 3.1. What Is Hyperbola?
Section 3.2. The Evolution of Hyperbola
Section 3.3. Development Environment Installation
Section 3.4. Target Setup
Section 3.5. Checkpoint
Section 3.6. Sample Code
Section 3.7. Learning by Example
Section 3.8. Summary
Chapter 4. The Hyperbola Application
Section 4.1. Hyperbola Hello World
Section 4.2. Tour of the Code
Section 4.3. Running and Debugging
Section 4.4. Summary
Chapter 5. Starting the Hyperbola Prototype
Section 5.1. Continuing from the Shell
Section 5.2. Adding a Contacts View
Section 5.3. The Chat Model
Section 5.4. Filling in the Contacts View
Section 5.5. Adding Images
Section 5.6. Summary
Section 5.7. Pointers
Chapter 6. Adding Actions
Section 6.1. Adding to the Menus and Toolbar
Section 6.2. Adding to the Status Line
Section 6.3. System Tray Integration
Section 6.4. Summary
Section 6.5. Pointers
Chapter 7. Adding a Chat Editor
Section 7.1. Views and Editors
Section 7.2. Defining the Chat Editor
Section 7.3. Checkpoint
Section 7.4. Summary
Section 7.5. Pointers
Chapter 8. Branding Hyperbola
Section 8.1. Defining the Hyperbola Product
Section 8.2. Window Images
Section 8.3. Customizing the Launcher
Section 8.4. Splash Screen
Section 8.5. About Information
Section 8.6. Summary
Chapter 9. Packaging Hyperbola
Section 9.1. Exporting Hyperbola
Section 9.2. Exporting for Other Platforms
Section 9.3. Summary
Section 9.4. Pointers
Chapter 10. Messaging Support
Section 10.1. Integrating a Third-Party Library
Section 10.2. Refactoring the Model
Section 10.3. Updating the UI
Section 10.4. Chatting with Eliza
Section 10.5. Summary
Chapter 11. Adding a Login Dialog
Section 11.1. Adding the Login Dialog
Section 11.2. Remembering Login Settings
Section 11.3. Adding Auto-login Preferences
Section 11.4. Summary
Chapter 12. Adding Key Bindings
Section 12.1. Defining Commands
Section 12.2. Checkpoint
Section 12.3. Adding Key Bindings for Workbench Actions
Section 12.4. Key Configurations
Section 12.5. Keys Preference Page
Section 12.6. Summary
Chapter 13. Adding Help
Section 13.1. Adding to the Target Platform
Section 13.2. Getting the Help Plug-ins
Section 13.3. Configuring the Help Plug-ins
Section 13.4. Add the Help Action
Section 13.5. Adding Help Content
Section 13.6. Help Content Structure
Section 13.7. Infopops or F1 Help
Section 13.8. Exporting Plug-ins with Help
Section 13.9. Summary
Section 13.10. Pointers
Chapter 14. Adding Update
Section 14.1. Getting Update Plug-ins
Section 14.2. Configuring the Update Plug-ins
Section 14.3. Defining Features
Section 14.4. Branding Features
Section 14.5. Adding Update Actions
Section 14.6. Automatic Updates
Section 14.7. Summary
Part III: The Workbench
Chapter 15. Workbench Advisors
Section 15.1. What Is an Advisor?
Section 15.2. WorkbenchAdvisor
Section 15.3. WorkbenchWindowAdvisor
Section 15.4. ActionBarAdvisor
Section 15.5. Workbench Overview
Section 15.6. Summary
Chapter 16. Perspectives, Views, and Editors
Section 16.1. Perspectives
Section 16.2. Views and Editors
Section 16.3. Multiple Workbench Windows
Section 16.4. Drag and Drop with Editors
Section 16.5. Summary
Chapter 17. Actions
Section 17.1. Overview
Section 17.2. Declarative Actions in Hyperbola
Section 17.3. Standard Workbench Actions
Section 17.4. Retargetable Actions
Section 17.5. Consolidating Declarative Actions
Section 17.6. Toolbar Action Tricks
Section 17.7. Adding Contributions to the Status Line
Section 17.8. Reporting Progress
Section 17.9. Summary
Chapter 18. Customizing Workbench Windows
Section 18.1. Customization Defined
Section 18.2. Customizing a Workbench Window
Section 18.3. Custom Window Shapes
Section 18.4. Summary
Chapter 19. Customizing the Presentation of Views and Editors
Section 19.1. Presentations
Section 19.2. Sample Presentations
Section 19.3. Writing a Presentation
Section 19.4. Example Presentation
Section 19.5. Summary
Part IV: Development Processes
Chapter 20. Integrating Code Libraries
Section 20.1. Plug-ins as JARs
Section 20.2. Bundling by Injection
Section 20.3. Bundling by Wrapping
Section 20.4. Bundling by Reference
Section 20.5. Troubleshooting Classloading Problems
Section 20.6. Summary
Chapter 21. Installing and Updating Plug-ins
Section 21.1. Update's Roles
Section 21.2. Features
Section 21.3. Creating and Managing Update Sites
Section 21.4. Example: Dynamic Content Handling
Section 21.5. Summary
Section 21.6. Pointers
Chapter 22. Dynamic Plug-ins
Section 22.1. Making Hyperbola Dynamic
Section 22.2. Dynamic Challenges
Section 22.3. Dynamic-awareness
Section 22.4. Dynamic-enablement
Section 22.5. Summary
Chapter 23. RCP Everywhere
Section 23.1. Sample Code
Section 23.2. The Scenario
Section 23.3. Product Configurations
Section 23.4. Hyperbola Product Configurations
Section 23.5. Code Structure
Section 23.6. Designing a Platform
Section 23.7. RCP-friendly Plug-ins
Section 23.8. Summary
Chapter 24. Building Hyperbola
Section 24.1. What Is PDE Build?
Section 24.2. Plug-in build.properties
Section 24.3. Feature build.properties
Section 24.4. Setting Up a Hyperbola Builder
Section 24.5. Running the Builder
Section 24.6. Building Products
Section 24.7. Cross-platform Building
Section 24.8. Tweaking the Build
Section 24.9. Summary
Chapter 25. The Last Mile
Section 25.1. Archives
Section 25.2. Native Installers
Section 25.3. Java Web Start (JNLP)
Section 25.4. Update Sites
Section 25.5. Initializing the Install
Section 25.6. Pre-initialized Configurations
Section 25.7. Multi-user Install Scenarios
Section 25.8. Summary
Section 25.9. Pointers
Part V: Reference
Chapter 26. OSGi Essentials
Section 26.1. OSGi and the Eclipse Runtime
Section 26.2. The Shape of Plug-ins
Section 26.3. Fragments
Section 26.4. Version Numbering
Section 26.5. Services
Section 26.6. Singletons
Section 26.7. Bundle Lifecycle
Section 26.8. Early Activation
Section 26.9. Auto-activation
Section 26.10. Classloading
Section 26.11. Data Areas
Section 26.12. Putting It All Together
Section 26.13. Summary
Chapter 27. Eclipse.org Plug-ins
Section 27.1. Where to Find Plug-ins
Section 27.2. Eclipse Platform Plug-ins
Section 27.3. Product Introduction
Section 27.4. Resources
Section 27.5. Text Editing
Section 27.6. Consoles
Section 27.7. Variables
Section 27.8. Outline and Property Views
Section 27.9. Forms
Section 27.10. Browser
Section 27.11. Summary
Index
Copyright
Many of the designations used by manufacturers and sellers to distinguish their products are
claimed as trademarks. Where those designations appear in this book, and the publisher was
aware of a trademark claim, the designations have been printed with initial capital letters or in
all capitals.
The authors and publisher have taken care in the preparation of this book, but make no
expressed or implied warranty of any kind and assume no responsibility for errors or omissions.
No liability is assumed for incidental or consequential damages in connection with or arising out
of the use of the information or programs contained herein.
The publisher offers excellent discounts on this book when ordered in quantity for bulk
purchases or special sales, which may include electronic versions and/or custom covers and
content particular to your business, training goals, marketing focus, and branding interests. For
more information, please contact:
U. S. Corporate and Government Sales:
(800) 382-3419
[email protected]
For sales outside the U. S., please contact:
International Sales:
[email protected]
Visit us on the Web: www.awprofessional.com
Copyright © 2006 Pearson Education, Inc.
All rights reserved. Printed in the United States of America. This publication is protected by
copyright, and permission must be obtained from the publisher prior to any prohibited
reproduction, storage in a retrieval system, or transmission in any form or by any means,
electronic, mechanical, photocopying, recording, or likewise. For information regarding
permissions, write to:
Pearson Education, Inc.
Rights and Contracts Department
One Lake StreetUpper Saddle River, NJ 07458
Text printed in the United States on recycled paper at R.R. Donnelley and Sons in
Crawfordsville, Indiana.First printing, October 2005
Library of Congress Cataloging-in-Publication Data
McAffer, Jeff.
Eclipse Rich Client Platform : designing, coding, and packaging Java applications /
Jeff McAffer, Jean-Michel Lemieux.
p. cm.
Includes bibliographical references and index.
ISBN 0-321-33461-2 (alk. paper)
1. Computer software--Development. 2. Java (Computer program language)
I. Lemieux, Jean-Michel. II. Title.
QA76.76.D47M383 2005
005.1--dc22
Dedication
To my best friends in the whole worldNancy, Sydney, and Toby.
Jeff
To Nadine, Gabriel, and Alizé.
Jean-Michel
2005018429
The Eclipse Series
SERIES EDITORS Erich Gamma
Lee Nackman
John Wiegand
Eclipse is a universal tool platform, an open extensible integrated development environment
(IDE) for anything and nothing in particular. Eclipse represents one of the most exciting
initiatives hatched from the world of application development in a long time, and it has the
considerable support of the leading companies and organizations in the technology sector.
Eclipse is gaining widespread acceptance in both the commercial and academic arenas.
The Eclipse Series from Addison-Wesley is the definitive series of books dedicated to the
Eclipse platform. Books in the series promise to bring you the key technical information you
need to analyze Eclipse, high-quality insight into this powerful technology, and the practical
advice you need to build tools to support this evolutionary Open Source platform. Leading
experts Erich Gamma, Lee Nackman, and John Wiegand are the series editors.
Titles in the Eclipse Series
John Arthorne and Chris Laffra, Official Eclipse 3.0 FAQs, 0-321-26838-5
Kent Beck and Erich Gamma, Contributing to Eclipse: Principles, Patterns, and Plug-Ins, 0-32120575-8
Frank Budinsky, David Steinberg, Ed Merks, Ray Ellersick, and Timothy J. Grose, Eclipse
Modeling Framework, 0-131-42542-0
Eric Clayberg and Dan Rubel, Eclipse: Building Commercial-Quality Plug-Ins, 0-321-22847-2
Steve Northover and Mike Wilson, SWT: The Standard Widget Toolkit, Volume 1, 0-321-25663-8
Foreword by John Weigand
We did not explicitly set out to create the Eclipse Rich Client Platform (RCP). The Eclipse mission
was to create a universal tool platforman open, extensible integrated development environment
(IDE). We wanted to make IDE creators successful by providing the "plumbing" so they could
invest in providing new capabilities to their users. We wanted to be open so that anyone could
contribute. We wanted to provide the right component model and building blocks to make
contributing easy. We hoped for widespread adoption of Eclipse and the growth of a vibrant
community of tool writers. Eclipse has delivered on each of these ambitions. We had one more
goal: We wanted Eclipse to be used in ways we never imagined. The Eclipse RCP delivered on
this goal. The community started taking the Eclipse building blocks to create end-user
applications. This was great, but required some "hacks" to the Eclipse platform to eliminate the
"IDE-ness." Once this became apparent, the Eclipse community worked together to enhance the
Eclipse platform to enable a first-class RCP solution. Today, Eclipse RCP applications gain the
same benefits as traditional Eclipse tools: They leverage the Eclipse component model, they
focus on the new capabilities of their application (ignoring the "plumbing"), and there is a
growing community of Eclipse RCP application developers. Eclipse isn't just for tool-builders
anymore.
If you are looking for a way to jump-start your knowledge of the Eclipse RCP, this book provides
a tutorial-based introduction to building an RCP application from initial prototype to functioning
application. You can feel the authors' presence throughout the tutorial; they reinforce Eclipse
design principles as they help you avoid "gotchas" along the way. For those already creating
RCP applications, this book includes a reference guide with a deeper exploration of RCP
capabilitiesfrom user interface customizations to integration with third-party libraries and many
points in between. This book will teach you about the latest possibilities enabled with Eclipse
3.1. (Have you ever heard of "buddy" loading?) In addition, this book contains a primer on
using the Eclipse Plug-in Development Environment (PDE) for building your RCP applications. I
have been using PDE from its inception, and still continue to discover pointers to capabilities I
didn't know about; now I am an even more satisfied PDE customer.
Jeff McAffer and Jean-Michel Lemieux are excellent sources of insight into RCP. They have both
been key contributors to Eclipse from the beginning, active in the creation and evolution of the
Eclipse Runtime, the project model (often called "resources"), the repository integration (usually
called "team"), and of course, RCP itself. In addition to their deep knowledge, they are well
connected to the entire Eclipse platform as committers on the project. This connectivity has
resulted in both a better book and a better technology through the team interactions.
I trust you will find this book as useful as I did. Good luck on your journey into the Eclipse RCP!
John Wiegand
Eclipse Platform PMC Lead
July 2005
Foreword by Jeff Norris
On June 10, 2003, I counted down the last seconds to the launch of the Spirit Mars Rover with
the rest of the Mars Exploration Rover Mission team. For my team, it was a break from the final
months of development and testing of Maestro, the software application that we would use to
analyze the data received from the rover and plan its science activities once it arrived on Mars
seven months later. One remarkable fact about launching something like Spirit is that the actual
rover, or payload, is crammed into a tiny chamber at the very top of a huge rocket, called the
launch vehicle. The launch vehicle is a fiendishly complicated system all on its own and its
criticality can't be overestimated. Still, it's not really the point of the mission.
I believe that Maestro and every other rich client application can also be divided into a payload
and a launch vehicle. An application's payload is the reason you wrote it in the first place. For
Maestro, it's the part that deals with spacecraft telemetry, immersive visualization, and mission
planning. For your program, it might be the part that manages a database of thousands of
products at a dozen warehouses or the part that seamlessly deploys intricate Web sites to highperformance servers. In any case, the payload is the part of our programs that we daydream
about in boring meetings and in the car on the way to work. It's the part that we love to work
on.
Then there's the program's launch vehicle, which is everything else. Every application should be
easy to use, which means it needs to provide things like a help system, progress updates for
lengthy operations, and a way for the user to access and arrange views of data without getting
overwhelmed. It needs dozens of little interface details like drag/drop, copy/paste, and
undo/redo. Finally, the development team needs an architecture underneath it all that lets them
add new features easily, deploy them quickly to their customers, and then update them later.
The launch vehicle is critical to the success of your program, but its only purpose is to support
your payload.
In June 2004, one year after launch and five months after Spirit and Opportunity arrived on
Mars, my team turned our operations duties over to replacements and eagerly started working
on the future of our software. When we looked back at the last version of Maestro, we realized
that we had gotten our payload right, but our launch vehicle had some problems. A tight budget
had forced us to leave out a lot of key things like copy/paste and a help system in order to finish
payload features like image processing. We didn't have sufficient time to test things like
software update, forcing us to eventually abandon it in favor of more arduous methods. We also
reached the somewhat painful realization that we were simply not experts on making a highly
usable application, or designing a flexible and sustainable development architecture. The launch
vehicle is something that is extremely difficult to get right, and what we wanted most was just
to focus on Maestro's payload.
The Eclipse RCP was the launch vehicle we were looking for.
Since adopting the RCP, our team has been able to retire thousands of lines of code from our
old program in favor of features provided by the Eclipse RCP. Maestro greets its users with a
welcome page and provides cheat sheets to walk them through the basics of the application. We
use the Jobs application programming interface (API) to keep users informed when the
application is performing lengthy operations and preference stores to allow them to adjust
settings in the program. Perspectives let our users organize our many views and switch quickly
between different mission operations tasks. We use the update manager to make sure our users
are using the latest versions of our plug-ins. Our visualization and planning components are
built on top of the Graphical Editing Framework (GEF), and we're planning to make heavy use of
the Business Intelligence and Reporting Tools (BIRT) system to display reports on spacecraft
resource usage. It's hard to find a part of our program that doesn't rely on some Eclipse
capability and we're constantly discovering new technologies being developed in the Eclipse
community that are enabling us to reach our goals more quickly.
In addition, we've used the modularity offered by the feature and plug-in architecture in the
RCP to deliver highly specialized applications to our missions. In the past, our customers would
receive a monolithic "core" application with a layer of adaptation that was designed to address
their specific needs. This enabled us to reuse software between projects, but this strategy
caused the core of the tool to grow larger and more unwieldy as each project added functions
that the users needed. Dependencies between plug-ins in the Eclipse RCP are explicitly defined
and enforced, so now we can safely pick and choose only those plug-ins that a particular
customer needs rather than forcing everyone to use a monolithic "one size fits all" program.
Maestro has become something of a chameleondifferent customers get exactly the functionality
they want without the distraction of features they don't need.
The RCP has also become the centerpiece for a new consortium of operations software
development teams within NASA called Ensemble. Multiple development teams at the Jet
Propulsion Laboratory and Ames Research Center are using the RCP to develop and integrate
their tools in a way that will enable more efficient mission operations. I think we will see more
consortiums built around the RCP in the future as other kinds of organizations decide to pool
their resources and share the responsibility for things that their programs have in common.
Choosing a framework for an application is a decision that must be handled with as much care
as the selection of a rocket that will carry a spacecraft into orbit. Fortunately, you don't have to
base your decision on testimonials or hypethis book offers you a "test flight" on the RCP.
Perhaps you'll find that this launch vehicle will get your payload where it needs to go too.
Jeff Norris
Supervisor, Planning Software Systems Group
Jet Propulsion Laboratory
California Institute of Technology
July 2005
Acknowledgments
It is impossible to write a book without the cooperation and help of a vast number of people. In
our case, virtually the entire Eclipse Platform team contributed directly to the end-result,
through conversations, help with code and concepts, bug fixes, or just general support.
Beyond the book, the Eclipse RCP itself would be a mere shadow of what it is without the
following people:
Nick Edgar Master of all things UI and maintainer of a great sense of what it means to be
RCP.
Wassim Melhem The man behind the Plug-in Development Environment (PDE). Without
him, this book would be twice as long and you would have to understand all of it to get
anything done. Every time you use a PDE wizard to do a day's work in minutes, say,
"Thank you, Wassim".
Pascal Rapicault Pascal is the power that drives PDE build and your ability to ship RCPbased products. He is also responsible for many enabling innovations such as buddy
classloading and running Eclipse via Java Web Start.
Particular thanks go to Doug Pollock, Tom Watson, Kim Horne, Stefan Xenos, Veronika Irvine,
Christophe Cornu, DJ Houghton, Tod Creasey, Dorian Birsan, Mazen Faraj, Konrad Kolosowski,
and Darin Swanson for taking the time to explain things, talk over ideas, and generally guide us
in the right direction on various topics.
We especially want to acknowledge Simon Archer's amazing reviewing skills. He reviewed the
entire manuscript in great detail and provided extremely useful and timely feedback. Simon,
you're the greatest.
Simon was joined in reviewing the entire book by Paul Vanderlei, Matt Lavin, and Pat McCarthy.
Their thoughtful remarks tangibly improved the content and quality of this book. Our hats are
off to you all.
We were also fortunate to have a large number of people from the community who reviewed
various chapters and provided valuable feedback. In particular, we'd like to acknowledge Chris
Laffra, Pascal Rapicault, Nick Edgar, Kim Horne, Doug Pollock, Wassim Melhem, Michael
Valenta, Rafael Chaves, and Darren Lafond.
John Wiegand, Mike Wilson, and Dave Thomson, our IBM® management trio, were fantastically
supportive of our effortsclearly, without their support, this book would not have happened.
Of course, no book project is possible without a publishing team. We were lucky to have John
Neidhart and Greg Doench as editors with Noreen Regina running the show. Thanks to the
whole crew at Addison-Wesley, Christy Hackerd, Mary Sudul, Mary Kate Murray, Stephane
Nakib, and Jake McFarland for making this a relatively painless and quite enjoyable experience.
It must be said, however, that the combined efforts of all the people acknowledged here do not
match the contributions made by our wives, Nancy and Nadine. They single-handedly kept the
households running, the four children under the age of 4 warm, fed, and clothed, and the two
would-be author husbands encouraged, motivated, and loved. Their support has been truly
phenomenal.
Preface
In many ways, this book is one of the design documents for the Eclipse Rich Client Platform
(RCP). It was written during the Eclipse 3.1 development cycle by members of the development
team. Its chapters were sometimes written before the related function was even implemented.
The exercise of explaining how things work forced upon us the realities of using the mechanisms
and concepts that make up the Eclipse RCP. This was not always pleasant. It did, however, give
us a unique opportunity to correct the course of the Eclipse RCP.
Whenever we came across something that was hard to explain or complicated to use, we were
able to step back and consider changing Eclipse to make things easier. Often we could, and
often we (or, more accurately, the Eclipse Platform team as a whole) did. It is somewhat hard
to convey the joyful feeling of deleting a complicated, detailed 10-page set of instructions or
explanation and replacing it with just a paragraph detailing a new wizard or facility.
On other occasions, we gained key insights that helped us produce a clearer, simpler description
of a function. Fixing bugs discovered during this process provided welcome distractions as we
were writing, coding, learning, and trying to have real lives all at the same time.
We learned an incredible amount about Eclipse as an RCP and trust that you will too.
About this Book
This book guides you, the would-be RCP developer, through all stages of developing and
delivering an example RCP application called Hyperbola, an instant messaging chat client.
We develop Hyperbola from a blank workspace into a full-featured, branded RCP application.
The choice of the instant messaging domain allowed us to plausibly touch a wide range of RCP
issues from building pluggable and dynamically extensible systems to using third-party code
libraries to packaging applications for a variety of environments. We cover scenarios ranging
from PDAs to kiosks, to standalone desktops, to full integration with the Eclipse IDE. This book
enables you to do the same with your applications.
Roughly speaking, the book is split in two. The first half, Parts I and II, sets the scene for RCP
and presents a tutorial-style guide to building an RCP application. The tutorial incrementally
builds Hyperbola into a functioning, branded chat client complete with Help, Update, and other
advanced capabilities. The tutorial is written somewhat informally to evoke the feeling that we
are there with you, working through the examples and problems. We share some of the pitfalls
and mishaps that we experienced while developing the application and writing the tutorial.
The second half of the book looks at what it takes to "make it real." It's one thing to write a
prototype and quite another to ship a product. Rather than leaving you hanging at the
prototype stage, Parts III and IV are composed of chapters that dive into the details required to
finish the jobnamely, the refining and refactoring of the first prototype, customizing the user
interface, and building and delivering products to your customers. This part is written as more
of a reference, but it still includes a liberal sprinkling of step-by-step examples and code
samples. The goal is to cover most of the major stumbling blocks reported in the community
and seen in our own development of professional products.
A final part, Part V, is pure reference. It covers the essential aspects of OSGi, the base
execution framework for Eclipse, and touches on various functions available in the Eclipse
Platform but not covered earlier in the book.
Since one book could not possibly cover everything about Eclipse, and there are many existing
books that cover Eclipse and plug-in development, we focus on the areas directly related to RCP
function, API, and development.
Audience
This book is targeted at several groups of Java™ developers. Some Java programming
experience is assumed and no attempt is made to introduce Java concepts or syntax.
For developers new to the Eclipse RCP, there is information about the origins of the platform,
how to get started with the Eclipse IDE, and how to write your first RCP application. Prior
experience with Eclipse is helpful, but not necessary.
For developers experienced with creating Eclipse plug-ins, the book covers aspects of plug-in
development that are unique to RCP development. For example, not only are there special
hooks for RCP applications, but RCP applications have additional characteristics such as
branding, plug-in building as part of a release engineering process, deployment, and
installation to name a few.
For experienced Eclipse RCP developers, this book covers new RCP features and functions in
Eclipse 3.1 as well as the new tooling that makes designing, coding, and packaging RCP
applications easier than ever before.
Sample Code
Reading this book can be a very hands-on experience. There are ample opportunities for
following along and doing the steps yourself as well as writing your own code. The CD that
accompanies the book includes code samples for each chapter. Instructions for managing these
samples are given in Chapter 3, "Tutorial Introduction," and as needed in the text. In general,
all required materials are available on the CD. Note that these materials are also available on
the Web from either http://eclipse.org or http://eclipsercp.org.
The CD includes development tooling, targets, and sample code appropriate for several
operating systems (OSs), including Windows, ® Linux,™ and Mac OS X. In particular, the
following resources are included:
a readme.html file with installation and usage instructions.
Eclipse 3.1 SDK
Eclipse 3.1 RCP SDK
Eclipse 3.1 RCP Delta pack
code samples for each chapter as needed
Java Development Kit (JDK) 1.4.2 for Windows and Linux
Conventions
The following formatting conventions are used throughout the book:
Bold Used for UI elements such as menu paths (e.g., File > New > Project) and wizard
and editor elements.
Italics Used for emphasis and to highlight terminology.
Lucida Sans Typewriter Used for Java code, property names, filepaths, plug-in ids, and
the like that are embedded in the text of a paragraph.
Lucida Console Used for Java code samples and XML snippets.
Lucida Console Bold Used to highlight important lines in code samples.
Notes and sidebars are used often to highlight information that readers may find interesting or
helpful in using or understanding the function being described in the main text. We tried to
achieve an effect similar to that of an informal pair-programming experience where you sit
down with somebody and get impromptu tips and tricks here and there.
Feedback
The official Web site for this book is http://eclipsercp.org/book. Additional information and
errata are available at http://www.awprofessional.com/title/0321334612. You can report
problems or errors found in the book or CD to the authors at [email protected] Suggestions
for improvements and feedback are also very welcome.
Part I: Introduction
This first part of the book introduces Eclipse as a Rich Client Platformthe Eclipse RCP.
Chapter 1 outlines the notion of "rich client platforms" and the origins of the Eclipse RCP,
and illustrates its use with some real-world examples of RCP in use today. Chapter 2 gives
an overview of Eclipse concepts, terminology, and architecture to ensure all readers have a
common understanding.
Chapter 1. Eclipse as a Rich Client
Platform
The term rich client was coined in the early 1990s with the rush to build client applications using
the likes of Visual Basic and Delphi. The dramatic increase in the number and popularity of
these client applications was due in part to the desire for a "rich" user experience.
Rich clients support a high-quality end-user experience for a particular domain by providing rich
native user interfaces (UIs) as well as high-speed local processing. Rich UIs support native
desktop metaphors such as drag-and-drop, system clipboard, navigation, and customization.
When done well, a rich client is almost transparent between an end-user and their
workfostering focus on the work and not the system. The term rich client was used to
differentiate such clients from terminal client applications, or simple clients, which they
replaced.
The rise of client technology was accompanied by improvements in development environments.
WYSIWYG UI designers made building rich client applications easy and fun. These development
tools allowed client programmers to reuse common building blocks to reduce development time.
Early rich client platforms (RCPs) were used to glue the client's business logic to the operating
system (OS). They eliminated many of the menial programming tasks required to create UIs
and access databases. The middleware provided frameworks and infrastructure so developers
could spend more time programming domain logic rather than reinventing the wheel.
End-users were happy with the resultant rich client applications, as they were functional and
easy to use. Information technology (IT) managers, however, found many hidden costs.
Deploying and upgrading clients is a manual task and users often tweaked their installs, moved
files, or installed other clients that overrode shared libraries.
Then along came the Internet and Web-based applications, or thin clients. Thin clients promised
to solve many of the deployment and management problems related to rich clients. Since
applications were on servers, updates were made centrally. User machines required only a Web
browser. This reduced the cost of deploying and maintaining enterprise applications at the
expense of the user experiencethin clients did not provide the UI features and high-speed
interactions users had come to expect.
The cost savings and simplifications were popular, but the move to thin clients was ultimately a
step back in functionality and capability. Thin client applications, using the request-andresponse model, required more networking capability to ensure optimal interaction
performance. Moreover, as applications and users became more sophisticated, a number of new
requirements for distributing business logic and handling mobile devices and disconnected
clients came to the surface. These could not be implemented using thin clients.
Today's users and problems are driving a return to rich clients. Domains are becoming more
complex, and the amount of data to visualize and manipulate is increasing, as is the need for
integration with other systems. The demand for rich clients goes beyond the desire for a richer
UI. Users need to be mobile, work offline, integrate their content and workflows, collaborate,
and take advantage of local hardware.
But what about the deployment and maintenance problems that caused the earlier shift to thin
clients in the first place? Has something changed to mitigate those issues? Yes, today there is
an increasing number of component mechanisms, such as Eclipse, available to rich client
application developers. Componentized systems address both deployment and maintenance
issues by insulating and isolating components from change. They also enable the type of
application integration needed for today's dynamic scenarios. In short, this new brand of rich
clients provides the best of both worlds.
1.1. Eclipse
If you are new to Eclipse, you are probably wondering, "What is Eclipse?" First and foremost,
Eclipse is an open source community of people building Java™-based tools and infrastructure to
help you solve your problems. The most obvious output of the community is the Eclipse Java
Integrated Development Environment (IDE). This world-class Java IDE regularly tops the charts
in developer satisfaction and use. It's also free from http://eclipse.org.
Underneath the IDE is a generic tooling platform that supports a wide range of tools for
languages and systems from Java to C to Python to Web technologies to data manipulation and
reporting. The Eclipse component model means that these tools can be combined and
integrated as needed.
Under the tooling platform is the Eclipse Rich Client Platform. This is a generic platform for
running applications. The Eclipse IDE happens to be one such application. This book focuses on
how you can build your application and take advantage of the Eclipse RCP.
1.2. The Eclipse Rich Client Platform
Why is Eclipse particularly suited to building rich client applications? To answer that, let's look
at the history of rich and thin clients and observe how the characteristics of Eclipse maintain the
benefits and address the pitfalls of past approaches. Those characteristics are summarized
below:
Components Eclipse includes a robust component model. Eclipse-based systems are built
by composing components known as plug-ins. Plug-ins are versioned and can be shared by
more than one application. Multiple versions of the same plug-in can be installed side-byside and applications can be configured to run with the exact versions they need. This
approach is attractive as it allows applications to evolve over time by adding and replacing
components.
Middleware and infrastructure On top of this component model is a set of frameworks
and facilities that makes the job of writing client applications much easier. The Eclipse RCP
is essentially the middleware function that you don't want to write because it is a means,
not an end, in your domain. It includes facilities such as a flexible UI paradigm, scalable
UIs, extensible applications, help support, context-sensitive help, network updates, error
handling, and much more.
Native user experience In contrast to what is provided by thin clients, many users want
a rich, comfortable, and native user experience. This includes a smooth, responsive UI that
integrates into the desktop. The Eclipse Standard Widget Toolkit (SWT) provides a
graphical user interface (GUI) toolkit for Java that allows efficient and portable access to
the native UI facilities of the OS.
Portability Thin clients have the advantage of running everywhere. Eclipse provides
support for heterogeneous OSs and client environments, ranging from servers to
traditional PCs, to thinner devices such as tablets and kiosks, down to mobile and
embedded devices such as PDAs and smartphones. As long as you can find a Java virtual
machine (JVM) with the J2ME™ Foundation libraries or greater (e.g., J2SE™ 1.4), then you
can run your application. Chapter 23, "RCP Everywhere," talks about how to use Eclipse to
design applications that essentially run everywhere.
Intelligent install and update Controlling the costs associated with deploying and
maintaining rich client applications was a problem in the early days. Eclipse's component
framework enables plug-ins to be deployed and updated using any number of
mechanisms: HTTP, Java Web Start, Update sites, simple file copying, or sophisticated
enterprise management systems. Chapter 25, "The Last Mile," details the task of getting
your Eclipse RCP applications to your users.
Disconnected operation Because rich client applications run on a local machine, they
can run standalone without a network connection. This is a major advantage over thin
clients. Applications that are inherently disconnected can use local caches, replicas, and
store-and-forward mechanisms to accommodate network interruptions.
Development tooling support Developers in the first wave of rich clients enjoyed IDEs
that helped them build their applications. Eclipse provides a first-class Java IDE that
contains integrated tooling for developing, testing, and packaging rich client applications.
Component libraries A component framework is not complete without a comprehensive
set of components with which to build your applications. The Eclipse community has
produced plug-ins for building pluggable UIs, managing help content, install and update
support, text editing, consoles, product introductions, graphical editing frameworks,
modeling frameworks, reporting, data manipulation, and much more. Some of these are
discussed in Chapter 27, "Eclipse.org Plug-ins," while Chapter 20, "Integrating Code
Libraries," demonstrates how to integrate non-Eclipse code libraries into your applications.
1.3. Eclipse RCP Over the Years
The Eclipse project did not start with the intention of building an RCP. Instead, its goal was to
create a platform for integrating development tools. Eclipse as an RCP started in the Eclipse 2.1
release timeframe as a hacker activity. The word was out that Eclipse-based IDEs were
professional, good-looking, polished, and performed well. A few intrepid developers further
observed that the same framework that made tooling easier to write and more attractive could
be used to build more generic applications.
By and large they were right, but there were many challenges. The most obvious of these were
the interweaving of assumptions based on Eclipse as a tooling platform and the resultant
inability to change certain elements of the environment's look and feel.
Eclipse 3.0 was a major enabling step for Eclipse as an RCP. Virtually all of the IDE-related
interdependencies were eliminated and many of the different parts of the UI were opened to
customization. The groundwork for dynamic plug-in installation, removal, and updating was
established with the introduction of an OSGi-based (http://osgi.org) runtime. These two work
items amounted to a massive refactoring of the main aspects of the Platform.
With these improvements, interest in RCP rose sharply and commercial applications began to
emerge. IBM introduced its Workplace™ products, NASA started using RCP for managing,
modeling, and analyzing space missions, and RCP showed up unnoticed in applications in
various domains.
This book's release coincides with the release of Eclipse 3.1 and what has really been a "coming
of age" for the RCP effort. Eclipse 3.1 contains countless improvements, refinements, and
wholesale leaps in the support of diverse application scenarios. The tooling for creating and
editing product definitions is one such leap. RCP developers are now able to define the
branding, content, and deployment strategies for their products using purpose-built editors and
wizards.
1.4. Uses of RCP
Even in the relatively short life of the Eclipse RCP, we have seen quite a number and range of
applications adopting the technology. A few are noted on the RCP applications page of the
Eclipse RCP Web site at http://eclipse.org/rcp. There are, however, too many to detail hereor
there for that matter. Rather, we highlight two uses that stand out as compelling examples of
what RCP is all about: IBM Workplace and the NASA Maestro project.
1.4.1. IBM Workplace Client Technology™
The Workplace team at IBM was one of the leading innovators in the use of Eclipse for generic
applications. They were looking to build the next generation of productivity tools (e.g., email,
messaging, document management) for enterprises and address the following issues:
Management costs
Deploying coherent componentized systems
Role-based function delivery
Rich user experiences
Collaboration
The Workplace team chose to build on Eclipse as a cross-platform, industrial-strength base to
enable supporting applications such as messaging, document management, collaboration, and
other business applications. An additional driver was that the extensible componentized Eclipse
Platform provides integration on-ramps for existing technologies.
The net result of this effort is the Workplace Client Technology platform, a server-managed
application container that runs on your desktop. One use of this technology has users interact
with the system through a server-based portal. The portal supplies various applications
depending on the user and their role. Application implementation technology varies from
portlets and Java server pages (JSPs™) running on the server to Eclipse RCP-based applications
that are dynamically downloaded to and run on the client. The set of applications available to a
user is managed by the server, as is the deployment of those applications.
The application container that runs on the client is essentially a standalone Eclipse RCP shell
with additional middleware, security, UI, and management support. Both platform-optional
components and end-user applications are incrementally provisioned by the server; that is, the
server provisions Eclipse plug-ins to the client. These plug-ins are dynamically installed and
run, thus giving the user access to the application. A key benefit of this model is that only the
platform extensions and applications a user has permission to use are provisioned, resulting in
a minimal fit-for-purpose configuration.
The container itself is highly configurable. Figure 1-1 shows a typical Workplace configuration.
Down the left side is a chooser that lists the applications available to the user. This list is based
on a user's roles and is centrally configured. Again, some applications may run on the server,
some on the desktop. Of those that run on the desktop, only the ones in use are physically
installed on the desktop; the others are downloaded and installed on-demand.
Figure 1-1. IBM Workplace
[View full size image]
On the right are a set of views showing tools such as instant messaging contacts and data
related to the application in the middle of the screen. In this example, the application is a threedimensional product lifecycle management tool displaying an automobile with its parts
assembly. The IBM Workplace Managed Client™ is completely pluggable and the layout highly
customizable using declarative markup.
A different user, in a different role, might experience a different look and feel and have access
to a different set of applications. This is all configured on, and managed by, the server.
Speaking of look and feel, notice that Workplace looks nothing like standard Eclipse. The
Workplace team used the extensibility of Eclipse RCP to create unique user interaction
paradigms and models to fit their needs. They in turn expose a personality mechanism that
allows consumers of Workplace to further customize and brand the UI.
Workplace is in use today in various enterprises that range from financial services to banking to
government and the public sector. Usage scenarios range from desktops to call center and bank
teller workstations to kiosks.
1.4.2. NASA and Eclipse RCP
In the late 1990s, NASA embarked on a software project that eventually became known as
Maestro. It was an ambitious project to write Java-based tools for managing remote vehicles
and experiments on space missions such as the Mars Polar Lander. They started out using
various Java technologies, including applets, and progressively improved the capability and
usability of their applications by moving to more powerful rich client approaches. This
culminated in a series of scientific analysis and planning tools used in surface missions such as
Spirit and Opportunity exploring Mars.
In mid-2004, the Maestro team changed to use Eclipse as their development environment and
the Eclipse RCP as the base for Maestro. Figure 1-2 shows the Eclipse RCP-based Maestro
manipulating some Mars Rover data.
Figure 1-2. Maestro
[View full size image]
Mission software has its unique characteristics, but by and large, it is subject to the same
requirements as the applications you write.
Diverse functional requirements Any given mission involves a vast number of tools
from data browsing to target acquisition to activity generation to spacecraft monitoring
and reporting. These particular tools likely do not apply in your domain, but the underlying
technologies (e.g., sophisticated user interaction, complex rendering, data management,
reporting) and the related Eclipse plug-ins are of interest in a wide variety of scenarios.
Adaptation The cumulative set of requirements on the system changes radically and
quickly. Each mission is different, each experiment unique. In effect, each mission requires
that a new suite of tools be brought together. The Eclipse component model enables the
sharing and reconfiguration of functions.
Diverse user base Mission software has a wide range of users from computer-shy domain
experts to technophile engineers to members of the general public. The Eclipse
frameworks simplify the creation of world-class, accessible UIs that scale to meet the
needs of the user.
Central management Much of the work done with mission software is, as the name
implies, mission-critical. As such, the deployed software must be closely managed to
ensure that it is consistent and correct. The Eclipse Update Manager ensures that the
correct components are deployed.
Beyond Maestro and its use of Eclipse RCP facilities, the move to the Eclipse RCP has had a
more profound impact. For example, the use of the Eclipse component model has fundamentally
changed the way NASA teams build their mission systems. Components are developed
independently by geographically and organizationally dispersed teams. These components must
be integrated on a continuous basis. The fact that all components are defined in common terms
(e.g., plug-ins and features), and their dependencies fully expressed, brings structure and
control to this process. NASA has, in effect, created a platform for space exploration mission
software and the teams are plugging into that platform.
The wealth of quality plug-ins available from the Eclipse project and elsewhere has allowed the
NASA team to throw away enormous chunks of their existing code. For example, they had
previously written their own update mechanism. Now they can use the one in Eclipse. The
earlier software had its own UI frameworks and mechanisms. These have been replaced with
the Eclipse Workbench model. The team as a whole is now able to focus more on the problem of
mission software and less on the infrastructure and middleware inherent in writing sophisticated
applications.
The use of Eclipse RCP within NASA has spread rapidly. The new Ensemble project seeks to
broaden Maestro and form a base for all NASA mission software tools.
1.5. Summary
Eclipse is many things to many people. This book focuses on Eclipse as an RCP. As you have
seen, rich client applications are making a comeback and componentized approaches are
making them even more attractive.
The Eclipse RCP addresses complex application scenarios that span the spectrum from thin to
rich clients and from enterprise- and business-oriented systems to scientific and data
management scenarios. The example use cases are from vastly different domains and
environments, but yet they illustrate Eclipse as it addresses the universal themes of:
Componentization
Focus on the domain rather than the infrastructure
Adaptation to changing requirements
The goal of this book is to describe the use cases, processes, and steps for using the Eclipse RCP
to similar effect and benefit while building your rich client applications quickly and effectively.
Chapter 2. Eclipse RCP Concepts
The Eclipse environment is very rich, but there are just a few concepts and mechanisms that are
essential to Eclipse-ness. In this chapter, we introduce these concepts, define some
terminology, and ground these concepts and terms in technical detail. The ultimate goal is to
show you how Eclipse fits together, both physically and conceptually.
Even if you are familiar with Eclipse, you might want to flip through this chapter to ensure we
have a common base of understanding and terminology. Writing RCP applications is subtly
different than just writing plug-ins. You have the opportunity to define more of the look and
feel, the branding, and other fundamental elements of Eclipse. Understanding these
fundamentals enables you to get the most out of the platform. With this understanding, you can
read the rest of the book and see how Eclipse fits into your world.
2.1. A Community of Plug-ins
In Chapter 1, "Eclipse as a Rich Client Platform," we described the essence of Eclipse as its role
as a component framework. The basic unit of function in this framework is called a plug-inthe
unit of modularity in Eclipse. Everything in Eclipse is a plug-in. An RCP application is a collection
of plug-ins and a Runtime on which they run. An RCP developer assembles a collection of plugins from the Eclipse base and elsewhere and adds in the plug-ins she has written. These new
plug-ins include an application and a product definition along with their domain logic. In
addition to understanding how Eclipse manages plug-ins, it is important to know which existing
plug-ins to use and how to use them, and which plug-ins to build yourself and how to build
them.
Small sets of plug-ins are easy to manage and talk about. As the pool of plug-ins in your
application grows, however, grouping abstractions are needed to help hide some of the detail.
The Eclipse teams define a few coarse sets of plug-ins, as shown in Figure 2-1.
Figure 2-1. 10,000-foot system architecture view
At the bottom of the figure is the Eclipse RCP as a small set of plug-ins on top of a Java Runtime
Environment (JRE). The RCP on its own is much like a basic OS or the Java JRE itselfit is waiting
for applications to be added.
Note
Don't take the boxes in Figure 2-1 too seriously. They are a guess, by the producers of
the plug-ins, at groupings that are coherent to consumers of the plug-ins. The
groupings are useful abstractions; but remember, for every person that wants some
plug-in inside a box, there is someone who wants it outside. That's OK. You can build
your own abstractions.
Fanning upwards in the figure is a collection of RCP applicationssome written by you, some by
others, and some by Eclipse teams. The Eclipse IDE Platform, the traditional Eclipse used as a
development environment, is itself just a highly functional RCP application. As shown in Figure
2-1, the IDE Platform requires some of the plug-ins in the Eclipse RCP. Plugged into the IDE
Platform is the Eclipse Software Development Kit (SDK) with its Java and plug-in tooling and
hundreds of other tools written by companies and the open source community.
This pattern continues. The general shape of the Eclipse RCP and your products is the samethey
are both just sets of plug-ins that make up a coherent whole. These themes of consistency and
uniformity recur throughout Eclipse.
Note
Notice in Figure 2-1 that the Eclipse RCP requires only Foundation Java class libraries.
Foundation is a J2ME standard class set typically meant for embedded or smaller
environments. See http://java.sun.com/products/foundation for more details. If you
are careful to use only a Foundation-supported API, then you can ship Eclipse-based
applications on a Java Runtime that is only about 6MB rather than the 40MB J2SE 1.4
JRE.
The internal detail for the Eclipse RCP plug-in set is shown in Figure 2-2. These plug-ins form
the base of your RCP applications. Here we see a set of interdependent plug-ins that provide
various capabilities as noted in the callout boxes. We could have zoomed in on any of the plugin sets in Figure 2-1 and seen the same basic structurean example of uniformity. You are in fact
free to slice and dice the RCP itself or any other plug-in set to suit your needs as long as the
relevant plug-in interdependencies are satisfied. In this book, we focus on RCP applications as
applications that use the full RCP plug-in set.
Figure 2-2. 1,000-foot RCP view
Managing the dependencies is a large part of building an Eclipse application. Plug-ins are selfdescribing and explicitly list the other plug-ins or functions that must be present for them to
operate. The Runtime's job is to resolve these dependencies and knit the plug-ins together. It's
interesting to note that these interdependencies are not there because of Eclipse, but because
they are implicit in the code and structure of the plug-ins. Eclipse allows you to make the
dependencies explicit and thus manage them effectively.
2.2. Inside Plug-ins
Now that you've seen the 10,000- and 1,000-foot views of Eclipse, let's drop down to 100 feet
and look at plug-ins, the basic building blocks of Eclipse. A plug-in is a collection of files and a
manifest that describe the plug-in and its relationships to other plug-ins.
Figure 2-3 shows the layout of the org.eclipse.ui plug-in. The first thing to notice is that the
plug-in is a Java Archive (JAR), org.eclipse.ui_3.1.0.jar. As a JAR, it has a MANIFEST.MF. The
manifest includes a description of the plug-in and its relationship to the rest of the world.
Figure 2-3. Plug-in disk layout
Plug-ins can contain code and/or read-only content such as images, Web pages, translated
message files, documentation, and so on. For instance, the UI plug-in in Figure 2-3 has code in
the org/eclipse/ui/... directory structure and other content in icons/ and about.html.
Notice that the plug-in also has a plugin.xml file. Historically, that was the home of the
execution-related information now stored in the MANIFEST.MF. The plugin.xml continues to be
the home of any extension and extension point declarations contributed by the plug-in.
Eclipse 3.1 vs. 3.0
Readers familiar with Eclipse 3.0 may be surprised by some of the characterizations
here. This book is written to reflect the best practices for Eclipse 3.1. In particular,
Eclipse 3.1 plug-ins are delivered as JARs rather than as directories. The executionrelated information formerly kept in the plugin.xml file is now in the MANIFEST.MF
file. Of course, Eclipse 3.1 is fully backward-compatible with both the Eclipse 3.0
approach to plug-in definition and delivery and legacy plug-ins.
Unless otherwise stated, the remainder of the book details Eclipse 3.1.
2.3. Putting a System Together
With all these plug-ins floating around, what does an Eclipse system look like on disk? Figure 24 shows a typical RCP SDK install. The top-most directory is the install location. It includes a
plug-in store, some bootstrap code, and a launcher, eclipse.exe, which is used to start Eclipse.
Figure 2-4. The anatomy of an Eclipse installation
The plug-in store contains a directory or JAR file for each plug-in. By convention, the name in
the filesystem matches the identifier of the plug-in and is followed by its version number. Each
plug-in contains its files and folders as described earlier.
The configuration location contains the configuration definition. This definition describes which
plug-ins are to be installed into the Runtime and run by Eclipse. The configuration location is
also available to plug-ins for storing settings and other data such as preferences and cached
indexes. By default, the configuration location is part of the install location. This is convenient
for standard single-user installs on machines where users have full control. Products and
shared, or multi-configuration, installs on UNIX systems may, however, put the configuration
location elsewhere, such as the current user's home directory.
2.4. OSGi Framework
The Eclipse plug-in component model is based on an implementation of the OSGi framework
R4.0 specification (http://osgi.org). You can see it at the bottom of Figure 2-4. In a nutshell,
the OSGi specification forms a framework for defining, composing, and executing components
or bundles. Think of bundles as the implementation of plug-ins. The term plug-in is used
historically to refer to components in Eclipse and is used throughout the documentation and
tooling.
There are no fundamental or functional differences between plug-ins and bundles in Eclipse.
Both are mechanisms for grouping, delivering, and managing content. In fact, the traditional
Eclipse Plugin API class is just a thin, optional layer of convenience functioning on top of OSGi
bundles. To Eclipse, everything is a bundle. As such, we use the terms interchangeably and walk
around chanting, "A plug-in is a bundle. A bundle is a plug-in. They are the same thing."
It is convenient to think of the OSGi framework as supplying a component model to Java; that
is, think of it as a facility at the same level as the base JRE. OSGi frameworks manage bundles
and their code by managing and segmenting their classloadingevery bundle gets its own
classloader. The classpath of a bundle is dynamically constructed based on the dependencies
stated in its manifest. The manifest defines who a bundle is and on whom it depends. All
bundles are self-describing.
The MANIFEST.MF shown in Figure 2-5 gives the org.eclipse.ui plug-in a plug-in id, or bundle
symbolic name, and a version. Common practice is to use Java package name conventions such
as org.eclipse.ui for the identifier and [major.minor.service.qualifier] tuples for the version
number. The id and version are paired together to uniquely identify the plug-in. The pairs are
then used to express dependency relationships. You can see this in the Require-Bundle header
of the manifestthe UI plug-in requires the Runtime, JFace, and SWT plug-ins.
Figure 2-5. Plug-in manifest
In the context of Eclipse, the OSGi framework's main role is to knit together the installed plugins, allowing them to interact and collaborate. The rigorous management of dependencies and
classpaths enables tight and explicit control over bundle interactions and thus the creation of
systems that are more flexible and more easily composed.
OSGi and Eclipse
The OSGi Alliance (http://osgi.org) was formed independently about the same time
the Eclipse project started. Its original mission was to provide a Java component
and service model for building embedded devices such as residential gateways, settop boxes, car dashboard computers, and so on.
The RCP focus during the Eclipse 3.0 development cycle spun off the Equinox
technology project (http://eclipse.org/equinox), which explored ways of making the
Runtime more dynamic to support plug-in install and uninstall without restarting.
Various existing alternatives were considered and OSGi emerged as a standard,
dynamic framework, quite similar to Eclipse. As a result, Eclipse 3.x is based on an
implementation of the OSGi framework specification and Eclipse 3.1 includes a
standalone OSGi implementation in org.eclipse.osgi_3.1.0.jar. See
http://eclipse.org/osgi for more details.
2.5. The Runtime
Historically, the Eclipse Runtime also included the plug-in model. As you have seen, the plug-in
model has moved down to the OSGi layer. This leaves the remainder of the Runtime on top. The
Runtime is home to several key mechanisms, in particular, the application model and extension
registry.
2.5.1. Applications
Like the OSGi framework and JVMs, the Eclipse Runtime has to be told what to do. To run
Eclipse, someone has to define an application. An application is very much like the main()
method in normal Java programs. After the Runtime starts, it finds and runs the specified
application. Applications are defined using extensions. Application extensions identify a class to
use as the main entry point. When you run Eclipse, you can specify an application to run. Once
invoked, the application is in full control of Eclipse. When the application exits, Eclipse shuts
down.
Standalone vs. extension offerings
Offerings are the things that you ship to customers. We distinguish between
standalone and extension offerings. A standalone offering is one that comes as a
complete set of plug-ins, with its own branding and its own application entry
pointend-users run standalone offerings.
Some standalone offerings are closedthey are not intended to be extended. The true
power of Eclipse comes from offerings that are designed to be extended by others
and thus create platforms. The Eclipse SDK is a platform, as are the offerings
described in Chapter 1.
Extension offerings are sets of plug-ins that are incomplete and destined to be
added to some platform. For example, sets of tooling plug-ins such as the Eclipse
Modeling Framework (EMF), Graphical Editor Framework (GEF), and C Development
Tooling (CDT), which are added to the Eclipse SDK tooling platform, are extension
offerings. They do not have an entry point of their own, nor do they have substantial
branding.
For most of this book, these distinctions are academic. When it comes to discussions
of packaging, branding, and updating, the differences become apparent.
2.5.2. Products
The notion of a product is a level above applications. You can run Eclipse by just specifying an
application, but the product branding context (e.g., splash screen and window icons) and
various bits of customization (e.g., preferences and configuration files) would be missing. The
notion of a product captures this diffuse set of information into one conceptsomething that users
understand and run.
Note
Any given Eclipse installation may include many applications and many products, but
only one product and application pair can be running at a time.
2.5.3. Extension Registry
The OSGi specification provides a mechanism for defining and running separate components.
The Eclipse Runtime adds to that a mechanism for declaring relationships between plug-insthe
extension registry. Plug-ins can open themselves for extension or configuration by declaring an
extension point. Such a plug-in is essentially saying, "If you give me the following information, I
will do ..." Other plug-ins then contribute the required information to the extension point in the
form of extensions.
The canonical example of this is the UI plug-in and its actionSets extension point. Simplifying
somewhat, action sets are how the UI talks about menu and toolbar entries. The Eclipse UI
exposes the extension point org.eclipse.ui.actionSets and says,
"Plug-ins can contribute actionSets extensions that define actions with an id, a label, an
icon, and a class that implements the interface IActionDelegate. The UI will present that
label and icon to the user, and when the user clicks on the item, the UI will instantiate the
given action class, cast it to IActionDelegate, and call its run() method."
Figure 2-6 shows this relationship graphically.
Figure 2-6. Extension contribution and use
[View full size image]
Extension-to-extension point relationships are defined using XML in a file called plugin.xml.
Each participating plug-in has one of these files. In this scenario, org.eclipse.ui's plugin.xml
includes the following:
org.eclipse.ui/plugin.xml
<extension-point id="actionSets" name="Action Sets"/>
The Hyperbola plug-in, org.eclipsercp.hyperbola, developed later in the book, similarly
contributes an extension using the markup shown in the plugin.xml snippet below:
org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.ui.actionSets">
<actionSet id="org.eclipsercp.hyperbola.debugActionSet">
<action
id="org.eclipsercp.hyperbola.debug"
class="org.eclipsercp.hyperbola.DebugAction"
icon="icons/debug.gif"
label="Debug Chats"/>
</actionSet>
</extension>
The actionSets extension point contract plays out as follows: The UI presents the label "Debug
Chats" along with the debug.gif icon. When the user clicks on the action, the class DebugAction
is instantiated and its run() method is called.
This seemingly simple relationship is extremely powerful. The UI has effectively opened up its
implementation of the menu system, allowing other plug-ins to contribute menu items. Further,
the UI plug-in does not need to know about the contributions ahead of time and no code is run
to make the contributionseverything is declarative and lazy. These turn out to be key
characteristics of the registry mechanism and Eclipse as a whole. Some other characteristics
worth noting here are:
Extensions and extension points are used extensively throughout Eclipse for everything
from contributing views and menu items to connecting Help documents and discovering
builders that process resource changes.
The mechanism can be used to contribute code or data.
The mechanism is declarativeplug-ins are connected without loading any of their code.
The mechanism is lazy in that no code is loaded until it is needed. In our example, the
DebugAction class was only loaded when the user clicked on the action. If the user does
not use the action, the class is not loaded.
This approach scales well and enables various approaches for presenting, scoping, and
filtering contributions.
2.6. SWT
Sitting beside the Runtime is the Standard Widget Toolkit (SWT). SWT is a low-level graphics
library that provides standard UI controls such as lists, menus, fonts, and colors, that is, a
library that exposes what the underlying window system has to offer. As the SWT team puts it:
"SWT provides efficient, portable access to the UI facilities of the OSs on which it is
implemented."
This amounts to SWT being a thin layer on top of existing windowing system facilities. SWT
does not dumb-down or sugarcoat the underlying window system, but rather exposes it through
a consistent, portable Java API. SWT is available on a wide variety of window systems and OSs.
Applications that use SWT are portable among all supported platforms.
The real trick of SWT is to use native widgets as much as possible. This makes the look and feel
of SWT-based applications match that of the host window system. As a result, SWT-based
systems are both portable and native.
Notice that SWT does not depend on the Runtime or OSGi framework. It is a standalone library
that can be used outside of Eclipse or RCP.
2.7. JFace
Whereas SWT provides access to the widgets as defined by the window system, JFace adds
structure and facilities for common UI notions. The UI team describes JFace as follows:
"JFace is a UI toolkit with classes for handling many common UI programming tasks. JFace
is window system-independent in both its API and implementation, and is designed to
work with SWT without hiding it."
It includes a whole range of UI toolkit components, from image and font registries, text support,
dialogs, and frameworks for preferences and wizards to progress reporting for long-running
operations. These and other JFace UI structures, such as actions and viewers, form the basis of
the Eclipse UI.
2.8. UI Workbench
As JFace adds structure to SWT, the Workbench adds presentation and coordination to JFace. To
the user, the Workbench consists of views and editors arranged in a particular layout. In
particular, the Workbench:
Provides contribution-based UI extensibility
Defines a powerful UI paradigm with windows, perspectives, views, editors, and actions
2.8.1. Contribution-Based Extensibility
Whereas JFace introduces actions, preferences, wizards, windows, and so on, the Workbench
provides extension points that allow plug-ins to define such UI elements declaratively. For
example, the wizard and preference page extension points are just thin veneers over the related
JFace constructs.
More than this, however, the use of extensions to build a UI has a fundamental impact on the
scalability of the UI both in terms of complexity and performance. Declarative extensions enable
the description and manipulation of sets of contributions such as the action sets we discussed
earlier. For example, the Workbench's capabilities mechanism supports progressive disclosure of
function by filtering actions until their defining action sets are triggered. Your application may
have a huge number of actions, but the user sees only the ones in which she is interestedthe UI
grows with the user's needs.
Since all of these extensions are handled lazily, applications also scale better. As your UI gets
richer, it includes more views, editors, and actions. Without declarative extensibility, such
growth requires additional loading and execution of code. This increases code bulk and startup
time and the application does not scale. With extensions, no code is loaded before its time.
2.8.2. Perspectives, Views, and Editors
The Workbench appears to the user as a collection of windows. Within each window, the
Workbench allows users to organize their work in much the same way as you would organize
your deskyou put similar documents in folders and stack them in piles on a desk. A perspective
is a visual container for a set of views and content editorseverything shown to the user is in a
view or editor and is laid out by a perspective.
Users organize content in perspectives in the following ways:
Stack editors with other editors
Stack views with other views
Detach views from the main Workbench window
Resize views and editors and minimize/maximize editor and view stacks
Create fast views that are docked on the side of the window
A perspective supports a particular set of tasks by providing a restricted set of views and
supporting action sets as well as shortcuts to relevant content creation wizards, other related
views, and other related perspectives. Users can switch between perspectives, for example, to
change between developing code, trading stocks, working on documents, and instant
messaging. Each of these tasks may have unique layouts and content.
2.9. Summary
In Eclipse, everything is a plug-in. Even the OSGi framework and the Runtime show up as plugins. All plug-ins interact via the extension registry and public API classes. These facilities are
available to all plug-ins. There are no secret back doors or exclusive interfacesif it can be done
in the Eclipse IDE, you can do it in your application.
SWT, JFace, and the UI Workbench plug-ins combine to form a powerful UI framework that you
can use to build portable, highly scalable, and customizable UIs that have the look and feel of
the platform on which you are running.
In short, Eclipse is an ideal technology for building rich client applications.
Part II: RCP by Example
The best way to learn about Eclipse as a rich client platform is to build a rich client
application. This part of the book guides you through just that. Starting with a machine
completely devoid of any Eclipse function, we walk through setting up Eclipse for RCP
development and then creating, running, debugging, and enhancing a reasonably fullfeatured instant messaging client application called Hyperbola. The screen shot on the next
page shows an example of the Hyperbola chat client application you will build.
The material in Part II is presented in an informal, tutorial styleassuming that we are
sitting with you and guiding you through Hyperbola's development. You are encouraged to
follow along and do the steps described. If you would rather not follow the steps, or are
having difficulties, the completed code for each chapter is also available on the CD
included with this book. Even though the chapters are very development-oriented, the text
for each chapter is complete and can be read without following the steps or looking at the
supplied code.
Chapter 3. Tutorial Introduction
Getting started often proves to be one of the biggest challenges. In particular, given the tutorial
nature of the next dozen or so chapters, understanding the goals of the tutorial and ensuring
you have a reasonable development setup is crucial to having an enjoyable learning experience.
This chapter is designed to set the scene and show you:
An overview of the tutorial content and evolution
How to set up for Eclipse RCP development
How to get, compare, and manage the sample code
Some tips for using the Eclipse IDE for exploring code
3.1. What Is Hyperbola?
Hyperbola is an instant messaging chat client developed expressly for this book. The instant
messaging domain is compelling because it is simple and easy to understand. In fact, you most
likely have used an instant messaging client at some point. Whether it is Yahoo messenger, AOL
instant messenger, MSN messaging, or Sametime®, the idea is basically the samethe client
connects to a server, which routes messages from one client to another.
What's so interesting about writing yet another chat client? Even though the idea is
straightforward, instant messaging clients themselves are rich in challenges and feature needs.
Enumerated below are a few of the challenges in putting together a full-featured chat client:
Third-party libraries Writing a messaging library is time consuming at best. Rather than
writing your own, most prefer to use one crafted by experts. Hyperbola is no differentit is
based on the Smack eXtensible Messaging and Presence Protocol (XMPP) messaging
library from Jive Software (http://jivesoftware.org).
Extensibility Simple instant messaging is quite straightforward. Hyperbola, however, is
based on the Internet Engineering Task Force's (IETF's) XMPP messaging standard that
evolved out of the Jabber protocols. Remember, the "X" in XMPP is for eXtensible. And it
certainly is that. There are XMPP extensions for multi-user chat (MUC), file transfer,
eXtensible HyperText Markup Language (XHTML) messaging, user location, and so on. This
implies that a full-featured chat client must also be extensible.
Varied execution environments Instant messaging is pervasive. People use it from their
desktops, handhelds, cell phones, and integrate it with other applications. Servers and
other machines use it to alert administrators, etc. The challenge here is to write a client
that is useful in as many of these environments as possible.
Update Given that the domain is so extensible, the ability to update a deployed chat
application's existing capabilities and add new capabilities is crucial. Updating should
happen with minimal intrusion on the use of the running client.
Complex workflows The domain is simple, but it gives rise to some interesting work and
execution flows. Even simple things like logging on can be complex and messaging is
inherently asynchronous and decoupled.
Customization Chat clients seem to be quite personal. Everyone likes to customize them.
Whether you are the end-user or product designer, customizing the look and feel is
becoming mandatory.
Generic Perhaps the most compelling part of the instant messaging domain as an
example is that many of the challenges described above are relatively genericyou have
probably identified with at least some of these as you read the list.
Stepping back and looking at these challenges, you should notice that they are driven by the
richness of the domain rather than the details of the infrastructure. That is, the Hyperbola
example highlights that RCP is easy; it's the domain that is hard.
3.2. The Evolution of Hyperbola
Over the course of the tutorial chapters, we develop Hyperbola through the series of prototypes
enumerated below. Each of the prototypes is set up to reach some level of application function
and illustrate a coherent set of Eclipse RCP features and functions as well as Eclipse
development environment functions. Each prototype is covered in a series of chapters and the
code added or changed in each chapter is supplied to you. You can follow along and do the
steps yourself, jump around and start in the middle, just browse the code, or simply read the
text.
Hello Hyperbola (Chapter 4) The tutorial starts off with an empty workspace and walks
through the basic elements of creating RCP applications and plug-ins, the structure and
control flow of a simple Hyperbola shell, and the running and debugging of applications.
By the end of this initial prototype, Hyperbola is just a simple shell, but it runs and you
know how to run it! The code for this chapter is entirely generated by templates in Eclipse.
UI sketch (Chapters 57) This prototype focuses on filling out the UI of Hyperbola. The
realities and details of messaging are ignored as we introduce UI concepts such as views,
editors, actions, and perspectives as well as some common user features like system tray
integration. The prototype finishes with a realistic-looking Hyperbola chat client prototype
without the chatting function.
Branded and packaged (Chapters 89) We take a break from coding and the chat domain
to address the somewhat generic issues of branding and packaging. Branding Hyperbola
by adding a splash screen, window images, and about information completes its look and
feel. Packaging it allows you to distribute it to friends and colleagues and get their
feedback. The branded and packaged Hyperbola is fully standalone.
Messaging (Chapters 1012) The main goal of this prototype is to put the "chat" in "chat
client." Here, we add the Smack messaging library to get real messaging capabilities. This
prototype also refines Hyperbola to have key bindings, preferences, and a login dialog.
Well-rounded (Chapters 1314) The last prototype rounds out Hyperbola by adding Help
and Update support. These are important capabilities for real-world applications. Their
addition here sets the stage for the integration of further function. By the end of this
prototype, Hyperbola is a complete XMPP-based chat client that has a number of bells and
whistles but has still only scratched the surface of what is possible with the Eclipse RCP.
3.3. Development Environment Installation
The first thing you need for the tutorial is a set of tools to write the code. The Eclipse SDK has a
full set of Java Development Tooling (JDT), complete with a comprehensive Plug-in
Development Environment (PDE). The CD included with this book contains a complete SDK
configuration for several platforms. You can use that setup, use one of your own, or download
the latest release from http://eclipse.org/downloads.
The Eclipse downloads site contains a vast array of downloads, but the Eclipse SDK is the most
popular and should be highlighted on the site. The download is a zip or tar.gz with a name
similar to:
eclipse-SDK-3.1-win32.zip
This book details function that is new to Eclipse in 3.1, the latest release as the book went to
press. You must be using the released version of 3.1 or later to follow the exercises. Be sure to
get the appropriate download for your machine (OS, window system, and processor). Once you
have the file downloaded, expand it in some convenient location (say c:\ide on Windows) as
shown in Figure 3-1.
Figure 3-1. Development environment disk layout
Note
The download site does not include a Java Runtime Environment™ (JRE) or JDK, but
the CD does. If you are supplying your own setup, you must ensure a compatible JRE
is installed. The download page for each Eclipse SDK release includes links to common
Java environments compatible with the release. To complete all of the work in this
book, you should get a Java SDK (sometimes called a JDK), as it includes a couple of
handy tools such as jarsigner. These are included in the SDKs on the CD.
Now you are set to run the IDE. Double-click on the launcher (eclipse.exe or eclipse,
depending on your machine). When Eclipse starts up, it asks for a workspace location. The
workspace is the place where development artifacts such as projects and code are stored. It is
typically a good idea to locate the workspace somewhere separate from the IDE install. This
simplifies the management of multiple workspaces as well as changing versions of the Eclipse
IDE. By default, Eclipse suggests a location in your user directory (e.g., c:\Documents and
Settings\you\workspace). This is a fine choice.
After you select a workspace location, Eclipse continues to start and shows the welcome page.
Feel free to play around with the information there. When you are ready to continue, go to the
Workbench by clicking on the arrow at the top right corner.
3.4. Target Setup
Before starting development, you have one more thing to install, the target. The target is the
set of plug-ins on which your application is based, that is, everything except the plug-ins you
are writing.
A good starting point for the target is the Eclipse RCP SDK for the latest release. This is the
fundamental set of 11 or so plug-ins that forms the basis of most Eclipse RCP-based
applications. It includes OSGi, the Runtime, SWT, JFace, and the UI Workbench framework.
As with the Eclipse SDK, the CD that comes with this book includes a set of target archives. You
can use these or go back to the Eclipse downloads site for the latest release. On the main
downloads site click the link called "Other downloads for 3.1" and on the next page there is a
section labeled Eclipse RCP SDK. Download the file that matches your machine's OS, window
system, and processor architecture. The download is a zip or tar.gz with a name similar to:
eclipse-RCP-SDK-3.1-win32.zip
Once the file is available, expand it in a convenient location, say c:\target. At this point you
have the development environment installed and running, the target is installed, and all that is
left is to hook them together.
Note
When developing Eclipse RCP-based products, it is best to ignore the fact that you are
using Eclipse to do the work. Be sure to separate the Eclipse you are using as your
development environment (the IDE Eclipse) and the Eclipse environment you are
buildingyour target Eclipse. Keeping these separate allows you to add and remove
plug-ins from one without affecting the other. It also allows you to replace your IDE
independent of your target (and vice versa).
This approach adds a little bit of complexity to the initial setup but makes life much
easier down the road by eliminating accidental references to code that will not be on
your user's machine.
The target platform is defined by setting a preference (Window > Preferences). Go to the
Plug-in Development > Target Platform preference page and use the Browse... button or
type in the Location field to set the target location to c:\target\eclipse and click Reload. You
should see a setup similar to Figure 3-2.
Figure 3-2. Defining a target platform
[View full size image]
Notice that all of the plug-ins you just downloaded and installed as the target appear in the
plug-in list. You can come back to this page anytime and add or remove plug-ins to the target.
Deselecting the check boxes filters out plug-ins that are not relevant to your ultimate execution
environment, that is, just because a plug-in is in the target location does not mean you need it
or want it to run. For example, you could be working on a Linux machine but writing code
intended to run on Windows.
Under the covers
The PDE included with the Eclipse SDK provides sophisticated support for the
development of plug-ins, RCP applications, and products. One of the PDE's primary
jobs is to build and maintain a model of the world you are creating. This model
consists of two groups of plug-ins: the ones from your workspace (i.e., the ones you
are actively writing) and the ones in your target definition (i.e., the ones you are
simply using). You add to the workspace by creating plug-in projects. You add to
the target platform through the PDE preference pages as described here.
In addition to defining a set of plug-ins, the target also defines the environment and JRE for
which you are building. You might be using JDK 1.4.2 to run your IDE, but targeting the J2ME
Foundation class libraries. The Target Platform preference pages allow you to configure these
characteristics.
3.5. Checkpoint
If you have been following along, you have installed and started your development environment
and you have installed and configured a basic RCP target. You should be looking at an empty
Eclipse workspace and have a structure similar to Figure 3-3 on disk. Of course, you could have
chosen different physical locations, but the overall structure should look the same. We use the
locations shown throughout the book, so simply substitute yours if you have chosen others.
Figure 3-3. Development disk layout
3.6. Sample Code
As mentioned earlier, the code and resources added in each chapter are available on a
companion CD to this book. They are also available from the book's Web site,
http://eclipsercp.org/book. The samples directory includes a directory of projects for each
chapter that has code. These projects contain the final state of Hyperbola after all steps
outlined in the corresponding chapter have been completed. So, to get the starting state for
Chapter N, you should load the final state for Chapter N-1 into your workspace. To make it easy
to load the projects for each chapter, we've written a "Samples Manager" tool as described in
3.6.1.
3.6.1. Moving from Chapter to Chapter
It turns out that managing a dozen different versions of the same plug-ins is quite complicated.
Ideally, we would have just named the projects differently and allowed you to load them all at
once. Unfortunately, all of the variations on that approach proved to be problematic in one way
or another. In the end, we wrote a Samples Manager tool to help you both manage the chapter
code and move from chapter to chapter.
Start by installing the tool in your Eclipse IDE, use Help > Software Updates > Find and
Install..., then select Search for new features to install. Use New Local Site and the CD's
updates directory to access the tool's feature. Click Finish and you should see an
org.eclipsercp.book.tools.feature feature in the Search Results dialog. Check that feature
and go through the following pages in the wizard then carefully read and accept the licenses
and warnings. After the feature is installed, a restart dialog appears. You can either Apply
Changes or Restart.
With the Samples Manager installed, there should be an RCP Book menu on the main menu
bar. Run the tool by selecting RCP Book > Samples Manager. You should see samples
manager view as shown in Figure 3-4.
Figure 3-4. Samples Manager
The list shows all chapters of the book that have associated sample code. Select a chapter and
click Import to load all related projects into the workspace. It is important that you not try to
load two copies of the same project into the workspace at the same time. That is the point of
the tool. Before importing new projects, it deletes the old ones.
After importing a chapter, the view highlights the chapter to remind you what is in the
workspace. The tool's help content includes the most up-to-date instructions and tips for using
the tool.
Note
Some of the chapters include steps that update the target platform by adding new
plug-ins. The Samples Manager does not update your target platform for you so you
have to carry out those steps by hand. Generally speaking, it is OK to have too many
plug-ins in your target. So, if you are moving back to an earlier chapter, you do not
have to remove the additional plug-ins from the target.
3.6.2. Comparing
The Samples Manager also supports comparing the current workspace to the set of projects for
a chapter. For example, if your workspace contains the projects for Chapter 5, you can see all
the changes required for Chapter 6 by selecting "Chapter 6" in the list and clicking Compare.
This gives you a standard Eclipse compare editor that you can use to browse the changes or
load them into the workspace.
This is extremely useful when following the tutorial steps. For example, while doing Chapter 6,
you may find that something is not working or the steps are unclear. Comparing the current
workspace to the Chapter 6 projects tells you what is left to do or where your setup is different
from the expected outcome.
Several chapters require sets of resources or large chunks of code that you are not expected to
create on your own. The Samples Manager's comparison tool has a Copy into Workspace
action, as shown in Figure 3-5, that allows you to select files and folders in the comparison and
copy them into the workspace.
Figure 3-5. Comparing Chapter 4 to Chapter 9
To distinguish between files that are changes and those that only exist in one location, the
compare editor shows a minus sign (-) if a file does not exist in the workspace but does exist in
the comparison chapter. Conversely, if a file exists only in the workspace, a plus sign (+) is
shown.
3.7. Learning by Example
One of the most efficient and effective ways of figuring out how to program a system is by
browsing examples. We can't emphasize this enough. Eclipse itself is one huge example. It can
be overwhelming, but there are various shortcuts and mechanisms you can use to help follow
the code. Below is a short list of the workspace navigation operations we use on a day-to-day
basis:
Navigate > Open Type... (Ctrl+Shift+T) Opens the Java type with the name you enter.
Wildcards are supported. This is a fine way of discovering where a type is or if it exists.
Navigate > Quick Type Hierarchy (Ctrl+T) Pops up a type hierarchy rooted by the type
associated with the selection in the current Java editor. For example, if the selection is on
a type, a normal type hierarchy is opened. If the selection is on or in a method, all
implementers of that method in the hierarchy are shown. Press Ctrl+T again to invert the
hierarchy.
Search > References > Workspace (Ctrl+Shift+G) Searches for references to the
selected Java element (e.g., type, method, field) in the current Java editor. Ctrl+Shift+U
does the same search but local to the current file.
Navigate > Open Declaration (F3) Opens the declaration of the Java element selected in
the current Java editor.
Since Eclipse is so decoupled, it can be hard to figure out how the various pieces interact. PDE
offers various tools and mechanisms for navigating these interconnections. The Plug-in
Development perspective (Window > Open Perspective > Other... > Plug-in
Development) includes a Plug-ins view. From this, you can easily navigate the dependencies
and references. From the PDE plug-in editor, you can discover the extension-to-extension point
interconnections, navigate to the classes defined in various extensions, and browse extension
point documentation.
Tip
In the Plug-ins view, select all plug-ins and use the Add to Java Search context
menu entry to add all known plug-ins to the Java search scope. This is helpful because
Java search only looks in projects in the workspace and their dependent projects. This
means that if you open an empty workspace and try to open a type (e.g.,
Ctrl+Shift+T). By adding all plug-ins in your target platform to the search, you can
more easily navigate example code and classes that you do not reference from your
projects.
3.8. Summary
Once set up, Eclipse and PDE make it easy to create and run applications. The setup detailed
here is robust in that you can use the IDE to work on many different workspaces, the target
platform can be updated independently of the IDE or the workspaces, and the IDE itself can be
updated without affecting the workspaces or the target platform.
By adding Samples Manager support, you can jump to any chapter and set up your workspace
in seconds. You can also validate the tutorial steps as you go and get quick summaries of all the
changes done so far and left to do.
Chapter 4. The Hyperbola Application
Where to start? The best bet is to create a simple shell that will evolve into Hyperbola. In this
chapter, we take you from your empty workspace to creating, running, and debugging an ultrasimple Hyperbola skeleton RCP-based application in about five minutes.
This is an introductory chapter aimed at showing you both to the tooling used when developing
Eclipse RCP applications and the general structure of RCP applications. In particular, the goals
of this chapter are:
Detail the creation of a Hello World Hyperbola application. All the code required for this
chapter is generated by standard Eclipse templates.
Introduce the mechanics of creating RCP applications and manipulating code in Eclipse by
creating and running a basic Hyperbola shell.
Walk through the code for Hyperbola and identify the major players, seeing how it all fits
together.
Show how to use launch configurations to change the way Hyperbola runs.
Show how to use launch configurations to debug Hyperbola.
4.1. Hyperbola Hello World
You are now set up to develop Eclipse RCP applications. Here we show you how to use the builtin wizards and tooling to create a simple skeleton for the Hyperbola chat client developed
throughout the book.
The first thing you need is a plug-in project to hold your code. Go to File > New > Project...
to start the new project wizard. Choose Plug-in Project and click Next. On this page, enter
the project name. Since you are just starting on Hyperbola, enter "org.eclipsercp.hyperbola".
Click Next and you should see a page similar to Figure 4-1.
Figure 4-1. Defining the plug-in content
Here, enter the information about the plug-in itselfits id, version, name, etc. Based on the
project name, the wizard guesses reasonable initial settings for most fields. The only things to
change are:
Uncheck the Plug-in Class option. We won't need a plug-in class for some time. Perhaps
never! Typically, you only need a plug-in class if your plug-in needs some sort of
initialization the first time it is accessed.
Select the Yes radio button in the Rich Client Application area of the page. This tells the
wizard to show the RCP templates on the next page rather than the standard templates.
Note
Notice here that the Classpath field is empty. As of Eclipse 3.1, it is common practice
to ship a plug-in as a JAR file. In this case, the plug-in itself is on the classpath, so
there is no need to specify a value.
Project names and plug-in ids
Project names such as org.eclipsercp.hyperbola look a little strange at firstthey
look a lot like Java package names! In fact, the Eclipse community uses two
conventions to manage the plug-in and project namespace. First, we use the reverse
domain name convention (i.e., Java package naming) to identify plug-ins. Plug-ins
are likely to end up in a pool with plug-ins from other sources, so they need to have
globally unique identifiers. Using reverse domain names as plug-in id roots is a
convenient, human-readable way of managing that namespace.
Since every plug-in is developed in a project, it is a convenient convention to match
project names with the ids of the plug-ins they contain. In our example, both the
project and its plug-in are called org.eclipsercp.hyperbola. Of course, you should
ensure that you own the rights to the related domain (e.g., eclipsercp.org in this
case).
Both of these practices are conventions not rules. We could have called the project
Hyperbola and set the plug-in id to org.eclipsercp.hyperbola, but that makes it
hard to remember which project is for which plug-in, etc.
Click Next and the wizard moves to the RCP Templates page. There are templates of varying
complexity. For this part of the book, pick the Hello RCP template to create what is probably
the simplest RCP application possible.
Click Next to advance the wizard to the page shown in Figure 4-2. Here, you identify the
Hyperbola application and give it a window title and perspective name, etc. You should only
have to change the Application window title.
Figure 4-2. Identifying the contents of the application
That's it. Click Finish to create your first Eclipse RCP application. You may be prompted to
change to the Plug-in Development perspective. This is an arrangement of views in the Eclipse
development environment that is particularly useful for plug-in development. We suggest that
you select Yes here.
When the wizard completes, your workspace contains a single project with the name
org.eclipsercp.hyperbola, as shown in Figure 4-3. The project contains an src folder that
contains the Java source files generated from the template by the new project wizard.
Figure 4-3. Hello Hyperbola project structure
If you selected Yes to switch to the plug-in development perspective, your new plug-in is
opened in a plug-in editor. The editor provides a comprehensive view of the various parts of the
plug-in definition captured in different files such as plugin.xml, MANIFEST.MF, and
build.properties. The plug-in editor works on all of these at onceyou can edit all aspects of a
plug-in in one place. Figure 4-4 shows the first page of the editor.
Figure 4-4. Hyperbola plug-in editor
[View full size image]
Note
If you close the plug-in editor and want it back, just double-click on the plugin.xml or
MANIFEST.MF files, or use Open on the context menu for the files.
To take Hyperbola for a spin, use the links in the Testing section of the Overview page. Click
on the Launch an Eclipse application link and run Hyperbola. This launches Hyperbola as a
separate Eclipse RCP application in its own JVM. As you can see from Figure 4-5, it doesn't do
much, but it's a start.
Figure 4-5. Simple Hyperbola shell
While the plug-in editor is open, take a look around. Along the bottom of the editor there are
tabs for the different aspects of the plug-in. Go to the Dependencies page and notice that the
Hyperbola plug-in depends on two other plug-ins: org.eclipse.core.runtime and
org.eclipse.ui. This means that the Hyperbola plug-in can use classes exposed by those plugins. It also means that classes in other plug-ins are not available to Hyperbola. This control over
class visibility is fundamental to the Eclipse notion of modularity and your ability to build
systems from sets of plug-ins using Eclipse.
The Dependencies page also has some useful Dependency Analysis tools to help you
navigate the dependencies between plug-ins, find unused dependencies, look for cycles, etc.
What about those other plug-ins?
You may be asking "What about JFace, SWT, and OSGi? I thought those were part
of the RCP as well." To find the answer, go to the Dependencies page in the
Hyperbola plug-in editor and click on Show the plug-in in the dependency
hierarchy in the Dependency Analysis section. You should see the hierarchy
shown in Figure 4-6.
Figure 4-6. Hyperbola plug-in dependencies
Notice that some of the plug-ins under the Runtime and UI, for example,
org.eclipse.swt, have little arrow decorations beside them. The arrows identify
plug-ins that are re-exported by their parents in the treeorg.eclipse.ui in the case
of the SWT plug-in. As such, anyone depending on the UI automatically depends on
the re-exported SWT. Similarly, the UI re-exports JFace and
org.eclipse.ui.workbench and the Runtime re-exports OSGi.
This dependency chaining mechanism is used wherever one plug-in exposes the API
of another plug-in as part of its own API. For example, the UI API has classes and
methods that name types found in SWT. To ensure that a plug-in requiring the UI
gets a coherent dependency chain, the UI re-exports SWT. Note that the UI does not
re-export all of its prerequisites, just those it exposes as part of its API.
Take a look at Hyperbola's Extensions page next. When PDE generated the Hyperbola
skeleton, it added two extensions: an application and a perspective. If you poke around in here,
you can see some of the values entered earlier as well as the names of various classes that were
generated by the template.
These extensions are the mechanism for linking classes into the Eclipse infrastructure. For
example, Figure 4-7 shows the Hyperbola perspective extension. Notice how it lists the new
perspective class (org.eclipsercp.hyperbola. Perspective) and links it into the
org.eclipse.ui.perspectives extension point.
Figure 4-7. Hyperbola extensions
[View full size image]
Take a look at the other pages in the editor if you like. If not, there will be plenty of opportunity
to use them later. The next section walks through the generated code and highlights its
structure. This is followed by a section on running and debugging Eclipse applications.
Subsequent chapters build on this base and add more and more function.
4.2. Tour of the Code
Since the template did all the work of creating the code, you don't really know what it's doing.
In this section, we walk through the generated code and point out the interesting bits. In fact,
all the generated code is included hereit's pretty small.
4.2.1. Application
The RCP SDK target you are using contains only framework libraries; you can't even run them.
It's like having a JRE installedlots of great code, but it still needs to be told what to do. In
regular Java systems, this is done by writing a class that has a main() method. In Eclipse, you
write an application. An application is the entry point to a program or product. The application
is run when the Runtime starts up, and when the application exits, Eclipse shuts down.
The PDE wizard used the Hello RCP template to generate the
org.eclipsercp.hyperbola.Application application below. Applications must implement
IPlatformRunnable and thus a run() method. Think of this as your main() method.
org.eclipsercp.hyperbola/Application
public class Application implements IPlatformRunnable {
public Object run(Object args) throws Exception {
Display display = PlatformUI.createDisplay();
try {
int returnCode = PlatformUI.createAndRunWorkbench(
display, new ApplicationWorkbenchAdvisor());
if (returnCode == PlatformUI.RETURN_RESTART) {
return IPlatformRunnable.EXIT_RESTART;
return IPlatformRunnable.EXIT_OK;
} finally {
display.dispose();
}
}
}
The critical code is marked in bold. The application creates a Display and then starts an Eclipse
Workbench by calling PlatformUI.createAnd RunWorkbench(Display, WorkbenchWindowAdvisor).
This opens a window and simply loops forever, handling user-generated events such as mouse
clicks, key presses, and mouse moves. The event loop finally returns when the last window is
closed or when it is explicitly told to exit. Before returning from the application, the created
Display must be disposed to free any allocated system resources.
Note
You can do just about anything you want in your application. In our example, we start
up a UI, but you could just as well start a server of some sort. In other words, the
Eclipse RCP can also be used for non-graphical applications.
The application class must be linked into the Eclipse Runtime's applications extension point, as
shown in Figure 4-7, thus making the Runtime aware of the application. Just as many Java JARs
on a classpath may contribute classes that have a main() method, many Eclipse plug-ins in a
system may contribute application extensions. In fact, one plug-in can contribute many
applications. When Eclipse is started, one and only one application is identified as the
application to run. Again, this is directly analogous to standard Java, where you specify exactly
one class to run on the command line. More on this in the section on running and debugging.
4.2.2. WorkbenchAdvisor
In the application code shown above, we glossed over the Application WorkbenchAdvisor that
was instantiated and passed into PlatformUI.createAndRunWorkbench(). In fact, this is the most
important part of the story.
As the name implies, a WorkbenchAdvisor tells the Workbench how to behavehow to draw, what
to draw, etc. In particular, our Application WorkbenchAdvisor identifies two things:
The initial perspective to be shown.
The WorkbenchWindowAdvisor to be used.
org.eclipsercp.hyperbola/ApplicationWorkbenchAdvisor
public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor {
private static final String PERSPECTIVE_ID =
"org.eclipsercp.hyperbola.perspective";
public WorkbenchWindowAdvisor createWorkbenchWindowAdvisor(
IWorkbenchWindowConfigurer configurer) {
return new ApplicationWorkbenchWindowAdvisor(configurer);
}
public String getInitialWindowPerspectiveId() {
return PERSPECTIVE_ID;
}
}
4.2.3. Perspective
The initial perspective is identified by its extension identifier, as shown at the top right of Figure
4-7. The extension gives the perspective a human-readable name and specifies a class that
defines the layout of the perspective. The given class must implement the IPerspectiveFactory
interface and the createInitial Layout(IPageLayout) method. The
org.eclipsercp.hyperbola.Perspective perspective is a trivial implementation that simply does
nothing. This perspective is added in later chapters.
org.eclipsercp.hyperbola/Perspective
public class Perspective implements IPerspectiveFactory {
public void createInitialLayout(IPageLayout layout) {
}
}
Note
As with applications, there may be many perspectives in the system. The application's
WorkbenchAdvisor identifies only one of these as the initial perspective. While
WorkbenchAdvisors must define an initial perspective, that setting can be overridden
using preferences. This is detailed in Chapter 16, "Perspectives, Views, and Editors."
4.2.4. WorkbenchWindowAdvisor
Every window in your application has a WorkbenchWindowAdvisor that guides the UI in rendering
the window. Window advisors are consulted at various points in the lifecycle of a window (e.g.,
preWindowOpen() and postWindowCreate() ) and have the opportunity to control the creation of
the window's contents. You will visit Hyperbola's window advisor often as you update the look
and feel of the application.
The ApplicationWorkbenchWindowAdvisor customizes Hyperbola windows. In the
preWindowOpen() method, it sets the initial size and title of the window and hides the status line
and toolbar. While we are looking at preWindowOpen(), go ahead and change the initial size of
the window to make it a bit smaller than the default. Don't forget to save the file when you are
done.
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public class ApplicationWorkbenchWindowAdvisor extends
WorkbenchWindowAdvisor {
public ApplicationWorkbenchWindowAdvisor(
IWorkbenchWindowConfigurer configurer) {
super(configurer);
}
public ActionBarAdvisor createActionBarAdvisor(
IActionBarConfigurer configurer) {
return new ApplicationActionBarAdvisor(configurer);
}
public void preWindowOpen() {
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
configurer.setInitialSize(new Point(250, 350));
configurer.setShowCoolBar(false);
configurer.setShowStatusLine(false);
configurer.setTitle("Hyperbola");
}
}
4.2.5. ActionBarAdvisor
ActionBarAdvisors create the actions needed for a window and position them in the window.
They are instantiated using createActionBarAdvisor() on WorkbenchWindowAdvisor. Since we
are just starting out and have no actions, the ActionBarAdvisor is largely empty.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
public class ApplicationActionBarAdvisor extends ActionBarAdvisor {
public ApplicationActionBarAdvisor(IActionBarConfigurer configurer) {
super(configurer);
}
protected void makeActions(IWorkbenchWindow window) {
}
protected void fillMenuBar(IMenuManager menuBar) {
}
}
Note
The name ActionBarAdvisor does not do justice to this class. It actually controls what
appears in the menu bar, the cool bar, also known as the toolbar, and the status line.
As such, it is a focal point of customization in RCP applications.
4.2.6. Summary
That's it. You have seen all the code involved in creating the simple Hyperbola RCP application.
Just to be sure you got all the steps right, use the Samples Manager detailed in Chapter 3,
"Tutorial Introduction," to compare your workspace to the sample code for this chapterthey
should be identical (except for possible formatting differences).
Of course the application does not do much; it doesn't even have any actionsnor does it have
any branding or splash screens. Nonetheless, the example shows how the major parts of the
Eclipse RCP fit together.
4.3. Running and Debugging
By this point, you have set up a development environment and target platform, defined a
simple Hyperbola application, and run it at least once. You have also seen how applications,
perspectives, and advisors fit together to form a system. You are now ready to start putting
some real function into Hyperbola.
Before doing that, it is worth spending a bit of time talking about how to run and debug Eclipse
applications effectively. In this section, we show you how to:
Launch applications in debug mode.
Set breakpoints and step through code.
Manage targets, launch configurations, and control the set of plug-ins used.
If you have been following along, Figure 4-8 matches your system. It shows the relationships
between the IDE install you are running, the workspace in which you are developing, and the
target plug-ins you are using for Hyperbola. The IDE plug-ins (bottom left) are the ones in
c:\ide. They are the full Eclipse SDK. Running them starts the Eclipse IDE (top left of the
diagram). Using the IDE, you work on plug-in projects that are in the workspace (center). When
you decide to try running Hyperbola, PDE creates and launches a configuration that lists the
relevant plug-ins from the target (bottom right) and the workspace. The result is a running
Hyperbola (top right).
Figure 4-8. Relationships among IDE, target, and workspace
By separating the concerns, the IDE install can be replaced without affecting the workspace or
the target. Similarly, the target plug-ins can be updated without changing the workspace or
IDE. Of course, there can be several workspaces using the same target.
4.3.1. Debugging
Previously, you ran Hyperbola using the Launch link in the Testing section on the Overview
page of the plug-in editor. You may have noticed that there is also a Debug link. Go ahead and
click on that link now. As with launching, debugging spawns a separate JVM to run the
application. The difference is that the Debug perspective opens and shows a list of threads in
the target application. Exit the application and the threads all terminate.
The debugger is useful when things are not going as you expected. Say, for example, you are
getting a NullPointerException. To illustrate, set up that scenario by changing
ApplicationWorkbenchWindowAdvisor.preWindowOpen() to simulate null coming back from
getWindowConfigurer().
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
configurer = null;
With the code changed, click on the normal Launch link in the plug-in editor. All you see is an
exception reported in the console as shown belowHyperbola does not start:
Unhandled event loop exception
Reason:
java.lang.NullPointerException
To find out more, add a breakpoint on the exception by using Run > Add Java Exception
Breakpoint... and selecting NullPointerException in the type chooser.
Tip
The filter in the type chooser takes wildcards. So, to get NullPointerException, just
open the chooser and type "null". Keep typing until the list of choices is refined
enough for you to see the type you want.
Now launch using the Debug link in the editor. Click Yes when prompted to open the Debug
perspective. When the debugger opens, the "main" thread is paused at the line where the
exception occurred, as shown in Figure 4-9.
Figure 4-9. Debugging using breakpoints
[View full size image]
The execution of the main thread has stopped in preWindowOpen() on the line highlighted. The
debugger also contains a Variables view. This view shows the values of all known fields and
variables for a given Java stack frame. You can navigate around the object structure by
expanding the object tree along the reference paths that interest you.
Tip
Using the Variables view to examine object structures can be extremely informative.
Note also that you can typically change the values of the variables and fields you find
along the way!
Looking at the line of code and the Variables view, it is easy to see that the configurer field is
null and to find the cause of the exception. This is the first step. Now you have to figure out
why configurer is null (ignoring, of course, the fact that it is set to null on the previous line).
One easy way to do this is to set a breakpoint on the first line of this method and then step
through the code to find out how the null value is set.
To set a breakpoint, position the text cursor on the line you want and use Run > Toggle Line
Breakpoint. Alternatively, you can double-click in the margin at the left of the line. Either way,
a blue ball should appear at the left of the line, as shown in Figure 4-10.
Figure 4-10. Line breakpoint
[View full size image]
Terminate the current run using either Run > Terminate or by clicking on the red square
button over the list of threads. Then, launch again using the debug link. This time, execution
stops at your new breakpoint. From here, you can use the stepping functions described in Table
4-1 to go into getWindowConfigurer() and see what is going on.
Table 4-1. Debugger Stepping Functions
Image Step Function
Step Into (F5)The next method call. If you think that the problem might be near,
step into. Note that Java debugging is line-oriented, so if you have multiple method
calls on one line, you might have to step into several times. You can step into and
then immediately step to return. You can refine this by using
Step Into Selection (Ctrl+F5) in the editor's context menu.
Step Over (F6)The next method call. This is useful for quickly getting around in the
execution. Use step over until you start seeing things going wrong and then look in
more detail.
Step to Return (F7)Of the current method. As the name implies, use this function
when you just want to get to the end of the method.
Drop to FrameDrop all stack frames above the selected frame. Execution is reset to
the beginning of the method in the current frame. This is useful if you've discovered a
problem and want to retry the execution. Note, however, that this does not undo side
effects such as writing or closing files or setting shared state.
Tip
Stepping through code is sometimes bewildering, but can be an effective way of
finding out what the system is doing and discovering API and coding patterns that are
useful in your applications.
The editor's context menu has a number of other step functions such as stepping into and
running up to a selected line of code, and the debugger supports conditional breakpoints. The
functions described here should be enough for you to handle most problems.
4.3.2. Launch Configurations
Launching using the links in the Testing section of the plug-in editor is easy and convenient.
Since you are likely to spend a lot of time running and debugging Hyperbola in the next
chapters, here we show you some tricks to make things easier.
Whenever you click on a test link, PDE manages a launch configuration describing the
configuration to run. You can look at this launch configuration using either Run > Run... or
Run > Debug... Either way, you get a launch configuration dialog as shown in Figure 4-11.
Figure 4-11. Launch configuration dialog
[View full size image]
The Main tab is the most interesting at this point. It has the following parts:
The Name of the launch configuration is currently "Eclipse Application". To eliminate
future confusion, you might want to change it to "Hyperbola" and click Apply. Notice that
the name in the left column changes as well.
The Workspace Data section shows where the target Eclipse puts its workspace or
instance data. Since we are running Hyperbola, there isn't any data to worry about. In
general, you do not need to change this location, but you may need to know where it is.
This is where Eclipse stores some preferences and plug-in-specific information. This is also
where you can find the log file if something should go wrong. Look in .metadata/.log in
the workspace location if you suspect errors are occurring.
The Program to Run section identifies the Hyperbola application,
org.eclipsercp.hyperbola.application, to run. Click on the drop-down arrow and take a
look at the other applications known by the system. Since we are targeting the RCP SDK,
Hyperbola is the only application.
The Command Line Settings area allows you to choose a JRE to use as well as set up
any command line arguments. There are quite a number of possible command line
arguments, all of which are detailed in the online documentation. Perhaps the most
interesting one for debug purposes is the -consoleLog program argument. This causes all
log output to be echoed to the Console. This helps because often messages are being
logged but go unnoticed unless you check the log. Using -consoleLog, these messages are
somewhat more "in your face."
Now that you know about launch configurations, you can run and debug your applications
directly rather than using the links on the plug-in editor's Overview page. As shown in Figure
4-12, the Run menu has a host of entries to help.
Figure 4-12. Launching entries in the Run menu
The context menu in the Package Explorer also has Run As and Debug As entries. When you
pick one of these entries and specify Eclipse Application, it is equivalent to using the run and
debug links in the plug-in editor. PDE finds or creates a launch configuration to match your
selection and then launches it.
As you can see, there are several more tabs on the dialog. For now, these are not needed. PDE
is pretty good at managing things for you in this relatively simple world. As we make Hyperbola
more and more sophisticated, there will be opportunities for you to use the other tabs, in
particular, the Plug-ins tab.
4.4. Summary
So that was easy. Once set up, Eclipse and PDE greatly simplify the creation and running of
applications. Even the generated code for the application is simple. We have now covered all the
major concepts, classes, tools, and techniques you need to develop the rest of Hyperbola. The
rest of the book builds on this base until we have a very sophisticated, fully extensible chat
client that can be deployed in a wide range of execution environments. You wouldn't think it
looking at the current Hyperbola shell, but it's true. The RCP part is easy; it's your domain that
is hard.
Chapter 5. Starting the Hyperbola
Prototype
At this stage, you have created the skeleton for Hyperbola. You know how to run it, debug it,
and are familiar with the basic classes that are part of all RCP applications. The next few
chapters focus on iteratively developing Hyperbola. This next iteration is interesting because it
allows you to quickly get something running so you can show it to your mom, your boss, or
your friends. It's also a lot more fun to learn RCP while developing something concrete.
The iterative approach used to develop Hyperbola is how software is developed in most
organizations. You start by creating a prototype that could be used to demo Eclipse RCP to your
friends or colleagues. You iterate again and augment the prototype with real domain logic, in
our case, the messaging library, and start to think about packaging and branding the product.
You then continue iterating and adding functionality and manage the complexity until you have
a product to ship.
Prototypes are often crude but provide an excellent way of exploring various aspects of the
application to be built. In this chapter, we show you how to extend the skeleton version of
Hyperbola by adding the following features:
a primitive contacts list that shows a list of contacts and contact groups
a simple messaging model that is used to drive the UI components
images to make the contact list look real
For the moment, we ignore the details of how to chat with someonethe focus is on getting
familiar with the basics. Here we add the contacts list and its supporting model. In the next two
chapters, we add the chat editor and associated actions. Figure 5-1 gives a peek at Hyperbola
after that work is completed.
Figure 5-1. Hyperbola prototype
5.1. Continuing from the Shell
Chapter 4, "The Hyperbola Application," gave you a good idea of all the classes and files needed
for the skeleton Hyperbola application. Let's critique the skeleton as shown in Figure 5-2 and
see how we can improve.
Figure 5-2. Hyperbola skeleton
If you resize the window, exit, and re-run Hyperbola, the window size and position are
reset to their defaults.
The menu bar, toolbars, and status line are not shown.
There is no indication in the task tray that Hyperbola is running.
Let's fix the resize and position bug and address the other issues in the next few chapters.
5.1.1. Saving Window Location and Size
One of the problems with the skeleton code is that when the window was resized or moved and
the application re-run, the window's size and position was reset. This is annoying. The good
news is that it's really easy to fixthe Workbench contains code that saves settings for open
windows. It's disabled by default as saving and restoring window state can be expensive, and
for some applications, not even necessary.
To enable it, first override the WorkbenchAdvisor.initialize() method from within
ApplicationWorkbenchAdvisor, as shown in the snippet below. A trick is to open the
ApplicationWorkbenchAdvisor class and type "ini", then "Ctrl+Space". A pop-up appears, listing
the possible methods that can be overridden. Simply select the initialize() method to get a
skeleton. This method is called by Eclipse to mark the beginning of the advisor's lifecycle. This
hooks in at the earliest possible point to enable save and restore.
org.eclipsercp.hyperbola/ApplicationWorkbenchAdvisor
public void initialize(IWorkbenchConfigurer configurer) {
configurer.setSaveAndRestore(true);
}
5.2. Adding a Contacts View
Since Hyperbola is a chat client, one of the most essential UI elements is a contacts list that
displays your friends and their presence status (i.e., whether or not they are online).
In Eclipse, users interact with applications through views and editors. Perspectives are a
mechanism for arranging views and editors and supporting scalable UIs. Put another way, views
and editors contain the content for your application; perspectives allow those elements to be
organized for users. We already have a perspective, it's just empty.
A view is added by contributing a view extension to the org.eclipse.ui.views extension point.
Open the org.eclipsercp.hyperbola project's plugin.xml and go to the Extensions page. Click
Add... and create an extension of type org.eclipse.ui.views. Right-click on the extension and
add a view attribute using New > view from the context menu. When you click on the new
view attribute, the details pane at the right shows the default values for the view. Update the id
and name fields to match those shown in Figure 5-3.
Figure 5-3. Adding ContactsView to Hyperbola's plugin.xml
[View full size image]
You also need a class to implement the view. Click on the class link to create a new class. When
the new class wizard appears, most of the fields, including superclass ViewPart , are already
filled in. All you have to do is type "ContactsView" for the class name. Click Finish and a
skeleton view class is created and opened in an editor.
Tip
You are building a view from scratch to understand how everything fits together.
However, the next time you add a view, you can use the handy PDE templates. You
may have noticed that the New Extension wizard lists the set of available templates
for each extension point. There's a template for a view that creates a sample view with
actions and dummy contents.
Before moving on, make a constant for the view id. Set the value to be the same as in the
extension and add it to the newly created ContactsView class. This will come in handy later.
Save the Java file and go back to the plug-in editor. Notice that the name of the new view class
is set in the class attribute of the extension.
org.eclipsercp.hyperbola/ContactsView
public class ContactsView extends ViewPart {
public static final String ID =
"org.eclipsercp.hyperbola.views.contacts";
public ContactsView() {
super();
// TODO Auto-generated constructor stub
}
public void createPartControl(Composite parent) {
// TODO Auto-generated method stub
}
public void setFocus() {
// TODO Auto-generated method stub
}
}
Finally, set the view's icon. View icons are optional but typically specified. You can either create
your own GIF image and place it in Hyperbola's icons directory or copy the images supplied with
the sample code for this chapter. Either way, fill in the icon field by browsing to the image or
entering the location directly.
Save the plug-in and now you have created a skeleton view. The next step is to customize the
perspective to include the new view and have it appear in the Hyperbola window.
5.2.1. Adding the Contacts View to a Perspective
All RCP applications must define at least one perspective; otherwise, there would be nothing to
lay out the views. Think of a perspective as a set of layout hints for a window. Every
IWorkbenchWindow has one page. The page owns its editor and view instances and uses the
active perspective to decide its layout. The perspective details where, and whether or not, to
show certain things, such as views, the editor area, and actions. Figure 5-4 provides an
overview of the main parts of a IWorkbenchWindow.
Figure 5-4. Overview of a WorkbenchWindow's parts
[View full size image]
The initial perspective, and thus the look of Hyperbola when it is first run, is identified by
WorkbenchAdvisor.getInitialWindowPerspectiveId() . The Hello RCP template defined the
Hyperbola perspective and contributed to the Workbench's perspective extension point, as
shown in Figure 5-5. Notice that the extension defines an id and identifies a class for the
perspective.
Figure 5-5. Hyperbola perspective defined
A perspective factory provides the initial layout for a perspective. When a perspective is needed,
the factory is created and passed an IPageLayout to which views are added. The factory is then
discarded. As the user rearranges the contents of the perspective, the Workbench saves the
settings on exit and restores them at startup (if so configured).
The perspective generated by the PDE template uses the default layout. This layout is just an
editor area with no additional views. That's why Hyperbola is just an empty shellthe perspective
is empty. Now that we have a view defined, go ahead and add the Contacts view to the
Hyperbola perspective as shown here:
org.eclipsercp.hyperbola/Perspective
public class Perspective implements IPerspectiveFactory {
public void createInitialLayout(IPageLayout layout) {
layout.setEditorAreaVisible(false);
layout.addView(ContactsView.ID, IPageLayout.LEFT,
1.0f, layout.getEditorArea());
}
}
IPageLayout contains several methods for defining the layout of a perspective. Everything added
to a perspective is related to something else. Here, the editor area is used as the base for
placing the Contacts view. The Contacts view is added to the left of the editor area and is given
all available space in the page by specifying 1.0f as the layout ratio. The ratio describes how to
divide the available space between the Contacts view and its reference part, in this case, the
editor area. The ratio must be between 0.0f (only title bar is shown) and 1.0f (view takes up the
entire window area).
Note
Editors cannot be added to a perspective layout. Instead, you position the area in the
perspective in which editors are opened. It's also possible to hide and show the editor
area using IWorkbenchPage.setEditorAreaVisible(boolean).
When you run Hyperbola now, you should see the Contacts view, as shown in Figure 5-6.
Figure 5-6. Hyperbola showing the Contacts view
It looks a little strange to have a tab when there is only one view shown, and there is no reason
to allow the view to be closed. Views added to a perspective using IPageLayout.addView()
inherit various default behaviors that allow them to be moved, un-docked, closed, minimized,
and maximized. Using IPageLayout.addStandaloneView(), however, adds a standalone view.
Standalone views hide the title area, thus preventing the view from being closed, moved, etc.
Make the Contacts view standalone by changing the code to use addStandaloneView(), as shown
here. Notice that the new code is almost identical to the previous except for the boolean
parameter that specifies if the title area should be hidden. The new Hyperbola looks like Figure
5-7.
org.eclipsercp.hyperbola/Perspective
public class Perspective implements IPerspectiveFactory {
public void createInitialLayout(IPageLayout layout) {
layout.setEditorAreaVisible(false);
layout.addView(ContactsView.ID, IPageLayout.LEFT,
1.0f, layout.getEditorArea());
layout.addStandaloneView(ContactsView.ID, false,
IPageLayout.LEFT, 1.0f, layout.getEditorArea());
}
}
Figure 5-7. Hyperbola with the standalone Contacts view
When you re-run Hyperbola, you may be surprised. The view still shows a title. The changes
you made to the perspective seem to have been ignored. Since we told the Workbench to save
settings on shutdown, it saves the perspective layouts in the workspace location and on startup
does not consult with the perspective factory at all. IPerspectiveFactory is only needed the first
time a perspective is created.
To debug changes to a perspective factory, you must configure your launch configuration to
clear the workspace area on each launch. Open the launch configuration dialog as shown in
Section 4.3.2, "Launch Configurations," and check the option called Clear workspace data
before launching and uncheck Ask for confirmation before clearing. Re-run Hyperbola.
You should see the empty view shown without a title bar, as shown in Figure 5-7.
5.3. The Chat Model
At this point, the Contacts view is empty because there is no underlying list of contacts to show.
That is, Hyperbola does not have a model. A model that supports Hyperbola's chat domain will
prove helpful as we work through building the application. You could go straight to a messaging
library and start implementing the UI pieces on top of its model. But, there is a danger that the
UI work will get bogged down in the details of sending bytes around. What we really need is a
simple chat model that has all the right entities and is sufficient to drive the UI parts of
Hyperbola. When it comes time to use a real chat library, there will be some rework needed, but
hopefully not too much.
The Hyperbola chat model shown in Figure 5-8 is very basic. Sessions comprise the central
object. They allow connecting to a messaging server and provide access to contacts, which are
either groups or individuals. A Session has a reference to the root group for the logged-in user.
Listeners can be attached to Sessions and receive notifications when the root ContactsGroup
changes.
Figure 5-8. Classes in the prototype chat model
[View full size image]
You can code the model yourself, or to save time, you can copy the code for this chapter using
the Samples Manager described in Section 3.6, "Sample Code." Here, you only want part of the
sample code, so copy just the package called org.eclipsercp.hyperbola.model by selecting the
package in the top part of the Compare editor and selecting Copy to Workspace from the
context menu.
5.4. Filling in the Contacts View
Now that you have a model, you have something to put in the Contacts view. In the model,
contacts are organized into groups of contact entries. A UI for this should be some sort of tree
design that allows users to organize their contacts and groups. To help create that UI, this
section shows you how to:
Add a tree viewer to the ContactsView class.
Initialize a Session object with test data.
Add a content provider to populate the tree based on the contents of the contacts list.
Add a label provider to display the labels and images in the ContactsView.
Note
For the rest of the UI discussion, it is useful to have some knowledge of SWT and
JFace. We do not go into great detail about their usage in Hyperbola since we want to
focus on RCP-specific concepts. See Section 5.7, "Pointers," for more pointers to
information on SWT and JFace.
5.4.1. The ContactsView
When the Workbench creates the Contacts view, it calls createPart Control(Composite) on the
view so it can create its controls. The code below does two things: it spoofs up a fake model and
it adds a treeViewer to the view. Add the code to the ContactsView class and then let's take a
look at it.
The fake model is created in initializeSession() out of convenience. Once a real chat model is
integrated into Hyperbola in Chapter 10, "Messaging Support," this code will be removed. In
any event, the model is built and is set as the input to the treeViewer. The treeViewer is also
set up as a selection provider so that actions can determine the selection in this view.
org.eclipsercp.hyperbola/ContactsView
public class ContactsView extends ViewPart {
public static final String ID =
"org.eclipsercp.hyperbola.views.contacts";
private TreeViewer treeViewer;
private Session session;
public ContactsView() {
super();
}
public void createPartControl(Composite parent) {
initializeSession(); // temporary tweak to build a fake model
treeViewer = new TreeViewer(parent,
SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
getSite().setSelectionProvider(treeViewer);
treeViewer.setLabelProvider(new WorkbenchLabelProvider());
treeViewer.setContentProvider(new BaseWorkbenchContentProvider());
treeViewer.setInput(session.getRoot());
session.getRoot().addContactsListener(new IContactsListener() {
public void contactsChanged(ContactsGroup contacts,
ContactsEntry entry) {
treeViewer.refresh();
}
});
}
private void initializeSession() {
session = new Session();
ContactsGroup root = session.getRoot();
ContactsGroup friendsGroup = new ContactsGroup(root, "Friends");
root.addEntry(friendsGroup);
friendsGroup.addEntry(new ContactsEntry(friendsGroup,
"Alize", "aliz", "localhost"));
friendsGroup.addEntry(new ContactsEntry(friendsGroup,
"Sydney", "syd", "localhost"));
ContactsGroup otherGroup = new ContactsGroup(root, "Other");
root.addEntry(otherGroup);
otherGroup.addEntry(new ContactsEntry(otherGroup,
"Nadine", "nad", "localhost"));
}
public void setFocus() {
treeViewer.getControl().setFocus();
}
}
TreeViewers do just what their name impliesdisplay tree structures. This is done using two
providers: the content provider and the label provider. Content providers supply the tree nodes
(e.g., parents and children) and the label provider produces human-readable names and
representative images for the nodes. treeViewer.setInput(Object) tells a treeViewer to build
the tree using the supplied object. It is up to the configured content providers to interpret the
object and make it look like a tree.
Note
Do not confuse views with JFace viewers. It's unfortunate that their names are so
similar, but they are two separate concepts. A viewer is a model-based adapter for an
SWT widget. A viewer can be used to show content within a view. Moreover, a view
can contain multiple viewers, and even editors can contain viewers.
5.4.2. Content Providers Overview
TreeViewers require content providers that implement ITreeContentProvider, as shown below.
This allows the viewer to query the structure of its input object using methods such as
getChildren(Object) and getParent(Object).
org.eclipse.jface/ITreeContentProvider
public interface ITreeContentProvider extends IStructuredContentProvider {
public Object[] getChildren(Object parentElement);
public Object getParent(Object element);
public boolean hasChildren(Object element);
}
The arguments to these methods are the elements being shown in the viewer. As shown in
Figure 5-9, treeViewer.setInput(Object) is called when ContactsView is created. This in turn
uses the configured ITreeContentProvider. getChildren(Object) method to find the first level
of elements to display. Notice that this means the root input object for the TReeViewer is never
displayedit provides the starting point from which the visible tree is built. The collaboration
between the viewer and the content provider continues as more elements in the tree are
expanded.
Figure 5-9. Content providers
[View full size image]
Figure 5-9 shows how viewers and providers work together, but not how providers and input
model objects interact. For example, how does the provider discover the children of a model
object? There are several possible techniques:
Make the model objects implement ITreeContentProvider.
Wrap the model objects with another object that an existing ITreeContentProvider
understands.
Supply a customized content provider.
Extend the objects with the required provider function.
Adding the provider methods, for example, getChildren(Object) and getParent(Object), to
ContactsEntry and ContactsGroup is simple and straightforward. The methods simply expose
the inherent structure of the underlying model objects. This approach has the drawback that it
pollutes the chat model with UI concerns. It is better to keep the model and UI layers decoupled
from one another.
Wrapping chat objects in provider-friendly objects gets around this limitation but introduces the
overhead of having two objects for every model object. It also makes identity maintenance, for
example, equality checks, difficult, as there can be more than one objectthe model and the
wrapperrepresenting the same entity.
TreeViewers are customizable and allow you to supply your own content providers. We could
simply implement providers that directly access model objects and navigate their object
structure. This approach requires control over the providers and is not extensible as all object
types handled must be known in advance.
The final approach is to use the Eclipse adapter mechanism to extend the behavior of the model
objects. The Workbench defines a standard content provider called
BaseWorkbenchContentProvider that knows how to navigate the IWorkbenchAdapter type shown
below.
org.eclipse.ui.workbench/IWorkbenchAdapter
public interface IWorkbenchAdapter {
public Object[] getChildren(Object o);
public ImageDescriptor getImageDescriptor(Object object);
public String getLabel(Object o);
public Object getParent(Object o);
}
If an object can adapt to IWorkbenchAdapter, then it can be shown in a tree viewer using the
standard content provider. In fact, objects that adapt to IWorkbenchAdapter can be shown in a
wide range of viewers. All you have to do to enable this is have the model object implement
IAdaptable and supply an adaptor factory that produces IWorkbenchAdapters for the model
objects.
5.4.2.1. Adding the IWorkbenchAdapters
Let's look at this in more detail. There are four things you have to do to use the adapter
technique:
1. Configure the treeViewer to use an instance of BaseWorkbenchContentProvider as its
content provider.
2. Implement IWorkbenchAdapters for the chat model elements that need to be displayed.
3. Implement an adapter factory that returns an IWorkbenchAdapter for each model element.
4. Register the Hyperbola adapter factory with Eclipse.
The first point was covered in Section 5.4.1, "The ContactsView," when the TReeViewer was
4.
created. The HyperbolaAdapterFactory class below covers the next two requirements. It
contains inner class implementations of IWorkbenchAdapter for ContactsGroup and
ContactsEntry . The adapters do not maintain any state, so the same instances are used for all
relevant model objects. When getAdapter(Object, Class) is called, the factory picks an adapter
to return.
org.eclipsercp.hyperbola/HyperbolaAdapterFactory
public class AdapterFactory implements IAdapterFactory {
private IWorkbenchAdapter groupAdapter = new IWorkbenchAdapter() {
public Object getParent(Object o) {
return ((ContactsGroup)o).getParent();
}
public String getLabel(Object o) {
// to be filled in soon!
return ((ContactsGroup)o).getName();
}
public ImageDescriptor getImageDescriptor(Object object) {
// to be filled in soon!
return null;
}
public Object[] getChildren(Object o) {
return ((ContactsGroup)o).getEntries();
}
};
private IWorkbenchAdapter entryAdapter = new IWorkbenchAdapter() {
public Object getParent(Object o) {
return ((ContactsEntry)o).getParent();
}
public String getLabel(Object o) {
ContactsEntry entry = ((ContactsEntry)o);
return entry.getName() + '-' + entry.getServer();
}
public ImageDescriptor getImageDescriptor(Object object) {
// to be filled in soon!
return null;
}
public Object[] getChildren(Object o) {
return new Object[0];
}
};
public Object getAdapter(Object adaptableObject, Class adapterType) {
if(adapterType == IWorkbenchAdapter.class &&
adaptableObject instanceof ContactsGroup)
return groupAdapter;
if(adapterType == IWorkbenchAdapter.class &&
adaptableObject instanceof ContactsEntry)
return entryAdapter;
return null;
}
public Class[] getAdapterList() {
return new Class[] {IWorkbenchAdapter.class};
}
}
The last step is to register the adapter factory with Eclipse when the ContactsView is created
and unregister it when the view is closed, as shown below:
org.eclipsercp.hyperbola/ContactsView
private IAdapterFactory adapterFactory = new HyperbolaAdapterFactory();
public void createPartControl(Composite parent) {
treeViewer = new TreeViewer(parent, SWT.BORDER | SWT.MULTI
Platform.getAdapterManager().
registerAdapters(adapterFactory, Contact.class);
...
}
public void dispose() {
Platform.getAdapterManager().unregisterAdapters(adapterFactory);
super.dispose();
}
5.4.3. The Label Provider
The content provider gives you the tree structure to display, but not the labels and icons needed
to paint elements on the screen. This is the role of the label provider. When the ContactsView
was created in Section 5.4.1, a default WorkbenchLabelProvider was configured as the
TreeViewer's label provider. Like BaseWorkbenchContentProvider , it used IWorkbenchAdapters to
determine the label and image to show in the tree.
Minimally, you should update the adapter created in the adapter factory to return the name of
the group or contact it adapts. To make things a little more interesting, the code below defines
the label for group names to include the number of logged-in contacts in the group. Update the
entry adapter as well, perhaps to show contacts with a nickname followed by their real name
and server. Decorating the entries with images is covered in the next section.
org.eclipsercp.hyperbola/HyperbolaAdapterFactory
private IWorkbenchAdapter groupAdapter = new IWorkbenchAdapter() {
...
public String getLabel(Object o) {
ContactsGroup group = ((ContactsGroup) o);
int available = 0;
Contact[] entries = group.getEntries();
for (int i = 0; i < entries.length; i++) {
Contact contact = entries[i];
if (contact instanceof ContactsEntry) {
if (((ContactsEntry) contact).getPresence()
!= Presence.INVISIBLE)
available++;
}
}
return group.getName() +
" (" + available + "/" + entries.length + ")";
}
Now Hyperbola is starting to look a bit more interesting, as shown in Figure 5-10.
Figure 5-10. Hyperbola showing a mock-up Contacts view
5.5. Adding Images
A contacts list without images doesn't look quite right. Beyond aesthetics, images are useful for
showing the status of each contact. In this section we:
Add images to the Hyperbola plug-in.
Add images to the Contacts view.
The first thing is to create the needed images. Use your favorite image-editing program to
create a couple of 16x16-pixel images in GIF format. Images used in trees and toolbars usually
have a transparent background; as such, use a drawing tool that allows you to create GIF files
with transparency. If you aren't much of an artist, copy the images we supplied with the code
for this chapter.[1] Table 5-1 shows the images included in the prototype.
[1] As you can see, we are not artistically inclined, but the images serve their purpose.
Table 5-1. Hyperbola Contacts View Icons
Image
Description
User is logged in and is available for a chat.
User is not logged in.
User is away from the computer. You can chat, but the user may not respond right
away.
User is logged in but doesn't want to be disturbed.
Groups.
Add the images to the standard Eclipse location in the Hyperbola plug-inin a directory called
icons at the root of the plug-in project. Next, identify the images in code so they can be
referenced without having to remember where they are located on disk. A standard approach is
to create an interface to track the image paths in your product. Create the IImageKeys interface
shown here. The constants identify the relative path to each image in the icons directory.
org.eclipsercp.hyperbola/IImageKeys
public interface IImageKeys {
public static final String ONLINE = "icons/online.gif";
public static final String OFFLINE = "icons/offline.gif";
public static final String DO_NOT_DISTURB = "icons/dnd.gif";
public static final String GROUP = "icons/groups.gif";
public static final String AWAY = "icons/away.gif";
}
Adding images to the Contacts view is easy; you simply update the appropriate
IWorkbenchAdapters to provide an image for each item in the tree. In Eclipse, there are two
image representations: Images and ImageDescriptors.
Images Images are graphical objects ready to be displayed. They maintain a handle to an
underlying OS resource and as such are considered heavyweight objects. Care must be
taken to dispose of these system resources when the image is no longer needed.
ImageDescriptors Descriptors are lightweight representations of an image. They know
where to find the image and can create images, but do not do so immediately.
Descriptors are handy tokens you can use to talk about images before you actually need an
Image object. The relationship is similar to the one between Java File and, say,
FileInputStream. Files are lightweight and are not directly associated with any OS resources,
whereas FileInputStreams retain file handles and trigger disk access.
The code below shows how to create an ImageDescriptor. The code first uses
Bundle.getEntry(String) to locate the image file and then calls createFromURL(URL) to create
an instance of the descriptor.
public ImageDescriptor createImageDescriptorFor(String id) {
URL url = Platform.getBundle("org.eclipsercp.hyperbola").
getEntry(id);
return ImageDescriptor.createFromURL(url);
}
Since this is such a common coding pattern, AbstractUIPlugin has a static helper called
imageDescriptorFromPlugin(String, String) that looks in all the right places for the requested
image and returns a descriptor. This method uses the Bundle instance to access files in the plugin, but does other bookkeeping that allows icons to be loaded from other locations. Since these
and other methods need the plug-in's id, it's a good time to create a constant for it. The id is
defined on the first page of the plug-in editor. Add the following to the Application class:
org.eclipsercp.hyperbola/Application
public static final String PLUGIN_ID = "org.eclipsercp.hyperbola";
Tip
Since plug-ins can be anywhere and can be in a directory or a JAR, you cannot access
your plug-in's files directly from disk (e.g., using java.io.File). Rather, the Eclipse
Runtime provides several convenient methods for accessing files within particular
plug-ins. See Bundle.getEntry(String) and related methods.
As we discussed earlier, images need to be managed because they represent OS resources.
Fortunately, when using the IWorkbenchAdapter, the Workbench manages the images that are
created and all you have to do is return the appropriate image descriptor.
The image shown for a ContactsEntry depends on each user's presence. Add the
presenceToKey() method below to HyperbolaAdapterFactory . It should return the appropriate
image key given the provided presence.
org.eclipsercp.hyperbola/HyperbolaAdapterFactory
private String presenceToKey(Presence presence) {
if(presence == Presence.ONLINE)
return IImageKeys.ONLINE;
if(presence == Presence.AWAY)
return IImageKeys.AWAY;
if(presence == Presence.DO_NOT_DISTURB)
return IImageKeys.DO_NOT_DISTURB;
if(presence == Presence.INVISIBLE)
return IImageKeys.OFFLINE;
return IImageKeys.OFFLINE;
}
Also, change the workbench adapters to return the image for both a contact group and a
contact entry.
org.eclipsercp.hyperbola/HyperbolaAdapterFactory
public ImageDescriptor getImageDescriptor(Object object) {
return AbstractUIPlugin.imageDescriptorFromPlugin(
Application.PLUGIN_ID, IImageKeys.GROUP);
}
...
public ImageDescriptor getImageDescriptor(Object object) {
ContactsEntry entry = ((ContactsEntry) object);
String key = presenceToKey(entry.getPresence());
return AbstractUIPlugin.imageDescriptorFromPlugin(
Application.PLUGIN_ID, key);
}
Run Hyperbola and notice that the contacts have detailed labels and presences images, as
shown in Figure 5-11the UI is coming together.
Figure 5-11. Hyperbola with images in the Contacts view
5.6. Summary
In this chapter, Hyperbola gained a simple model. You added the first view and learned about
managing images. You should be getting more comfortable with using the RCP and doing plugin development with Eclipse. If you were just skimming or had trouble, you can catch up by
getting the completed code for this chapter as described in Section 3.6.
5.7. Pointers
Perspectives, views, and editors; see Chapter 16.
Customizing Workbench windows; see Chapter 18.
Customizing views and editors; see Chapter 19.
To learn more about JFace and SWT, here are several good starting points:
Northover, Steve and Wilson, Mike. SWT: The Standard Widget Toolkit, Volume 1,
Addison-Wesley, 2004, ISBN: 0321256638.
Clayberg, Eric and Rubel, Dan. Building Commercial-Quality Plug-ins, Addison-Wesley,
2004, ISBN: 0321228472.
Harris, Robert and Warner, Rob. The Definitive Guide to SWT and JFACE, Apress, 2004,
ISBN: 1590593251.
Eclipse Help > Platform Plug-in Developer Guide > Programmer's Guide > Standard
Widget Toolkit
Eclipse Help > Platform Plug-in Developer Guide > Programmer's Guide > JFace UI
Framework
Chapter 6. Adding Actions
In Eclipse, the term action is used to describe a visible element in an application that allows
users to initiate a unit of work. You may notice several terms in Eclipse documentation used to
describe units of work: operations, actions, action delegates, commands, and jobs. However,
actions figure most prominently in RCP applications. The key concept is that when you click a
menu, toolbar, or invoke a key sequence, an action is run. It's that simple.
As you can imagine, actions are essential for most applications. Hyperbola doesn't have any
actions yet, so it's difficult to see how the real product will look and feel. In this chapter, we
show you how to:
Add a top-level menu and toolbar.
Add the standard File > Exit and Help > About actions.
Create an action to add a contact, and place the action in the toolbar and menu.
Add the Hyperbola icon to the task tray.
Allow Hyperbola to minimize to the task tray.
Add a connection and presence indicator to the status line.
Figure 6-1 gives a peek at Hyperbola after that work is completed.
Figure 6-1. Hyperbola with actions and a status line
6.1. Adding to the Menus and Toolbar
Actions are everywhere: in toolbars, top-level menus, context menus, status lines, and so on. In
most applications, the menu and toolbar play a supporting role for the main content area. When
running an application for the first time, most users browse the top-level menu structure to find
out what the application can do. This makes the top-level menu structure of your application
very important.
Take a look at Hyperbola as it is now. Notice that it does not have a top-level menu. Let's fix
that. The general pattern for making the toolbar, status line, or menu bar available is to
configure the window before it is opened. This is done in
ApplicationWorkbenchWindowAdvisor.preWindowOpen() by adding a call to methods such as
setShowMenuBar(boolean) , as shown below:
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void preWindowOpen() {
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
configurer.setInitialSize(new Point(250, 350));
configurer.setShowMenuBar(true);
...
}
You should be familiar with this methodit's what you used to change the size of the window in a
previous chapter. The preWindowOpen() method is called before the window's controls have been
created, and it's the primary place for you to control which WorkbenchWindow parts are visible.
Note
For more advanced window customizations, refer to Chapter 18, "Customizing
Workbench Windows," which explains how to customize the layout of
WorkbenchWindows and how to let the user toggle the toolbar and status line.
The code above enables the menu bar, but does not force it to be shown. It is only shown if it
contains menu items. If you run Hyperbola now, the menu bar does not appear because it's
empty.
6.1.1. Create Top-level Menu
Now that the top-level menu has been enabled, the next step is to create actions and add them
to the menu. RCP applications have a dedicated advisor, called the ActionBarAdvisor, whose
job is to create the actions for a window and populate the menu, toolbar, and status line. The
ActionBarAdvisor is separate from the WorkbenchWindowAdvisor since an application often has
hundreds of actionsthis more clearly separates the concerns.
Figure 6-2 shows the call sequence between the WorkbenchWindow, the WorkbenchWindowAdvisor,
and the ActionBarAdvisor. Notice that ActionBarAdvisor.makeActions() is called before the
WorkbenchWindow's controls are created in createWindowContents(). This means that you cannot
access any of the window's widgets when creating the actionsyou cannot link the actions to any
menus or other window parts.
Figure 6-2. ActionBarAdvisor method sequencing
In Hyperbola, we need two top-level menus: Hyperbola and Help. The Hyperbola menu
should have general application actions and the Help menu some higher level information
related to Hyperbola itself (e.g., Help, About information, etc.). For now, let's just add Exit and
About items to these menus.
Figure 6-2 shows that the steps for creating actions, makeActions() , and placing actions,
fill*(), are separated. This allows you to create the actions just once and then place the same
action objects in several locations, for example, in both the toolbar and a menu. This very useful
coding pattern is shown in the following code snippet:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
public class ApplicationActionBarAdvisor extends ActionBarAdvisor {
private IWorkbenchAction exitAction;
private IWorkbenchAction aboutAction;
protected void makeActions(IWorkbenchWindow window) {
exitAction = ActionFactory.QUIT.create(window);
register(exitAction);
aboutAction = ActionFactory.ABOUT.create(window);
register(aboutAction);
}
protected void fillMenuBar(IMenuManager menuBar) {
MenuManager hyperbolaMenu = new MenuManager(
"&Hyperbola", "hyperbola");
hyperbolaMenu.add(exitAction);
MenuManager helpMenu = new MenuManager("&Help", "help");
helpMenu.add(aboutAction);
menuBar.add(hyperbolaMenu);
menuBar.add(helpMenu);
}
}
When you created the skeleton RCP application, it included an ActionBarAdvisor
implementation with empty makeActions() and fillMenuBar() methods. Now, modify the
generate code as shown above to get top-level Hyperbola and Help menus with associated
Exit and About actions.
Notice that makeActions() creates each action and saves it in a field. Each action is also
registered. Registering actions ensures that they are deleted when the related Workbench
window is closeda very important characteristic. Registering actions also enables key bindings,
as discussed in Chapter 12, "Adding Key Bindings."
Creating the Exit and About actions was easy because they already existed. The Workbench
defines a set of common actions that are reusable in all RCP applications. Each of these actions
is defined as an inner class of org.eclipse.iu.actions.ActionFactory. You instantiate them
and use them as regular actions. They are preconfigured with a standard name, icon, and id.
The code snippet just shown demonstrates how this is done.
Run Hyperbola now and you should see the top-level menus and the new Exit and About
actions, as shown in Figure 6-1. If you run the Help > About action, it displays an empty
dialog, as shown in Figure 6-3. The Plug-in Details button shows the list of installed plug-ins,
and Configuration Details shows information about your environment (e.g., the command line
arguments used to start Hyperbola, system properties, and user preferences). Chapter 8,
"Branding Hyperbola," shows you how to brand this dialog with an about image and some
descriptive text.
Figure 6-3. Empty About dialog
Note
The Plug-in details list is very handy for confirming which plug-ins are running as part
of your application.
6.1.2. Menu Managers
Earlier, the Exit and About actions were added to a menu manager. A menu manager is
responsible for keeping track of actions and sub-menus and allowing you to create logical
structures of actions by grouping. It allows you to organize the actions you want to show
without concerning yourself with how the menu is created. For example, the following modified
version of the earlier code adds placeholders for actions. The real actions can then be added
after the menu manager is created. This flexibility is quite powerful. For example, it lets you
create menus that are filled in by other plug-ins.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillMenuBar(IMenuManager menuBar) {
MenuManager hyperbolaMenu = new MenuManager(
"&Hyperbola", "hyperbola");
hyperbolaMenu.add(exitAction);
hyperbolaMenu.add(new GroupMarker("other-actions"));
...
hyperbolaMenu.appendToGroup("other-actions", aboutAction);
}
Menu managers can also be nested. This allows you to create multidimensional action structures
such as cascading menus. Let's experiment with menu managers a bit. Change the
ActionBarAdvisor with the following code snippet to add a Help cascading menu below the toplevel Hyperbola menu, as shown in Figure 6-4:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillMenuBar(IMenuManager menuBar) {
MenuManager hyperbolaMenu = new MenuManager(
"&Hyperbola", "hyperbola");
hyperbolaMenu.add(exitAction);
MenuManager helpMenu = new MenuManager("&Help", "help");
helpMenu.add(aboutAction);
menuBar.add(hyperbolaMenu);
hyperbolaMenu.add(helpMenu);
}
Figure 6-4. Cascading menu example
6.1.3. The Add Contact Action
The Exit and About actions are generic, so we used predefined actions. Of course, Hyperbola
also needs some actions specific to instant messaging. Here we talk about how to:
Implement an action that adds a contact to the Contacts list.
Ensure the action is only enabled if a contact group is selected in the Contacts view.
Add the action into the top-level Hyperbola toolbar.
Some actions should be available at all times and are independent of the current state of the
application. Exit is a good example of this. Other actions should only be available when
Hyperbola is in a certain state. For example, the Add Contact action you are about to add
requires that a contact group be selected. Actions that do not make sense in the current state of
Hyperbola should be disabled to indicate that they are not applicable.
First, create the Add Contact action as shown below. Don't worry about the compile errors; the
missing run() and selectionChanged() methods are about to be added.
org.eclipsercp.hyperbola/AddContactAction
public class AddContactAction extends Action implements
ISelectionListener, ActionFactory.IWorkbenchAction {
private final IWorkbenchWindow window;
public final static String ID = "org.eclipsercp.hyperbola.addContact";
private IStructuredSelection selection;
public AddContactAction(IWorkbenchWindow window) {
this.window = window;
setId(ID);
setText("&Add Contact...");
setToolTipText("Add a contact to your contacts list.");
setImageDescriptor(
AbstractUIPlugin.imageDescriptorFromPlugin(
"org.eclipsercp.hyperbola", IImageKeys.ADD_CONTACT));
window.getSelectionService().addSelectionListener(this);
}
public void dispose() {
window.getSelectionService().removeSelectionListener(this);
}
// Additional run() and selectionChanged() methods to be added
// here.
...
}
The constructor for this action is pretty standard; the action is given a name, an icon, a tool tip,
and an id. The id is used to uniquely identify the action and is used by
ActionBarAdvisor.register(IAction) to manage the action. The IImageKeys.ADD_CONTACT is a
new image key that you must define. Alternatively, you can use an existing key just for now or
import icons from the final code sample.
Tip
To make the examples clearer, we do not worry about translating the action labels.
However, the Eclipse Java IDE comes with a handy wizard that helps internationalize,
or externalize, the strings in your Java code. The tool can be run from Source > Find
Strings to Externalize...
The interesting part of the action is around the selection listening. In the constructor, the action
is registered as a selection listener. Notice that the action implements ISelectionListener . This
combination means that when the selection changes in the window, the action is notified via its
selectionChanged(IWorkbenchPart, ISelection) method. An implementation of
selectionChanged() is shown in the following snippet:
org.eclipsercp.hyperbola/AddContactAction
public void selectionChanged(IWorkbenchPart part, ISelection incoming) {
// Selection containing elements
if (incoming instanceof IStructuredSelection) {
selection = (IStructuredSelection) incoming;
setEnabled(selection.size() == 1 &&
selection.getFirstElement() instanceof ContactsGroup);
} else {
// Other selections, for example containing text or of other kinds.
setEnabled(false);
}
}
The code first checks that the incoming selection is structured. If it is notfor example, if it's a
text selection from an editor or something elsethe selection cannot affect the action's
enablement state. If more than one item is selected, it does not make sense for the user to add
a contact so the action is disabled. If the incoming selection is structured and contains only one
element and that element is a ContactsGroup , then the Add Contact action is enabled.
Notice that the new selection is remembered by the action so that if it is run, it knows the group
in which to add the new contact. Notice also that it only makes sense to hang onto the selection
if it caused the action to be enabled. All other cases should make the selection field null.
We have not said where these selection events come from. You could bind the listener to just
the Contacts view, but what if there is another view or window that shows contact groups? By
adding its listener to the window's selection service, the action hears about all selection changes
made in that window. You can access a window's selection service by calling
IWorkbenchWindow.getSelectionService() .
The selection service doesn't generate selections on its own. For that purpose, views and editors
can register as selection providers and essentially publish their selections to selection listeners.
In the current Hyperbola, we expect most contact group selection events to come from the
Contacts view so the Contacts view needs to publish its selection events to the window! This
needs to be done when the contents of the Contacts view is created. Take a look at
ContactsView.createContents(Composite) and look for the line:
getSite().setSelectionProvider(treeViewer);
This registers the treeViewer (i.e., the contents of the Contacts view) as a selection provider.
The summary is that listening to the window instead of directly to a particular event source
(e.g., a view) decouples actions from views and allows them to be used in other scenarios.
Tip
Notice that the action also implements ActionFactory.IWorkbenchAction and thus the
dispose() method. When the action is disposed, it is essential that its selection
listener be removed from the selection service. Failure to do this is a common cause of
memory leaksdouble-check to ensure that you always remove any listeners you
register.
The action is configured and structured, so now add the run() method that does the real work.
The code for this is shown below. Note that you can get the code for the dialog by using the
Samples Manager tool as described in Section 3.6, "Sample Code," or you can use a simple
InputDialog to prompt for the contact information.
org.eclipsercp.hyperbola/AddContactAction
public void run() {
AddContactDialog d = new AddContactDialog(window.getShell());
int code = d.open();
if (code == Window.OK) {
Object item = selection.getFirstElement();
ContactsGroup group = (ContactsGroup) item;
ContactsEntry entry =
new ContactsEntry(group, d.getNameText(), d.getNickname(),
d.getServerText());
group.addEntry(entry);
}
}
The action needs the contact's name, nickname, and host server so it opens a dialog with three
entry fields. The entered information is then used to create a ContactsEntry and add it to the
previously selected group.
6.1.4. Adding the "Add Contact" Action
Now that you have created the Add Contact action, you need to add it to the top-level menu
and toolbar. This part is easy since you did the same with the Exit and About actions. First,
ensure that the action is created and registered in
ApplicationActionBarAdvisor.makeActions(IWorkbenchWindow). Remember to register the
action to ensure that it is deleted when the window is closed. Minimizing a window does not
close it; instead, a window is closed when its Shell.close() method is called.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
this.window = window;
exitAction = ActionFactory.QUIT.create(window);
register(exitAction);
aboutAction = ActionFactory.ABOUT.create(window);
register(aboutAction);
addContactAction = new AddContactAction(window);
register(addContactAction);
}
Update fillMenuBar(IMenuManager) and fillCoolBar(ICoolBarManager) to add the action to
both the menu and toolbar as shown in the ApplicationActionBarAdvisor snippet below. Note
that managing toolbars and menus is very similar.
Note
It's common jargon to refer to the top-level toolbar simply as the toolbar. But the
methods in the IWorkbenchWindowConfigurer refer to it as the coolbar. This is an
implementation detail that has leaked into the APIs. The toolbar is implemented using
an SWT CoolBar to support dynamic positioning of its controls. In this chapter, we
refer to this area as the top-level toolbar.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillMenuBar(IMenuManager menuBar) {
MenuManager hyperbolaMenu =
new MenuManager("&Hyperbola", "hyperbola");
hyperbolaMenu.add(addContactAction);
hyperbolaMenu.add(new Separator());
hyperbolaMenu.add(exitAction);
MenuManager helpMenu = new MenuManager("&Help", "help");
helpMenu.add(aboutAction);
menuBar.add(hyperbolaMenu);
menuBar.add(helpMenu);
}
protected void fillCoolBar(ICoolBarManager coolBar) {
IToolBarManager toolbar = new ToolBarManager(coolBar.getStyle());
coolBar.add(toolbar);
toolbar.add(addContactAction);
}
Run the application and you should see an icon in the toolbar and an entry for Add Contact in
the Hyperbola top-level menu. If you click the icon, you are prompted for the contact
information, and after clicking OK, the contact is created and appears in the Contacts list.
Notice that as you click around in the Contacts view, the action changes from enabled to
disabled depending on the selection.
6.1.5. Customizable Toolbars
It is quite common for applications to allow customization of the toolbar. The Hyperbola toolbar
is implemented in terms of the SWT CoolBar. Each ToolBarManager that is added to the
ICoolBarManager is shown in a separate CoolItem group. As such, it can be separately
positioned by the user. For example, toolbar managers can be moved onto separate rows or
reordered within Hyperbola's toolbar. The Hyperbola in Figure 6-5 has two toolbar managers
instead of one. Both managers have the Add Contact action. Since they are in separate
managers, the actions can be moved independently within the toolbar. Experiment with this by
adding the Add Contact action to multiple ToolBarManagers.
Figure 6-5. Top-level toolbar showing two cool items with the move
handle
If you want your actions in the same toolbar group but separated, you can use JFace Separator
instances to divide the actions into groups. An update that uses the Separator is shown below:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
public void populateCoolBar(IActionBarConfigurer configurer) {
ICoolBarManager mgr = configurer.getCoolBarManager();
IToolBarManager toolbar = new ToolBarManager(mgr.getStyle());
mgr.add(toolbar);
toolbar.add(addContactAction);
toolbar.add(new Separator());
toolbar.add(addContactAction);
}
6.2. Adding to the Status Line
The status line at the bottom of the Hyberbola window is a great place to show information that
is either global to the application or pertinent to the user's current task. Most instant messaging
applications place an indicator in the status line to show the user's online status and presence.
Since it's somewhat of a standard, Hyperbola should have it too.
Figure 6-6 shows Hyperbola with an image that indicates whether or not the user is connected
to a chat server and some text that indicates the current presence (e.g., available to chat, do
not disturb). Eventually, we want the icon and text to update automatically as the user's status
changes, but for now, let's keep it simple.
Figure 6-6. Hyperbola with a status line
Remember earlier when you enabled the toolbar and menu in
ApplicationWorkbenchWindowAdvisor.preWindowOpen()? Go back there to enable the status line.
From within the preWindowOpen() method, type "configurer.set" and then press Ctrl+Space to
see all the setters. Then, find the setShowStatusLine() method. This is a great use of Eclipse
Java IDE's content-assistthere are fewer mistakes, less to remember, and it's better than cutand-paste.
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void preWindowOpen() {
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
configurer.setInitialSize(new Point(250, 350));
configurer.setShowCoolBar(true);
configurer.setShowMenuBar(true);
configurer.setShowStatusLine(true);
configurer.setTitle("Hyperbola");
}
Double-check that this worked by running Hyperbola. The status line should be empty, but
nonetheless visible. Now you can add the icon and text using the following snippet for
ApplicationWorkbenchWindowAdvisor . The statusImage is a field on the window advisor.
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void postWindowOpen() {
statusImage =
AbstractUIPlugin.imageDescriptorFromPlugin(
"org.eclipsercp.hyperbola",
IImageKeys.ONLINE).createImage();
IStatusLineManager statusline = getWindowConfigurer().
getActionBarConfigurer().getStatusLineManager();
statusline.setMessage(statusImage, "Online");
}
public void dispose() {
statusImage.dispose();
}
Like the menu bar and toolbar, the status line is controlled by the ActionBarAdvisor. You may
have noticed the fillStatusLine (IStatusLineManager) method in ActionBarAdvisor.
IStatusLineManagers are regular contribution managers similar to IMenuManagers and
IToolbarManagers. They include a handful of methods specific to status lines such as
getProgressMonitor(), setMessage(Image, String), and setErrorMessage (Image, String).
There is one caveat on these additional methods: They can only be called after the status line's
controls have been created. Since ActionBarAdvisor. fillStatusLine() is called before the
status line has been created, you can't call these methods in the ActionBarAdvisor. Instead, a
good place to set the message, for example, is in the
WorkbenchWindowAdvisor.postWindowOpen() method, as shown in the previous snippet.
6.2.1. Status LineA Shared Resource
As you have seen, it's very easy to add images and messages to the status line. Unfortunately,
the status line is a shared resource and can be written to by any plug-in. If your application is
small, like Hyperbola, you can simply centralize the use of the status line and avoid conflicts.
This is not always feasible.
The status line is also special because it is configured with a pre-defined layoutthe bar contains
several reserved areas for standard controls, as shown in Figure 6-7.
Figure 6-7. Status line area breakdown
Fast views The fast views area shows views that are removed from the window but can
be accessed quickly from an icon docked in the fast view bar. For more information on fast
views, consult the online help.
Icon/message You just used the icons and messages area to show the user's connection
and presence in Hyperbola. Consider this the status line contribution that any part is
allowed to make when it has focus.
Progress The first progress area is used for showing modal progress. It is normally
invisible until an IWorkbenchWindow.run() operation is invoked. When the operation is
running, a progress indicator and Cancel button are made visible.
Contributions The contributions area is reserved for the Workbench advisor and active
part contributions.
Jobs progress The jobs progress area is hidden by default, but can be enabled by calling
IWorkbenchWindowConfigurator.setShowProgress Indicator(boolean) . See Sections
14.5.1, "Updating Hyperbola," and 17.8, "Reporting Progress," for more details.
Since the layout is somewhat fixed, it is not possible to left-align additional contributions to the
status linethe reserved areas for the other items get in the way. However, when the job
progress area is not shown, user contributions are right-aligned. You can make additions to the
status line using IStatusLineManager.add(IContributionItem).
In any event, there are many uses of the status line and directly setting the icon and message
area is very effective. For more advanced status line uses, see Section 17.7, "Adding
Contributions to the Status Line."
6.3. System Tray Integration
Another common feature of instant messaging applications is integration into the system tray.
Using this idea, we want Hyperbola to appear in the system tray as an icon representing the
user's presence. This also allows Hyperbola to minimize to the system tray rather than
continuing to be displayed in the task bar even though it is minimized. The tray items are also a
popular location for adding actions related to your application. Figure 6-8 shows how this looks
for Hyperbola.
Figure 6-8. Context menu on the Hyperbola task tray item
Tip
If you are going to use the system tray, it is good practice to make its use optionalif
every application put itself in the system tray, it would get cluttered. When it is
enabled, it should be used to display important status information to the user and
provide a quick access point for running frequent actions.
Also, the system tray is not available on all platforms, so do not make it the focal
point of your application's workflow. It should be used as an optional integration
feature.
In this section, we show you how to:
Add the Hyperbola icon to the task tray.
Allow Hyperbola to minimize to the task tray.
Add the About and Exit actions to the context menu of the task tray item.
6.3.1. Obtaining a Display
To get the system tray, you need a Display. A Display is an SWT object that represents the
underlying graphics system. Typically, the Display is not available until the window has been
created. Figure 6-9 shows the WorkbenchWindow lifecycle and highlights the earliest point, in
postWindowOpen(), where the Display is available. So, the first time a window is opened, the
WorkbenchWindowAdvisor can get the Display via the window's shell and set up the system tray
for Hyperbola. At this point, the tray item is created and configured such that when its menu is
shown, the ActionBarAdvisor populates the menu.
Figure 6-9. How the ActionBarAdvisor contributes actions to the task
item
6.3.2. Creating the Tray Item
The code snippet below from ApplicationWorkbenchWindowAdvisor shows how the system tray
item is created and how it operates. First, the item is created and configured and then listeners
are added to support showing the menu and minimizing into the tray item. After the tray item is
set up, a menu listener is added to it. That way, when the tray item is right-clicked, a context
menu appears and is populated by the ActionBarAdvisor. Some platforms do not have system
trays and initTaskItem() may return null. In that case, the tray item is not created.
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void postWindowOpen() {
initStatusLine();
final IWorkbenchWindow window = getWindowConfigurer().getWindow();
trayItem = initTaskItem(window);
if (trayItem != null) {
hookPopupMenu(window);
hookMinimize(window);
}
}
private void hookMinimize(final IWorkbenchWindow window) {
window.getShell().addShellListener(new ShellAdapter() {
public void shellIconified(ShellEvent e) {
window.getShell().setVisible(false);
}
});
trayItem.addListener(SWT.DefaultSelection, new Listener() {
public void handleEvent(Event event) {
Shell shell = window.getShell();
if (!shell.isVisible()) {
shell.setVisible(true);
window.getShell().setMinimized(false);
}
}
});
}
private void hookPopupMenu(final IWorkbenchWindow window) {
trayItem.addListener(SWT.MenuDetect, new Listener() {
public void handleEvent(Event event) {
MenuManager trayMenu = new MenuManager();
Menu menu = trayMenu.createContextMenu(window.getShell());
actionBarAdvisor.fillTrayItem(trayMenu);
menu.setVisible(true);
}
});
}
private TrayItem initTaskItem(IWorkbenchWindow window) {
final Tray tray = window.getShell().getDisplay().getSystemTray();
if (tray == null)
return null;
TrayItem trayItem = new TrayItem(tray, SWT.NONE);
trayImage = AbstractUIPlugin.imageDescriptorFromPlugin(
"org.eclipsercp.hyperbola", IImageKeys.ONLINE).createImage();
trayItem.setImage(trayImage);
trayItem.setToolTipText("Hyperbola");
return trayItem;
}
public void dispose() {
if (trayImage != null) {
trayImage.dispose();
trayItem.dispose();
}
}
Note
The image and tray item area are saved as fields on the WorkbenchWindowAdvisor so
that they can be deleted when the window is closed.
Since the ActionBarAdvisor already manages all of Hyperbola's actions, it is a good place to put
the method that fills the system tray item's menu. This also allows the tray item menu to reuse
actions created in the ActionBarAdvisor. Add the following snippet to
ApplicationActionBarAdvisor:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillTrayItem(IMenuManager trayItem) {
trayItem.add(aboutAction);
trayItem.add(exitAction);
}
A shell listener is added in the hookMinimize() method to the window so that the window is
marked as hidden when minimized. This simulates minimizing the window into the task bar.
When the window is invisible, the only indication that Hyperbola is running is the system tray
icon.
Another listener, also added in the hookMinimize() method, listens to the tray item to detect
when it is selected. Selecting the task item is the cue that Hyperbola's window should be made
visible if it had previously been minimized.
After setting this up, run Hyperbola and look for the system tray icon. Right-click it and the
context menu appears. Minimize Hyperbola and notice that Hyperbola is completely gone except
for the task tray icon. Click the tray icon and Hyperbola reappears.
6.4. Summary
There you have itHyperbola now has a minimal set of actions and some more advanced desktop
integration. As we mentioned at the outset, actions are one of the key elements of building RCP
applications. There are many ways actions can be used and placed in the Workbench. With the
information in this chapter, you are well on your way to mastering action definition and
placement.
But what about declarative actions?
If you have some Eclipse plug-in development experience, you may be asking
yourself why we have not used any of the Workbench's extension points for defining
actions. After all, Eclipse is all about declarative contributions. This is a common
questionwhen should an RCP application use the declarative Workbench extension
points as opposed to simply creating the actions programmatically as in Hyperbola?
The short answer is that within the context of a single small application, there is
usually no need to use the extension point approach. In fact, an RCP application
must minimally define a top-level menu structure using programmatic actions
because without this, there would be no place to contribute actions declaratively.
However, there are advantages to using declarative actions. These are detailed later
in the book but summarized here:
They allow lazy loading of plug-ins by being shown in the UI without loading
their associated plug-in. In large applications with many plug-ins, this is very
important.
Declarative actions can be associated with perspectives and easily allow
dynamic reconfiguration of top-level menus and toolbars based on the active
perspective.
They allow users to configure top-level menus and toolbars via the perspective
customization dialog (refer to the ActionFactory.EDIT_ACTION_SETS class for
more details).
Declarative action contributions can easily be filtered out of the application
using capabilities.
Refer to Chapter 17, "Actions," for examples on how to use declarative actions in
Hyperbola. Programmatic actions are used for the remainder of the Hyperbola
tutorial.
6.5. Pointers
This book contains many other action-related sections; if you are anxious to learn more, you
can skip ahead to any of these sections:
Adding key bindings to actions; see Chapter 12.
Understanding the differences between actions and commands; see Section 12.1.
Mastering programmatic and declarative actions; see Section 17.2.
Showing progress for long-running actions; see Section 17.8.
Chapter 23 talks about action definition and placement to maximize flexibility.
Chapter 7. Adding a Chat Editor
Hyperbola is taking shape. Now you can pretty much define and manage users as well as track
your status and integrate Hyperbola with the desktop. The only major item left is the chat area
itself. Figure 7-1 shows the UI you are about to add. It's composed of a transcript area at the
top and an input area at the bottom. Notice that unlike the Contacts view, the chat area is
closable and has a title.
Figure 7-1. Chatting with Hyperbola
In this chapter, we show you:
the difference between a view and an editor
how to create a chat editor
how to create an action that opens an editor
7.1. Views and Editors
Before getting into the details of how to create the chat editor, let's step back a bit and consider
whether it should be a view or an editor. When you created the Contacts view in Chapter 5,
"Starting the Hyperbola Prototype," we mentioned that views and editors contain the "real
content" for your application. But how do you choose which one to use?
The rule of thumb is that editors are meant for the primary focus of attention while views
provide supporting information for a given task. So, in Hyperbola, chatting is the primary task
and browsing or managing contacts is a supporting function. This indicates that the Hyperbola
chat area should be implemented as an editor.
Not all decisions are quite that clear-cut, however. Even the choice made here has been hotly
discussed by the Hyperbola development team. It is, of course, technically possible to use a
view to represent a unique chat instead of using the chat editor (an excellent exercise for the
reader).
Here is a short list of differences between views and editors to help you decide which to use for
your application:
Editors are shared between perspectives in the same window. For example, if you close an
editor in one perspective, it is closed in all perspectives.
Editors and views cannot be mixed in the same stack. For example, you can't drag and
drop views and editors into the same location in the perspective.
Views can be detached from a Workbench window.
Views can be shown without a title.
Editors add contributions to the main toolbar and menu whereas views add contributions
to their local toolbar and menu. You can, however, associate action sets to appear when a
view is active.
It is possible to ask for the active editor even if the editor does not have focus. This makes
it easier to synchronize views with editors, for example, linking outline-style views to the
current editor.
In Section 16.2.1, "Multiple Instances of the Same View," we show that it's possible to have
multiple instances of the same view, so it is possible to allow chatting from within a view, or
even better, in a separate window. For now, we use the editor mainly because you already
know how to create a view and this gives us a chance to look at how to use editors.
7.2. Defining the Chat Editor
The first step in adding the chat editor is defining the editor extension. As we saw with the
Contacts view in Chapter 5, first you define an extension, then you implement the class
identified in the extension. Start by opening the Hyperbola plug-in editor and from the
Extensions page, click Add... Select the org.eclipse.ui.editors extension point from the list
and click OK. This adds the extension point to the All Extensions list so you can contribute
extensions.
Next, right-click on the added org.eclipse.ui.editors item in the All Extensions list, and
from the context menu, select New > editor. On the right side of the plug-in editor, the details
of the new extension are shown, as in Figure 7-2. Fill in the id, name, and icon fields.
Figure 7-2. Adding the editor extension point to plugin.xml
[View full size image]
The class field references a class that implements the chat editor. A trick for creating the class
is to click on the class hyperlink in the plug-in editor. This launches the New Class wizard
primed with the correct superclass (EditorPart) and various other settings. Enter the class
name ChatEditor and click Finish to create the skeleton code for the editor. The generated
skeleton will contain many TODOs. Browse the file to get a feel of the methods that need to be
implemented.
Now let's look at each method individually and see how you should fill in the code. Keep in mind
that any method we do not mention can be left as-is. As usual, it's good to define a constant to
track the id for the editor; this is the same id as defined in the editor extension. The first thing
the editor needs to do is create the UI elements, such as the transcript area where the chat text
appears, and the text entry area. The remainder of the editor code handles key presses and
transferring text to the transcript area from the entry area.
org.eclipsercp.hyperbola/ChatEditor
public class ChatEditor extends EditorPart {
public static String ID = "org.eclipsercp.hyperbola.editors.chat";
private Text transcript;
private Text entry;
// Always need a no-arg constructor
public ChatEditor() {
}
public void init(IEditorSite site, IEditorInput input)
throws PartInitException {
setSite(site);
setInput(input);
setPartName(getUser());
}
public void createPartControl(Composite parent) {
Composite top = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginWidth = 0;
layout.marginHeight = 0;
top.setLayout(layout);
transcript = new Text(top, SWT.BORDER | SWT.MULTI | SWT.WRAP);
transcript.setLayoutData(new GridData(
GridData.FILL, GridData.FILL, true, true));
transcript.setEditable(false);
transcript.setBackground(transcript.getDisplay().getSystemColor(
SWT.COLOR_INFO_BACKGROUND));
transcript.setForeground(transcript.getDisplay().getSystemColor(
SWT.COLOR_INFO_FOREGROUND));
entry = new Text(top, SWT.BORDER | SWT.WRAP);
GridData gridData = new GridData(
GridData.FILL, GridData.FILL, true, false);
gridData.heightHint = entry.getLineHeight() * 2;
entry.setLayoutData(gridData);
entry.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent event) {
if (event.character == SWT.CR) {
sendMessage();
// ignore the CR and don't add to text control
event.doit = false;
}
}
});
}
public void setFocus() {
if (entry != null && !entry.isDisposed()) {
entry.setFocus();
}
}
private String getUser() {
return ((ChatEditorInput) getEditorInput()).getName();
}
private String renderMessage(String from, String body) {
if (from == null)
return body;
int j = from.indexOf('@');
if (j > 0)
from = from.substring(0, j);
return "<" + from + "> " + body;
}
private void scrollToEnd() {
int n = transcript.getCharCount();
transcript.setSelection(n, n);
transcript.showSelection();
}
private void sendMessage() {
String body = entry.getText();
if (body.length() == 0)
return;
transcript.append(renderMessage(getUser(), body));
transcript.append("\n");
scrollToEnd();
entry.setText("");
}
}
Editors need an IEditorInput before they can be opened. Editor inputs are a lightweight
description of the initialization information the editor uses to decide what to show. The chat
editor expects a ChatEditorInput that contains the user name of the person at the other end of
the chat. For now, just assume that a ChatEditorInput exists while we continue reviewing the
code for the editor. The next section shows how to create the ChatEditorInput.
Note
In the real code, there are several methods needed to support the Save and Save As
workflows. These are not shown here. You can simply use the defaults because you
won't need to save a chat when Hyperbola exits.
Editor implementations follow a common pattern that starts when
EditorPart.createPartControl() is called and the editor can create its widgets.
ChatEditor.createPartControl(Composite) creates two text fields: one that contains the chat
transcript and another to allow the user to enter and send messages.
In addition to creating the editor's widgets, an editor can update its title using
EditorPart.setPartName(). Even though a title was specified in the extension description, it can
be changed at any time. The last detail in the ChatEditor is a constant called ID. This is the
editor id as defined in the editor extension and is used to programmatically refer to this editor.
This is going to be particularly useful when it comes time to write the action to show the editor.
The life of an editor
The general lifecycle of an editor is:
The Workbench instantiates the editor, creates an editor site, then calls
EditorPart.init(IEditorSite, IEditorInput). The editor site allows the
editor to access the Workbench's services. It is important that the editor class
has a public default constructor. This is used by the Workbench to instantiate
the editor.
When the editor is made visible, the method
EditorPart.createControl(Composite) is called to create the editor's widgets.
Once the editor is created, the method EditorPart.setFocus() is called.
When the editor is closed, if the contents need to be saved, the method
EditorPart.doSave(IProgressMonitor) is called.
At the end the editor lifecycle, the method EditorPart.dispose() is called.
In addition to the temporal relationship between editors and the Workbench, editors refer to
and access parts of the Workbench. Figure 7-3 shows the key characteristic that an EditorPart
relates to the WorkbenchWindow via its site (IWorkbenchPartSite ). The site is the context that the
editor uses to access the Workbench and various Workbench services such as key bindings, the
selection service, and their action bars.
Figure 7-3. The ChatEditor in context
7.2.1. Editor Input
All editors are opened with an IEditorInput via the IWorkbenchPage, as shown below. We are
going to add the action to open the editor in a bit, but for now, we need a specific
implementation of IEditorInput, to be called ChatEditorInput, so that Hyperbola can tell which
editor it's talking to.
IWorkbenchPage.openEditor(IEditorInput input, String editorId)
The editor input is the model for the editor. Editor inputs are both initialization data for the
editor, telling it what to show, and identification information for the Workbench, telling it which
inputs have open editors. The latter point allows the Workbench to decide if an editor is already
open for an input and to show it instead of opening a new one.
Create the ChatEditorInput as shown below. Most of the code here is bookkeeping. This editor
input maintains a participant, the other person in the chat, and returns that value to identify
the chat to be edited.
org.eclipsercp.hyperbola/ChatEditorInput
public class ChatEditorInput implements IEditorInput {
private String participant;
public ChatEditorInput(String participant) {
super();
Assert.isNotNull(participant);
this.participant = participant;
}
public boolean exists() {
return false;
}
public String getToolTipText() {
return participant;
}
public ImageDescriptor getImageDescriptor() {
return null;
}
public String getName() {
return participant;
}
public IPersistableElement getPersistable() {
return null;
}
public boolean equals(Object obj) {
if (super.equals(obj))
return true;
if (!(obj instanceof ChatEditorInput))
return false;
ChatEditorInput other = (ChatEditorInput) obj;
return participant.equals(other.participant);
}
public int hashCode() {
return participant.hashCode();
}
}
7.2.2. The Chat Action
At this point, the editor has been defined, but if you run Hyperbola, the ChatEditor cannot be
shown. Editors are not opened automatically in a perspective, but rather as the direct result of a
user action. You need a ChatAction that initiates a chat based on the contact selected by the
user. Use the New Class wizard to create a class named ChatAction, as shown in Figure 7-4.
The action implements the ISelectionListener to track the selection to calculate enablement. It
also implements IWorkbenchAction to make it disposable.
Figure 7-4. New class wizard for creating the ChatAction
Once the skeleton action has been generated, add a constructor and a couple of instance
variables to track the selection and window. This is almost identical to the AddContactAction
you created in the previous chapter.
org.eclipsercp.hyperbola/ChatAction
public class ChatAction extends Action implements ISelectionListener,
IWorkbenchAction {
private final IWorkbenchWindow window;
public final static String ID = "org.eclipsercp.hyperbola.chat";
private IStructuredSelection selection;
public ChatAction(IWorkbenchWindow window) {
this.window = window;
setId(ID);
setText("&Chat");
setToolTipText("Chat with the selected contact.");
setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
Application.PLUGIN_ID, IImageKeys.CHAT));
window.getSelectionService().addSelectionListener(this);
}
public void dispose() {
window.getSelectionService().removeSelectionListener(this);
}
In Hyperbola, it only makes sense to use the ChatAction when a contact is selected. You should
add logic to the ChatAction that enables the action only when a Contact is selected. This is the
same logic you used for the Add Contact action in Chapter 6, "Adding Actions." As an exercise,
go and get that code now and update it to work in this case.
org.eclipsercp.hyperbola/ChatAction
public void selectionChanged(IWorkbenchPart part, ISelection incoming) {
if (incoming instanceof IStructuredSelection) {
selection = (IStructuredSelection) incoming;
setEnabled(selection.size() == 1 &&
selection.getFirstElement() instanceof ContactsEntry);
} else {
// Other selections, for example containing text or of other kinds.
setEnabled(false);
}
}
To round out ChatAction, add a run() method that creates an input for the selected user and
asks the Workbench to open a chat editor. The Workbench takes care of finding existing open
editors. Notice that different kinds of editors, like different kinds of views, are referenced by
their id. This id is the one you defined in the org.eclipse.ui.editors extension. It was also
defined as a constant on the ChatEditor.
org.eclipsercp.hyperbola/ChatAction
public void run() {
Object item = selection.getFirstElement();
ContactsEntry entry = (ContactsEntry) item;
IWorkbenchPage page = window.getActivePage();
ChatEditorInput input = new ChatEditorInput(entry.getName());
try {
page.openEditor(input, ChatEditor.ID);
} catch (PartInitException e) {
// handle error
}
}
The final step is to add the new action to the Hyperbola menu and toolbar. By now you have
done this three or four times, so we leave it as an exercise for you. Hint: Update the methods
makeActions() , fillMenuBar() , and fillCoolBar() in the class ApplicationActionBarAdvisor.
7.3. Checkpoint
Now is a good time to run Hyperbola. Select a contact in the Contacts view and run the Chat
action. A chat editor should open as shown in Figure 7-1. Notice that the chat editor is rather
narrow. Even if you make the window bigger, the chat editor stays skinny. Remember all the
way back to Chapter 5, when you defined the Hyperbola perspective? There we were not
concerned about the editor area, and we wanted the Contacts view to take the whole window.
To fix this, first increase the default size of the Hyperbola windowwe need more room now. Go
to the ApplicationWorkbenchWindowAdvisor 's method preWindowOpen() and change the initial
size of the window. The user can ultimately decide on the size of the window and all the views
and editors, but it's always good to provide a good default layout.
Next, go to the Perspective class and change it to show the editor area. While you are there,
shrink the relative size of the Contacts view by changing the ratio to .50f. These changes are
shown below:
org.eclipsercp.hyperbola/Perspective
public void createInitialLayout(IPageLayout layout) {
layout.setEditorAreaVisible(false);
layout.setEditorAreaVisible(true);
layout.addStandaloneView(ContactsView.ID, false,
IPageLayout.LEFT, .50f, layout.getEditorArea());
}
The result of these changes is shown in Figure 7-5. The editor area is empty, but shown, and
the Contacts view takes 50% of the page width.
Figure 7-5. Hyperbola with the editor area shown
If you want to get fancy, you can show and hide the editor area automatically by adding an
action that uses IPartListeners to listen for editors closing and then hides the area when the
last editor is closed using IWorkbenchPage.setEditorAreaVisible(boolean).
7.4. Summary
You now have a running prototype of Hyperbola. Hyperbola is taking shape and is already far
enough along to give people an idea of the end-product and what is involved in making RCP
applications. Along the way, you have become familiar with RCP and plug-in development in the
Eclipse IDE.
The next chapters step back from the code and brand Hyperbola with a splash screen, custom
executable, and icons to get it ready for shipping.
7.5. Pointers
Creating multiple instance views; see Section 16.2.1.
Drag and drop into the editor area; see Section 16.4.
Customizing the look and feel of views and editors; refer to Chapter 19.
Chapter 8. Branding Hyperbola
Hyperbola is looking pretty good at this pointyou can add contacts, initiate chats, and exercise
the UI. Hyperbola shows images for the various online states, has actions in all the right places,
and puts forward the look and feel we want. The people around you are in awe. But it still does
not feel like a normal applicationit is missing the splash screen, the images in the window title
bar, a branded launcher, etc. We'll take care of that here. More generally, the goals of this
chapter are:
Detail the branding for Hyperbola.
Show how product configurations are created.
Enumerate the various aspects of Eclipse product branding and how to configure them.
Create a fully branded Hyperbola product.
8.1. Defining the Hyperbola Product
Before you can do any branding or packaging of Hyperbola, you must define a product
configuration. Product configurations gather together all the information about splash screens,
launcher icons, window images, about text, plug-in and feature lists, and so on into one place.
Select the org.eclipsercp.hyperbola project and, if you are in the Plug-in Development
perspective, use File > New > Product Configuration to start the New Product
Configuration wizard as shown in Figure 8-1. Otherwise, use File > New > Other... > Plugin Development > Product Configuration.
Figure 8-1. New product configuration wizard
In the wizard, pick a location for the product configuration. The configuration file can go in any
project associated with the product, but so far, we only have one project, so there's not much
choice. Now, give it a filename such as "hyperbola.product". The configuration filename must
end in ".product".
Next, choose a technique for initializing the configuration. The wizard can extract information
from an existing product or launch configuration or simply create a basic product. If you have
been following along, then you already have a launch configuration called "Hyperbola" or
"Eclipse Application." The currently defined configurations are listed in the Use an existing
launch configuration drop-down. Enable this option and pick a configuration you have already
used and you know works.
Note
If you don't have a suitable launch configuration, launch Hyperbola by opening the
Hyperbola plug-in editor and clicking on one of the Launch links in the Testing
section of the Overview page. Then continue creating the product configuration.
When you click Finish, the wizard reads the launch configuration and uses it to build a product
definition. In particular, it gets the list of plug-ins and the id of the application extension used in
the launch. The new product configuration is opened in an editor, as shown in Figure 8-2.
Figure 8-2. Hyperbola product overview
[View full size image]
As with the plug-in editor, the product configuration editor gathers together information from
many different files and presents it all in one place. The configuration information is grouped
onto several tabs within the editor. The Overview page in Figure 8-2 shows the Product
Definition section at the top.
Here, you specify the Product ID, the Application ID, and the Product Name of the product.
In our case, the launch configuration used to initialize the product had only an Application ID,
so the Product ID text field is empty. In fact, there are no products defined at alleven the
drop-down list is empty. You need a product definition and id to tell Eclipse how to brand your
application, so click New... to get the New Product Extension dialog shown in Figure 8-3.
Figure 8-3. New product extension dialog
As with applications, product extensions are contributed via extensions. They have an id and
identify the application to run when the product is run. Choose the Hyperbola plug-in and type
"product" in the Product ID field. The Hyperbola application should already be selected, but it's
worth checking. Click Finish to return to the product editor. The new product and application
values show up on the Overview page.
Synchronizing the configuration
From time to time, you may make changes in the product editor and then try them
out only to find they did not take effect. It is likely that the product configuration
and its constituent files are not synchronized. As we mentioned at the outset, a
product configuration is really an amalgam of information found in various files in
the system. For example, the product extension is defined in the product plug-in's
plugin.xml file. The application may be found in the same file or somewhere else.
The information on the Configuration page may be found in the config.ini file or
the product launcher's .ini file. Some of the branding information is stored in the
product extension in the defining plug-in.
The product editor gives you the ability to explicitly synchronize the product
configuration with the files it encompasses. The Testing section on the Overview
page has a link that offers to Synchronize this configuration with the product's
defining plug-in. You should click on this when you change the configuration.
PDE automatically synchronizes the configuration when you use the Launch links in
the Testing section of the Overview page to run the product.
You still need to fill in the Product Name, "Hyperbola Chat Client". This is the string that
appears in the title bar of the Hyperbola windows. Launch the product using the links in the
Testing section of the Overview. Hyperbola should now look something like Figure 8-4.
Figure 8-4. Ignored window title branding
Notice that the title bar of the window still says "Hyperbola". It should be "Hyperbola Chat
Client" as you entered in the product configuration editor. Where's that coming from?
Remember all the way back to Chapter 4, "The Hyperbola Application," when you first
generated Hyperbola from the template? You entered "Hyperbola" in the template wizard and
the string was embedded in the code of ApplicationWorkbenchWindowAdvisor.preWindowOpen():
configurer.setTitle("Hyperbola");
Go back and delete the setTitle() call and then run the application again. Figure 8-5 shows
Hyperbola with a properly branded title bar label.
Figure 8-5. Good window title branding
You may not have noticed, but launching the product caused a new launch configuration to be
created. Open up the launch configuration dialog (Run > Run...) and take a look at the list.
There is "Hyperbola," the one you used as a base for the product configuration, and a new one
called "hyperbola.product" (assuming that is the name you used for your product configuration
file). This new configuration is used by the product editor to launch your product. Since it is a
normal launch configuration, you can run or debug it directly and use keyboard shortcuts such
as F11 to debug the last launched configuration.
PDE keeps the launch configuration and product configuration synchronized. If you change the
list of plug-ins in the product configuration and save, the launch configuration is updated. This
relationship is one-waylaunching from or changing the launch configuration does not trigger any
synchronization with the product configuration.
8.2. Window Images
Now that Hyperbola has a product configuration and the window title is set correctly, the next
step is to add the graphical branding cues such as the splash screen, images, and icons. The
next few sections go over each element of product branding.
Select the Branding page in the product editor and review the options for window images.
Window images are the images that are typically shown at the top left corner of the window, in
the task bar, and in other locations; it really depends on the window system you are running.
Figure 8-6 shows the Window Images section of the Branding page. Here you identify two
GIF images, one 16x16 and the other 32x32.
Figure 8-6. Window images configuration
Typically, the images are stored in the plug-in that contributes the product extension. The
images for Hyperbola are in the sample code for this chapter, as outlined in Chapter 3, "Tutorial
Introduction." Use the Samples Manager to copy the contents of the
org.eclipsercp.hyperbola/icons folder into the org.eclipsercp.hyperbola project in your
workspace and fill in the image locations as shown. Then, save the product configuration and
launch Hyperbola to see how the window images show up. Figure 8-7 shows the new look of
Hyperbola.
Figure 8-7. Window image branding
The running Hyperbola now looks like any other application on your system. The next step is to
provide a custom launcher with proper icons.
8.3. Customizing the Launcher
The launcher is the program that end-users run when they want to start Hyperbola, for
example, hyperbola.exe on Windows. You could just use the launcher that comes with Eclipse,
but of course, you don't want to tell users to "double-click on eclipse.exe" to run Hyperbolayou
want a hyperbola.exe for them to run. Further, the Eclipse launcher has Eclipse icons associated
with it. It makes more sense that these be Hyperbola-specific icons.
To set this up, use the Program Launcher section on the Branding page of the product editor
as shown in Figure 8-8.
Figure 8-8. Launcher branding
The Launcher Name box allows you to enter the simple name of the launcher. You should not
append ".exe". That information is platform-dependent and PDE takes care of it when the
product is exported.
The next section contains a series of entry fields for identifying the icons associated with
Hyperbola's launcher. It turns out that each OS requires different image sizes and formats so
the product editor has a section for the supported OS. These sections are initially collapsed, but
if you click on the OS name, the section related to the OS expands. Figure 8-8 shows the
sections for linux, macosx, and windows expanded, but the section for solaris is collapsed.
Fill in the image names for the OSs in which you are interested. The Hyperbola images are in
the sample files you imported earlier in the branding folder. The launcher images are used in
the process of exporting the products. During export, PDE creates a launcher program that
behaves exactly like the standard Eclipse launcher but is named hyperbola and is branded with
the icons you specified. We talk about launcher branding here to complete the branding story,
but you cannot test it until the next chapter where you learn how to export Hyperbola.
8.4. Splash Screen
The splash screen is the first visible part of Hyperbola. Figure 8-9 shows the Splash Screen
section of the Branding page in the product editor. Eclipse expects the splash screen to be
called splash.bmp, so all you need to do is identify the plug-in that contains the file. You can get
Hyperbola's splash screen from the sample code for this chapter. Note that if you leave this field
blank, the splash screen is assumed to be in the same plug-in as the product. We recommend
making an explicit choice here for completeness.
Figure 8-9. Defining the splash screen
Note
Currently, the Eclipse launcher can only display splash screens that are saved as BMP
images, the standard bit-mapped graphics format used on Windows. Although BMPs
are a Windows standard, they can be saved and read on any platform.
Shipping locale-specific files
If you need to have locale-specific splash screens or message catalogs, then you
have to put the relevant files into a locale-based directory structure, as shown in
Figure 8-10. Under the nl directory, there is a structure that mimics the structure of
Java locale strings. For example, the splash screen for the English locales is in the
directory nl/en. The directories under nl/en contain data for English locale
variations. Note that the root of the plug-in still contains splash.bmp and
plugin.properties files. These are used when matching locale-specific files cannot
be found.
Figure 8-10. Locale-specific file structure
The nl directory structure can be carved off and shipped separately in one or more
fragments. When fragments are installed, their resources and code are seamlessly
merged with those of their host plug-in. See Section 26.3, "Fragments," for more
details. Using this approach, the nl file structure in Figure 8-10, is simply moved to
a set of fragments.
The locale-specific files in the structure are automatically searched when looking for
translated strings. For example, many of the values in a plugin.xml are displayed in
the UI and should be translated to support multiple locales. To enable this,
%variables are used instead of the real text. The variable names correspond to keys
in a plugin.properties file. The values in the file are translated for each locale and
shipped separately in fragments. The text for the About dialog discussed later in this
chapter is a good example of text that should be translated.
Now when you run Hyperbola, the splash screen appears. Hyperbola is a modest sized
application and it comes up very quickly on average machines. So quickly in fact that there is
barely time to read the splash screen. However, if the machine is slower, bogged down, or
Hyperbola is installed on a network drive, the startup time may be longer. Without a splash
screen, users may begin to wonder what, if anything, is happening. Including a splash screen
helps set user expectations and gives your product a somewhat more polished feel.
8.5. About Information
Most applications have an About dialog that reports product version, license, and copyright
information to the user. The Eclipse Workbench includes a standard dialog that presents this
information and gives users access to configuration information that is useful if they need to
report problems. You are, of course, free to ignore this dialog and write your own. If you want
to use the standard About dialog, read on.
8.5.1. Product About Information
The About action, accessed via Help > About Hyperbola, was added to Hyperbola in Chapter
6, "Adding Actions," but the About dialog is currently empty. Let's add branding information to
update the dialog, as shown in Figure 8-11.
Figure 8-11. Hyperbola About dialog
[View full size image]
The standard About dialog is branded by supplying an about image and some about text. The
about image is the graphic shown down the left side of the dialog. This image can be anything
you want, but it should be in GIF format. If the image is smaller than 250x330 pixels, then the
about text is shown at the right of the image in the dialog, as in Figure 8-11. If the image is
larger, the about text is not shown and the image fills the entire top portion of the dialog. In
any event, the image should be less than 500x330 pixels.
The branded about image and text are defined in the About Dialog section of the Branding
page in the product editor, as shown in Figure 8-12. Notice that the about text has a value of
%aboutText. You could put real text here. Using %aboutText indicates to Eclipse that the value is
human-readable and the real text can be found in the plugin.properties file associated with
the product plug-in. This allows the text to be adjusted to suit different locales. Create a
plugin.properties file at the root of org.eclipsercp.hyperbola and add the following content:
org.eclipsercp.hyperbola/plugin.properties
aboutText=\n\nHyperbola Chat Client (Workbench)\n\
Version: 1.0.0\n\n\
Hyperbola is an RCP application developed for the book \n\
\tEclipse Rich Client Platform \n\
\t\tDesigning, Coding, and Packaging Java Applications \n\
\n\
(c) Copyright Jeff McAffer and Jean-Michel Lemieux 2005. \
All Rights Reserved.\n
Figure 8-12. Defining About dialog branding
Specializing the About text
Typical About information includes the version number of the product and perhaps
even the build number or date. Redefining the About text for every build across all
translations is challenging. Rather than forcing such values to be embedded in the
text, the standard Eclipse About dialog does variable substitution in the About text
string.
The About text is treated as a java.text.MessageFormat template, where values are
supplied by the properties file called about.mappings in the product's plug-in. For
example, change the Hyperbola aboutText shown in this section to be
Version: {0}\n\n\
and then add an about.mappings file in the Hyperbola product plug-in with the
following line:
0=1.0.0
The net result is that the About dialog shows
"Hyperbola Version 1.0.0"
To move to the next version of Hyperbola, you only need to update the value of the
key '0' in about.mappings. The downside to this approach is that the mapped values
cannot be translated.
As one final check, run Hyperbola and open the About dialog. Don't forget to synchronize. Click
on the Plug-in Details button. There should be a collection of Eclipse plug-ins and a single
Hyperbola plug-in. Notice that our plug-in does not have a provider. Open the plug-in editor for
Hyperbola by double-clicking on the plugin.xml file. On the Overview page, enter
"eclipsercp.org" in the Provider field. Now re-run and check the plug-in details page.
8.5.2. Plug-in About Information
You may have noticed a More Info button at the bottom left of the plug-in details dialog. When
you select a plug-in and click More Info, you are shown the contents of the about.html file in
the root of the plug-in. Typically, this contains detailed licensing information about the plug-in,
but since it is a standard HTML page, it can present anything you like.
Note
If your plug-in is shipped as a JAR file and the about.html references other pages in
the plug-in, the additional pages must be in a directory called about_files at the root
of the plug-in. This is required because Eclipse extracts the files from the JAR before
presenting the root file in the Web browserbrowsers typically cannot see inside JARs.
8.6. Summary
Now you have a fully branded, somewhat functional Hyperbola product. There is enough here to
give users a real feeling for the look and feel of the final product on their desktop.
The only problem is that your office is getting crowded with all the people wanting to take
Hyperbola for a spin. You really should distribute it for them to see. Unfortunately, Hyperbola is
still in the laboratoryyour workspace. To give it to another user means giving that person your
entire workspace and getting her to install the full Eclipse SDK. That's where product exporting,
the subject of the next chapter, comes in.
Chapter 9. Packaging Hyperbola
Even though Hyperbola is fully branded, you still can't send it to your friends because it lives in
your workspace. The good news is that you have a branded product configuration. In this
chapter, we show you how to package that configuration and export it in various forms. The
goal is to take Hyperbola from a laboratory prototype to a complete and ready-to-install Eclipse
application. By the end of this chapter, you will be able to impress your mother with the
application you've written.[1] This chapter also covers:
[1] Don't laugh. The "Mom Test" can be quite challenging.
the different shapes that RCP products can have
exporting Hyperbola and running it outside the workspace
exporting Hyperbola for other platforms
moving Hyperbola out of the laboratory and into the real world
9.1. Exporting Hyperbola
So far, you have been running Hyperbola in-place in the workspace. To export it, you have to
identify what parts of the org.eclipsercp.hyperbola project go into the
org.eclipsercp.hyperbola plug-in.
Open the plug-in editor for the Hyperbola plug-in and switch to the Build page. The Binary
Build section shown in Figure 9-1 lists the set of development-time files and folders that are
also part of the Runtime plug-in structure. PDE takes care of adding the compiled Java classes
to the build output, but you have to manage the other files. For example, the whole META-INF
directory and plugin.xml are required at runtime as they describe the plug-in. These were
added to the list in Chapter 4, "The Hyperbola Application," when the project was first created.
Figure 9-1. Binary build specification
In Chapters 5, "Starting the Hyperbola Prototype," and 8, "Branding Hyperbola," you added an
icons directory, the splash.bmp file, the about.html file and the plugin.properties fileall of
which are runtime artifacts. Update the build specification for the Hyperbola plug-in by checking
these resources in the list. If a resource is not checked, it is not included in the output.
Tip
Failure to correctly set up the Binary Build list is a very common source of errors.
Typically, the plug-in works fine when run from the workspace, but when exported,
various images, text messages, and other elements are missing. If this happens to
you, first check the Binary Build list.
To start off, find the hyperbola.product file in the Package Explorer or Navigator. Right-click
and choose Export... > Eclipse product. Alternatively, open the product editor, select the
Overview page, and click on the Product Export wizard link. Either way, you should see the
product export wizard, as shown in Figure 9-2.
Figure 9-2. Product export wizard
First, ensure that the Hyperbola product configuration is selected in the Configuration dropdown. Then, fill in the Root directory, the top-level directory that is embedded in the export
output. For example, it is useful to set this to be the name of your product with the version
number. This way, people can extract the product and it gets laid out on disk in a descriptive
directory structure. For now, use "Hyperbola 1.0" in this field.
Earlier we talked about how the product configuration is an aggregation of information
maintained in several different files. Checking the Synchronization checkbox ensures that the
product configuration is properly synchronized before exporting. This helps avoid any
surprising, and typically quite hard to debug, problems when running the exported product.
Leaving this checked is highly recommended.
Next, pick the Export Destination and set the shape of the export Archive file or Directory.
This setting does not affect the content of the outputit is the same either way. Choose
Directory so you can easily test what you are exporting. Later you can export as an archive to
make Hyperbola easier to distribute. Put the output in any convenient location, but remember
the root directory entered earlier is appended to the location specified here.
Having set the various options, click Finish and PDE starts the export in the background (i.e.,
you can continue using Eclipse while the export completes). First, it compiles the code from the
workspace according to the configuration you described. The export wizard then gathers the
compiled code and required parts of the target and outputs them to the specified location.
When the export is done, c:\Hyperbola 1.0 contains a fully branded Hyperbola that runs
outside the workspace. Navigate to c:\Hyperbola 1.0\hyperbola.exe. Notice that the
executable has a branded icon. Run the executable and enjoy your completed RCP product!
Undoubtedly you will want to share Hyperbola with your friends and co-workers. Go back and
export the product again. This time, specify an Archive file output and mail them the archive.
Cleaning the install
When you run from the workspace, PDE takes care of many details. Once you export
the product and run it directly from the filesystem, PDE is no longer in the loop and
cannot help.
This crops up notably in Eclipse's cache management. Typically, Eclipse keeps a
number of caches to improve startup time and reduce memory footprint. Since most
production installations are not manually modified by users, Eclipse does only
rudimentary cache validation on startup.
During development, however, there are a number of scenarios where previously
installed files are changed without going through the standard channels. For
example, if you export Hyperbola on top of a previous installation, some of the plugin content may change but the plug-in was never "updated" or "uninstalled." As
such, the Runtime does not notice the change. To you it appears as though your
changes are not being picked up.
The easiest way around this is to avoid overwriting or tweaking previous installs.
Failing that, however, you can run Eclipse using the -clean command line argument
or put the osgi.clean=true system property in the product's config.ini or launcher
initialization file.
Running this way during development is useful as it tells Eclipse to flush all of its
caches and rebuild its state. Startup is a little slower, but you are guaranteed to get
the latest information.
9.2. Exporting for Other Platforms
But what about your friends who use different operating or window systems or run on different
hardware? They can't run the Hyperbola you just built because it contains platform-specific
code. To package for other platforms, you either need to run on that platform, which is hard to
manage, or you must have the code for those platforms on your machine. This is a typical
cross-platform development scenario.
The first problem is to acquire all the platform-specific Eclipse code needed for these other
platforms. Fortunately, the Eclipse platform team supplies a delta pack for every release. The
delta pack contains all parts of the Eclipse SDK that are platform-specific. So, for example, if
you are on Windows and want to export Hyperbola for Linux/GTK, you need the delta pack.
Note
The issue of platform-specific code shows up for several plug-ins in the Eclipse
platform. SWT, for example, has considerable amounts of platform-specific code.
Several other plug-ins have platform-specific fragments that deliver natives and Java
code to support optimizations. In some cases (e.g., Resources), the plug-ins work fine
without the platform fragments, but with the fragments they are faster or support
enhanced function.
The CD with this book includes the Eclipse 3.1 delta pack. You can also get it from the Eclipse
downloads site, http://eclipse.org/downloads, by going to the Eclipse Platform downloads page.
Scroll down to the section labeled Eclipse RCP SDK and download the RCP delta pack. It
should be in a file similar to:
eclipse-RCP-3.1-delta-pack.zip
There is only one delta pack since by definition it covers every platform. Get the file from the
download site or the CD and expand it over top of your target to add in the files for the other
platforms.
Note
When you expand the delta pack, you will likely be prompted whether or not to
replace various plug-ins that are already installed. You can safely answer either way,
assuming you got the delta pack version that matches your target version.
All that is left is to tell Eclipse about the new plug-ins and fragments. Update the target
platform definition by going to the Window > Preferences > Plug-in Development >
Target Platform preference page. The target location is already set, so just click Reload. PDE
reloads the plug-ins and fragments from the target location and discovers the new elements
from the delta pack. Check that the list contains several new plug-ins for other platforms and
click OK to dismiss the Preferences dialog.
You may notice some error log entries when PDE reloads the target platform. This is caused by
PDE attempting to reconcile the new target elements but finding that various plug-ins are
missing. This is expected. The delta pack contains platform-specific parts for all of the Eclipse
SDK, not just the RCP.
Note
In Chapter 3, "Tutorial Introduction," we talked about the importance of keeping your
target and development Eclipse installs separate. Using the delta pack is a fine
example that motivates that practice.
If you were using the development Eclipse install as a target, the simple default setup,
the above steps would have added the delta pack to the development install. As a
result, the development environment would be cluttered with extra and irrelevant
plug-ins.
Now your target has all the plug-ins, fragments, and launchers for every platform supported by
Eclipse, but you have to set up your product to use them. Return to the Hyperbola product
editor and switch to the Configuration page. In the Plug-ins and Fragments section, click
Add... and select all the SWT-related elements listed, that is, all elements starting with
"org.eclipse.swt". These are the SWT implementations for all supported platforms. Since SWT is
the only platform-specific part of Hyperbola, that is all you need. Save the product configuration
and open the Export wizard from the Overview page.
Open the Export wizard as before and this time check the Export for multiple platforms
option. Complete the first page of the wizard as before and click Next to get to the Crossplatform export page shown in Figure 9-3. Select the set of platforms for which you want to
export and click Finish.
Figure 9-3. Cross-platform export
The export output goes to archives or directories specific to the related platform. For example,
the Windows output appears in the directory named c:\win32.win32.x86\Hyperbola 1.0.
9.3. Summary
Exporting Hyperbola is effectively the final step in branding the application. Without exporting,
Hyperbola is just a wad of code in your workspace. Exported, Hyperbola becomes a full-fledged,
standalone application. PDE's exporting facilities make it easy to create these fully branded
product packageseven across platforms. Now you have something to send to others.
9.4. Pointers
Chapter 24 describes how to automate this build process.
Chapter 25 details how to sign the output JARs and how to export for use in Java Web
Start scenarios.
The Lopica project (http://lopica.sourceforge.net) contains a wealth of information about
Java Web Start.
The standard Java documentation (http://java.sun.com/j2se/1.4.2/docs) includes
documentation on jarsigner and Java Web Start.
Chapter 10. Messaging Support
Eclipse RCP tooling makes creating RCP applications easythere are wizards and editors that help
automate the error-prone tasks of branding, exporting, and configuring an application. You
could stop here, but the real fun of developing an RCP application is writing the domain-specific
code. Hyperbola is no exception.
These tools and programming constructs are generic; they are not specific to application
domains such as banking, stock trading, streaming video, instant messaging, or Mars space
missions. That's where you come in. You will spend most of your time working on the code for
your domain. Again, this is one of the benefits of Eclipseit is essentially middleware that allows
you to focus on your problems rather than on the infrastructure.
This chapter makes Hyperbola a real instant messaging client. The goal is to get Hyperbola into
a state where you can chat and add in more of the features you expect from an instant
messaging application. As such, this chapter focuses somewhat more on the instant messaging
domain and the details of getting set up to chat rather than generic RCP issues. Specifically, we
show you how to:
Integrate a third-party XMPP library called Smack.
Replace the prototype model with Smack.
Note
There are several places in this chapter that require more code changes or new code
than we can detail in the text. Instead, the sample code for this chapter contains all
the changes, and we essentially take you on a tour of the code and highlight the
points of particular interest. If you have the time, you may want to follow along and
make the changes yourself.
10.1. Integrating a Third-Party Library
Alas, not all Java code is structured as Eclipse plug-ins. There is a vast array of useful Java code
available in standard Java JAR files. Whether it is third-party libraries or in-house legacy
function, reusing this code is extremely attractive and efficient. Fortunately, integrating these
libraries into Eclipse is reasonably straightforward. The process of taking non-Eclipse code
libraries and transforming them into Eclipse plug-ins is referred to as bundling.
10.1.1. Bundling Smack
Writing your own XMPP messaging library is quite a challenge. Fortunately, the folks at Jive
Software (http://jivesoftware.org) wrote and open-sourced their Smack messaging library. We
use this throughout the Hyperbola examples and have found it to be stable and easy to use.
The fine print
When you decide to reuse someone else's code, the first thing to ask yourself is:
"Does the license allow you to repackage it, and what are the exact terms of the
license?" This is true regardless of whether or not you are working on open-source
code or proprietary in-house software. Make sure you read and understand the
license terms completely. You should not assume that just because you can look at
it, you can use it, and just because you can use it, you can ship it.
It's not only what you can do with their code, but how does their license impact
what you can do with your code and what your users can do with your product! You
have to figure out what rights you are giving up by using a given library and how
that affects your future users' use of the software.
First, get Smack from the smack directory on the CD that accompanies this book or from
http://jivesoftware.org/smack/. At the time of this writing, the latest version was 1.5.0. Expand
the archive somewhere convenient.
Create a plug-in project using File > New > Project... > Plug-in Development > Plug-in
from existing JAR archives. On the next page, identify the JARs you want in the plug-in. Click
on Add External... and locate and select both smack.jar and smackx.jar from the Smack 1.5.0
install. Click Next to get the page shown in Figure 10-1.
Figure 10-1. New plug-in project for bundling the Smack libraries
Fill in the project name and plug-in id. As discussed previously, the plug-in id and project name
should match and the plug-in id should be based on the plug-in originator's Java package
naming conventions. Here, the plug-in's code is coming from Jive Software, whose package
naming convention for Smack is org.jivesoftware.smack. Use that as the project name. As you
fill in the project name, the plug-in id is automatically completed by the wizard. Update the
plug-in version to "1.5" to match Smack's version and fill in the provider.
Note
Some see this use of org.jivesoftware.* (i.e., Jive Software's spot in the plug-in id
namespace) and their name as the provider as bad form. Others feel that using your
own id (e.g., org.eclipsercp.smack) and name seems to claim credit for the Smack
code and implies that the code is being forked. There is likely no right answer here.
Eclipse itself does both. The Eclipse teams have tended to use the originator's package
naming (e.g., org.apache.ant) if the code is being included verbatim. If the code is
being modified or adapted, then the Eclipse namespace is used (e.g.,
org.eclipse.tomcat ). The best bet is to ask the originator to deliver her code as
bundles!
When the wizard is finished, you should have a Smack plug-in project that looks like the one in
Figure 10-2.
Figure 10-2. Smack project after creation
The option to unzip the JAR archives is useful if you want to ship the new plug-in as a JAR itself.
Otherwise, the JARs would simply be copied into the plug-in and the plug-in would have to be
shipped as a directory.
Aside from creating the project and exploding the JARs, the wizard generates a MANIFEST.MF file.
The generated file is shown below. Notice the various values you entered are embedded in the
manifest. Notice also there is a list of exported packages. These are all the packages the wizard
discovered in the Smack JAR.
org.eclipsercp.hyperbola/MANIFEST.MF
Bundle-Name: Smack Plug-in
Bundle-SymbolicName: org.jivesoftware.smack
Bundle-Version: 1.5
Bundle-ClassPath: .
Bundle-Vendor: Jive Software
Export-Package: .,
org.jivesoftware.smack,
org.jivesoftware.smack.debugger,
org.jivesoftware.smack.filter,
org.jivesoftware.smack.packet,
org.jivesoftware.smack.provider,
org.jivesoftware.smack.util,
...
The Export-Package header lists those packages available to other plug-ins. Since the main
point of bundling is to make the library classes available to others, exporting them is a good
thing. Note that without the exports, the Smack code would be usable inside the Smack plug-in,
but not visible outside.
Notice that the bundling did not import any of the other files that were included in the Smack
download. If you want the other files in the project, run the import wizard and select the File
system import option. Then browse to the expanded Smack directory and choose the files to
import into the existing Smack plug-in project.
10.1.2. Testing the Bundling
It's a good idea to test your newly bundled Smack just to be sure everything is in order. The
easiest way to do this is to create another plug-in that uses Smack and then ensure that you
can compile against it and run with it. The Smack API is very convenient, so let's just create an
RCP application that sends a message using the new Smack plug-in.
First, create a new plug-in project and call it smack.testing . Make it an RCP application, and on
the templates page, select Hello RCP. This is similar to the procedure you followed in Chapter
4, "The Hyperbola Application," to create the Hyperbola shell. When the wizard is done, the
smack.testing plug-in editor is left open. On the Dependencies page, add
org.jivesoftware.smack to the Required Plug-ins list and save the file.
Dynamic classpaths
One of the advantages of defining dependencies at the plug-in level is that it allows
Eclipse tooling to manage your classpaths. Here you added Smack to the
smack.testing plug-in's required list and automatically the content of the Smack
plug-in was available on the smack.testing plug-in's classpath. The required plugins need not even be in the workspacePDE and JDT take care of it for you.
You have already been working in this mixed mode throughout the previous
chapters. The Hyperbola plug-in depends on the org.eclipse.core.runtime plug-in,
but that plug-in has always been in your target. The Hyperbola plug-in compiles
because it said it required the Runtime. PDE hides the details.
If at some point you check the Runtime out of CVS and into your workspace (e.g., to
fix a bug or prototype a new feature), PDE updates the classpath and Hyperbola
continues to compile and run.
We don't really care about a UI for our little test, so open the generated Application class and
replace the run() method with the little Smack example below:
smack.testing/Application
public Object run(Object args) throws Exception {
try {
XMPPConnection con = new XMPPConnection("eclipsercp.org");
con.login("reader", "secret",
Long.toString(System.currentTimeMillis()));
Chat chat = con.createChat("[email protected]");
chat.sendMessage("Hi There!");
Message message = chat.nextMessage(5000);
System.out.println("Returned message: "
+ (message == null ? "<timed out>" : message.getBody()));
} catch (XMPPException e) {
e.printStackTrace();
}
return IPlatformRunnable.EXIT_OK;
}
As you type the code, notice that code completion works and classes are resolved by the Java
editor. This is a good signthe smack.testing plug-in can see the Smack classes. Ultimate proof
comes when you save the Application class and it compiles without errors.
To run the test, right-click on the project and from the context menu, select Run As > Eclipse
Application. If all goes well, the test code connects to the eclipsercp.org chat server as the
user "reader" and sends a message to "eliza".
Eliza is actually a chat robot that responds to your messages in various ways. You should see
her response in the regular Eclipse Console view. Depending on your UI setup, you may have to
open it using Window > Show View > Other... > Basic > Console.
Returned message: HI THERE!
Here, Eliza has simply changed your message to uppercase and sent it back. Even if you get a
timeout, the test has proven itselfyou were able to write a plug-in that compiled and ran with
the bundled Smack library.
Bundling Smack was relatively easy. Other libraries may be more complicated depending on
what they are doing and how they work. The typical problems encountered revolve around
Eclipse's strong notion of component, classloading, and how the classpath is managed. Many
libraries assume that all classes are available on the classpath, but Eclipse puts constraints on
what classes are visible and from where. The most common programming patterns that can
cause problems when bundling are the following:
Does the code use reflection, Class.forName(String), or class references such as
Foo.class?
Does the library use the context classloader?
Does it make assumptions about seeing classes from the Java Extensions classloader?
There are also some general issues to consider when bundling existing libraries:
Are the original JARs signed and is the signature important?
Does the license allow you to repackage?
Can multiple JARs be combined and still work? For example, are there overlapping
package structures that rely on classpath ordering?
If you are having problems bundling librariesfor example, you are seeing a lot of
ClassNotFoundExceptions and NoClassDefFoundErrorssee Chapter 20, "Integrating Code
Libraries," for instructions on how to fix these problems.
10.2. Refactoring the Model
It's time to refactor Hyperbola to use Smack rather than the prototype model. Doing this
requires quite a number of minor but pervasive changes to accommodate new class and method
names. Rather than detailing each of these changes, we take this opportunity to step back and
show you how to use more Eclipse tooling, for example, code completion, organized imports,
and refactoring. We also take you on a tour of the final code to highlight some of the
transformation.
With the information provided here, it is quite feasible for you to do the refactoring yourself. Of
course, if you would rather skip to the final code, you can compare against the sample code for
this chapter.
10.2.1. Introduction to Smack
Now that you have a Smack plug-in, let's look at the Smack APIs. Smack and XMPP are based
on a few very simple concepts. The XMPP protocol is an IETF standard for instant messaging
and presence awareness. The basic idea is that a client connects to a server. The server
manages a list of contacts for each user and routes chat messages from one user to another.
Messages are sent to and from the server in packets that are simple blocks of XML. There are
only three basic message types: <message/>, <presence/>, and <iq/>. For example, the
following XMPP message is sent by a client to indicate a presence change to "away" or that the
user has been idle for some time. The details of the markup are not particularly interesting
except to say that the protocol is extensible and packets can have subtypes. The XMPP
specification process includes a number of Jabber [1] Enhancement Proposals (JEPs) that seek to
add all manner of functionality such as file transfer.
[1] "Jabber" is the historical name for XMPP.
<presence>
<c
node="http://exodus.jabberstudio.org/caps"
ver="0.9.0.0"
xmlns="http://jabber.org/protocol/caps"/>
<show>away</show>
<priority>1</priority>
</presence>
Of course, no one really wants to write code to parse XML and manage streams of packets.
Smack does all the message parsing and stream management for you. It also provides clients
with APIs that hide the XMPP implementation details.
Smack provides helpful classes that take care of listening to common packets and maintain
models of many of the basic messaging concepts such as chats (Chat), the contacts list
(Roster), individual contacts (RosterEntry), groups of contacts (RosterGroup), and connections
to the server (XMPPConnection). It's pretty easy to do things with Smack without needing
intimate knowledge of XMPPthat is why we like it!
The typical workflow for a Smack client is to connect to the server and then send and receive
messages while listening to packets sent and received using PacketListeners. The following code
snippet illustrates how to use a PacketListener to look for incoming chat requests and tell the
chat editor when one arrives.
org.eclipsercp.hyperbola/ApplicationWorkbenchAdvisor
XMPPConnection connection = session.getConnection();
if (connection != null) {
PacketListener listener = new PacketListener() {
public void processPacket(Packet packet) {
Message message = (Message) packet;
if (message.getType() == Message.Type.CHAT)
startChat(message);
}
};
PacketFilter filter = new PacketTypeFilter(Message.class);
connection.addPacketListener(listener, filter);
}
The packet listener is added to the connection for a particular server. It is triggered when a
message arrives from that server. This is a standard pattern in Smack: register a listener and
do something as a result of an incoming or outgoing message.
Another common example is listening for presence and contact list changes. In XMPP, the server
manages your contacts list. Changes to the list or the state of those in the list are sent to the
client by the server. As such, you can use a PacketListener to track the state of the contact list
and the status of your contacts.
10.2.2. Design Objectives
When the Hyperbola prototype was created, the hope was that by decoupling the UI from the
domain logic, we could ignore the details of messaging and eventually replace the prototype
model with a real messaging library without major changes. Indeed, the Smack domain model
is close to that of the original prototype. For example, in the prototype there are three main
classes called Contacts , ContactsGroup , and ContactsEntry ; in Smack, the equivalent classes
are called Roster, RosterGroup, and RosterEntry.
There are two approaches to integrating the Smack infrastructure into Hyperbola:
Proxy all the Smack classes behind the existing prototype model and try to run without
any changes to the actions and UI.
Delete the prototype model and replace it with Smack directly.
The proxy approach is attractive as it isolates the changes. Unfortunately, it creates duplicate
classes and overhead for keeping the proxies synchronized with the Smack classes. It is an
interesting approach if you have the requirements to support different XMPP librariesthat is not
the case here. So instead, the Hyperbola model is replaced entirely by that of Smack. The
Session class is retained as it is still useful and does not have a Smack equivalent.
10.2.3. Deleting Prototype Classes
To start the refactoring, add a dependency from the Hyperbola plug-in to the newly created
org.jivesoftware.smack plug-in. This is what you did with the smack.testing plug-in a little
earlier via the plug-in editor's Dependencies page. Be sure to include the
org.jivesoftware.smack plug-in in the Hyperbola product by adding it to the Plug-ins list on
the Configuration page of the product editor.
Now that the Smack classes are available in Hyperbola, it's time to change the code. The
general strategy for the refactoring is to do all the following steps on each model class in turn:
1. Rename a model class to match the simple name of the related Smack class.
2. Delete the renamed model class.
3. Go to each file that has errors and organize the imports and fix-up methods that changed
names or signatures.
Renaming and deleting are relatively easy. In the Package Explorer, right-click on the
Contacts class and select Refactor > Rename... You are prompted for a new name and given
the option to Update references. Ensure that this option is selected. You can also select some
of the other update options, but they are not particularly needed here. Click OK and the tooling
renames the class and updates all references. Use this to rename Contacts , ContactsEntry , and
ContactGroup to Roster, RosterEntry, and RosterGroup, respectively. You need to also rename
IContactsListener to RosterListener. The Chat and Presence classes have equivalent names in
Smack, so you can leave them alone. Note you do not need to change the package name here.
This step serves to update all the references to use the new short class names.
Tip
When "following the little red x's," it is convenient to use "Ctrl+." to cycle between
lines with compilation errors in the Java editor. Typically this is much easier than
scrolling the file manually.
Next, delete all the renamed model classes except for Session. The Session class is still needed
and is augmented with a ConnectionDetails class to track information about the user logged in.
Then, start working through the compile errors. For example, open the AddContactAction class
and use Source > Organize Imports to fix the import list. Alternatively, click on the project or
package in the Package Explorer and select the same operation to organize imports in all
classes in the project or package. Then look at the remaining errors and fix the method calls. In
general, the changes are just updates to referenced method names. For example, the snippet
below shows the AddContactAction class' run() after being updated:
org.eclipsercp.hyperbola/AddContactAction
public void run() {
Object item = selection.getFirstElement();
if (item instanceof RosterGroup) {
RosterGroup group = (RosterGroup) item;
AddContactDialog d = new AddContactDialog(window.getShell());
int code = d.open();
if (code == Window.OK)
try {
Roster list = Session.getInstance().getConnection().getRoster();
String user = d.getUserId() + "@" + d.getServer();
String[] groups = new String[] { group.getName() };
list.createEntry(user, d.getNickname(), groups);
} catch (XMPPException e) {
// handle
}
}
}
Once the refactoring is done, you are left with just Session in the original model packageSmack
does not have an equivalent class. Smack does have an XMPPConnection class that manages the
notion of server connections, but it connects to the server in its constructor. This makes it
impossible to model connections before they are actually connected. By adding a connection
field to Session, a session can simply have a null connection until the user has logged in.
For convenience, we also added a simple ConnectionDetails class as a data structure to store
login information.
10.2.4. Adding Chats
Smack provides an object that models chats. To expose this in the UI, ChatEditors need to hook
into Smack such that new chats cause an editor to be opened and incoming messages are
directed to the correct ChatEditor.
Previously when we were introducing the PacketListeners, we showed an example of hooking a
listener that calls startChat(Message) for all incoming chat messages. The following snippet
shows the implementation of startChat(Message) and the logic for directing messages.
org.eclipsercp.hyperbola/ApplicationWorkbenchAdvisor
private void startChat(final Message message) {
String user = StringUtils.parseBareAddress(message.getFrom());
Chat chat = session.getChat(user, false);
if (chat != null)
return;
IWorkbench workbench = getWorkbenchConfigurer().getWorkbench();
workbench.getDisplay().asyncExec(new Runnable() {
public void run() {
openChatEditor(message);
}
});
}
private void openChatEditor(Message message) {
IWorkbenchPage page = findPageForSession(session);
if (page != null) {
String user = message.getFrom();
ChatEditorInput editorInput = new ChatEditorInput(session, user);
try {
ChatEditor editor =
(ChatEditor)page.openEditor(editorInput, ChatEditor.ID);
editor.processFirstMessage(message);
} catch (PartInitException e) {
e.printStackTrace();
}
}
}
The code first looks for a Chat object matching the sender of the current message. If one is
found, a chat already exists and the related ChatEditor is already listening for messages. No
further action is needed since the editor gets its own notification and has a chance to display the
message.
If, however, this is a new chat, we have to open a new ChatEditor, as shown in
openChatEditor(Message) . Note that since the new editor will have missed the first messageit
didn't exist and so was not listeningwe have to prime it with the first message.
Note
There is an important pattern shown in startChat(). All Eclipse UI drawing and
interaction must take place on the UI thread. So, when a method can be run on any
thread, you must ensure that any UI-related code is wrapped in either
Display.asyncExec(Runnable) or Display.syncExec(Runnable). Here the startChat()
is called when an XMPP packet is received and there are no guarantees about the
current thread. This pattern is very common in Eclipse applications using SWT.
10.3. Updating the UI
In Chapter 5, "Starting the Hyperbola Prototype," we saw that TReeViewers use content and
label providers to determine what elements to show and how to show them. The setup there
was quite straightforward:
The chat model objects implemented IAdaptable.
Hyperbola registered an adapter factory that produced different IWorkbenchAdapters for
the different chat objects.
The treeViewer was configured with standard Workbench content and label providers that
knew how to use instances of IWorkbenchAdapter to produce the information needed by the
treeViewer.
Now that we have moved to Smack model objects such as RosterEntry and RosterGroup, we no
longer have IAdaptables. These classes cannot be modified to implement IAdaptable or extend
PlatformObject as we did in the chat model. While it is still possible to map Smack model
objects to IWorkbenchAdapter, the standard TReeViewer and provider infrastructure expect
IAdaptables instances. In the next section, we replace the standard providers with something
more flexible.
Before doing that, let's update the adaptor factory from Chapter 5, "Starting the Hyperbola
Prototype," to adapt instances of RosterEntry and RosterGroup to IWorkbenchAdapter. While
we're at it, we'll change the adapter factory to be registered declaratively using extensions
rather than registering it programmatically as we did before. The snippet below shows the XML
markup that identifies the HyperbolaAdapterFactory class as being able to supply
IWorkbenchAdapters for the new Smack RosterGroup and RosterEntry model objects. This also
highlights the fact that the factories and types being adapted need not be in the same plug-in.
org.eclipsercp.hyperbola/plugin.xml
<extension
point="org.eclipse.core.runtime.adapters">
<factory
adaptableType="org.jivesoftware.smack.RosterGroup"
class="org.eclipsercp.hyperbola.HyperbolaAdapterFactory">
<adapter type="org.eclipse.ui.model.IWorkbenchAdapter"/>
</factory>
<factory
adaptableType="org.jivesoftware.smack.RosterEntry"
class="org.eclipsercp.hyperbola.HyperbolaAdapterFactory">
<adapter type="org.eclipse.ui.model.IWorkbenchAdapter"/>
</factory>
</extension>
10.3.1. The Content Provider
Now that we have Smack model objects adapted to be IWorkbenchAdapters , we should be able
to use the default providers to populate the Contacts treeViewer with RosterGroups and
RosterEntrys. Unfortunately, the Workbench's built-in content and label provider classes require
instances of IAdaptable, not just objects that can be adapted to IWorkbenchAdapter. This is a
quirk of the implementation, not a design point. To work around this, we have to implement our
own providers. Luckily this is reasonably easy.
The required content provider simply implements four methods that map almost directly onto
the IWorkbenchAdapter methods. Go ahead and create the content provider as shown below.
Notice that this content provider is completely generic and does not mention anything about
Smack, chats, or Hyperbola. It just interprets IWorkbenchAdapters as needed by treeViewers.
org.eclipsercp.hyperbola/HyperbolaContentProvider
public class HyperbolaContentProvider implements ITreeContentProvider {
protected IWorkbenchAdapter getAdapter(Object element) {
IWorkbenchAdapter adapter = null;
if (element instanceof IAdaptable)
adapter = (IWorkbenchAdapter) ((IAdaptable) element)
.getAdapter(IWorkbenchAdapter.class);
if (element != null && adapter == null)
adapter = (IWorkbenchAdapter) Platform.getAdapterManager()
.loadAdapter(element, IWorkbenchAdapter.class.getName());
return adapter;
}
public Object[] getChildren(Object element) {
IWorkbenchAdapter adapter = getAdapter(element);
if (adapter != null)
return adapter.getChildren(element);
return new Object[0];
}
public Object[] getElements(Object element) {
return getChildren(element);
}
public Object getParent(Object element) {
IWorkbenchAdapter adapter = getAdapter(element);
if (adapter != null)
return adapter.getParent(element);
return null;
}
public boolean hasChildren(Object element) {
return getChildren(element).length > 0;
}
}
10.3.2. The Label Provider
The label provider is a little more complex because it has to manage images, but as you can see
from the snippet below, it otherwise follows exactly the same pattern as the content provider.
Notice again that this provider is generic.
org.eclipsercp.hyperbola/HyperbolaLabelProvider
public class HyperbolaLabelProvider extends LabelProvider {
private Map imageTable = new HashMap(7);
protected IWorkbenchAdapter getAdapter(Object element) {
IWorkbenchAdapter adapter = null;
if (element instanceof IAdaptable)
adapter = (IWorkbenchAdapter) ((IAdaptable) element)
.getAdapter(IWorkbenchAdapter.class);
if (element != null && adapter == null)
adapter = (IWorkbenchAdapter) Platform.getAdapterManager()
.loadAdapter(element, IWorkbenchAdapter.class.getName());
return adapter;
}
public final Image getImage(Object element) {
IWorkbenchAdapter adapter = getAdapter(element);
if (adapter == null)
return null;
ImageDescriptor descriptor = adapter.getImageDescriptor(element);
if (descriptor == null)
return null;
Image image = (Image) imageTable.get(descriptor);
if (image == null) {
image = descriptor.createImage();
imageTable.put(descriptor, image);
}
return image;
}
public final String getText(Object element) {
IWorkbenchAdapter adapter = getAdapter(element);
if (adapter == null)
return "";
return adapter.getLabel(element);
}
public void dispose() {
if (imageTable != null) {
for (Iterator i = imageTable.values().iterator(); i.hasNext();)
((Image) i.next()).dispose();
imageTable = null;
}
}
}
10.4. Chatting with Eliza
Now that Hyperbola has a real messaging library behind it, exercising it gets a little more tricky.
You need to have a real messaging server and someone to chat with. When you tested the
bundled Smack, you connected to eclipsercp.org as "reader" and then chatted with
[email protected], a robot chat agent. Let's do the same thing with Hyperbola.
Open the Application class and modify run(Object) to call a login method before starting the
Workbench as shown in the snippet below.
org.eclipsercp.hyperbola/Application
public class Application implements IPlatformRunnable {
public Object run(Object args) throws Exception {
Display display = PlatformUI.createDisplay();
try {
final Session session = Session.getInstance();
if (!login(session))
return IPlatformRunnable.EXIT_OK;
int returnCode =
PlatformUI.createAndRunWorkbench(display,
new ApplicationWorkbenchAdvisor());
if (returnCode == PlatformUI.RETURN_RESTART)
return IPlatformRunnable.EXIT_RESTART;
return IPlatformRunnable.EXIT_OK;
} finally {
display.dispose();
}
}
private boolean login(Session session) {
try {
ConnectionDetails d =
new ConnectionDetails("reader", "eclipsercp.org", "secret");
XMPPConnection con = new XMPPConnection(d.getServer());
con.login(d.getUserId(), d.getPassword(), d.getResource());
session.setConnection(con);
session.setConnectionDetails(d);
} catch (XMPPException e) {
return false;
}
return true;
}
}
Add the login(Session) method as shown. This is basically the same code from when you were
testing earlier. The code opens the connection, stashes it and the connection details in the given
session, and returns. If the login fails, false is returned and Hyperbola exits.
Now start the Hyperbola application. You are automatically logged onto the server as "reader."
Hyperbola shows you the contacts list that contains only one entry, "eliza." Select that entry
and use Hyperbola > Chat to start chatting with Eliza, as shown in Figure 10-3.
Figure 10-3. Chatting with Eliza
That's it. The Hyperbola application is now a functioning chat client.
10.5. Summary
There were a lot of details covered in this chapter. Although most of it was specific to the
Hyperbola application and XMPP, it's worthwhile to step back and see how the experiences
apply more generally to your RCP applications.
If you are going to use non-Eclipse libraries, they need to be bundled into Eclipse plug-ins.
Bundling the libraries does two things: describes the library to other plug-ins (e.g., what
classes are available at runtime) and adds these classes to the build path. See Chapter 20
for more details on bundling.
We can't say this enough: Separate the UI from the domain model. Build actions that
manipulate the model and UIs that update accordingly. This makes your application easier
to test and evolve. Many applications take this one step further and split the UI classes
from the domain model classes into separate plug-ins. This is a popular pattern with
Eclipse platform plug-ins, where related plug-ins are split and post-fixed with "core" and
"ui" to differentiate them. We have not done this here just to keep things simple. The
examples in Chapter 23, "RCP Everywhere," go into plug-in structuring in great detail.
Your domain model is important and is something that is not addressed by Eclipse. Eclipse
does, however, provide frameworks such as the extension registry and reusable UI
components that make it easier to quickly get a product-quality application running. As we
mentioned at the outset, this is the power of the Eclipse RCP. It is all the middleware code
that you need to have but don't really want to write.
If you had trouble following along with all the code changes, import the code for this chapter
and browse it to see the complete picture of changes that occurred.
Chapter 11. Adding a Login Dialog
At the end of Chapter 10, "Messaging Support," you hard-coded a login server and account so
that you could test the newly enabled Hyperbola prototype. That was great for testing, but
clearly not realistic. In this chapter, we walk through the creation and use of a login dialog and
configuring Hyperbola to automatically log you in when it is started. In addition, we talk about
how to:
Display a login dialog before the Workbench starts.
Ensure that the Hyperbola application's branding icon is shown on the dialog.
Manually take down the splash screen before showing the login prompt.
Add a preference page.
Remember login preferences.
11.1. Adding the Login Dialog
The first thing to decide when adding a login prompt to an application is at what point in the
application's lifecycle the prompt should actually appear. In this iteration of Hyperbola, we want
to be logged in as soon as possibleeven before the first Workbench window is shown. This has a
number of advantages: it keeps the rest of the code simple since the connection information is
assumed to be available and the connection open; the application can check licensing
information; plus, the application can configure itself based on the logged-in user. For example,
the name of the logged-in user can be used to determine what the UI looks like, what actions
are available, and so on.
The first place that the Hyperbola application gets control is in the Application method
run(Object). This method simply creates an SWT Display and opens the Workbench using that
Display. We want to prompt after creating the Display but before starting the Workbench. Note
that there is no magic herethe run() method is not special. You are free to do as much UI work
as you like before creating the Display or starting the Workbench. The only restriction is that
you have to create the Display before anything can be drawn.
Note
Think of the Display as SWT's model. When a Display is created, it does not mean
that a window or dialog has been opened. The Display is the single point of contact
for all UI capabilities of SWT. It provides capabilities such as running the event loop,
inter-thread communication, timers, fonts, and colors.
At the end of Chapter 10, you modified the Application's run(Object) method to call
login(Session). Now you have to change the login() method to prompt the user instead of
automatically connecting with hard-coded connection details. You can write the login dialog
yourself or you can use the BasicLoginDialog class from the sample code for this chapter. You
may notice another login dialog, LoginDialog, in the code for this chapter. This is the final
version of the dialog as it appears at the end of the chapter. We included the BasicLoginDialog
to help you get started. If you get the BasicLoginDialog from the code for this chapter, rename
it to LoginDialog.
Modify the login(Session) method to open the login dialog as shown below. Notice that
whenever a dialog is shown before the first Workbench window is opened, it should not be
parented. Here the BasicLoginDialogs constructor takes a null argument. Dialogs without
parents are managed by the windowing system, and as such, have an entry in the task bar. This
means that users can tell there is a dialog open and can easily bring it to the front if needed.
org.eclipsercp.hyperbola/Application
private boolean login(final Session session) {
while (session.getConnection() == null ||
!session.getConnection().isAuthenticated()) {
LoginDialog loginDialog = new LoginDialog(null);
if (loginDialog.open() != Window.OK)
return false;
session.setConnectionDetails(loginDialog.getConnectionDetails());
connectWithProgress(session);
}
return true;
}
The method login(Session) loops forever, prompting the user for login information and trying
to log in using that information. The details of the LoginDialog are not particularly important
here. It suffices to say that if the user clicks OK in the dialog, the login code gets the required
information and attempts to log in by calling the connectWithProgress(Session) method. If the
user clicks Cancel, then the method returns and Hyperbola exits. If the login attempt fails, the
user is prompted again.
Progress reporting during login is vital. In particular, during login, you cannot be sure how long
it will take to contact the remote server. Users appreciate feedback about the status of the login
and expect the opportunity to cancel if it is taking too long.
As part of the refactoring in Chapter 10, a new login method that tracks progress during the
initial connection and login was added to Session. The technique for reporting progress is to
add an XMPP packet listener to the connection. The Session reports progress on an
IProgressMonitor and checks for cancellation as packets are sent.
It's a good habit to ensure that long-running methods have some sort of feedback. In
Hyperbola, the only thing we can tell the user is whether he or she is authenticating or receiving
the roster from the server. The user can cancel between these operations.
org.eclipsercp.hyperbola/Session
public void connectAndLogin(final IProgressMonitor monitor)
throws XMPPException {
PacketListener progressPacketListener = new PacketListener() {
public void processPacket(Packet packet) {
if (monitor.isCanceled())
throw new OperationCanceledException();
String message = null;
if (packet instanceof Authentication)
message = "Authenticating...";
if (packet instanceof RosterPacket)
message = "Receiving roster...";
if (message != null)
monitor.subTask(message);
}
};
try {
monitor.beginTask("Connecting...", IProgressMonitor.UNKNOWN);
String server = connectionDetails.getServer();
monitor.subTask("Contacting " + server);
connection = new XMPPConnection(server, 5222);
connection.addPacketWriterListener(progressPacketListener,
new OrFilter(new PacketTypeFilter(Authentication.class),
new PacketTypeFilter(RosterPacket.class)));
connection.login(connectionDetails.getUserId(),
connectionDetails.getPassword(),
connectionDetails.getResource());
} finally {
if (connection != null)
connection.removePacketWriterListener(progressPacketListener);
monitor.done();
}
}
The caller of Session.connectAndLogin(IProgressMonitor)the Application, in our caseprovides
a monitor and displays the progress to the user. There's an existing dialog called
ProgressMonitorDialog that shows progress that can be used for this purpose. The
connectWithProgress(Session) method called from the application opens a progress dialog,
then connects using the Session, as shown below:
org.eclipsercp.hyperbola/Application
private void connectWithProgress(final Session session) {
ProgressMonitorDialog progress = new ProgressMonitorDialog(null);
progress.setCancelable(true);
try {
progress.run(true, true, new IRunnableWithProgress() {
public void run(IProgressMonitor monitor)
throws InvocationTargetException {
try {
session.connectAndLogin(monitor);
} catch (XMPPException e) {
throw new InvocationTargetException(e);
}
}
});
} catch (InvocationTargetException e) {
} catch (InterruptedException e) {
}
}
Now when you run Hyperbola, it should appear as shown in Figure 11-1. Enter the user
information used in Chapter 10, that is, the user is "reader", the server is "eclipsercp.org", and
use "secret" as the password. Click Login and you should once again be able to chat with Eliza.
Of course, now you can log into your own XMPP account and chat with your friends if you like.
Figure 11-1. Login dialog
You might have noticed that while the login dialog was up, the Hyperbola splash screen was
showing. That seems a bit strange. Splash screens are there to reassure the user that
something is happening. Once the Hyperbola application is up and interacting with the user, the
splash screen is no longer needed. You can fix that by adding the following code just before
calling login(Session) in Application.run(Object) :
org.eclipsercp.hyperbola/Application
Platform.endSplash();
Note that the Workbench automatically closes the splash screen, if it is still up, when the startup
sequence is completed. Once the splash screen is down, it cannot be redisplayed.
Note
It's actually possible to run code before the application is run. If you need to perform
more advanced configuration, such as determining which plug-ins are loaded, see
Chapter 26, "OSGi Essentials," for information about starting plug-ins from within the
config.ini file using start levels.
11.1.1. Branding the Dialog
The best practice of setting the dialog's parent to null means that it does not inherit the icon
set by the Workbench on the top-level window. Actually, there isn't even a Workbench or a toplevel window. As a result, the Hyperbola icon is missing from the login dialog and the task bar
entry, as shown in Figure 11-2.
Figure 11-2. Task bar and dialog missing the Hyperbola icon
The quick fix for this is to directly access a Hyperbola icon and call Dialog.setImage(Image).
Since you've already configured the product icon via the product configuration, a more elegant
solution is to use the icon from the Hyperbola product description. This isolates the login dialog
from changes to the product's branding.
Every running Eclipse has at most one productthe IProduct returned from
Platform.getProduct(). The product contains all of the information provided in the related
extension to the org.eclipse.runtime.product extension point. Of course, getProduct() can
return null since you can run an Eclipse application directly without having a product.
Most of the information related to a product comes as free-form key/value pairs. Eclipse defines
some canonical keys such as the application name, the about text, and so on in
org.eclipse.ui.branding.IProductConstants. The IProductConstants.WINDOW_IMAGES property
is of particular interest here. The value is a comma-separated list of image paths for different
sized images that can be used to represent product windows. Update the LoginDialog method
configureShell(Shell) to get the window images and use them for the dialog, as shown in the
snippet below:
org.eclipsercp.hyperbola/LoginDialog
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText("Hyperbola Login");
IProduct product = Platform.getProduct();
if (product != null) {
String bundleId = product.getDefiningBundle().getSymbolicName();
String[] imageURLs = HyperbolaUtils.parseCSL(
product.getProperty(IProductConstants.WINDOW_IMAGES));
if (imageURLs.length > 0) {
images = new Image[imageURLs.length];
for (int i = 0; i < imageURLs.length; i++) {
ImageDescriptor descriptor =
AbstractUIPlugin.imageDescriptorFromPlugin(
bundleId, imageURLs[i]);
images[i] = descriptor.createImage(true);
}
newShell.setImages(images);
}
}
}
public boolean close() {
if (images != null) {
for (int i = 0; i < images.length; i++)
images[i].dispose();
}
return super.close();
}
This code is doing more than you think. In particular, the AbstractUIPlugin static method
imageDescriptorFromPlugin(String, String) isolates you from the form of the image paths.
Contributed image paths can be a fully qualified uniform resource locator (URL) to the image or
a relative path from within the declaring product plug-in. Here is an example of a fully qualified
window image URL:
org.eclipsercp.hyperbola/plugin.xml
<property
name="windowImages"
value="platform:/plugin/org.eclipsercp.hyperbola.ui/icons/alt16.gif"/>
Whereas this example shows the value as a relative image path:
org.eclipsercp.hyperbola/plugin.xml
<property
name="windowImages"
value="icons/alt16.gif"/>
Most of the images and icons contributed to UI extension points can take either relative or
absolute URLs. This is useful as it allows you to put all your images and branding in one spot
and then refer to it from wherever it is needed. Note, however, that if you are forcing or
expecting other plug-ins to access your image, this effectively makes the image locations and
names APIif you change them, the other plug-ins break.
11.2. Remembering Login Settings
If you have been following along and testing Hyperbola, you are likely tired of typing the same
login information every time. These login settings should be saved between invocations of
Hyperbola. In this section, we augment the LoginDialog to show a list of users and also save
the list of users and their information for next time.
11.2.1. The Basics
You could just remember the last set of login information, but chat users often have several
different identities. To accommodate this, Hyperbola needs a login dialog, as shown in Figure
11-3. The dialog has a list of user names instead of a single user. When a user name is selected
in the User ID combo box, the Server and Password fields are updated with the information
for that user. Further, if you can add users, you should be able to delete them. The Delete User
button allows users to delete the current account.
Figure 11-3. Improved login dialog
Note
To keep the code snippets concise, many of the details of building the UI and data
structures have been omitted. The most interesting code is how to save and restore
the list of users. Enough context is provided to understand the scope of the changes,
but you should look at the sample code for this chapter for the complete story.
To minimize concurrent code changes, you should stage the implementation of this feature.
First, refactor the dialog to accommodate several connection lists and then add the code to save
and restore the user information. This approach makes it easier to test and pinpoint problems
since you are changing fewer things at once.
The LoginDialog class currently has a single ConnectionDetails field to track the login
information for one user. You must refactor the dialog to support sets of connection details.
Here is an overview of the refactoring:
1. Update the UI to allow selecting from a list of users. Replace the User ID text field with a
combo box.
2. Add listeners to the User ID field to update the Server and Password fields when the
user changes.
3. Update the data structures to track multiple user logins. Replace the ConnectionDetails
field with a Map , called saveDetails, whose keys are user names and values are
ConnectionDetails.
The first code snippet below shows the new savedDetails that tracks the connection information
loaded and saved using the preferences mechanismmore on that a bit later. In addition, the
userIdText field has been changed to a Combo. The code registers a listener on the Combo such
that when the value is changed, the serverText and passwordText values are updated based on
the saved connection information from savedDetails.
org.eclipsercp.hyperbola/LoginDialog
public class LoginDialog extends Dialog {
private Combo userIdText;
private Text serverText;
private Text passwordText;
private ConnectionDetails connectionDetails;
private Map savedDetails = new HashMap();
protected Control createDialogArea(Composite parent) {
...
userIdText = new Combo(composite, SWT.BORDER);
GridData gridData = new
GridData(GridData.FILL, GridData.FILL, true, false);
gridData.widthHint = convertHeightInCharsToPixels(20);
userIdText.setLayoutData(gridData);
userIdText.addListener(SWT.Modify, new Listener() {
public void handleEvent(Event event) {
ConnectionDetails d = (ConnectionDetails)
savedDetails.get(userIdText.getText());
if (d != null) {
serverText.setText(d.getServer());
passwordText.setText(d.getPassword());
}
}
});
The user combo should be initialized with the list of known user names from previous sessions
and with the name used in the last session selected at startup. For the time being, just add a
couple of sample connection details to the savedDetails field. The initializeUsers(String)
method shown below can be called any time after the combo is created, for example, at the end
of createDialogArea(Composite). It may be convenient to use this method in other scenarios to
reset the list of names in the combo.
org.eclipsercp.hyperbola/LoginDialog
protected void initializeUsers(String defaultUser) {
userIdText.removeAll();
passwordText.setText("");
serverText.setText("");
for (Iterator it = savedDetails.keySet().iterator(); it.hasNext();)
userIdText.add((String) it.next());
int index = Math.max(userIdText.indexOf(defaultUser), 0);
userIdText.select(index);
}
Finally, the dialog refactoring is rounded out by adding the Delete User button to the button
bar. When the button is clicked, the current user in the combo is removed from the
ConnectionDetails map and the combo is re-initialized.
org.eclipsercp.hyperbola/LoginDialog
protected void createButtonsForButtonBar(Composite parent) {
Button deleteUser = createButton(parent,
IDialogConstants.CLIENT_ID, "&Delete User", false);
deleteUser.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
savedDetails.remove(userIdText.getText());
initializeUsers("");
}
});
createButton(parent, IDialogConstants.OK_ID, "&Login", true);
createButton(parent, IDialogConstants.CANCEL_ID,
IDialogConstants.CANCEL_LABEL, false);
}
Now you can test the new dialog by initializing the savedDetails map with a set of dummy
users.
org.eclipsercp.hyperbola/LoginDialog
Public LoginDialog(Shell parent) {
super(parent);
savedDetails.put("reader",
new ConnectionDetails("reader", "eclipsercp.org", "secret"));
savedDetails.put("friend",
new ConnectionDetails("friend", "eclipsercp.org", "secret"));
}
When you run the Hyperbola application, you should get the login dialog first. The dialog shows
the sample users and allows you to delete users. Of course, Hyperbola does not remember your
deletions yetthat's next.
11.2.2. Using Preferences
Now that the UI is mocked up, let's think about how to save connections information between
sessions. There are many options for saving the user data from the login dialog. The brute-force
approach is to use Platform.getStateLocation(Bundle) . This gives you a location on the user's
machine where the given plug-in can store any files it likes. This is useful if the plug-in needs to
save large files or already has a persistence story. It is, however, a bit too heavyweight for
storing simple preferences. All you really need here is a way to store a small set of key/value
pairs. An easier approach is to use the preferences mechanism provided by the
org.eclipse.core.runtime plug-in.
Preferences are key/value pairs where the key is an arbitrary name for the preference and the
value is one of several types: boolean, byte[], long, int , String, float, or double. Preferences
are stored and retrieved by the org.eclipse.core.runtime plug-in, so you don't have to be
concerned with how this works. This makes it easier than having to write and read the file
yourself.
Note
Eclipse preferences are very similar to java.utils.prefs.Preferences with additional
support for searching, storing, and scoping. If you have already used Java
Preferences, then Eclipse Preferences should be familiar.
Think of preferences as a hierarchical node structure where each node has a name and a unique
and absolute path, as shown in Figure 11-4. Nodes in a preference tree are named and
referenced in a similar manner as directories and files in a filesystem. The root node is
referenced as "/", and children are referenced as absolute paths followed by the child's name.
Each preference node has zero or more properties associated with it, where a property consists
of a key and a value.
Figure 11-4. Preference node structure
[View full size image]
The direct children of the root node are special and are referred to as scopes. Each scope is the
root for all the preferences in that scope. The structure of the nodes in each scope is specified
by the scope itself. Scopes are an open-ended set that controls the visibility and persistence of
preferences. This means that the scope determines the location on the filesystem where
preferences are stored. For example, in Hyperbola, it may make sense to maintain some
preferences on a per-user basis and some on a global, application basis. The set of scopes is
extensible, but the Eclipse Runtime defines three basic scopes:
Instance scoped Preferences that are stored per workspace, or per running instance of
Eclipse. If your product can run on different data sets, then preferences stored in this type
of scope are not available across the data sets.
Configuration scoped Preferences that are stored per Eclipse configuration. Such
preferences are shared between multiple running instances of an Eclipse configuration.
Configuration scoped preferences are best suited for preferences that apply across the
product regardless of the user or data set.
Default scoped Preferences that represent the default values for preferences. These are
not changed or stored by the Eclipse Runtime, but rather supplied by initialization files in
plug-ins and product definitions.
Most of the preferences in the Hyperbola application are product levelthere is no real data set or
workspaceso we should use the configuration scope. The instance scope could be used,
however, to store preferences specific to particular servers. For example, the user's preferred
chat mode and initial state may vary from server to server.
Scopes are just specially named preference nodes, so the following two lines are equivalent
methods of accessing the configuration scope and getting a preference node called pluginid .
Preferences configurationScope =
Platform.getPreferencesService().getRootNode().
node(ConfigurationScope.SCOPE).node("pluginid");
Preferences preferences = new ConfigurationScope().getNode("pluginid");
Let's return to Hyperbola and store and retrieve the list of connection information using the
configuration scope. The following snippet from the LoginDialog class shows the use of
Preferences to save the connection information.
org.eclipsercp.hyperbola/LoginDialog
private static final String
private static final String
private static final String
private static final String
PASSWORD = "password";
SERVER = "server";
SAVED = "saved-connections";
LAST_USER = "last-connection";
public void saveDescriptors() {
Preferences preferences = new ConfigurationScope()
.getNode(Application.PLUGIN_ID);
preferences.put(LAST_USER, connectionDetails.getUserId());
Preferences connections = preferences.node(SAVED);
for (Iterator it = savedDetails.keySet().iterator(); it.hasNext();) {
String name = (String) it.next();
ConnectionDetails d = (ConnectionDetails) savedDetails.get(name);
Preferences connection = connections.node(name);
connection.put(SERVER, d.getServer());
connection.put(PASSWORD, d.getPassword());
}
try {
connections.flush();
} catch (BackingStoreException e) {
e.printStackTrace();
}
}
The basic pattern when accessing Preferences is to start with the scope and then use your plugin id to isolate your preferences from those of other plug-ins. You can see this in the first line of
saveDescriptors(). The last connection information is stored right on the node for the
Hyperbola application in the configuration scope. This information is used to prime the login
dialog with the previous selection the next time it is shown.
The hierarchical nature of Preferences is used to create a node for all saved connections.
Effectively, this is a folder, just as you would use on the filesystem. In this node, there is a node
for each user. The node name is the user name and its map contains the server name and
password.
Warning
It is generally not a good idea to save passwords in preferences because they are
stored as text and are easily read by anyone who has access to your machine. Since
the Hyperbola application is sending its login information as text, we don't worry
about this for now. If you need to store passwords on the user's machine, you can use
the Platform's basic authentication store, Platform.addAuthorizationInfo(), which
saves passwords in a lightly encrypted file.
When the preferences have been created, calling Preferences.flush() ensures that they are
saved to disk. The connection information is saved when the login dialog is closed. Overriding
the Dialog.buttonPressed() method allows you to run arbitrary code based on the button that
was pressed. It's important to eventually delegate to the overridden method so the standard
behavior, such as closing the dialog, is still performed.
org.eclipsercp.hyperbola/LoginDialog
protected void buttonPressed(int buttonId) {
String userId = userIdText.getText();
String server = serverText.getText();
String password = passwordText.getText();
connectionDetails = new ConnectionDetails(userId, server, password);
savedDetails.put(userId, connectionDetails);
if (buttonId == IDialogConstants.OK_ID ||
buttonId == IDialogConstants.CANCEL_ID)
saveDescriptors();
super.buttonPressed(buttonId);
}
The LoginDialog must also load the preferences when it's created. Loading is a mirror image of
storing, as shown in the LoginDialog snippet below. You simply ask for the same Preferences
node and scan its properties and child nodes. You do not have to explicitly load the preferences
from disk; they are loaded by the Eclipse Runtime as needed.
org.eclipsercp.hyperbola/LoginDialog
private void loadDescriptors() {
try {
Preferences preferences = new ConfigurationScope()
.getNode(Application.PLUGIN_ID);
Preferences connections = preferences.node(SAVED);
String[] userNames = connections.childrenNames();
for (int i = 0; i < userNames.length; i++) {
String userName = userNames[i];
Preferences node = connections.node(userName);
savedDetails.put(userName, new ConnectionDetails(
userName,
node.get(SERVER, ""),
node.get(PASSWORD, "")));
}
connectionDetails = (ConnectionDetails) savedDetails.get(
preferences.get(LAST_USER, ""));
} catch (BackingStoreException e) {
e.printStackTrace();
}
}
11.3. Adding Auto-login Preferences
Even though previously entered login information is now saved, users often appreciate the
option to log in automatically at startup. The Hyperbola application can present this option
directly from the login dialog, as shown in Figure 11-5, or from a Preferences dialog, as shown
in Figure 11-7. Let's do both!
Figure 11-5. Login dialog with the auto-login preference
11.3.1. Creating a Preference Page
Preference pages contain a grab bag of application settings that users normally do not have to
change. They are there to allow specific configuration of your application. Basic support for
preference pages in Eclipse is provided by JFace. This support is extended by the Workbench to
allow contributing preference pages in a plugin.xml. The Workbench also adds helper widgets
useful in building preference pages.
To add a preference page for the auto-login preference, you first need to define a preference
page extension for the Workbench's org.eclipse.ui. preferencePages extension point.
Preference pages contributed this way automatically appear in the Workbench's standard
Preferences dialog. You have added extensions a few times by now, so we will skim over it
quickly here.
Open the Hyperbola plug-in editor and flip to the Extensions page and click Add.... Choose the
org.eclipse.ui.preferencePages extension point and click Finish. That creates the extension.
Right-click on the extension itself and choose New > page. Once you have the page element,
fill in the details as shown in Figure 11-6. For the class field, use the normal trick of clicking on
the class link to create a skeleton for the preference page class.
Figure 11-6. Preference page extension point
All preference pages must implement the interface IWorkbenchPreferencePage by either
subclassing PreferencePage or FieldEditorPreferencePage. The skeleton you created likely
subclassed PreferencePagechange this to subclass FieldEditorPreferencePage. Field editors are
very useful controls that are linked to underlying preferences. FieldEditorPreferencePage has
various helpers for creating FieldEditors.
For the Hyperbola application, all we want to do is display a check box that allows toggling the
auto-login preference. The resulting preference page is shown in Figure 11-7. The following
code snippet shows the source for that preference page.
org.eclipsercp.hyperbola/GeneralPreferencePage
public class GeneralPreferencePage extends FieldEditorPreferencePage
implements IWorkbenchPreferencePage {
public static final String AUTO_LOGIN = "prefs_auto_login";
private ScopedPreferenceStore preferences;
public GeneralPreferencePage() {
super(GRID);
preferences = new ScopedPreferenceStore(
new ConfigurationScope(),Application.PLUGIN_ID);
setPreferenceStore(preferences);
}
public void init(IWorkbench workbench) {
}
protected void createFieldEditors() {
BooleanFieldEditor boolEditor = new BooleanFieldEditor(
AUTO_LOGIN,
"Login automatically at startup",
getFieldEditorParent()
);
addField(boolEditor);
}
public boolean performOk() {
try {
preferences.save();
} catch (IOException e) {
HyperbolaUtils.log(
"Unable to save general preference page preferences", e);
}
return super.performOk();
}
}
Figure 11-7. Standard Preferences dialog
When the preference page is constructed, a ScopedPreferenceStore is created to wrap the
configuration scoped preferences for the Hyperbola plug-in. This is a backwards-compatible
layer that allows using preferences with FieldEditors. The FieldEditor APIs pre-date the use of
the Runtime preferences and thus are not compatible without a helper class.
When the page is drawn, createFieldEditors() is called by the Workbench. The
BooleanFieldEditor here is given the key for the auto-login preference. It then reads and writes
that preference automatically. When the user clicks the Apply button, the preferences are saved
to disk.
11.3.2. Adding the Action
Now that the preference page is defined, you have to add an action that opens the Preferences
dialog. The Workbench provides an action called ActionFactory.PREFERENCES that opens the
standard Preferences dialog and shows all registered preference pages. Update Hyperbola's
ActionBarAdvisor to create and place the preferences action. The snippet below shows the
relevant lines and leaves you to place them accordingly.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
public void makeActions(IWorkbenchWindow window) {
...
preferencesAction = ActionFactory.PREFERENCES.create(window);
register(preferencesAction);
}
protected void fillMenuBar(IMenuManager menuBar) {
MenuManager hyperbolaMenu = new MenuManager("&Hyperbola",
"hyperbola");
...
hyperbolaMenu.add(preferencesAction);
}
Start the Hyperbola application and select Hyperbola > Preferences to see the Preferences
dialog, as shown in Figure 11-7.
11.3.3. Accessing Preferences
To implement auto-login, Hyperbola must be updated to check the preference value and react
accordingly, as shown in the snippet below.
org.eclipsercp.hyperbola/Application
private boolean login(final Session session) {
boolean firstTry = true;
LoginDialog loginDialog = new LoginDialog(null);
while (session.getConnection() == null ||
!session.getConnection().isAuthenticated()) {
Preferences preferences = new
ConfigurationScope().getNode(Application.PLUGIN_ID);
boolean auto_login = preferences.getBoolean(
GeneralPreferencePage.AUTO_LOGIN, true);
ConnectionDetails details = loginDialog.getConnectionDetails();
if (!auto_login || details == null || !firstTry) {
if (loginDialog.open() != Window.OK)
return false;
details = loginDialog.getConnectionDetails();
}
firstTry = false;
session.setConnectionDetails(details);
...
}
11.3.4. Default Preference Values
The preference page is up and working, but there is still one last detail to addressthe default
value of the auto-login preference.
The Workbench provides a default preferences scope called DefaultScope, which contains
default values for preferences. Having a default scope and searching multiple scopes allows you
to have defaults for a preference initialized in the DefaultScope but customized in another
scope.
There is also an extension point called org.eclipse.runtime.preferences that can be used to
register a class that initializes default preference values. The preference initializer ensures that
a plug-in's preferences are initialized before another plug-in can access them.
Let's add a preference initializer for the Hyperbola application that sets the auto-login default to
false. First, add an extension to the org.eclipse.runtime.preferences.initializer extension
point. For the class attribute, create a class called PreferenceInitializer. The implementation
for this preference initializer is shown below. Eclipse ensures that the preference initializer is
run before any preferences are accessed in the node that matches this plug-in's id. This implies
that each plug-in is storing its preferences under a node with its plug-in id.
org.eclipsercp.hyperbola/PreferenceInitializer
public class PreferenceInitializer extends
AbstractPreferenceInitializer {
public PreferenceInitializer() {
super();
}
public void initializeDefaultPreferences() {
IEclipsePreferences defaults = new
DefaultScope().getNode(Application.PLUGIN_ID);
defaults.putBoolean(GeneralPreferencePage.AUTO_LOGIN,
false);
}
}
11.3.5. Preferences on the Login Dialog
Although the auto-login preference is available in the Preferences dialog, it's convenient to also
allow the user to set this preference in context, for example, when he is using the login dialog.
This is very easy to add. In the LoginDialog method createDialogArea(Composite), create an
extra check box that is initialized with the preference value. Then, when the state of the check
box changes, update the preference value, as shown in the following snippet:
org.eclipsercp.hyperbola/LoginDialog
final Button autoLogin = new Button(composite, SWT.CHECK);
autoLogin.setText("Login &automatically at startup");
autoLogin.setLayoutData(
new GridData(SWT.BEGINNING, SWT.CENTER, true, true, 2, 1));
autoLogin.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
IEclipsePreferences prefs =
new ConfigurationScope().getNode(Application.PLUGIN_ID);
prefs.putBoolean(
GeneralPreferencePage.AUTO_LOGIN,
autoLogin.getSelection());
}
});
Preferences preferences =
new ConfigurationScope(). getNode(Application.PLUGIN_ID);
boolean auto_login = preferences.getBoolean(
GeneralPreferencePage.AUTO_LOGIN, true);
autoLogin.setSelection(auto_login);
11.4. Summary
The login dialog greatly improves the usability of Hyperbola. Here you learned how to add
preferences to your application. Notice that again most of the hard work is taken care of by
Eclipse. The Eclipse RCP is full of frameworks for adding standard elements, such as
preferences, to your application. Further chapters highlight additional facilities, but there are
too many for us to cover them all. The online Eclipse help is a good source of information on the
ones we omit.
Testing with other servers
Chatting with Eliza is fun, but it's better to set up your own accounts and chat with
your friends. To do this, you must find a server on which to create an account.
Jabber.org lists a number of public Jabber (XMPP) servers in addition to running one
itself. See http://jabber.org/network for the full list. This is the easiest path. Pick a
server, sign up for an account, and chat away.
Note: You should be aware that the XMPP and Jabber protocols share ancestry
but have some subtle incompatibilities. As a result, you may experience some
glitches when using the Hyperbola application and Smack with some servers.
Typically, this is in the more advanced or fringe areas.
Chatting with Eliza is also good for testing, but may not be convenient for people on
the road, behind firewalls, etc. The alternative is to install and run a server on your
machine. There are several free XMPP servers available for download. Again,
Jabber.org has a list of free server software. See
http://jabber.org/software/servers.shtml for a complete listing. When writing this
book, we used Jive Messenger with great success. Of course, it helps that Jive
Messenger and Smack are developed by the same company!
Setting up Messenger is quite easy. Start by downloading the server from
http://jive-software.org/messenger, or get it from the jive-messenger directory on
the CD included with the book. Then follow the installation instructions. Once it's
installed, configure the server using a Web browser. The installation process
automatically launches a browser and asks you to create an administrator login.
Once the server is running, ensure that under Server Settings > Registration &
Login that both Inband Account Registration and Anonymous login are
enabled. This allows you to create new users from the Hyperbola application. Then
run the sample Hyperbola for Chapter 23 which allows you to create users.
Chapter 12. Adding Key Bindings
All applications have actions that are more often initiated from the keyboard than from a
toolbar or menu. Power-users especially expect quick access to common actions using the
keyboard. The Workbench includes an extremely flexible framework to manage actions and key
bindings. This includes having locale- or function-grouped key binding configurations and key
bindings that apply to only certain parts of the application.
You can add key bindings for all Hyperbola actions since there aren't that many. In general,
however, you have to carefully select the actions to bind to key sequences. One rule of thumb
comes from the observation that toolbars also contain frequently used actions. As such,
consider providing key bindings for all of your toolbar items. Note also that actions often show
up in various menus, so you can show their related key sequences there.
In this chapter, we show you how to:
Define key bindings for all the Hyperbola application's actions.
Show key bindings in menus.
Add key bindings for the reused Quit and About Workbench actions.
Use key binding configurations to scope bindings.
Add the Keys preference page to Hyperbola.
12.1. Defining Commands
To assign a key binding to an action, you first have to create a command. Commands are the
declarative component of an action and are independent of the implementation. They can be
categorized and assigned key bindings.
Tip
Why is it important to separate a command from an action implementation? A good
example is when you need to provide key bindings for standard actions such as copy,
cut, and paste. The implementation of these operations depends on the context. So,
the command must be separate from the implementation. A key binding can be
defined for the command that applies regardless of the underlying implementation.
To start, let's add a key binding for the Add Contact action. Open the Hyperbola plug-in editor
and create a org.eclipse.ui.commands extension. You can specify all aspects of the key binding
through this extension. Once you've created a commands extension, take a look at the
extension point description using the Open extension point description link in the Extension
Details section. This gives you a good description of what the extension point does, and in the
case of commands, the meaning of the fields for commands extensions. Notice that there are
several different elements for a commands extension, such as commands, categories, and key
bindings. In brief, a commands extension provides one-stop shopping for all your command
needs.
It's good practice to group related commands together using categories. This helps users
navigate the list of commands when managing key bindings, etc. Start by creating a category,
as shown in Figure 12-1. Click on the "commands" extension and from the context menu, select
New > category.
Figure 12-1. Category element details
Once the category is defined, create a command for the Add Contact action, as shown in Figure
12-2. Again, click on the "commands" extension and select New > command.
Figure 12-2. Command element details
For the id, use the value defined in AddContactAction.ID. This is the unique identifier for this
command. You should always have a constant for the command id since it is needed to
programmatically associate an action with a command. The name is a human-readable form
that is displayed in a preference or configuration dialog. This name is not shown in menus and
so it does not need a menu accelerator (e.g., "&" character). Lastly, the categoryId references
the command category you just defined. The category element is deprecated and can be left
empty.
The next step is to add a keyBinding element and assign it to the command via the
commandId attribute.
Note
The org.eclipse.ui.commands/keyBinding element was deprecated late in the 3.1
release in favor of a new extension point. The new extension point currently provides
the same behavior and was put in place as a stepping stone for new features to come
in 3.2. For now, just ignore the deprecation warnings.
Click on the org.eclipse.ui.commands extension and from the context menu, select New >
keyBinding.
The keyBinding details are shown in Figure 12-3. The most interesting part of the keyBinding
element is the keySequence; in this case, M1+A. "M1" in the sequence stands for "Meta 1."
This is the platform-independent way of talking about the "Command" key on the Mac and the
"Ctrl" key just about everywhere else. Similarly, "M2" is "Shift" and "M3" is "Alt."
Figure 12-3. Key binding element details
The commandId defines the id for the command to which this key binding applies. The
keyConfigurationId is used to identify the configuration in which this key binding lives. The
org.eclipse.ui.defaultAcceleratorConfiguration is the default configuration created by the
Workbench and is also the default active configuration. For now, the default configuration is
fine. We discuss how to use alternate configurations in Section 12.4, "Key Configurations."
Key sequences
Key sequences are sets of key chords separated by spaces. A key chord represents
one or more keys held down at the same time. A chord can have zero or more
modifier keys and exactly one other key. The keys in a chord are separated by the
"+" character.
Keys are generally specified simply as uppercase ASCII characters. For example, "F"
and "," are examples of key specifications. However, there are many keys that have
no printable ASCII representation. The following is a list of the current special keys:
ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, BREAK, BS,
CAPS_LOCK, CR, DEL, END, ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
F13, F14, F15, FF, HOME, INSERT, LF, NUL, NUM_LOCK, NUMPAD_0, NUMPAD_1,
NUMPAD_2, NUMPAD_3, NUMPAD_4, NUMPAD_5, NUMPAD_6, NUMPAD_7,
NUMPAD_8, NUMPAD_9, NUMPAD_ADD, NUMPAD_DECIMAL, NUMPAD_DIVIDE,
NUMPAD_ENTER, NUMPAD_EQUAL, NUMPAD_MULTIPLY, NUMPAD_SUBTRACT,
PAGE_UP, PAGE_DOWN, PAUSE, PRINT_SCREEN, SCROLL_LOCK, SPACE, TAB, and
VT.
Now that you have the required extension elements defined, you must link the
AddContactAction to its associated command. This is done by assigning the command id to the
action using the IAction method setAction DefinitionId(String). Update AddContactAction's
constructor, as shown in the code snippet below.
org.eclipsercp.hyperbola/AddContactAction
public AddContactAction(IWorkbenchWindow window) {
this.window = window;
window.getSelectionService().addSelectionListener(this);
setId(ID);
setActionDefinitionId(ID);
setText("&Add Contact...");
setToolTipText("Add a contact to your contacts list.");
setImageDescriptor(
AbstractUIPlugin.imageDescriptorFromPlugin(
Application.PLUGIN_ID, IImageKeys.ADD_CONTACT));
}
actionDefinitionId versus id
A common point of confusion is that the id defined on an IAction is not the same as
the action's command id. The id is used exclusively for supporting retargetable
actions, while the actionDefinitionId is used to associate actions with commands.
To reduce the complexity in your application, we recommend that you simply set
both to the same value. It makes your life easier if you consider them to be the
same and use a common constant for both.
Once the action is associated with the command id, the action has to be registered with the
Workbench. This tells the Workbench to call the action when the key sequence is input by the
user. Hyperbola's actions are created in ApplicationActionBarAdvisor. Each action is registered
by calling ActionBarAdvisor.register(IAction).
You might recall that they are registered to ensure that they are properly deleted. Registering
an action using ActionBarAdvisor.register(IAction) also registers the action's key binding.
The following code snippet shows the register(IAction) method supplied by the Workbench. If
you are already calling the method, no further effort is required. If not, either change the code
to call register(IAction) or to call registerGlobalAction(IAction) directly.
org.eclipse.ui.workbench/ActionBarAdvisor
protected void register(IAction action) {
String id = action.getId();
Assert.isNotNull(id, "Action must not have null id");
getActionBarConfigurer().registerGlobalAction(action);
actions.put(id, action);
}
Just to be sure, go back to ApplicationActionBarAdvisor.makeActions(IWorkbenchWindow) and
confirm that the actions are being registered. By setting the action definition id in the
AddContactAction constructor and ensuring the action is registered with the Workbench, the
action can participate in the key binding mechanism. This is a useful pattern to use for all your
actions.
org.eclipsercp.hyperbola/HyperbolaActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
exitAction = ActionFactory.QUIT.create(window);
register(exitAction);
aboutAction = ActionFactory.ABOUT.create(window);
register(aboutAction);
addContactAction = new AddContactAction(window);
register(addContactAction);
chatAction = new ChatAction(window);
register(chatAction);
...
}
12.2. Checkpoint
At this point, everything is in place to have the Add Contact action run when the user inputs
the "Ctrl+A" key sequence. Run Hyperbola now, try typing "Ctrl+A", then look at the
Hyperbola menu and notice that the key binding is displayed at the right of the menu, as
shown in Figure 12-4.
Figure 12-4. Add Contact action with key binding in menu
12.3. Adding Key Bindings for Workbench Actions
Remember back when you added the Exit and About actions? They are defined by the
Workbench, and in the ApplicationActionBarAdvisor class, you simply created the actions and
added them to the Hyperbola menu. Let's add some key bindings for them: "Ctrl+Q" to exit and
"Ctrl+Shift+F1" to launch the About dialog.
Normally, you would create a key binding for the actions as we did previously. But these are
reusable actions from the Workbench and they likely already have commands. Unfortunately,
the only way to find out if a command exists is by looking at the action's implementation to
determine if the IAction method setActionDefinitionId(String) is being called. If it is, you
don't need to define a new command. If a command is not defined, or you cannot tell, you must
define one.
The following code snippets are what you would find if you browsed starting in the
ActionBarAdvisor method makeActions() , then to the org.eclipse.ui.actions.ActionFactory,
and finally into the QuitAction's constructor. Here you can see that the command id for the
Quit action is org.eclipse.ui.file.exit. Use this command id to create a key binding for the
action.
org.eclipsercp.hyperbola/HyperbolaActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
this.window = window;
exitAction = ActionFactory.QUIT.create(window);
register(exitAction);
}
org.eclipse.ui.workbench/ActionFactory
public static final ActionFactory QUIT = new ActionFactory("quit") {
public IWorkbenchAction create(IWorkbenchWindow window) {
if (window == null)
throw new IllegalArgumentException();
IWorkbenchAction action = new QuitAction(window);
action.setId(getId());
return action;
}
};
org.eclipse.ui.workbench/QuitAction
public QuitAction(IWorkbenchWindow window) {
this.workbenchWindow = window;
setText(WorkbenchMessages.Exit_text);
setToolTipText(WorkbenchMessages.Exit_toolTip);
setActionDefinitionId("org.eclipse.ui.file.exit");
window.getWorkbench().getHelpSystem().setHelp(this,
IWorkbenchHelpContextIds.QUIT_ACTION);
}
After following through the code and finding the command id, go and add a key binding for the
Quit action and ensure that the ActionBarAdvisor method register(IAction) is called. The
steps are the same as you used previously to register the Add Contact action.
Now that the Quit action has a key binding, as shown in Figure 12-5, re-run the same steps to
add a key binding for the About action.
Figure 12-5. Key binding element details for existing Workbench
actions
12.4. Key Configurations
You've seen that when defining a key binding, it must be assigned to a key configuration, and
the binding is only enabled when the given configuration is active. The Workbench defines a
configuration called org.eclipse.ui.defaultAcceleratorConfiguration, which is used as the
configuration for all commands defined in the Workbench. It is also the default configuration
used when the Workbench starts.
When defining key bindings in your RCP application, you have the choice of assigning them to
this default key configuration with the caveat that there may be conflicts with existing bindings.
An alternative is to define another key configuration using the
org.eclipse.ui.commands/keyConfiguration extension point and assigning all your key bindings
to this configuration. You can even define key bindings to commands provided by the
Workbench and assign them into this new configuration. The following example assigns the add
contact command to the "M1+B" key instead of to "M1+A", which was its value in the default
key configuration.
org.eclipsercp.hyperbola/plugin.xml
<extension
point="org.eclipse.ui.commands">
<keyConfiguration
description="Keys for Hyperbola"
id="org.eclipsercp.hyperbola.keyConfig"
name="Hyperbola Keys"/>
<keyBinding
commandId="org.eclipsercp.hyperbola.addContact"
keyConfigurationId="org.eclipsercp.hyperbola.keyConfig"
keySequence="M1+B"/>
</extension>
The new key configuration, called org.eclipsercp.hyperbola.keyConfig, is not enabled by
default. To enable it, you must add an entry to your plug-in customization file as follows (see
Section 13.5, "Adding Help Content," for how to create a preference customization file):
org.eclipse.ui/KEY_CONFIGURATION_ID=org.eclipsercp.hyperbola.keyConfig
Alternatively, you can use the Keys preference page explained in the next section to switch
between registered key configurations.
12.5. Keys Preference Page
The Workbench ships with a preference page that allows users to configure key sequences and
toggle the active configuration. This preference page is called the Keys preference page. You
can see it in action in the IDE by opening Window > Preferences... > General > Keys. The
first page, called View, shows all the commands in the active configuration that are bound to a
key sequence. The second page, called Modify, allows users to switch the configuration or
change the key sequences for any defined command.
You can add this preference page to your application by defining an
org.eclipse.ui.preferences extension with a special syntax. The Workbench includes an
extension factory that provides access to these pages via a set of identifiers. Extension factories,
or IExecutableExtensionFactory, are parameterized executable extensions that can create
classes based on the parameters defined in the extension definition. This is useful for hiding
implementation classes. The identifiers are used in preferences extensions as shown below.
Notice that the markup is the same as a regular preference extension, but instead of specifying
the preference page's implementation class directly, org.eclipse.ui. ExtensionFactory is used
and the Keys preference page identifier, "keysPreferencePage", is given as a parameter.
org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.ui.preferences">
<view
class="org.eclipse.ui.ExtensionFactory:keysPreferencePage"
id="org.eclipsercp.hyperbola.preferences.keys"
name="Keys"/>
</extension>
If you provide the Keys preference page in your application, you must define your own key
configuration, as defined in Section 12.4, because this preference page shows all the commands
defined by the Workbench, even those that are not used in your application. Having a specific
configuration will trim this list down to only those defined by your application. See Section 17.3,
"Standard Workbench Actions," for more reusable preference pages and actions, and Section
11.3.2, "Adding the Action," for details on how to add the action to open the preferences page.
12.6. Summary
Key bindings are an essential part of your application's usability. You've seen that command ids
are the key to linking actions to key bindings. There are many steps required to link actions to
key bindings. From the basic pattern, you can see that it all comes down to everyone agreeing
on the common currencycommand ids.
Always start by creating a set of constants for each of your actions. In Hyperbola, they are
added as static fields on each action. Then, create associated commands for each using the ids
you've already defined, and add the key bindings that reference the commands. The final step is
to associate an action with a command so that when a key sequence is pressed, the Workbench
knows which action to run. A good rule of thumb is to configure each action in its constructor
with its associated command id by calling IAction.setDefinitionId(String). Then, register the
action in the ActionBarAdvisor by calling register(IAction), or if the action is defined within a
view or editor, call IActionBars. setGlobalActionHandler(String, IAction).
Chapter 13. Adding Help
Hyperbola is a complete though basic chat client. Even with its modest capabilities, users can
run into trouble getting it configured or dealing with connections. Fortunately, the Eclipse
Platform includes support for static, dynamic, and context-sensitive help. Adding help
capabilities to Hyperbola gives its users a place to go for answers. This is an integral part of
real-world applications.
Adding help support also marks the first time that you need to use plug-ins that are not
included in the RCP SDK. The new mechanisms and procedures needed while adding help
support apply more generally to situations where you need to use other plug-ins in your
application.
In this chapter, we show how to:
Get and add new plug-ins to your target environment.
Add help support to Hyperbola.
Add context-sensitive help support.
Create and structure help content.
13.1. Adding to the Target Platform
The Hyperbola prototypes in previous chapters used plug-ins that were found in the RCP SDK
(in the target platform) and some of our own code (in the workspace). In this chapter, we are
looking to expand the target platform and use Eclipse plug-ins from outside the RCP SDK. To do
this, we must get the plug-ins from somewhere and add them to the target.
Early in Chapter 3, "Tutorial Introduction," you set up separate IDE and target environments.
This separation became important in Chapter 9, "Packaging Hyperbola," when we added the
delta pack to the target platform to allow for cross-platform exporting. Here, we'll see how the
separation helps, but also makes life a little harder. Note that this discussion is completely
generic and unrelated to Help, but it is presented here, as this is the first time the need for
additional plug-ins has been encountered.
13.1.1. Getting Plug-ins
At some point, you will add capability to your application that you think someone else has
already implemented. Instead of searching the Web, looking for plug-ins, you should start by
looking at the list of plug-ins included in the Eclipse Platform. There are over 60 plug-ins
included in the Platform, and many are useful in RCP applications. See Chapter 27, "Eclipse.org
Plug-ins," for more information on which of the 60 plug-ins can actually be used in your RCP
applications.
When looking for an Eclipse Platform plug-in, chances are you already have it somewhere on
your system. For example, if you did exactly as we described when creating your target
platform and IDE setups, the target platform and IDE are based on the same version of Eclipse.
That means your IDE has many plug-ins that can be used in your application. Since they are the
same version levels of Eclipse, the plug-ins are all compatible with each other.
If your IDE and target versions do not match (as often happens at some point during a project),
or you need plug-ins that do not happen to be in the IDE install, you must acquire them from
somewhere. Typically, going to the originator is the best bet. For Eclipse plug-ins, that means
returning to http://eclipse.org/downloads. Once there, find the archive that has the plug-ins you
need. For example, the Platform SDK contains all the plug-ins you need for the Hyperbola
example in this book. If you are using the EMF, GEF, or one of the other Eclipse projects, they
typically have reasonably obvious SDK downloads.
Tip
When you are downloading plug-ins to put in the target, ensure that you get the
"SDK" downloads, not the "Runtime Binary" downloads. The SDKs contain source code
and developer documentation that may help you when coding and debugging.
13.1.2. Adding Plug-ins
Once you have the plug-ins on your machine, they need to be added to the target platform. This
is done using the following steps.
1. Copy the plug-ins to the target platform directory tree (c:\target\eclipse\plugins). If
you are getting plug-ins from your IDE install, it is very important that you copy rather
than move the plug-ins. If you move them, they are removed from the development
environmentprobably not what you want.
Note
In Eclipse 3.1 and later, some plug-ins are shipped as JARs and some are shipped
as directories. These two forms are equivalent. When you need a plug-in that is in
directory form, copy the whole directory (e.g., org.eclipse.tomcat_4.1.30.1). If
you are getting a JAR'd plug-in, just copy the JAR.
2. Repeat Step 1 until you have all the plug-ins you want.
3. Update the target platform definition in the development environment by going to the
Window > Preferences > Plug-in Development > Target Platform preference page.
The target Location is already set, so just click Reload. PDE reviews the target location
and discovers the new plug-ins and fragments you just acquired.
4. Check the target plug-in list to ensure that all the desired plug-ins are there. If a new plugin is missing a prerequisite, it will not resolve and does not appear in the list. If some plugins are missing, go back to Step 1, copy the missing prerequisites, and reload.
Tip
You can use the plug-in counts reported on the Target Platform > Plug-ins page
to tell you if everything you added was resolved. For example, if the count is 24
and you copy 5 plug-ins to the target directory and click Reload, the count should
be 29. To find out what is not resolving, open the Error Log view (Window >
Show View > Error Log) and then click Reload in the Target Platform
preference page. Unresolved plug-ins are reported in the log. Note that with the
delta pack installed, some warnings are expected.
13.2. Getting the Help Plug-ins
Following the steps outlined in the previous section, get the following plug-ins either from your
IDE install or from the download site and add them to the target platform.
1. org.apache.lucene
2. org.eclipse.help.appserver
3. org.eclipse.help.base
4. org.eclipse.help.ui
5. org.eclipse.help.webapp
6. org.eclipse.tomcat
7. org.eclipse.ui.forms
13.3. Configuring the Help Plug-ins
Now, the desired plug-ins are available for use, but you have to configure the Hyperbola
product to use them. Go back to the IDE and add the new plug-ins to the product configuration
using the following steps. The org.eclipse.help plug-in is the stub of the Help support and is
already in the configuration since it is part of the RCP base.
1. Go to the Configuration page of the Hyperbola product editor and click Add... to add
plug-ins.
2. In the dialog, choose org.eclipse.help.ui and click OK.
3. Click Add Required Plug-ins. Notice that several plug-ins, including org.apache.lucene,
org.eclipse.help.appserver, and org.eclipse.help.base, are added to the configuration.
These are the plug-ins directly or indirectly required by the plug-ins already in the list.
4. The Eclipse Help system is decoupled from the Web server and Web application used to
manage and serve help content. As such, the plug-ins that supply Web servers and help
Web applications are not prerequisites of the Help plug-insyou must add these manually.
Click Add, select org.eclipse.help.webapp and org.eclipse.tomcat from the list, and click
OK.
5. Use File > Save to save the Hyperbola product configuration.
13.4. Add the Help Action
Now that the Help infrastructure is configured as part of the application, you are ready to add
the help actions and content. Hyperbola already has a Help menu (for the About action) to
which you can add a Help action. The UI workbench has a number of handy preconfigured
actions (e.g., ActionFactory.HELP_CONTENTS) just for this purpose.
In the ApplicationActionBarAdvisor.makeActions() method, instantiate and register the help
contents action as follows:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
helpAction = ActionFactory.HELP_CONTENTS.create(window);
register(helpAction);
Tip
When you added the create(IWorkbenchWindow) call, the helpAction field was not
declared so the editor marked the line with an error. Use the Eclipse quick-fix feature
to add the field. Click on the line and press Ctrl+1. Then, pick the Create field
'helpAction' quick-fix. The helpAction field is added at the top of the class.
Now add the action before the About action in the Help menu by updating fillMenuBar() as
shown below.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
MenuManager helpMenu = new MenuManager("&Help","help");
helpMenu.add(helpAction);
helpMenu.add(aboutAction);
Save the file and run Hyperbola. You should see a Help > Help Contents menu item. Select
that item and you should get a dialog saying that the Help content is not installed. If you do not
see the dialog, double-check that your launch configuration and production configuration list all
the required Help plug-ins as listed in Section 13.2, "Getting the Help Plug-ins."
13.5. Adding Help Content
Help content is written as a set of HTML documents and a number of table of contents (toc) files
that give the HTML structure. The easiest way to start adding Help content is by using an
extension template to generate a skeleton document structure.
Open the Hyperbola plug-in editor and on the Extensions page, click on Add... to add an
extension. In the New Extension dialog, uncheck the Show only extension points from the
required plug-ins option. Then select the Extension Wizards tab and pick Extension
Templates. You should see something like Figure 13-1.
Figure 13-1. New Extension wizard
Select the Help Content template and click Next. This shows you the help template
configuration page, as shown in Figure 13-2.
Figure 13-2. Help template configuration
Enter "Hyperbola Help" for the Label for table of contents field. Select the Primary check box
to ensure that this help is always shown at the top level in the help outline. You can enable a set
of categories that makes sense to you. Don't worry about this too much; it is easy to change
them later. Click Finish to generate the various files and extensions.
Run Hyperbola again and select Help > Help Contents. Hyperbola spawns a separate window
that looks like Figure 13-3. Notice here the table of contents structure down the left with the
categories you selected in the template. The template generated sample topics for each.
Figure 13-3. Initial Help content
When you ran the Help > Help Contents action, you likely saw a page on the right offering
information on how to use the Eclipse Help system. This is the default Help home page.
Fortunately, you can change this for Hyperbola by overriding a public Help preference in your
product description.
Public preferences are used by many plug-ins to allow applications and products to tweak
specific configuration options. For example, the Workbench has preferences to control things
such as the perspective bar location, fast view bar location, traditional vs. curvy tabs, and so
on. Similarly, Help has preferences to control many aspects of the Help UI. For a complete list of
Help preferences, refer to the section called "Help System Preferences" in the Eclipse IDE online
help. The particular preference to change the Help home page is called
org.eclipse.help.base/help_home.
To override this Help preference, you need to specify a preference customization file and
associate it with the Hyperbola product. Open Hyperbola's plug-in editor and on the Extensions
page, select Hyperbola Chat Client product extension to the
org.eclipse.core.runtime.products extension point. Using its context menu, select New >
property to add the preferenceCustomization property, as shown in Figure 13-4.
Figure 13-4. Adding the preferenceCustomization property to the
product
[View full size image]
Create the preferences.ini file in the root of the Hyperbola plug-in and add the following line:
org.eclipsercp.hyperbola/preferences.ini
org.eclipse.help.base/help_home=\
/org.eclipsercp.hyperbola/html/help_home.html
The preferences customization file is a standard Java properties file where the keys are
preference ids encoded as /plugin-id/preference-name and values are the new values for the
preferences. Create the home page at the root Hyperbola's html directory and call it
help_home.html. For now, you can simply show the Hyperbola splash screen, as shown below.
org.eclipsercp.hyperbola/html/help_home.html
<html>
<body>
<img src="../splash.bmp">
</body>
</html>
Of course it's an HTML file, so you can make this as fancy as you like. Run Hyperbola again and
launch Help. The first page shows the Hyperbola splash screen in the right pane. You could
expand on the branding in this page to contain pointers on how to get started with Hyperbola.
Note
Adding Help to Hyperbola does not require that new dependencies be added to
Hyperbola. Notice that in the step above, you did not add any Help plug-ins to the
Hyperbola plug-in's prerequisite list. All you did was add the Help-related plug-ins to
the Hyperbola product configuration, thus ensuring that the Help infrastructure is
shipped as part of Hyperbola.
The Help actions used are supplied by the UI Workbench plug-in, which is already a
prerequisite. The Hyperbola Help content is contributed via extensions to a Help plugin extension point. Contributing extensions to a plug-in's extension point does not
imply that you depend on the plug-in.
13.6. Help Content Structure
The Help extension template added three things to the Hyperbola plug-in: a help extension, an
XML toc, and HTML stub files for each help page. You should think of the help system as a mini
Web site. The toc files simply define a set of bookmarks for the help Web site. Of course, there
is nothing hard-coded about this structure. You are free to set up whatever structure best suits
your needs. You can have as many categories with whatever names and contents you choose.
Let's look at what the template generated.
The help extension informs the Help system of all the toc files. The Help system uses these to
knit together diverse sets of help into a coherent structure that is presented in the left pane of
the help window. This extension identifies three XML toc files: toc.xml at the top level and one
for each category selected when instantiating the template. The file toc.xml is marked as
primary to ensure that it is always shown in the help outline.
org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.help.toc">
<toc file="toc.xml" primary="true"/>
<toc file="tocgettingstarted.xml"/>
<toc file="toctasks.xml"/>
</extension>
The toc.xml file simply defines an anchor for each category. Anchors are locations to which
other toc files link and add their substructure. Below are the contents of toc.xml and
toctasks.xml. Notice how toc.xml defines the "tasks" anchor and toctasks.xml links to that
anchor. That setup inserts the toctasks.xml structure in the main table of contents at the
"Tasks" entry.
org.eclipsercp.hyperbola/toc.xml
<toc label="Hyperbola Help" topic="html/toc.html">
<topic label="Getting Started">
<anchor id="gettingstarted"/>
</topic>
<topic label="Tasks">
<anchor id="tasks"/>
</topic>
</toc>
org.eclipsercp.hyperbola/toctasks.xml
<toc label="Tasks" link_to="toc.xml#tasks">
<topic label="Main Topic" href="html/tasks/maintopic.html">
<topic label="Sub Topic" href="html/tasks/subtopic.html"/>
</topic>
<topic label="Main Topic 2">
<topic label="Sub Topic 2" href="html/tasks/subtopic2.html"/>
</topic>
</toc>
From toctasks.xml, you can infer that the template laid down an HTML structure, as shown in
Figure 13-5. There is one directory for each category and a top-level toc.html file. The HTML
files are just stubs, but they represent the real help content shown in the right pane of the help
window. It is up to you to fill in and link together the relevant content.
Figure 13-5. Help HTML content structure
13.7. Infopops or F1 Help
In Hyperbola, we thought it would be good for users to be able to get context-sensitive help.
For example, when they are in a chat view, they should be able to find out how to send special
messages, for example, smileys. The Infopop mechanism links UI elements such as views, menu
items, and buttons to help content. The user simply selects the element in question and presses
the appropriate help keyF1 on Windows, Ctrl+F1 on GTK, and the "Help" key on the Mac. The
associated help is then displayed. Adding Infopops is done by tagging UI elements (e.g., views
and actions) with help contexts. For example, the Contacts view is tagged by adding the
following line in ContactsView. createPartControl(Composite):
org.eclipsercp.hyperbola/ContactsView
PlatformUI.getWorkbench().getHelpSystem().setHelp(
treeViewer.getTree(), "org.eclipsercp.hyperbola.contactsView");
The second argument to setHelp(Control, String) is a context id. This is used as a key in a
context table to look up help content to display. The context table is maintained by the Help
system and is constructed by gathering context extensions. The Hyperbola plug-in contributes
the following context extension:
org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.help.contexts">
<contexts file="contexts.xml"/>
</extension>
The extension simply identifies contexts.xml as a file containing any number of context ids to
content mappings. Notice in the snippet below that the context id, contactsView, matches the
context id assigned to the ContactsView after adding on the id of the defining plug-in. The help
content for this context includes two topics.
org.eclipsercp.hyperbola/contexts.xml
<contexts>
<context id="contactsView">
<description>This is Contacts View</description>
<topic
label="Managing Contacts"/>
href="html/tasks/managingContacts.html"
<topic
label="Grouping Contacts"/>
href="html/tasks/groupingContacts.html"
</context>
</contexts>
13.8. Exporting Plug-ins with Help
There is one final step to adding help to Hyperbola. In the procedure described so far, we have
added a number of files to the Hyperbola plug-in. These files need to be listed in the
build.properties so that PDE knows to include them when the plug-in is exported. Open
Hyperbola's build.properties and check off the following files or folders:
html/
toc.xml
tocgettingstarted.xml
toctasks.xml
preferences.ini
contexts.xml (if needed)
Save the file, export Hyperbola, and run the application. Try accessing the various help contents
and make sure that everything works. If it doesn't, ensure that all the right files are in the
exported Hyperbola.
13.9. Summary
Help is an important part of many applications. The Eclipse help support is very easy to
integrate and supports a wide range of options from static content to context-sensitive help.
Help files can be shipped with an application or hosted remotely on a server.
13.10. Pointers
The Eclipse Help infrastructure supports many capabilities not discussed in this chapter. For an
overview of these, refer to the following Eclipse IDE online help topics:
Platform Developer Guide > Programmer's Guide > Plugging in Help
Platform Developer Guide > Reference > Other reference information > Help System
Preferences
Platform Developer Guide > Reference > Other reference information > Installing the
stand-alone help system
Platform Developer Guide > Reference > Other reference information > Installing the help
system as an infocenter
Chapter 14. Adding Update
Hyperbola is ready to ship. But what about the next version? What if there is a bug? And what
about all this XMPP extensibility we talked about earlier? How can users get updates or find cool
new features?
The Eclipse Platform includes an Update component that discovers and installs updates to both
existing function and totally new function. This is exactly what we need for Hyperbola in the
long run.
In this chapter, you will:
Learn about the end-user facilities provided by Update.
Add update capabilities to the target platform and to Hyperbola.
Learn about features and create a feature for Hyperbola.
14.1. Getting Update Plug-ins
Using the steps outlined in Chapter 13, "Adding Help," get the following plug-ins and add them
to the target:
1. org.eclipse.ui.forms
2. org.eclipse.update.core
3. org.eclipse.update.scheduler
4. org.eclipse.update.ui
Note that org.eclipse.ui.forms is already there if you added the Help support in Chapter 13.
14.2. Configuring the Update Plug-ins
To allow Hyperbola to update itself with new versions, you first need to add the Update plug-ins
to the product configuration. Follow these steps to add the required plug-ins:
1. Go to the Configuration page of the Hyperbola product editor and click Add to add plugins.
2. In the dialog, choose org.eclipse.update.ui and click OK. The Update UI plug-in is added
to the list.
3. Click Add Required Plug-ins. Notice that org.eclipse.update.core was added to the
configuration.
When you added the Help plug-ins to Hyperbola in the previous chapter, just adding the plugins to the configuration was enough to enable the functionHyperbola's help content was enabled
via extension contributions. For Update, Hyperbola needs to access code. As such, you must add
the Update plug-ins to Hyperbola's list of required plug-ins.
Open Hyperbola's plug-in editor and on the Dependencies page, click Add... to see a list of
plug-ins that are currently not required by Hyperbola. Select
1. org.eclipse.update.core
2. org.eclipse.update.ui
and click OK to add them. You do not need to add all the Update plug-ins, only the ones whose
code is directly referenced from Hyperbola. Don't forget to save the changes.
Tip
If you are looking for a class that you know exists and is in your target but you still
cannot find it, it is likely that your plug-in dependencies are wrong. Eclipse only shows
classes that are directly visible on your prerequisite chain. That is, Hyperbola requires
the UI plug-in and the UI requires the Help plug-in. Hyperbola can see the classes in
the UI, but not those in Help.
On rare occasions, a plug-in such as the UI is configured to re-export some of its
prerequisites. We saw this in Section 4.1, "Hyperbola Hello World," where the UI
required and re-exported SWT. In that case, Hyperbola was able to see SWT's classes
through the UI. Eclipse contains a few other examples of this. Notably,
org.eclipse.core.runtime re-exports org.eclipse.osgi.
So, when you are missing classes, hunt down the plug-in that contains them and add
a direct dependency on it.
14.3. Defining Features
Not much happens in Update without features. Features collect sets of plug-ins that go together
to form some coherent unit of function. Features have a very simple structure; they are a list of
plug-ins and other features. They essentially comprise a bill of materials for a set of functions.
This makes them very useful for organizing and managing systemsjust what you would expect
from an Update/Install concept.
Note
As discussed in Chapter 23, "RCP Everywhere," features are also very useful in
keeping your team organized during the development of your product.
When we started out with the Hyperbola product definition in Chapter 8, "Branding Hyperbola,"
we chose to use a plug-in-based configuration (see the Overview page of the Hyperbola
product editor). Let's change that now and use features. Go back to the Hyperbola product
editor and on the Overview page, change the product configuration to be based on features, as
shown in Figure 14-1.
Figure 14-1. A feature-based product configuration
Next, flip over to the Configuration page and click on New Feature... to start the New
Feature wizard shown in Figure 14-2. You can also create features using New > Project... >
Plug-in Development > Feature Project. Enter "org.eclipsercp.hyperbola.feature" for the
project name. By default, the project name matches the feature id just as with plug-ins. Click
Next and fill in the feature properties, as shown in Figure 14-3.
Figure 14-2. Defining the Hyperbola feature
Figure 14-3. Filling in the feature properties
Choosing feature ids
The feature and plug-in id namespaces are distinct. In fact, the features concept is
defined by the Update component, whereas plug-ins are defined by the Runtime and
OSGi.
As a result, you can have a plug-in and a feature with the same id. This can be
convenient, but it can also be confusing. At development time, you may want to
have both the feature and plug-in projects in your workspace at the same time. If
you follow the recommended practice of matching the project names to the ids, the
plug-in and feature projects will collide and cannot be loaded together.
There are various conventions for naming features and feature projects. For
historical reasons, the Eclipse platform feature ids tend to overlap with the plug-in
namespace and feature project names are made up of the feature ids followed by "feature". For example, the "org.eclipse.platform" feature is housed in a project
called "org.eclipse.platform-feature ".
In this book, we tend to avoid overlapping the feature and plug-in namespaces and
use a naming convention that puts the word "feature" relatively early in the feature
id and matches projects names to feature ids. For example, using
org.eclipsercp.hyperbola.feature.muc for both the id and the project name
distinguishes the MUC feature from the MUC plug-ins and results in all the
Hyperbola feature projects sorting together in the repository and workspace.
Leave the Feature Id matching the project name. The Feature Name should be a humanreadable string. It is shown to the user at various points when using the Update mechanisms.
Similarly, the Feature Provider should be the readable name of your organization.
Click Next and the Plug-ins and Fragments page comes up, as shown in Figure 14-4. This
page allows you to add plug-ins to the feature being created. Here, you can take advantage of
the RCP feature. The org.eclipse.rcp feature that is included with Eclipse lists the 10 or so
basic plug-ins (e.g., OSGi, Runtime, SWT, JFace, and UI) that make up the basic Eclipse RCP.
So you need only list the plug-ins that either you developed (e.g., org.eclipsercp.hyperbola)
or that you have added to the target yourself (e.g., the plug-ins added in this chapter). Go
through the list and select the following plug-ins (there should be 11):
1. org.apache.lucene
2. org.eclipse.help.appserver
3. org.eclipse.help.base
4. org.eclipse.help.ui
5. org.eclipse.help.webapp
6. org.eclipse.tomcat
7. org.eclipse.ui.forms
8. org.eclipse.update.core
9.
10.
7.
8.
9. org.eclipse.update.ui
10. org.eclipsercp.hyperbola
11. org.jivesoftware.smack
Figure 14-4. Listing feature plug-ins
Click Finish. The Hyperbola feature project is created and opened in a feature editor. On the
Overview page of the editor, there is a General Information section, as shown in Figure 145. Here you see the values that you just entered in the wizard. There is one bit of information
that's missingthe Update Site information. Fill it in now as shown. You can set up your own
update site, but for now, these values will connect the feature to a real update site so you can
test your function.
Figure 14-5. Adding update site information
The only thing left to do is connect the Hyperbola feature to the RCP base feature. Move over to
the Included Features page and look for the Included Features section, as shown in Figure
14-6. Click on Add... to get a list of all the features known to the system. Select
org.eclipse.rcp, click OK, and save the feature.
Figure 14-6. Adding the RCP base feature
That completes feature creation, so now go back to the Hyperbola product editor. Notice that
the org.eclipsercp.hyperbola.feature you just created is listed in the Features section of the
Configuration page, as in Figure 14-7.
Figure 14-7. The Hyperbola product with its feature
Now you are back in exactly the same position you were a few minutes ago, but the Hyperbola
product is described in terms of features rather than just a list of plug-ins. This allows you to
directly use the Update function to manage Hyperbola installs.
14.4. Branding Features
Since an Eclipse-based system is a composition of features and plug-ins from different sources,
the standard About dialog exposes branding for individual features. This allows feature vendors
to have a user-visible presence in the system. For example, after following the steps below, the
Hyperbola feature branding seen via Help > About Hyperbola > Feature Details displays as
shown in Figure 14-8.
Figure 14-8. Hyperbola feature branding
The branding images and text are supplied by a branding plug-in defined in the Overview >
General Information > Branding Plug-in field of the feature editor. The branding plug-in can
be any plug-in included in the feature. Use the Browse... button and select
org.eclispercp.hyperbola as the branding plug-in.
The branding information is contained in a set of files, as shown below. The about.ini defines
two properties: one for the informational About text and one for the feature image.
org.eclipsercp.hyperbola/about.ini
# Property "aboutText" contains blurb for feature details in the "About"
# dialog (translated). Maximum 15 lines and 75 characters per line.
aboutText=%blurb
# Property "featureImage" contains path to feature image (32x32)
featureImage=icons/32h.bmp
The aboutText property gives the text that is displayed when the feature is selected in the
feature details dialog. The main reason for this information being in a plug-in is to enable
translation. As such, the value %blurb is defined in an associated about.properties file, which is
shown here:
org.eclipsercp.hyperbola/about.properties
blurb=Hyperbola Chat Client Product\n\
Version: {featureVersion}\n\
Build id: {0}\n\
(c) Copyright Jean-Michel Lemieux and Jeff McAffer. All rights reserved.\n\
Visit http://eclipsercp.org/hyperbola
Translations of this value are then supplied in language-specific files in fragments of the
Hyperbola plug-in as discussed in Section 8.4, "Splash Screen." The About text for features can
be specialized for particular builds as described in Section 8.5, "About Information." That is, the
"{0}" in the about blurb can be replaced by values in an about.mappings file in the branding
plug-in.
org.eclipsercp.hyperbola/about.mappings
0=1.0.0
Warning
Feature branding information is separate from the product branding that is defined in
org.eclipsercp.hyperbola. That branding is specified in the product extension and is
displayed on the main About dialog. Due to a naming overlap, however, the
about.mappings files are shared between the two. Care should be taken when
assigning and using properties from this file.
Add these files as described and update the Build page in the Hyperbola plug-in editor to have
all the about.* files checked in the Binary Build list. Re-export Hyperbola, then run it and
check that your feature branding worked.
14.5. Adding Update Actions
Now that we have a description of Hyperbola that Update can manage, let's add the different
Update actions to Hyperbola. There are three main functions supported by Update:
Searching for updates to existing features This finds and installs updates to existing
features. For example, if you are running Hyperbola 1.0 and 1.1 is released, Update allows
users to install the new version of Hyperbola.
Searching for new features Hyperbola is based on XMPP and is thus very extensible. It
makes sense to allow users to search for new features, such as MUC, to install.
Managing the existing configuration of features If users can install new features, it
makes sense to allow them to enable/disable or remove these features.
Up to now, we've been using many standard actions defined by other plug-ins, such as Help,
Preferences, and Exit. Update does not provide standard actions, but instead exposes a set of
APIs that can be used to build the appropriate actions for your application. The reason for this is
that many applications need specific control of how Update works, for example, to show their
own wizard to guide users through the update process. To support these different workflows,
Update provides the mechanics and leaves it up to you to write the supporting code. To add the
Update function to Hyperbola, we first have to write the actions.
The action classes needed are relatively straightforward IActions they all just set up calls to the
UpdateManagerUI API methods that open the various wizards. Each action has a constructor that
sets up the action with an id, text, icon, tool tip, etc. The meat of the action simply invokes a
different operation on UpdateManagerUI depending on what it is doing.
14.5.1. Updating Hyperbola
The first action to create is called UpdateAction. It sets up an UpdateJob that checks for updates
to the current function but does not download it. Below is the complete code for the action.
org.eclipsercp.hyperbola/UpdateAction
public class UpdateAction extends Action implements IAction {
private IWorkbenchWindow window;
public UpdateAction(IWorkbenchWindow window) {
this.window = window;
setId("org.eclipsercp.hyperbola.newUpdates");
setText("&Update...");
setToolTipText("Search for updates to Hyperbola");
setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
Application.PLUGIN_ID, "icons/usearch_obj.gif"));
window.getWorkbench().getHelpSystem().setHelp(this,
"org.eclipsercp.hyperbola.updates");
}
public void run() {
BusyIndicator.showWhile(window.getShell().getDisplay(),
new Runnable() {
public void run() {
UpdateJob job = new UpdateJob(
"Searching for updates", false, false);
UpdateManagerUI.openInstaller(window.getShell(),
}
});
job);
}
}
Tip
When you are testing the Update function, the easiest thing to do is export the product
and run it as a normal application. Update Manager expects a particular setup on disk
and typical workspace configurations are not suitable, that is, Update does not
initialize properly if you run out of the IDE unless you take special steps. These steps
are a bit of a distraction to the current discussion. For now, simply export Hyperbola
and run its launcher to test your Update function.
To try it out, first make sure you add and register the action in the Help menu, the standard
location for Update actions. Then enable progress reporting. Update operations typically contact
a server and may take some time. Users need to be reassured that something is happening and
must have the opportunity to cancel the operation. Add the following line to
ApplicationWorkbenchWindow Advisor.preWindowOpen():
configurer.setShowProgressIndicator(true);
Save all the files and copy all the icons from the sample code for this chapter. Exporting the
Hyperbola product now puts it in the same shape as it will be on the end-user's machine. You
should notice in the exported Hyperbola directory that there is a features directory containing a
feature for org.eclipse.rcp and org.eclipsercp.hyperbola.feature. The new Hyperbola runs
the same as always.
In Hyperbola, use Help > Update... to open the Updates wizard. The wizard goes out and
consults the Hyperbola update site and finds all the updates available for your version of
Hyperbola. From the page shown in Figure 14-9, select the versions you want and follow the
wizard's steps to have Update install the new code.
Figure 14-9. Available Hyperbola updates
When you run through the wizard, you are prompted for several things, including agreeing to a
license. After the new Hyperbola feature is installed, you are prompted to restart. Go ahead and
do that. When Hyperbola starts up again, open the About dialog and verify the Hyperbola
version is now 1.0.1.
Note
You had to restart here because we have not enabled Hyperbola for dynamic
capabilities. In any case, the update includes a new version of Hyperbola itself, so a
restart is required.
Version 1.0.1 is the same as the code you are running now just with a different version number.
Now that you have tried it, the easiest way to revert is to just delete the entire Hyperbola install
and re-export Hyperbola. Do this now so that the new version does not override other parts of
the example.
14.5.2. Extending Hyperbola
The AddExtensionsAction is added in the same way as the UpdateAction. Rather than searching
for updates to features Hyperbola already has, it looks for new features that Hyperbola does not
have. The action itself is very much like the UpdateAction, but with a more complex run()
method. Integrate this action with the rest of Hyperbola as usual.
org.eclipsercp.hyperbola/AddExtensionAction
public class AddExtensionsAction extends Action implements IAction {
private IWorkbenchWindow window;
public AddExtensionsAction(IWorkbenchWindow window) {
this.window = window;
setId("org.eclipsercp.hyperbola.newExtensions");
setText("&Search for Extensions...");
setToolTipText("Search for new extensions for Hyperbola");
setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
Application.PLUGIN_ID, "icons/usearch_obj.gif"));
window.getWorkbench().getHelpSystem().setHelp(this,
"org.eclipsercp.hyperbola.updates");
}
public void run() {
BusyIndicator.showWhile(window.getShell().getDisplay(),
new Runnable() {
public void run() {
UpdateJob job = new UpdateJob(
"Search for new extensions", getSearchRequest());
UpdateManagerUI.openInstaller(window.getShell(), job);
}
});
}
private UpdateSearchRequest getSearchRequest() {
UpdateSearchRequest result = new UpdateSearchRequest(
UpdateSearchRequest.createDefaultSiteSearchCategory(),
new UpdateSearchScope());
result.addFilter(new BackLevelFilter());
result.addFilter(new EnvironmentFilter());
UpdateSearchScope scope = new UpdateSearchScope();
try {
String homeBase = System.getProperty("hyperbola.homebase",
"http://eclipsercp.org/updates");
URL url = new URL(homeBase);
scope.addSearchSite("Hyperbola site", url, null);
} catch (MalformedURLException e) {
// skip bad URLs
}
result.setScope(scope);
return result;
}
}
The interesting part is in getSearchRequest() . This method builds a search request that
explicitly looks for features that are:
Not already installed.
Compatible with the current code base (BackLevelFilter).
Compatible with the current environment (EnvironmentFilter).
On the Hyperbola home update site. The code snippet defaults to look in
http://eclipsrcp.org/updates but you can change this by changing the code or setting the
hyperbola.homebase system property in the config.ini file as outlined in Section 24.3.3,
"config.ini." For example, setting it to files:/c:/site points Update at a test Hyperbola
Update site.
When Hyperbola is run, the action shows up as Help > Add Extensions.... This action creates
the wizard shown in Figure 14-10. Under the Hyperbola site is the list of available Hyperbola
extensions that are not already installed. You can choose from this list and walk through the
remaining steps to get the function downloaded and installed into your running Hyperbola.
Figure 14-10. Add new extensions wizard
Note that these extensions are set up for the full Hyperbolanot the prototypeso they are unlikely
to work if installed.
14.5.3. Managing Extensions
Since you are allowing users to install new extensions into Hyperbola, it is reasonable that you
allow them to discover and manage the extensions they have. The ManageExtensionsAction
shown below opens an Update dialog that shows users what is installed in the system and
allows them to enable or disable elements. In the snippet we include only the run() method as
the rest of the code is pretty standard.
org.eclipsercp.hyperbola/ManageExtensionsAction
public void run() {
BusyIndicator.showWhile(window.getShell().getDisplay(),
new Runnable() {
public void run() {
UpdateManagerUI
.openConfigurationManager(window.getShell());
}
});
}
Add this action to the Help menu and it shows up as Help > Manage Extensions.... Running it
opens the dialog shown in Figure 14-11. At the left are all the locations that contain features
and plug-ins. Update calls these sites. Typically, you have only one site. In each site is a set of
features that has been installed. At the right is information about the current selection and tasks
users can perform on that selection. You can enable or disable them and look at their copyright
and other information.
Figure 14-11. Managing Hyperbola extensions
14.6. Automatic Updates
The Update Manager includes the ability to periodically poll for updates to Hyperbola. In
essence, it is simply a scheduler that runs the UpdateAction according to a policy that you or
the end-user defines. For example, Update can check every day, on particular days of the week,
or every time Hyperbola is started. Users are notified when updates are found. A preference
controls whether or not Update automatically downloads the updates for the user to install.
To enable this behavior, follow the basic steps used above to add the
org.eclipse.update.scheduler plug-in to Hyperbola, that is, first add the plug-in to the target,
then add it to the Hyperbola feature and export Hyperbola. Run Hyperbola and use Hyperbola
> Preferences... > Install/Update > Automatic Updates to get to the preference page
shown in Figure 14-12. These preferences can also be set in the Hyperbola product's preference
initialization file, as discussed in Section 13.5, "Adding Help Content."
Figure 14-12. Automatic Update preferences
14.7. Summary
With the addition of the Update function, Hyperbola is now future-proofed. Distributing bug
fixes is easierdeployed clients can just download the new updates from the Hyperbola update
site. The Update function also allows new and optional features to be distributed over the
Internet. All of this makes it easier and cheaper to manage.
This marks the end of the Hyperbola tutorial. Hyperbola is now a complete XMPP-based chat
client that has an impressive feature list for such a small application (it is only 24 classes and
about 80K of source code). It has a pretty cool UI, is fully branded with its own launcher and
splash screen, has integrated Help, and is packaged to enable updates and extensions.
Part III: The Workbench
The Workbench provides the UI building blocks that make Eclipse applications easy to
write, easy to use, scalable, and extendable. One of the main advantages of using the
Eclipse RCP is the benefit you get from reusing the UI building blocksit allows you to focus
on your domain without having to redesign the wheel. This is evident from the fact that the
Hyperbola application developed in Part II required relatively little code.
But that work only scratched the surface of what is possible. The Workbench is much more
powerful. It is made up of 42 extension points and approximately 350 API classes. It
would take an entire book to do justice to the function provided. Rather than attempt a
broad partial coverage, the next few chapters focus on the parts of the Workbench that are
essential for RCP applications. They dive into their API and use them to solve problems
motivated by Hyperbola in various real-world scenarios.
You should come away from this part of the book with a solid understanding of the
Workbench's structure and how you can control and customize its operation.
Chapter 15. Workbench Advisors
In Part II, you got a glimpse of the ways in which the Workbench advisors are used in real
applications, but you may have noticed that many of the advisors' methods were not used. This
chapter explores these additional APIs and explains how they can be used in your application.
Another area that you've started to get familiar with is the Workbench; we end this chapter with
an overview of the Workbench and its extension points.
15.1. What Is an Advisor?
You may recall from Part II that when the Hyperbola application starts, its main job is to call
PlatformUI.createAndRunWorkbench(Display, WorkbenchAdvisor), as shown in the snippet
below. This somewhat inconspicuous method bootstraps your UI by starting the Workbench.
This leads to the creation, configuration, and opening of the application windows.
org.eclipsercp.hyperbola/Application
public class Application implements IPlatformRunnable {
public Object run(Object args) throws Exception {
Display display = PlatformUI.createDisplay();
try {
int returnCode = PlatformUI.createAndRunWorkbench(display,
new ApplicationWorkbenchAdvisor());
if (returnCode == PlatformUI.RETURN_RESTART)
return IPlatformRunnable.EXIT_RESTART;
return IPlatformRunnable.EXIT_OK;
} finally {
display.dispose();
}
}
}
The Workbench, however, does not know how the application should behave or lookthat's where
the Workbench advisors come in. As their name implies, advisors give advice to the Workbench.
In doing so, they influence what the UI contains and how it looks and feels.
Instead of changing the behavior of the Workbench, say by subclassing, the Workbench
aggregates the advisors and allows them to participate in the running of the Workbench. As
you've seen in Hyperbola, when the Workbench opens a window, it asks an advisor to determine
whether or not it should include a menu bar by calling the
WorkbenchWindowAdvisor.preWindowOpen() method.
Adviser or advisor?
The dictionary says that either spelling is correct, but common usage says that an
"adviser" is someone defined by what they are doing: defined by a verb, one who is
advising or dispensing advice. An "advisor" is someone defined by what they
aredefined by what they continually do. They are in the position of providing advice.
For a bit of history, search the newsgroups for arguments that arose when the term
was first coined (see http://dev.eclipse.org/mhonarc/lists/platform-uidev/msg01362.html).
There are three types of advisors and each is characterized by the part of the Workbench to
which it gives advice:
WorkbenchAdvisor This advisor provides application-level advice. It participates in the start
up and shut down of the Workbench itself; there is one running Workbench per running
Eclipse application.
WorkbenchWindowAdvisor This advisor provides window-level advice. It participates in
showing or hiding the menu, toolbar, and status line, and in configuring the controls
shown in the window. There is one WorkbenchWindowAdvisor instance for each window.
ActionBarAdvisor This advisor provides window-level advice and helps define the actions
that appear in the menu, toolbar, and status line of each window. There is one
ActionBarAdvisor instance for each window.
Each advisor has an associated configurer that provides privileged access to the Workbench,
Workbench window, and window's action bars (e.g., menu, toolbar, and status line). There's
one configurer for each advisor type. Since the configurers provide privileged access to the
Workbench, they should never be passed around to other plug-ins. The three different types of
configurers are: the IWorkbenchConfigurer, IWorkbenchWindowConfigurer, and
IActionBarConfigurer. The following snippets demonstrate how each can be used within their
associated advisor:
org.eclipsercp.hyperbola/ApplicationWorkbenchAdvisor
public void initialize(IWorkbenchConfigurer configurer) {
configurer.setSaveAndRestore(true);
}
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void preWindowOpen() {
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
configurer.setInitialSize(new Point(250, 350));
configurer.setShowMenuBar(true);
...
}
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillMenuBar(IMenuManager menuBar) {
IMenuManager mainMenu = getActionBarConfigurer().getMenuManager();
MenuManager hyperbolaMenu = new MenuManager(
"&Hyperbola", "hyperbola");
hyperbolaMenu.add(exitAction);
MenuManager helpMenu = new MenuManager("&Help", "help");
helpMenu.add(aboutAction);
mainMenu.add(hyperbolaMenu);
}
The advisors have another very important rolethey define top-level integration points for the
product. For example, the advisors decide the names for the groups and separators within the
top-level menu, toolbar, and status line. Plug-ins contributing to the product must use these
names as integration points when defining and placing their own actions. More on this is
discussed in Section 17.2.2, "Allowing Contributions."
Advisors are very closely linked to the concept of a product. Although this relationship is not
technically enforced, for example, the product extension point does not include details about the
advisors, from the outside they are often seen as one. You typically have one set of advisors for
each of your products. As you've seen, a product as defined by the product extension point
refers to one application (e.g., IPlatformRunnable), and each application is associated with one
set of advisors.
15.1.1. Workbench Lifecycle
To understand the full scope of how advisors are used to configure the Workbench, let's take a
look at the Workbench lifecycle and the points at which the different advisors participate.
Under the covers, when PlatformUI.createAndRunWorkbench() is called, the Workbench
performs the following high-level steps:
Initializes an exception handler to trap uncaught exceptions.
Opens the main window, creating the default controls (toolbar, status line, perspective
switcher, perspective), and restores any saved state.
Takes down the splash screen.
Opens one or more windows and initializes them with UI settings that have been saved
from the last session.
Runs the SWT event loop.
When IWorkbenchWindow.close() is called, the windows' states are saved, then the
windows are closed.
Figure 15-1 shows the typical runtime interaction between the Workbench and the various
advisors.
Figure 15-1. Workbench advisors' lifecycle events
[View full size image]
When the Workbench runs, it allows the WorkbenchAdvisor to participate from the very
beginning by calling WorkbenchAdvisor.initialize(). This happens before any windows are
opened. As each window is opened, a WorkbenchWindowAdvisor is created and associated with
that window instance. This WorkbenchWindowAdvisor instance is consulted throughout the
window's lifecycle. The ActionBarAdvisor is created by the WorkbenchWindowAdvisor before the
window is opened and participates in populating the menu, toolbar, and status line of each
window. The next sections reveal more detail about the lifecycle and APIs for each type of
advisor.
15.2. WorkbenchAdvisor
The WorkbenchAdvisor in Hyperbola played only a small part in the overall application. It
initialized settings in the WorkbenchAdvisor method initialize() and then hooked the chat
listener to process incoming chats. This is very typical of a WorkbenchAdvisorinitializing
application lifecycle settings and cleaning up when the Workbench shuts down.
It is tempting to use the lifecycle methods in your WorkbenchAdvisor to trigger initialization of
additional parts of your application. Resist that urge! The work done here sits squarely in the
path of displaying the application's first windowthe more work you do, the longer your users
wait to see your application. This can't be stressed enough: Do as little as possible in the
WorkbenchAdvisor's initialization code.
Note
A good technique for minimizing initialization costs is to initialize resources only when
first accessed. For example, do not bother creating fonts or images until they are
needed. This has the added benefit of saving space since only those resources being
used are allocated. Note also that this approach applies to the initialization of model
data structures and references to other plug-ins.
The WorkbenchAdvisor's API methods fall into three categories:
Lifecycle Allows the advisors to participate in startup and shutdown of the application.
Exception and idleness Allows the advisor to participate when an exception occurs and
when the application is idle.
Configuration Allows the advisor to determine default values for global application
settings.
15.2.1. Lifecycle API
You should use the lifecycle methods to initialize, restore, and then save and shut down aspects
of your application. The description for each method in Table 15-1 provides a hint as to the type
of work typically done in each method.
Table 15-1. Workbench Lifecycle Events
Method
Description
initialize(IWorkbenchConfigurer) This marks the beginning of the advisor's lifecycle and is
called during Workbench initialization prior to any windows
being opened. This is a good place to parse the command
line and register adaptors. Most applications call
IWorkbenchConfigurer.setSaveAndRestore(true) to ensure
that the Workbench settings are saved on exit. See 15.2.1.1,
"IWorkbenchConfigurer," for information on the
IWorkbenchConfigurer.
preStartup()
Called after the Workbench has been initialized and just
before the first window is about to be opened. This is a good
place to configure settings that affect the perspectives,
views, and editors to be shown, for example, when
overriding the initial perspective.
openWindows()
Called by the Workbench to restore the Workbench state and
open the Workbench windows. This method is rarely
overridden.
restoreState(IMemento)
Called from within openWindows() when the Work-bench is
starting. The given memento contains settings that have
been saved during the last shutdown from saveState().
createWorkbenchWindowAdvisor Called before each Workbench window is opened to create
(IWorkbenchWindowConfigurer) the WorkbenchWindowAdvisor for the window.
postStartup()
Performs arbitrary actions after the Workbench windows
have been opened or restored, but before the main event
loop is run. This is a good place to start any background jobs
such as auto-update daemons.
preShutdown()
Called immediately prior to Workbench shutdown before any
windows have been closed. The advisor may veto a regular
shutdown by returning false. Advisors should check
IWorkbenchConfigurer.emergencyClosing() before
attempting to communicate with the user.
postShutdown()
Called during Workbench shutdown after all windows have
been closed. Advisors should check
IWorkbenchConfigurer.emergencyClosing() before
attempting to communicate with the user.
saveState(IMemento)
Called when the Workbench is shutting down and allows the
WorkbenchAdvisor to save state that is persisted to disk. This
state is available to the restarted application via
restoreState(IMemento).
15.2.1.1. IWorkbenchConfigurer
When the WorkbenchAdvisor method initialize(IWorkbenchConfigurer) is called, it's provided
with an IWorkbenchConfigurer. The configurer is used to configure the Workbench and provides
privileged access to it, for example, telling it to save Workbench settings or registering images
with the Workbench. The configurer can be accessed after the initialize() method is called by
calling the WorkbenchAdvisor method getWorkbenchConfigurer().
The IWorkbenchConfigurer is mostly used to:
Set and get properties on the configurer using the setData(String, Object) and
geTData(String) methods. This is useful for passing data from the WorkbenchAdvisor to the
WorkbenchWindowAdvisor or ActionBarAdvisor.
Close the Workbench in case of emergency and find out if it's closing via the
emergencyClose() and emergencyClosing() methods.
Determine if the Workbench should exit when the last window is closed via the
setExitOnLastWindowClose(boolean) . If the Workbench does not exit and the event loop
keeps running, you can either open another window using
IWorkbench.openWorkbenchWindow(String, IAdaptable) or close the Workbench by calling
IWorkbench.close() .
Because advisors have privileged access to the Workbench, it's best to keep both the
configurers and advisors private and not pass them to other plug-ins.
15.2.1.2. Closing the Workbench
The Workbench can be closed anytime after the WorkbenchAdvisor method initialize() is
called. There are three ways of closing the Workbench:
IWorkbench.close() This method performs a normal shutdown. In Hyperbola, the
ActionFactory.QUIT_ACTION calls this method to exit the Workbench.
IWorkbench.restart() This method performs a normal shutdown and causes the
application to restart immediately.
IWorkbenchConfigurer.emergencyClose() This method causes Eclipse to exit gracefully but
immediately. It is called when a fatal error occurs and the application cannot risk a normal
shutdown. The Workbench attempts to at least save the user's settings.
15.2.1.3. Workbench Preferences
In addition to configuration settings available on the IWorkbenchConfigurer, there are additional
Workbench preferences to control the look and feel of the application. These include setting the
perspective bar's location, the fast view bar's location, and the use of traditional versus curvy
tabs. Table 15-2 lists the most popular public preferences for RCP applications. See the
org.eclipse.ui.IWorkbenchPreferenceConstants interface for a complete list.
Table 15-2. Useful Workbench Customization Preferences Defined by
org.eclipse.ui
Preference
Name Description
PRESENTATION_FACTORY_ID
Overrides the presentation used to display editors and
views. See Chapter 19, "Customizing the Presentation of
Views and Editors," for a detailed look at changing the
look and feel of editors and views.
Preference
Name Description
SHOW_TRADITIONAL_STYLE_TABS Eclipse 3.0 has the ability to show curved tabs for editors
and views. This preference is used to switch between
rectangular (traditional) and curved tabs. Note that the
IDE application sets this preference to false by default. By
default, this preference is set to true.
EDITOR_MINIMUM_CHARACTERS
Sets the minimum number of characters shown on editor
tabs before they get shortened when many editors are
opened. This preference's default value is 8.
KEY_CONFIGURATION_ID
Sets the default key configuration to use for key bindings.
This preference's default value for RCP applications is
org.eclipse.ui.defaultAcceleratorConfiguration. See
Section 12.4, "Key Configurations," for an example of
using this preference.
SHOW_PROGRESS_ON_STARTUP
Eclipse 3.1 added the ability to show a progress dialog at
startup. If this preference is enabled, then shortly after
the splash screen is shown, a progress window appears,
showing which plug-ins are being loaded. By default, this
preference is set to false.
The WorkbenchAdvisor can set preferences in the initialize() method as shown below.
PlatformUI.getPreferenceStore().setValue(
IWorkbenchPreferenceConstants.SHOW_TRADITIONAL_STYLE_TABS, true);
As described in Section 13.5, "Adding Help Content," preferences can also be initialized using
the product preference customization file. For example, add the following to your product's
preferences customization file to show the perspective bar and fast view bar on the left, and to
use curvy tabs. The advantage of using a customization file is that it allows different products
that may be built from your application to have different values for these preferences.
org.eclipsercp.hyperbola/preferences.ini
org.eclipse.ui/DOCK_PERSPECTIVE_BAR=left
org.eclipse.ui/SHOW_TEXT_ON_PERSPECTIVE_BAR=false
org.eclipse.ui/SHOW_TRADITIONAL_STYLE_TABS=false
15.2.2. Exceptions and Idleness API
The Workbench runs a standard SWT event loop. It is possible that bugs in the application, a
constituent plug-in, or critical situations (e.g., running out of memory) in the system can cause
exceptions to be thrown into the event loop code. When this happens, the Workbench's event
loop calls WorkbenchAdvisor. eventLoopException() , as shown in the snippet below. Table 15-3
also lists the exception and idleness methods available on the WorkbenchAdvisor.
org.eclipse.ui.workbench/Workbench
while (loopShell != null && !loopShell.isDisposed()) {
try {
if (!display.readAndDispatch())
display.sleep();
} catch (Throwable e) {
wb.getAdvisor().eventLoopException(e);
}
}
Table 15-3. Workbench Event Loop Methods
Method
Description
eventLoopException(Throwable)
This method is called when an unchecked exception
propagates to the Workbench's event loop. The default
implementation logs the exception and keeps the
Workbench running. The Workbench also tracks how
many times this method was called since fatal recursive
exceptions (out of memory, stack overflow) may have
occurred, and instead of prompting many times, allow
the user to close the Workbench. Call
IWorkbench.emergencyClose() from this method.
eventLoopIdle(Display)
This method is called when there are no more events on
the queue. It is safe to call IWorkbench.close() from this
method.
Even though this usually means that the application is in an invalid state, the
WorkbenchAdvisor's default implementation logs the exceptions and allows the application to
keep running. Your advisor can extend this exception handling by, for example, prompting the
user to take some action such as logging a bug report or by shutting down the application. To
shut down the Workbench in an emergency, the WorkbenchAdvisor should call the
IWorkbenchConfigurer method emergencyClose().
To avoid causing such emergencies, you should write your code defensivelycatching and
handling exceptions locally. This can be challenging since you do not have control over the
exceptions thrown during calls to third-party code. To help, the Runtime provides the platform
method run(ISafeRunnable) , a convenient mechanism for safely running untrusted code.
The ISafeRunnable interface contains two methods: run() and handleException(Throwable).
You simply wrap calls to untrusted code in the run() method. When ISafeRunnable is executed,
the platform catches all exceptions and allows handleException() method to handle them. Since
the platform automatically logs exceptions, your handleException() should focus on recovering
from the problem if possible. The following snippet shows a typical use of ISafeRunnable when
notifying listeners. If the listener fails, the code moves on to the next listener, trusting that the
failure has been logged.
for (int i = 0; i < listeners.length; i++) {
final ContentTypeChangeEvent event = new ContentTypeChangeEvent(type);
final IContentTypeChangeListener listener =
(IContentTypeChangeListener) listeners[i];
ISafeRunnable work = new ISafeRunnable() {
public void handleException(Throwable exception) {
// already logged in Platform#run()
}
public void run() throws Exception {
listener.contentTypeChanged(event);
}
};
Platform.run(work);
}
15.2.3. Configuration API
When a Workbench window is opened for the first time, it asks the WorkbenchAdvisor for the
settings that are used to create the initial perspective and page input. Without this information,
your application does not start. The information is provided by the methods listed in Table 15-4.
These are used only once to initialize the setting. Subsequent runs use the values saved with
the Workbench settings.
Table 15-4. Workbench Configuration Methods
Method
Description
getInitialWindowPerspectiveId()
Returns the initial perspective id. In Hyperbola, this
method returns org.eclipsercp.hyperbola.perspective,
used for new Workbench windows. If this method returns
null, then the Workbench window is considered empty
and the control returned from WorkbenchWindowAdvisor.
createEmptyWorkbenchWindow(Composite) is shown instead
of the contents of a perspective.
getMainPreferencePageId()
Returns the preference page that should be displayed first
in the list. Defaults to null, indicating that pages should
be arranged alphabetically.
getDefaultPageInput()
Returns the page inputan opaque IAdaptablethat is
passed to new IWorkbenchPage objects when they are
opened. The page input is then available to all views and
editors on the page via
IWorkbenchPart.getSite().getPage().getInput(). This
mechanism is handy for passing a context to all
components of a perspective.
15.3. WorkbenchWindowAdvisor
In Hyperbola, WorkbenchWindowAdvisor plays a more visible role than WorkbenchAdvisor. It
configures the window's title, the visibility of the menu bar, toolbar, and status line, and many
more things. Essentially, this advisor controls the appearance of each Workbench window.
The methods on WorkbenchWindowAdvisor relate to the lifecycle of the window instead of the
Workbench's lifecycle. Within the lifecycle methods, the advisor can configure the window via its
IWorkbenchWindowConfigurer. Think of the configurer as providing special APIs that allow the
advisor to fine-tune elements of the WorkbenchWindow that are not available via the standard
IWorkbenchWindow APIs. As such, the configurer has several useful methods such as
setInitialSize(Point), setShowToolbar(boolean) , setTitle(String), and
setShowMenuBar(boolean) . The following snippet shows a typical use of this advisor:
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void preWindowOpen() {
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
configurer.setInitialSize(new Point(400, 300));
configurer.setShowCoolBar(true);
configurer.setShowStatusLine(true);
configurer.setShowMenuBar(true);
}
public void dispose() {
statusImage.dispose();
trayImage.dispose();
trayItem.dispose();
}
public void postWindowOpen() {
initStatusLine();
final IWorkbenchWindow window = getWindowConfigurer().getWindow();
trayItem = initTaskItem(window);
if (trayItem != null) {
hookPopupMenu(window);
hookMinimize(window);
}
}
The Hyperbola WorkbenchWindowAdvisor overrides a few methods such as:
preWindowOpen() Configures the parts of the window that should be visible and sets the
initial window size, as shown in the snippet above.
postWindowOpen() Configures things that need the window to be created, for example, to
set up system tray integration.
Working through the tutorial in Part II is the best way to understand how a
WorkbenchWindowAdivsor is used. For more advanced Workbench window customization, see
Chapter 18, "Customizing Workbench Windows," and Chapter 19, "Customizing the
Presentation of Views and Editors."
15.3.1. IWorkbenchWindowConfigurer
As shown in the previous code snippet, the window's configurer is most often used in the
WorkbenchWindowAdvisor to show and hide items in the window. It can also be used to:
Set and get properties on the configurer using the setData(String, Object) and
geTData(String) methods. This is useful for passing data from this advisor to the
ActionBarAdvisor.
Allow the advisor to create the menu, toolbar, and status line itself and arrange these
controls in different ways. See Chapter 17, "Actions," for examples of using these methods
with Hyperbola.
Configure the drag and drop behavior of the editor's area. See Section 16.4, "Drag and
Drop with Editors."
15.4. ActionBarAdvisor
The ActionBarAdvisor is responsible for the actions shown in the top-level menu, toolbar, and
status line of your application. As you saw in Chapter 6, "Adding Actions," this advisor has a
very straightforward lifecycle. It creates the actions when makeActions(IWorkbenchWindow) is
called and positions the actions in their respective areas when fillMenuBar() , fillCoolBar() ,
and fillStatusLine() are called. When the window is closed, the dispose() method is called so
it can clean up. The following snippet shows a typical use of this advisor:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
exitAction = ActionFactory.QUIT.create(window);
register(exitAction);
aboutAction = ActionFactory.ABOUT.create(window);
register(aboutAction);
addContactAction = new AddContactAction(window);
register(addContactAction);
...
}
protected void fillMenuBar(IMenuManager menuBar) {
MenuManager hyperbolaMenu = new MenuManager("&Hyperbola", "hyperbola");
hyperbolaMenu.add(addContactAction);
hyperbolaMenu.add(chatAction);
hyperbolaMenu.add(new Separator());
hyperbolaMenu.add(preferencesAction);
hyperbolaMenu.add(new Separator());
hyperbolaMenu.add(exitAction);
MenuManager helpMenu = new MenuManager("&Help", "help");
helpMenu.add(helpAction);
...
}
The following describes the responsibility of the ActionBarAdvisor:
Creates the fixed menu, toolbar, and status line structure for the application.
Adds placeholders for contributed actions and menus.
Creates the static actions and registers them with the Workbench to enable key bindings.
Reading Chapter 6 and seeing how it is used in building Hyperbola is the best way to learn
about ActionBarAdvisor. Chapter 17 contains more advanced examples.
15.4.1. IActionBarConfigurer
As shown in the previous code snippet, the action bar's configurer is only used in
ActionBarAdvisor to access the areas in the window that can accept actions and contributions.
15.5. Workbench Overview
There is much more to the Workbench than the advisors. In Part II, you saw that much of the
Workbench configuration is done by adding extensions to the many Workbench extension
points. This section provides a quick overview of the overall role of the Workbench and a
reference to its many extension points.
As JFace adds structure to SWT, the Workbench adds presentation and coordination to JFace.
Consider the Workbench as providing the following function to your application:
Configures and manages windows The first Workbench window is opened using
PlatformUI.createAndRunWorkbench(Display, IWorkbenchAdvisor) during application
startup. This also runs the event loop and manages all subsequent windows. As an RCP
application, you have special status with the Workbenchyou provide a set of Workbench
advisors that participates in the management of the Workbench and the windows that it
opens. This part of the Workbench is specific to RCP applications since only one application
plug-in can define the running of the Workbench.
Defines a UI paradigm Chapters 5, "Starting the Hyperbola Prototype," and 7, "Adding a
Chat Editor," introduced the RCP basic building blocks of the Workbench UI: perspectives,
views, and editors. These common UI components define the way application content is
shown to the user. A perspective is a visual container for a set of views and
editorseverything shown to the user is in a view or editor and is laid out by a perspective.
There are many APIs available to manage and control these components of the UI.
Provides contribution-based extensibility While JFace defines the notions of actions,
preferences, wizards, windows, etc., the Workbench defines extension points that expose
these elements. For example, the Workbench's wizard and preference page extension are
just thin veneers over the related JFace constructs.
The use of extension points to build UIs has a fundamental impact on the scalability of the
interface both in terms of complexity and performance. Since all these extensions are
handled lazily, applications scale better with respect to performance. As the UI gains
views, editors, actions, etc., the additional contributions are available, but are not loaded
or executed until it is requiredno code is loaded before its time.
The use of extension points allows multiple unrelated plug-ins to co-exist within the same
dialogs and menusUI elements do not need to know about each other to be integrated.
Browsing the Workbench
Since the Workbench contains a significant body of code and extension points, it's
worth a few words on how to browse the code and discover its capabilities.
Online help
The Eclipse IDE online help shown in Figure 15-2 contains a complete set of
reference material for both APIs and Workbench extension points. While this is a
good place to look for information on standard Workbench features, most of the
content is general-purpose and does not focus on RCP issuesthat is why you have
this book!
Figure 15-2. Platform Developer Guide reference section
Browsing the source
You can also browse the Workbench code from within the IDE and take advantage of
the search facilities such as Search > Search..., and Navigate > Open * to see
how other plug-ins use the Workbench facilities. This is a very effective means of
learning how to use the APIs.
You can browse the extension point documentation using the plugin.xml editor.
Open the Plug-ins view using Window > Show View > Other ... > PDE > Plugins, and from there, you can open a plug-in editor on any plug-in in your target or
workspace. Both the Extensions and Extension Points pages have an Open
extension point description line that opens the documentation for the extension
point.
15.5.1. Workbench Extension Point Reference
Throughout the tutorial in Part II, you encountered most of the important extension points
defined by the Workbench. For example, in Chapters 5 and 7, you added a view, an editor, and
a perspective to Hyperbola. This section gives you a quick overview of all the Workbench
extension points. This provides context for the following chapters and provides a reference for
extension points that are not described later but that may be useful in your RCP application.
Note
Some extension points are not fully covered in this book because they are covered
well in existing documentation or are not specific or key to RCP use cases.
Although the list of over 42 Workbench extension points looks daunting, categorizing them
makes it much more tractable. The next sections (including Tables 15-5 through 15-9)
summarize the categories and give short descriptions of each of the Workbench's extension
points. When possible, forward pointers to other chapters in the book are provided in an
extension point's description.
15.5.2. Actions
The extension points in Table 15-5 support adding menus, menu items, and toolbar buttons to
windows, editors, and views. Also, as shown in Chapter 12, "Adding Key Bindings," you can use
them to add key bindings to existing actions.
Table 15-5. Action Extension Points at a Glance
Extension Point
Description
actionSets
Used to add menus, menu items, and toolbar buttons to the
common areas in the Workbench window. These contributions,
collectively known as an action set, appear in a Workbench window
by adding them to a perspective. See Chapter 16, "Perspectives,
Views, and Editors," for how to add action sets to a perspective,
and Chapter 17, "Actions," for how to define and use actions sets
in RCP applications.
actionSetPartAssociation Used to define an action set to be added to a perspective when a
part (view or editor) is opened in the perspective. In the case of an
editor, the action set remains visible while the editor has focus. In
the case of a view, the action set is visible when the view has
focus.
commands
Used to declare commands and command categories and assign
key sequences to commands. Commands are useful for assigning
key bindings. See Chapters 12 and 17.
dropActions
Used to allow drag and drop between unrelated plug-ins. Due to
the UI layering imposed by the plug-in design, views are often not
aware of the content and nature of other views. This mechanism
delegates the drop behavior back to the originator of the drag
operation.
Extension Point
Description
editorActions
Used to add actions to the menu and toolbar for editors registered
by other plug-ins.
popupMenus
Used to add new actions to context menus owned by other plugins. The plug-in defining the context menu has to explicitly register
with the Workbench to accept contributions from other plug-ins.
See Chapter 17.
viewActions
Used to add actions to the pull-down menu and toolbar for views
registered by other plug-ins.
15.5.3. Scalability
The extension points described in Table 15-6 support the creation of large-scale applications.
For example, activities are used to automatically or manually hide certain elements of the UI.
Table 15-6. Scalability Extension Points at a Glance
Extension Point
Description
activities
Used by the Platform to filter contributions from the Workbench until such
time that a user expresses interest in them. This allows the Workbench to
dynamically reveal contributions based on the usage pattern of a user.
It's important to note that only UI elements contributed declaratively via
extensions can be filtered.
elementFactories
Used to add element factories to the Workbench. An element factory,
IElementFactory, is used to recreate IAdaptable objects that are
persisted during Workbench shutdown, for example, to restore editors or
persist objects during drag and drop operations. See Section 16.4 for a
drag and drop example.
themes
Allows the definition of colors, fonts, and other presentation theme
preferences. Themes allow applications to selectively override default
color and font specifications for particular uses. See IThemeManager and
ExtensionFactory.COLORS_AND_FONTS_PREFERENCE_PAGE.
workingSets
Used to define a working set wizard page. Working sets contain a number
of elements of type IAdaptable and can be used to group elements for
presentation to the user or for operations on a set of elements. A working
set wizard page is used to create and edit working sets that contain
elements of a specific type.
15.5.4. Contributions
The extension points in Table 15-7 allow the contribution of pages, wizards, and other standard
elements to the UI.
Table 15-7. Contribution Extension Points at a Glance
Extension Point
Description
decorators
Used to add label decorators to JFace viewers in other unrelated plugins, for example, to add CVS decorations into the Navigator and
Package Explorer.
propertyPages
Used to add additional property pages for objects of a given type. RCP
applications can open a generic properties dialog by using the
ActionFactory.PROPERTIES actions defined by the Workbench.
preferencePages
The Workbench provides a common dialog box for preferences. The
purpose of this extension point is to allow plug-ins to add pages to the
Preferences dialog box. When the Preferences dialog is opened,
usually from the menu bar, it's populated with contributed pages. See
Chapter 11, "Adding a Login Dialog," for an example.
newWizards
Used to register element creation wizard extensions. Creation wizards
appear as choices within the new dialog. See ActionFactory.NEW and
Section 17.5, "Consolidating Declarative Actions," for examples.
importWizards
Used to register import wizard extensions. Import wizards appear as
choices in the Import dialog and are used to import artifacts into the
Workbench. See ActionFactory.IMPORT.
exportWizards
Used to register export wizard extensions. Export wizards appear as
choices in the Export dialog and are used to export artifacts from the
Workbench. See ActionFactory.EXPORT.
systemSummarySections The Workbench provides an About dialog that can be branded and
reused by client product plug-ins. This dialog can show other dialogs
containing configuration details. Clients can add their own information
in this summary by contributing to this extension point. See
ActionFactory.ABOUT.
15.5.5. Perspectives
The extension points in Table 15-8 are used to define the basic components of the UI in terms of
views, editors, and perspectives. Most RCP applications have at least one perspective and
minimally one view. Perspective extension points are meant to allow plug-ins to contribute to
perspectives contributed from other plug-ins.
Table 15-8. Perspective Extension Points at a Glance
Extension Point
Description
views
Used to define additional views for the Workbench. A view is a
visual component within a Workbench page.
editors
Used to add new editors to the Workbench. An editor is a visual
component within a Workbench page. It is typically used to edit
or browse a document or input object. See Chapters 7 and 16.
Extension Point
Description
perspectives
Used to add perspective factories to the Workbench. A perspective
factory is used to define the initial layout and visible action sets
for a perspective. See Chapters 5, 7, and 16.
perspectiveExtensions
Used to extend perspectives registered by other plug-ins.
presentationFactories
Used to register a presentation factory. A presentation factory
defines the overall look and feel of the Workbench, including how
views and editors are presented. See Chapter 19.
15.5.6. Startup
The extension points in Table 15-9 relate to the behavior of the Workbench when the application
starts up.
Table 15-9. Startup Extension Points at a Glance
Extension Point
Description
intro
This extension point is used to register implementations of special
Workbench parts, called intro parts. Intro parts are responsible for
introducing a product to new users. An intro part is typically shown the
first time a product is started.
startup
Used to register plug-ins that want to be activated on startup.
15.6. Summary
In this chapter, you got a glimpse of the Workbench features available to you when building
RCP applications. The features in the Workbench exist because they were needed when building
the Eclipse IDE productnot because the Eclipse RCP needed a UI framework. That is, they
address real, concrete problems but are general enough to be used in other situations. After all,
the IDE is essentially a very sophisticated RCP application!
The next couple of chapters are dedicated to uncovering the essential RCP-specific Workbench
features.
Chapter 16. Perspectives, Views, and
Editors
The Hyperbola UI described in Part II is purposely simple, but it still touches on most of the
central features of the Workbench's UI paradigm; in particular, perspectives, views, and editors.
If you take a careful look at the Eclipse IDE's UI, you can see that much more can be done with
these building blocks than what we've used so far in Hyperbola. For example, as shown in
Figure 16-1, the Workbench allows multiple windows to be opened and each window to have
multiple perspectives. You can also define perspectives that are fully customizable by the
userviews can be moved, minimized, closed, or arranged as fast views. RCP applications can
use this power to provide a rich user experience and many layers of functionality without
overwhelming the user.
Figure 16-1. Runtime composition of perspectives, views, and editors
[View full size image]
In this chapter, we extend Hyperbola to have multiple perspectives and multiple windows, and
to use advanced view manipulation techniques. Specifically, we show you how to:
add perspectives
use the programmatic perspective and view APIs
use multiple instances of a view and sticky views
open and track multiple Workbench windows
connect parts together
add drag and drop support to the editor area
16.1. Perspectives
Perspectives group together and organize UI elements that relate to a specific task or workflow.
For example, in the Eclipse IDE, the Java perspective contains views for editing Java source
files, while the Debug perspective contains views for debugging Java programs. You can have
multiple perspectives in the same window, allowing you to switch tasks without having to
change windows.
To show how perspectives can be used in RCP applications, let's walk through the steps required
to add two new perspectives, "Free Moving" and "Debug", to Hyperbola. The Free Moving
perspective allows the Contacts view to be moved around by the user. The Debug perspective,
as shown in Figure 16-2, shows a console with the XMPP output of all incoming and outgoing
messages.
Figure 16-2. The Debug perspective in Hyperbola
16.1.1. Adding Perspectives
The first step is to define the new perspective extensions and perspective factory classes. Open
Hyperbola's plug-in editor and add two new perspective extensions to the existing
org.eclipse.ui.perspectives extension point, as shown in Figure 16-3. The steps are the same
as in Chapters 4, "The Hyperbola Application," and 5, "Starting the Hyperbola Prototype."
Figure 16-3. Two new perspectives added to Hyperbola
[View full size image]
Notice that the original Hyperbola perspective does not have an icon. An icon was not needed
because the perspective concept was never exposedusers just saw the Hyperbola window. In
this new Hyperbola, perspectives are exposed; therefore, you need icons. For each of the new
perspectives and for the default perspective, supply a meaningful name and icon.
The class attribute defines an IPerspectiveFactory that generates the initial page layout and
visible action sets for a page. You can use the usual trick of clicking on the class attribute to
launch the New Class wizard. Name the perspective classes "PerspectiveFreeMoving" and
"PerspectiveDebug".
The Free Moving perspective is the same as the original Hyperbola perspective except that the
Contacts view has a title bar and is moveable. So, instead of calling
IPageLayout.addStandaloneView(), call IPageLayout.addView() as shown in the following:
org.eclipsercp.hyperbola/PerspectiveFreeMoving
public class PerspectiveFreeMoving implements IPerspectiveFactory {
public void createInitialLayout(IPageLayout layout) {
layout.addStandaloneView(ContactsView.ID, false, IPageLayout.LEFT,
0.33f, layout.getEditorArea());
layout.addView(ContactsView.ID, IPageLayout.LEFT, 0.33f,
layout.getEditorArea());
layout.getViewLayout(ContactsView.ID).setCloseable(false);
}
}
By default, Eclipse views are closeable. To override this behavior, the Free Moving perspective
retrieves the IViewLayout and calls setCloseable(false). The decision to allow views to be
closed depends on how your UI is structured. Preventing views from being closed keeps things
simple for the user. If you decide to allow views to be closed, ensure that users know how to get
them back again. See Section 16.2.1 for examples of actions that open views.
Notice that createInitialLayout() method is passed an IPageLayout. The perspective uses the
layout object to control how the page looks and where views go.
Note
Editors are not directly added to a perspective layout; instead, you position the views
around the area in which the editors are opened. It's also possible to hide the editor
area. It can be made visible later via the IWorkbenchPage.setEditorArea
Visible(boolean) method or by using the org.eclipse.ui.workbench.
ActionFactory.SHOW_EDITOR action.
16.1.2. Adding the Debug Perspective and Console View
The Eclipse Platform includes a very useful Console plug-in that can be used to add a console to
your RCP application. Here we use the console view contributed by this plug-in in the Debug
perspective. The console supports advanced features such as line coloring and hyperlinks. To
add the console to Hyperbola, follow the instructions in Chapter 13, "Adding Help," for adding
plug-ins to your target, but instead of adding the Help plug-ins, add the following:
org.eclipse.text
org.eclipse.jface.text
org.eclipse.ui.console
org.eclipse.ui.workbench.texteditor
Then, to implement a console that displays all outgoing and incoming XMPP message in raw
XML, create the DebugConsole class as follows:
org.eclipsercp.hyperbola/DebugConsole
public class DebugConsole extends MessageConsole {
private MessageConsoleStream outMessageStream;
private MessageConsoleStream inMessageStream;
private PacketListener outListener = new PacketListener() {
public void processPacket(Packet arg0) {
outMessageStream.println(arg0.toXML());
}
};
private PacketListener inListener = new PacketListener() {
public void processPacket(Packet arg0) {
inMessageStream.println(arg0.toXML());
}
};
public DebugConsole() {
super("XMPP Debug", null);
outMessageStream = newMessageStream();
outMessageStream.setColor(Display.getCurrent().getSystemColor(
SWT.COLOR_BLUE));
inMessageStream = newMessageStream();
inMessageStream.setColor(Display.getCurrent().getSystemColor(
SWT.COLOR_RED));
Session.getInstance().getConnection().
addPacketWriterListener(outListener, null);
Session.getInstance().getConnection().
addPacketListener(inListener, null);
}
protected void dispose() {
Session.getInstance().getConnection().removePacketWriterListener(
outListener);
Session.getInstance().getConnection().removePacketListener(inListener);
}
}
Add the new DebugConsole to the Console Manager in the ApplicationWorkbenchAdvisor's
initialize(IWorkbenchConfigurer) method as follows:
org.eclipsercp.hyperbola/ApplicationWorkbenchAdvisor
public void initialize(IWorkbenchConfigurer configurer) {
...
ConsolePlugin.getDefault().getConsoleManager().addConsoles(
new IConsole[] { new DebugConsole(session) });
}
Alternatively, you can use the org.eclipse.ui.console.consoleFactories extension point to
register the Debug console with the Console view. This approach is good if you expect to ship
Hyperbola without a console and have it added by another plug-in. In this case, decoupling the
advisor from the console is a good idea.
The Debug perspective is very similar to the original Hyperbola perspective, but it adds a
Console view below the chat editor area.
org.eclipsercp.hyperbola/PerspectiveDebug
public class PerspectiveDebug implements IPerspectiveFactory {
public void createInitialLayout(IPageLayout layout) {
layout.addStandaloneView(ContactsView.ID, false,
IPageLayout.LEFT, 0.33f, layout.getEditorArea());
layout.addView(IConsoleConstants.ID_CONSOLE_VIEW,
IPageLayout.BOTTOM, 0.70f, layout.getEditorArea());
}
}
The IPageLayout.BOTTOM flag is used to place the Console view below the editor area. The
Console view's id is defined by the Console plug-in and is available from IConsoleConstants.
16.1.3. IPageLayout Reference
Our use of IPageLayout has been straightforward; but in fact, there are many additional useful
methods. These methods can be grouped into two categories:
View placement Methods meant for placing views in the perspective, for example,
methods that stack views together or add placeholders for views to be opened after the
perspective is shown. See Table 16-1 for a complete list.
Perspective configuration Methods to configure other elements associated with a
perspective, for example, which wizards or action sets are shown when the perspective is
activated. Refer to Chapter 17, "Actions," for how to define and use action sets and new
wizards. See Table 16-2 for a complete listing of perspective configuration methods.
Table 16-1. IPageLayout Placement Methods
Method
Description
addView(String id, int
relationship, float
ratio, String refPartId)
Adds a view with the given id to this page layout. Placement of
the view is specified using the relationship, ratio, and
refPartId.
addStandaloneView (String
id, Boolean showTitle,
int relationship, float
ratio, String refPartId)
Adds a standalone view with the given id to this page layout. A
standalone view cannot be docked together with other views. A
standalone view's title can optionally be hidden. If hidden, any
controls typically shown with the title (such as the Close button)
are also hidden.
addPlaceholder (String
id, int relationship,
float ratio, String
refPartId)
Adds a placeholder for a view with the given id to this page
layout. View placeholders are used to define the position of a view
before the view appears. Initially, the placeholder is invisible;
however, if the user ever opens a view with the same id as a
placeholder, the view replaces the placeholder as it is made
visible.
createFolder (String id,
int relationship, float
ratio, String refPartId)
Creates and adds a folder with the given id to this page layout. A
folder is an area that contains several views in the same location.
To the user, this shows as a set of views stacked together.
createPlaceholderFolder
(String id, int
relationship, float
ratio, String refPartId)
Creates and adds a placeholder for a folder with the given id to
this page layout. The placeholder folder allows a set of views to
be described that can be stacked together without having the
views added to the perspective when the perspective is opened.
This is commonly used with IPlaceHolderFolderLayout.
addPlaceholder(String id).
addFastView (String id)
Adds a view to the page layout. By default, the view is hidden and
shown in the perspective's fast view bar.
Table 16-2. IPageLayout Configuration Methods
Method
Description
addPerspectiveShortcut
(String perspId)
Adds a perspective shortcut to the page layout. These are
typically shown in the UI to allow rapid navigation to appropriate
new wizards. The shortcuts are only useful if you are using the
ContributionItemFactory.PERSPECTIVES_SHORTLIST menu. See
Section 16.1.5, "Perspective Menu," for more details.
addNewWizardShortcut
(String wizardId)
Adds a new wizard shortcut to the page layout. These are
typically shown in the UI to allow rapid navigation to appropriate
new wizards.
addShowInPart (String
viewed)
Adds an item to the Show In prompter. These are typically added
to allow quick navigation from an element in one view to an
element in another view. See
ContributionItemFactory.VIEWS_SHOW_IN.
setEditorAreaVisible
(boolean)
Shows or hides the editor area. The editor area is empty when the
perspective is created, but on subsequent starts of the
application, it may contain editors that have been persisted from
the last session.
setFixed(boolean)
Resides in a fixed layout, where parts cannot be moved or
zoomed and the initial set of views cannot be closed. You can also
make individual views non-closeable by calling
IViewLayout.setCloseable(boolean) .
addActionSet (String
actionSetId)
Adds an action set to the current layout. The action set must have
been previously contributed to the Workbench via the
org.eclipse.ui.actionSets extension point. Another way of
associating an actionSet with a perspective is to use the
org.eclipse.ui.perspectiveExtensions extension point. The
perspectiveExtensions allows you to selectively make an action
set visible in a specific perspective as opposed to visible at all
times.
Note
The user can manually detach movable views from a Workbench window by dragging
them, but there is no programmatic or declarative support for creating a detached
view using IPageLayout.
Note
Many of these methods take view identifiers, for example, as the base for relative
positioning. IPageLayout includes constants for view identifiers. They are listed mostly
for convenience and backwards compatibility. We recommend that you define your
layout relative to the editor area and use your own view ids instead of these. It's
entirely possible that these pre-defined view ids are contributed plug-ins (e.g., IDE
plug-ins) that are not included in your application.
If you have more complex layout requirements or want to allow users to drag and stack views
together, refer to the APIs available on IPageLayout, IViewLayout, and IFolderLayout .
16.1.4. Perspective Bar
Hyperbola's two new perspectives are defined, but they are not available to the user yetusers
have no way of opening them. For most applications, providing a way to switch between
perspectives is sufficient. The Workbench provides a perspective switching bar that is hidden by
default in RCP applications. It has to be enabled from the WorkbenchWindowAdvisor to appear, as
shown in Figure 16-4.
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void preWindowOpen() {
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
configurer.setInitialSize(new Point(600, 500));
configurer.setShowCoolBar(true);
configurer.setShowMenuBar(true);
configurer.setShowStatusLine(true);
configurer.setShowPerspectiveBar(true);
...
}
Figure 16-4. Perspective bar
The perspective bar allows switching between open perspectives and includes the ability to open
new perspectives. Its context menu contains several useful options for manipulating
perspectives and the perspective bar itself. You can show or hide perspective text, close
individual or all perspectives, as well as customize, reset, and change the location of the bar.
The perspective bar's default location is just below the toolbar, but it can be placed in the top
right or on the left using the IWorkbenchPreferenceConstants.DOCK_PERSPECTIVE_BAR preference.
You can set this in the Hyperbola preferences.ini file as follows:
org.eclipsercp.hyperbola/preferences.ini
org.eclipse.ui/DOCK_PERSPECTIVE_BAR=TOP_RIGHT
16.1.5. Perspective Menu
Instead of, or in addition to, the perspective switching bar, you can use the Perspective menu.
The Perspective menu displays a list of perspective shortcuts that have been configured for the
active perspective and an Other... item, as shown in Figure 16-5. The Other... item shows a
dialog allowing the user to open any other perspective. If you do not like the wording or the
prompting dialog, you can always write a custom menu and dialog using the perspective APIs
discussed in Section 16.1.6, "Programmatic Perspective Control." But if you want to use it as-is,
simply add the contribution item to an existing menu in the ActionBarAdvisor.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
perspectivesMenu =
ContributionItemFactory.PERSPECTIVES_SHORTLIST.create(window);
}
protected void fillMenuBar(IMenuManager menuBar) {
MenuManager layoutMenu = new MenuManager("Switch Layout", "layout");
layoutMenu.add(perspectives);
menuBar.add(layoutMenu);
}
Figure 16-5. Using the Perspective menu
The menu shows the list of perspective shortcuts that have been added to the perspective layout
via the IPageLayout method addPerspectiveShortcut (String). To keep menus and toolbars
scalable as your application grows, it's important to allow the perspective to control the
available set of actions, perspectives, and view shortcuts. In the case of Hyperbola, each
perspective should add a shortcut for each of the other perspectives, as shown below, so users
can get to any perspective from any other perspective.
org.eclipsercp.hyperbola/Perspective
public class Perspective implements IPerspectiveFactory {
public static final String ID = "org.eclipsercp.hyperbola.perspective";
public void createInitialLayout(IPageLayout layout) {
layout.addStandaloneView(ContactsView.ID, false, IPageLayout.LEFT,
0.33f, layout.getEditorArea());
layout.addPerspectiveShortcut(PerspectiveDebug.ID);
layout.addPerspectiveShortcut(PerspectiveFreeMoving.ID);
}
}
In large applications, shortcuts are added for the most frequently used transitions and the
Others... category is used to navigate to the others. A common performance blooper is to
reference perspectives from other plug-ins in the perspective factory. This causes the other
plug-ins to be loaded at startup.
16.1.6. Programmatic Perspective Control
If the perspective bar and menu give users too much control over your application, you can hide
the bar and add your own actions to switch between perspectives. You may not even want to
expose the term "perspective." One common pattern is to add an action that shows a particular
set of views that matches a workflow the user understands to the top-level menu or toolbar. To
the user, it is a set of arranged views; to the application, it is a perspective.
The following snippet shows a sample action that shows a given perspective in a given window.
This might surface in Hyperbola, for example, as a simple "Debug" menu entry. The action uses
the perspective's id to find the perspective descriptor from the registry. The descriptor contains
the label and icon that were defined in the perspective extension. If you prefer to have
perspectives open in a separate window, you can set the
IWorkbenchPreferenceConstants.OPEN_PERSPECTIVE_WINDOW preference to TRue and then
IWorkbench.showPerspective() opens all new perspectives in their own windows.
org.eclipsercp.hyperbola/SwitchPerspectiveAction
public class SwitchPerspectiveAction extends Action {
private final IWorkbenchWindow window;
private final String id;
private IPerspectiveDescriptor desc;
public SwitchPerspectiveAction(IWorkbenchWindow window, String id) {
this.window = window;
this.id = id;
desc = PlatformUI.getWorkbench().
getPerspectiveRegistry().findPerspectiveWithId(id);
if(desc != null) {
setText(desc.getLabel());
setImageDescriptor(desc.getImageDescriptor());
}
}
public void run() {
try {
PlatformUI.getWorkbench().showPerspective(id, window);
} catch (WorkbenchException e) {
MessageDialog.openError(window.getShell(), "Error",
"Error opening perspective:" + e.getMessage());
}
}
}
All defined perspectives are stored by the Workbench perspective registry and are accessed by a
unique id. Given an id, the following classes offer APIs for manipulating, querying, and
observing perspectives within the Workbench:
IWorkbenchPage The Workbench page has methods for opening, closing, and reverting
perspectives.
IPerspectiveRegistry The registry provides access to the perspectives known to the
Workbench. A perspective descriptor has the information described in the extension point
such as id, name, and icon.
IPerspectiveListeners You can register to receive perspective notifications using the
IWorkbenchWindow.addPerspectiveListener() method. There are three separate listener
interfaces available: IPerspectiveListener, IPerspectiveListener2, and
IPerspectiveListener3. A common use of perspective listeners is to update the title of the
application window when a perspective is changed. See
org.eclipse.ui.workbench.PerspectiveAdapter .
Tip
You may notice when debugging perspectives that you modify the perspective, but the
changes do not appear when you re-launch. We saw this in Chapter 5. It is because
the Workbench saves perspective layouts between launches. If you modify a
perspective in code, you may have to reset the perspective to see your changes. You
can reset a perspective using the context menu on the perspective bar or by calling
IPerspectiveRegistry.revertPerspective(IPerspectiveDescriptor) in your code.
16.2. Views and Editors
While developing Hyperbola in Part II, you learned the basics of using views and editors and the
differences between the two. Typically, an editor shows the main content of your application,
whereas views support this with additional navigation or context-sensitive information related
to the task being done with the editor.
If you are familiar with the Eclipse IDE, then you already know that in Hyperbola, we have
hidden a couple of the standard IDE mechanisms for managing views and editors. For example,
in Hyperbola, the idea of a user opening an arbitrary view is not exposed, whereas in the
Eclipse IDE, there's a Window > Show View menu entry that allows the user to open any
view.
As with many things related to perspectives, views, and editors, it's up to the individual RCP
application to decide how to expose these to the end-users. The goal of this section is to review
some of the advanced RCP-related view and editor features.
16.2.1. Multiple Instances of the Same View
As we have seen in Hyperbola, it's possible to open multiple instances of an editor each with its
unique editor input. It's also possible to open multiple instances of a view. Imagine a more
advanced Hyperbola that allows you to connect to multiple servers at once. Users can then
manage different contact lists or different logins within the same application. To present this,
however, Hyperbola would need a Contacts view for each connection.
There are two techniques for opening multiple instances of the same view in the same
perspective. Regardless of the technique used, the first step is to edit Hyperbola's plug-in
definition and change the enableMultiple property of the Contacts view extension point to true.
Having done that, multiple views can be opened directly in the perspective factory, as shown in
the following. The view id argument of the addView() method can take a qualified view format
as primary-id:secondary-id . The secondary id must be unique within the perspective.
org.eclipsercp.hyperbola/Perspective
public void createInitialLayout(IPageLayout layout) {
layout.addStandaloneView(ContactsView.ID + ":1", false,
IPageLayout.LEFT, 0.33f, layout.getEditorArea());
layout.addView(ContactsView.ID + ":2",
IPageLayout.BOTTOM, 0.70f, layout.getEditorArea());
layout.addView(ContactsView.ID + ":3",
IPageLayout.BOTTOM, 0.70f, layout.getEditorArea());
}
The second technique is to open the secondary views via an action. The OpenViewAction below is
generically defined to open a new instance of a given view id, and the action is added to
Hyperbola as Hyperbola > New Contacts View.
org.eclipsercp.hyperbola/OpenViewAction
public class OpenViewAction extends Action {
private final IWorkbenchWindow window;
private int instanceNum;
private final String viewId;
public OpenViewAction(IWorkbenchWindow window, String viewId) {
this.window = window;
this.viewId = viewId;
}
public void run() {
try {
window.getActivePage().showView(viewId,
Integer.toString(instanceNum), IWorkbenchPage.VIEW_ACTIVATE);
instanceNum++;
} catch (PartInitException e) {
// handle exception
}
}
}
Notice that the view is created with both a view id and a secondary unique instance id. The
secondary id must be remembered if you need to refer to individual views. In this Hyperbola
example, the secondary id does not need to be remembered.
If you run Hyperbola and trigger the New Contact View action, a new Contacts view opens at
a default location, at the bottom right of the editor area in the perspective. This can be
improved. Just as you can define a perspective that contains multiple instances of the same
view, you can define placeholders for these views in the layout. When the views are opened,
they are placed in the pre-defined placeholder locations. The snippet below shows how the Free
Moving perspective is set up to ensure that all Contacts views are opened together in the same
stack.
org.eclipsercp.hyperbola/Perspective
public void createInitialLayout(IPageLayout layout) {
IFolderLayout folder = layout.createFolder("contacts",
IPageLayout.LEFT, 0.33f, layout.getEditorArea());
folder.addPlaceholder(ContactsView.ID + ":*");
folder.addView(ContactsView.ID);
IViewLayout viewLayout = layout.getViewLayout(ContactsView.ID);
viewLayout.setCloseable(false);
layout.addPerspectiveShortcut(Perspective.ID);
layout.addPerspectiveShortcut(PerspectiveFreeMoving.ID);
}
Notice that the Contacts view placeholder uses a multi-instance view format of
primaryId:secondaryId and a * wildcard for the secondary id. This indicates that any Contacts
view with a secondary id should be placed in the folder. Wildcards are also supported for the
primary id.
16.2.2. Sticky Views
A sticky view is a view that stays open across perspective switches. Once open in one
perspective, the sticky view remains open in all perspectives hosted in that Workbench window.
This is true even for perspectives that do not define that view as part of their layout. Sticky
views were added to support instructional aids that can span perspectives such as Help, Intro,
and Cheat Sheets.
Sticky views are defined using the stickyView tag in an org.eclipse.ui.views extension, as
shown in the snippet below:
org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.ui.views">
<view
class="org.eclipsercp.hyperbola.ContactsView"
icon="icons/groups.gif"
id="org.eclipsercp.hyperbola.views.contacts"
name="Contacts"/>
<stickyView
closeable="false"
id="org.eclipsercp.hyperbola.ContactsView"
location="LEFT"
moveable="false"/>
</extension>
The sticky view extension adds a placeholder for the view in the Workbench page, but does not
show the view. The given id must be that of an existing view. When the existing view is opened,
it becomes sticky. Think of this extension as adding the sticky attribute to an already defined
view.
Sharing views and editors
When multiple perspectives are open in the same window, it's common to have the
same views open in multiple perspectives. If two or more perspectives have the
same view open, they share the same view instance. For example, open the Java
and the Java Browsing perspectives in the same window. Then open the Package
Explorer in the two perspectives. Expand some nodes in the Package Explorer in
one perspective and notice that the other perspective for the Package Explorer has
the exact same expansion state. Notice, however, that if you close the view in one
perspective, it is not closed in the others.
Editors are very similar to views in terms of sharing the same instances, but in
addition to being the same instance in all perspectives, closing an editor in one
perspective closes it in all perspectives. For perspectives in different Workbench
windows, neither editors nor views are shared.
16.2.3. Showing Contributed Views
It's quite easy to open specific views if the application knows in advance about all the possible
view ids. In applications that allow views to be contributed by other plug-ins, you may want to
allow users to open views manually.
The Workbench exposes a view shortlist mechanism that you can use to show users a list of
available views. This is similar to the Window > Show View menu entry found in the Eclipse
IDE. Set this up by creating a contribution item in the ActionBarAdvisor and adding it to any
menu. In the example below, the item is added to the Hyperbola main menu.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
views =
ContributionItemFactory.VIEWS_SHORTLIST.create(window);
}
protected void fillMenuBar(IMenuManager menuBar) {
MenuManager viewsMenu = new MenuManager("Open View", "views");
viewsMenu.add(views);
menuBar.add(viewsMenu);
}
The initial list of views shown in the menu consists of the view shortcuts defined by the active
perspective. This list is configured by a perspective using
IPageLayout.addShowViewShortcut(String).
16.2.4. View Registry
Of course, if you don't like the pre-defined view menus or features, you can consult the view
registry and customize how views are managed in your application. The view registry is
accessed using the IWorkbench method getViewRegistry() and has several useful methods for
querying registered views. Each view is described using an IViewDescriptor that includes the
view's name, icon, id, and other properties defined in the view's extension point.
16.2.5. Connecting Parts Together
There's a good chance that your UI partsthe views and editorswill need to communicate. For
example, the Hyperbola Contacts view could show a contact in bold when a chat editor for that
contact is active.
In the Workbench, there are three techniques for communicating between parts. First, let's
review the techniques, then we'll look at how to use one of these techniques to implement
contact entry bolding.
Using the selection The ISelectionService allows views and editors to register their
selection with the Workbench, thus allowing other parts to listen to selection changes and
respond appropriately. To publish selections, use the IWorkbenchSite method
setSelectionProvider (ISelectionProvider), and to subscribe to selections, use the
ISelectionService method addSelectionListener(ISelectionListener). This is a good
technique because it allows parts to be decoupled. We've already used this technique to
connect the Hyperbola actions to the Contacts view and ensure that when the selection
changed, the actions are enabled correctly.
Part listeners You can also connect parts together by listening to the events that are fired
when a part is closed, opened, and hidden. Use IPartService to register for part events.
Again, this is a good technique because it keeps the parts decoupled. This is the technique
that is used in the next snippet to implement the Hyperbola bolding of the contact
example.
Direct communication Whereas the selection and part service allows any part to listen
and react to changes, you can also use a direct connection by having specific views call
back to or open other views or editors (e.g., using the methods on IWorkbenchPage to open
or close parts). For example, the Hyperbola bolding feature can be implemented by
allowing the ChatEditor to notify the ContactsView when it opens. This technique is not
ideal because it places a tight coupling between the parts.
To implement bolding of contacts when a chat is in progress, the Contacts view has to know
when a chat editor is opened and closed and bold the appropriate contact accordingly. The best
technique is for the Contacts view to register a part listener using the IPartService and
remember which chat editors are open. The label provider is refreshed when a part is opened or
closed, and the active chat list is consulted to decide whether or not a contact is bold.
The code changes are simple, and are described below.
Add a part listener to the Contacts view to track which chat editors are opened and closed.
Register and unregister the part listener.
Change the label decorator to set the font of an item and use the list of active chat editors
to determine the font for a contact.
The part listener is added as a field of ContactsView. It remembers the set of open chats in
openEditors and refreshes the labels when a chat editor is opened or closed.
org.eclipsercp.hyperbola/ContactsView
private IPartListener partListener = new IPartListener() {
public void partOpened(IWorkbenchPart part) {
trackOpenChatEditors(part);
}
public void partClosed(IWorkbenchPart part) {
trackOpenChatEditors(part);
}
private void trackOpenChatEditors(IWorkbenchPart part) {
if (! (part instanceof ChatEditor))
return;
ChatEditor editor = (ChatEditor) part;
ChatEditorInput input = (ChatEditorInput) editor.getEditorInput();
String participant = input.getParticipant();
if (openEditors.contains(participant)) {
openEditors.remove(participant);
} else {
openEditors.add(participant);
}
treeViewer.refresh(true);
}
...
};
The part listener is registered with the Workbench when the Contacts view is created and
unregistered when it's closed.
org.eclipsercp.hyperbola/ContactsView
public void createPartControl(Composite parent) {
...
getSite().getWorkbenchWindow().
getPartService().addPartListener(partListener);
}
public void dispose() {
getSite().getWorkbenchWindow().
getPartService().removePartListener(partListener);
}
The existing label provider is augmented with a special decorating label provider that allows
other label decorators to participate in label decorations. The new ContactsDecorator consults
the list of opened chat editors and makes the contacts that have open editors show as bold. It
also implements an IFontProvider as an indication that it can change the font in addition to
providing text and images. The original HyperbolaLabelProvider is still used, but it is wrapped
in the DecoratingLabelProvider instance.
org.eclipsercp.hyperbola/ContactsView
private class ContactsDecorator implements ILabelDecorator, IFontDecorator {
...
public Font decorateFont(Object element) {
if(element instanceof RosterEntry) {
RosterEntry entry = (RosterEntry)element;
if(ContactsView.this.openEditors.contains(entry.getUser()))
return JFaceResources.getFontRegistry().
getBold(JFaceResources.DEFAULT_FONT);
}
return null;
}
}
public void createPartControl(Composite parent) {
treeViewer = new TreeViewer(parent, SWT.BORDER | SWT.MULTI
| SWT.V_SCROLL);
getSite().setSelectionProvider(treeViewer);
HyperbolaLabelProvider hyperbolaLabelProvider = new
HyperbolaLabelProvider();
DecoratingLabelProvider decorator = new
DecoratingLabelProvider(hyperbolaLabelProvider,
new ContactsDecorator());
treeViewer.setLabelProvider(decorator);
...
}
}
The result of these changes is shown in Figure 16-6.
Figure 16-6. Contacts view with bold contacts
Approaches such as this are very powerful. One of the key points here is that the chat editor did
not require modification. You can imagine adding other plug-ins to Hyperbola that also connect
to existing parts without needing to modify those parts. That is, plug-ins have opportunities to
integrate with your application without you needing to know about them.
16.3. Multiple Workbench Windows
The Workbench supports opening multiple top-level windows. These windows are managed by
the Workbench and the WorkbenchWindowAdvisor participates in each window's lifecycle. There
are two techniques for opening a new Workbench window from an RCP application:
Use the ActionFactory.OPEN_WINDOW or the OpenInNewWindow action. These actions open a
new window using the perspective currently showing in the window in which the action is
run.
Call the IWorkbench method openWorkbenchWindow(String, IAdaptable). This method
opens a new window and shows the identified perspective.
16.3.1. Window Navigation Menu
When an application allows multiple windows to be created, it should provide an easy way of
navigating between the windows. The Workbench provides a reusable menu that displays a list
of open windows. Selecting a window causes it to get focus. To add the windows list menu to
your application, add the ContributionItemFactory.OPEN_WINDOWS item to a menu, as shown
below and in Figure 16-7.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
openWindows =
ContributionItemFactory.OPEN_WINDOWS.create(window);
}
protected void fillMenuBar(IMenuManager menuBar) {
...
windowsMenu.add(openWindows);
}
Figure 16-7. Window list menu in action
16.4. Drag and Drop with Editors
Each Workbench window has an editor area that is optionally visible. When the editor area is
visible, it can be used as a target for drag and drop operations. For example, this is used by the
IDE to open an editor on a file dropped on the editor area. This is possible even if the editor
area is blank. Drag and drop on the editor area is set up using the IWorkbenchWindowConfigurer
for a particular window.
To show how this works, let's enhance Hyperbola to allow dragging contacts from the Contacts
view into the editor area to initiate a chat. There are four main parts involved in making this
happen:
Decide which transfer types are to be supported by the editor area and implement them.
In Hyperbola, the transfer type is an IEditorInput that describes the editor to be opened
when the drop completes. Since drag and drop can occur between applications, a transfer
type is used to serialize the required information. Even if the drag and drop occurs within
the same application, a transfer type is needed.
Because editor inputs are serialized when they are transferred in a drag and drop event,
the ChatEditorInput must be modified to implement IPersistableElement.
Add a drop adapter that knows the actions to take when an IEditorInput transfer type is
dropped onto the editor area. Drop adapters are simplethey perform actions based on the
transfer type dropped. In the Hyperbola case, the drop adapter opens an editor.
Enable the Contacts view to initiate a drag operation. The Contacts view also has to create
the editor inputs that are dropped in the editor area.
The next sections step through the code needed for each of these four parts. The transfer type
for dragging editor inputs from one part to another is already defined in the Workbench and is
called EditorInputTransfer. This is the class that serializes the editor inputs into a byte array
that is then added to the drag and drop events. The transfer is added to the Workbench window
by calling the IWorkbenchWindowConfigurer method addEditorAreaTransfer(Transfer) from the
WorkbenchAdvisor, as shown below.
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void preWindowOpen(IWorkbenchWindowConfigurer configurer) {
...
configurer.addEditorAreaTransfer(EditorInputTransfer.getInstance());
configurer.configureEditorAreaDropListener(
new EditorAreaDropAdapter (configurer.getWindow());
}
Notice that this also configures a DropTargetListener to listen for drops in the editor area. The
drop adapter registered is Hyperbola's EditorAreaDropAdapter shown in the following. It
processes the drop event and opens an editor if the transfer type is an editor input. The adapter
can open multiple editors if the transfer contains an array of editor inputs. You can extend its
behavior to accept application-specific objects as well. The editor area for each window can be
individually configured and reconfigured with additional editor area transfers and drop listeners
at any time.
org.eclipsercp.hyperbola/EditorAreaDropAdapter
public class EditorAreaDropAdapter extends DropTargetAdapter {
public void handleDrop(IWorkbenchPage page, DropTargetEvent event) {
if (EditorInputTransfer.getInstance().
isSupportedType(event.currentDataType)) {
EditorInputTransfer.EditorInputData[] editorInputs =
(EditorInputTransfer.EditorInputData []) event.data;
for (int i = 0; i < editorInputs.length; i++) {
IEditorInput editorInput = editorInputs[i].input;
String editorId = editorInputs[i].editorId;
openEditor(page, editorInput, editorId);
}
}
...
}
}
Once the editor area is configured to accept drop requests, the next step is to enable dragging
from the Contacts view. Add the following method to the ContactsView and call it from
createPartControl():
org.eclipsercp.hyperbola/ContactsView
protected void initDragAndDrop(final StructuredViewer viewer) {
int operations = DND.DROP_COPY;
Transfer[] transferTypes = new
Transfer[]{EditorInputTransfer.getInstance()};
DragSourceListener listener = new DragSourceAdapter() {
public void dragSetData(DragSourceEvent event) {
if
(EditorInputTransfer.getInstance().isSupportedType(event.dataType))
{
String[] names = getNames();
EditorInputTransfer.EditorInputData[] inputs =
new EditorInputTransfer.EditorInputData[names.length];
if (names.length > 0) {
for (int i = 0; i < names.length; i++)
inputs[i] = EditorInputTransfer.createEditorInputData(
ChatEditor.ID, new ChatEditorInput(getSession(), names[i]));
event.data = inputs;
return;
}
}
event.doit = false;
}
public void dragFinished(DragSourceEvent event) {}
public void dragStart(DragSourceEvent event) {
super.dragStart(event);
}
};
viewer.addDragSupport(operations, transferTypes, listener);
}
The method adds drag and drop support to the ContactsView's treeViewer. When a drag is
started, the listener initializes the event by creating an editor input for each of the contacts
selected in the viewer. The selection is obtained using getNames(). If the event does not support
EditorInputTransfers, then the drag is cancelled by setting event.doit to false.
The last step is to modify ChatEditorInput to implement IPersistableElement. The
IPersistableElement allows the input to be serialized during a drag and drop. Drag and drop
transfers are always serialized, even when the drop occurs inside the application.
This requires two changes; the first is to implement saveState(IMemento) and getFactoryId().
The next is to implement a factory that can deserialize the chat editor inputs. Since the chat
editor input is simple, all it takes to serialize the input is to remember the user name. The
factory can create a new input using the name.
org.eclipsercp.hyperbola/ChatEditorInput
public class ChatEditorInput implements IEditorInput, IPersistableElement
{
public String getFactoryId() {
return ChatEditorInputFactory.ID;
}
public void saveState(IMemento memento) {
memento.putString(KEY_NAME, getParticipant());
}
...
}
public class ChatEditorInputFactory implements IElementFactory {
public static final String ID = "org.eclipsercp.hyperbola.chatinput";
public IAdaptable createElement(IMemento memento) {
String name = memento.getString(ChatEditorInput.KEY_NAME);
if (name != null)
return new ChatEditorInput(Session.getInstance(), name);
return null;
}
}
With these changes Hyperbola is drag and drop enabled. We only set up the Contacts view to
initiate drags, but it's easy to see how other views could be similarly set up. If you want to learn
more about drag and drop, read the following articles:
"Drag and DropAdding Drag and Drop to an SWT Application" by Veronika Irvine (IBM),
August 25, 2003 (http://eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html)
"Drag and Drop in the Eclipse UI" by John Arthorne (IBM), August 25, 2003
(http://eclipse.org/articles/Article-Workbench-DND/drag_drop.html)
16.5. Summary
The one thing to remember when designing your RCP application is that the Workbench
provides a default implementation for many useful features that are designed to scale.
Perspectives, views, and editors provide a rich UI model that you do not have to reinvent. In
places where the standard features do not apply to your application, the Workbench tries to
allow you to rewrite your own custom solution using the provided APIs.
Chapter 17. Actions
There is much more to the notion of actions than we've covered in our examples so far. Chapter
6, "Adding Actions," ended with an explanation of why Hyperbola does not use declarative
actions, a standard technique in Eclipse. To recap, the actions in Hyperbola are defined and
placed programmatically by the ActionBarAdvisor. This technique is simple and ideal for small,
closed applications. In Chapter 14, "Adding Update," we talked about updating deployed
applications and adding new plug-ins. How do these new plug-ins add their actions? The answer
lies in the Workbench's support for declarative actions.
This chapter is split into two parts: the first explains when and how to use declarative actions,
while the second gives useful tips and tricks for writing product-quality actions. Specifically in
this chapter, we show you how to:
Decide between using declarative versus programmatic actions.
Add declarative actions to Hyperbola.
Use retargetable actions to define pluggable behavior.
Write actions that correctly track selection.
Show progress for long-running actions.
Use actions in alternate ways, such as showing text, creating multi-row toolbars, and
adding controls to toolbars.
Add contributions to the status line.
17.1. Overview
Before introducing declarative actions, let's take a step back and look at what an action is all
about. The primary role of actions is to expose application behavior to the user. When you click
on a menu item or toolbar item or invoke a key sequence, an action is run. Further, you can set
up a system such that the same action is placed in a menu, a toolbar, and bound to a key
sequenceactions allow you to separate behavior from placement. Actions also determine if they
are enabled, figure out what elements to work on, run the work, and display the results.
Below is a list of the basic responsibilities of actions. As you explore the various ways of defining
and using actions, remember that these responsibilities apply regardless of how or where they
are defined and usedonly the syntax is different.
Placement Actions can be placed in many areas of the Workbench: in views, editors,
context menus, and in top-level menus and toolbars. When they are created, they are
given a reference to the container in which they are placed, allowing them to query for
selection or access other information.
Rendering There are several types of actions, for example, menus, toolbar buttons, radio
buttons, drop-down menu buttons, and so on. When you create an action, you have to
decide what type of action it is and how it will be rendered in the UI. Actions also contain
basic information, such as a label, an icon, and a tool tip, which is used in displaying and
describing the action to the user.
Input and enablement Actions usually perform work on a set of objects. The input to an
action is defined by the selection in the related UI part. For example, if an action is in a
view, then the related selection is that of the widgets in the view. An action in the
Workbench window uses the selection that comes from the active view or editor. As an
action's context changes (e.g., its input changes or its container state changes), the action
must update its enablement state.
Behavior When an action is eventually run, it performs some operation on its input. If the
action is long-running, it should show progress to the user.
Binding An action can be controlled by the keyboard instead of by its explicit UI element.
Actions can also be bound to other mechanisms that control when they are run.
When you add an action, you have to implement each of these responsibilities. Figure 17-1
shows how the "Build" action from the IDE product implements these responsibilities.
Figure 17-1. Action responsibilities
[View full size image]
The Build action is placed in the top-level toolbar and menu, it is rendered as a push button
with a name and image, it's enabled when at least one project is selected, and its input is the
selected project. Its behavior is to build the selected project.
17.2. Declarative Actions in Hyperbola
Imagine a scenario where Hyperbola is extended by the Debug and MUC (multi-user chat) plugins. Ideally, the actions defined by these plug-ins are placed side-by-side in some top-level
menu and the fact that they originate from different sources is hidden. This promotes the added
behavior as an integral part of Hyperbola rather than as something tacked on the side.
Here we focus on this scenario and talk about:
The different kinds of declarative actions.
Extending the ActionBarAdvisor to support action contributions into the top-level menu
and toolbar.
Using an action set to add two declarative actions, "Export Contacts" and "Import
Contacts," to Hyperbola's top-level menu and toolbar. The actions are added in several
locations to demonstrate placement control.
Adding a context menu with Export and Import actions to the Contacts view.
17.2.1. Declarative Actions
One of the Workbench's main roles is as an integration point. Plug-ins contribute actions, views,
and the like to the Workbench and the Workbench structures, places, and manages them.
Diverse sets of plug-ins are thus integrated and the user experience is improved. To participate
in the integration, however, plug-ins must supply their contributions declaratively rather than
programmatically.
The Workbench supports the following extension points into which plug-ins make action
contributions, describing the action's implementation, placement, icon, and label. Armed with a
set of extensions to these extension points, the Workbench inserts the actions into existing
menus and toolbars.
org.eclipse.ui.actionSets This extension describes a set of menus and actions that is
added to the top-level menu and toolbar. Action sets are enabled and disabled as a group.
org.eclipse.ui.popupMenus This extension point is used to contribute actions to context
menus that have been registered with the Workbench.
org.eclipse.ui.editorActions This extension point is used to add actions to the top-level
menu and toolbar when a particular editor is enabled.
org.eclipse.ui.viewActions This extension point is used to add actions to the local menu
and toolbar for views.
Within the context of a small application, such as Hyperbola, there is usually no need to use
declarative actions. However, there are benefits of using declarative actions that should be
considered:
Declarative actions are shown in the UI without loading their associated plug-in. In large
applications with many plug-ins, this is crucial to scalability.
Dynamic reconfiguration of top-level menus and toolbars based on the active perspective
is enabled by associating action sets with perspectives.
Users can configure top-level menus and toolbars via the perspective customization dialog
(see the ActionFactory.EDIT_ACTION_SETS action).
The downside of declarative actions is that they cannot build on one another and their ordering
within a menu is controlled by the Workbench. In other words, you cannot deterministically
order two actions sets within the same menu. The other issue is that toolbar and menu paths
are error-prone. It's hard to find the right paths, and when you get it wrong, menus just do not
show up.
17.2.2. Allowing Contributions
To take advantage of these features, RCP applications should define a top-level menu and
toolbar skeleton in the ActionBarAdvisor and use declarative actions for everything else.
Even if you choose not to use declarative actions for your part of the application, you should
design your application so that other plug-ins can extend its menus and toolbars. This way,
third parties can add functions and you can ship additional functions after the main product
ships.
The first step is to add placeholders into the top-level menus and toolbars. This is where the
declarative actions will plug in. Placeholders are added to IContributionManager instances, such
as ToolbarManager or MenuManager. They are named entities that are referenced from declarative
action definitions.
Hyperbola already has a named top-level menu. In the ActionBarAdvisor method fillMenu(),
the Hyperbola menu is created as a MenuManager using the snippet below. The first parameter to
the constructor is the menu name, as shown in the UI, and the second, "hyperbola", is the id of
the menu.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
MenuManager hyperbolaMenu = new MenuManager("&Hyperbola", "hyperbola");
You can define placeholders using the "hyperbola" menu as part of the path (e.g.,
hyperbola/placeholder). Once the placeholders are defined, the Workbench takes care of the
restit decides which contributed actions are applicable and automatically inserts them when the
toolbar or menu is shown.
The Workbench supplies a standard placeholder id that is used to mark the location in a
contribution manager where contributions are added. The constant
IWorkbenchActionConstants.MB_ADDITIONS is used in the following code snippets to identify
placeholders. It is then used in plugin.xml files of plug-ins contributing action sets to link
actions into menus and toolbars.
org.eclipse.ui/IWorkbenchActionConstants
/**
* Name of group for adding new top-level menus (value "additions").
*/
public static final String MB_ADDITIONS = "additions";
Let's change Hyperbola's ActionBarAdvisor to allow contributed actions in the following areas:
on the menu bar, between the Hyperbola and Help menus
in the Hyperbola menu, between the first two groups
at the end of the toolbar
in the Contacts view's context menu
We cover the first three actions next and the context menu action in Section 17.2.4, "Context
Menus."
There's no magic when adding placeholders; you simply add them the same way that actions
are added. The following code snippet from Hyperbola's ActionBarAdvisor adds a placeholder to
the Hyperbola menu as well as to the menu bar and the end of the toolbar:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillMenuBar(IMenuManager menuBar) {
// Top-level menu called Hyperbola with id 'hyperbola'.
MenuManager hyperbolaMenu = new MenuManager("&Hyperbola", "hyperbola");
hyperbolaMenu.add(addContactAction);
hyperbolaMenu.add(removeContactAction);
hyperbolaMenu.add(chatAction);
// Placeholder within the 'hyperbola' menu called 'additions'. This
// can be referenced as 'hyperbola/additions'.
hyperbolaMenu.add(new
Separator(IWorkbenchActionConstants.MB_ADDITIONS));
hyperbolaMenu.add(new Separator());
hyperbolaMenu.add(preferencesAction);
hyperbolaMenu.add(new Separator());
hyperbolaMenu.add(exitAction);
...
menuBar.add(hyperbolaMenu);
// Top-level menu placeholder with id 'additions'.
menuBar.add(new
Separator(IWorkbenchActionConstants.MB_ADDITIONS));
menuBar.add(helpMenu);
}
protected void fillCoolBar(ICoolBarManager coolBar) {
IToolBarManager toolbar = new ToolBarManager(coolBar.getStyle());
coolBar.add(toolbar);
toolbar.add(addContactAction);
toolbar.add(removeContactAction);
toolbar.add(chatAction);
// Top-level toolbar placeholder with id 'additions'.
coolBar.add(new
Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
Placeholders are added as either Separators or GroupMarkers. Separators surround all
contributions by the appropriate separators, whereas contributions to a GroupMarker are added
as-is, without any additional separators.
Tip
To support action contributions to menus anywhere in your application, not just at the
top level of menus, you must document the menu and group ids defined by your
application.
The Workbench provides a list of commonly used menu ids in
IWorkbenchActionConstants. The IDE product uses these, but you are free to use your
own identifiers instead. These ids effectively become API and so should be
documented and maintained.
17.2.3. Declaring Actions
Now that the placeholders are in place, let's declaratively add some action sets to Hyperbola. In
this example, the two actions called "Export Contacts" and "Import Contacts" are added to the
three placeholders we just defined. In practice, placing an action in one or two spots is enough,
but here we want to demonstrate all the cases.
The action set below adds a menu called "Tools" to the top-level menu bar and adds the import
and export actions to that menu.
org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.ui.actionSets">
<actionSet
id="org.eclipsercp.hyperbola.actionSet1"
label="Hyperbola Tools"
visible="true">
<menu
id="org.eclipsercp.hyperbola.tools"
label="&amp;Tools"
path="additions">
<groupMarker name="group1"/>
</menu>
<action
class="org.eclipsercp.hyperbola.actions.ExportContactsAction"
icon="icons/export.gif"
id="org.eclipsercp.hyperbola.exportContacts"
label="&amp;Export Contacts"
menubarPath="org.eclipsercp.hyperbola.tools/group1"
style="push"/>
<action
class="org.eclipsercp.hyperbola.actions.ImportContactsAction"
icon="icons/import.gif"
id="org.eclipsercp.hyperbola.importContacts"
label="&amp;Import Contacts"
menubarPath="org.eclipsercp.hyperbola.tools/group1"
style="push"/>
</actionSet>
</extension>
The first thing in an action set is its id. An id must be globally unique relative to all other action
set ids. The easiest way to ensure uniqueness is to prefix the id by the id of the contributing
plug-in.
Action sets are not normally shown to the user. The actionSet element's label attribute is used
to represent the action set in the perspective configuration dialog (see
ActionFactory.EDIT_ACTION_SETS). The actionSet element's visible attribute determines if the
action set should be visible in all perspectives. If this is set to false, then perspectives
determine which action sets to show using IPageLayout.addActionSet(String). When true, the
action set can only be hidden by the user if the perspective customization dialog is shown, or by
calling the IWorkbenchPage method hideActionSet(String).
The menu element defines a top-level menu into which actions can be placed. The menu
element's path attribute causes the menu to be placed in the menu bar in the "additions"
placeholder that we defined in the code earlier. The menu element's label attribute defines the
text that is shown in the UI to represent the menu. This menu element also defines a
groupMarker element that defines a placeholder for actions. The menu element's menu id is
important because it is used to place the actions defined in the containing actionSet element.
Again, the menu element's id should be unique among menus.
The action elements carry enough information to support displaying an action without running
any code. That information includes the label, icon, and action style (e.g., push button, toggle,
radio). The location of the action is determined by the action element's menubarPath attribute.
The path consists of multiple menu ids with a terminating group id. In this example, the menu
and group happen to be defined by the action set's menu and the menu itself is placed in the
menu bar.
The implementation of an action is defined by its class attribute. The supplied class must
implement an interface that is particular to the extension point to which the action is being
added. For example, here the class must implement IWorkbenchWindowActionDelegate. This
gives the action a Workbench window as context for its operation. If the action was added to a
view or editor, it would have to implement a different interface. All these interfaces extend
IActionDelegate. Let's take a look at the ImportContactsAction:
org.eclipsercp.hyperbola/ImportContactsAction
public class ImportContactsAction implements IWorkbenchWindowActionDelegate
{
private IWorkbenchWindow window;
public void dispose() {
}
public void init(IWorkbenchWindow window) {
this.window = window;
}
public void run(IAction action) {
HyperbolaUtils.import();
}
public void selectionChanged(IAction action, ISelection selection) {
}
}
Action classes for declarative actions are very similar to a regular IAction, but they have
additional methods such as init() and selectionChanged() . An IAction is not used directly
because the Workbench is lazy. It places proxy IAction instances into menus and toolbars
instead of instantiating declared actions. The proxy actions keep a reference to the action
delegate for which they are serving as a proxy.
Notice that run(IAction) and selectionChange(IAction) take an action argumentthis is the
proxy. Under the covers, the proxy is the action that is part of the menu. As such, changing how
the action appears (e.g., its enablement or label) requires changing the proxy action. The proxy
action uses the information from the declarative action's description to display, perform basic
enablement, and then ultimately run the action.
Adding these actions to the Hyperbola menu and the toolbar follows the same pattern. The
only difference is that the menubarPath attribute has a different value. For example, adding the
following action to the action set places the Import and Export actions, as shown in Figure 172.
org.eclipsercp.hyperbola/plugin.xml
<action
class="org.eclipsercp.hyperbola.actions.ExportContactsAction"
icon="icons/export.gif"
id="org.eclipsercp.hyperbola.action2"
label="&amp;Export Contacts"
menubarPath="hyperbola/additions"
style="push"
toolbarPath="additions"/>
Figure 17-2. Import and Export declarative actions in the menu and
toolbar
Here, the hyperbola/additions menu path and addition toolbar path refer directly to the names
of the menus and placeholders we added to the Hyperbola ActionBarAdvisor earlier.
Note
To enable key bindings in declarative actions, use the same steps as defined in
Chapter 12, "Adding Key Bindings." Instead of registering the action in the
ActionBarAdvisor, set the definitionId attribute in the action definition to that of the
command id.
17.2.4. Context Menus
It is often convenient for users to act directly on a UI element using a context menu or by
double-clicking, for example, initiating a chat in Hyperbola by double-clicking on a contact in
the Contacts view. So far, Hyperbola does not have any context actions let alone any extensible
structure for adding them. In this section, we add a context menu and then add some
declarative actions to it.
The snippet below shows the ContactView method makeActions() . This method is called from
the ContactView method createControlPart() and does the following:
adds a context menu
adds the chat action to the menu
adds the MB_ADDITIONS placeholder to the menu
registers the menu with the Workbench
initializes double-click behavior on the tree viewer
org.eclipsercp.hyperbola/ContactsView
private void makeActions() {
chatAction = new ChatAction(getSite().getWorkbenchWindow());
// Initiate a chat on double click.
treeViewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
chatAction.run();
}
});
// Create the context menu and register it with the Workbench.
MenuManager menuMgr = new MenuManager("contactsPopup");
manager.add(chatAction);
manager.add(new
Separator(IWorkbenchActionConstants.MB_ADDITIONS));
Menu menu = menuMgr.createContextMenu(viewer.getControl());
treeViewer.getControl().setMenu(menu);
getSite().registerContextMenu(menuMgr, treeViewer);
}
The ChatAction used here is the same one that was added to Hyperbola's top-level menu in
Chapter 7, "Adding a Chat Editor." The context menu is defined using the MenuManager in the
same way as the top-level menus defined previously.
The registerContextMenu() method is used to hook the menu into the Workbench and its
declarative action mechanisms. When a registered menu is shown, the Workbench adds any
related declarative contributions. In the case of a context menu, contributions to the
org.eclipse.ui.popupMenus extension point that apply to the current selection are added.
The last step is to define the declarative actions for Export and Import that place them in the
context menu. The following snippet adds an org.eclipse.ui.popupMenus extension that applies
to any instance of RosterEntry. Notice that here, the action is not placed in a particular menu.
Rather, it is scoped such that when a RosterEntry (e.g., a contact) is selected, the action is
available and is added to any open context menu.
org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.ui.popupMenus">
<objectContribution
adaptable="false"
id="org.eclipsercp.hyperbola.objectContribution1"
objectClass="org.jivesoftware.smack.RosterEntry">
<action
class="org.eclipsercp.hyperbola.actions.ExportContactsAction"
icon="icons/export.gif"
id="org.eclipsercp.hyperbola.action1"
label="&amp;Export Contacts"/>
</objectContribution>
</extension>
Apart from the objectContribution element, the actions in this example are described in the
same manner as action sets. The existing ExportContactsAction implementation is even reused
after one small changethe action class must implement IObjectActionDelegate in addition to
IWorkbenchWindowActionDelegate. The new interface is needed so the action can track its
context. In the case of a context menu action, the context is the UI part that is showing the
context menu.
17.3. Standard Workbench Actions
In Chapter 6, you saw that the Workbench defines a set of reusable actions. These actions are
defined as inner classes of org.eclipse.ui.actions.ActionFactory and are instantiated and
used as regular actions.
exitAction = ActionFactory.QUIT.create(window);
The reusable actions have preconfigured names, icons, ids, and action definition ids, but any of
these attributes can be overridden after creating the action.
exitAction.setText("&Quit"); // Change from Exit to Quit
All actions created from the ActionFactory are instances of IWorkbenchAction.
IWorkbenchAction extends IAction and adds the dispose() method since instances must be
deleted when their associated window is closed.
Before implementing an action in your application, check to see if the ActionFactory defines a
related action. Reusing these standard actions gives your application a more consistent look and
feel. A partial list of reusable actions is given in Table 17-1.
Table 17-1. Partial List of Actions Available from ActionFactory
Method
Description
SAVE
Saves the active Workbench part if it implements ISaveablePart .
SAVE_ALL
Saves all open editors that have unsaved changes.
SAVE AS
Allows the active part to save its contents to another object.
QUIT
Exits the Workbench.
PREFERENCES
Opens the Workbench Preferences dialog, composed of pages
contributed via the org.eclipse.ui.preferencePages extension
point.
PROPERTIES
Opens the Properties dialog and primes it with the property pages
for the current selection. The pages shown are those contributed
via the org.eclipse.ui.propertyPages extension point.
OPEN_NEW_WINDOW
Opens a new Workbench window using the same perspective as
the one currently showing in the originating window. If you need
to open another window using another perspective, you can
directly call the IWorkbench method openWorkbenchWindow() with a
perspective id.
ABOUT
Opens the standard Workbench About dialog.
CLOSE_ALL
Closes all open editors.
Method
Description
CLOSE
Closes the active editor.
IMPORT
Opens the import wizard.
EXPORT
Opens the export wizard.
HELP_CONTENTS
Displays the registered help contribution. This is a no-op until you
have added a help provider to your application. See Chapter 13,
"Adding Help," for more details on adding Help to your
application.
Reusable preference pages and views
The Workbench also provides a set of reusable preference pages and views. The
complete list can be found on the org.eclipse.ui.ExtensionFactory class. It
includes common things such as the Keys, Colors and Fonts, and Decorators
preference pages, as well as the Progress view.
The extension factory provides access to these pages and views via a set of
identifiers. The identifiers are used in view extensions as shown below. Notice that
the markup is the same as a regular view extension, but instead of specifying the
view's implementation class directly, the ExtensionFactory is used and the view
identifier is provided after the ":".
<extension point="org.eclipse.ui.views">
<view
class="org.eclipse.ui.ExtensionFactory:progressView"
icon="icons/progress.gif"
id="org.eclipsercp.hyperbola.views.progress"
name="Progress"/>
</extension>
See IExecutableExtensionFactory for how to define your own factories.
17.4. Retargetable Actions
The Workbench defines yet another special type of action called retargetable actions.
Retargetable actions are similar to regular actions except they do not have behavior. Instead,
their behavior is supplied by another action. Hyperbola does not have a particularly compelling
use case for retargetable actions, so to illustrate the concept, we look at implementing Back
and Forward buttons in an RCP-based Web browser application.
Note
The source code for an RCP Web browser that uses retargetable actions is available in
the Eclipse Platform CVS repository, in the org.eclipse.ui.examples. rcp.browser
project, or from the book's CD in the sample code for Chapter 27.
The Web browser application uses several views, each with its own Browser widget. The main
toolbar has Back and Forward actions that control the active browser. If there is only one set
of actions in the toolbar, how do they control the view that happens to be active?
One way to implement this is to create two regular actions that can be accessed statically from
the views and implement the views to hook themselves to the actions when they become active.
When the actions run, the current view gets called and performs the required behavior. The
views also have to unregister themselves when they are deactivated.
This is essentially what retargetable actions do, with the benefit that the Workbench provides
the management of binding actions to retargetable actions as Workbench parts are activated
and deactivated.
Note
Some of the reusable actions defined on ActionFactory are retargetable actions (e.g.,
COPY, PASTE, CUT , NEXT, PREVIOUS , RENAME, and DELETE). By using these actions, your
application exposes standard icons, labels, and key bindings that are familiar to users.
The following snippet shows how the Forward retargetable action is added to the toolbar in the
ActionBarAdvisor. Notice that retargetable actions are added in exactly the same way as
regular actions.
org.eclipse.ui.examples.rcp.browser/BrowserActionBarAdvisor
RetargetAction action = new RetargetAction("myapp.forward","Forward");
action.setImageDescriptor(forwardImage);
action.setToolTipText("Forward");
toolbar.add(action);
When creating a retargetable action, you have to specify the id for the action and the default
label. The id is used to match the action that implements the behavior to the retargetable action
that places it.
Now that the Forward action is in the toolbar, the browser view needs to register its
implementation of the Forward action with the Workbench. To do this, it implements an IAction
and registers it as a global action handler with the browser view's action bars. This is usually
done when the part creates its actions and controls, as shown in the next snippet. The id of the
retargetable action specified earlier is used to link in the new action.
org.eclipse.ui.examples.rcp.browser/BrowserView
public class BrowserView extends ViewPart {
...
public void createPartControl(Composite parent) {
IAction nextAction = new Action("Forward") {
public void run() {
browser.forward();
}
};
// Add global action handlers.
getViewSite().getActionBars()
.setGlobalActionHandler("myapp.forward", forwardAction);
}
The Workbench handles the details of ensuring that the retargetable action is associated with
the active view or editor. When retargetable actions are not linked with an action, they are
automatically disabled.
17.5. Consolidating Declarative Actions
The Workbench offers some facilities for aggregating and presenting similar actions. For
example, the New wizard dialog presents a set of wizards and allows users to choose the one
they want. Plug-ins hook into this facility by contributing extensions to the relevant Workbench
extension points. The Workbench exposes chooser dialogs or menus for preference pages,
property pages, new wizards, import wizards, and export wizards. See Section 15.5.3,
"Scalability," for the complete extension point reference.
This approach is powerful for applications as they can expose common access points for
exporting, importing, and creating new things without having to know or determine what kinds
of new things or exports and imports are supported. The mechanism is useful for plug-in writers
as their actions are integrated into the host application, alongside other related functions.
The actions that show dialogs or menus are available on ActionFactory as
ActionFactory.EXPORT, ActionFactory.IMPORT, ActionFactory.NEW, ActionFactory.PREFERENCES,
and ActionFactory.PROPERTIES. The following snippet from Chapter 11, "Adding a Login
Dialog," shows how these actions are added to toolbars or menus:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
preferencesAction = ActionFactory.PREFERENCES.create(window);
register(preferencesAction);
...
}
If the standard dialogs and menus are not suitable, you can access the contributions directly
and build your own UI. The extension registries for the contributions to the Import, Export, and
New wizards are accessed by calling the appropriate IWorkbench method,
get[New|Import|Export]WizardRegistry() .
The Workbench also exposes a handy New menu that is similar to the Perspectives and
Views menus shown in Chapter 16, "Perspectives, Views, and Editors." This is a cascading
menu with quick access to the list of contributed new wizards. You can add this menu using the
ContributionItemFactory. NEW_WIZARD_SHORTLIST helper, as shown below:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
MenuManager newMenu = new MenuManager("New", "new");
perspectives =
ContributionItemFactory.NEW_WIZARD_SHORTLIST.create(window);
}
protected void fillMenuBar(IMenuManager menu) {
newMenu.add(perspectives);
}
To keep menus short, the quick access list of wizards shown is the set of new wizards explicitly
defined by the active perspective. Perspectives configure the list using the IPageLayout method
addNewWizardShortcut(String) .
17.6. Toolbar Action Tricks
Desktop applications often need to show more than buttons in the toolbar. This adds a bit of
polish and usability to the application. In this section, we show you how to tweak your toolbar
actions and have them show text, wrap on multiple lines, and host various controls.
17.6.1. Showing Images and Text
Showing both images and text for certain toolbar items is a common requirement. The text is
generally placed either to the right or below the image for the action, as shown in Figure 17-3.
This improves usability for new users who do not know the meanings of the images. The
Workbench supports mixing images and text, but only with programmatic actions.
Figure 17-3. Toolbar actions with text
Let's walk through how this is done. When an application contributes actions to the top-level
toolbar, it adds them to an ICoolBarManager. The ICoolBarManager interface extends
IContributionManager and can manage both IActions and IContributionItems . Contributed
items wrap the SWT controls that are shown in the contribution manager and are ultimately
responsible for rendering the items in a menu, a composite, or a coolbar.
JFace provides a helper class called ActionContributionItem for directly adding actions to a
contribution manager. Contributed action items effectively transfer the properties of an action
down to an SWT control. As such, they can be used to configure whether or not text is shown on
the toolbar.
To control the ActionContributionItem for an action, first create the toolbar with a style flag
that includes SWT.BOTTOM or SWT.RIGHT to indicate where to show the text. Then, for each action,
create an ActionConfigurationItem and set its mode to
ActionContributionItem.MODE_FORCE_TEXT . Next, call ToolBarManager.add(IContributionItem)
to add the contribution item to the toolbar. The following code shows how to do this in
Hyperbola's ActionBarAdvisor:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillCoolBar(ICoolBarManager coolBar) {
IToolBarManager toolbar =
new ToolBarManager(coolBar.getStyle() | SWT.BOTTOM);
coolBar.add(toolbar);
ActionContributionItem addContactCI = new
ActionContributionItem(addContactAction);
addContactCI.setMode(ActionContributionItem.MODE_FORCE_TEXT);
toolbar.add(addContactCI);
ActionContributionItem removeContactCI = new
ActionContributionItem(removeContactAction);
removeContactCI.setMode(ActionContributionItem.MODE_FORCE_TEXT);
toolbar.add(removeContactCI);
ActionContributionItem chatActionCI = new
ActionContributionItem(chatAction);
chatActionCI.setMode(ActionContributionItem.MODE_FORCE_TEXT);
toolbar.add(chatActionCI);
coolBar.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
17.6.2. Adding Controls to the Toolbar
Another common feature is to place SWT controls in the toolbar instead of just buttons. Again,
the key here is to put an IContributionItem into the toolbar instead of an IAction. This works
because ToolItems and CoolItems both allow controls to be set as their contents. JFace includes
a helper class, ControlContribution, that supports adding controls to toolbars and coolbars.
The code for ControlContribution is shown below:
org.eclipse.jface/ControlContribution
public abstract class ControlContribution {
protected abstract Control createControl(Composite parent);
public final void fill(Composite parent) {
createControl(parent);
}
public final void fill(Menu parent, int index) {
Assert.isTrue(false, "Can't add a control to a menu");
}
public final void fill(ToolBar parent, int index) {
Control control = createControl(parent);
ToolItem item = new ToolItem(parent, SWT.SEPARATOR, index);
item.setControl(control);
item.setWidth(computeWidth(control));
}
}
To add your own control, subclass ControlContribution and implement
createControl(Composite) to return the desired control. The returned control is added to the
toolbar by placing it in a dedicated tool item. The following snippet from Hyperbola's
ActionBarAdvisor shows how to place a ComboBox into the Hyperbola main toolbar:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillCoolBar(ICoolBarManager coolBar) {
IToolBarManager toolbar = new ToolBarManager(coolBar.getStyle());
coolBar.add(toolbar);
IContributionItem comboCI = new ControlContribution() {
protected Control createControl(Composite parent) {
Combo c = new Combo(parent, SWT.READ_ONLY);
c.add("one");
c.add("two");
c.add("three");
return c;
}
};
toolbar.add(comboCI);
}
17.7. Adding Contributions to the Status Line
In Chapter 6, you added a presence indicator to Hyperbola's status line. You may have noticed
that as parts are activatedfor example, activating the Contacts view, then a chat editorthis
status line indicator disappears. This happens because the indicator was added to the shared
message area of the status line (refer to Chapter 6 for an overview of the areas in the status
line). A better approach is to add the indicator to the contribution area so that it remains visible
across part switches.
Actions or contribution items can be added to the status line in the ActionBarAdvisor method
fillStatusLine(). To maintain some stability in the status line, you should add contributions to
pre-defined named groups that are defined on StatusLineManager such as BEGIN_GROUP,
MIDDLE_GROUP, and END_GROUP. This enables aligning global contributions to the right or left of
the contribution area.
Status line contributions are very similar to the ControlContributions classes used in the last
section. Instead of adding a control to a toolbar, they add controls to a compositethe status line
is really just a fancy composite.
The sample code for this chapter includes a custom contribution item called
StatusLineContribution, which uses a CLabel control to show its contents. A CLabel is a custom
SWT control that displays both an image and a label together. The fill(Composite) method in
StatusLineContribution is called to add the contribution to the status line. You can add just
about anything you like at this point. Here, we use two CLabels, the first to show a separator
and the other to show an icon followed by text:
org.eclipsercp.hyperbola/StatusLineContribution
public void fill(Composite parent) {
Label separator = new Label(parent, SWT.SEPARATOR);
label = new CLabel(parent, SWT.SHADOW_NONE);
GC gc = new GC(parent);
gc.setFont(parent.getFont());
FontMetrics fm = gc.getFontMetrics();
Point extent = gc.textExtent(text);
if (widthHint > 0)
widthHint = fm.getAverageCharWidth() * widthHint;
else
widthHint = extent.x;
heightHint = fm.getHeight();
gc.dispose();
StatusLineLayoutData statusLineLayoutData = new StatusLineLayoutData();
statusLineLayoutData.widthHint = widthHint;
statusLineLayoutData.heightHint = heightHint;
label.setLayoutData(statusLineLayoutData);
label.setText(text);
label.setImage(image);
...
}
The StatusLineLayoutData is a special layout used to tell the status line how much room is
needed for the contribution area. These areas cannot be resized dynamically, so you must
commit to a size when they are created.
To add the contribution to the status line, create the contribution in the ActionBarAdvisor
method makeActions() and add it as follows:
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillStatusLine(IStatusLineManager statusLine) {
statusLine.add(statusContribution);
}
17.8. Reporting Progress
In a perfect world, actions run at the speed of light and the user never has to wait. However, in
reality, many actions may take a noticeable amount of time to complete. Without feedback to
the user, these actions make your application look unresponsive. Adding progress reporting to
actions is therefore critical.
The simplest feedback option is to show a busy cursor, as shown in the following:
org.eclipsercp.hyperbola/ProgressAction
public void run() {
BusyIndicator.showWhile(display, new Runnable() {
public void run() {
// perform action's work here
}
});
}
This is great for operations that are not instant, but are likely to take less than about two
seconds. If the action takes longer than two seconds, the busy cursor does not provide enough
feedbackit appears that the UI is blocked and there is no possibility of canceling the action.
The Runtime's IProgressMonitor interface is useful for reporting progress and allowing
cancellation. Below is an example of running a long operation while showing a progress dialog:
org.eclipsercp.hyperbola/ProgressAction
ProgressMonitorDialog pd = new ProgressMonitorDialog(window.getShell());
pd.run(true /*fork*/, true /*cancelable*/,
new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) throws
InvocationTargetException, InterruptedException {
monitor.beginTask("Long running action", 100);
for (int i = 0; i < 10; i++) {
if (monitor.isCanceled())
return;
monitor.subTask("working on step " + i);
Display.asyncExec(new Runnable() {
public void run() {
textField.setText("New work: " + i);
}
});
monitor.worked(10);
sleep(1000);
}
monitor.done();
}
});
The progress dialog runs the given runnable and displays the main task name, optional subtask
names, and a progress bar. A Cancel button allows the user to cancel the action. The ability to
cancel an action depends on how often the action checks the IProgressMonitor for cancellation.
This example forks the operation. Forking a long-running action is highly desirable because it
allows the UI to repaint and process events while the action is runningthe application looks
more responsive. It does mean, however, that the action's code must use
Display.syncExec(Runnable) or Display.asyncExec (Runnable) to do any drawing since the
action is run outside the main UI thread.
The problem with progress dialogs is that they flicker and flash when the operation runs quickly.
A better solution is to show a busy cursor and only show the progress dialog if the action has
been running for more than a specified amount of time. The IProgressService provides this
functionality and unifies progress reporting in the Workbench.
The progress service tries to achieve a balance between a busy cursor and a dialog. The coding
pattern is very similar to that of the progress dialog except that the
IRunnableWithProgress.run() method is always forked. The progress service displays a busy
cursor first, then pops up a progress dialog when the operation runs for more than a specified
threshold. The threshold for switching between a busy cursor and the progress dialog is hardcoded into the Workbench; it's currently 800ms. The following snippet shows how the service is
accessed via the IWorkbench:
PlatformUI.getWorkbench().getProgressService().
busyCursorWhile(new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) {
// do the work
}
});
The progress service also handles the case when a foreground action is blocked by a
background action. In this case, the service displays a list of the actions that may be blocking
the foreground action and allows them to be canceled by the user.
17.8.1. Non-modal Progress
The Runtime provides a jobs mechanism that is useful for managing background tasks. The Job
class is a cross between a java.lang.Runnable and a java.lang.Thread. Jobs require less
overhead than threads because they are pooled. They also support progress and cancellation
and can be configured with varying priorities and scheduling rules that describe how they
interact with other jobs. Here is a simple Job that does some fake work:
org.eclipsercp.hyperbola/ProgressAction
Job job = new Job("long running action") {
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask("Long running action", 100);
// do some work
return Status.OK_STATUS;
}
};
job.setUser(true);
job.schedule();
As with actions, the user probably wants to know about running jobs, but not in a way that
interrupts or blocks the UI. Not all jobs are alike and the progress presentation depends on how
the job is created. There are three categories of jobs:
User-initiated jobs These are jobs that the user has triggered. The Workbench
automatically shows user jobs in a modal progress dialog with a button to allow the user
to run the job in the background and continue working. Jobs are marked as user jobs
using the Job method setUser(boolean).
Automatically triggered jobs These jobs have meaning for the user but are not initiated
directly by the user. They are shown in the progress view and in the status line, but the
modal progress dialog is not shown.
System operations These are jobs that are not triggered by the user and can be
considered as platform implementation detail. They are created by setting the system flag
using the Job method setSystem(boolean) .
The Workbench shows progress for user jobs using an area in the status line, as shown in Figure
17-4, and supplies a modal dialog that contains a Run in background button. The background
job progress area has to be explicitly enabled in WorkbenchWindowAdvisor.preWindowOpen() as
follows:
configurer.setShowProgressIndicator(true);
Figure 17-4. Showing job progress in the status line
When this is enabled and a background job is run, the user is presented with a progress dialog.
If the user selects Run in background, the dialog is dismissed and progress is shown in the
progress area in the status line.
Note
The sample code for this chapter includes an example action that runs several longrunning actions using the different progress feedback mechanisms. The last action is
run as a job. The action can be found in the main menu as Tools > Long running
action example.
17.8.2. Progress View
The Workbench also provides a view that displays progress for multiple jobs simultaneously. It
complements the status line progress by providing more detailed status information about
running jobs as well as the ability to cancel jobs.
In everyday usage, users are not expected to use the progress view since background jobs
should complete reasonably quickly and users can keep working while background jobs run.
Nevertheless, if you want to include the progress view in your application, add the following
view extension to your plugin.xml file:
org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.ui.views">
<view
class="org.eclipse.ui.ExtensionFactory:progressView"
icon="icons/progress.gif"
id="org.eclipsercp.hyperbola.views.progress"
name="Progress"/>
</extension>
You can give the view any name and still use the implementation provided by the Workbench's
ExtensionFactory. Don't forget to update your perspective to show the progress view, as shown
below:
org.eclipsercp.hyperbola/Perspective
public class Perspective implements IPerspectiveFactory {
public void createInitialLayout(IPageLayout layout) {
layout.addStandaloneView(ContactsView.ID, false,
IPageLayout.LEFT, 0.33f, layout.getEditorArea());
layout.addView("org.eclipsercp.hyperbola.views.progress",
IPageLayout.BOTTOM, 0.22f, layout.getEditorArea());
}
}
You can control a job's icon and various other job properties using the constants in
IProgressConstants and the Job method setProperty(QualifiedName, Object).
17.8.3. Customizing Progress
Job progress support in Eclipse is tailored for large-scale IDE applications, in which the type of
background jobs are not known at design time. A quick survey of existing non-Eclipse-based
products confirms that there are no standard ways of showing background progressthe chosen
solution is often domain-specific and highly integrated.
Here is a list of different ways applications can show background progress:
Web browser Web browsers load pages in the background and show an animated icon,
but do not lock the UI. In many applications, this image is branded. Status text is often
shown in the status line.
Non-blocking progress dialogs Some applications allow concurrent operations and
display their progress by showing a non-modal progress dialog for each separate
operation. The advantage with this approach is that the user can easily cancel individual
background tasks without hunting for a progress view.
Specific task progress Progress reporting for specific types of operations is also
common. For example, a word processor may have one animated image used when saving
a file, another for checking spelling and grammar, and yet another when loading large
files.
Context progress Applications using a tabbed browsing paradigm often show progress on
each tab to indicate the status of the background tasks initiated from a certain tab. In
other words, progress is shown in multiple locations in the application.
If you decide that you don't like the background progress support in Eclipse, it's possible to
implement a custom solution. To demonstrate, let's work through an example of replacing the
existing support with an implementation that shows progress for background jobs in separate
non-modal dialogs. When the background job is finished, the dialog is automatically closed.
Jobs are created by the user and run by the Platform. The IProgressMonitor passed to the job's
run() method is used to report the job's progress. Here is an example of a job reporting
progress:
Job job = new Job("Working") {
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask("Calculating things", 1000);
for (int i = 0; i < 1000; i++) {
monitor.worked(1);
monitor.subTask("doing " + i);
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
}
monitor.done();
return Status.OK_STATUS;
}
};
job.setUser(true);
job.schedule();
To write your own progress reporting, first implement a subclass of
org.eclipse.core.runtime.jobs.ProgressProvider and then register the provider with the
Workbench. The provider is responsible for creating IProgressMonitor instances for use by the
job framework when running jobs. This, combined with notifications about the lifecycle of jobs,
allows you to implement background progress reporting. The Workbench registers its progress
provider only if progress support is enabled; otherwise, you can register your own in the
WorkbenchAdvisor method preStartup().
Platform.getJobManager().setProgressProvider(new
DialogProgressProvider());
17.8.4. Writing a ProgressProvider
The most interesting method on the ProgressProvider is createMonitor(Job) . This method
returns the progress monitor that is passed to the running job. In our example below, a nonmodal progress monitor dialog is created every time a new job is run:
org.eclipsercp.hyperbola/DialogProgressProvider
public class DialogProgressProvider extends ProgressProvider {
public IProgressMonitor createMonitor(final Job job) {
final IProgressMonitor[] m = new IProgressMonitor[]{
new NullProgressMonitor()};
Display.getDefault().syncExec(new Runnable() {
public void run() {
final ProgressMonitorDialog dialog =
new
NonBlockingProgressMonitorDialog(
Display.getDefault().getActiveShell());
dialog.setBlockOnOpen(false);
dialog.setCancelable(true);
dialog.open();
job.addJobChangeListener(new JobChangeAdapter() {
public void done(IJobChangeEvent event) {
// what if the job returned an error?
close(dialog);
}
});
m[0] = new AccumulatingProgressMonitor(
dialog.getProgressMonitor(), Display.getDefault());
}
});
return m[0];
}
private void close(final Dialog d) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
if (d != null && ! d.getShell().isDisposed())
d.close();
}
});
}
}
The essential part of this snippet is the subclass of ProgressMonitorDialog that makes the
progress dialog non-modal. The new dialog is used to create an IProgressMonitor that shows
progress using an indicator with status messages and task names. When a job completes, the
dialog is closed.
To keep this example simple, the error status of the job is not checkedit is possible to check the
return status and prompt the user or provide an indication that an error has occurred. The
dialog's progress monitor is wrapped with another progress monitor,
AccumulatingProgressMonitor, that ensures all calls to the IProgressMonitor methods by the
background job are run in the UI thread. This is important because the dialog assumes that it is
being called from the UI thread, whereas jobs are always run in a non-UI thread.
Warning
The progress view relies on the Workbench's progress provider. If you add your own
provider, the progress view stops working as there can only be one progress provider
registered at a time.
17.9. Summary
The main difference between RCP development and regular plug-in development is the level of
control and responsibility you have when writing an RCP application. RCP application writers
have full control over the structure of the menus and toolbars, and whether to allow
contributions and where they are added. Plug-in writers have it easythey plug into an existing
application in well-defined ways. RCP application writers, in essence, define the ways in which
others can plug in.
In this chapter, you learned when and how to use declarative actions and how to open up your
application to accept them. This, in combination with the general tips and tricks, gives you a
solid base for adding well-behaved, well-integrated, and responsive actions to your application.
Chapter 18. Customizing Workbench
Windows
A side effect of creating applications with a UI framework is that they tend to look somewhat
similar. This may be a good thing; as more applications share a UI model, they become familiar
in their look and feel and it is easier to learn new applications.
In reality, the modern desktop is a far cry from being an example of uniformity. Even though
each OS provides a standard set of native widgets, an increasing number of applications
attempt to distinguish themselves by looking different, providing non-rectangular windows,
having different layout mechanisms (e.g., tabbed browsing with custom drawn tabs), and often,
their own widget sets. It is very common for applications to promote brand recognition based
on a specific look.
The next two chapters are dedicated to developers who want to create applications that do not
look and feel like Eclipse. In this chapter, you'll learn to:
Understand the different customization options available.
Understand their limitations.
Extend Hyperbola to allow the toolbar and status line to be hidden. In addition, you'll add
a new quick bar to the window layout.
Make Hyperbola run in a non-rectangular window.
18.1. Customization Defined
Eclipse is all about plug-ins and extensibility. In a perfect world, the entire Workbench would be
pluggable in every possible way. For practical reasons, the Workbench is designed to meet
specific extensibility requirements and these do not include fine-grain customizations of
Workbench windows or the assembly and painting of every UI element.
Before the Eclipse RCP opened up new opportunities for the Workbench, this level of
customization was not needed. The Workbench's focus was to support an IDE platform. The
capabilities found in the Eclipse Workbench are now being used for much more than IDEs. To
support this, the Eclipse team has enhanced the Workbench's UI to be customizable in the
following ways:
Customize a window's contents A Workbench window's default layout includes a
toolbar, perspective bar, docking trim, content area, and status line.
WorkbenchWindowAdvisors , however, can override all aspects of Workbench window
creation and change the locations of elements as well as add or remove elements.
Customize a window's shape By default, Workbench windows are rectangular. Nonrectangular Workbench windows can be created by overriding how windows are created.
Customize the drawing and behavior of views and editors Views and editors provide
much of the look and feel of applications. You can provide your own implementation of
view and editor management and drawing and fundamentally change the look and feel of
an application.
The next two chapters cover these three Workbench customization options in more detail,
providing both background information and examples.
Note
You can do a lot with SWT without ever programming to the graphics interface or
implementing a custom layoutthe supplied widgets handle the painting of icons, text,
and other data for you. However, when customizing the Workbench look and feel, it's
useful to understand how to draw objects and be creative with layouts. Our discussion
here assumes some familiarity with SWT concepts and operation.
18.2. Customizing a Workbench Window
The Workbench opens and configures Workbench windows based on the configuration
information provided by a WorkbenchWindowAdvisor. The typical behavior when a Workbench
window is created is to create the following controls: a menu bar, a toolbar, a trim that is used
to place fast views and the perspective bar, a page content area, and a status line. Figure 18-1
shows a typical Workbench window's contents.
Figure 18-1. Typical Workbench window's contents
[View full size image]
The creation of these controls can be changed using the following techniques:
Change the visibility of the standard controls by calling the IWorkbenchWindowConfigurer
methods setShow*() from the WorkbenchWindowAdvisor method preWindowOpen(). These
methods control the initial visibility only and do not hide/show the controls once the
window is opened.
Override the creation of the window's controls by implementing the
WorkbenchWindowAdvisor method createWindowContents(). This moves the responsibility
for creating a window's controls to the advisor.
For most applications, the setShow*() methods available on IWorkbenchWindowConfigurer are
sufficient. To provide further customization, however, the WorkbenchWindowAdvisor needs to
create a window's contents from scratch. To give you an idea of how this works, the following
snippet shows a customized createWindowContents(Shell) method that creates a window in a
manner very similar to the Workbench:
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void createWindowContents(Shell shell) {
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
Menu menu = configurer.createMenuBar();
shell.setMenuBar(menu);
FormLayout layout = new FormLayout();
layout.marginWidth = 0;
layout.marginHeight = 0;
shell.setLayout(layout);
toolbar = configurer.createCoolBarControl(shell);
page = configurer.createPageComposite(shell);
statusline = configurer.createStatusLine(shell);
}
Notice that most of the work is done by a set of helper methods on IWorkbenchWindowConfigurer
(see Table 18-1 for a complete list). These methods create the typical Workbench controls and
make it easier to compose a custom layout from standard parts. The controls supplied by the
helpers are untyped and are not meant to be changed in any way. For example, do not
downcast them and tweak the underlying control because the control type is not API. Just place
them in the layout.
Table 18-1. IWorkbenchWindowConfigurer Control Creation Methods
Method
Description
createMenuBar (Composite)
Creates the top-level menu with style SWT.BAR. The created
control is assigned as the menu bar for a shell using the
Decorations method setMenuBar(menu). This menu is
managed by the Workbench.
createCoolBarControl
(Composite)
Creates the top-level toolbar that is managed by the
Workbench. There are no parenting restrictions on this
control, and it can be positioned anywhere.
createPageComposite
(Composite)
Creates the area in which views and editors are shown. This
method must be called from createWindowContents() or the
Workbench will not run.
createStatusLine (Composite)
Creates the status line that is managed by the Workbench.
There are no parenting restrictions on this control, and it
can be positioned in areas other than under the top-level
menu.
These methods are more than just conveniences. If the Workbench creates the controls, it can
also add in declarative contributions. We saw this in Chapter 17, "Actions," where menus and
toolbars needed to be managed by the Workbench if contributions were to be added.
If your application does not need action contributions in the menu, toolbar, or status line, you
can leave these calls out completely or replace them. Of course, this may prevent some plug-ins
from integrating into your application as they expect a menu bar and toolbar into which they
can contribute actions.
In any event, createPageComposite(Composite) must be called for the Workbench to run
properly. It can be called at any time within the createWindowContents() method.
Note
There are limitations as to when you can override the Workbench window. The
Workbench's default implementation of createWindowContents() creates controls that
are not available to clients, such as the job progress area, the trim that docks fast
views, and the perspective bar. When you override createWindowContents(), you lose
these areas.
18.2.1. Example: Hide and Show
To demonstrate the customization of a window's content, let's modify Hyperbola so that users
can hide and show the toolbar and status lines. The Workbench does not directly support
thisyou can add or remove the toolbar and status line when the window is created, but cannot
change the visibility afterwards.
While we're at it, let's include a quick search panel that appears below the page content area
when Ctrl+F is pressed and hidden when the Escape key or Close button is pressed. The quick
search panel is not a view or editor, but a custom area that is completely controlled by
Hyperbola.
Figure 18-2 shows the result of this customization. The toolbar and status line are hidden and
the quick search panel is visible at the bottom of the window. There are actions in the main
menu that toggle the visibility of each.
Figure 18-2. Example of new panel with toolbar and status line hidden
18.2.2. FormLayout
The key to getting the flexibility we need in this scenario is the use of SWT's FormLayout. A
FormLayout can be used to describe complex layouts and make it easy to add or remove
controls. A FormLayout is based on FormAttachments. There is an attachment for each side of a
control. Each attachment specifies how its side of the control is attached to other controls or the
control's parent composite.
Window creation in the WorkbenchWindowAdvisor method createWindowContents() is simple. The
FormLayout is added to the shell and then the toolbar, page, and status line are created using
the appropriate IWorkbenchWindowConfigurer methods. Notice that each control is stored as a
field on the WorkbenchWindowAdvisorthis is needed to allow changing the layout of the controls
as they are hidden and shown.
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void createWindowContents(Shell shell) {
IWorkbenchWindowConfigurer configurer =
getWindowConfigurer();
Menu menu = configurer.createMenuBar();
shell.setMenuBar(menu);
FormLayout layout = new FormLayout();
layout.marginWidth = 0;
layout.marginHeight = 0;
shell.setLayout(layout);
toolbar = configurer.createCoolBarControl(shell);
page = configurer.createPageComposite(shell);
statusline = configurer.createStatusLineControl(shell);
// The layout method does the work of connecting the
// controls together.
layoutNormal();
}
After the controls are created, they are connected together in the layoutNormal() method. Here,
the attachments are specified either as a percentage of the space available in the parent or as a
direct connection to a neighboring control. Once the layout is configured, the shell must be told
to lay out its controls.
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
private void layoutNormal() {
// Toolbar
FormData data = new FormData();
data.top = new FormAttachment(0, 0);
data.left = new FormAttachment(0, 0);
data.right = new FormAttachment(100, 0);
toolbar.setLayoutData(data);
// Status line
data = new FormData();
data.bottom = new FormAttachment(100, 0);
data.left = new FormAttachment(0, 0);
data.right = new FormAttachment(100, 0);
statusline.setLayoutData(data);
// page contents
data = new FormData();
data.top = new FormAttachment(toolbar);
data.left = new FormAttachment(0, 0);
data.right = new FormAttachment(100, 0);
data.bottom = new FormAttachment(statusline);
page.setLayoutData(data);
getWindowConfigurer().getWindow().getShell().layout(true);
}
18.2.3. Hiding the Toolbar
With this setup, hiding a control is a matter of making it invisible and then rewiring the controls
to which it is attached. For example, the code snippet below, from Hyperbola's
WorkbenchWindowAdvisor, shows how to hide and show the toolbar from Figure 18-1. To hide the
toolbar, it is made invisible and its bottom neighbor, the page area, has its top wired to the top
of the parent composite. The toolbar disappears and the page area snaps up to take its place.
Showing the toolbar is the reverse. This same pattern is used to control the layout of the status
line.
org.eclipseFrcp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void setShowToolbar(boolean visible) {
if (visible) {
if (toolbar.isVisible())
return;
FormData data = (FormData) page.getLayoutData();
data.top = new FormAttachment(toolbar, 0);
page.setLayoutData(data);
toolbar.setVisible(true);
} else {
if (!toolbar.isVisible())
return;
FormData data = (FormData) page.getLayoutData();
data.top = new FormAttachment(0, 0);
page.setLayoutData(data);
toolbar.setVisible(false);
}
getWindowConfigurer().getWindow().getShell().layout(true);
}
18.2.4. Adding the Toggle Actions
Now that the window and its controls have been created, we need actions that toggle the
visibility of these controls. The setShow*() methods on the WorkbenchWindowAdvisor can be used
to toggle the controlsall that is left is to create a set of actions that call these new methods.
Hyperbola's ActionBarAdvisor's constructor is modified to take an extra argument of type
WorkbenchWindowAdvisor. This allows it to call back and control the visibility, as shown in the
snippet below. As usual, actions are created and managed by the ActionBarAdvisor.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void makeActions(IWorkbenchWindow window) {
...
toggleToolbar = new Action("Toolbar", IAction.AS_CHECK_BOX) {
public void run() {
windowAdvisor.setShowToolbar(!windowAdvisor.getShowToolbar());
}
};
}
There is a subtle implementation issue to consider: As you can see in Figure 18-3, the actions
are created before the window. Actions whose enablement depends on the state of a control in
the window must be initialized after the window is created rather than when the action bars are
created.
Figure 18-3. Example method sequence
[View full size image]
Figure 18-3 also shows an easy solution to this problemcode your WorkbenchWindowAdvisor and
ActionBarAdvisor to coordinate when the window's contents have been created. See the call to
updateEnablements() below.
The code for updateEnablements(), shown below, is straightforward. It sets the state of the
actions according to the current visibility of the associated controls.
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
private void updateEnablements() {
toggleToolbar.setChecked(windowAdvisor.getShowToolbar());
toggleStatusLine.setChecked(windowAdvisor.getShowStatusline());
toggleQuickSearch.setChecked(windowAdvisor.getShowSearchPanel());
}
In addition to using this method when the window is created, it is handy to call the toggling
actions to update the action, as shown in the following snippet from the ActionBarAdvisor
method makeActions() :
org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
toggleQuickSearch = new Action("Search Panel", IAction.AS_CHECK_BOX) {
public void run() {
windowAdvisor.setShowSearchPanel(!windowAdvisor.getShowSearchPanel());
updateEnablements();
}
};
18.2.5. Quick Search Panel
So far, you've seen how to toggle controls that the Workbench creates. Adding controls to a
Workbench window is handled the same way. Most of the work required is in designing the
controls and deciding on an appropriate SWT layout. The snippet below sketches the quick
search panel:
org.eclipsercp.hyperbola/QuickSearchPanel
public class QuickSearchPanel extends Composite {
private Image nextImage;
private Image previousImage;
private Image closeImage;
public QuickSearchPanel(Composite parent, final ICloseable closeable) {
super(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginHeight = 3;
layout.marginWidth = 5;
layout.numColumns = 4;
setLayout(layout);
Label l = new Label(this, SWT.NONE);
l.setText("Find Contact: ");
...
}
To add the quick search panel, create the control and wire it into the window's layout. In our
example, the quick search panel does not show at startup, but is instead shown when explicitly
toggled via the keyboard or menu item. The snippet below shows how the layout must consider
the visibility of the status line in determining where to attach the bottom of the search panel:
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void setShowSearchPanel(boolean visible) {
if (visible) {
if (searchPanel != null)
return;
searchPanel = new QuickSearchPanel(
getWindowConfigurer().getWindow().getShell(), null);
FormData data = (FormData) page.getLayoutData();
data.bottom = new FormAttachment(searchPanel, 0);
page.setLayoutData(data);
data = new FormData();
data.left = new FormAttachment(0, 0);
data.right = new FormAttachment(100, 0);
if (statusline.isVisible()) {
data.bottom = new FormAttachment(statusline, 0);
} else {
data.bottom = new FormAttachment(100, 0);
}
searchPanel.setLayoutData(data);
} else {
...
}
}
18.2.6. Checkpoint
Overriding the Workbench window's content creation is possible, but not free. If you rely on
things such as the perspective bar, fast views, and the background progress area, then you
should think twice about this approach. In fact, it's really only fast views that are lost when
replacing the window's contents because in Chapter 16, "Perspectives, Views, and Editors," you
saw how to implement your own perspective bar and in Chapter 17, "Actions," how to
implement your own background progress indicator.
The reason for showing the quick search panel was to emphasize that once you control the
creation of the window's content area, anything is possible. The next section shows another
example of this when we override createWindowContents() to create a completely new window
shape.
18.3. Custom Window Shapes
Another common technique for branding an application is the use of non-rectangular windows.
An application window can be curved and have transparent areas. Here, we skim the surface of
creating custom windows to whet your appetite.
The simplest approach to customizing the window shape is to modify the shell created by the
WorkbenchWindowAdvisor and then add widgets, such as toolbars and lists, directly to the shell.
Ideally, the widgets you add are crafted to match the custom look of the shell. Here we stick to
the standard widgets used in Hyperbola and end up with the Hyperbola shown in Figure 18-4.
Figure 18-4. Hyperbola in squares
Note
The elements of the new Hyperbola may be rectangular, but the overall shell mask is
the union of the three rectangular componentsthis forms a non-rectangular whole. (As
you can tell, we are not graphic artists, but this shape demonstrates our point.)
Manipulating SWT regions and shells is the key to creating non-rectangular windows. Shells
have styles that determine how a window is rendered, for example, with a border, with
Maximize and Minimize buttons, resizable, and so on. The area of the window that is visible to
the user is controlled by setting the shell's region. A region is a collection of polygons in the x-y
coordinate system. A region can be as complex or simple as you need.
Note
The region of a shell can only be specified if its trim (i.e., the border and title area) is
not shown. This is controlled using the SWT.NO_TRIM flag on shell creation.
Shell shell = new Shell(null, SWT.NO_TRIM);
Region region = new Region();
region.add(new Rectangle(0, 0, 400, 600));
Shell.setRegion(region);
You can even call setRegion(Region) multiple times to change the shape of the shell
dynamically. This is how the famous Paper Clip animation is implemented. By changing the
shape of the shell based on a timer, you can effectively create an animated window with
transparent areas.
Note
The sample code for this chapter includes both the "Hide and Show" example and this
non-rectangular window example. The login dialog includes a checkbox to select the
example to run.
18.3.1. Creating the Shape
Simple shapes that can be defined as a set of rectangles are easy to set upsimply create a
region and add the various rectangles. More complex regions, however, are hard to describe
explicitly. In the end, it is easier to build a region from an image.
For example, you can use any drawing tool to draw an image that is a template for your
window. Design the image with spots for the various controls you need in the application. From
this, you get two things: the look of your application and a mask that defines the region. The
mask is made up of every pixel in the rectangular image that is not transparent. For example,
Figure 18-5 shows the mask for our Hyperbola example from Figure 18-4.
Figure 18-5. Mask layer used to define a region
You can load the mask and define the region at the same time as the application template
image is loaded using the code in the following snippet:
org.eclipsercp.hyperbola/ImageUtilities
static void loadRegionsAndImages(Display display,
ImageData[] datas, Image[] images, Region[] regions) {
for (int i = 0; i < datas.length; i++) {
Region region = new Region(display);
ImageData data = datas[i];
ImageData mask = data.getTransparencyMask();
Rectangle pixel = new Rectangle(0, 0, 1, 1);
for (int y = 0; y < mask.height; y++) {
for (int x = 0; x < mask.width; x++) {
if (mask.getPixel(x, y) != 0) {
pixel.x = data.x + x;
pixel.y = data.y + y;
region.add(pixel);
}
}
}
images[i] = new Image(display, datas[i]);
regions[i] = region;
}
}
Here, the template image is loaded and its gettransparencyMask() scanned to create the region.
The region ends up being a collection of 1x1 rectangles, each representing one non-transparent
pixel in the mask. Of course, the underlying implementation merges these rectangles.
Notice that the method above handles the loading of several templates and regions. This is
useful if your application allows switching the window shape or animating the window.
Note
The image used in this example has a fixed size. To create a resizable non-rectangular
window based on an image requires more work than we can include in this example.
18.3.2. Creating the Window
Once regions are initialized with images, you need to configure the application's shell from the
WorkbenchWindowAdvisor. The shell has to be configured with the SWT.NO_TRIM style bit before it
is created. This is done in the WorkbenchWindowAdvisor method preWindowOpen().
org.eclipsercp.hyperbola/ApplicationWorkbenchWindowAdvisor
public void preWindowOpen() {
getWindowConfigurer().setShellStyle(
SWT.NO_TRIM | SWT.ON_TOP | SWT.NO_BACKGROUND);
}
Next, you must update the method postWindowCreate() to configure the shell in the following
ways:
Load the regions and images and add a paint listener to the shell to draw the shell border.
This is much easier than drawing it programmatically using SWT graphics context (GC)
calls.
Add support for dragging the shell with the mouse. The usual method of moving shells
with the title bar is not available here since the shell uses the NO_TRIM style.
Set the size and region for the shell.
org.eclipsercp.hyperbola/NonRectWorkbenchWindowAdvisor
public void postWindowCreate(IWorkbenchWindowConfigurer configurer) {
datas = loadImages();
images = new Image[datas.length];
regions = new Region[datas.length];
final Shell shell = configurer.getWindow().getShell();
loadRegionsAndImages(shell.getDisplay(), datas, images, regions);
Listener listener = new Listener() {
int startX, startY;
public void handleEvent(Event e) {
if (e.type == SWT.MouseDown && e.button == 1) {
startX = e.x;
startY = e.y;
}
if (e.type == SWT.MouseMove && (e.stateMask & SWT.BUTTON1) != 0) {
Point p = shell.toDisplay(e.x, e.y);
p.x -= startX;
p.y -= startY;
shell.setLocation(p);
}
if (e.type == SWT.Paint) {
ImageData data = currentData
e.gc.drawImage(currentImage, data.x, data.y);
}
}
};
shell.addListener(SWT.MouseDown, listener);
shell.addListener(SWT.MouseMove, listener);
shell.addListener(SWT.Paint, listener);
currentData = datas[0];
currentImage = images[0];
currentRegion = regions[0];
ImageData data = datas[0];
Point location = shell.getLocation();
shell.setSize(data.x + data.width, data.y + data.height);
shell.setRegion(currentRegion);
}
Don't forget to dispose of the images when the window is closed.
18.3.3. Defining the Window Contents
The last step is to place the controls in the custom shaped window. Unfortunately, SWT does not
provide a pre-defined layout that can help here. Instead, since the window is a fixed size, you
can simply place the controls at absolute locations within the shell. Using the drawing
application you used to create the image templates, identify the coordinates for different
locations in the image and then use these to set the bounds of the controls. This is brittle, but it
works.
In this example, three controls are placed in the window: a combo box is added to the top, the
page content area is added in the middle, and the toolbar is added to the floating area at the
bottom.
org.eclipsercp.hyperbola/NonRectWorkbenchWindowAdvisor
public void createWindowContents(
IWorkbenchWindowConfigurer configurer, Shell shell) {
int x = 26;
int y = 66;
Combo box = new Combo(shell, SWT.DROP_DOWN);
box.setBounds(34, 40, 150, 20);
Control page = configurer.createPageComposite(shell);
page.setBounds(x, y, 499 - x, 391 - y);
Control coolBar = configurer.createCoolBarControl(shell);
coolBar.setBackground(
new Color(coolBar.getDisplay(), new RGB(159,159,169)));
coolBar.setBounds(29, 428, 499 - 29, 21);
}
You can also set the background color for controls to make them blend into the window's colors.
Colors that can't be changed
Some of the widgets created in the page composite area are drawn by the
Workbench. These include the background area beside the tabs and the trim shown
between views and editors that allow resizing. Refer to Chapter 19, "Customizing
the Presentation of Views and Editors," for information on customizing the look and
feel of these areas.
18.4. Summary
This chapter should have opened your eyes to some of the UI customization options available in
Eclipse. The Workbench is extremely flexible, and where it is less obviously customizable, it
offers ways of overriding its mechanisms and letting you do what you want. Going off the
beaten track is always more work, but as we have shown here, the cost of getting radically
different looks is relatively modest.
The next chapter covers another set of customizations, changing how views and editors look
and behave. Those facilities, in concert with the ones described here, remove all limits to
customization of the Workbench.
Chapter 19. Customizing the Presentation
of Views and Editors
Much of the "look and feel" of Eclipse applications is defined by the way editor and view tabs are
shown. Most of the Eclipse IDE uses native widgets, for example, toolbars, dialogs, and menus.
There are some Workbench components, however, that use custom widgets. For example,
native tab widgets don't support drag and drop reordering, focus highlighting, and Close
buttons. As such, Eclipse uses a custom widget called CTabFolder for view and editor tabs.
As you saw in Chapter 18, "Customizing Workbench Windows," there are many good reasons
for RCP applications to customize their look. Changing how editors and views are shown has a
significant impact. Fortunately, you can provide your own implementation of view and editor
rendering and navigation. If the standard look or behavior does not match your needs, you can
change it. This chapter shows you how to use a presentation and how to write your own.
19.1. Presentations
The Workbench uses the term presentation to define the set of Workbench classes that is
responsible for managing and displaying editors and views. Presentations do more than paint
widgetsthey are not just skins for the application. They also provide behavior for widgets.
Presentations control the look of tabsthe very fact that tabs are used at allas well as toolbars,
menus, and how parts are dragged from place to place.
Presentations manage stacks of presentable parts such as views and editors. They allow
collections of like parts to be stacked together and control the presentation and behavior of the
stack. The Workbench may instantiate several presentations for a given page depending on the
perspective layout. In essence, each hole that you define in your perspective is filled with a
presentation that stacks views or editors in the hole.
Figure 19-1 shows what Hyperbola would look like if you could remove all presentations from
the Workbench. This isn't a mock-up; it is using a presentation that does not do much. The look
resembles a perspective in which all views and editors are standalone. The most obvious quirk
is that the chat editors and Console views no longer show their tabs. From the example, you can
see that presentations play an important role in the Workbench and in defining the overall look
and feel of your application.
Figure 19-1. Hyperbola without a presentation
19.2. Sample Presentations
Eclipse comes with a couple of sample presentations that you can use if the standard
presentation is not suitable for your application. They also provide a good example of writing
presentations. In this section, we show you how to use these existing presentations in your
application.
To use an existing presentation, identify the presentation as your preferred presentation factory
as outlined below. First, find the id for the presentation. This is the id of its extension to the
org.eclipse.ui.presentationFactories extension point. Look for it in the plugin.xml file of the
plug-in providing the presentation. An example of the R21 extension is shown in the following:
org.eclipsepresentations.r21/plugin.xml
<extension point="org.eclipse.ui.presentationFactories">
<factory
name="R21"
class="org.eclipse.ui.internal.presentations.R21PresentationFactory"
id="org.eclipse.ui.internal.r21presentationFactory">
</factory>
</extension>
Next, set the UI's presentationFactoryId preference to this id. You can do this either in the
product preference initialization file, as described in Section 13.5, "Adding Help Content," which
is preferred, or in code. Both techniques are shown in the snippets below:
org.eclipsercp.hyperbola/preferences.ini
org.eclipse.ui/presentationFactoryId=\
org.eclipse.ui.internal.r21presentationFactory
Setting the preference in code effectively overrides the Workbench's default presentation. Note
that the preference must be set before the first Workbench window is created.
IPreferenceStore store = PlatformUI.getWorkbench().getPreferenceStore();
store.put(IWorkbenchPreferenceConstants.PRESENTATION_FACTORY_ID,
"org.eclipse.ui.internal.r21presentationFactory");
19.2.1. The R21 Presentation
The first presentation, called "R21", provides the look and feel of Eclipse as it was in Eclipse 2.1.
The look and feel of Eclipse underwent an overhaul during the development of Eclipse 3.0, but
some people still prefer the old presentation. Figure 19-2 shows Hyperbola using the R21
presentation.
Figure 19-2. Hyperbola with the R21 look
If you like the retro look, you can use the R21 presentation plug-in available in the Eclipse SDK
in a plug-in called org.eclipse.ui.presentations.r21. The Eclipse team provides this
presentation as more of an example than a full-featured presentation. For example, it does not
support standalone views. To use the R21 presentation, add the
org.eclipse.ui.presentations.r21 plug-in to your target and set up the presentation as
described above.
19.2.2. Example Presentations
In addition to the R21 presentation, there is a plug-in that contains example presentations. This
plug-in can be found on http://dev.eclipse.org in the
/home/cvs/org.eclipse.ui.examples.presentation plug-in. When you check out this project
from CVS, have a look in the plugin.xml for the list of registered presentation factories and
their ids.
19.3. Writing a Presentation
Presentations are, of course, entirely customizable. This section guides you through the classes
and coding tricks needed to implement a custom presentation. The presentations API is
relatively small, but quite detailed and involved. We start with a short introduction and then
show you how to build your own simple presentation.
Workbench windows delegate the creation of the part stacks to a registered
AbstractPresentationFactory. As you saw in the previous section, this is contributed by an
extension to the org.eclipse.ui.presentationFactories extension point. The factory to use is
identified via the UI's presentationFactoryId preference.
The factory provides methods for creating the different stack types: standalone view, regular
view, and editor. Each stack is an instance of a StackPresentation and is assigned a number of
parts to show. It is completely up to the stack to decide how to display these parts to the user.
The stacks do not create a part's widgets; they simply define where and how the part is shown.
At creation time, stacks are given a presentation site, an instance of IStackPresentationSite.
The site is used to send events and requests from the StackPresentation to the Workbench. The
stack presentation site controls stack-related behavior such as dragging and dropping of the
stack and minimize/maximize.
Multiple page editors
The Workbench provides support for multi-page editors where each page is
represented by a tab at the bottom of the editor. It is currently not possible to
override this behavior with a presentation.
Figure 19-3 shows the primary classes in the org.eclipse.ui.presentations package. To get
started, you need to provide two concrete implementations shown in gray: a stack presentation
and a presentation factory.
Figure 19-3. Presentation classes
[View full size image]
At runtime, when a window is created, the presentation factory is called by the Workbench and
asked to create a stack presentation. The factory is straightforwardit simply creates the stack
presentation instance providing both a parent composite and an IStackPresentationSite.
Subsequently, the Workbench tells the presentation its bounds (i.e., its size) using
setBound(Rectangle) and the presentation is given a part to show via
addPart(IPresentablePart). The simplest presentation ensures the bounds of the parts it's
showing are set using the IPresentablePart method setBounds(Rectangle) and the part is
made visible using the IPresentablePart method setVisible(boolean).
Presentation sites provide helper methods to support dragging and querying the page layout for
settings such as whether or not the stack is moveable or closeable. Presentations should honor
these configuration settings.
19.3.1. Widget Hierarchy
A full understanding of widget hierarchy is essential when implementing a presentation. It's not
complicated, but provides insight into many design points in the presentation APIs. For
example, even though the presentation has a method called addPart(IPresentablePart), the
presentation does not actually parent the widget related to the presentable part. Instead, the
part's control and toolbar are parented by the Workbench window, as shown in Figure 19-4.
That is, the Runtime widget hierarchy of the Workbench does not reflect the presentation class
hierarchy. This is to allow re-parenting and moving of parts between stacks.
Figure 19-4. Runtime widget parenting
Similarly, a presentation should never reference a presentable part's control because a part can
be shown simultaneously in several stacks. Instead, presentations should call the
IPresentablePart method setBounds(Rectangle) and the IPresentablePart method
setVisible(boolean) to control the placement of the part's controls.
The stacks do parent some widgets, however. Normally they create a number of widgets to
display tabs or other UI pieces used for inter-part navigation. These parts should be parented
by the stack itself.
19.3.2. StackPresentation
A StackPresentation contains the bulk of a presentation's definition. Its main responsibilities
are to control the visibility of its presentation parts, to supply the trim around the presentable
parts, and to show the part's toolbar.
Although most users expect presentations to support the behavior provided by the Workbench's
default presentation, much of that behavior is optional. Your presentation needs to support only
the features required by your application. Table 19-1 provides a brief overview of the features
provided by the Workbench's built-in presentation. This is a good guide as to what a fullfeatured presentation should support.
Table 19-1. Workbench's Default Presentation Features
Feature
Description
Location
The stack must set the bounds of the presentable part so the part
can be drawn in the appropriate location. The stack doesn't
actually parent the presentable part's widget; it simply informs
the part where it can display itself. A stack selects a part by
calling the IStackPresentationSite method selectPart().
Disposal
The stack must set a listener on the composite it creates and
Feature
Disposal
Description
The stack must set a listener on the composite it creates and
delete the stack when its composite is deleted or when the stack's
dispose() method is called. The stack cannot assume that
disposal occurs only when the stack's dispose() method is called
since there are times when the StackPresentation method
dispose() is not called and instead the stack's control is simply
destroyed.
Part property changes
Presentable parts have several properties that can dynamically
change (e.g., name, image, dirty, and busy states). Stacks should
listen for changes to a part and update themselves accordingly.
Closing parts
The stack should allow the user to select a part and close it. To
close a part, call the IStackPresentationSite method
closePart().
Toolbars
Determines the visibility and location of the toolbar for a part
when the part is visible.
Ordering parts
You can allow the user to reorder parts within the stack.
Parts list
The stack can display a part picker that is shown when the "Open
Editor Drop Down" key is pressed. It can also provide a button in
the stack to show the drop-down.
Part menu
The stack should display the part menu provided by the
IPresentablePart method showMenu().
Borders and trim
The stack presentation creates a trim that is displayed around the
stack.
Focus
A stack can listen to focus changes and highlight itself based on
its focus. The stack site can set the active state via the
StackPresentation method setActive().
Themes
The stack uses theme colors via the IWorkbenchThemeConstants
when providing focus and selection highlights to parts. It can also
listen to changes to the theme and update appropriately.
Persistence
A stack can persist and restore information related to its parts
using the StackPresentation methods restoreState() and
saveState(). For example, the stack can remember the parts that
it contains and automatically recreate the parts using the
IPresentationSerializer factory when the stack is recreated.
19.4. Example Presentation
This section illustrates how to code some of the features in Table 19-1 to implement the simple
presentation shown in Figure 19-5. Rather than showing all the code here, we highlight the
most important parts. The complete version is available in the sample code for this chapter.
Figure 19-5. Hyperbola's new look
Let's start by defining the requirements:
The presentable part tabs must appear at the top of the stack and allow the user to switch
between parts. The active part's tab must be shown in green and the hidden parts shown
in white.
The part tabs must wrap to another line if there are too many parts to show on one line.
Toolbars and view menus must be supported.
To close a part, there must be a close action in the context menu for a tab. In addition
there are actions to customize the look and location of the tabs (e.g., bottom, right, left).
The user must be able to drag and drop parts from one stack to another and move the
stacks around. In this example, layout preferences such as standalone and non-closeable
are to be ignored.
The presentation border and trim must be drawn to highlight the active stack.
19.4.1. The Presentation Factory
The first thing to do is declare the presentation factory and register it with the
org.eclipse.ui.presentationFactories extension point, as shown below:
org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.ui.presentationFactories>
<factory
class=
"org.eclipsercp.hyperbola.presentation.HyperbolaPresentationFactory"
name="Hyperbola Presentation"
id="org.eclipsercp.hyperbola.presentation"/>
</extension>
The implementation of the factory is straightforwardit simply creates the stack presentation
instances as shown in the next code snippet. Notice that this is where the stack presentation
gets access to the IStackPresentationSite and the Composite, with which it can parent its
controls. These are passed to the factory by the Workbench and are simply forwarded to the
stack presentations. For simplicity in our example, all the methods return the same
StackPresentation. An alternative is to create different types for editors, views, and standalone
views.
org.eclipsercp.hyperbola/HyperbolaPresentationFactory
public class HyperbolaPresentationFactory
extends AbstractPresentationFactory {
public StackPresentation createEditorPresentation(Composite parent,
IStackPresentationSite site) {
return new HyperbolaPresentation(parent, site);
}
public StackPresentation createViewPresentation(Composite parent,
IStackPresentationSite site) {
return new HyperbolaPresentation(parent, site);
}
public StackPresentation createStandaloneViewPresentation(Composite parent,
IStackPresentationSite site, boolean showTitle) {
return new HyperbolaPresentation(parent, site);
}
}
19.4.2. The Stack Presentation
Again, the stack is the meat of any presentation. In our example, the meat is in the
HyperbolaStackPresentation class. The main task in creating a presentation is to design its
layout and pick the UI widgets needed for switching between presentable parts, showing the
part contents, the part toolbar, and any other widgets that appear in the presentation. Figure
19-6 shows a sketch of our example presentation with the widgets that are used to place the
different elements of a IPresentablePart. At the top is the titleArea, which contains the tabs
that allow switching between parts. This is a simple Composite with a RowLayout that wraps
children if necessary. When a part is added to the presentation, a composite that shows the
part's name and icon is added to the titleArea.
Figure 19-6. Design for the example presentation
Note
Using Composites for the tabs is a bit heavyweight; instead, we could have used
org.eclipse.swt.widgets.items. Whereas Composites can contain other controls,
items can't. The main reason we used Composites was that the existing SWT layouts
work with Composites and not items. We didn't want to add more complexity to the
example by having to write a custom layout. For an example of how to use items, see
SWT's CTabFolder, CTabItem , and CTabLayout.
The contentArea is where the IPresentablePart can be displayed. It's implemented as a
ViewForm , which is a handy SWT class that allows the layout of three controls at the top and one
content control below. If an IPresentablePart has a toolbar, then it's shown in the ViewForm 's
top right slot.
The bulk of a presentation involves laying out the controls in the presentation and calculating in
which area the part should be shown when calling IPresentablePart.setBounds(Rectangle).
However, the first thing the presentation does is create the widgets shown in Figure 19-6,
adding a dispose listener to the top-most control in the presentation.
org.eclipsercp.hyperbola/HyperbolaStackPresentation
public HyperbolaStackPresentation(Composite parent,
IStackPresentationSite stackSite) {
// Create a top-level control for the presentation.
presentationControl = new Composite(parent, SWT.NONE);
titleArea = new Composite(presentationControl, SWT.NONE);
contentArea = new ViewForm(presentationControl, SWT.NONE);
clientArea = new Composite(contentArea, SWT.NONE);
clientArea.setVisible(false);
// Add a dispose listener. This will call the presentationDisposed()
// method when the widget is destroyed.
presentationControl.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
// Remove any listeners or resources that were created
presentationDisposed();
}
});
}
public void dispose() {
presentationDisposed();
}
Note here that the presentation has a dispose() method, but it is not guaranteed to be called.
Since each presentation creates a top-level control, the disposal of that control indicates that it
is safe to clean up any resources held by the presentation. Without this structure, it is very easy
to leak presentations as stacks are closed.
The titleArea is used to show view and editor tabs. A simple Composite with a RowLayout is
enough here. The tabs that are added as children of the titleArea use the RowLayout's wrapping
behavior.
org.eclipsercp.hyperbola/HyperbolaStackPresentation
public HyperbolaStackPresentation(Composite parent,
IStackPresentationSite stackSite) {
...
titleArea.addListener(SWT.MenuDetect, menuListener);
RowLayout rowLayout = new RowLayout ();
rowLayout.marginLeft = 0;
rowLayout.marginRight = 0;
rowLayout.marginTop = 0;
rowLayout.marginBottom = 0;
rowLayout.spacing = 0;
titleArea.setLayout (rowLayout);
...
}
It is standard practice for presentations to draw an outline of the stack area and differentiate
the part switching (i.e., tab) area from the area that shows the presentable parts themselves.
To do this, a paint listener is added to the top-most control.
org.eclipsercp.hyperbola/HyperbolaStackPresentation
public HyperbolaStackPresentation(Composite parent,
IStackPresentationSite stackSite) {
...
presentationControl.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
Rectangle clientArea = presentationControl.getClientArea();
e.gc.setLineWidth(getBorderWidth());
e.gc.setForeground(getBorderColor());
e.gc.drawRectangle(clientArea.x, clientArea.y,
clientArea.width-1, clientArea.height-1);
Rectangle contentAreaBounds = contentArea.getBounds();
int ypos = contentAreaBounds.y - 1;
e.gc.drawLine(clientArea.x, ypos,
clientArea.x + clientArea.width - 1, ypos);
}
});
}
19.4.3. Size and Position
When the presentation is told to update its size, it lays out its controls and initializes the bounds
for the presentable part, as shown below. The presentable part needs to be told where, relative
to the presentation, to draw.
org.eclipsercp.hyperbola/HyperbolaStackPresentation
public void setBounds(Rectangle bounds) {
Rectangle newBounds = Geometry.copy(bounds);
// Set the bounds of the presentation widget
presentationControl.setBounds(newBounds);
// Update the bounds of the currently visible part and the title area
// Determine the inner bounds of the presentation
Rectangle presentationClientArea = presentationControl.getClientArea();
presentationClientArea.x += getBorderWidth();
presentationClientArea.width -= getBorderWidth() * 2;
presentationClientArea.y += getBorderWidth();
presentationClientArea.height -= getBorderWidth() * 2;
Point p = titleArea.computeSize(presentationClientArea.width,
SWT.DEFAULT);
titleArea.setBounds(x, y, presentationClientArea.width, p.y);
contentArea.setBounds(presentationClientArea.x, yy,
presentationClientArea.width, presentationClientArea.height - p.y);
}
Usually, the presentable part's placement depends on the other widgets shown in the
presentation. In this example, the size of the titleArea dictates the size of the contentAreathe
tabs take priority over the part content. The thickness and shape of the border must also be
considered.
The setBounds(Rectangle) method gives you full control. The layout can be as complicated or as
simple as you need. The only restriction is that only one part can be visible at a time within the
same stack. Beyond that, you can put the tabs at the bottom, down the sides, have big tabs,
small tabs, no tabs, and so onyou have complete control.
19.4.4. Adding, Selecting, and Removing Parts
Now that the widgets are created and laid out, the presentation has to handle part
manipulation. This requires implementations of the following StackPresentation methods:
addPart(IPresentablePart), removePart (IPresentablePart), and showPart(IPresentablePart).
This example shows presentation parts as rectangles in the title area. Clicking on a rectangle
switches to the part associated with that rectangle.
As shown in the next snippet, when a part is added, a PartTab composite is created and
parented by the titleArea. The new part is attached to the PartTab and the PartTab is
initialized using the image and text from the IPresentablePart. All the PartTab does is add the
image and text and decide how to render the tab. As shown below, the PartTab does all of its
work in a paint listener:
org.eclipsercp.hyperbola/PartTab
public PartTab(Composite parent) {
super(parent, SWT.NONE);
addPaintListener(this);
}
public void paintControl(PaintEvent e) {
Rectangle titleRect = getClientArea();
int x = titleRect.x + VERT_SPACING;
GC gc = e.gc;
setBackground(getParent().getBackground());
fill(gc, titleRect.x, titleRect.y, titleRect.width - 1,
titleRect.height);
// Draw the image and text
...
}
public void setSelected(boolean selected) {
this.selected = selected;
redraw();
}
public Point computeSize(int wHint, int hHint) {
int width = VERT_SPACING; int height = HORIZ_SPACING;
GC gc = new GC(this);
if (image != null && showImage) {
Rectangle imageBounds = image.getBounds();
height = imageBounds.height + HORIZ_SPACING;
width += imageBounds.width + VERT_SPACING;
}
}
...
Our PartTab is relatively simple. There are many other properties that could be shown, for
example, the part's content description or busy state. Your presentation can show these and
others however you like.
The following snippet shows the creation and initialization of a PartTab when a new part is
added. Notice that a property listener is added to the presentation part. This allows the PartTab
to be updated when a part property changes. Properties are things such as the part's text, icon,
tool tip, and dirty stateessential data to show in the tab. See IPresentablePart for a complete
listing of supported properties.
org.eclipsercp.hyperbola/HyperbolaStackPresentation
public void addPart(IPresentablePart newPart, Object cookie) {
PartTab item = new PartTab(titleArea);
// Initialize the PartTab with the image, text... from the part.
updatePartItem(item, newPart);
item.setData(PART_DATA, newPart);
// This will update the ToolItem to reflect changes in the part.
newPart.addPropertyListener(childPropertyChangeListener);
// Attach a dispose listener to the item.
item.addDisposeListener(tabDisposeListener);
// Listen to selection events in the new tool item.
item.addMouseListener(mouseListener);
...
}
Tip
Remember to remove listeners when they are no longer needed to avoid memory
leaks.
The snippet above added a mouse listener to the new tab. This is used to detect when tabs are
selected and then to make parts visible. The snippet below shows the listener and highlights the
importance of remembering the part with its associated tab. Otherwise, how would you find the
part that matches the event?
org.eclipsercp.hyperbola/HyperbolaStackPresentation
private MouseListener mouseListener = new MouseAdapter() {
public void mouseDown(MouseEvent e) {
PartTab toolItem = (PartTab) e.widget;
IPresentablePart item = getPartForTab(toolItem);
if (item == null)
return;
// Clicking on the active tab should give focus to the current part
if (item == current)
item.setFocus();
getSite().selectPart(item);
selectPart(item);
Point toDisplay = toolItem.toDisplay(new Point(e.x, e.y));
if (e.button == 3)
showSystemMenu(toDisplay);
...
};
19.4.5. Menus
There are three basic menus that should be handled by a presentation: the system menu, the
part list menu, and the view pane menu. The system menu normally contains actions related to
the presentation; the part list menu should allow the user to navigate between the parts in the
stack; and the view pane menu is the menu returned by the IPresentablePart method
getMenu().
In our example, only the system menu is needed. The presentation is responsible for creating
and showing the menu, but must allow the IStackPresentation site to add standard actions
such as move, size, and detach actions to the menu.
org.eclipsercp.hyperbola/HyperbolaStackPresentation
Menu aMenu = systemMenuManager.createContextMenu(titleArea);
getSite().addSystemActions(systemMenuManager);
systemMenuManager.update(true);
aMenu.setLocation(displayPos.x, displayPos.y);
aMenu.setVisible(true);
19.5. Summary
In this chapter, you learned that presentations are responsible for managing and drawing a
stack of views or editors. A presentation instance is created for each stack of views or editors in
a perspective. These instances do not actually parent the widgets created for the view or editor;
rather, they simply control the size and location of the views and editors in the stack. With an
understanding of the basic implementation patterns from Section 19.4 and the responsibilities
of a presentation from Table 19-1, you should have enough background to write your own
presentation.
The best way to write your own presentation is to start with an existing one then modify it to
suit your needs. There is actually quite a bit more to the implementation than shown here. The
full example included in the sample code for this chapter adds support for dragging, closing
parts, moving the tabs to the bottom, right, or left of the content area, and much more. It
should serve as a good starting point for creating your own presentations. The example
presentations in org.eclipse.ui.examples.presentations can also serve as a base for your own
presentations.
Part IV: Development Processes
The creation of a rich client application is as much about the actual code that needs to be
written as it is about how to structure, package, and deliver it. Part IV of the book
introduces you to an array of topics from integrating non-Eclipse code libraries to creating
dynamic systems that run in a wide variety of operating environments to running
automated builds and delivering RCP applications.
Chapter 20. Integrating Code Libraries
Even the most Eclipse-biased developer would concede that the majority of Java libraries out
there are not shipped as plug-ins. This chapter discusses the integration of these libraries into
Eclipse.
In Part II, you saw that the Smack messaging library proved to be very useful for Hyperbola. In
Chapter 10, "Messaging Support," we used the term bundling to capture the idea of adapting or
integrating one or more libraries (i.e., JARs) into Eclipse. We also showed you how to use PDE
to convert Smack libraries into a bundle.
Bundling is typically a straightforward process, but there are choices to be made and issues to
be resolved. In this chapter, we discuss the different bundling variants and common problems
that arise when using existing code within Eclipse. In particular, we show you how to:
Structure plug-ins differently.
Bundle by injectionAdd bundle metadata to existing JARs.
Bundle by wrappingWrap JARs with bundle metadata.
Bundle by referenceAdd bundle metadata beside existing JARs without affecting the JARs,
their original location, or their surrounding directory structure.
Solve common classloading problems.
20.1. Plug-ins as JARs
Before diving into the different bundling techniques, it's worth looking at how plug-ins are
typically structured. Traditional plug-ins are stored as a directory structure, as shown on the left
of Figure 20-1. The directory contains the code JARs, metadata such as a plugin.xml and
MANIFEST.MF, and other non-code resources.
Figure 20-1. JAR'ing a traditional plug-in directory
[View full size image]
As of Eclipse 3.1, plug-ins as JARs is more the rule. The bottom right of Figure 20-1 shows this
new structure. Note that it is more than a simple JAR'ing of the plug-in directory. The non-code
contents of the original plug-in, that is, the metadata and files around the plug-in's code JAR,
are injected into the root of the code JAR itself. This flips things around a bit and looks at plugins as code JARs with some supporting metadata and extra files inside. This view is subtle, but
quite powerful.
For example, the basic structure of a standard, non-Eclipse code library and a JAR'd plug-in are
the samethey are both just JARs. It is then easier and more natural for library producers to
simply include the extra bundle metadata in their MANIFEST.MF and ship their library as both a
standalone JAR and a bundle ready for integration into Eclipse. If all libraries were shipped this
way, you could skip the rest of this chapter! That day may come, but in the meantime, this
mindset should help you in the process of bundling the libraries you want to use in Eclipse.
The top right of Figure 20-1 shows a straightforward JAR'ing of a traditional plug-in directory.
Note that this results in a plug-in JAR that contains nested code JARs. Chapter 26, "OSGi
Essentials," and Section 20.3, "Bundling by Wrapping," outline a number of issues with this
structure. Here, it suffices to say that plug-ins in this form can be used, but they are inefficient
in both speed and space.
20.2. Bundling by Injection
As you saw in the previous section, JAR files can be used as plug-ins as long as they contain the
metadata needed by Eclipse. Here we look at how to bundle existing code library JARs by
injecting them with this Eclipse metadata. This approach retains all the benefits of JAR'd plugins and increases the chances that the library authors will include the injected metadata directly
in their original releasesit directly illustrates the simplicity of the required changes.
Figure 20-2 shows the process of bundling the two Smack JARs on the left into one JAR that is a
bundle. The operation merges the two input JARs and adds the required Eclipse bundle
definition information in the MANIFEST.MF file. This is what we did with the Smack JARs in
Chapter 10.
Figure 20-2. Injecting metadata into a code library
[View full size image]
To bundle a set of library JARs, create a new plug-in project using File > New > Project... >
Plug-in from existing JARs, as you did in Chapter 10. Ensure that the Unzip the JAR
archives into the project box is checked so that the wizard unpacks all the JARs as they are
imported into the new project. If more than one JAR is listed, as in Figure 20-2, they are
merged as if they were on the classpath in the order specified in the wizard. That is, resources
in subsequent JARs do not overwrite resources in previous JARs. The wizard then generates a
manifest that exports all the packages in the plug-in (i.e., those found in the original JARs).
There are two manual steps that you may need to take when bundling JARs:
Create a plugin.xml Bundled libraries may be crafted to contribute extensions, but
generally they do not contribute extension points. Extension points require Eclipse-specific
processing and these libraries are, by definition, written without Eclipse in mind. If you
want the library to contribute extensions, use the plug-in editor to define the extensions as
outlined in Part IIthe bundling wizard cannot generate these for you.
Define dependencies In some cases, a library A uses code found in library B. Assuming
you are bundling both A and B independently, you need to configure A to require B or
import the needed packages. The wizard automatically generates exports for all the
packages in both A and B, but does not analyze A's code to determine what packages it
needsyou have to do that manually using the plug-in editor.
The resultant project is just like any other plug-in project. You can leave it in the workspace and
code against it, you can run with it, and you can export it. A handy trick is to export it and add
it to the target platform. You can then delete it from the workspaceit becomes just another
plug-in that you are using. This keeps your workspace clean and allows the new plug-in to be
shared between workspaces.
An alternative to combining JARs is to convert each to a bundle individually. This is certainly
feasible, but is not always the best choice. For example, Ant comes as a set of about 28 JARs.
Many of these are tiny (<10K). While the overhead of a bundle is small, this is too finegrainedvast numbers of plug-ins are harder to manage.
Bundling JARs separately is also a problem when packages are split over two or more JARs. If
code in these package fragments needs to see a package's private members in the other JARs,
bundling separately does not work. Each bundle gets its own classloader. As such, the package
org.eclipsercp.hyperbola loaded by bundle A is actually different from the one with the same
name but loaded by bundle B. They do not share package visibility.
20.3. Bundling by Wrapping
Since injecting metadata as described in the previous section requires modification of the
original JARs, it is not always feasible. The following list outlines the most common problems:
Licensing Some licenses explicitly state that the licensed material cannot be modified or
that modifications trigger further restrictions or obligations.
Signing JARs are often signed to prevent otherwise undetected tampering of their
contents. In this use of signing, it may be possible to inject the metadata and additional
filesthese files are either not signed or are signed by a different signer. In other situations,
signing is used to imply permissions and rights. In these cases, it is less clear that
metadata injection is feasible.
Multiple JARs Some libraries come as multiple JARs. As discussed above, the JARs can be
bundled separately or they can be combined. Both approaches are feasible, but may not
be attractive in some cases.
The best approach may be to wrap the set of JARs in a plug-in directory and create a
MANIFEST.MF that describes their dependencies and exports, as shown in Figure 20-3.
Figure 20-3. Wrapping a code library
[View full size image]
The same bundling wizard used to inject metadata can be used to wrap JARs. Simply
unchecking the Unzip the JAR archives into the project box tells the wizard to copy the JARs
into the project without extracting their contents. The JARs are then listed on the bundle's
classpath in the order you added them to the wizard.
Again, the resultant project is just like any other plug-in project. When you go to export it,
however, you should package the plug-in as a directory. Use the Package plug-ins and
individual JAR archives option on the Export > Deployable plug-ins and fragments
wizard. Otherwise, you will end up with the original library JARs nested inside the new plug-in
JAR. Plug-ins in this layout are usable, but are inefficient since the nested JARs must be
extracted before being used and standard Java compilers cannot compile against such JARs.
Chapter 26, "OSGi Essentials," has a lengthy discussion of the pros and cons of various plug-in
shapes.
20.4. Bundling by Reference
Warning
This approach is experimental in Eclipse 3.1 and is subject to change. It breaks some
fundamental notions of plug-in encapsulation and is not compatible with various parts
of the Eclipse tooling. It does, however, address some real use cases. Use with caution
and only when absolutely needed.
In some situations, installed JARs cannot be moved let alone modified. This typically happens
when the libraries are delivered as part of another product and are laid down by an installer.
The JARs are in a specific spot and are expected to be there to be found by other programs.
The bundling approaches outlined so far do not work because they modify either the JAR itself
or its surroundings. For example, wrapping adds plug-in metadata beside the JAR being
wrapped. If there is only one set of JARs to wrap in a directory, the generated metadata can be
directly added to that directory. If there are multiple libraries in the same directory, the
metadata files conflict with each other. Metadata injection can only be used if the issues
mentioned earlier are not applicable. For example, the JAR has to be writable.
Even if injection or wrapping was used, there is still the problem of how to get the resultant
plug-in installed into Eclipse. The natural Eclipse pattern is to have plug-ins either in the main
plugins directory in the Eclipse install or in a plugins directory in an extension location. Both
approaches require moving the newly bundled library. Plug-ins can be installed using markup in
the config.ini file (see Chapter 26 for a discussion of the osgi.bundles property), but that is
cumbersome and hard to manage. It would be better to simply create a plug-in in one of the
normal spots (i.e., in a plugins directory that is known by Eclipse) and point at the required
JARs wherever they happen to be on disk, that is, put the metadata on the side and leave the
code JARs external to the plug-in.
To illustrate how this works, consider a mythical Java database connectivity (JDBC) driver JAR
that comes with a database product. The product installer puts jdbc.jar in
c:\db\drivers\jdbc.jar and you cannot modify it, move it, or add files to the drivers
directory.
To set this up, proceed as though you are using the wrapping approach from the previous
section. Run the New Project wizard and create a JDBC plug-in based on jdbc.jar . Don't
worry about the libraries being copied into the project; you can use them to do your normal
development.
When you go to run your application, you need to use the original JDBC libraries. Use the
following steps to set up the structure shown in Figure 20-4:
1. Export the newly created JDBC plug-in from your workspace to your target's plugins
directory.
2. Delete the exported JARs and extraneous files (e.g., .project ) from the exported target
plug-in.
3. Edit the exported target plug-in's MANIFEST.MF and change the classpath to point to the
original JARs using absolute filesystem paths. For example, replace "jdbc.jar" with
"external:$JDBC_HOME$/drivers/jdbc.jar". You can use environment or system properties,
or full filesystem paths to identify the desired JAR.
4. In the IDE, use Window > Preferences... > Plug-in Development > Target Platform
> Reload to refresh the target and add the new JDBC plug-in.
5. Set up an Eclipse Application launch configuration to run your product. On the Plug-ins
page, select the third option, Choose plug-ins and fragments to launch from the list
.... In the list of plug-ins, uncheck the JDBC plug-in in the Workspace Plug-ins list and
check the one in the External Plug-ins list.
6. Run the launch configuration. It is difficult to tell which JAR is being used, but it should be
the original c:\db\drivers\jdbc.jar. You can confirm this by renaming the original JAR
and running. The application should fail when trying to load JDBC classes.
Figure 20-4. External plug-in JARs
[View full size image]
When it comes time to deploy your application and the JDBC plug-in, you have to rely on a
native installer or feature install handler to set up the plug-in's manifest and ensure that
jdbc.jar is in fact installed. The task is quite a bit easier if the database product defines
environment variables or Java system properties, as shown in the example, to describe the
location of its install. For example, if the product defined JDBC_HOME as an environment variable,
then you can set up the JDBC plug-in's manifest to include the line:
Bundle-Classpath:
external:$JDBC_HOME$/drivers/jdbc.jar
This mechanism has the added benefit that the JDBC plug-in can be built and delivered using
standard Eclipse mechanisms. Variables make this even easier.
The real danger in using this setup is the potential for mismatching the metadata and contents
of the JARs. For example, you might generate the metadata based on version 3 of the JDBC
drivers, but the actual installed drivers are version 2. Tracking down these kinds of bugs is
challenging to say the least. Nonetheless, the mechanism is there and it solves some real
problems. Use it with caution and care.
20.5. Troubleshooting Classloading Problems
Most code libraries are quite straightforward to bundle and then use in Eclipse-based systems.
You've seen that the wizard to create plug-ins from existing JARs does most of the work for you.
But what happens if there are problems after bundling? If you remember from Chapter 10, after
bundling Smack, the next thing we did was write a test plug-in to see if Smack could be
referenced as a plug-in. At this point, there are two main problems that could occur. The first is
at compile timeclasses in the bundled JAR may not be visible. This is easily fixed by ensuring
that the plug-in containing the bundled JAR exports all the necessary packages from the JAR.
But what if something goes wrong at runtime? The classic symptom is ClassNotFoundExceptions
and NoClassDefFoundErrors showing up in the console or the log file.
This entire section is devoted to helping you understand and troubleshoot these runtime errors.
Typically, these relate to the classloading structure inherent in Eclipse and OSGi. The OSGi
classloading strategy and mechanism is discussed in Chapter 26, but here we detail some
standard library coding patterns and how to handle them in Eclipse.
20.5.1. Issues with Class.forName()
Let's start with the classic example of ClassNotFoundExceptions , which occurs while using a
bundled code library. Consider adding logging using log4j, a popular library for managing and
logging events (http://logging.apache.org/), in Hyperbola. Using the techniques described
earlier, you can bundle log4j and add it to either your workspace or target and continue
development. At runtime, however, log4j throws a number of ClassNotFoundExceptions when
trying to configure its appenders.
log4j is extensible in that it allows clients to supply log appenderseffectively log event handlers.
Appenders are configured by naming their implementation classes in metadata files, much like
Eclipse plug-ins define extensions. log4j then reads these files and loads the named classes
using a code pattern similar to the snippet below:
public class AppenderHelper {
private Appender createAppender(appenderName) {
Class appenderClass = Class.forName(appenderName);
return appenderClass.newInstance();
}
}
Note
log4j actually uses a more advanced code pattern that is detailed in the next section.
For the sake of this example, assume that log4j is running with the log4j.ignoreTCL
property set to true and Class.forName(String) is its only classloading option.
Class.forName(String) is the classic Java mechanism for dynamic class discovery and loading.
It uses the current classloader to look for and load the requested class, in this case, an
appender. The current classloader is the classloader that loaded the class containing the method
executing the forName(String) call. In the snippet above, the current classloader is the one that
loaded AppenderHelper. The net result is the same as if a reference to the appender class was
compiled into createAppender(). This is exactly what using Class.forName(String) is trying to
work around.
In Eclipse, this is problematic because the log4j plug-in typically does not depend on the plugins providing the appenders. This is actually the pointappenders are log4j's way of allowing its
function to be extended, but the log4j plug-in cannot load these appenders because it does not
have the proper visibility.
If log4j was written as a standard Eclipse plug-in, it could, for example, use the Eclipse
extension registry and define an appenders extension point. Plug-ins wanting to provide
extenders would then contribute executable extensions that name their appender classes and
log4j would use createExecutableExtension() (see Chapter 23, "RCP Everywhere") rather than
the code in createAppender() above. Unfortunately, log4j is not written as an Eclipse plug-in
and this technique is not available.
Buddy classloading offers an alternative integration strategy that does not require code
modification. The mechanism works as follows:
Plug-ins declare that they need the help of other plug-ins to load classes.
They also identify the kind of help they want by specifying a buddy policy. The policy
defines what kinds of plug-ins are to be considered to be buddies as well as how (e.g., in
what order) they are consulted.
When a plug-in fails to find a desired class through all the normal routes as outlined in
Section 26.10, "Classloading" (i.e., Import-Package, Require-Bundle, and local classes), its
buddy policy is invoked.
The invoked policy discovers a set of buddies and consults each one in turn until either the
class is found or the list is exhausted.
Let's apply the built-in registered buddy policy to the log4j case and see how it helps. In the
log4j scenario, there are a relatively large number of potential clients of the logging API and a
small number of clients supplying appenders. For performance and simplicity, it makes sense to
limit the buddy search scope to just those supplying appenders. The simplest approach is to
make those plug-ins explicitly register as buddies of log4j.
To set this up, you first mark the log4j plug-in as needing classloading help and identify the
"registered" policy as the policy to use. The following line added to log4j's MANIFEST.MF makes
that declaration:
Eclipse-BuddyPolicy:
registered
Then in each plug-in that supplies appenders, add the following line to the MANIFEST.MF to
register the plug-in as a buddy of log4j (i.e., org.apache.log4j):
Eclipse-RegisterBuddy:
org.apache.log4j
At runtime, when log4j goes to instantiate an appender using Class.forName(String), it first
tries all its normal prerequisites. Then, when it fails to find the appender class, each of its
registered buddy plug-ins is asked to load the class. If all the appender plug-ins are registered,
the appender class is sure to be found.
Note
Buddies are consulted as if they were originating the load class request using
Bundle.loadClass(String). That is, the buddy's imported packages, required bundles,
and in fact, its own buddies are all invoked as necessary in the search for the desired
class.
20.5.1.1. Built-in Buddy Policies
Eclipse supplies a number of built-in policies, as summarized in Table 20-1:
Table 20-1. Built-in Buddy Policies
Name
Description
boot
Indicates that the standard Java boot classloader is a buddy.
ext
Indicates that the standard Java extension classloader is a buddy. This
policy is a superset of the boot policy.
app
Indicates that the standard Java application classloader is a buddy.
This policy is a superset of the ext policy.
parent
Indicates that the plug-in's parent classloader is a buddy. By default,
the parent classloader is the standard Java boot classloader. Plug-in
classloader parentage is controlled on a global basis by setting the
osgi.parentClassloader system property.
dependent
Consults all plug-ins that directly or indirectly depend on the current
plug-in. Note that this casts a rather wide net and may introduce
performance problems as the number of plug-ins increases.
registered
Is similar to the dependent policy, but only dependent plug-ins that
have explicitly registered themselves as buddies of the current plug-in
are consulted.
One plug-in can apply several policies simply by listing them on the Eclipse-BuddyPolicy line in
the MANIFEST.MF. Eclipse invokes each policy in turn until either the class is found or all policies
have been consulted.
20.5.1.2. Buddy Classloading Considerations
As powerful and useful as buddy classloading is, it is still a mechanism of last resort. There are
a number of issues that you should consider carefully before using buddies in your system:
Buddy classloading runs counter to the notions of component that Eclipse attempts to
maintain and is not particularly well-suited to dynamic environmentsparticularly ones
where buddies can be uninstalled.
Buddy classloading also incurs various performance costs. For example, when a normal
classload fails in a plug-in using buddy loading, the buddy policy is invoked. Typical Java
resource bundle loading causes up to three classload failures and some number of
resource load failures before finally getting the desired resource. Each of these failures
repeats the buddy search.
Buddy loading is relatively undirected. Normally, the OSGi classloading infrastructure
knows exactly where to go to find any given packagethis is the information gleaned from
the MANIFEST.MF files. Typical buddy loading policies simply search successive buddies.
It is possible that the buddy search will find the wrong class with the right name. If two
buddies contain the same class, depending on the policy, it may be ambiguous as to which
buddy ultimately supplies the class.
20.5.1.3. Dynamic-ImportPackage vs. Buddy Classloading
Readers familiar with OSGi may be scratching their heads asking, "What about DynamicImportPackage ?" For readers who are not familiar with OSGi, Dynamic-ImportPackage is a
mechanism that allows a bundle to state its need to use a given set of packages but not force an
early binding to the exporters of those packages. Rather, the binding to package exporters is
done at runtime when the bundle tries to load from a dynamically imported package.
So, some Class.forName() problems can be alleviated simply by adding
DynamicImport-Package: <list of packages or *>
to the MANIFEST.MF for the plug-in using Class.forName(String). This has the following
drawbacks compared to the buddy loading described here:
Dynamic importing is unscoped. That is, all bundles exporting packages are considered. As
such, the search may include many irrelevant and unrelated bundles. By contrast, the
buddy loading mechanism allows for policies that use dynamic information such as the
plug-in dependency graph to drive the search for classes.
Dynamic importing implies inter-bundle constraints. That is, when a bundle A loads a class
from a bundle B using dynamic importing, A is then considered to be dependent on B. If B
is refreshed or uninstalled, A is refreshed. This behavior is valuable for maintaining
consistency when A actually uses and retains references to B's classes. However, several
serialization scenarios have A simply using B's classes temporarily (e.g., to load some
object stream)there should be no lasting dependency.
Dynamic import considers only packages explicitly exported by other bundles. Again this
can be a desirable characteristic, but in various use cases such as serialization, the
importing bundle potentially needs access to all classes in the system, for example, to load
instances from an object stream.
This is not to say that Dynamic-ImportPackage should never be used, just that it should be used
appropriately. For example, when the set of packages needed is well-known and the importing
bundle has a lasting dependency on the imported packages.
20.5.2. Issues with Context Classloaders
Since Java 1.2, the Class.forName(String) mechanism has been largely superseded by context
classloading. As such, most modern class libraries use a context classloader. In the discussion
below, we show how Eclipse transparently converts the use of context classloaders into
something equivalent to Class.forName (String). Doing this allows the buddy loading and
Dynamic-Import mechanisms described above to be used to eliminate ClassNotFoundExceptions
and NoClassDefFoundErrors.
Each Java Thread has an associated context classloader field that contains a classloader. The
classloader in this field is set, typically by the application container, to match the context of this
current execution. That is, the field contains a classloader that has access to the classes related
to the current execution (e.g., Web request being processed). Libraries such as log4j access and
use the context classloader with the updated AppenderHelper code pattern below:
public class AppenderHelper {
private Appender createAppender(String appenderName) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class appenderClass = loader.loadClass(appenderName);
return (Appender)appenderClass.newInstance();
}
}
By default, the context classloader is set to be the normal Java application classloader. That is,
the use of the context classloader in normal Java application scenarios is equivalent to using
Class.forName(String) and there is only one classloader, the application classloader. When
running inside Eclipse, however, the code pattern outlined above fails because:
By default, Eclipse does not consult the application classloader. Eclipse-based applications
put their code on dynamic plug-in classpaths rather than on the normal Java application
classpath.
Eclipse cannot detect plug-in context switches and set the context classloader as required.
That is, there is no way to tell when execution context shifts from one plug-in to the next
as is done in Web application servers.
These characteristics, combined with the compositional nature of Eclipse, mean that the value of
the context classloader field is seldom useful.
Clients can, however, explicitly set the context classloader before calling libraries that use the
context classloader. The snippet below shows an example of calling log4j using this approach:
Thread thread = Thread.currentThread();
ClassLoader loader = thread.getContextClassLoader();
thread.setContextClassLoader(this.getClass().getClassLoader());
try {
... log4j library call that calls AppenderHelper.createAppender() ...
} finally {
thread.setContextClassLoader(loader);
}
First the current context classloader is saved. The context classloader is then set to an
appropriate value for the current execution and log4j is called. log4j's AppenderHelper uses the
context classloader, so in this case, it uses the client's classloader (e.g.,
this.getClass().getClassLoader()). When the operation is finished, the original context
classloader is restored.
The assumption here is that the client's classloader is able to load all required classes. This may
or may not be true. Even if it can, the coding pattern is cumbersome to use and hard to
maintain for any significant number of library calls. Ideally, log4j would be able to dynamically
discover the context relevant to a particular classloading operation. Eclipse enables this using
the context finder.
The context finder is a kind of ClassLoader that is installed by Eclipse as the default context
classloader. When invoked, it searches down the Java execution stack for a classloader other
than the system classloader. In the AppenderHelper example above, it finds the log4j plug-in's
classloaderthe one that loaded AppenderHelper. The context finder then delegates the load
request to the discovered classloader.
This mechanism transforms log4j's call to getContextClassLoader(). loadClass(String) to the
equivalent Class.forName(String) call using log4j's classloader to load the given class. Now the
buddy classloading techniques discussed in Section 20.5.1 can be applied to help log4j load the
needed appender classes.
The net effect is that clients of log4j do not have to use the cumbersome coding pattern outlined
above even though the libraries they call use the context classloader. This approach generalizes
to other context classloading situations.
20.5.3. Managing JRE Classes
For various reasons, some libraries include packages that are normally found in the JRE. For
example, version 2.6 of Xalan, the XML transformation engine, comes with types from the
org.w3c.dom.xpath package in xalan.jar. These types are also included as part of typical JRE
distributions. When xalan.jar is used as part of a normal Java application, it is added to the
classpath, but its xpath classes are obscured by those in the JRE. Everything is fine.
When you bundle Xalan, the tooling produces Export-Package enTRies for all packages in
xalan.jar. However, the tooling cannot know that it should add imports for the
org.w3c.dom.xpath package found in the JRE. Without the import, Xalan uses its own copies of
the xpath types and may conflict with those supplied by the JRE.
This happens because Eclipse 3.1 plug-in classloading is highly optimized. These optimizations
depend on the plug-in manifest information to know which packages come from which plug-ins.
Except for the use of certain buddy policies, the classloaders never search for classesthey
always knows exactly where to find them.
For the JRE packages, only java.* packages are assumed to come from the boot classloader. All
others must be imported in the consuming plug-in's MANIFEST.MF. The API packages included in
the JRE are typically exported by the system bundle (i.e., org.eclipse.osgi or system.bundle ).
This list is captured in a JRE profile. Eclipse includes a number of profiles for common JREs and
automatically detects the appropriate one to use. You can control this further by setting the
osgi.java.profile property to the URL of a profile file to use.
So, if the Xalan plug-in fails to import the xpath packages, its local copies are used. This may
result in ClassCastExceptions because the plug-in's copy of the type is not interchangeable with
the copy supplied by the JRE. Changing the plug-in to import the packages tells Eclipse to use
the external copy. Alternatively, the offending packages can be removed from Xalan.
20.5.4. Serialization
Serialization of objects occurs in many different situations. Some libraries use the built-in
java.io.Serializable mechanism directly. Some use it indirectly as a consequence of using
Remote Method Invocation (RMI). Others serialize objects using their own marshalling
strategies (e.g., Hibernate stores/loads objects to/from relational databases). Regardless of the
technique used, these plug-ins have the following characteristics:
They are typically generic utilities and do not have access to, or knowledge of, your
domain classes.
They do not hold onto the classes they request, but rather use them to load objects and
then discard their references.
They need access to internal classes if instances of internal classes have been serialized.
Buddy classloading and context classloading solve all these problems. In effect, loading a
serialized object is equivalent to the log4j appender problem. Appender classes are identified by
name to log4j. Classes to load are identified to the serialization plug-in by name in the object
stream. In both cases, the loading plug-in needs to search beyond its prerequisite plug-ins to
find the desired classes.
20.6. Summary
The Java world includes a wealth of useful code libraries. Eclipse provides multiple techniques
for integrating these code libraries into the Runtime environment. This can be as simple as
running a wizardmost of the time it is.
In some cases, however, the code in the library uses certain patterns that are at odds with
Eclipse. Classloading is the most common bone of contention. Eclipse includes a number of
mechanisms and strategies for dealing with these cases. In particular, you can use buddies or
Dynamic-ImportPackage to provide visibility to classes that would normally not be visible. In this
chapter, we described the most significant of these and illustrated their use. The mechanisms
outlined here enable you to resolve most remaining classloading issues encountered when
integrating code libraries into Eclipse.
Chapter 21. Installing and Updating Plugins
In Chapter 14, "Adding Update," you defined a feature for the Hyperbola plug-ins and added
Update support to allow a user to install new features (e.g., MUC support) and update existing
features. That discussion did not talk about the details of what is happening under the covers,
how to structure the features, or how update sites work. This chapter digs deeper and
Introduces the various roles of the Update component.
Outlines the use of features for describing dependencies.
Shows how to create and manage the update site for Hyperbola.
Enhances Hyperbola to install features such as MUC on the fly.
21.1. Update's Roles
The Update component plays two main roles in the running of an Eclipse-based system. The first
role is to install plug-ins that are already on your machine. For example, when you first get an
Eclipse download, there are many plug-ins present, but none are installed. To be installed, a
plug-in must be explicitly identified to Eclipse using some calls to OSGi API. This is the job of
Update's configurator. The configurator is the only component of Update that comes with the
base RCP SDK.
When Eclipse starts, Update's configurator is run. The configurator scans a set of disk areas
called local sites and automatically discovers a list of available plug-ins. Then it consults the
OSGi framework to see which of the plug-ins are already installed, which have yet to be
installed, and which are installed but no longer present on disk. Using this information, the
configurator installs and uninstalls plug-ins to synchronize the framework's list of plug-ins with
its own. This process enables you to unzip a set of plug-ins into an Eclipse directory and have
them picked up the next time you runthe configurator compares what it finds with what is
installed and installs the new plug-ins.
Update's second role is to explicitly install a set of features and plug-ins, as you saw in Chapter
14. This component of Update is not part of the base RCP SDK. Having installed a set of plugins, Update is also responsible for updating them. With Update added, Hyperbola can interact
with Update servers and discover new versions of previously installed plug-ins. The user can
then select updates to be downloaded and installed. In addition, new features and plug-ins can
be discovered, downloaded, and installed.
21.2. Features
Manually dealing with collections of plug-ins is quite problematic. Human error and
miscommunication cause you to get a few too many plug-ins, miss a few plug-ins, or pick up
the wrong versions of plug-ins. Features allow you to abstract away the packaging details and
talk about arbitrarily complex collections of plug-ins that can be built and deployed.
21.2.1. Uses of Features
Update uses features to manage plug-ins in the following ways:
Install/Update Features are the basic element of Update management. Users and
programs install and update features, and through the features, the listed plug-ins.
Individual plug-ins are not updated directly.
Branding Features can appear in a product's About dialog. This gives feature creators an
opportunity to both make their presence known within a larger product setting and give
access to any required legal information.
Building Features describe complete sets of plug-ins that make up a system. These plugins also need to be built and managed during development. Features play a central role in
PDE's automated build process. Chapter 24, "Building Hyperbola," covers this in detail.
Install handlers Not everything in the world fits into a plug-in. Features also support
install handlers. Install handlers are run by Update when the corresponding feature is
installed or uninstalled. Install handlers are used to place additional files in the filesystem,
set file permissions, or complete the configuration of a feature or product.
21.2.2. What Is a Feature?
A feature is a list of related plug-ins and other features. They are defined in a feature.xml file
that lives in a feature directory, as shown in Figure 21-1.
Figure 21-1. Feature directory structure
At its simplest, a feature is an id/version pair and a list of plug-ins. As with plug-in ids, feature
ids typically follow Java package naming conventions and their version numbers take the form
major.minor.micro.qualifier.
The example below shows an abbreviated version of the feature.xml for the org.eclipse.rcp
feature in your Eclipse 3.1 target platform. The body of the feature lists the plug-ins that
comprise the RCP Base. These plug-ins are said to be included in the feature. This means that if
you install the RCP Base feature, all listed plug-ins are installed.
org.eclipse.rcp/feature.xml
<feature id="org.eclipse.rcp" label="Eclipse RCP Base" version="3.1.0">
<plugin id="org.eclipse.core.commands" version="3.1.0"/>
<plugin id="org.eclipse.core.expressions" version="3.1.0"/>
<plugin id="org.eclipse.core.runtime" version="3.1.0"/>
<plugin id="org.eclipse.osgi" version="3.1.0"/>
<plugin id="org.eclipse.help" version="3.1.0"/>
<plugin id="org.eclipse.swt" version="3.1.0"/>
<plugin id="org.eclipse.jface" version="3.1.0"/>
<plugin id="org.eclipse.ui" version="3.1.0"/>
<plugin id="org.eclipse.ui.workbench" version="3.1.0"/>
<plugin id="org.eclipse.update.configurator" version="3.1.0"/>
</feature>
Note
It is important to note that features do not contain the plug-ins. Rather, features
reference their constituent parts. This allows the same plug-in to appear in many
features, but only be installed once.
The previous snippet shows the XML form of the featurethis reiterates the idea that a feature is
really just a list of plug-ins and other features. In practice, you never have to edit the XML
directly as PDE includes a comprehensive feature editor. You can open the editor on the RCP
feature using File > Open File... and navigating to the feature in your target (e.g.,
c:\target\eclipse\features\org.eclipse.rcp_3.1.0\feature.xml) or by double-clicking on the
RCP Base feature in the Included Features list of the Hyperbola feature. Either way, open the
editor and flip to the Plug-ins page, as shown in Figure 21-2.
Figure 21-2. RCP feature plug-in list
[View full size image]
Notice that the list shown has a number of entries related to SWT. These entries are the SWT
fragments that contain the code specific to a given OS, window system, and processor
architecture. If you install the RCP Base feature on Windows, for example, you need a different
SWT fragment than if the feature is installed on Linux using GTK. The Plug-in Details shown
indicates that the selected fragment (org.eclipse.swt.gtk.linux.x86) is specific to "linux",
"gtk", and "x86". As such, Update ignores this fragment on other configurations.
Only the relevant attributes need to be specified. For example, if your plug-in runs on all Motif
systems, simply setting the Window System to "motif" is sufficient. Of course, if the plug-in or
fragment includes native code, you must specify at least the Operating System and
Architecture attributes.
Note
Notice also that the selected fragment is marked as not needing to be unpacked
during installation. Plug-ins on an update site are stored as JARs. Traditionally,
Update has unpacked these JARs into plug-in directories after downloading. As of
Eclipse 3.1, many plug-ins no longer need to be unpacked and therefore have this
option deselected.
Now that you have the RCP Base feature, you no longer have to laboriously list each of the RCP
plug-ins when defining other features. Figure 21-3 shows the Included Feature page for the
Hyperbola feature editor. Included are the RCP Base feature you added back in Chapter 14 and
org.eclipsercp. hyperbola.feature.muc, an additional feature used to demonstrate the notion
of optional features.
Figure 21-3. Including features
[View full size image]
Here, the Included Feature Details section shows the MUC feature marked as optional.
Typically, a parent feature fails to install if some of its included features and plug-ins are not
present. However, marking a feature as optional allows the parent to be installed even if the
optional feature is not present or cannot be installed. This is ideal for defining products that are
extensible according to user needs or OS facilities.
The above examples illustrate the definition of feature contentfeatures are made up of the plugins and features that are included in their definition. If you install a feature, all its constituents
are also installed, if not already available.
This is powerful, but the inclusion characteristic is limiting. Typically, when you include some
plug-ins or features, you are committing to shipping them. If your product is to be installed with
others, it is better to simply state a dependency on the parts that are needed. For example, the
Hyperbola feature could specify a requirement that the RCP Base feature already be installed.
This does not commit you to delivering the RCP Base, but it does express the dependency so
that it can be checked. On the Dependencies page, you can list the features and plug-ins that
must be present to successfully install your feature.
Figure 21-4 shows a feature that declares the RCP Base feature and Browser and Intro plug-ins
as required, but not supplied. Installing this feature makes no attempt to download or install
the listed elements; rather, the information is used by the Update UI to verify the correctness of
a configuration.
Figure 21-4. Feature dependencies
[View full size image]
Tip
To install features using Update, the features must have a license agreement. This
agreement is shown to the user as he goes through the Update/Install wizard. To
define the license, go to the License Agreement tab on the Information page of
the feature editor. There you can fill in the license text and a URL to use when showing
the license to the user.
21.3. Creating and Managing Update Sites
Once you have defined your features, they need to be distributed. The initial distribution might
be with your product in a zip archive or laid down by a native installer. When users want to
update their install or add new plug-ins, they need an update site.
You saw this in Chapter 14 while adding Update to Hyperbola. There we pointed you at a prebuilt update site (http://eclipsercp.org/updates) for testing the Update actions. Here we show
you how to create a site yourself.
Update sites are structured locations that contain sets of features and plug-ins and a top-level
index file called site.xml . There is no need to run a special server or Web application, or to
even have a server at allthe update site can be on a local drive. For example, the CD included
with this book has an update site in the updates directory. To create a site, use File > New >
Project... > Plug-in Development > Update Site Project. Fill in the wizard as shown in
Figure 21-5.
Figure 21-5. New Update Site wizard
When you define the site project, choose a Project contents location that is separate from
your workspace so you can build it up over time. When you build the site, all the features and
plug-ins are copied into the site project. Selecting a location outside of your workspace isolates
the site project from switching workspaces and allows you to populate it with multiple versions
more easily. You may choose to align this location with a local Web server's documents
structure or some synchronization or upload software used to populate the live update site. The
Project name of the site project does not figure heavily in its operation.
When you click Finish, the site project is created and the site editor opened. It should look like
an empty version of the editor shown in Figure 21-6. Use New Category to create a new
category of features and fill in the Category Properties as in the figure. Having added the
category, use Add Feature... to add the Hyperbola feature to the RCP Products category.
Figure 21-6. Populating the site
[View full size image]
You should also fill in the Site Description on the Archives page of the editor, as in Figure 217. Of course, your URL will be different. For simplicity, you might want to use file: URLs while
testing.
Figure 21-7. Update site description
[View full size image]
Once the site is defined and some features added, go to the Site Map page in the site editor
and click on Build All. This kicks off an export of the features to the site. In this example, only
the Hyperbola feature is built.
When the build is done, notice that the site project now has features and plugins directories.
Not only have the org.eclipsercp.hyperbola.feature and its immediate plug-ins been built and
put in the site, but all the features and plug-ins included in the Hyperbola feature are also in the
site. Building an update site ensures that everything needed by the features in the site is
present. The required features and plug-ins are not copied over as they are assumed to come
separately, perhaps from a different source.
Tip
The site.xml does not have to list all features available on a site. Update tests the
existence of a feature simply by constructing the relevant URL and checking if the file
exists. Your site.xml should list only those features that you want users to seethe
top-level features.
The update site is now built and on your machine. You can test it by revisiting Chapter 14 and
using the local update site's file: URL to check for updates. When everything works as expected,
the local site can be uploaded to the live update site. As you discover bugs, updated versions of
Hyperbola can be put on the site and users can install them as they choose. As we saw in
Chapter 14, you can also add new functions here.
Tip
If you are planning on committing the site project to CVS, you may not want to
commit the actual contents of the site (e.g., the features and plug-ins directories) as
the complete set of plug-ins and features may be quite large and can often either be
rebuilt or reacquired if needed.
To avoid committing these files, select the features and plugins folders and tell CVS
to ignore them. Using the context menu, select Team > Add to .cvsignore....
21.4. Example: Dynamic Content Handling
So far, the uses of Update have been somewhat manual. Even with the scheduled updates
outlined in Chapter 14, users still have to complete the operation. Further, the update itself
occurs independently of what is happening in Hyperbola. In this section, we look at how to use
the Update mechanism to dynamically discover and install code based on the happenings in
Hyperbola itself.
Web browsers are a prime example of this scenario. The first time a browser sees a PDF or
Flash document, it searches for a plug-in that can handle the content. After locating such a
plug-in, it downloads and installs it and then uses it to display the content. All of this is done
without restarting the browser and with minimal user interaction.
Let's extend Hyperbola to do the same for different kinds of chat traffic. Remember, XMPP, the
underlying messaging protocol, is highly extensible. That means new kinds of packets can be
defined after Hyperbola has been deployed. For example, the basic Hyperbola does not handle
MUC messages, but XMPP supports that function.
Because of XMPP's extensibility, Smack was designed to have different providers for different
packet types. In effect, Smack looks at each incoming packet and attempts to find an
appropriate provider. Hyperbola hooks into Smack's provider infrastructure and when an
unrecognized packet type is encountered, Hyperbola searches a directory service for an
appropriate handler entry.
The directory service example used here is based on a simple properties file downloaded from
an update site when needed. More sophisticated discovery mechanisms can be used if required.
The snippet below shows the MUC-related entries in the directory file:
directory.txt
<x/><jabber\:x\:conference/><extension/>=\
org.eclipsercp.hyperbola.feature.muc,1.0.0
<x/><http\://jabber.org/protocol/muc#user/><extension/>=\
org.eclipsercp.hyperbola.feature.muc,1.0.0
<query/><http\://jabber.org/protocol/muc#admin/><iq/>=\
org.eclipsercp.hyperbola.feature.muc,1.0.0
<query/><http\://jabber.org/protocol/muc#owner/><iq/>=\
org.eclipsercp.hyperbola.feature.muc,1.0.0
The format of this file does not particularly matter. The important part is that the directory
entries are keyed by packet type information and the values identify the feature and version to
download to handle packets of that type.
In the code snippet below, the mappings are initialized when the Directory is created. When a
packet without a handler is detected, the packet details are passed to getProvider(String,
String, String). This searches the mappings for an IBundleGroup associated with that kind of
packet, as described in the mappings file. Note that IBundleGroups are just a generic
representation of Update's features.
org.eclipsercp.hyperbola.discovery.update/Directory
private String homebase = "http://eclipsercp.org/updates";
private HashMap mappings;
public Directory() {
super();
initializeMappings();
}
public IBundleGroup getProvider(String elementName, String namespace,
String type) {
return (IBundleGroup) mappings.get(getKey(elementName, namespace,
type));
}
public void installProvider(IBundleGroup provider,
IProgressMonitor monitor) throws Exception {
String id = provider.getIdentifier();
String version = provider.getVersion();
if (checkFeature(id, version))
return;
downloadFeature(id, version, monitor);
}
private boolean checkFeature(String feature, String version) {
IPlatformConfiguration config =
ConfiguratorUtils.getCurrentPlatformConfiguration();
IPlatformConfiguration.IFeatureEntry[] features =
config.getConfiguredFeatureEntries();
PluginVersionIdentifier targetVersion =
new PluginVersionIdentifier(version);
for (int i = 0; i < features.length; i++) {
String id = features[i].getFeatureIdentifier();
if (feature.equals(id)) {
PluginVersionIdentifier v = new PluginVersionIdentifier(
features[i].getFeatureVersion());
if (v.isCompatibleWith(targetVersion))
return true;
}
}
return false;
}
private void downloadFeature(String feature, String version,
IProgressMonitor monitor) throws Exception {
InstallCommand command =
new InstallCommand(feature, version, homebase, null, "false");
command.run(monitor);
command.applyChangesNow();
}
If a bundle group is found, the feature id and version are passed to installProvider(String,
String, IProgressMonitor). This invokes Update API to check if the given feature is already
installed. If it is, there is nothing more to do. If it is not installed, downloadFeature(String,
String, IProgressMonitor) is called. This method creates an Update InstallCommand, listing the
feature, its version, and the update site URL. The command is then run and the feature and its
included features and plug-ins are downloaded. Once everything is downloaded, the new plugins are installed using applyChangesNow().
As soon as applyChangesNow() is done, all downloaded plug-ins are installed and ready to run.
From this point, Hyperbola retries the provider lookup. Since the provider was just installed, it
is found and used to process the packet that started this whole scenario.
Note
This example uses Update's command line operations (e.g., InstallCommand). These
are a set of simplified operations intended to be run by external agents running Java
from the command line. If you are just doing something simple, these are fine. If your
install or update scenario is a little more complicated, org.eclipse.update.core has a
reasonably comprehensive set of APIs you can use.
Of course, since the MUC feature and plug-ins are on the update site, Hyperbola users are free
to proactively enable Hyperbola with MUC supportallowing them to initiate group chatsby
accessing the site and installing the MUC support using the procedure outlined in Chapter 14.
21.5. Summary
The Eclipse Update mechanism is based on features. Features are a powerful structure for
grouping plug-ins and other features to create complex configurations. Feature sets can be
added to update sites and used to install or update products on end-user machines as well as to
build the individual plug-ins described.
The transparent download and install mechanism detailed here can be used to implement a
wide range of sophisticated user interactions. Everything from using minimal installers to
progressively adding functions to scheduled and automatic updates is possible.
21.6. Pointers
In addition to the details in this chapter, there's an excellent article on eclipse.org with
additional information on using Update. The article can be found at
http://eclipse.org/articles/Article-Update/keeping-up-to-date.html.
Chapter 22. Dynamic Plug-ins
Applications are often dynamic. New functions are added, old functions are removed, but the
system keeps running. We saw this in Chapter 21, "Installing and Updating Plug-ins," with the
dynamic installation of MUC capabilities into Hyperbola. The Eclipse Runtime and OSGi enable
this kind of behavior and the RCP base plug-ins tolerate it, but the ability does not come
freeyou must follow certain practices to make the most of these scenarios.
This chapter discusses the unique challenges presented to plug-in writers as they attempt to
handle the comings and goings of plug-ins in the environmentdynamic tolerance. We first look
at Hyperbola as an example of dynamic tolerance. With that as a base, we identify some
common dynamic plug-in scenarios and outline coding practices and designs for handling them.
Throughout the discussion, the dynamic MUC example from Chapter 21 is used as an example
of exploiting Eclipse's dynamic capabilities.
22.1. Making Hyperbola Dynamic
The main goal here is to make Hyperbola react correctly when plug-ins are added or removed.
The first step is to understand how the Hyperbola plug-ins are connected to each other and to
plug-ins in upper layers. In Chapter 21, we added XMPP MUC capabilities on the fly, but the
same discussion applies if the scenario is video conferencing, file transfer, or any other XMPP
extension.
Beyond the details of installing the function, however, are the issues in getting Hyperbola to
notice the arrival of new functions and the management of interconnections when plug-ins are
removed from the systemthese are the topics addressed in this chapter.
Figure 22-1 gives a rough outline of the Hyperbola plug-ins involved in MUC support and shows
how they relate to each other.
Figure 22-1. Hyperbola plug-in structure
First, let's look at the comings and goings of content handler plug-ins. The MUC support brings
along the following elements that need to be linked into Hyperbola:
Providers These are the packet handlers that plug into Hyperbola via an extension point.
They are in turn supplied to Smack using Hyperbola's provider manager.
Listeners The MUC infrastructure needs to register various listeners, both at the UI level
for selection events and such, and at the Smack level (to monitor message traffic,
participant presence, and so on).
UI parts The MUC UI needs to be displayed and driven by the user. This requires action
sets, views, and editors contributed via extension points.
The MUC plug-ins register various listeners and Eclipse or Hyperbola instantiates the various
extensions it contributes. MUC also explicitly registers GroupChat objects with Hyperbola's
SessionManager.
The MUC support has the following requirements:
MUC must register and unregister packet handlers as the MUC support is installed or
uninstalled.
MUC must clean up installed listeners on removal.
Hyperbola and Smack must react to the arrival and departure of packet handlers.
The Hyperbola UI must react to the coming and going of action contributions.
Hyperbola must react to the invalidation of GroupChat objects.
The rest of this chapter looks at how to handle each of these requirements both in the context of
Hyperbola and MUC and more general scenarios so that you can apply them to your domain.
22.2. Dynamic Challenges
Being dynamic is all about managing the links between types, their instances, and plug-ins.
There are two main challenges when trying to operate in a dynamic world: being dynamicaware and being dynamic-enabled. Being enabled is relatively straightforward to achieve
because it's a self-centered concernall you have to do is to clean up after yourself. Dynamic
awareness is an outward involvement, ensuring that the links you have to others are updated as
plug-ins are added to and removed from the system. Because awareness is tricky to get right,
most of the remaining text outlines techniques and helpers for making your plug-ins dynamicaware.
22.3. Dynamic-awareness
Dynamic-awareness has to do with updating your plug-in's data structures in response to
changes in the set of installed plug-ins. That is, a dynamic-aware plug-in is one that can handle
other plug-ins coming and going. Dynamic-awareness needs to be considered wherever a plugin has a data structure that is based on types, objects, or contributions from other plug-ins. In
the Hyperbola case, extensions such as MUC contribute packet handlers, menu entries, and chat
objects. This means Hyperbola definitely has to be dynamic-aware.
Dynamic-awareness comes in two flavors: addition and removal. That is, we say that a plug-in
is dynamic-aware for addition if it is set up to handle the dynamic addition of plug-ins to the
system. Similarly, we say that a plug-in is dynamic-aware for removal if it can handle the
dynamic removal of plug-ins from the system.
Addition is generally easier to deal with as there is less cleanup. Structures can simply be
augmented, caches flushed, or new capabilities discovered on the fly. Handling the removal of
relationships may be as easy as flushing some caches, or it may be as complicated as tracking
contributed or constructed objects, deleting them as required and cleaning up afterwards.
22.3.1. Dynamic Extension Scenarios
The most common dynamic-awareness challenge is for plug-ins that host extension points. The
extensions for a plug-in are woven into the extension registry when the contributing plug-in is
resolvedwhen all of its dependencies are met. Similarly, if the plug-in subsequently becomes
unresolved, say when a prerequisite plug-in is removed, its extensions are removed from the
registry. Section 26.7, "Bundle Lifecycle," gives more detail on the lifecycle of plug-ins.
Both executable extensions (i.e., extensions that provide code) and descriptive extensions (i.e.,
extensions that provide just information) are problematic for dynamic-awareness. The problems
arise as a result of caching information from others in your plug-in. Whether it is caching of the
extension itself or of any objects created via executable extensions, the cache's coherence must
be maintained.
In Hyperbola, the core messaging model exposes two extension points that accept providers:
org.eclipsercp.hyperbola.extensionProviders
org.eclipsercp.hyperbola.iqProviders
Contributed extensions list the packet types in which they are interested and identify a class to
handle such packets. When a matching packet is encountered, the contributed class is
instantiated, as well as the instance used to handle the packet. The MUC plug-ins contribute
executable extensions to both of these to handle MUC message flow.
If Hyperbola was not dynamic-aware for addition, it might miss the addition of these providers
and thus be unable to handle MUC messages. If it was not dynamic-aware for removal, it would
miss the removal of the extensions and continue trying to use the MUC handlers even though a
function might have been removed. Furthermore, an uninstalled plug-in is not garbagecollected until all instances of its types are collected and all references to its types are dropped.
That is, they continue occupying memory until their types and objects can all be collected.
Technically, you can continue using existing types and objects even after the plug-in is
uninstalled, but the state of the plug-in is not guaranteedyet another reason why dynamicawareness is important.
The next three sections enumerate dynamic extension scenarios and how to handle them. In
general, they revolve around whether or not the supplied extension is descriptive or executable
and whether or not you cache values discovered in the registry or consult the registry each time
you need something.
22.3.1.1. Scenario 1: No Caching
If your plug-in consults the extension registry each time a value is needed, the burden of being
dynamic-aware is substantially reduced. All data lives in the extension registry and is
maintained by the Runtimeno work for your plug-in. The downside of this approach is that
accessing the extension registry is likely slower than consulting an optimized, special-purpose
data structure or cache in your plug-in. If the extensions are accessed infrequently, however,
this trade-off is reasonable.
For example, Hyperbola can use this approach for the providers, but since there is no caching,
the extension registry would be consulted for every data packet that does not have a built-in
handler. This is fine for simple chatting, but it is likely not satisfactory if more intensive
operations such as file transfer or video conferencing used the same mechanism.
22.3.1.2. Scenario 2: Extension Caching
The performance of extension lookup can be improved by caching the structure of the
extensions. For example, Hyperbola might keep an explicit, in-memory table keyed by packet
type where the value is the contributing extension. This table needs to be updated accordingly
when a plug-in contributing a handler is added to the system. Similarly, if an existing plug-in is
removed, its contributed handlers must be removed. Updating the cache is quite trivialthe
changed key/value pair is simply added or removed.
This approach improves the time to access the extensions and find the correct handler class for
a given packet, but it suffers on two counts: first, it essentially duplicates the extension
structure and data; and second, it requires additional infrastructure to implement dynamicawareness.
In some cases, there may be no choice. For example, the Resources plug-in's markers extension
point takes marker extensions and builds a multiple inheritance hierarchy of marker types. This
structure must be computed rather than read directly from the extension registry, so it
inherently requires some level of caching. As such, the Resources plug-in must implement some
dynamic-awareness support to clean up the cache when the set of resolved plug-ins changes.
The cache cleanup is more complicated as the cached markers' data structure is inherently
interconnected.
To support this need, the Runtime broadcasts registry change events (IRegistryChangeEvent) to
registered registry change listeners (IRegistryChange Listener) whenever extension registry
contributions are added or removed. The listeners can then query an extension delta
(IExtensionDelta) to find out what changed and how. This information is in turn used to update
the cached data structures.
Updating the cache need not be a heavyweight operation. For example, if the cache is not
critical and rebuilding is not overly expensive, flushing the entire cache on change is
reasonable. This approach is sketched in the code snippet below:
public class ExtensionManager implements IRegistryChangeListener {
private Map cache = null;
public void registryChanged(IRegistryChangeEvent event) {
cache = null;
}
private Map getCache() {
Map result = cache;
if (result == null)
return initializeCache();
return result;
}
public Object getExtension(String id) {
return getCache().get(id);
}
}
Registry change listeners are notified serially, but you still have to be concerned about threads
accessing the cache while listeners are clearing it. The code in getCache() gets a reference to
the cache and uses that reference. The cache may be flushed at any point after that and the old
value used. That's acceptable because there were no guarantees about ordering here anyway.
Notice that this coding pattern closes the window between getting and testing the cache state,
and returning the cache as the result.
Of course, another situation may not be that easy. If cache entries are expensive to rebuild, it is
better to add and remove entries incrementally. The following snippet shows how to handle
change events and traverse the deltas:
public void registryChanged(IRegistryChangeEvent event) {
// get the changes for one of my extension points
// and walk through processing the changes.
IExtensionDelta delta[] = event.getExtensionDeltas(
"org.eclipsercp.hyperbola", "iqProviders");
for (int i = 0; i < delta.length; i++)
switch (delta[i].getKind()) {
case IExtensionDelta.ADDED :
// add an extension in some application-specific way
cache.add(delta[i].getExtension());
break;
case IExtensionDelta.REMOVED :
// remove an extension in some application-specific way
cache.remove(delta[i].getExtension());
break;
}
}
Here, the listener queries the delta from the change event for any changes to the
org.eclipsercp.hyperbola.iqProviders extension point. For extension additions, you can
choose to aggressively populate the cache with the new extensions or just ignore the additions
and look for them later when you have a cache miss. For removals, you need to tear down any
data structures and remove the extension from the cache.
22.3.1.3. Scenario 3: Object Caching
The packet handler lookup mechanism is still not as good as it could be. Even with extension
caching, Hyperbola still has to look up the required class and instantiate a handler for each
packet. Assuming the handlers are context-free, one of each type could be cached and used to
handle packets as required.
Even if Hyperbola does not cache the extensions themselves (i.e., Scenario 2 is not applicable),
Hyperbola may hold on to either the handler class or some created handler instances. This
prevents the contributing plug-ins from being properly garbage-collected. Further, the created
handlers cannot be left active when the contributor is removedthe handler is likely to be invalid
because its plug-in has been shut down and removed.
Note
It is instructive to note that the Eclipse UI plug-ins have various extension points that
cover each of these scenarios. Before the dynamic-awareness requirements were
placed on the UI, it cached most extension information and created objects. In some
cases, this resulted in duplicate information and inefficiencies. It also inhibited the
Runtime's ability to flush its registry caches and adapt its space requirements to the
current usage patterns. And of course, it meant that the UI plug-ins would have to do
considerably more work to be dynamic-aware. In the end, the approach was to
remove the various levels of caching whenever appropriate and rely on direct access
to the extension registry or configuration elements.
If Hyperbola is to be dynamic-aware, the handlers it instantiates must be tracked so they can
be cleaned up if their contributor is removed. To make it just a little more complicated, it turns
out that Smack wants to cache the handler instances for efficiency purposes.
One thing to watch in this scenario is that the cached objects often play a deeper role in the rest
of your system. That is, they may be woven tightly into the fabric of the application. The
challenge is to understand the interconnections and ultimately reduce them so there is less to
clean up.
To update the object structure, you can use the same sort of listener as the one described in
Scenario 2. This time, however, the cache contains contributor-supplied objects (i.e., handlers)
rather than Hyperbola's internal data structures. In the case of Hyperbola and Smack, it turns
out that there is only one reference to any given handler. The registry change listener simply
tells Smack to remove all providers contributed by the deleted extension. The following code
snippet shows this in action:
public void registryChanged(IRegistryChangeEvent event) {
IExtensionDelta delta[] = event.getExtensionDeltas(
"org.eclipsercp.hyperbola", "iqProviders");
for (int i = 0; i < delta.length; i++)
if (delta[i].getKind() == IExtensionDelta.REMOVED) {
IExtension extension = delta[i].getExtension();
IConfigurationElement[] elements =
extension.getConfigurationElements();
for (int j = 0; j < elements.length; j++) {
IConfigurationElement element = elements[j];
String elementName = element.getAttribute("elementName");
String namespace = element.getAttribute("namespace");
ProviderManager.getDefault().removeIQProvider(elementName,
namespace);
}
}
}
Notice that each extension may contribute several handlers and the provider list cache has
entries for each provider. Note also that the cache is primed lazily so there is no need to handle
extension additions as they happen.
To handle more complex situations, the Runtime has utility classes that help with tracking and
disposing object references. In the code snippet below, the HyperbolaProviderManager
implements IExtensionChangeHandler and registers itself with an IExtensionTracker to get
notification of changes in select extensions.
Extension trackers track objects created for an extension. These objects might be the result of
using IConfigurationElement.createExecutableExtension() or an object created manually.
Either way, the objects share the common trait that they should be cleaned up when the related
extension disappears.
org.eclipsercp.hyperbola/HyperbolaProviderManager
public class HyperbolaProviderManager implements IExtensionChangeHandler {
private IExtensionTracker tracker;
public void start() {
initializeTracker();
}
private void initializeTracker() {
tracker = new ExtensionTracker();
IFilter filter =
ExtensionTracker.createNamespaceFilter(HyperbolaPlugin.ID);
tracker.registerHandler(this, filter);
}
public void stop() {
tracker.close();
}
public void addExtension(IExtensionTracker tracker,
IExtension extension) {
// new extensions are accessed on demand.
}
public void removeExtension(IExtension extension, Object[] objects) {
for (int i = 0; i < objects.length; i++)
removeProvider(objects[i]);
}
public Object getProvider (String elementName, String namespace,
String type) {
String point = type.equals(IQ) ?
"org.eclipsercp.hyperbola.iqProviders"
: "org.eclipsercp.hyperbola.extensionProviders";
IConfigurationElement[] decls = Platform.getExtensionRegistry()
.getConfigurationElementsFor(point);
for (int i = 0; i < decls.length; i++) {
IConfigurationElement element = decls[i];
if (elementName.equals(element.getAttribute("elementName"))
&& namespace.equals(element.getAttribute("namespace"))) {
try {
Object provider = element
.createExecutableExtension("className");
tracker.registerObject(element.getDeclaringExtension(),
provider, IExtensionTracker.REF_WEAK);
return addProvider(elementName, namespace, type, provider);
} catch (CoreException e) {
e.printStackTrace();
}
}
}
return null;
}
}
Looking through the code from the top down, initializeTracker() creates an ExtensionTracker
and adds the provider manager as a handler. Notice that the filter used means that the provider
manager is only notified of changes to extension points in the Hyperbola plug-in's namespace.
You can narrow this to individual extension points, but this is good enough here.
The provider manager is notified of changes through addExtension() and removeExtension().
Since provider extensions are accessed on demand, we do not need to do anything for addition.
On removal, however, we do need to ensure that any registered providers are removed. The
removeExtensions() method receives a list of objects that the tracker is tracking relative to the
given extension. This list is populated by the provider manager whenever it creates a provider,
as shown in getProvider() . Ignoring the detail of how the provider is discovered, at some
point, a class supplied by an extension is instantiated. The resultant object is then registered
with the tracker using registerObject(). The provider is then added to the provider manager's
internal data structure. It is this data structure that needs to be updated if the extension is
removed. You saw that code in the method removeExtension().
Notice also that the tracker uses weak references (REF_WEAK ) to track the providers. This ensures
that provider objects do not stay live in the system just because they are being tracked.
The use of extension trackers is somewhat overkill in this case, but the example gives you an
idea of the mechanism's power. You can use one tracker to track many different extension
points and many different objects. You can also use it as your primary data structure by calling
getObjects(IExtension) to access all tracked objects for the given extension.
22.3.2. Object Handling
Hyperbola is fundamentally listener-based. Clients can listen for packets, connections,
authentication messages, and so on. Every time a client adds a listener, it creates a link
between itself and Hyperbola. If the client disappears, the link needs to be cleaned up.
Note
Here we talk about "listeners," but we really mean "any object given to, and held on
by, another plug-in." It could be an actual listener or some other callback handler, a
factory, or the implementation of an algorithm. If you register a reference, you should
unregister it.
In a perfect world, all clients would be dynamic-enabled and would clean up after themselves.
Failure to clean up listeners prevents the uninstalled client from being garbage-collectedthis is
effectively a leak. Here are a few strategies you can use to handle contributed objects:
Ignore Assume that everyone is a good citizen and code your notifier robustly to handle
any errors that might occur when notifying a stale listener. This tolerates the removal of
the contributing plug-in, but leaves dangling listeners and has the potential to leak
memory.
Validity testing Include a validity test in your listener API. Before notifying any listener,
the notifier tests its validity. Invalid listeners are removed from the listener list. The
registering client then invalidates all its listeners when it is stopped. This lazily removes
the listeners, but still has the potential to leak if the notifier never tries to broadcast to,
and thus test the validity of, an invalid listener.
Weak listener list Using a weak data structure such as WeakReferences or
SoftReferences to maintain the listener list allows defunct listeners to simply disappear.
Since clients typically have to maintain strong references to their listeners to support
unregistering, there is little danger of the listeners being prematurely garbage-collected.
Co-register the source Rather than just registering the listener, have clients register
both themselves and the listener. You then listen for bundle events (see below) and
proactively remove listeners contributed by bundles being removed.
Introspection Every object has a class. The plug-in that loaded the class can be found
using PackageAdmin.getBundle(listener.getClass()) . With this information, you can
tweak the co-registration approach to use introspection and cleanup. This approach is
transparent, but can be a bit costly and does not catch cases where one plug-in adds a
listener that is an instance of a class from a different plug-in.
In the end, there are no right answers. The different strategies have different characteristics.
The point is that you must be aware of the inter-plug-in linkages and make explicit decisions
about how they are managed. You should choose the coding patterns that best suit your
requirements (speed, space, complexity) and apply them consistently and thoroughly.
Note
In all of these cases, there are windows of opportunity for Hyperbola to accidentally
attempt to notify a stale listener. As with any notification mechanism, it is important
that the notification code be robust enough to handle any errors that might occur. You
should consider using Platform.run(ISafeRunnable) to help manage such errors.
22.3.3. Bundle Listeners
BundleListeners are a powerful OSGi mechanism for handling change in a running system.
Whenever a bundle changes state in the system, all registered BundleListeners are notified.
Listeners typically do the same sort of cache management described earlier and as shown in the
snippet below:
public class Activator implements BundleActivator {
private BundleListener listener;
private Object cache = null;
public void start(BundleContext context) throws Exception {
listener = new CacheManager();
context.addBundleListener(listener);
}
public void stop(BundleContext context) throws Exception {
context.removeBundleListener(listener);
}
public class CacheManager implements BundleListener {
public void bundleChanged(BundleEvent event) {
if (cache == null)
return;
synchronized (cache) {
if (event.getType() == BundleEvent.UNINSTALLED
|| event.getType() == BundleEvent.UNRESOLVED)
cache.remove(event.getBundle());
}
}
}
}
In this case, the listener is registered as soon as the bundle is started. It listens for UNINSTALLED
and UNRESOLVED bundle events and removes the affected bundle from the cache it is managing.
Notice that this code is a good citizen as it removes its listener when the bundle is stopped.
22.4. Dynamic-enablement
Dynamic-enablement means being a good plug-in citizen. That is, a dynamic-enabled plug-in is
written to correctly handle its own dynamic addition and removal. If you don't clean up, you
become a leak. Leaks bloat the system and eventually cause it to run out of memory or become
intolerably slow. In the case of MUC support, this means correctly unregistering listeners and
other objects that are hooked into the base Hyperbola facilities.
Depending on the implementation of the MUC support, being dynamic-enabled may also mean
disposing OS resources. The OS does not know when a bundle is stopped. To the OS, the JVM is
still running, so it has to maintain all resources allocated to the JVM process. These include:
open files
graphical objects such as images, colors, and fonts
sockets
For MUC support to be dynamic-enabled, it must clean up any such resources as they are
removed or stopped.
22.4.1. Cleaning Up After Yourself
Developers often assume that when their plug-in's stop() method is called, Eclipse is exiting. As
such, they do only mild cleanup, if any at all. These plug-ins are not dynamic-enabled. A
dynamic-enabled plug-in is one that:
Ensures all objects it registers are unregistered when no longer needed.
Implements a rigorous stop() method as a backstop.
Plug-ins that register listeners, handlers, and UI contributions via code or allocate shared
resources must take care to unregister or dispose of such objects when they are obsolete. This
is just good programming practice. If you call an add() method, ensure that you call the
matching remove() when appropriate. Similarly, this should be done for alloc() and free(),
create() and dispose(), etc..
To implement a backstop, your bundle activator needs to know which objects to dispose. This
can be hard-coded if the set is rather limited and known ahead of time. For example, if your
plug-in holds a socket open, ensure it is closed in the stop() method.
More generally, you can track the objects needing disposal. The code below is a sketch of how
this works. The activator maintains a weak set of objects that need disposal. Throughout the life
of the plug-in, various disposable objects are added to and removed from the set. When the
plug-in is finally stopped, all remaining disposable objects are disposed. The set is weak to
avoid leaks in situations where an object is added but not removed, even though it is no longer
live.
public interface IDisposable {
public void dispose();
}
public class Activator implements BundleActivator {
private Map disposables = new WeakHashMap(11);
private static Activator instance = null;
public Activator getInstance() {
return instance;
}
public Activator() {
super();
instance = this;
}
public void addDisposable(IDisposable object) {
disposables.put(object, null);
}
public void removeDisposable(IDisposable object) {
disposables.remove(object);
}
public void stop(BundleContext context) throws Exception {
for (Iterator i = disposables.keySet().iterator(); i.hasNext();)
((IDisposable) i.next()).dispose();
disposables = null;
}
}
public class Listener implements IDisposable, IRegistryChangeListener {
public Listener() {
super();
Activator.getInstance().addDisposable(this);
}
public void registryChanged(IRegistryChangeEvent event) {
// do some processing here
}
public void dispose() {
Platform.getExtensionRegistry().removeRegistryChangeListener(this);
}
}
At the end of the snippet, there is an example of a registry change listener that lists itself as a
disposable on creation. You can register the disposable at any point as long as it is added before
the plug-in stops. When the plug-in stops, the listener is guaranteed to be removed from the
event sourcethe extension registry in this case. If the listener is removed from the source and
not the disposal list, it either becomes garbage and is removed transparently or it is
unregistered from the source when the plug-in is stopped. Unregistering a listener that is not
registered is a no-op.
Note
For clarity and simplicity, we have omitted the synchronization code and errorchecking needed to make this pattern robust.
22.5. Summary
Dynamic update and addition of functions to running applications is an important part of the
total user experience and Hyperbola would be significantly diminished without it. Even with the
Runtime mechanisms that support dynamic plug-ins, being dynamic is not free. It is somewhat
akin to concurrent programming. You need to revisit and isolate your assumptionslikely a good
thing to do anyway! In many cases, the outcome is well worth the effort.
Chapter 23. RCP Everywhere
Up to now, Hyperbola has been contained within a single plug-in and has been designed to run
as a standalone desktop application. In this chapter, we look at restructuring Hyperbola to run
in many different environments: on PDAs, in kiosks, or plugged into the Eclipse IDE. In
addition, we talk about how to set up your development process to simplify supporting multiple
product configurations from one code base. In this chapter, we:
Explain why multiple product configurations are interesting in the real world.
Detail the factoring of Hyperbola into multiple product configurations.
Examine how Hyperbola's code is layered to be reusable across multiple configurations.
Provide rules to help refactor your own products.
Outline tips and tricks for designing platforms from your products.
Detail how to identify RCP-friendly plug-ins.
23.1. Sample Code
The sample code for this chapter is different from the other samples in this book. Whereas the
other samples are either part of the Part II tutorial or derived from it, the code for this chapter
is a completely refactored Hyperbola that includes many more features that were not added in
the tutorial chapters. The other versions of Hyperbola were pedagogical in nature and left out
many details needed to make a real product, such as exception handling and nifty features. This
Hyperbola contains it allit's dynamic-enabled and aware, has more UI features, and more
importantly, is split into several plug-ins to support making it run everywhere.
23.2. The Scenario
Imagine a scenario where Hyperbola has been deployed in a hospital. Before Hyperbola, staff
used e-mail to communicate with units when trying to admit a patient and secure a bed. The
hospital has 425 beds and things are pretty hectic. From time to time, registration personnel
lost track of requests and patients were forgotten. With Hyperbola installed, front desk staff use
instant messaging to contact the admitting and bed control department. Messages appear on all
desktops so anyone from the admitting team can respond immediately and confirm the
allocation.
During the first few months, Hyperbola became immensely popular and provided a real
alternative to e-mail. It was a success and other departments quickly started to see the value of
the technology in their own workflows. It was so popular, in fact, that the hospital expanded its
requirementsit wanted Hyperbola to run everywhere.
Doctors wanted to use PDAs to contact other doctors, nurses, and lab technicians as they made
their rounds. Patients would be able to chat with family and other patients from their in-room
touch screen kiosks or from kiosks in the ER and waiting rooms. The research centers and labs
wanted to be hooked in. And, of course, the IT department wanted to have Hyperbola extend
their developers' Eclipse IDE.
23.2.1. About the Scenario
The scenario painted here is not real, but was inspired by a news article about St. Rita's Medical
Center (http://stritas.org), describing its use of instant messaging in much the way we initially
set out this scenario.
Hospitals are diverse environments employing medical professionals, IT staff (i.e., developers
and technicians), administrators, and management personnel. Quite by chance we have heard
of imaging technicians using Eclipse as an IDE, medical researchers using Eclipse to conduct
and manage trials, and administrators using it to track patients. The initial scenario was
extended to include these use cases. Having said that, all the Hyperbola configurations
described here are very real and run on Eclipse 3.1.
The instant messaging scenarios and Eclipse RCP are even more compelling when you realize
that XMPP (the basis of Hyperbola messaging) is extremely extensible and Hyperbola, via
Eclipse, exposes this. Hyperbola becomes a platform for collaboration between everyone in the
enterprisevideo conferencing, records exchange, lab collaborationthere is no limit.
Even if you are not interested in such a diverse set of runtime environments and functionality,
your product may have evaluation, "lite," and enterprise configurations. It may need to stand
alone as well as extend other products. The design philosophies for setting up and managing
these configurations are similar.
23.3. Product Configurations
There are two types of Eclipse product configurations: standalone and extension. The hospital
scenario requires both. Standalone products are "traditional" RCP applications and have been
the focus of this book. Hyperbola's typical configuration is as a standalone application. It has its
own entry point (i.e., an IPlatformRunnable and an application extension point), a
WorkbenchAdvisor, a product extension, and other product-level branding such as a splash
screen and a customized launcher. This type of product is used at the nursing station, on
administrator desktops and kiosks, and in PDAs.
By contrast, an extension product is one intended to extend an existing standalone product. As
such, extension products do not have an application entry point or the same degree of
customization typically associated with a standalone productextension products do not have
their own WorkbenchAdvisors. This configuration is used for the hospital's developers and other
tool-based workers who are already using another Eclipse-based product such as the Eclipse
IDE.
One of the selling points of Hyperbola in this scenario is that Hyperbola can simply be
integrated with other Eclipse-based applications as the hospital's needs expand.
Figure 23-1 shows how common groups of plug-ins, called framework plug-ins, are used in
combination with product-specific plug-ins to build both extension and standalone product
configurations. The various configurations share a common set of framework plug-ins and
optionally add their own customization plug-ins to the mix. Most of the function of the product
lives in the framework plug-ins and is reused in all product configurations. The trick is deciding
what to include in the framework plug-ins and what to put in the product configurations.
Figure 23-1. A product with several product configurations
[View full size image]
Supporting a wide range of product configurations is a very powerful advantageit gives your
product more exposure and provides users with access to your product when and where they
need it. This sounds great on paper, but in reality, creating a fully configurable product suite is
easiest if you make it an explicit design objective. Like any software system, if reuse is not a
concern, the code will not be reusable.
The essence of promoting reuse is the ability to split Hyperbola into several configurations that
run in different environments but share as much code as possible. This section looks at
restructuring Hyperbola as several plug-ins. We detail what goes in each plug-in, plug-in
naming conventions, and implications on building and packaging. The rest of the chapter then
builds on this refactoring and describes and compares Hyperbola configurations for the different
execution environments.
23.3.1. Restructuring Hyperbola
The best way to understand the restructuring of Hyperbola is to contrast the prototype structure
built in Part II with the multi-configuration structure developed here. Figure 23-2 shows the
Hyperbola plug-in structure in the prototype. The product information is embedded in the
monolithic Hyperbola plug-in that includes the advisors, the product description, and the basic
messaging infrastructure. This is a natural organization that is easy to understandperfect for a
prototype.
Figure 23-2. Hyperbola that supports one product configuration
[View full size image]
Figure 23-3 shows Hyperbola after being restructured to support multiple configurations. The
figure shows framework plug-ins and product configurations. The framework plug-ins contain
the Hyperbola application logic, Contacts view, and Chat editor, with all product-specific files
and code removed.
Figure 23-3. Hyperbola that supports multiple product configurations
[View full size image]
Product configurations pull together the product-specific and common parts of Hyperbola to run
in a particular environment. They typically include some number of product plug-ins that
coordinate and position the various elements defined in the framework. The configurations also
include features that capture the complete set of required plug-ins.
The key point here is that the UI components and actions are implemented once and then
reused in different product configurations. The benefits cannot be over-estimated. As the rest of
this chapter highlights, this approach clarifies the structure of the system and enables the use of
your function in vastly different scenarios. The amount of code specific to individual
configurations is kept to a minimum and sometimes is completely eliminated.
23.3.2. Hyperbola Projects
There are three basic configurations of Hyperbola at the root of the four execution environments
in the hospital scenario:
Workbench A standalone product that includes the UI Workbench.
JFace A standalone product that relies only on JFace.
IDE An extension product that plugs into an existing Eclipse IDE.
These configurations can be tweaked and parameterized to fit all the use cases outlined above.
For example, both the desktop and kiosk requirements can be met using the Workbench
configuration. Before digging into that, let's look at the basic configurations. Figure 23-4 shows
the Hyperbola feature and plug-in projects needed to support all of the hospital's requirements.
Figure 23-4. Hyperbola projects with support for multiple product
configurations
23.3.3. Project Naming
This list looks daunting at first, but makes sense once you understand the naming convention.
The word "product" is used in a name to identify plug-ins that adapt infrastructure pieces to
product configurations (e.g., the org.eclipsercp.hyperbola.product.workbench plug-in
contributes the actions and views for Hyperbola as a regular RCP standalone application).
As we mentioned in Section 14.3, "Defining Features," and Chapter 21, "Installing and Updating
Plug-ins," the word "feature" is used to distinguish feature projects from all others. Again, the
challenge here is that the feature and plug-in namespaces are separate, so technically, you can
have plug-ins and features with the same name. This, of course, would be very confusing, not to
mention the difficulty of managing plug-in project names in the workspace.
Finally, the framework or infrastructure plug-in names have unqualified names that reflect their
functional content (e.g., org.eclipsercp.hyperbola.ui contains a generic UI function for
Hyperbola).
This naming convention may not suit your environment, but it has been useful in the
development of Hyperbola and in helping explain the structures in this book.
23.3.4. Why So Many Projects?
You may still be asking why there are so many projects. The short answer is that each exists
because either there is a need for reuse of that function or there is a corresponding product
configuration defined by that project. This leads to our first rule:
Rule 1: Top-level featureHave a top-level feature and plug-in for every
product configuration.
Following this rule allows you to describe the complete set of plug-ins required for each product
configuration in a form that is clear, complete, and understood by various parts of the Eclipse
tooling. There are several other benefits to this approach:
Simply exporting the feature is one way to package the configuration.
The feature or root plug-in also serves as an anchor point for creating product definitions
(.product files).
Features are required if you plan to use the Eclipse Update Manager in your deployments.
Features are also required if you want to use PDE's automated build mechanisms.
A top-level plug-in serves as the home for the feature branding content.
Underneath these top-level structures, Hyperbola's feature set is factored into as many reusable
features as needed. Don't go overboard or there will be one feature for every plug-in.
Remember, features are lists of plug-ins to build or deploy togetherthey are an abstraction to
help you structure and manage your world. If you have 100 plug-ins that always go together,
one feature should suffice. On the other hand, if you want to manage only handfuls of plug-ins
at a time, use more features.
Go back now and look at the list of projects in Figure 23-4 and compare it to the structure
shown in Figure 23-3there is a project in the list for every element in the structure diagram.
This is a simple and straightforward approach.
23.4. Hyperbola Product Configurations
Now that we have covered some of the organizational strategies that help with the management
of product configurations, let's take a more detailed look at the configurations needed for
Hyperbola in the hospital scenarios. The configurations shown in Figure 23-3 are reviewed in
subsequent sections.
Just to recap, the product as a whole is defined by a product configuration that contains the
following elements:
One or more product definitions (assuming a standalone scenario).
UI contributions such as action placement. These may be done in code or declaratively in
the plugin.xml, depending on the situation.
A list of plug-ins to include in the configuration. This is optionally specified using one or
more features. Using features as outlined in Chapter 14, "Adding Update," and Chapter 21,
"Installing and Updating Plug-ins," is highly recommended.
Product-level branding, such as launcher icons, window images, and splash screens.
A product configuration definition (.product ) file that serves as an anchor point for all of
the above.
Each product configuration should have a clear definition of where these elements are coming
from and what they contain. Given that you have a top-level feature for every product
configuration, it is reasonable to put your product definition files in the corresponding feature.
In fact, if you do that, the product definition will identify exactly one feature in its list of
contentsits containing feature. This is a convenient correspondence that helps keep things
simple.
23.4.1. The JFace Configuration
As the name implies, the UI of this configuration is based solely on JFace. It does not include
the UI Workbench. The primary motivator for this configuration is footprinta smaller footprint
decreases both download time and space on disk. Remember that "on disk" means "in memory"
on many PDA devices.
This Hyperbola configuration nets out to about 3MB total (excluding the JRE). This is clearly a
reasonable size for a download as well as for a footprint on a PDA or desktop. Since the Eclipse
RCP plug-ins require only the CDC Foundation class libraries, a JRE capable of running this is
only about 6MB. So Hyperbola, complete and ready to run, is under 10MBstill quite a
reasonable download. It gets even better when you consider that most of the bulk is reusable
components such as the VM, SWT, JFace, and OSGi, which may already be on the machine or
device.
Figure 23-5 shows the content of the org.eclipsercp.hyperbola.product.jface plug-in. Its
plugin.xml contributes both product and application extensions. The product defines some
branding for the windows and so on, while the application simply opens a Hyperbola window.
Figure 23-5. JFace product plug-in contents
The Hyperbola window defines its own layout that just contains a ContactsViewer and populates
the menu bar and context menus with actions. Both the ContactsViewer and the needed actions
are the same ones used in the other configurations. They are defined in the
org.eclipsercp.hyperbola.ui framework plug-in and are simply composed here.
The other component of the configuration is the feature that gathers the relevant plug-ins. The
feature is shown here. Notice that it includes the Hyperbola base feature (all the bits of
Hyperbola that are common) and picks and chooses only those plug-ins from the Eclipse RCP
base feature that are needed for this scenario. Notice that the splash screen is in a separate
plug-in as some configurations such as the PDA do not need one.
org.eclipsercp.hyperbola.feature.jface/feature.xml
<feature
id="org.eclipsercp.hyperbola.feature.jface"
label="JFace Hyperbola Chat Client"
version="1.0.0">
<includes id="org.eclipsercp.hyperbola.feature.base"/>
<plugin id="org.eclipsercp.hyperbola.product.jface"/>
<plugin id="org.eclipsercp.hyperbola.splash"/>
<plugin id="org.eclipse.core.commands"/>
<plugin id="org.eclipse.core.runtime"/>
<plugin id="org.eclipse.jface"/>
<plugin id="org.eclipse.osgi"/>
<plugin id="org.eclipse.swt"/>
<plugin id="org.eclipse.swt.win32.win32.x86"/>
<plugin id="org.eclipse.swt.linux.gtk.x86"/>
<plugin id="org.eclipse.swt.macosx.carbon.ppc"/>
...
</feature>
This feature also contains a product configuration file that captures all the relevant information
from launcher name and icons to product feature set and splash screen.
23.4.2. The PDA Configuration
PDAs are characterized by their reduced footprint and screen real-estate requirements. The
obvious choice here is to deploy the JFace configuration discussed above to the handheld
device. It is a reasonable size and runs well on the reduced SWT drops available for PocketPCs.
The main changes needed are to update the JFace configuration feature to include the new SWT
fragment and supply a new application that sets up the JFace window screen dimensions
appropriately.
Of course, there is also work to be done in adapting Hyperbola's UI to the metaphors and
practices native to the device. In some cases, this may mean reducing function or changing the
use of wizards or dialogs. The code samples supplied provide some of the initial structure
needed, but do not fully integrate Hyperbola into the PocketPC world.
23.4.3. The Extension Configuration (IDE)
Integrating Hyperbola into an existing product such as the Eclipse IDE turns out to be relatively
simple. Figure 23-6 shows that the IDE configuration consists entirely of static markupno code
is required. Everything needed is already defined, according to the rules set out in this chapter,
in the base Hyperbola UI-related plug-ins.
Figure 23-6. IDE product plug-in contents
The plugin.xml contributes and positions numerous actions and various views, perspectives,
preference pages, and wizards, all using extensions. The plug-in does not contribute an
application or product definition as it is destined to extend an existing IDE-based product.
Note
The extension contributions used here are specific to the IDE product. For example,
the menubarPath value used for action contributions may differ from product to
product. This is what makes this example so interesting. The integration is done
entirely in static markup that places actions and other contributions in the context of
the base product. Adapting to a different product may mean defining some additional
markup.
The feature definition for this configuration simply lists the Hyperbola base feature of the
relevant Hyperbola plug-ins. The standard Eclipse plug-ins are required, but are assumed to be
there as part of the RCP feature in the base product.
org.eclipsercp.hyperbola.feature.ide/feature.xml
<feature
id="org.eclipsercp.hyperbola.feature.ide"
label="IDE Hyperbola Chat Client"
version="1.0.0">
<requires>
<import feature="org.eclipse.rcp" version="3.1.0"/>
</requires>
<includes id="org.eclipsercp.hyperbola.feature.base"/>
<plugin id="org.eclipsercp.hyperbola.ui.workbench"/>
<plugin id="org.eclipsercp.hyperbola.product.ide"/>
</feature>
Since this configuration is an extension product, it does not need a product configuration filethe
branding and launcher information contained in the configuration file is not relevant here. The
feature itself is sufficient to enable exporting or building the product configuration. Notice also
that the feature requires the RCP base feature rather than including it. That is, the RCP feature
is assumed to be present.
23.4.4. The Workbench Configuration
The standalone Workbench configuration is the most complex of the three configurations. It
gathers together a more diverse set of plug-ins and more attention is paid to branding. It's very
close to the Hyperbola you developed in Part IIit has all the tell-tale signs of a typical RCP
application such as Workbench advisors, its own application, and branding.
Figure 23-7 shows the content of the product plug-in. Notice that the bulk of the classes are
advisors that define the product's look and feel, action placement, and such. Like the other
configurations, any required UI elements such as actions and views are defined in the generic,
product-independent framework plug-ins and merely identified and positioned here. This
version of Hyperbola also supports non-rectangular windows, so there is a set of advisors for
that.
Figure 23-7. Hyperbola Workbench product plug-in
Since this is a standalone configuration, the plug-in contributes product and application
extensions and defines an application class as the main entry point. The plug-in also contributes
Intro extensions and content (e.g., introContent.xml and the css folder) to help first-time
users.
The top-level feature shown below includes the org.eclipse.rcp featurethe base set of plug-ins
that comprises the base Eclipse RCPand the org.eclipsercp.hyperbola.feature.base feature. It
also identifies several additional plug-ins, including the splash screen and MUC support.
org.eclipsercp.hyperbola.feature.workbench/feature.xml
<feature
id="org.eclipsercp.hyperbola.feature.workbench"
label="Hyperbola Workbench Chat Client"
version="1.0.0">
<includes id="org.eclipse.rcp"/>
<includes id="org.eclipsercp.hyperbola.feature.base"/>
<plugin id="org.eclipsercp.hyperbola.ui.workbench"/>
<plugin id="org.eclipsercp.hyperbola.muc"/>
<plugin id="org.eclipsercp.hyperbola.splash"/>
<plugin id="org.eclipsercp.hyperbola.product.workbench"/>
</feature>
The feature project contains a product configuration file that captures the relevant product
information, including the launcher name and icons, product feature set, and splash screen. This
product configuration is largely the same as that of the JFace configuration, but with different
product/application settings.
23.4.5. A Hyperbola Kiosk
Using Eclipse as a base for kiosk systems is attractive. It is modular, updateable, and highly
customizable. The sample code for the chapter contains a generic kiosk mechanism for Eclipse.
This mechanism is not specific to Hyperbola, that is, the hospital scenario does not call for a
chat kiosk, but rather a kiosk that has chat capabilities. The actual patient or emergency room
kiosk may well have many more facilities from specifying food preferences and ordering movies
to checking waiting times.
Note
Kiosk or "restricted desktop" mode is not part of Eclipse. We have developed the
simple structure outlined, but leave the code for locking down the desktop and
restricting users to product developers. Different OSs and window systems have
varying levels of support for locked down modes, and this is beyond the scope of our
work here.
Hyperbola in the kiosk scenario is interesting because it is not standalone; the kiosk
infrastructure is the base product. Yet we still want Hyperbola's window branding to show
throughHyperbola must still supply the various advisors. To get this effect, the standalone
Workbench configuration is wrapped in an adaptor that supplies its unique window and action
bar advisors.
Figure 23-8 shows the content of the Hyperbola kiosk adaptor plug-in. As per the kiosk mode
setup, the adaptor contributes the Hyperbola perspective to the kiosk desktop and identifies the
window advisor to use for Hyperbola windows. This way, whenever the user runs Hyperbola
from the kiosk desktop, he is just opening a Hyperbola perspective, which in turn is branded
using the supplied advisors. This is an important point. The different products installed on the
kiosk desktop can all be branded differently.
Figure 23-8. Kiosk adaptor plug-in
The advisors supplied here are subclasses of the originals found in the Workbench product
configuration, but they have been tweaked to present a slightly different look and set of actions.
Of course, some care must be taken with the actions made available. For example, the regular
Hyperbola ExitAction exits the Workbench, and thus Eclipse, and thus the entire kiosk
mechanism. Such dangerous actions are simply trimmed out of the Hyperbola menus by the
HyperbolaKioskActionBarAdvisor.
23.5. Code Structure
Now that you have seen the general structure of Hyperbola, let's look at how classes, images,
views, editors, viewers, and extension points are split among plug-ins. The ability to build
multiple configurations of Hyperbola hinges on the plug-in dependencies being crafted to allow
reuse across the different product configurations. You do not want to write actions or dialogs
twice!
Dependency management is perhaps one of the biggest challenges when designing
componentized systems. Developing Hyperbola as one monolithic tower of function was initially
practical, but the approach does not scale welldeployment options are limited and code reuse is
inhibited. On the other hand, Hyperbola as a set of loosely coupled and smaller plug-ins is much
more flexible.
Factoring Hyperbola into a set of interdependent plug-ins is a step in the right direction, but is
really only the beginning of the journey. Exercising good API hygiene and rigorous dependency
management improve coding efficiency by minimizing ripple effects and increase the ability to
reuse plug-ins in different application configurations. This leads to the most important rule to
remember:
Rule 2: Manage dependenciesMinimize and layer plug-in dependencies.
Developers need to be conscious of the interdependencies they are establishing. Dragging 20MB
of code into your system just to get one small bit of function does not make sensebut it
happens.
23.5.1. Hyperbola Layering
Let's start with a quick overview of the functionality included in each of the refactored
Hyperbola framework plug-ins. The significant difference between this structure and the
Hyperbola in the rest of the book is the refactoring of Hyperbola into core and UI plug-ins. The
UI plug-ins are further factored into JFace- and Workbench-related plug-ins. Otherwise, the
remainder of the refactoring involves moving all the contributions into the product
configurations.
org.eclipsercp.hyperbola This plug-in contains the data model and XMPP protocol
operations that do not require UIs. This is the core messaging infrastructure that enables
the use of Hyperbola messaging in a wide range of environments, all the way down to
being embedded in medical instruments.
org.eclipsercp.hyperbola.ui This plug-in contains all the base UI features that do not
require the Workbench. This includes the actions, ContactsViewer, ChatViewer, preference
pages, images, and wizards. This plug-in contains most of the interesting code needed to
implement a Hyperbola UI and is based on JFace. In the Part II Hyperbola, the
ContactsView and ChatEditor contained both the container and viewer logic. This made it
hard to show a chat in a window or view. In this version of Hyperbola, we split the basic UI
constructs from their placement in the UI. This provides more flexibility in placing different
UI elements (e.g., in dialogs, windows, views, or editors).
org.eclipsercp.hyperbola.ui.workbench This plug-in wraps the basic UI pieces from the
Hyperbola UI plug-in. This includes a ContactsView wrapper for the ContactsViewer and a
ChatEditor wrapper for the ChatViewer. Both of these are contributed via the Workbench
extension points. This plug-in defines these UI elements, but does not place them in the
Workbenchthat is left to a product plug-in. This plug-in does, however, contribute a set of
command definitions for the actions that are defined by the base UI plug-in. This enables
Workbench key bindings. The only extensions defined in this plug-in are to declare the
views, editors, and commands.
The remainder of this section explains some guidelines that were used to arrive at this structure
that you can use in your own products.
23.5.2. Workbench Contributions
When you contribute to various Workbench extensions, such as action sets, preference pages,
perspective extensions, views, and editor actions, they must be contributed to a specific
product. In general, you cannot write a plug-in that integrates tightly with the IDE and then
expect to integrate it unchanged with a random RCP-based application.
The issue is not mechanical, but rather practicalthe UI context for the plug-in is different. Menu
bar paths may be different, the integration points may disappear, and your plug-in needs to
adapt. Figure 23-9 illustrates this situation. The plug-in on the left may physically load and run
if dropped into the product's plugins directory, but it is pure luck if its menus, toolbars, and
views appear correctly.
Figure 23-9. Plug-ins need to know their integration partners
To develop UI plug-ins that can be used in different applications, you must separate the
implementation of UI elements from their contributions.
Rule 3: Framework plug-insMinimize Workbench contributions in framework
plug-ins.
The goal is to define as much as possible, in generic terms, without committing to the details of
how the UI component is placed or contributed. Leave that to the product plug-ins.
What happens when you don't follow this rule? Figure 23-10 shows what happens when you add
the org.eclipse.ui.editors and org.eclipse.ui.externaltools plug-ins to the Hyperbola
Workbench product configuration. Of course, if you are adding the plug-ins, you likely want to
use their function, but you may want to present it differently. For example, these plug-ins are
designed to be used in Eclipse IDE-based products and they place their actions accordingly. This
is not a bug in the plug-ins, but rather a choice that was made by the plug-in designers.
Figure 23-10. Hyperbola running with a non-framework plug-in
23.5.3. Actions
In Hyperbola, most actions are defined in the org.eclipsercp.hyperbola.ui plug-in and
implement IAction. IAction is used because it is part of JFacethe action definitions do not
needlessly drag the Workbench into product configurations. This supports the desire to run
Hyperbola on PDAs and smaller environments. Another common layering technique is to define
operationsessentially non-UI actions that perform work on your data model. You can then wrap
one or more operations into appropriate actions that appear in the UI. This has the bonus of
further decoupling behavior from how it is modeled in the UI.
The goal is to define as many actions as possible in the framework plug-ins so that they are
reusable in different product configurations. Of the five main action characteristics we discussed
in Chapter 17, "Actions," only the placement and key bindings for an action should be delayed
until the action is used in a specific configurationall other parts should be defined in one place
and reused.
To add actions to the Workbench as actions sets, popup menus, view actions, or editor actions
in a particular configuration, adapt the base actions so that they implement the appropriate
Workbench interface for the given extension point (e.g., IWorkbenchWindowActionDelegate,
IObjectActionDelegate).
23.5.4. Key Bindings
Because of differences in the key binding support in SWT and the Workbench, key bindings are
left to the product configuration using the actions. In the case of a configuration running
without the Workbench, accelerators are set on the action itself using SWT accelerators.
addContactAction = new AddContactAction(shell, session);
addContactAction.setAccelerator(SWT.CTRL | 'N');
When running with the Workbench, commands are created for each action and then used to add
key bindings, as shown in the following snippet. Notice that the same Action class is used, but
instead of being initialized with an accelerator, the action's id is registered with the Workbench,
as discussed in Chapter 12, "Adding Key Bindings."
addContactAction = new AddContactAction(shell, session);
register(addContactAction);
When designing for multiple products, you often have to define multiple sets of key bindings,
one for each product. For example, with Hyperbola, the standalone configuration defines its own
key configuration, but when Hyperbola is used to extend the IDE, it has to define key bindings
in the context of the IDE to increase integration and avoid key binding collisions.
23.5.5. Views and Editors
As we have seen, to enable maximum flexibility in building applications without the Workbench,
most of the UI elements are created using only JFace primitives. At first this is a bit
shockingpeople often think, "I'm implementing a view," or "I'm implementing an editor."
Rather, we have focused on implementing the basic UI elements that match the domain
problems, and then we have looked at views and editors as containers for these elements. Once
again, there is a separation between the UI elements and how or where they are presentedyou
design the UI components separately from the containers and then plug them together as
needed.
Put another way, you should build the core UI elements based on your application's data models
and user interactions and then use a decoupling pattern, such as Inversion of Control (IoC)[1] ,
to enable the use of these elements in different scenarios.
[1] IoC is a design pattern that helps teams avoid the dependency hell that results when an application grows into a large pseudo-
platform without taking care to adequately decouple logic. One well-known synonym for IoC is DIPDependency Inversion
Principle (see http://www.martinfowler.com/articles/injection.html).
Rule 4: UI decouplingDecouple UI components from their container.
In Hyperbola, the ContactsViewer and ChatViewer UI components are built on a few base
concepts, such as XMPP sessions and SWT composites. In Workbench-based configurations,
these base UI components are used to build views and editors, whereas in JFace-based
configurations, they are simply added to the top-level shells as appropriate.
23.5.6. Wizards, Preferences, and Property Pages
Hyperbola's wizards, preference pages, and property pages follow the same pattern as
actionsthey are defined using only JFace and then contributed by the product configurations.
When these pages are used in Workbench configurations, they are adapted to implement the
appropriate Workbench interface (e.g., IWorkbenchWizard, IWorkbenchPreferencePage, and
IWorkbenchPropertyPage). Adapting a JFace component to be compatible with a Workbench
extension is extremely easy. You just wrap the JFace component with the appropriate
Workbench interface and implement the one or two Workbench-specific operations.
In JFace configurations, these pages are simply placed in the relevant wizard or preference
page dialogs. Note that property pages are really just preference pages shown for a specific
object. This allows you to arrange the pages differently in different product configurations, for
example, keeping all preference pages flat in one, not organized in another, or grouped into a
main category in another.
23.5.7. Optional Dependencies
Instead of layering dependencies by splitting plug-ins, you can layer dependencies within the
same plug-in using optional dependencies.
Rule 5: Optional dependenciesUse optional dependencies for intra-plug-in
layering.
This capability was outlined in Chapter 21. Hyperbola does not use optional dependencies, but
the EMF core plug-in contains a good example of how they works and where to use them. In the
EMF core plug-in, all the code related to the Eclipse Resources plug-in is collected into one
package. The EMF core is designed around an abstract data model that accommodates both
java.io.File and org.eclipse.core.resources.IResource data objects. The dependency on the
plug-in org.eclipse.core.resources is then marked as optional, as shown below. Since the
bulk of the EMF code is isolated from the Resources API, EMF continues to run even if the
Resources plug-in is not installedEMF clients are never able to pass in or expect back Resources
objects.
org.eclipse.emf.ecore/plugin.xml
Require-Bundle:
org.eclipse.core.runtime,
org.eclipse.core.resources;resolution:=optional
This approach allows the same EMF plug-in to be used in both standalone RCP configurations
and Eclipse IDE-based configurations. It also helps prevent a combinatorial explosion of plugins in each layer of your system. The disadvantage with layering within a plug-in is that unused
code may be shipped with an application. In this case, EMF's Resources support is not separable
and must be shipped with all EMF systems.
23.5.8. Icons and Images
When we first started breaking up Hyperbola, we found that images were being duplicated in
several places. In addition to being a waste of disk space, this approach is very hard to
managewe were always forgetting to update images here or there and were still finding old
images months after the refactoring.
The alternative is to treat images like code and share them. In the refactored world, all
Hyperbola images are in org.eclipsercp.hyperbola.ui and are simply used by other plug-ins.
Image-sharing in code is done by exposing an ImageRegistry on a common plug-in and using it
from clients. For example, the pattern shown below occurs in several places in Eclipse itself:
Hyperbola.getDefault().getSharedImages().getImage(IMG_ONLINE);
URLs can be used to share images in declarative files and extension definitions. For example,
the following URL indicates that the image at icons/pub/chat.gif in the
org.eclipsercp.hyperbola.ui plug-in should be used as the icon for the editor:
org.eclipsercp.hyperbola.ui/plugin.xml
<extension point="org.eclipse.ui.editors">
<editor
class="org.eclipsercp.hyperbola.internal.ui.ChatEditor"
icon=
"platform:/plugin/org.eclipsercp.hyperbola.ui/icons/pub/chat.gif"
default="false"
name="Chat"
id="org.eclipsercp.hyperbola.chateditor"/>
</extension>
Tip
Just like sharing code, sharing images should only be done through API. That is, if a
plug-in exposes its images as API, you can use them. If not, you should not.
Uncontrolled reaching into other plug-ins and using images (or any file, for that
matter) exposes you to more change than you might like.
23.6. Designing a Platform
Not only has Hyperbola been refactored to run in many configurations, it has also turned into a
platform for messaging clients. The progression from a simple product to a platform involves
pushing function down into framework plug-ins. The focus shifts from shipping one specific
product to understanding what function is generic and how other products can be built around
that function. That is the nature of the platform.
To build a platform, you should first consider the rules outlined in this chapter. Your plug-ins
need to be framework plug-ins to have a platform. Given a set of framework plug-ins, you
should look for ways to add extensibility.
One way is to use standard coding techniques and APIs. Section 26.5, "Services," introduces the
OSGi notion of services as a means of making a system extensible. Throughout this book, you
have been using Eclipse's extension registry mechanism, as discussed in Section 2.5.1,
"Extension Registry."
This section covers the use of extension points to allow other plug-ins to customize or extend
your framework plug-ins. To recap, the advantages of extensions and extension points are:
They are declarative. Declarative extensions can be filtered, checked, and composed. This
improves UI scalability and enables various approaches for presenting, scoping, and
filtering contributions.
They promote lazy loading of code. Plug-ins contributing extensions can have a presence
in the system without requiring their code to be loaded.
They can be added and removed at runtime. As you saw in Chapter 22, "Dynamic Plugins," this means that plug-ins can be added and removed after the application has started.
The mechanism can be used to contribute code and/or data.
23.6.1. Extension Points
By now, you are familiar with contributing extensions to the extension points defined by the
Eclipse platform, but you have not actually written one of your own. Adding an extension point
involves first declaring the extension point in a plug-in and describing it to other plug-ins. The
plug-in is then coded to use contributed extensions as set out in the description.
Creating an extension point declaration is done using the Add... button on the Extension
Points page of the relevant plug-in editor. Figure 23-11 shows an example of declaring the
iqProviders extension point in the org.eclipsercp.hyperbola plug-in. The Extension Point
Schema file location field is filled in automatically. Schemas are used to describe the expected
structure of extensions contributed to an extension point. They describe everything from the set
of tags and attributes to the kinds of values placed in the attributes. See PDE Guide > Tasks >
Creating an extension point schema in the online help for details on schemas and using the
schema editor.
Figure 23-11. New extension point declaration
Having defined the extension point, various plug-ins can contribute extensions that conform to
the schema. The snippet below shows the XML markup for an iqProvider extension. Typically,
developers use the facilities on the plug-in editor's Extensions page to define and manage their
extensions.
org.eclipsercp.hyperbola.muc/plugin.xml
<extension
name="MUC IQ Providers"
point="org.eclipsercp.hyperbola.iqProviders">
<provider
elementName="x"
namespace="jabber:x:conference"
className="org.jivesoftware.smackx.GroupChatInvitation$Provider"/>
</extension>
As you saw earlier, the Eclipse Runtime maintains a registry that connects together extensions
and extension points. The org.eclipsercp.hyperbola plug-in can access extensions contributed
to its iqProviders extension point by accessing this extension registry, as shown in the snippet
below. The extension point's fully qualified id is used to get a set of configuration elements.
org.eclipsercp.hyperbola/HyperbolaProviderManager
public IQProvider getProvider (String elementName, String namespace) {
String point = "org.eclipsercp.hyperbola.iqProviders";
IConfigurationElement[] decls = Platform.getExtensionRegistry()
.getConfigurationElementsFor(point);
for (int i = 0; i < decls.length; i++) {
IConfigurationElement element = decls[i];
if (elementName.equals(element.getAttribute("elementName"))
&& namespace.equals(element.getAttribute("namespace"))) {
try {
return
(IQProvider) element.createExecutableExtension("className");
} catch (CoreException e) {
e.printStackTrace();
}
}
}
return null;
}
Configuration elements are object representations of the XML elements under the <extension>
tag in the plugin.xml files; for example, the <provider> markup in the example above surfaces
as a configuration element. Accessing the registry as shown returns a list of the top-level
configuration elements across all extensions in the given extension point. This is convenient
where the identity of the extensions and the number of configuration elements per extension
are not important.
The iqProviders extension point requires extensions to identify a class that implements an
IQProvider interface. The details are not particularly important. The key point is that the
Hyperbola plug-in is allowing other plug-ins to contribute code that is used to extend the
capabilities of Hyperbolain this case, to handle different messaging protocols.
The previous snippet scans the set of configuration elements, and when it finds an applicable
one, it instantiates the class identified in the extension by calling
createExecutableExtension(String) . The result object is then used according to the contract of
IQProvider.
23.6.2. Extension Factories
In the previous example, the class to instantiate was given as a fully qualified class in the
className attribute of the extension. This requires the identified class to implement a public
zero-argument constructor and restricts the initialization that can be done on the resultant
object. Another approach is to use an extension factory to create or discover the desired object.
Factories are useful as they allow more complex discovery and initialization. For example,
executable extensions can be discovered using OSGi service lookup or Web service discovery.
Factories hide these implementation details from the extension point.
If the class identified in an extension implements IExecutableExtensionFactory, the factory
class is instantiated and the instance is given the information in the related configuration
element. It then uses this information to determine what kind of object to return. For example,
the snippet below from Section 17.3, "Standard Workbench Actions," shows the UI's
ExtensionFactory being used to create a progress view. Note that the UI's extension factory is
free to decide how it creates the requested progress view. The Javadoc for
IExecutableExtension and the individual factories detail the acceptable syntax of the data
following the factory name in the markup.
plugin.xml
<extension point="org.eclipse.ui.views">
<view
class="org.eclipse.ui.ExtensionFactory:progressView"
...
23.6.3. Named and Anonymous Extensions
Extension ids are, in some cases, optional. For example, the previous provider extension did
not have an id attribute. Extensions to the Runtime's applications extension point, however,
do require ids, as shown in the snippet below. Both anonymous and named extensions are
prevalent in Eclipse.
org.eclipsercp.hyperbola/plugin.xml
<extension
id="application"
point="org.eclipse.core.runtime.applications">
<application>
<run class="org.eclipsercp.hyperbola.Application"/>
</application>
</extension>
The choice of whether or not to have named extensions is completely up to the plug-in defining
the extension point. In the case of the Runtime's applications extension point, the id is
required because the Runtime is given an application id to run by the user or the product. It
needs to find the extension that matches that id, instantiate the associated class, and run it.
Having a system-managed id is convenient.
In the case of most UI extension points and those in Hyperbola, extensions are treated
anonymously. Anonymous extensions allow many contributions, each in their own configuration
element, in one extension. This is sometimes simpler and clearer for developers.
The benefit of having ids on extensions is that you can call the optimized IExtensionRegistry
method, getExtension(String, String) , to fetch a specific extension as opposed to having to
traverse or remember the extensions yourself. For example, the earlier
HyperbolaProviderManager code snippet repeatedly iterates over all contributed configuration
elements to find the appropriate provider, whereas the Runtime can simply access the registry
and directly look up the required application extension.
23.7. RCP-friendly Plug-ins
A common question is how to tell if a plug-in is RCP-friendly. We use the term friendly as a
synonym of what we've also been calling framework plug-ins. These are plug-ins that are
designed to work in any product. Most people ask this question when they are building an RCP
product and want to use someone else's plug-ins. The simple answer is that if a plug-in
manages its dependencies and uses optional dependencies appropriately, then it is a framework
plug-in and is RCP-friendly.
Unfortunately, Eclipse does not include markup to quickly identify plug-ins that have these
characteristics. The easiest way for you to tell if a plug-in follows Rule 2 (Manage dependencies)
and 3 (UI decoupling) is to look for the following:
Open the plugin.xml for the plug-in you are examining and look at the set of plug-ins it
requires. If it requires a product, for example the Eclipse IDE, then it is not a framework
plug-in.
Next, look at its extensions for specific references to toolbar or menu paths, preference
pages, or other contributions that place elements in the UI. In general, views and editors
are acceptable since they do not appear in the UI unless they are explicitly placed. See
Section 23.4.2.
A common misconception is that depending on plug-ins from outside the RCP Base, for
example, the Resources plug-in, means you are not RCP-friendly. This is not necessarily true. If
the prerequisite plug-in's dependencies follow the rules, then it is RCP-friendly. The
org.eclipse.core.resources plug-in is in fact RCP-friendly.
23.8. Summary
After reading this chapter, you should have a solid base on which to build your own set of
product configurations. Even though Hyperbola is a relatively small example, the hospital
scenario shows how it can be expanded to have a real impact across enterprises.
By following our rules, we ended up with something that is extremely flexible. These rules can
be applied to any size product:
Rule 1: Top-level featureHave a top-level feature for every product
configuration.
Rule 2: Manage dependenciesMinimize and layer plug-in dependencies.
Rule 3: Framework plug-insMinimize Workbench contributions in framework
plug-ins.
Rule 4: UI decouplingDecouple UI components from their containers.
Rule 5: Optional dependenciesUse optional dependencies for intra-plug-in
layering.
Emboldened readers are now able to build and deploy applications in a vast array of scenarios,
from integration into existing products (e.g., IDEs) to field force PDAs and customer kiosks.
Chapter 24. Building Hyperbola
Up to this point, you have been using the PDE Export Product wizard to create end-user
deliverable versions of Hyperbola. As the Hyperbola product grows, has more developers
working on it, and more configurations, there's a pressing need for automated, reproducible,
and accessible builds.
Building Eclipse systems by hand is somewhat challenging and tedious. The compile-time
classpath for any given plug-in includes the code from all its prerequisites. Just computing the
classpath and build order is hard. Mix in variations such as different source locations (e.g.,
projects in the workspace, checked out from CVS in the filesystem, or both) and the myriad of
output packaging options and you need help.
So far, PDE's Export wizards have insulated you from most of these details. Unfortunately,
those wizards are hard to automate, as you have to click around in a UI to launch a build. It is
also hard to make repeatable as it depends on the contents of the user's workspace. Product
teams and communities need release engineering builds that are automated and more rigorous.
Fortunately, the same underlying infrastructure used for exporting plug-ins, features, and
products from the workspace can be used to perform release engineering builds of Eclipsebased products. This mechanism is called PDE Build. PDE Build takes a set of features and plugins and compiles and packages them according to the dependency information in their manifests
and a set of control parameters. The output is an archive or directory structure that can be
deployed either directly, via an update site, or even using Java Network Launch Protocol (JNLP).
In short, PDE Build helps you create regular and reproducible builds.
This chapter dives into PDE Build and guides you through setting up an automated build for
Hyperbola. We cover:
The different build.properties files associated with building.
Setting up the build scripts to build Hyperbola.
Running builds.
Building products.
Cross-platform building.
Customizing the build scripts.
Automatic version number qualification.
24.1. What Is PDE Build?
At its heart, PDE Build is an Ant script generator. It takes in a collection of plug-ins and
features, their manifests and build.properties files, and generates a set of Ant build scripts.
These scripts are run to produce a build. The export operations you have been doing throughout
this book use PDE Build under the covers.
PDE Build is quite flexible. It can consume hybrid mixes of plug-ins and features that are prebuilt and those that remain to be built. Some may be included in the final output, while others
may not. The output of a build can also vary from plug-ins in directories to update sites and Zip
archives of JAR'd and signed plug-ins and features.
The build mechanism builds plug-ins and features, or cascades of the transitively included
features and plug-ins starting at a root feature. Cross-platform building is also supported.
The main benefit of PDE Build is that it brings all this together into one relatively simple
process. Developers express their normal runtime dependencies in manifest files and a mapping
from development time structure to runtime structure in the feature and plug-in
build.properties files. PDE Build does the rest.
Key to this process is the automatic generation of the build scripts. Using the input manifests
and build.properties, PDE generates Ant scripts that copy identified files and compile
identified code using a classpath derived by effectively flattening the plug-in dependency graph.
The runtime classpath for a plug-in is defined as a complex graph of plug-in dependencies as
described in its manifest file. The classes referenced at runtime are also needed at compiletime, so the compile-time classpath is similarly complex. PDE Build uses the Runtime's plug-in
resolution and wiring mechanisms to derive the classpath for each plug-in being built.
As we mentioned above, you have already been using PDE Build if you followed along with the
feature- or product-exporting examples. When you use these actions, you are under the covers,
running PDE Build. The rest of this chapter explores the use of PDE Build in a release
engineering setting, where reproducibility and automation are key concerns.
24.2. Plug-in build.properties
Before we get too far into PDE Build itself, let's recap what you have used as a build process so
far in the book. Since the PDE wizards have been doing most of the work, you have only seen
the build.properties file for the Hyperbola plug-in. This file is exposed on the Build page of
the plug-in editor. For example, in Chapter 9, "Packaging Hyperbola," you updated the set of
included files to contain Hyperbola's icons and other branding information.
The role of the build.properties file is to map development-time structures in a plug-in's
project onto the Runtime or deployed structures described in the plug-in's manifest. For
example, by adding elements to the Binary Build section, you are stating that the deployable
version of the plug-in must include those elements.
Tip
The various build.properties file options are documented in Help > PDE Guide >
Getting Started > Features > Advanced topics in building features > Build
configuration.
24.2.1. Control Properties
The plug-in editor's Build page helps set up common build-related properties. To add more
advanced properties, you have to edit the build.properties file directly using the plug-in
editor's build.properties page. When you set up automated builds, these advanced build
properties become more relevant. As such, this section provides a reference table (Table 24-1)
and an example properties file. See the PDE help for a full list of build properties.
build.properties
bin.includes=plugin.xml, META-INF/, ., icons/, html/
bin.excludes=html/private*.html
source..=src/
extra..=library.jar
Table 24-1. Plug-in Build Properties
Property
Description
bin.includes
A comma-separated list of development-time resources that are
copied into the plug-in when it is built. This must include the plug-in
markup (e.g., MANIFEST.MF and plugin.xml) as well as any code
(using simply "." for plug-ins you want JAR'd or plug-in-relative JAR
names for flat plug-ins) and additional files such as icons, message
catalogs, and licensing files.
Entries in the list are expressed using Ant pattern syntax. The most
common patterns include * (e.g., *.html) and a trailing "/" (e.g.,
html/) to indicate that a directory structure is to be included.
The bin.includes line in the example declares that plugin.xml and
the contents of the META-INF , icons, and html directories should be
included in the deployable runtime version of the plug-in.
bin.excludes
A comma-separated list of development-time resources that should
not be included in the built version of this plug-in. The entries in
this list override those in the bin.includes list. Excludes list entries
are also expressed as Ant patterns.
The bin.excludes line in the example declares that all "private"
HTML files should not be included in the deployable runtime version
of the plug-in.
source.<library>
The set of development-time resources to compile to create the Java
executable element identified by "<library>". Here, "<library>" is
typically "." to indicate the plug-in itself. Alternatively, it is the name
of a JAR file. The value is a comma-separated list of Ant patterns
that identifies files passed to the Java compiler during the build.
The source.. line in the example declares that the files in the src
directory are compiled and the output is placed in the root of the
plug-in (as indicated by the second "." in "source.. ").
extra.<library>
A comma-separated list of elements to add to the compile-time class
path when compiling the source as defined in a corresponding
source.<library> property. This is commonly needed when you
have JARs you compile against but do not ship and do not include in
any of the plug-ins this plug-in requires.
PDE uses this information, in combination with the plug-in manifests, to generate a build.xml
script for each plug-in that is then run during the build process.
24.2.2. Using Custom Build Scripts
You can opt out of the build script generation by supplying your own build.xml and selecting
Custom Build on the Build page of the plug-in editor, as shown in Figure 24-1.
Figure 24-1. Custom build selection
This is useful if, for example, you want to compile C code or build other artifacts while building
the plug-in. Note that if you opt for a custom build.xml, you take complete responsibility for
implementing a build script with all the right targets and that does all the right things. A good
place to start is to use PDE Tools > Create Ant build file on a plugin.xml or MANIFEST.MF to
create an initial build.xml that you can then edit to suit your needs.
24.3. Feature build.properties
Like plug-ins, features have their own build.properties file that maps the development-time
structure onto the runtime structure. For a feature, the main concerns are to describe the set of
files to include in the feature, that is the bin.includes property, plus the set of files to copy to
the root of the final build output. The snippet below shows a common feature build.properties.
The bin.includes property behaves exactly as described for plug-ins. The remainder of this
section details the setup for root files. See the PDE help for a full list of feature build properties.
bin.includes=feature.xml, about.html, feature.properties, license.html
root=rootfiles
24.3.1. Identifying and Placing Root Files
Root files comprise the set of files that appears at the root of the product install. The most
obvious root file is the product launcher (e.g., hyperbola.exe ). Other root files include the
launcher's initialization file, startup.jar, the config.ini, and various licenses and legal files.
The root may even include a JRE.
When you used the PDE product editor in Part II, you identified the launcher, launcher
initialization file contents, and config.ini file needed to create a real product build. This
information was used by the export wizard to copy, brand, and place the necessary files in the
root of the output product. The wizard also ensured that startup.jar was included in the root.
The product definition and export wizard do not give you full control, however. For example,
arbitrary root files such as licenses and legal files cannot be directly identified. These files must
be enumerated in the build.properties for a feature included in the product. Note that many
features can contribute to the root files for a build. The feature root files in parent features
overwrite those of their children. The properties relevant to defining the root files are listed
below:
root Files listed here are always copied to the root of the output. If a directory is listed, all
files in that directory are copied to the root of the output.
root.<os.ws.arch> There should be one of these lines for each OS, window system, and
processor architecture combination that has unique root files. For example, if you need to
include compiled libraries or executables, you should identify them on the root lines for the
appropriate configurations.
Each property value is a comma-separated list of files to copy to the root of the output. The files
are identified using one of the following techniques:
The contents of a directory structure are included by listing the parent directory itself. For
example,
root=rootfiles
copies the entire contents of the rootfiles directory in the feature to the root of the build
output. This is the most common setup seen in features.
Individual files must be identified by prefixing their location with file:. For example, the
line
root.linux.gtk.x86=file:linux/special_executable
copies just the special_executable file to the root. Note that the given path is relative to
the feature and the containing directory structure is not copied.
Absolute paths can be specified by prefixing the location with absolute:.
Executable files and libraries often need to have special permissions set when they are placed in
the final archive so that when end-users unpack the archives, the product is ready to run. You
can control the permissions of the root files by defining a property of the form:
root[.os.ws.arch].permissions.<perm_pattern>=<files>
The os.ws.arch configuration identification is optional. The <perm_pattern> is a UNIX® file
permissions triple, such as 755, which should be familiar to chmod users. The value of the
property is a comma-separated list of files to which the given permissions should be applied.
Ant patterns are supported for identifying files. Note that the permissions are applied once the
files have been copied to their position in the root directory and so all paths to the files should
be relative to the output root. Non-existent files are silently skipped and folders must be
indicated with a trailing "/".
24.3.2. The Launcher
One of the most important root files in your product is the launcher. The main role of the
launcher is to find a JRE and launch it on the Eclipse startup code in startup.jar. It also serves
several additional purposes:
It simplifies the launching of Eclipse. You can just run the launcher rather than having to
figure out various VM command line arguments. This eliminates the need for batch files.
It manages the splash screen. The splash screen is essentially a hint that provides
feedback to the user that she did indeed double-click on the program and something is
happening. Having the launcher display the splash screen means that it's shown to the
user as soon as possible.
The launcher is the interface with the OS and window system. It dictates ownership and
permissions as well as how Eclipse is presented in the user's desktop, for example, which
icon is shown and the name of the process.
As you have seen, PDE takes care of branding the launcher for you when a product is exported.
The resultant launcher is identical to the standard Eclipse launcher, but with a different name
and different icons.
The launcher can take direction from an initialization file that allows you to define default sets of
command line arguments for both Java and Eclipse and thus define how your product operates.
The launcher looks for an initialization file of the same name but with ".ini" appended. For
example, the initialization file for hyperbola.exe is hyperbola.ini . The file itself is a simple text
file structured as a command line with one command line token per line in the file. The following
file tells Hyperbola to start the identified JVM using the given VM arguments:
hyperbola.ini
-vm
c:\java 1.4.2\jre\bin\java.exe
-vmargs
-verbose
The initialization file is essentially a standard command line that has been tokenized such that
each token is placed on a line by itself. This syntax is a little strange, but it greatly simplifies the
parsing required by the launcher's C code. Since the file represents a standard command line,
order mattersthe VM arguments must go last.
Tip
Putting all the command line tokens on one line is a common source of problems with
the launcher initialization file. If your command line arguments appear to be ignored,
check the format of your file.
The Configuration page in the product editor has a Launching Arguments section that
exposes both the program arguments and VM arguments. An example of its use is shown in
Figure 24-2. When the product is exported or launched, PDE creates an .ini file named and
placed according to the product definition. Note that arguments containing spaces must be
quoted accordingly.
Figure 24-2. Adding launching arguments
Typically, you should use this mechanism to set VM arguments or supply program command
line arguments that do not have system property equivalents. System properties should be set
using the config.ini file described in the following section.
Note
If you use -Dsystem.property=value -style VM arguments, such values take precedence
over properties set any other way.
24.3.3. config.ini
The most interesting root file is config.ini. It defines the product to run and a small set of
bootstrap plug-ins to get the system going. This is a standard Java properties file whose
key/value pairs are merged into Java's system properties. By "merged" we mean that if a
property with the given key already exists, the value in the config.ini is ignored.
Eclipse uses a number of system properties and most command line arguments (e.g., -data, configuration ) have system property equivalents. As such, they can be set in the config.ini
file to control how the configuration behaves. A typical config.ini for Hyperbola looks
something like the snippet below:
Hyperbola/configuration/config.ini
osgi.splashPath = platform:/base/plugins/org.eclipsercp.hyperbola
osgi.bundles=\
[email protected]:start,\
[email protected]:start
eclipse.product=org.eclipsercp.hyperbola
[email protected]/hyperbola
Let's look at these in order:
osgi.splashPath This is the set of locations to search for the splash screen for the
corresponding product. We talked about this in Chapter 8, "Branding Hyperbola."
osgi.bundles When Eclipse starts, it needs to know the bootstrap bundles to install and
optionally start. The Eclipse Runtime is the essence of Eclipse, and Update's configurator is
useful as it installs the rest of the plug-ins discovered in the standard Eclipse plug-ins
directories. The annotations following the plug-in ids indicate the start level of the plug-in
and whether or not the plug-in should be explicitly started. Chapter 26, "OSGi Essentials,"
gives more detail on this property.
eclipse.product This is the id of the product definition to run. As we have noted
previously, a given Eclipse install may include many product definitions. Defining this
property is critical to correctly running your product because the product describes which
application to run.
osgi.instance.area A running Eclipse system often needs to write either user data or
internal plug-in data (e.g., UI layout). The instance area is one location where this
information can be written. In the example here, Hyperbola is told to write all such data in
the hyperbola sub-directory of the user's home directory. Section 26.10, "Classloading,"
contains more information on positioning the instance area.
A complete list of properties and their use is given in the reference section of the online help,
under the topic "Runtime options." Of course, your plug-ins can also define more properties.
You might notice that the information above is supplied in the product definition itself. As with
the launcher initialization file, PDE can generate the config.ini file when you launch or export
a product. The Configuration File section on the Configuration page of the product editor
allows you to control this behavior. Figure 24-3 shows an example where we have opted to
define our own config.ini.
Figure 24-3. Defining a config.ini file
Note
Though we call the config.ini file a "root file," it is actually positioned in the
configuration directory at the root of a normal Eclipse directory structure.
Most of the time, you can let PDE create your config.ini directly from the product configuration
file. However, Section 25.7, "Multi-user Install Scenarios," and Section 26.8.2, "config.ini," give
examples of cases where you may need to modify the file by hand. Furthermore, when building
with PDE Build, you must manually create and position the config.ini and hyperbola.ini files.
24.4. Setting Up a Hyperbola Builder
To see how all this works in practice, let's set up a managed build process for the final
Hyperbola prototype from Chapter 14, "Adding Update." The prototype consists of a Hyperbola
feature and several Hyperbola-related plug-ins. The Hyperbola feature includes the Eclipse RCP
Base feature and several additional plug-ins (e.g., Help and Update).
Start by creating a simple project for the build scripts (File > New... > Project > Simple >
Project). Call it "hyperbola.builder". In the filesystem, navigate to your Eclipse IDE install and
go to the org.eclipse.pde.build plug-in (e.g., the
c:\ide\eclipse\plugins\org.eclipse.pde.build_3.1.0 directory). Copy the two files,
build.properties and customTargets.xml, from the templates directory to the
hyperbola.builder project. These are templates for the files used to control builds. In the
subsequent sections, the templates are filled in and used to build Hyperbola.
The builder's build.properties file is quite different from the other build.properties files you
have seen so far. It contains key/value pairs that define the input parameters to the build, while
customTargets.xml is an Ant build script that defines a set of Ant targets. These targets are
called during the build process. Having them here allows you to override or add behavior to the
build.
24.4.1. build.properties
Below is a summary of the changes needed to the template build.properties that was copied
to the builder project. Some of the properties shown are only needed later on, but are listed
together here to show the big picture. If a property is not listed here, it does not need to be
changed. Of course, you should replace the filesystem locations appropriately.
Tip
Use Open With... > Properties File Editor to edit the source of build.properties. It
has better syntax highlighting and is easier to work with.
hyperbola.builder/build.properties
# Product and packaging control
product=/org.eclipsercp.hyperbola/hyperbola.product
runPackager=true
archivePrefix=hyperbola
configs=win32,win32,x86
# Build naming and locating
buildDirectory=${user.home}/eclipse.build
buildType=I
buildId=TestBuild
buildLabel=${buildType}.${buildId}
# Base identification and locating
skipBase=true
base=c:/target
baseLocation=${base}/eclipse
baseos=win32
basews=win32
basearch=x86
# CVS access control
skipMaps=true
mapsRepo=:pserver:[email protected]/path/to/repo
mapsRoot=path/to/maps
mapsCheckoutTag=HEAD
skipFetch=true
fetchTag=HEAD
# Java class libraries and compiler controls
bootclasspath=${java.home}/lib/rt.jar
Let's look at each of these values and see how they affect the build. There are, of course, many
more properties, but understanding these should give you an idea of how the build goes
together and the level of control you have. The main information in build.properties covers
roughly five areas of concern in the build process. Each of these is detailed in a separate section
below. They are presented roughly in decreasing order of interest. That is, you have to set up
the values in the first section, but may not have to change things in the last section.
24.4.1.1. Product and Packaging Control
These properties describe what you are building, the branding you want, the machine
configurations, and the shape of the output:
product The location of the product file that describes what is being built. The value takes
the form /<id>/path/to/.product, where <id> is the id of the feature or plug-in that
contains the .product file.
runPackager A marker property that if set causes the builder to add all included plug-ins
and features to the final output of the build.
archivePrefix The specified prefix is added to the beginning of all paths in the output
archive. This gives you control over the shape of your product when it is extracted on the
user's machine.
configs A list of target machine configurations for which you want to build. Each
configuration consists of an os, ws, arch triple, such as win32,win32,x86. Configurations
are separated by ampersands. The build process creates a separate output for each
configuration. If the configuration is not set or is set to *,*,*, the build is assumed to be
platform-independent. This is suitable for building update sites and extension products.
archivesFormat The format in which to write the output for each configuration. By default,
all configurations are created using Ant's zip task, and this property is not needed. The
format of this optional property is the same as that of the configs property, but each
configuration is followed by an output format indicator (e.g., -folder or -antZip). You
need only to specify those configurations that are not using antZip format.
24.4.1.2. Build Naming and Locating
These properties allow you to control the working directories and names for the build output:
buildDirectory The absolute filesystem path where the build is executed. All build input is
downloaded to this location, and all compilation and composition are done under this
directory. You should keep this path reasonably short to avoid exceeding filesystem length
limits.
buildType An arbitrary string used to name the build output and identify the type of build.
For example, organizations often have nightly ("N") builds, builds in a maintenance stream
("M"), integration ("I") builds, etc. You can set up whatever structure you like. There is no
need to limit this value to a single character.
buildId The main part of the build output name. By default, the buildId is used in
composing the name of the output archives. Typically, the id conveys some semantics,
such as "TestBuild" or "CustomerX", or a full date stamp, such as 20050622.
buildLabel The actual name used. The buildLabel is typically a composition of buildType
and buildId. It is used in the naming of the output directories.
24.4.1.3. Base Identification and Locating
Most of the time you are building a set of plug-ins that sits on top of some base. Think of the
base as the target for your workspaceit is all the plug-ins and features that you are not
developing yourself. This may simply be the Eclipse RCP download, or it may be a whole
product suite if you are a value-add developer. The properties here allow you to set where the
base is, what's inside, and how to get it if it is not present:
base The location of the product on which the build is based. This is used to determine if
the base needs to be installed. If the directory exists, its contents are assumed to be
complete. If it does not exist, the getBaseComponents target in customTargets.xml is run to
install the base components in this location. In the example, we set the base to be the
target you have been using for all your other development.
baseLocation The location of the actual Eclipse install against which the plug-ins being
built are to be compiled. This is the logical equivalent of the target used during
developmentall the plug-ins and features come from elsewhere. Note that this can be a full
Eclipse install using link directories, etc. This is specified separately from base because
different products have different internal structures. For example, the standard Eclipse
downloads include an eclipse directory in their structure. In these cases, the baseLocation
is ${base}/eclipse.
baseos, basews, basearch The os, ws, and arch values for the base set of Eclipse
components in the install. Eclipse installations may support many platform configurations,
so these settings are used to clarify the set of base plug-ins, fragments, and features to
use. If there are several configurations in your base, pick one and assign the properties
accordingly.
skipBase A marker property that is set indicates that fetching the base should be skipped.
24.4.1.4. CVS Access Control
The build process can automatically check out the source for the build from CVS. The location of
the source is dictated by map files, which can themselves be checked out from CVS. The
following properties let you bootstrap that process by setting basic locations and CVS tags to
use:
mapsRepo The CVS repository that contains the map files needed for the build.
mapsRoot The path in the CVS mapsRepo to the map files for the build.
mapsCheckoutTag The CVS tag is used to check out the map files. The map files, in turn,
control the CVS tags used for checking out the plug-in and feature projects.
skipMaps A marker property that, if set, indicates that the map files are local and should
not be checked out.
fetchTag A property used to override the CVS tags defined in the map files. For example,
setting it to "HEAD" is useful for doing nightly builds.
skipFetch A marker property that, if set, indicates that the source for the build is local and
should not be checked out.
24.4.1.5. Java Class Libraries and Compiler Control
Of course, the build is primarily concerned with compiling Java code. The properties here allow
you to define the compilation classpath as well as various flags passed to the Java compiler:
bootclasspath The boot classpath to use when compiling code. This should point to all the
classes that are expected to be on the boot classpath when the product being built is run.
The value is a semi-colon-separated (;) list of filesystem locations.
Managing Ant properties
PDE Build makes heavy use of Ant constructs, and in particular, Ant properties. The
properties listed here are treated as normal Ant properties, so ${variable}
substitution is supported. Also, values such as the bootclasspath are passed directly
to the associated Ant task.
The so-called marker properties are ones that are simply set or not set. The value is
irrelevant and not checked. For simplicity, we tend to show the value as "true," but
setting the value to "false" does not unset the property.
It is often convenient to use build.properties to set up defaults and then override
these values for a particular build by setting properties from the command line
using the -D<prop>=<value> VM arguments.
For more advanced settings, look at the Ant documentation at
http://ant.apache.org.
24.4.2. customTargets.xml
Now open the customTargets.xml. This file contains a number of Ant targetsyou only need to
look at a few. Here we walk through the points of interest and help you set up the build for the
Hyperbola feature for Windows and Linux GTK.
Tip
The Ant Editor greatly simplifies the editing of Ant scripts. Set up an association
between *.xml and the Ant Editor using Window > Preferences... > General >
Editors > File Associations before opening the Ant files.
24.4.2.1. allElements
The allElements target is used to enumerate the top-level features you want to build. Typically,
a single feature is listed here. All included plug-ins and features are recursively built, so there is
no need to list them individually. Configure the file to list org.eclipsercp.hyperbola.feature.
As an element of type feature as shown below:
hyperbola.builder/customTargets.xml
<target name="allElements">
<ant antfile="${genericTargets}" target="${target}">
<property name="type" value="feature"/>
<property name="id" value="org.eclipsercp.hyperbola.feature"/>
</ant>
</target>
24.4.2.2. assemble.<element.id>
For each element listed in the allElements target and for each configuration being built, you
have to describe how the final result is assembled. PDE Build generates the assembly scripts,
but you have to identify which script to run, as shown in the snippet below:
hyperbola.builder/customTargets.xml
<target
name="assemble.org.eclipsercp.hyperbola.feature.win32.win32.x86">
<ant antfile="${assembleScriptName}" dir="${buildDirectory}"/>
</target>
Since you are building one element ( org.eclipsercp.hyperbola.feature) for one platform
configuration (e.g., Windows), you need only one target. The target name must follow the
naming convention
assemble.<element.id>[.config.spec]
There is no need to change the values of the antFile or dir attributes.
24.5. Running the Builder
Now that the Hyperbola builder is defined, you are ready to build Hyperbola. For most of this
chapter, we assume that you are working locally and already have the Hyperbola code in your
workspace. For simplicity, we also assume that you are using your workspace target as the base
against which to compile. This means that the builder does not need to access a CVS server. To
set this up, make sure that build.properties has the following settings:
hyperbola.builder/build.properties
skipBase=true
base=c:/target
skipMaps=true
skipFetch=true
Because the plug-ins and features are not being checked out from CVS, you need to create the
build directory by hand. In the steps below, replace ${buildDirectory} with the value from the
build.properties (e.g., ~you/eclipse.build ).
1. Create ${buildDirectory}
2. Create ${buildDirectory}/plugins
3. Create ${buildDirectory}/features
4. Copy the org.eclipsercp.hyperbola and org.jivesoftware.smack projects from your
workspace to the plugins directory and the org.eclipsercp.hyperbola.feature project to
the features directory.
Note
Since the builder is not checking files out from CVS every time, step 4 must be
repeated any time the content of the projects changes.
You should end up with a layout that looks like Figure 24-4 (assuming your user.home directory
is ~you).
Figure 24-4. Build layout
Now run the builder. The easiest way is to use a command prompt and change directories to the
directory containing your builder. If you have been following along, the builder files (e.g.,
build.properties and customTargets.xml) are in the hyperbola.builder project in the
workspace. Once there, run PDE's "Build" application using the command line shown below. The
-consolelog argument ensures that you can see the output messages as the build progresses.
cd <filesystem location of builder>
c:\ide\eclipse\eclipse.exe
-application org.eclipse.pde.build.Build -consolelog
Of course, you can run directly from Eclipse by setting up a Java program launch configuration
for the command line shown below. Don't forget to set the working directory to the builder
directory.
java -cp c:\ide\eclipse\startup.jar org.eclipse.core.launcher.Main
-application org.eclipse.pde.build.Build -consolelog
The output produces the structure shown in Figure 24-5 in the
${buildDirectory}/${buildLabel} directory. So in our example, the output goes in
~you/eclipse.build/I.TestBuild. This directory contains one archive per configuration that was
built. Each archive is a complete, ready-to-run Hyperbola.
Figure 24-5. Build output
The compilelogs directory contains the build logs for each plug-in that was built. The various
assemble and package scripts in the build directory are left over from the build and can be
deleted. They are automatically deleted and regenerated each time the builder is run.
Debugging the build
Builds are notoriously hard to get right. Spelling mistakes, commented lines, and
typos all contribute to builders that just do not work. The Eclipse IDE includes
comprehensive support both for authoring Ant files and also for debugging Ant
scripts. There are a few quirks to setting this up for PDE Build, so the steps are
detailed here.
You must have the root build script in your workspace. The Hyperbola scripts are
already there, but you need the root script for PDE Build. Use the Import >
External Plug-ins and Fragments wizard to import the org.eclipse.pde.build
plug-in. In the wizard, set the Plug-in Location to your IDE location (e.g.,
c:\ide\eclipse) and choose Import As > Binary projects. Click Next, select the
org.eclipse.pde.build plug-in, and Add it to the list. Click Finish.
Now you have to set up a launch configuration to run PDE Build's build.xml, the
root of the build mechanism. Navigate to org.eclipse.pde.build/
scripts/build.xml and use the context menu's Debug As > Ant Build... to open
the Ant launch configuration dialog.
On the JRE page, select Run in the same JRE as the workspace. On the
Properties page, uncheck Use global properties... and use Add Property... to
add a property called builder, as shown below:
Click Debug and run the build. Everything should work as before. Now you can
open PDE's Ant scripts, such as build.xml or your customTargets.xml, and add
breakpoints by double-clicking in the left margin or using the Toggle Breakpoint
context menu entry. Debug the build again. When the breakpoint is hit, you can
inspect Ant properties and step over and into Ant statements.
24.6. Building Products
You might notice by looking at the build output that it does not include the Hyperbola launcher,
startup.jar, or other root files needed by standard Eclipse applications. This is because you
have been building just the Hyperbola feature, not the Hyperbola product. To set up an
automated product build, you have to create another feature that represents the product and
then build that feature.
Create the feature now and call it "hyperbola.product.feature". In the feature editor, add the
org.eclipsercp.hyperbola.feature to the Included Features list. Go to the Build page and
uncheck all items in the Binary Build and Source Build sections.
Tip
If nothing is included in the Binary Build section (i.e., you omit the bin.includes line
in the build.properties) of a feature, PDE reads and processes the feature, but does
not add the feature itself to the output.
We use this characteristic to good effect here, but in general, you can only use this
with top-level features. If the feature has dependents, they will not be satisfied at
runtime since the feature being built is not included in the output.
At the root of the hyperbola.product.feature project, create a folder called rootfiles. Copy the
Eclipse launcher (eclipse or eclipse.exe), startup.jar, from the target into the root files. You
also need a config.ini file, but so far, we've just been using the auto-generated filePDE Build
cannot generate the config.ini for you. The easiest way to get a config.ini is to export
Hyperbola from the product editor and then copy the one generated there into the rootfiles
directory. Your project should look like Figure 24-6.
Figure 24-6. Hyperbola product feature layout
Go to the build.properties page in the feature editor and enter the following line to have PDE
Build copy all the files found in rootfiles to the root of the build output:
root=rootfiles
Now set up the branding part of the product in terms the PDE Build can understand. In the
Hyperbola builder's build.properties file, ensure that the product property is set up as shown
next. This identifies the product configuration file that contains the branding information such as
the launcher name and icons.
hyperbola.builder/build.properties
product=/org.eclipsercp.hyperbola/hyperbola.product
The new hyperbola.product.feature feature is essentially wrapping the standard Hyperbola
feature so you have to update customTargets.xml to build hyperbola.product.feature instead.
Update both the allElements target and the assemble.<element.id> target. Below are the
updated versions of those targets:
hyperbola.builder/customTargets.xml
<target name="allElements">
<ant antfile="${genericTargets}" target="${target}">
<property name="type" value="feature"/>
<property name="id" value="hyperbola.product.feature"/>
</ant>
</target>
<target
name="assemble.hyperbola.product.feature.win32.win32.x86">
<ant antfile="${assembleScriptName}" dir="${buildDirectory}"/>
</target>
Copy the hyperbola.product.feature project to ${buildDirectory}/features and run the
builder. Notice that the output contains the specified root files and the launcher is now branded
as hyperbola.exe . Extract the launcher and check that its icons have been branded as well. If
you have a JRE on the system path, you should be able to run Hyperbola now by double-clicking
the launcher.
24.7. Cross-platform Building
In Chapter 9, you downloaded the delta pack and exported Hyperbola for several different
platforms. Since we are using the target from Part II as the Eclipse base to build against, all the
platform-specific fragments you need should already be available. If you did not follow along,
go to Chapter 9 and follow the instructions for getting the delta pack and install it into your
workspace target (i.e., ${base}).
Once the base Eclipse is installed, update the configs property in the builder's
build.properties to list both Windows and Linux GTK configurations, as shown here:
hyperbola.builder/build.properties
configs=win32,win32,x86 & linux,gtk,x86
In customTargets.xml, add an assemble.<element.id> target as well:
hyperbola.builder/customTargets.xml
<target
name="assemble.hyperbola.product.feature.linux.gtk.x86">
<ant antfile="${assembleScriptName}" dir="${buildDirectory}"/>
</target>
You can add more platforms simply by repeating these steps.
In the previous section, you added eclipse.exe and startup.jar as root files in
hyperbola.product.feature. Listing the root files this way does not scale well as there are quite
a few platforms and each has its own launcher and some of those have particular requirements.
Fortunately, the delta pack you installed earlier has the org.eclipse.platform.launchers
feature that contains all the launchers and root file statements to place them correctly. All you
have to do is include the Launchers feature in hyperbola.product.feature.
Open an editor on hyperbola.product.feature and go to the Included Features page. Add the
org.eclipse.platform.launchers feature to the list of Included Features, as shown in Figure
24-7.
Figure 24-7. Included features for Hyperbola product build
Now go to the hyperbola.product.feature/rootfiles folder and delete the launcher (e.g.,
eclipse.exe) and startup.jarthese are now supplied by the Launchers feature. You should
leave the rootfiles directory and entry in build.properties since you still need to supply the
config.ini.
Replace the hyperbola.product.feature in ${buildDirectory} with the new oneensure that the
copy does not include the launcher and startup.jar. Run the builder and notice that the output
directory contains two archives: one for Windows and one for Linux GTK. Look in the archives
and see that each has a different launcher and that they are still branded using the branding
defined in the Hyperbola product definition.
24.8. Tweaking the Build
Now that you've seen the basics of how to build an RCP application, here are some of the more
common and useful customizations. These are in no way mandatory, but are generally useful.
24.8.1. Managing the Base
Depending on your build machine, you may need to ensure that the base Eclipse product (i.e.,
your target) is installed. This can be automated using the getBaseComponents target in
customTargets.xml and various build properties. In the template, this target is called from the
postSetup targetjust after configuring the build, but before doing any work. In the runs above,
we used skipBase=true so getBaseComponents was not run. Commenting out the property allows
getBaseComponents to do its work.
The properties below configure the builder to automatically download the Eclipse 3.1 Platform
binary and unzip it on your C: drive as a base against which Hyperbola can be compiled. The
CD for the book includes a copy of the Eclipse Platform SDK, so you can update the URLs below
to point to the CD itself:
hyperbola.builder/build.properties
# skipBase=true
base=c:/target
eclipseURL=\
http://download.eclipse.org/downloads/drops/R-3.1-200506271435
eclipseBuildId=3.1
eclipseBaseURL=\
${eclipseURL}/eclipse-platform-${eclipseBuildId}-win32.zip
For Hyperbola, the Eclipse Platform download has all the plug-ins needed. Your product,
however, may require more elaborate base setup. For example, it might run the product's
native installer. You can customize getBaseComponents to do whatever you need.
24.8.2. Fetching from CVS
PDE Build can also be configured to check out the source for the plug-ins and features being
built from CVS. It uses the notion of map files to map feature and plug-in ids onto CVS
repository locations and tags. This allows you to identify org.eclipsercp.hyperbola.feature
and let PDE Build figure out that you really mean "check out a particular location in a particular
repository using a particular CVS tag." A map file contains a series of lines, each of which takes
the following form:
feature|fragment|[email protected]=\
cvs tag,:method:[email protected]/path/to/repo \
[,cvs password][,path/in/repository]
Typically, the CVS password is supplied via a .cvspass file, configured using the command line
CVS client itself rather than encoding it here. Of course, you can use anonymous access since all
you are doing is checking out files.
If the path in the repository (last element) is not specified, then PDE Build assumes that the
element to fetch is at the root of the repository and has the same name as the element. If your
artifacts are in a different location in the repository, you must specify the complete path from
the root of the repository to the directory containing the contents of the element (i.e., parent
directory of the feature.xml or plugin.xml, etc.). Note that this path must not start with a "/".
In your ${buildDirectory}, create a maps directory, and in that directory, create a
hyperbola.map file that contains the following entries. Be sure to replace the repository
information and the tag. You can use "HEAD" for the tag if you only ever want to build from
HEAD.
hyperbola.map
[email protected]=
tag,:method:[email protected]/path/to/repo
[email protected]=tag,:method:[email protected]/path/to/repo
[email protected]=\
tag,:method:[email protected]/path/to/repo
Notice that there is an entry for each of the plug-ins and features that needs to be built. All
other elements are assumed to be in the base Eclipse and do not need to be fetched or built.
Enable fetching by commenting out the skipFetch property in build.properties; leave
skipMaps=true for now. Delete the plugins and features directories from the
${buildDirectory} and run the build. Notice that the source listed in the map is checked out
and built.
Setting up CVS
CVS figures heavily in the overall PDE Build process. It is certainly possible to do
automated and reproducible builds using other repository technologies, but the
discussion here assumes that you are using CVS. Setting up a CVS server is beyond
the scope of this chapter, but a few pointers on CVS client setup might help you
along.
PDE Build requires a native CVS client. That is, it does not use the Eclipse CVS
client. You have to arrange to install the appropriate client.
If you are on a UNIX machine, chances are you have CVS already installedtype "cvs"
at the command line to check. If not, consult your OS installer instructions.
If you are on Windows, download CVS from http://cvshome.org. At the time of
writing, this site was a little confusing. Follow the "CVS downloads" at the top left of
the main page. Then select "binaries" and choose "Windows". Scroll through the list
of files on the right until you find the .zip file marked "Stable". Download and
extract it to an appropriate location (e.g., c:\Program Files\cvs).
Now you have to get CVS on the system path. Right-click on My Computer and
choose Properties > Advanced > Environment Variables. In the System
variables section, choose "Path" and Edit. Add ";c:\Program Files\cvs" at the end
and click OK. Note that you must restart any programs that will need the new path
settings (e.g., existing command prompts).
.cvspass setup
Since the builder is automated, you have to pre-configure any required passwords.
This may not be required if you are just using anonymous access. The passwords
can be embedded in the build configuration files, or you can store them in the CVS
.cvspass file. At the command line, type
cvs -d :method:[email protected]/path/to/repo login
CVS then prompts you for a password. The value entered is encrypted and stored in
the .cvspass file in your home directory. Future accesses to the specified repository
do not require that you enter a password.
24.8.3. Fetching the Maps
Sharing the map files in the CVS repository is the next logical step. There may be many map
files, for example, each controlled by different teams. The simplest structure is to have a
directory in the repository that holds the map files. Different teams then update their map files
and the build automatically picks up their changes.
During the build process, the getMapFiles target in customTargest.xml is called to download all
the map files. The behavior of getMapFiles is controlled by setting the various properties in
build.properties, as shown below:
hyperbola.builder/build.properties
# skipMaps=true
mapsRepo=:pserver:[email protected]/path/to/repo
mapsRoot=path/to/maps
mapsCheckoutTag=HEAD
If skipMaps is commented out, getMapFiles checks out the contents of ${mapsRoot} from
${mapsRepo} using ${mapCheckoutTag} and puts it into a maps area in ${buildDirectory}.
Set this up in the build.properties, check your map files into a repository, and delete the
entire contents of ${buildDirectory}. Now, run the builder and watch that first the maps are
checked out, then the features and plug-ins. Then, the build should continue as normal.
24.8.4. Auto-substitution of Version Numbers
Deployed features are full of version numbersincluded plug-ins and features are all identified by
precise versions. Listing and managing these specific version numbers at development time is
challenging, to say the least. If the version of a plug-in changes, then all referencing features
have to be updated. This is cumbersome and error-prone.
To simplify the process, PDE includes support for automatically substituting version numbers
during the build. You saw this in Chapter 14, where included plug-ins and features were
identified as version 0.0.0. The use of 0.0.0 tells PDE build to substitute the version number of
the plug-in or feature used in the build. This eliminates the need to change the containing
feature definition during development and ensures the deployed version numbers are always
correct. This is the default behavior of PDE.
You can lock in version numbers by setting them explicitly in the feature editor. For example, on
the Plug-ins page of the feature editor, select Versions... in the Plug-ins and Fragments
section. There you can select various policies for managing the version numbers. The options
specified in the dialog apply to all plug-ins and fragments. If you want to lock down some plugin numbers but leave some to be assigned at build time, you have to use the feature.xml page
and edit the file directly.
24.8.5. Qualifying Version Numbers
It is often handy to have output version numbers qualified by a build timestamp or other
information. PDE Build supports a mechanism for optionally qualifying select plug-in and feature
version numbers during the build process.
Open the Hyperbola plug-in's editor and on the Overview page, set the plug-in Version field to
"1.0.0.qualifier". Do the same for the Hyperbola feature. During the build process, the
"qualifier" segment of the version is replaced with a user-selected value.
By default, the qualifier is derived from the context of the build. For example, if the build is
based on checking out particular CVS tags using the map files described earlier, the qualifier for
each plug-in or feature is the CVS tag used to check out the source for the plug-in. This way,
the plug-in's full version number is based on its source.
If the build is not based on CVS checkouts or is using the CVS HEAD tags, for example, nightly
builds, the qualifier for the version is the time at which the plug-in or feature was built.
You can force the value of the qualifier to be uniform across the build by setting the
forceContextQualifier property in the builder's build.properties, as shown below. You should
take care to use qualifier strings that are valid for file and folder names and adhere to the
Eclipse version numbering rules.
hyperbola.builder/build.properties
forceContextQualifier=someQualifierString
It is also possible to control the qualification of plug-ins and features on an individual basis by
setting the qualifier property in the relevant build.properties, as shown below:
org.eclipsercp.hyperbola/build.properties
qualifier=<arbitrary string value here>
24.8.6. Controlling the Output Names
The build mechanism generates output archives following this pattern:
<element.id>-<buildId>-<config>.zip
for example, org.eclipsercp.hyperbola.feature-TestBuild-linux.gtk.x86. zip. This is
informative, but may be too detailed for your needs. You can control the output archive name
by tweaking the customTargets.xml's assemble.<element.id> targets. For example, the
following snippet shows the change to make the Windows archive file called "Hyperbola for
Windows.zip":
<target
name="assemble.org.eclipsercp.hyperbola.feature.win32.win32.x86">
<ant antfile="${assembleScriptName}" dir="${buildDirectory}">
<property
name="archiveName"
value="Hyperbola for Windows.zip"/>
</ant>
</target>
Note
This is an example of a more general approachproperty setting. In Ant, the outermost
setting of a property takes precedence. So even though the generated assemble script
sets the archiveName property, if the property is already set, the new value is ignored.
This allows scripts to assign default values that can be overridden by someone who
has more context.
24.9. Summary
Here we covered the basics and got you started creating automated builds of Hyperbola.
Release engineering is typically much more involved than you expect. There are always special
cases to consider, the source control tooling to contend with, determining how to collect and
process all the build output, and deciding then how to publish the builds. There are many Javabased build frameworks that can help with these other tasks, and since PDE build is Ant-based,
it can be integrated into an existing build process. You might also consider looking at the
org.eclipse.releng project. It contains the infrastructure for building and distributing the
Eclipse SDK itself.
Chapter 25. The Last Mile
We are now down to what telephone companies call "the last mile"actually delivering the
product to the end-users. There are, of course, many different ways that you can deliver
Hyperbola and other Eclipse-based products. Here we cover some of these options and detail
other issues related to installing and configuring Eclipse-based products on the user's machine.
In particular, we cover:
delivery techniques such as archives, native installers, update sites, and JNLP
initializing an install before first use
delivering pre-configured installs
setting up shared installs
25.1. Archives
The simplest and most widely supported delivery mechanism is using standard zip and tar
archive files. As you have seen, the Eclipse tooling directly supports the creation of these
archives from the workspace using either File > Export... > Eclipse product, or File >
Export... > Deployable plug-ins and fragments, or by using the automated build setup
detailed in Chapter 24, "Building Hyperbola." To these archives, you can add the JRE and any
additional files needed for the product.
Note
Including the JRE in your product distribution is a mixed blessing. On the one hand, it
simplifies the install and reduces the chance of problems on the user's machine. On
the other hand, it greatly increases the size of the distribution and its footprint on
disk.
You should weigh the installation scenarios and potential legal requirements carefully
and determine what is best for your product. Of course, if you code your application to
be independent of the JRE, then you have more options.
The archives can be delivered over the Internet or on direct-read media. End-users unpack the
archive in some location on disk and run the provided executable. This is suitable for simple
systems and reasonably knowledgeable users, but has the following drawbacks:
Not all OSs have the required archive reading software installed by default.
The OS cannot manage the install since it is just a set of files unpacked on disk. This
makes uninstallation a manual process.
There are no scripting capabilities, so setting up or querying for system settings and
configuring the product during the install is cumbersome.
25.2. Native Installers
A common approach to installing end-user applications is through a native installer such as
InstallShield, NSIS, RPM, and such. The great thing about Eclipse is that it is just a set of files
written to a location on the target machineno special registry keys or other hooks are required.
As such, you can use any of these installers to deliver your Eclipse-based applications.
All of the installers function by delivering a set of files to the user's machine. They vary in how
the files are delivered and their mechanisms, if any, for querying the user as to what he wants
installed and configuring the installed files by running scripts.
Native installers let users install applications such that they are tightly integrated with the
underlying OS. So, if your Windows version needs to create registry keys or hook system
menus, a native installer can help. Similarly, on Linux installs, RPMs can be registered in the
RPM database.
Due to the wide variation in setup and capabilities, a native installer tutorial is beyond the
scope of this bookyou should choose the technologies that best suit your environment and meet
the needs of your users.
25.3. Java Web Start (JNLP)
Java Web Start, also known as JNLP, is a mechanism whereby applications are stored on a Web
server and exposed to users as links on a Web page. When the user clicks on a link, his browser
uses its JNLP content type handler to read the JNLP manifest and download, install, and launch
the described Java resources.
25.3.1. How Java Web Start Works
Without turning this into a Java Web Start tutorial, the following is a brief introduction to the
technology and its use with Eclipse.
A Web Start application is described by one or more .jnlp files. These are XML manifests that
describe the application itself (e.g., its name, location, description) and the set of JARs that
make up the application. JNLP manifest files can also include, or be extended by, other JNLP
manifest files. Below is the top-level Hyperbola JNLP manifest file with the interesting parts
highlighted:
hyperbola.jnlp.feature/rootfiles/hyperbola.jnlp
<?xml version="1.0" encoding="UTF-8"?>
<jnlp
spec="1.0+"
codebase="http://eclipsercp.org/hyperbola"
href="hyperbola.jnlp">
<information>
<title>Hyperbola Chat Client</title>
<vendor>eclipsercp.org</vendor>
<homepage href="http://eclipsercp.org"/>
<description>Hyperbola Chat Client</description>
<icon kind="splash" href="splash.gif"/>
<offline-allowed/>
</information>
<security>
<all-permissions/>
</security>
<application-desc
main-class="org.eclipse.core.launcher.WebStartMain">
<argument>-nosplash</argument>
</application-desc>
<resources>
<j2se version="1.4+" />
<jar href="startup.jar"/>
<extension
name="Hyperbola Chat Feature"
href="features/org.eclipsercp.hyperbola.feature_1.0.0.jnlp"/>
<property
name="osgi.instance.area"
value="@user.home/Application Data/hyperbola"/>
<property
name="osgi.configuration.area"
value="@user.home/Application Data/hyperbola"/>
<property
name="eclipse.product"
value="org.eclipsercp.hyperbola.product"/>
</resources>
</jnlp>
This file and the referenced resources are placed on a Web server and the manifest file is linked
to from any number of Web pages. When the user clicks the link, the browser sees that it is a
JNLP manifest file and invokes the Java Web Start infrastructure to handle the content. This is
much like the way browsers handle Flash or PDF content. Depending on the browser
configuration, the Web Start support and JRE may be dynamically downloaded and installed
into the browser on first use.
After downloading and caching the JARs and extensions described in the JNLP manifest file, the
Web Start handler starts Java and the requested application. While this process is kicked off via
your browser, the resultant application is not run in the browser. Once a Java Web Start
application is installed, the JARs and manifests need not be downloaded again. The second time
the link is selected, the cached files are used. In some scenarios, the application can be
configured to run directly from the user's desktop.
An additional point of interest is the startup.jar file included in the manifest. This is the
standard Eclipse startup.jar. Notice that the <application-desc> tag identifies the
WebStartMain rather than the standard Eclipse Main class as the entry point.
Top-level JNLP manifest files essentially play the role of hyperbola.exe , hyperbola.ini , and the
config.ini in standard Eclipse deployments. They define command line arguments, some VM
arguments, the splash screen, and set various system properties.
25.3.2. Hyperbola and Java Web Start
To illustrate how Web Start works in practice, let's set up Hyperbola to run via Web Start. Look
back at the JNLP manifest and notice the <extension> tag. The JNLP manifest file listed there
extends the current file. The snippet below shows the key elements of the extension file. Notice
that it lists some plug-in JARs and it too has an extensionthe structure is recursive.
org.eclipsercp.hyperbola.feature_1.0.0.jnlp
<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+" codebase="http://eclipsercp.org/hyperbola">
<information>
<title>Hyperbola</title>
<vendor>eclipsercp.org</vendor>
</information>
<resources>
<extension name="RCP" href="features/org.eclipse.rcp_3.1.0.jnlp"/>
<jar href="plugins/org.eclipsercp.hyperbola_1.0.0.jar"/>
<jar href="plugins/org.jivesoftware.smack_1.5.0.jar"/>
...
</jnlp>
This structure looks remarkably like Eclipse featuressome amount of identification and location
information followed by a list of included plug-ins and nested lists. In general, the structure of
JNLP manifest files is completely under your control, however, Eclipse maps features to JNLP
manifest files as a matter of convenience and to enable tooling reuse.
For example, PDE takes advantage of this correlation and allows you to augment update sites
with JNLP information as the features are built. This is convenient since update sites are already
on the Web and already have the plug-ins needed. This approach has the added benefit that
you do not have to change mindsets or build two parallel configurations. The tooling does not,
however, generate the top-level JNLP manifest file described aboveyou have to do that.
To build the JNLP-enabled update site, first create a feature to describe the Web Start version of
Hyperbola. Follow the same basic steps as in Chapter 24 when you created
hyperbola.product.feature. This time, name the feature "hyperbola.jnlp.feature". Once
created, add org.eclipsercp.hyperbola.feature to the Included Features list, and on the
build.properties page, remove the bin.includes line altogether. Add in a root declaration, as
shown below. It should be the only line in the file.
root=rootfiles
In the case of building an update site, declared root files get copied to the root of the site. In
the Web Start case, we want startup.jar and the top-level JNLP manifest file to be at the root.
Create a rootfiles directory at the root of the hyperbola.jnlp.feature project and copy in
startup.jar from your target. Use File > New > File to create a text file called hyperbola.jnlp
in the rootfiles directory. This is the top-level JNLP manifest file. Fill it in with the content
shown above. For simplicity, set the codebase attribute to file:/c:/site you can change that
later when you deploy to a real site.
When you are done, the hyperbola.jnlp.feature project should look like Figure 25-1. As usual,
you can get a pre-configured setup from the sample code for this chapter.
Figure 25-1. Hyperbola JNLP feature
25.3.3. JAR Signing
Eclipse must run with all-permissions since it creates classloaders, reads and writes files, and
performs other protected operations. To run a Java Web Start application with all-permissions,
all JARs must be signed. Signing makes the JARs a bit bigger, but otherwise does not affect
their normal use. The PDE Export wizard allows you to sign JARs as they are exported or added
to a build output.
Before you can sign a JAR, you must have a keystore set up. To do this for production scenarios,
you should acquire the appropriate certificates from a trusted certificate authority. If you use
your own self-signed key, your users will get warnings that the JAR signatures cannot be
authenticated.
For testing, you can set up a simple keystore using the keytool program included with typical
JDKs, as shown below. For more options, see the keytool documentation.
keytool genkey alias <alias> keypass <password>
Note
Eclipse uses the jarsigner application typically found in the bin directory of a JDK. As
such, you must be running with a JDK or have jarsigner on the system's program
path to sign JARs.
Once you've set up the keystore, sign the startup.jar you added to the
hyperbola.jnlp.feature project using the command below. The <alias> is an arbitrary string of
your choosing.
jarsigner.exe <location of startup.jar> <alias>
25.3.4. Exporting for Java Web Start
Now you are ready to export Hyperbola for Web Start. Open the File > Export... >
Deployable features wizard and select the hyperbola.jnlp.feature. Ensure that the Package
features and plug-ins as individual JAR archives checkbox is checked, and for this
example, set the export location to c:\site. Click Next until you get to the Advanced Options
page shown in Figure 25-2.
Figure 25-2. Hyperbola Web Start export
Fill in your JAR signing information and ensure that the Site URL is the same as you put in your
top-level hyperbola.jnlp manifest file. Note that by default, keytool creates the keystore in
your home directory in a file called .keystore.
Click Finish and PDE builds and signs the features and plug-ins included in Hyperbola. Note
that signing JARs is costly, so expect your exports to take a little longer. The resulting update
site is shown in Figure 25-3. Notice that the structure looks like a normal update site but with
some .jnlp files added in. The site.xml is left over from Chapter 14, "Adding Update."
Figure 25-3. Web Start-provisioned update site
Tip
Web Start mechanisms only handle JARs. It is recommended that all plug-ins being
used in Web Start deployments be structured to have "." on their classpath and have
all their code at the root of the JAR. That is, they should be structured as normal JAR'd
plug-ins.
Test the Web Start version of Hyperbola by double-clicking on the hyperbola.jnlp manifest file.
Depending on your OS, you may have to launch it via a Web browser. You should be able to just
enter the following URL:
file:/c:/site/hyperbola.jnlp
or, if that doesn't work, create the simple HTML file shown below and click on the link:
<html>
<a href="file:/c:/site/hyperbola.jnlp">Start Hyperbola</a>
</html>
25.3.5. Building JNLP Manifests
The PDE Build automated build infrastructure can also be configured to generate JNLP manifest
files. Go back to the setup you had in Chapter 24 and add the following to the builder's
build.properties and build the hyperbola.jnlp.feature. You might want to set the output
format to be -folder to build the required update site directly.
hyperbola.builder/build.properties
jnlp.codebase=file:/c:/site
jnlp.j2se=1.4+
sign.alias=<alias>
sign.keystore=<keystore location>
sign.storepass=<password>
Note
If you do not want to code your password in the properties file, it can be supplied on
the command line using a -D VM argument (e.g., -Dsign.storepass=<password>).
25.3.6. Java Web Start and Update
Each time the user starts his Web Start application, the Web Start infrastructure checks to see if
the application has been changed. If it notices updated manifests or JARs, it replaces the local
copies with the newer versionsWeb Start manages the install. As such, there is no particular
need to use the Update technology in Web Start configurations. WebStartMain automatically
discovers and installs downloaded plug-ins based on the classpath used to start the Java Web
Start JVM.
It is possible, however, to use the Eclipse Update mechanism in concert with Web Start. Once
the Web Start-based application is running (and assuming it includes the Update
infrastructure), users can use Update to add functions, as outlined in Chapter 14. Newly
acquired features and plug-ins are downloaded to a local site of their choosing and installed into
Eclipse. These features and plug-ins are managed on the client by UpdateWeb Start does not
install new versions for you even if they are added to the update/Web Start download site.
25.4. Update Sites
Eclipse update sites can be used to deliver product function. The catch is getting started. To use
an update site, you need Eclipse and the Update function installed and running. The update site
then allows you to add to that installed base using the approach outlined in Chapter 14.
Using that function, it is reasonably straightforward to write an installer application that offers
to add more and more function to an existing application. The installer and JRE are delivered
using one of the techniques outlined above.
25.5. Initializing the Install
Eclipse manages a number of caches to improve performance. Typically, these are written to the
configuration area of the application. The caches are simply read during subsequent
sessionsthey are only rewritten if something in the install changes. That is, on its second
invocation, Eclipse generally does not write anything to disk. This is a great performance
optimization for subsequent invocations, but it both requires some writable storage and
somewhat more time for the first run.
To alleviate this, you can initialize an install after laying down all of its files on disk. Initializing
tells Eclipse to build any structures and caches that it needs and results in the user's first run of
the product being faster. The standard initialization process starts Eclipse normally, but just
before running product code, it exits. To initialize Hyperbola, run with the -initialize option as
follows:
hyperbola initialize
To reinitialize Hyperbola, execute the line below. This discards any previously computed caches
and then initializes the install as normal.
hyperbola clean initialize
25.6. Pre-initialized Configurations
Initializing an install is also useful when setting up Hyperbola to run off a read-only media such
as a CD. In this scenario, you want to do the first run of Hyperbola before burning the CD and
include all the pre-initialized caches on the CD. Depending on the application, this can eliminate
the need for writable media altogether.
The trick here is to initialize everything needed to run Hyperbola. The built-in initialization you
get with -initialize TReats only the internal Eclipse caches. To initialize Hyperbola, you can
easily write a special application that initializes the Hyperbola state and then run the application
when Hyperbola is installed, as shown below:
hyperbola
application
org.eclipsercp.hyperbola.initializer
Data required by other plug-ins is more difficult. The most common problem is files that need to
be extracted from plug-in JARs before they can be used. For example, native libraries, HTML
files, and the like all need to be extracted from their containing plug-in JARs before being used.
The infrastructure around these files does the extraction on-demand at runtime. For example,
when a library is opened, the classloader triggers the extraction of the library and then opens
the extracted copy. Similarly, the About information is extracted before being viewed with a
Web browser. To fully pre-initialize Hyperbola then, you have to touch all of these files and
ensure that they are extracted and cached so you can burn them onto the CD.
Fortunately, all you have to do is call Platform.asLocalURL(URL) for each file or directory that
needs to be extracted. The hard part then is identifying all of these files. Eclipse 3.1 does not
have direct support for this, but the Eclipse Platform Core team does supply an initializer
application on their home page. See the Core page on http://eclipse.org/platform. See also
Eclipse Bug #90535 for more details.
25.7. Multi-user Install Scenarios
In UNIX and enterprise environments, it is often useful to share one install of Hyperbola
between many users. Rather than having a copy of Hyperbola on each end-user machine,
Hyperbola is installed on a central server and simply instantiated by each user.
Some of the advantages of this approach are:
Reduced management cost since there is only one install to manage.
Increased control over what plug-ins people are running since the configuration can be
protected by filesystem permissions.
Better integration into the native environment. For example, shared application installs are
the norm in the UNIX world.
Some issues to consider are:
How to update shared configurations while users are using them.
How to manage data stored by an instance in the configuration area.
Additional complexity in the initial setup.
Note
This section manipulates the location of the Eclipse configuration and instance areas to
get various sharing and separation effects. Please refer to Chapter 26, "OSGi
Essentials," for details on how to use and control these areas.
By default, Eclipse expects to have or create a configuration area inside the install directory
(e.g., /Hyperbola/configuration), as shown on the left of Figure 25-4. The right of the figure
shows Hyperbola installed on another machine, but with the configuration location in
~me/hyperbola . Notice that in this case, the installation is untouched; all generated
configuration data is put into the user's configuration area.
Figure 25-4. Independent installs
[View full size image]
The location of the configuration area is controlled using the -configuration command line
argument or by setting the osgi.configuration.area system property. The machine on the left
is using the default configuration location; the one on the right is running with
hyperbola configuration ~me/hyperbola
Hyperbola does not need workspace in the classic Eclipse sense, but it does use an instance area
to store information such as the window layout. The instance location should be set in the
relevant config.ini. Section 26.11, "Data Areas," discusses this in detail, but in this example,
the config.ini might contain the following:
[email protected]/hyperbola
25.7.1. Shared Installs
The independent install approach works well for standalone installs on individual machines
where users have global privileges. In shared scenarios, however, you typically want to protect
the install by marking it read-only.
When Eclipse runs in a read-only install, it notices that the default configuration location (i.e.,
the install area) is not writable and looks for an alternative. Unless you use -configuration, the
configuration area is placed under the user's home directory, as shown in Figure 25-5.
Figure 25-5. Shared install
[View full size image]
Here, Hyperbola is installed on the server and locked. Various clients access the installed files
and run. Since the server does not have a configuration, each client does its own initialization
and builds its own configuration. Notice that this essentially splits the setup on the right of
Figure 25-4. The server install is the same as the Hyperbola directory and the clients are the
same as the ~me directory. The configurations on the clients are distinct and must be updated
independently. In effect, the installed files are being shared, but not the configurations.
The location of the configuration area in this scenario is controlled by the shared config.ini on
the server. It includes the following line to set the configuration location, as shown in the
figures:
[email protected]/hyperbola
25.7.2. Shared Configurations
To get more sharing, you can initialize the server install using one of the techniques discussed
in Sections 25.5 and 25.6 and then have clients share the configuration information as well.
Figure 25-6 shows a shared install that has been fully initialized. As such, clients do not need
any configuration or application information; it is all shared.
Figure 25-6. Shared configuration
[View full size image]
Depending on the level of pre-initialization, there may be some configuration information on the
clients. In such cases, we say that the client's configuration is cascaded with the shared
configuration and that the client's configuration is parented by the shared configuration. To
support this approach, plug-ins that use the configuration area must know to look both locally
and in the parent configuration location using the code in the following snippet. The parent is
null if the configuration is not cascaded.
Location location = Platform.getConfigurationLocation();
Location parent = location.getParentLocation();
Under the shared configuration model, users invoking the shared launcher run the exact same
configuration. When the configuration needs to be updated, you only have to update the shared
configuration.
25.7.3. Multiple Configurations
The multiple configurations scenario is relevant to both single-machine installs and shared
installs/configurations. The shared version of the scenario is shown in Figure 25-7. Here, the
server contains some plug-in install sites (e.g., /Common/plugins) and several configuration
directories. Each configuration is, in effect, a shared configuration from above, but the
configurations themselves share a common set of installed plug-ins. The figure shows the clients
from the previous scenarios all using this one shared server.
Figure 25-7. Multiple (shared) configurations
[View full size image]
This highlights a somewhat hidden design feature of Eclipse. Configurations can be used to
subset a larger body of available plug-ins. Here, the plugins directory may well contain
thousands of plug-ins, but the Hyperbola configuration requires only 15 and is fully initialized.
The Doctor configuration, on the other hand, uses all available plug-ins and is not initializedall
doctors have their own configuration information. Finally, the Nurse configuration may use 138
of the shared plug-ins and is partially initialized, enabling single-point update. The sets of plugins for each configuration may overlap or be completely disjointed.
This approach is particularly useful when managing product suites or setting up, in this
example, doctors to run on Windows and nurses to run on UNIX.
The trick to setting this up is managing platform.xml files. So far, you have not seen or touched
the platform.xml, but it has always been there working for youlook for it in the configurations
pictured above.
You can think of platform.xml as an extension of the config.ini file. Typically, you do not craft
or ship a platform.xml; it is dynamically computed and written. When an Eclipse install is
started, the set of bundles listed on the osgi.bundles property of the config.ini is installed and
started. Typically, this list includes the org.eclipse.update.configurator plug-in, as discussed
in Chapter 14.
The configurator searches for and discovers available features and plug-ins and installs them
into Eclipse. It records the configuration it created in the platform.xml file. The snippet below
shows the platform.xml for the Hyperbola configuration in Figure 25-7:
platform.xml
<config transient="true" version="3.0">
<site url="platform:/base/"
enabled="true" policy="MANAGED-ONLY" updateable="false">
<feature id="org.eclipse.rcp" version="3.1.0"
url="features/org.eclipse.rcp_3.1.0/"/>
<feature id="org.eclipsercp.hyperbola" version="1.0.0"
url="features/org.eclipsercp.hyperbola_1.0.0/"/>
</site>
</config>
The markup identifies some number of <site> locations containing features and plug-ins. It then
lists a set of features from the site that should be installed. On subsequent runs, the
configurator confirms that the correct features and plug-ins are installed.
As the product definer or installer, you can create configurations of Eclipse plug-ins simply by
crafting your own platform.xml files and putting them in their own configuration locations.
Note
The format of platform.xml is not API in Eclipse 3.1, but it is documented in the
Eclipse Help and is quite stable. Since the platform.xml contains feature version
numbers, it is likely that you have to craft new platform.xml files anyway. So, if the
format does change, the syntax of existing files can be updated at that time.
To isolate users from the details of configurations, you should set up each configuration location
to have its own launcher and launcher .ini file, as shown in Figure 25-7. For example, the
hyperbola.ini contains the following directives:
hyperbola.ini
-vmargs
[email protected]/configuration
This directs Eclipse to use the current directory as the configuration area. The config.ini in
that location is set up as in the shared configuration case and includes the following in the
config.ini:
config.ini
[email protected]/hyperbola
[email protected]/configuration
Typically, the person defining a suite of products or installing several products together like this
is responsible for crafting the platform.xml files and the launcher information described here. If
you are writing a native installer, you should consider giving users the option to simply add
your product to an existing install and create a separate configuration, as described in this
section.
25.8. Summary
Deployment is a critical step in the overall process of making and delivering an RCP application
to your users. Eclipse installs are relatively simple structures of files and they can be delivered
using a wide variety of mechanisms from simple archives to native installers or network-based
installs such as Java Web Start. Enterprise users can set up shared installs that offer varying
amounts of sharing and flexibility. So, Eclipse not only helps you build your RCP products, but it
helps you deliver them too.
Each of the approaches described has benefits and drawbacks. These mostly revolve around the
issues of separating users and ease of managing configurations. As an RCP application writer,
you should allow for these installation scenarios and thus give your users the option of how to
install your product.
25.9. Pointers
The reference section of the standard Eclipse plug-in developer help (Help > Platform Plug-in
Developer Guide > Reference > Other reference information) contains details of the
system properties, file formats, and other structures discussed in this chapter.
Part V: Reference
There are several pieces of the RCP that do not fit neatly into a greater topic or compelling
use cases and scenarios. These pieces nonetheless are vital to a full understanding and use
of the Eclipse RCP. This last part of the book includes reference chapters covering the
Eclipse implementation and use of the OSGi framework specification and information about
useful RCP plug-ins found in the Eclipse platform.
Chapter 26. OSGi Essentials
In Chapter 2, "Eclipse RCP Concepts," we outlined the basic Eclipse concepts of plug-ins, the
Runtime, applications, products, and the extension registry. Further chapters provided
additional detail in the context of particular problems or scenarios related to Hyperbola, our
omnipresent chat client example. This chapter digs into the OSGi concepts and constructs that
underlie the Eclipse Runtime.
You should think of this chapter as reference material and use it as needed. We cover many
advanced topics and explain exactly what your application does from start to finishthe kind of
information you need when you have problems and are up late at night troubleshooting. Of
course, you are free to read through the chapter and pick up various background information
and helpful tips and tricks that can be applied everyday.
The material here is by no means a complete treatment of OSGi and its use in Eclipse; that
would take another book! It is, however, useful for people who are:
curious about the how plug-ins relate to OSGi constructs such as bundles
troubleshooting their application, for example, tracking down ClassNotFoundExceptions
designing a set of plug-ins and fragments
looking to understand more about how Eclipse starts, runs, and stops
It is worth pointing out here that the OSGi framework specification is just that, a specification
for a framework. The framework is intended to be implemented and run on a wide range of
platforms and environments. As such, it does not say anything about, for example, how bundles
are installed, how they are started, how they are laid out on disk, or even if they are laid out on
disk. It is up to implementations to define these characteristics.
The bulk of this chapter is devoted to mapping the OSGi specification onto the Eclipse use case.
Readers are encouraged to read the OSGi Framework Specification Release 4 from
http://osgi.org and treat this chapter as a guide to the Eclipse implementation and use of that
specification.
26.1. OSGi and the Eclipse Runtime
Eclipse is based on an implementation of the OSGi framework specification. The Eclipse Runtime
is a very thin veneer over the OSGi APIs that serves as a compatibility layer both for existing
code and coding practices and for existing terminology and documentation.
The most important aspect of this layering is in the area of plug-ins. Eclipse users, its
documentation and many books, and UIs all refer to plug-ins as the components that make up
Eclipse applications, but now, under the covers, everything is implemented in terms of OSGi
bundles. This is not as dramatic as it sounds because the OSGi notion of bundle is
synonymous with the Eclipse notion of plug-in. A statement from Chapter 2 bears
repeating here:
There are no fundamental or functional differences between plug-ins and bundles in
Eclipse. Both are mechanisms for grouping, delivering, and managing content. In fact, the
traditional Eclipse Plugin API class is just a thin, optional layer of convenience function on
top of OSGi bundles. To Eclipse, everything is a bundle. As such, we use the terms
interchangeably and walk around chanting, "A plug-in is a bundle. A bundle is a plug-in.
They are the same thing".
At the conceptual and structural levels, this mapping certainly holds, but what about at the code
level? The org.eclipse.core.runtime.Plugin class seems pivotal to the Eclipse model. Let's
look at this more closely by decomposing the roles of a traditional Eclipse Plugin class and see
how it fits with the OSGi structures:
Identity to others Plug-ins are passed around and their function accessed via the Plugin
object.
Identity to the system The system creates and manages Plugin objects that hold or
represent state for the system.
Lifecycle handler Plugin objects implement start(BundleContext) and
stop(BundleContext) methods to do initialization and cleanup.
Handy access point Plugin classes often have handy methods that give internal and
external access to plug-in behavior. For example, the ResourcesPlugin.getWorkspace()
returns the current workspace managed by the Resources plug-in.
By contrast, OSGi separates these roles into different objects, as described below. The methods
on Plugin and methods that take Plugin arguments are then implemented in terms of these
objects.
Bundle = Identity to others Other bundles can ask the system for a Bundle object, query
its state (e.g., started or stopped), look up files using getEntries(), and control it using
start() and stop(). Developers do not implement Bundlesthe OSGi framework supplies
and manages Bundle objects for you.
Do not confuse the Bundle object with any Plugin object you may choose to define. Plugin
classes are purely optional and the system-supplied Plugin class is there as a helper only.
The Bundle object, on the other hand, is mandatory and completely defined and managed
for you.
You can access the complete set of installed Bundles using various methods on Platform
and BundleContext .
BundleContext = Identity to the system At various points in time, bundles need to ask
the system to do something for them, for example, install another bundle or register a
service. Typically, the system needs to know the identity of the requesting bundle, for
example, to confirm permissions or attribute services. The BundleContext fills this role.
BundleContexts are created and managed by the system as an opaque token. You simply
pass it back or ask it questions when needed. This is much like ServletContext and other
container architectures.
BundleContexts are given to bundles when they are started, that is, when the
BundleActivator method start(BundleContext) is called. This is the sole means of
discovering the context. If the bundle code needs the context, its activator must cache the
value.
BundleActivator = Lifecycle handler Some bundles need to initialize data structures or
register listeners when they are started. Similarly, they need to clean up when they are
stopped. Implementing a BundleActivator allows you to hook these start and stop events
and do the required work.
The traditional Plugin class is in fact an implementation of the BundleActivator interface.
BundleActivators define start(BundleContext) and stop(BundleContext) methods as
replacements for the now deprecated Plugin methods startup() and shutdown().
Note
OSGi does not have explicit support for the role of "handy access point" as outlined
above. This role can be filled by any class.
Identity theft
Of the three OSGi objects outlined here, only your Bundle object is meant for others
to reference. That, in fact, is its role. Since your BundleContext is your identity to
the system, you do not really want to hand it out to others and allow them to
pretend to be you. Hold your context near and dear and be careful not to share it
via convenience methods or exposed fields.
Similarly, your BundleActivator controls your initialization state. Normally, it is
invoked solely by the system. If you give others access to your activator, they can
call start(BundleContext) and stop(BundleContext) directly and circumvent any
checks and management that the system does. If you extend Plugin and make the
class available to others, you should consider having a separate BundleActivator.
26.2. The Shape of Plug-ins
On disk, a plug-in can either be a directory of files or a JAR containing files. Figure 26-1 shows
the UI plug-in structured as a JAR, as you saw in Section 2.2, "Inside Plug-ins."
Figure 26-1. JAR'd plug-in layout
Figure 26-2, on the other hand, shows the org.eclipse.ui plug-in as a directory. Notice that
everything is the same, except the code is in ui.jar rather than in the org directory.
Figure 26-2. Plug-in disk layout
These two forms of plug-ins are equivalentthe Eclipse Runtime and tooling (e.g., PDE, Update,
etc.) manage both forms equally well. Below are some useful tips to help you decide which
format is best for your plug-ins:
Use directories
If the plug-in contains many files that must be directly on the native filesystem. Examples
of such files are shared libraries (e.g., DLLs), program executables, and JARs. Most Eclipse
facilities (e.g., classloaders, Intro, Help, About, etc.) transparently extract the required
files on-demand, so the main concern here is efficiency. Extracting doubles the disk
footprint and incurs a one-time cost. If the plug-in has many such files or they are large,
consider packaging the plug-in as a directory.
Use JARs
If the plug-in contains many small files that would otherwise fragment the disk.
If the plug-in contains highly compressible files.
If you want to sign the plug-ins and have that signature maintained after installation to
support install verification and running with security (e.g., for Web Start).
The Eclipse SDK is about 75% JAR'd plug-ins. Of those that are not JAR'd, more than half are
documentation, source, and other plug-ins that include large volumes of nested archives. The
rest are left as directories for various reasons, as mentioned above. So, in Eclipse 3.1,
deploying plug-ins as JARs is the norm rather than the exception.
While this is transparent to both the user and the developers coding to the API of the plug-in,
there are a few considerations to note for the developers of JAR'd plug-ins:
JAR'd plug-ins should always have a classpath of "." or no classpath specification (this
implies "."). A "." signifies that the JAR itself is the classpath entry since the JAR directly
contains the code.
It is technically possible to run a JAR'd plug-in that has nested JARs on the classpath. Such
nested JARs are automatically extracted and cached by the OSGi framework. As noted
earlier, this effectively duplicates the amount of disk space required for the plug-in. More
significantly, however, the tooling (both PDE and JDT) is unable to manage classpaths that
include nested JARs. The net result is that while your plug-in runs, it takes more space and
others cannot compile against it.
Similarly, Eclipse understands JAR'd plug-ins with code in packages that do not start at the
root of the JAR (e.g., /bin/org/eclipse/...). Again, the tooling is not set up for that
structure. In particular, all standard Java compilers recognize only package structures
directly at the root of a JAR. As such, developers are unable to code against JARs
structured in this way.
The PDE export operations automatically JAR plug-ins that have "." on the classpath and
create directory structures for those that do not.
26.3. Fragments
Sometimes it is not possible to package a plug-in as one unit. There are two common scenarios
where this occurs:
Platform-specific content Some plug-ins need different implementations on different
OSs or window systems. You could package the code for all platforms in the plug-in, but
this is bulky and cumbersome to manage when you want to add another platform.
Splitting the plug-in into one for common code and others for platform-specific code is
another possibility. This is problematic since implementation objects often need to access
one another's package-visible members. This is not possible across plug-in boundaries.
Locale-specific content Plug-ins often need locale-specific text messages, icons, and
other resources. Again, it is possible to package the required resources for all locales
together in the plug-in, but this is similarly cumbersome and wasteful. It would be better
to package locale content separately and deploy only what is needed.
Eclipse supports these use cases using fragments. Fragments are just like regular plug-ins
except their content is seamlessly merged at runtime with a host plug-in rather than being
standalone. A fragment's classpath elements are appended to its host's classpath and its
registry contributions are made under the host's id. You cannot express dependencies on
fragments, just their hosts.
Figure 26-3 shows examples of both platform- and locale-specific fragments. Both fragments
have a manifest file that identifies the fragment, its version, and its host id and version range.
The following is an example of the markup found in the Runtime's National Language (NL)
fragment:
org.eclipse.core.runtime.nl1/MANIFEST.MF
Bundle-SymbolicName: org.eclipse.core.runtime.nl1
Bundle-Version: 3.1.0
Fragment-Host:
org.eclipse.core.runtime;bundle-version="[3.1.0,4.0.0)"
Bundle-ClassPath: .
Figure 26-3. Plug-in fragments
[View full size image]
The next snippet is from the Windows SWT fragment's manifest file. Notice the highlighted
platform filter line. This is an Eclipse-specific header that identifies the set of environmental
conditions that must be met for this bundle to be resolved. In this case, the osgi.os, osgi.ws,
and osgi.arch system properties must match the given values. The syntax of the filter is that of
standard Lightweight Directory Access Protocol (LDAP) filters and is detailed in the OSGi
specification.
org.eclipse.swt.win32.win32.x86/MANIFEST.MF
Bundle-SymbolicName: org.eclipse.swt.win32.win32.x86; singleton:=true
Bundle-Version: 3.1.0
Fragment-Host: org.eclipse.swt; bundle-version="[3.0.0,4.0.0)"
Eclipse-PlatformFilter:
(& (osgi.ws=win32) (osgi.os=win32) (osgi.arch=x86))
In both cases, the fragments directly contain code or resources to be appended to the host's
classpath. Adding a fragment's classpath entries, or the fragment itself, to the host is a vital
characteristic of fragments. Fragments generally contain implementation detail for the host.
Whether it is code or messages, the contents need to be accessed as though they were part of
the host. The only way to do this is to put the fragment on the host's classloader. This gives
bidirectional access to the classes and resources as well as enables Java package-level visibility.
At runtime, the host and fragment behave as if they were a single plug-in.
Fragments are a powerful mechanism, but they are not for every use case. There are several
characteristics to consider when you are looking at using fragments:
Fragments are additive Fragments can only add to their host; they cannot override
content found in the host. For example, their classpath contributions are appended to
those of the host, so if the host has a class or resource, all others are ignored. Their files
and resources are similarly added to those of the host plug-in.
Fragments cannot be prerequisites Since they represent implementation detail, their
existence should be transparent to other plug-ins. As such, plug-ins cannot depend on
fragments.
Fragments are not intended to add API Since you cannot depend on a fragment, they
ought not expose additional API since plug-in writers are not able to express a dependency
on that API.
Fragments can add exports Normally, fragments are used to supply internal
implementation detail. In certain instances such as testing and monitoring fragments,
however, they may need to extend the set of packages exported by the host. They do this
using normal Export-Package syntax. To support development-time visibility, the host
plug-in should be marked with the header Eclipse-ExtensibleAPI: true. This tells PDE to
expose the additional fragment exports to plug-ins that depend on the host.
Figure 26-4 shows the OSGi console of a running Eclipse application after typing the short
status ("ss") command. Notice that org.eclipse.resources.win32 fragment (#12) is shown as
installed (and resolved) and bound to the Resources host plug-in (#13). Notice also that the
report for the Resources plug-in also lists plug-in #88 as an attached fragment. It is not shown
here, but it is actually the Core Tools Resource Spy fragment. This illustrates that you can have
multiple fragments attached to the same host.
Figure 26-4. Fragment resolution status
The OSGi console
It is quite curious that in this day and age of GUIs, a simple command line UI such
as the OSGi console should cause such a stir. When people first see that there is a
console under the covers, the geek in them comes out and they succumb to the
need to manually install, start, stop, and uninstall bundles.
This is good fun, but the console is actually quite powerful and useful for both
controlling and introspecting a running system. In addition to controlling bundles,
you can investigate specific bundles, diagnose problems with bundles not being
resolved, find various contributed services, and so on.
The console is not started by default. To get a console, start Eclipse using the console command line argument and look for the console's "osgi>" prompt in either
the shell you used to launch Eclipse or the new shell created if you launched Eclipse
from a desktop icon. Type "help" to get a complete list of available commands.
26.4. Version Numbering
The pair of a plug-in id and its version should uniquely identify the plug-in's content. Since the
plug-in id never changes, the version number must change whenever the content of the plug-in
changes. Plug-in version numbers are made up of four parts: major.minor.service.qualifier:
Major Differences in the major part indicate significant differences such that backwardcompatibility is not guaranteed.
Minor Changes in the minor part indicate that the newer version of the plug-in is
backward-compatible with the older version, but it includes additional functionality and/or
API.
Service The service part indicates the presence of bug fixes and minor implementation
(i.e., hidden) changes over previous versions.
Qualifier The qualifier is not interpreted and are compared using standard string
comparison.
Following these numbering semantics is important. As plug-ins come to depend on one another,
they need to know about changes in the compatibility contract as well as updating their
requirements. For example, the following plug-in declaration claims that the Hyperbola plug-in
works with any version of the Runtime plug-in. This is likely incorrect.
org.eclipsercp.hyperbola/MANIFEST.MF
Bundle-SymbolicName: org.eclipsercp.hyperbola
Bundle-Version: 1.0.0
Require-Bundle: org.eclipse.core.runtime
A more likely scenario is that Hyperbola is buying into the Runtime API at a specific minimum
level. For example:
org.eclipsercp.hyperbola/MANIFEST.MF
Bundle-SymbolicName: org.eclipsercp.hyperbola
Bundle-Version: 1.0.0
Require-Bundle:
org.eclipse.core.runtime;bundle-version="[3.1.0,4.0.0)"
In this case, Hyperbola is happy with any Runtime plug-in from 3.1 to 4.0 (not including 4.0).
This is the traditional Eclipse match="compatible" scenariothe default behavior. Here, the
Hyperbola plug-in is saying that it needs 3.1-level functionality.
On the other hand, the specification below is the same as the traditional Eclipse
match="perfect" and is too strictit prevents the Hyperbola plug-in from running on any version
of the Runtime other than 3.1.0. Unfortunately, that means that if users update the Runtime to
version 3.1.1 to fix a bug, Hyperbola no longer resolves and no longer works.
org.eclipsercp.hyperbola/MANIFEST.MF
Bundle-SymbolicName: org.eclipsercp.hyperbola
Bundle-Version: 1.0.0
Require-Bundle:
org.eclipse.core.runtime;bundle-version="[3.1.0,3.1.0]"
So the challenge here is to specify the version dependencies loose enough so that the plug-ins
work in various settings, but tight enough that the required API contracts are guaranteed. Of
course, this mechanism only works if plug-in producers update their plug-in's version number
according to the version semantics and thus give their consumers a chance to get it right.
26.5. Services
Services are a key element of the OSGi architecture. In short, the service mechanism is a way
for one bundle to register a service provider (i.e., an implementation of some Java type) and
have others discover and use that service. The services model is powerful and flexible.
On the surface, the functionality supplied by OSGi services and the Eclipse extension mechanism
appears to overlap somewhat. Indeed, both are mechanisms for facilitating and managing
interactions between components, but they are more complementary than overlapping.
The service mechanism requires a component that implements some interface to register its
implementation as publicly available. Interested components then acquire and use the
implementation as needed. This mechanism is quite effective for decoupling dynamic
components and implementation from specification.
By contrast, the extension mechanism is a means for some implementation to expose
extensibility. It results in private contract between the extension point definer and extension
contributor. For example, the UI renders views and allows components to contribute views for it
to render. View contributors contribute directly to the UI plug-in and do not expect to find their
view being used by others.
Further, extensions are declarative and can carry more than just code. View extensions, for
example, include the name of the view and the icons to use in addition to the name of the class
that implements the view. Similarly, documentation extensions carry no code at alljust
documentation archivesso there is no service implementation to register.
Services are not covered extensively in this book as Eclipse currently makes relatively little use
of them. This is not a statement against services, but rather a pragmatic realization that in both
Eclipse 3.0 and 3.1, there have been several major restructuring efforts (e.g., replacing the
entire Runtime) and restructuring to use services was just not practical.
Over time, we expect to see services used more and more as appropriate. You are encouraged
to grab the OSGi framework specification from http://osgi.org to see if services make sense in
your architecture. See also the BundleContext.getService*() methods and the ServiceTracker
utility class.
26.6. Singletons
In general, Eclipse is able to concurrently run multiple versions of the same plug-in. That is,
org.eclipsercp.hyperbola version 1.0 and 2.0 can both be installed and running at the same
time. This is part of the power of the Runtime's component model. Dependent plug-ins are
bound to particular versions of their prerequisites and see only the classes supplied by them.
There are cases, however, where there really should be only one version of a given plug-in in
the system. For example, SWT makes certain assumptions about its control over the display and
main thread. SWT cannot cohabitate with other SWTs. More generally, this occurs wherever one
plug-in expects to have exclusive access to a global resource, whether it be a thread, an OS
resource, a network port, or the extension registry namespace.
To address this, OSGi allows a bundle to be declared as a singleton. The bundle in the example
below is marked as a singleton. This tells OSGi to resolve at most one version of the bundle. All
other version constraints in the system are then matched against the chosen singleton version.
org.eclipse.core.runtime/MANIFEST.MF
Bundle-SymbolicName: org.eclipse.core.runtime;singleton:=true
Bundle-Version: 3.1.0
The most common reason to mark a plug-in as a singleton is that it declares extensions or
extension points. The extension registry namespace is a shared resource that is populated by
bundle ids. If we allowed multiple versions of the same bundle to contribute to the registry,
interconnections would be ambiguous.
By default, bundles that declare extensions or extension points but do not have a MANIFEST.MF
are marked as singletons. If you have a MANIFEST.MF, you should annotate it accordingly.
26.7. Bundle Lifecycle
Bundles go through a number of states in their lives. Understanding these helps you understand
when and why various things happen to your bundle and what you can do about it. Section
22.3.3, "Bundle Listeners," details how to monitor any events arising from bundles changing
state.
Deployed When a bundle is deployed, it is laid down on disk (e.g., extracted from an
archive) and is physically available. Note that bundles may be deployed to a server and
may never actually be present on the executing machine. This state is not formally
represented in the system, but the term deployed is used to talk about a bundle that could
become installed.
Installed An installed bundle is one that has been deployed and presented to the OSGi
framework as a candidate for execution. Installed bundles do not yet have a classloader,
and if the Eclipse Runtime is running, their registry contributions are not yet added to the
extension registry.
Resolved Resolved bundles have been installed and all of their prerequisites have been
satisfied by other bundles, which are also resolved. If the Eclipse Runtime is running, the
contributions of resolved bundles are added to the extension registry. Resolved bundles
may have a classloader and may be fully operational. As shown in Figure 26-5, there are
some bundles that can stay in the resolved state and never become started.
Figure 26-5. Example console output showing bundle state
Starting A resolved bundle that is transitioning to active state passes through starting
state. Bundles remain in the starting state while their activator's start(BundleContext)
method is executing.
Active An active bundle is one that has been resolved and whose start() method has
been successfully run. Active bundles have a classloader and are fully operational.
Stopping An active bundle that is transitioning back to resolved state passes through
stopping state. Bundles remain in the stopping state while their activator's
stop(BundleContext) method is executing.
Uninstalled An uninstalled bundle is a bundle that was previously in the installed state
but has since been uninstalled. Such bundles are still present in the system, but may
behave in unexpected ways.
Figure 26-6 shows the details of the state transitions for bundles. Notice that the deployed state
is not shown as it is outside the scope of Eclipse itself. Being deployed simply means that the
bundle is available to be installed.
Figure 26-6. Bundle state transitions
26.7.1. BundleActivator (Plugin Class)
In support of the starting and stopping lifecycle transitions, a bundle can supply an activator
class. As we mentioned earlier, the standard Plugin class implements the OSGi-defined
BundleActivator interface. Activators are instantiated by the OSGi framework and their
start(BundleContext) and stop(BundleContext) methods are called when a bundle is started or
stopped. Extending Plugin is the traditional Eclipse coding pattern, but you are free to
implement the BundleActivator interface directly. Eclipse's Plugin class is essentially a helper
that supplies some useful methods for logging, preference accessing, and so on. Use whichever
technique suits your needs.
Tip
The BundleActivator methods are called on whatever thread happens to be executing
at the time the bundle changed state. Your start(BundleContext) and
stop(BundleContext) methods should be coded accordingly.
Either way, if you define an activator class, you need to tell the framework about it. Use the
Class field in the General Information section on the plug-in editor's Overview page, as
shown in Figure 26-7.
Figure 26-7. Specifying the bundle activator class
26.7.2. The Downside of Activators
Having given details and examples of how to write and identify your activator or Plugin class,
we caution you against using them. Running code on activation is an overused capability. One
of the basic tenets of Eclipse is that laziness is good"Run no code before its time." In our
experience, the start(BundleContext) and stop(BundleContext) methods rarely need to be
implemented.
Bundles can be activated for many different reasons in many different contexts. We commonly
see bundles that load and initialize models, open and verify caches, and do all manner of other
heavyweight operations in their start(BundleContext) methods. The cost of this is added to the
startup time and footprint of your application and is not always justified.
We once found a bundle that was loading 11MB of code as a side effect of being activated. First
of all, that's a lot of code. More critically, however, there were several cases where activation
occurred as a result of some trivial processing of resource navigator decorators.
Decorators are those little annotations you see on base resource icons that indicate the resource
has a marker or is part of a repository, etc. The bundle in question supplied a very small class
to determine if a given project needed domain-specific decoration. The loading of this one class
triggered the activation of the bundle. The authors assumed that if the bundle was started, the
entire complex model it maintained would be needed. Of course, the decorator processing was
trivial, it did not require the model at all, and often determined that no decoration was
requiredall that work and space was completely wasted.
This is just one example of activator abuse. A better approach is to initialize your caches and
data structures as required with more precision.
Note
Startup time is a bit of a misnomer in a system such as Eclipse. What is really
important is the time taken to start individual bundles. The Eclipse Runtime itself
starts in milliseconds. The rest of the time is spent executing the
start(BundleContext) methods and other initialization code for the bundles in the
system.
Since bundles are only started as needed, the initial start time of your application may
be fast, but as the user progressively touches more function, more bundles are
activatedlengthy activations delay the user in her quest to use your application.
26.7.3. Uses for Activators
So if activators are bad, why do they exist? Well, they are actually valuable in certain scenarios:
Registering services or listeners As the code snippet below illustrates,
start(BundleContext) adds a listener and stop(BundleContext) removes it. This is wellformed, small, and quick:
public class Activator implements BundleActivator {
private BundleListener listener = null;
public void start(BundleContext context) {
listener = new BundleListener() { ... };
context.addBundleListener(listener);
}
public void stop(BundleContext context) {
if (listener != null)
context.removeBundleListener(listener);
listener = null;
}
}
Starting shared mechanisms The canonical example here is a Web container bundle.
When this bundle is started, it opens a socket and starts listening on a port. When it is
stopped, it closes all sockets and releases all resources. Even here, however, care should
be taken to do as little work as possible until it is neededthe server may never need to
service a request!
Cleaning up When a bundle is stopped, it should release shared resources it has
allocated. Sometimes this allocation occurs in the activator's start(BundleContext)
method as above, but it may also occur lazily as shown below. Cleanup should be done
proactively and immediately. That is, do not return from stop(BundleContext) until all
cleanup for your bundle is complete. Here, the ImageRegistry is created lazily. Note that
there is no startup overhead in this activator.
public class Activator implements BundleActivator {
private ImageRegistry registry = null;
public void stop(BundleContext context) {
if (registry != null) {
registry.dispose();
registry = null;
}
}
public ImageRegistry getRegistry() {
ImageRegistry result = registry;
if (result != null)
return result;
synchronized(this) {
registry = new ImageRegistry();
... populate registry ...
return registry;
}
}
}
26.8. Early Activation
By this time, you should get the idea that doing work when your bundle starts is a bad thing. It
may cause unnecessary work and delay others from getting the function they want. Even worse
is forcing your bundle to be started when Eclipse starts. This makes the end-user wait for the
system to come up.
There are, however, some scenarios where both evils are necessary. The example of a Web
container highlights this. Consider an application that inherently supplies external access via a
Web browser. The implementation includes a bundle that supplies a Web container. The
container's operation is dependent on it, opening a network port and servicing HTTP requests
received from clients. Since this is an inherent part of the application, it should happen
automatically and not depend on some user action to start the server.
There are two ways of doing this in Eclipse: the early activation extension point and the
osgi.bundles list. Both are detailed in the following sections.
26.8.1. Early Activation Extensions
The Eclipse UI plug-in defines the org.eclipse.ui.startup extension point. Contributed
extensions must supply a class that implements IStartup . You must use the UI's default
Workbench to manage the UI to use this mechanism. Once the Workbench is initialized and
running, the startup extensions are run. For each extension, the given class is loaded and
instantiated and earlyStartup() is called on the resultant object.
Note
It is tempting to make your Plugin or BundleActivator implement IStartup . Don't.
Both Plugin and BundleActivator are managed objects and should not be instantiated
by you or supplied as executable extensions. Failure to follow this rule confuses the
Runtime and leads to unpredictable behavior.
Extensions are run in random order and you cannot make any assumptions about the thread
used to run the extensions. In practice, they are run on a worker thread spawned specifically for
this purpose.
The UI also manages preferences to track which registered extensions to invoke. In the IDE,
these are visible on the General > Startup and Shutdown preferences page.
26.8.2. osgi.bundles
The osgi.bundles list is a comma-separated list of bundles that are automatically installed and
optionally started when Eclipse is run. It is maintained as a system property and typically
defined in the config.ini file of a configuration. The default entry for the Eclipse IDE is shown
below:
$ECLIPSE_HOME/configuration/config.ini
osgi.bundles=\
[email protected]:start,\
[email protected]:start
Each entry is of the following form:
<URL | simple bundle location>[@ [<startlevel>] [":start"]]
Simple bundle locations are interpreted as relative to the OSGi framework's parent directory.
URLs must be of the form platform:/base/ or file:. In general, the URLs may include a version
number (e.g., .../location_1.2.3 ). If a version is not specified, the system binds to the
location that matches exactly or to the versioned location with the latest version number. If a
version number is given, then only exact matches are considered.
26.8.3. Start Levels
In the example osgi.bundles property above, the start level value indicates the OSGi start level
at which the bundle should run. OSGi start levels are much like UNIX start levels. As the system
starts, its start level is increased and all bundles marked as started at a particular level are
started before those of the next level. So, for example, by the time bundles at level 4 are
started, all those needing to be started at level 3 have been started. If a level is not specified,
the framework uses the default start level determined by the value of the
osgi.bundles.defaultStartLevel system property (currently 4 in Eclipse). If the "start" tag is
added, then the bundle is marked as started after being installed.
You can inject your bundle into the startup sequence by controlling its start level. This allows
you to, for example, add login prompters before the application is run, control the bundles that
are installed, or do last-minute cleanup as the system shuts down.
Warning
This mechanism is for advanced use only. You really have to understand how the
system works before looking to manage start levels manually. Note also that the
current start level values are not API and are subject to change in future releases.
26.9. Auto-activation
A bundle can be manually activated and deactivated by calling the Bundle methods start() or
stop(), respectively. These methods are public OSGi API, but you should never need to call
them yourselfEclipse extends OSGi and automatically starts bundles when they are needed and
stops them on shutdown.
Auto-starting works by having the Runtime detect the first time a bundle is asked to supply a
class. Normally, a resolved bundle does not have a classloader; it is simply present in the
system and all its prerequisites are satisfied. When a resolved bundle is asked to supply a class,
the Runtime ensures that the bundle is activated.
When the classloader is created, the bundle's start() method is called. This, in turn, causes the
bundle's activator to be loaded and its start(BundleContext) method to be called. This is all
done before attempting to load the requested class. After a successful start() call, the
requested class is loaded and returned as normal.
The net effect is that you can always be sure that your bundle has been activated by the time its
code is running (except, of course, the code involved in evaluating the activator's
start(BundleContext)). This frees you from continually having to check. It also means that the
system as a whole can be lazier. There is no need for a central management agent or
complicated policy to determine when bundles should be startedthey are simply started as
needed.
It's worth highlighting some of the typical scenarios that do (and do not) cause activation:
Using IConfigurationElement.createExecutableExtension() does cause the bundle
supplying the specified class to be activated. Note the subtlety here. It is not the bundle
defining the extension, but rather the one defining the class specified in the extension.
Typically these are the same, but not always.
Calling Bundle.loadClass(String) does cause activation of the bundle that eventually
supplies the requested class. Again, note the subtlety. If, for example, bundle A asks
bundle B and B asks bundle C and C eventually loads and returns the class, bundle B is not
activated. B was simply a step along the way.
Loading a class from bundle A that depends on a class from bundle B does activate B.
Here, the notion of depends on is derived from the JVM specification. If loading and
verifying the class from A requires a class from B, B's class is loaded and B is activated.
This can occur if A's class extends B's or references B's in a method signature.
Accessing, traversing, or otherwise using a bundle's extensions does not cause activation.
Bundle activation is not transitive. That is, activating a bundle A that depends on another
bundle B does not in and of itself cause B to be activated. Of course, if classes are loaded
from B while activating A, B is activated.
OSGi and bundle auto-starting
The Eclipse auto-start mechanism is not a standard part of the OSGi bundle model.
In a conventional OSGi framework, bundles must be explicitly activated by calling
their Bundle start() method. The OSGi framework specification is silent on how or
when start() is calledtypically this is done by a central management agent or
simply by having the framework always start all bundles.
In Eclipse, the only viable point of management is the Runtime itself. For traditional
OSGi bundles (i.e., bundles with a MANIFEST.MF), the auto-start behavior must be
explicitly enabled by adding the following line to the MANIFEST.MFthis is done
automatically for traditional Eclipse plug-ins:
Eclipse-AutoStart: true
The name of this header is a bit of a misnomer. Setting the value to true declares
that the bundle should be started when needed. It does not cause the bundle to be
started proactively.
You can identify exceptions to the auto-start flag setting by adding an exceptions
attribute as follows:
Eclipse-AutoStart: true;
exceptions=<package list>
If auto-start is on, loading classes from exception packages does not cause
activation. If it is off, the exceptions do cause activation. This would have been
useful in the decorator scenario mentioned earlier. In that case, the decorator code
would be placed in a package of its own and that package listed as an exception.
26.10. Classloading
One of the things that sets Eclipse and OSGi apart from other systems is the modularity
mechanism. The core of this is the classloading strategy. The following is a deep dive into the
guts of Eclipse and OSGi classloading. This section is not for everyone, nor is it for the faint of
heart. It is included here for those poor lost souls who, for whatever reason, cannot seem to
find the classes they need or are finding classes they don't need.
Traditionally, Java developers put all their code on the classpath and forget about the real
dependencies in their systems. This does not make for modular systems. In Eclipse, each bundle
has its own classloader. This effectively partitions the class namespaces and enables API
boundary enforcement, bundle unloading, bundle activation lifecycles, and multiple versions of
the same classes being loaded concurrently.
By default, the bundle classloaders are parented by the standard Java boot classloader. It is
possible to change this globally using the osgi.parentClassloader property. The parent can be
one of the following: the standard extension classloader, the standard application classloader,
or the OSGi framework's classloader. The buddy classloading mechanism outlined in Chapter
20, "Integrating Code Libraries," makes this global setting obsolete.
26.10.1. Class Lookup Algorithm
Enumerated in pseudo-code below are the steps OSGi uses for deciding where to look for
classes. Here, we assume that a bundle is trying to load some class C in package P.
1. if P starts with "java."
return parent.loadClass(C)
2. if P is imported
return exporter.loadClass(C)
3. if P is exported by some required bundles
for each exporter
return exporter.loadClass(C) if found
4. if C is found locally
return C
5. if C is found in a fragment
return C
6. if P is dynamically imported
return exporter.loadClass(C);
7. if buddy loading is enabled for this bundle
return BuddyLoader.loadClass(c)
8. throw a ClassNotFoundException
The next few sections look more closely at how this algorithm works.
Note
Step 7 is not standard OSGi behavior. Eclipse adds this step to facilitate the bundling
of code libraries, as outlined in Chapter 20.
26.10.2. Declaring Imports and Exports
The basic premise of the OSGi component model is that all bundles explicitly declare the
packages they expose to others and the packages they require from others. Notice that the
algorithm outlined above does not "search" for class C; the system knows which bundles have
which packages so the exporter is simply looked up. This yields two main benefits:
By explicitly declaring bundle dependencies, bundles are easier to configure and creating
valid configurations is easier. For every import, there must be an export or the bundle
does not resolve.
After a bundle dependency graph has been resolved, the system knows exactly where to
look for any given package. This eliminates costly searching and greatly improves
classloading time for large systems.
Step 1 of the classloading algorithm ensures that all java.* packages are implicitly available to
all bundlesthey need not be explicitly imported. All other packages from the JRE must, however,
be explicitly imported. This implies that there is a matching exporter. The OSGi specification
states that the system bundle must export the additional packages from the JRE.
What is the "system bundle?"
The system bundle is the bundle that implements the OSGi framework. The OSGi
specification states its symbolic name as system.bundle . In Eclipse, it is also known
as org.eclipse.osgi.
To implement this, the Eclipse OSGi implementation maintains a set of profiles that lists the
standard API packages available in common JRE class libraries such as J2SE1.4, J2SE1.5, and
JCL Foundation. These profiles do not include implementation-specific packages such as
com.sun.*, and sun.*, as they are not standard and are not available in all JREs. The framework
automatically finds and uses the profile that matches the current JRE level. You can override
this and set your own profile using the osgi.java.profile property.
Note
By default, Eclipse 3.1 in fact always delegates to the parent classloader in Step 1.
This is done for backwards-compatibility and to enable certain performance
monitoring tools. In any event, to create well-specified, portable, and robust
components, you should ensure that your bundles correctly import or require all
packages they consume.
So, for example, a bundle using the org.xml.sax.Parser class must either import the
org.xml.sax package or specifically require the system.bundle . If it does not, the SAX classes
are hidden from the bundle. Hiding JRE classes can be useful, for example, if you want to use a
particular SAX parser supplied as part of your bundle.
The set of packages imported and exported for a bundle is controlled using the Imported
Packages section on the Dependencies page of the plug-in plug-in editor and the Exported
Packages section on the Runtime page in the plug-in editor, respectively.
26.10.3. Importing versus Requiring
There is a subtle but important distinction between importing packages and requiring bundles.
Imports are undirected in that any suitable bundle exporting the package can be nominated to
satisfy the import. This increases flexibility as it separates implementation and API. Typically,
you import only API packages and are thus oblivious to who is supplying the
implementationimplementations can be replaced without your notice.
Requiring bundles, on the other hand, states that the dependent bundle consumes the packages
exported by the prerequisite. That is, the consumer is bound to the supplier and its
implementation. This is less flexible, but is also simpler and more deterministic as the consumer
knows exactly which implementation she is getting.
As you can see, there are benefits and drawbacks to both. Traditionally, Eclipse plug-ins use the
require method rather than importing packages. OSGi programmers traditionally have used
only imports. The PDE plug-in editor allows you to pick how you specify your dependencies.
Dependencies are defined using the Required Plug-ins and Imported Packages sections of
the Dependencies page in the plug-in editor.
26.10.4. Optionality
OSGi allows prerequisites to be optional. Optional prerequisites, whether imports or requires,
do not prevent bundles from resolving. Rather, if the prerequisite element is available at
resolution time, the dependent and prerequisite are wired together. Otherwise, the dependency
is ignored and you must ensure that your code handles the potential classloading errors. This is
useful for separating concerns, as we saw in Chapter 23, "RCP Everywhere."
This property is controlled using the Properties... button in the Required Plug-ins section of
the Dependencies page in the plug-in editor.
26.10.5. Re-exporting
The OSGi dependency mechanism also supports the re-exporting of required bundles. As
discussed in Chapter 4, "The Hyperbola Application," re-exporting is a structuring where one
bundle exposes packages from a prerequisite as its own. For example, the Eclipse UI bundle
requires and re-exports JFace and SWT bundles. As a result, UI-related bundles need only to
specify a dependency on the UI bundle to get access to all of SWT and JFace.
You should only consider using this when the prerequisite classes somehow form an integral
part of your bundle's API. By re-exporting SWT, the UI bundle is effectively adopting the SWT
API as its own, similarly for JFace. In a sense, the UI is acting as a façade or wrapper around
these plug-ins. This hides the structuring details, allowing it to evolve over time.
This property is controlled using the Properties... button in the Required Plug-ins section of
the Dependencies page in the plug-in editor.
Note that a bundle can export a package it does not contain but rather gets elsewhere via an
import or require statement.
26.10.6. x-internal and x-friends
There is a healthy tension between designing and defining durable API and enabling advanced
exploration and experimentation. If you take a very strict API stance, then your bundle should
export only the packages that contain API. As we have seen, however, that approach means
that other bundles can never see the non-API packagesunder any circumstances. Depending on
your needs and those of your consumers, this approach may be too restrictive.
The Eclipse community has repeatedly stated that researchers and technology innovators need
access to internal implementation details to get their job done. They are fully aware that their
code might be broken at any time by an implementation change, but are willing to take that
risk.
To enable clear API guidance yet still give consumers the power to access internals as needed,
Eclipse 3.1 extends OSGi and offers two experimental package export directives, x-internal
and x-friends. By convention, all Eclipse plug-ins export all packages and in some cases, these
exported packages are qualified with one of the new directives.
By marking a package as x-internal, you clearly position it as non-API. When Eclipse is
running in strict mode (i.e., osgi.resolverMode=strict), the x-internal directive blocks all
external access to the package. Similarly, the x-friends directive marks a package as internal,
but allows a defined set of bundles free access to the package. PDE uses these directives at
development time to register warnings in code that accesses packages that are not visible when
running in strict mode.
These facilities are exposed in the Package Visibility section of the Runtime page in the plugin editor.
26.11. Data Areas
Applications often need to read or store data. Depending on the use case, this data may be
stored in one of many locations. Consider preferences as an example.
Typical products use at least some preferences. The preferences themselves may or may not be
defined in the product's plug-ins. For example, if you are reusing plug-ins from different
products, it is more convenient to manage the preferences outside the plug-in.
In addition, applications often allow users to change preference values or use preferences to
store recently opened files, recent chat partners, and so on. These values might be stored
uniquely for each user or shared among users. In scenarios where applications operate on
distinct datasets, some of the preferences may even relate to the particular data and should be
stored or associated with that data.
Preferences are just one example, but they illustrate the various scopes and lifecycles that
applications have for the data they read and write. Eclipse defines four data areas that capture
these characteristics and allows application writers to properly control the scope of their data:
Install The install area is where Eclipse itself is installed. The install area is generally
read-only. The data in the install area is available to all instances of all configurations of
Eclipse running on the install. See also Platform.getInstallLocation() and
osgi.install.area.
Configuration The configuration area is where the running configuration of Eclipse is
defined. Configuration areas are generally writable. The data in a configuration area is
available to all instances of the configuration. Chapter 25, "The Last Mile," contains
additional detail on the configuration area. See also
Platform.getConfigurationLocation() and osgi.configuration.area .
Instance The instance area is the default location for user-defined data (e.g., a
workspace). The instance area is typically writable. Applications may allow multiple
sessions to have concurrent access to the instance area, but must take care to prevent lost
updates, etc. See also Platform.getInstanceLocation() and osgi.instance.area .
Note
The Eclipse IDE's workspace is an example of the use of instance locations. The
Resources plug-in implementers chose to make the default location for projects
be in the instance area defined by the Runtime. Eclipse IDE users commonly
think they are setting the Resource's workspace location, but actually they are
setting the Runtime's instance location.
User The user area is where Eclipse manages data specific to a user, but independent of
the configuration or instance. The user area is typically based on the Java user.home
system property and the initial value of the osgi.user.area system property. See also
Platform.getUserLocation() and osgi.user.area.
In addition to these Eclipse wide areas, the Runtime defines two locations specifically for each
installed plug-in:
State location This is a location within the instance area's metadata. See
Plugin.getStateLocation().
Data location This is a location within the configuration's metadata. See
Bundle.getDataFile().
Each of these locations is controlled by setting the system properties described before Eclipse
starts (e.g., in the config.ini). Locations are URLs. For simplicity, file paths are also accepted
and automatically converted to file: URLs. For better control and convenience, there are also a
number of predefined symbolic locations that can be used. Note that not all combinations of
location type and symbolic value are valid. Table 26-1 details which combinations are possible.
Table 26-1. Location Compatibilities
Supports
Location/Value default? File/URL @none
@noDefault @user.home @user.dir
Install
No
Yes
No
No
Yes
Yes
Configuration
Yes
Yes
Yes [*]
Yes [*]
Yes
Yes
Instance
Yes
Yes
Yes
Yes
Yes
Yes (default)
User
Yes
Yes
Yes
Yes
Yes
Yes
[*] Indicates that this setup is technically possible, but pragmatically quite difficult to manage. In particular, without a configuration
location, the Eclipse Runtime may only get as far as starting the OSGi framework.
@none Indicates that the corresponding location should never be set either explicitly or to
its default value. For example, an RCP-style application that has no instance data may use
[email protected] to prevent extraneous files being written to disk. @none must
not be followed by any path segments.
@noDefault Forces a location to be undefined or explicitly defined (i.e., Eclipse does not
automatically compute a default value). This is useful when you want to allow for data in
the corresponding location, but the Eclipse default value is not appropriate. @noDefault
must not be followed by any path segments.
@user.home Directs Eclipse to compute a location value relative to the user's home
directory. @user.home can be followed by path segments. In all cases, the string
"@user.home" is replaced with the value of the Java user.home system property. For
example, setting
[email protected]/myWorkspace
results in a value of
file:/users/fred/myWorkspace
@user.dir Directs Eclipse to compute a location value relative to the current working
directory. @user.dir can be followed by path segments. In all cases, the string "@user.dir"
is replaced with the value of the Java user.dir system property. For example, setting
[email protected]/myWorkspace
results in a value of
file:/usr/local/eclipse/myWorkspace
Since the default case is for all locations to be set, valid, and writable, some plug-ins may fail in
other setups, even if they are listed as possible. For example, it is unreasonable to expect a
plug-in focused on instance data, such as the Resources plug-in, to do much if the instance area
is not defined. It is up to plug-in developers to choose the setups they support and design their
functions accordingly.
Note that each of the locations can be statically marked as read-only by setting the
corresponding property osgi.AAA.area.readonly=true, where "AAA" is one of the area names.
26.12. Putting It All Together
Most of this book has focused on the steady state of an Eclipse application, that is, the operation
of the system once it is up and running. It is sometimes useful to have a more complete picture
that includes the startup and shutdown. This section draws a timeline of events and interactions
that occur throughout the life of Hyperbola.
1. User launches Hyperbola by double-clicking on its launcher executable.
2. The Hyperbola executable reads hyperbola.ini and forms a command line, complete with
program and JVM arguments.
3. The launcher searches for a JVM to run (assuming one was not specified in hyperbola.ini
or on the original command line). It first looks for the Java executable in the jre/bin
directory, if any, that is a sibling of the launcher. The launcher then searches the PATH
environment variable for a directory containing the Java executable.
4. The discovered VM is spawned as an OS process with the command line constructed
earlier. The command line includes -jar startup.jar. startup.jar lists
org.eclipse.core.launcher.Main as its Main-Classthe class to run.
5. The Main class runs and reads the config.ini discovered in the configuration area, and if
required and present, the one in the Hyperbola install directory.
6. Main uses the osgi.splashPath and osgi.splashLocation properties in the configuration to
discover the splash screen image. If found, it uses Runtime.exec() to invoke the launcher
again and have it display the splash image while Hyperbola starts up.
7. Main proceeds to construct a classpath and classloader to load the OSGi framework. The
location of the framework is discovered either from the osgi.framework configuration
property or by searching for the org.eclipse.osgi bundle in the install's plugins directory.
8. Reflection is used to find and load the framework's EclipseStarter class and then call its
run(String[], Runnable) method to start the framework and run the desired product or
application.
9. EclipseStarter instantiates and initializes the framework and starts the OSGi console if
requested by using -console or osgi.console.
10. The framework loads descriptions of all previously installed bundles. On first run, this list
is empty. On subsequent runs, this list includes all plug-ins installed at the end of the
previous run.
11. EclipseStarter processes the osgi.bundles configuration property and ensures that the
listed bundles are installed in the framework at their default or specified start level. For
standard Eclipse systems, the osgi.bundles list includes org.eclipse.core.runtime at
start level 2 and org.eclipse.update.configurator at start level 3. All bundles listed with
the "start" annotation are marked as startable in the framework. They are finally started
when the system's start level increases to be the same as their start level.
12.
12. Initially the system's start level is 1. EclipseStarter sets the start level to the value of
osgi.startLevel, by default 6. This causes all bundles marked as "started" from levels 16,
in start level order, to be activated. The activation is done on a separate thread and the
main thread waits until all started bundles have completed their activation.
13. When the Runtime bundle is activated at start level 2, it initializes various data structures
and starts the extension registry. It also registers an application service, a Runnable, with
the property eclipse.application="default". When run, this service looks up and invokes
the Eclipse application indicated by the product or the -application command line
argument.
14. When the Update configurator runs at start level 3, it compares the set of plug-ins listed in
its platform.xml file to install with the list that is already installed (from Step 10). Any
missing plug-ins are installed at a start level dictated by the
osgi.bundles.defaultStartLevel property. These bundles are not marked as startable. On
first startup, the platform.xml file does not exist, so the configurator scans the known
install areas and builds a list. On subsequent runs, the platform.xml list is reconciled
against the plug-ins on disk to ensure they are synchronized.
15. After the start level is set and all relevant bundles are started, EclipseStarter resumes
execution on the main thread and looks up the application service registered by the
Runtime in Step 13.
16. The application service, a Runnable , is invoked using run(). This looks up the application
extension, an IPlatformRunnable, and runs it. The splash screen is taken down and the
application (e.g., Hyperbola) runs.
17. At some point, the application completes and the run() method returns. EclipseStarter
then stops the framework. This, in turn, causes the start level to be reduced to 0 and all
started bundles at each level in reverse order are told to stop().
18. In addition, the Runtime stops all started bundles in dependents-first order. That is,
bundles with no dependents are stopped first, then their prerequisites and so on.
19. Finally, when all bundles have been stopped, the framework exits and returns the exit
code integer returned from the application.
26.13. Summary
The strength of Eclipse lies in its robust plug-in model. This, in turn, maps directly onto the
OSGi framework specification and its bundle model. Plug-ins and bundles bring advantages of
scale, composition, serviceability, and flexibility. The costs of this power are the rigor and
attention to detail required when defining plug-inspoorly defined plug-ins are hard to compose
and reuse in the same way as poorly defined objects.
This chapter exposes the essential details of the OSGi component model and the Eclipse
implementation of the OSGi specification. We touched on some of the framework's configuration
options and provided a number of guidelines for building your plug-ins.
With this information, you will design and implement better components that run more
efficiently and have more classloading and composition options.
Chapter 27. Eclipse.org Plug-ins
The success of a platform can be measured by the size of its community, and ultimately, by the
number of applications built with it. As the size of a community increases, so does the collection
of components that are produced and made availableeither commercially or for free. By these
metrics, Eclipse is a huge success. The community is vibrant, the number of projects doing
interesting and widely relevant work is impressive, and several of the world's major software
companies are members of the Eclipse Foundation. This translates into a huge number of useful,
quality plug-ins that save you time and money.
You probably noticed that the RCP SDK includes only a few plug-ins. In fact, at last count, the
RCP download contained just 16 plug-ins (including 2 source plug-ins). In Part II, you learned
how to add Eclipse Platform plug-ins such as Help and Update to Hyperbola. The Eclipse SDK
includes more than 85 plug-ins, many of which are useful in RCP applications. Add that to the
others available at Eclipse.org and the hundreds available on Web sites such as sourceforge.net
and eclipseplugincentral.com and it is clear that a key part of developing an RCP application is
deciding which existing plug-ins to use and how they help solve the problems of your
application domain.
A complete survey of the existing plug-ins is beyond the scope of this book and would likely be
obsolete before it even reached your hands. Instead, this chapter contains an overview of the
plug-ins in the Eclipse SDK that can be used in RCP applications.
27.1. Where to Find Plug-ins
There are two main reasons to look for additional plug-ins: either you are looking to augment
your Eclipse IDE with more tooling (e.g., Web tooling, C tools, AspectJ, etc.) or you need more
function for your RCP application. You can find both in the following places.
Before going on a treasure hunt for plug-ins, read Section 23.6, "Designing a Platform," to learn
how to identify plug-ins that are meant to be reused in RCP applications. Some plug-ins are
written for a particular product and are not what we call RCP-friendly.
Eclipse SDK (eclipse.org/downloads) The SDK includes more than 85 plug-ins,
including Java tooling (JDT), plug-in tooling (PDE), Help, Update, and various other bits
and pieces. Chances are you are using the SDK to develop your plug-ins, but you might
also want to include some of these in your target application.
Eclipse Tools (eclipse.org/tools) The Eclipse Tools project includes many interesting
and useful tooling extensions for the Eclipse IDE. These include the GEF, EMF, a visual
editor (VE), UML2 tools, and so on. Several of these projects (e.g., GEF and EMF) contain
runtime elements that you add to your application.
Eclipse Technology (eclipse.org/technology) The Eclipse Technology project includes
all manner of experimental and not-so-experimental work on tooling and runtime
technologies. There are sub-projects for aspect-oriented development/runtimes,
collaborative development, embedded RCP runtimes, voice tooling, communications
infrastructure, and much more. Again, some of these projects include additional tooling for
your development environment, some include runtime elements for your target, and some
include both.
Other Eclipse projects Eclipse.org hosts a number of other top-level projects such as
Web tooling (WTP), test and performance (TPTP), and reporting (BIRT). Again, some are
strictly tooling to help you build or implement your application, and some include or
generate application runtime facilities.
Links to community resources The Eclipse community is very active and includes many
sites that either host or point to collections of plug-ins. The community resources page at
http://eclipse.org/community/sources.html#links includes links to the major plug-in
repositories and directories.
Search the Web There are many other sources for Eclipse plug-ins. For example,
http://eclipseplugincentral.com and http://sourceforge.net host hundreds of Eclipse plugins.
27.2. Eclipse Platform Plug-ins
The development of the Eclipse Platform is dedicated to providing a robust, full-featured, and
commercial-quality platform for the development of highly integrated tools. The Eclipse
Platform is thus an excellent source of product-quality plug-ins that can be used in your
applications.
In an effort to keep the RCP download small, only a small set of the platform plug-ins is
included in the RCP SDK. However, as you saw in Chapter 13, "Adding Help," and Chapter 14,
"Adding Update," you can add plug-ins from the Eclipse SDK to your application.
There is one caveat, however. As mentioned in Chapter 23, "RCP Everywhere," and in particular
as explained in Section 23.4, "Hyperbola Product Configurations," plug-ins are designed to work
either with a specific product or alternatively with any product. The Eclipse Platform contains
both types. The plug-ins that depend on the existence of the IDE product, for example, having a
dependency on the org.eclipse.ui.ide plug-in, are not meant to be used by RCP applications.
If you aren't sure, then review the checklist in Section 23.4 for how to determine if a plug-in can
be used in an RCP application.
In this chapter, we review the Eclipse SDK plug-ins that can be used by RCP applications. We do
not have enough pages to provide an exhaustive reference to each, but we provide enough
detail for you to understand how they work and where to find more information.
27.3. Product Introduction
Plug-ins
org.eclipse.ui.intro
Dependencies
org.eclipse.core.runtime, org.eclipse.help, org.eclipse.ui,
org.eclipse.ui.forms
When a user starts an application for the first time, they often do not know how or where to
start. This leads the user to either dismiss the application because they don't understand its
features, or waste precious time trying to figure out how to get started.
Intro support solves these problems by giving users an introduction to the features of the
product the first time it is started. This is your opportunity to direct users to important starting
points, highlight interesting capabilities, and ultimately help users understand what your
product is and how to use it.
The Workbench provides an extension point named org.eclipse.ui.intro and an interface to
describe Intro pages named IIntroPart. The Workbench does not, however, provide any
reusable pieces that make it easy to create an Intro page. Applications can either provide their
own IIntroPart implementation or use the org.eclipse.ui.intro plug-in and its helpers to
build a nicer Intro page.
By default, the Intro page is shown the first time a product is started. The showing of the Intro
pages is controlled by the preference IWorkbenchPreferences.SHOW_INTRO. The actual creation
and triggering is done in WorkbenchWindowAdvisor.openIntro(). The default implementation
opens the contributed Intro content, but you can override this to do whatever you like.
The lifecycle of the Intro part is as follows:
The Intro part is created on Workbench startup. As with editor and view areas, this area is
managed by an Intro site (implementing org.eclipse.ui.intro.IIntroSite).
The id of the current product ( Platform.getProduct()) is used to choose the Intro part to
show.
The Intro part class (implementing org.eclipse.ui.intro.IIntroPart) is created and
initialized with the Intro site.
While the Intro part is showing to the user, it can transition back and forth between full
and standby mode (either programmatically or explicitly by the user).
Eventually, the Intro part is closed (either programmatically or explicitly by the user) and
the current perspective takes over the entire Workbench window area.
A good way to understand how to build Intro pages is to look at the new Intro template that is
part of Eclipse 3.1. Create a new RCP plug-in project, and from the list of templates, select RCP
application with intro. The sample code for Chapter 23 includes Intro support for Hyperbola,
and the Workbench itself includes a standard action to open the Intro page called
org.eclipse.ui. actions.ActionFactory.INTRO .
27.4. Resources
Plug-ins
org.eclipse.core.resources
Dependencies org.eclipse.core.runtime
The Resource plug-in offers a layer above java.io.File that adds support for markers, builders,
change notifications, local history, and persistent and session properties. It introduces a
workspace in which projects, files, and folders are created and manipulated. Projects are root
folders in the workspace and contain files and folders. This plug-in is the basis for all IDE
tooling in Eclipse.
It is a common misconception that the Resources plug-in is only for tools. In fact, Resources is
all about file manipulation. If your application does a lot of sophisticated file manipulation,
either explicitly or under the covers, you might be interested in Resources. Of course, you can
always use java.io.File and related classes to manage files directly.
27.4.1. Overview of Resources Key Features
From the very beginning of Eclipse, it was clear that there was a need for a plug-in that
provides base-level resource support on which to build programming language tooling such as
compilers, debuggers, versioning, and search. When you look at the services provided by the
Resources plug-in, it is obvious that they were added specifically to satisfy these requirements.
Following is a small overview of the features provided by this plug-in:
Change notifications When resources are changed, there is always someone that cares
(e.g., UI viewers, builders). The plug-in provides efficient deltas that describe the changes
made to a set of resources. This is useful since file notifications are not part of the
standard Java class libraries.
Incremental builders Incremental project builders transform or manipulate resources to
produce additional resources. For example, the Java development tools define an
incremental project builder that compiles a Java source file into a class file any time a file
is added or modified in a Java project. It also keeps track of dependent files and
recompiles them when necessary.
Markers During the course of editing or building a resource, a plug-in may need to tag
resources to communicate problems or other information to the user. The resource marker
mechanism is used to manage this kind of information. A marker is like a yellow sticky
note stuck to a resource. On the marker, you can record information about a problem
(e.g., location, severity) or a task to be done. Markers can also be used to simply record
the location of a marker as a bookmark.
Properties Resources have properties that can be used to store meta-information about a
resource. Your plug-in can use these properties to hold application-specific information
about a resource. Resource properties are declared, accessed, and maintained by various
plug-ins and are not interpreted by the platform. When a resource is deleted from the
workspace, its properties are also deleted.
Derived resources Many resources get created in the course of translating, compiling,
copying, or otherwise processing files that the user creates and edits. Derived resources
are resources that are not original data and can be recreated from their source files. It is
common for derived files to be excluded from certain kinds of processing. For example,
derived resources are typically not kept in a team repository since they clutter the
repository, change regularly, and can be recreated from their source files.
The Eclipse.org Web site has many examples and articles that explain how to use the features
outlined above. If you do not need any of these features, then just go ahead and use
java.io.File instead. Otherwise, the next sections provide some basic introduction to the plugin.
27.4.2. Getting Started with Resources
The Resource plug-in provides access to resources via a workspace. Imagine the workspace as
the container for all resources, through which you must pass to access any resource. The
snippet below shows how to access the workspace and create a project and file. Projects are the
top-level elements in the workspace.
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IProject project = workspace.getRoot().getProject("New Project");
if (!project.exists())
project.create(new NullProgressMonitor());
if (!project.isOpen())
project.open(new NullProgressMonitor());
IFile myFile = project.getFile("perfs.txt");
myFile.create(
new ByteArrayInputStream("contents".getBytes()),
true, new NullProgressMonitor());
IResources (e.g., IFile, IFolder) objects are handled to state maintained by the workspace.
This allows you to access a resource before it exists, and at the same time, hold on to a
resource that has been deleted.
27.4.3. Resources in the Workbench
Prior to Eclipse 3.0, resources were at the heart of the Workbench and building an application
that did not include resources was not possible. When the RCP was created, these dependencies
were moved to org.eclipse.ui.ide or the IDE product. The IDE uses the workspace resource
model as its underlying data model. The IDE plug-in, and the extensions defined within it, are
not designed to be reused in other RCP applications. That is, although it is technically
possible to either subclass or reference IDE classes, it is not particularly supported by the
generic UI Workbench. So, if you are developing an IDE-style product, you should consider
extending the existing IDE application rather than developing your own RCP application.
27.5. Text Editing
Plug-ins
org.eclipse.text, org.eclipse.jface.text,
org.eclipse.ui.workbench.texteditor, org.eclipse.swt
Dependencies org.eclipse.core.runtime, org.eclipse.jface, org.eclipse.ui
If you need to show or edit text in your application, you need the text editing framework.
Programming languages have evolved, but the fact remains that developers spend most of their
time writing code in a text editor of some sort. Given Eclipse's origin as an IDE, the text
infrastructure included in the platform is very advanced and includes state-of-the art text
editing features.
The breath of support provided by the text editing framework comes at a priceit's complex.
Basic text editing capabilities are fairly easy to implement, but as soon as you start adding
advanced features such as syntax highlighting, content assist, and annotations, you have to
invest some time to understand the framework. This section shows you how to add basic text
editing to an RCP application.
27.5.1. Text Plug-ins
The text framework is split across several plug-ins to modularize and encapsulate functionality.
For example, this allows reuse of the text model without implying a dependency on text
presentation. Figure 27-1 shows how these plug-ins are layered and what functionality is
included in each plug-in.
Figure 27-1. Overview of text-related plug-ins
[View full size image]
Support for IResource text editing is provided by the org.eclipse.ui.editors plug-in and is
tightly coupled to the Eclipse IDE product. As such, it is not part of the RCP. There are,
however, many pieces of the text infrastructure that are available for use in RCP applications.
The remainder of this section demonstrates how to integrate text editing support into an RCP
application.
27.5.2. Editing versus Editor
The text framework can be used to implement a full-featured programming language editor,
and can also be used to allow editing of text in a dialog or show output for a console.
Given its architecture, you can use org.eclipse.text to manipulate text without a presentation.
The org.eclipse.jface.text plug-in provides the link between the model (IDocument) and the
view (StyledText) via the TextViewer. The coupling to the Workbench, in particular to editors,
is provided by the org.eclipse.ui.workbench.texteditor plug-in with the addition of an
AbstractTextEditor .
As a developer of an RCP application, you can pick and choose the text features you need from
the framework.
27.5.3. Text and StyledText
The simplest way to either show or allow editing of text is to use the SWT Text control. This is a
native control that is good enough for many simple editing requirements. The SWT custom
control called StyledText is a step up from the Text control. It is a subclass of Canvas and
allows editing of text, changing the font and colors of text regions, and changing the cursor.
Because it is a Canvas, clients can draw directly on the widget and extend the presentation of
the text. For example, a common pattern is to add a line styler to StyledText to show syntax
highlighting.
text = new StyledText (shell,
SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
text.addLineStyleListener(new ChatLineStyler());
text.setEditable(false);
Color bg = Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
text.setBackground(bg);
text.setText(chatContents);
If all you require is a rich edit control, then using StyledText is good enough. You can embed
the control in dialogs or viewsand Bingo!, you have rich edit capabilities. If you want more bells
and whistles such as content assist, hyperlinks, overview, and outline rulers, then the Text
framework plug-ins are what you need.
Examples
The examples that come with the Eclipse IDE contain a good sample of both a Java
editor implemented directly above StyledText and a Java editor implemented using
the text plug-ins. You can install the examples into the Eclipse IDE by showing the
Welcome page from Help > Welcome and following the Samples link.
27.5.4. IDocument
Manipulating text is error-prone and difficult; you have to manage lines, indexes, wrapping, and
many other details to keep the text correct. IDocument is a text model designed to help with
manipulating text, and in some cases, with storing text so it can be presented. For example,
documents can be partitioned into disjointed parts by using token- and rule-based filters.
Partitions are useful for syntax highlighting and are stored with a document to avoid having to
maintain additional data structures. Think of an IDocument as a java.util.StringBuffer on
steroids.
27.5.5. TextViewers and TextEditors
The next step up from using SWT controls to display text is using the TextViewer classes in
org.eclipse.jface.text. A TextViewer connects a StyledText control with an IDocument model,
and a TextEditor provides support for displaying a TextViewer in an editor. This sounds simple,
but many details are being taken care of for you by the viewer and editor.
Below is a quick example of how to provide a simple text editor. Here we create a concrete
implementation of AbstractTextEditor and a concrete IDocumentProvider. The document
provider connects the document to the editor and allows sharing of documents between editors.
Figure 27-2 shows how this works in more general terms.
public class SimpleEditor extends AbstractTextEditor {
public SimpleEditor() {
super();
configureInsertMode(SMART_INSERT, false);
this.enableOverwriteMode(true);
setDocumentProvider(new SimpleDocumentProvider());
}
}
Figure 27-2. Relationships among key text classes
[View full size image]
AbstractTextEditor provides most of the features you need from a basic editor. As a result, our
simple text editor implementation only needs to hook the document provider to the editor.
The SimpleDocumentProvider handles reading and saving the file. When the editor is opened, it
is provided with an IEditorInput that describes the file to be opened. The editor delegates the
task of manipulating the bytes in the file to the document provider. Your main coding job is to
provide implementations of the following methods for the document provider:
createDocument() Loads the file from disk and creates the IDocument with the contents of
the file.
doSaveDocument() Saves the file to disk.
isModifiable() Decides if the user can edit the file.
isReadOnly() Decides if the document is going to be able to be saved.
27.5.6. What Is Missing?
Because org.eclipse.ui.editors is not part of RCP, there are some features that are not
available to RCP applications. For example, the most feature-filled editor is
AbstractDecoratedTextEditor. It supports line numbers, change ruler, overview ruler, print
margins, and current line highlighting.
In addition, the UI Editors plug-in contains IResource editing support. As a result, there is no
easy way of editing an IFile without creating your own specific document provider. Have a look
at FileDocumentProvider for hints if you need to implement IResource-aware editors.
The sample code for this chapter includes an example text editor in the project called
org.eclipse.ui.examples.rcp.texteditor .
27.6. Consoles
Plug-ins
org.eclipse.ui.console
Dependencies org.eclipse.core.runtime, org.eclipse.jface.text, org.eclipse.ui,
org.eclipse.core.expressions,
org.eclipse.ui.workbench.texteditor
Consoles are traditionally used to display raw output from a running program and also accept
keyboard commands. They are also very common in IDEs. Instead of having each plug-in define
its own console views, the Console plug-in provides a single view into which other plug-ins can
contribute their consoles. This makes it much easier for users to find consolesall they have to do
is open one view, and from there, they can easily toggle between the available consoles.
The Console plug-in also provides many helpful classes for creating consoles. For example, in
Chapter 16, "Perspectives, Views, and Editors," we showed how simple it is to add an XMPP
messaging console to Hyperbola. The plug-in also provides many features useful in writing text
consoles, for example, hyperlinking and text coloring. To find out more about the console, see
the example in Chapter 16 and look at org.eclipse.ui.console.TextConsole and the extension
points provided by the Console plug-in.
27.7. Variables
Plug-ins
org.eclipse.core.variables
Dependencies org.eclipse.core.runtime
This plug-in provides classes and interfaces to support the definition and contribution of
variables for the purpose of string substitution. It supports the recursive replacement of
variables referenced by name in arbitrary strings with the value of the variable. Two types of
variables are provided: value variables (IValueVariable) and dynamic variables
(IDynamicVariable).
A value variable has a simple setter and getter for its value. A dynamic variable has an
associated resolver that provides a variable's value each time it is referenced (replaced) in a
string substitution. A dynamic variable may also provide an argument in its reference, which
can be used by its resolver to determine its substitution value.
A variable manager (IStringVariableManager) is provided to manage the set of defined
variables. Value variables may be created via API on the variable manager, or contributed via
the valueVariables extension point. Dynamic variables must be contributed via the
dynamicVariables extension point.
The variable manager provides change notification for value variables and an API for performing
string substitution. Substitution simply takes a string containing variable patterns and returns
that string with all the variables replaced by their associated variable values.
Variables are referenced in strings by enclosing them in braces, preceded with a dollar sign. For
example, "abc${foo}ghi" is composed of the strings "abc" and "ghi" sandwiching the variable
"foo". If the value of foo is "def", string substitution gives "abcdefghi".
In the case of dynamic variables, an optional argument is supplied by appending a colon and
argument value after the variable name, for example, "${foo:bar}". In this case, the resolver
associated with foo is given the referenced argument, bar, when asked to resolve a value for the
variable foo.
27.8. Outline and Property Views
Plug-ins
org.eclipse.ui.views
Dependencies org.eclipse.core.runtime, org.eclipse.ui, org.eclipse.help
The outline and property views are not included in the RCP, but you can easily include them in
your application by adding the org.eclipse.ui.views plug-in to your target.
To use the property or outline view, add the views to a perspective using the usual
IPageLayout.addView() or IPageLayout.addStandaloneView() methods, or open them
programmatically using IWorkbenchPage.showView(). The view ids for both are found on
IPageLayout, even though the views are not part of the Workbench. This is historical. Before
RCP, they were part of the IDE Workbench. For backwards-compatibility reasons, they were left
there.
Both these views are interesting because they work by supporting the active editor and switch
their content as the editor is changed. They do this by listening to part changes and they query
the active part to see if they have anything to contribute to the outline or property views.
The Eclipse help and examples provide ample information on how to use these views. Also see
Chapter 16 for hints about integrating views into your RCP application.
27.9. Forms
Plug-ins
org.eclipse.ui.forms
Dependencies org.eclipse.core.runtime,
org.eclipse.ui
If you've opened a plugin.xml file using the Plug-in Manifest Editor, then you've seen the Forms
plug-in in action. The Forms plug-in provides Web-like UIs by modestly extending SWT to
manipulate style bits, colors, fonts, and other properties to get the desired look and feel. It's
actually become a standard to use forms in editors that display and allow editing of structured
data.
The Forms classes handle many of the SWT details needed to write nice-looking and wellbehaved editors. For example, the FormToolKit widget factory is responsible for creating formfriendly SWT controls for use in custom form layouts that handle wrapping and scrolling for you.
The best source of information about the Forms API is the following tutorial:
http://dev.eclipse.org/viewcvs/index.cgi/%7Echeckout%7E/pde-uihome/working/EclipseForms/EclipseForms.html
27.10. Browser
Plug-ins
org.eclipse.ui.browser
Dependencies org.eclipse.core.runtime,
org.eclipse.ui
The Browser plug-in and the Workbench's browser APIs and extension point provide all you
need to open a URL in either an internal or external Web browser. Although the Workbench
provides the generic APIs, such as IWorkbenchBrowser.createBrowser(String), the Browser
plug-in provides the implementation for this by extending the org.eclipse.ui.browserSupport
extension point.
To use the browser plug-in, simply add it to your target and then access the Workbench's
browser support via IWorkbench.getBrowserSupport(). From the returned IWorkbenchBrowser,
you can call createBrowser(String) to get the default browser IWebBrowser reference. You can
then call IWebBrowser.openURL(URL) to open a Web page. The Browser plug-in contributes a
preference page that allows the user to configure the browser to use to show Web pages.
27.11. Summary
Here we have seen just a taste of the wide range of plug-ins available from Eclipse.org for use
in writing RCP applications. There are plug-ins for text manipulation, user assistance, resource
management, and so on. Beyond the Eclipse Platform and eclipse.org, the list becomes almost
boundless.
The net result is that before writing your function, you should look around for existing plug-ins
that you can reuse. When browsing plug-ins, always ensure that the plug-in was designed to be
used in other applications. To do this, refer to the rules and guidelines from Section 23.4 for
how to determine if a plug-in is RCP-friendly. The corollary to this is that you may also want to
contribute some of your plug-ins back to the community for others to use.
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
About information 2nd
About text string
Action (in Eclipse)
Action extension points
ActionBarAdvisor 2nd 3rd 4th
actions in Hyperbola multiple product configurations
adding actions to Hyperbola application 2nd
adding contributions to status line
menus and toolbar additions
progress reporting
status line additions
system tray integration
adding Update actions
command id
consolidating declarative action
declarative action 2nd 3rd
allowing contributions 2nd
example in auto-login preference page
responsibilities
retargetable Workbench action
standard Workbench action
toolbar action tricks
Adding a chat editor to Hyperbola application
checkpoint run
defining chat editor extension 2nd
chat action
editor input
Adding help 2nd
adding to the target platform
and exporting plug-ins
configuring help plug-ins
content
structure
getting help plug-ins
help action
Infopop mechanism (F1 help)
Adding key bindings 2nd
defining commands 2nd
for Workbench actions
key configurations
keys preference page
testing (checkpoint)
Adding to the target platform
adding plug-ins
getting plug-ins
Adviser/advisor
Ant constructs
Application
example
Applications
Archives
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Base
Behavior
as basic responsibility of action
Binary Build section
Binding
as basic responsibility of action
key bindings in Hyperbola multiple products configuration
Branding features 2nd
Branding Hyperbola 2nd 3rd
About information 2nd
define product configuration 2nd
launcher customization
Splash screen 2nd
Window Images
Branding login dialog
Breakpoints in debugging
Browser plug-in
Building Hyperbola [See also PDE Build]
role of features
Bundle 2nd
auto-activation
bundling 2nd
by injection
by reference
by wrapping
troubleshooting [See Classloading/problems]
bundling Smack
testing 2nd
early activation
early activation extensions
osgi.bundles
start levels
lifecycle
BundleAactivator (downside of usage)
BundleActivator (PluginClass)
BundleActivator (value of)
listeners
requiring vs. importing packages
system bundle
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Cache management issues
Chat editor [See Adding a chat editor to Hyperbola application]
Chat model
ChatAction
Chords [See Key sequences]
Class.forName() issues
Classloading 2nd
buddy classloading
built-in policy
considerations
vs. Dynamic-ImportPackage (OSGi)
current classloader
declaring imports/exports
imports vs. requirements
lookup algorithm
optionality
problems
Class.forName() issues
context classloaders issues
JRE classes (managing)
serializaiton of objects issues
re-exporting
x-internal/x-friends
Code libraries (integration of) [See Bundle]
Plug-ins
Smack
Commands
defining 2nd
Componentized systems
config.ini
Configuration location
Configuration synchronizing
Configurator (Update)
Consoles
Contacts
Add Contact action
add to toolbar
list
adding images
view 2nd 3rd
content providers overview
filling in
label
vs. chat area
Content providers overview
Context menus
Contribution extension points
Custom window shapes
defining window contents
shape creation
window creation
Customization
CVS (setting up)
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Debugging
Hello Hyperbola example
breakpoints
stepping functions
Declarative action
consolidation facilities
in Eclipse
in Hyperbola
allowing contributions 2nd
declaring actions
Delivery mechanisms (Hyperbola and other Elipse-based products) 2nd
archives
install initialization
Java Web Start (JNLP)
multi-user install scenarios
multiple configurations
shared configurations
shared installs
native installers
pre-initialized configurations
Update sites
Depencies/Dependency Analysis
in Hyperbola multiple products configuration
optional dependencies
Dependencies/Dependency Analysis
and dependency management in componentized systems
at plug-in level
dynamic classpaths
specifying features
Dependencies/Dependency Analysis tools
Development environment installation
Dynamic awareness
addition/removal
Dynamic classpaths
Dynamic tolerance
Dynamic-awareness
bundle listeners
dynamic extension scenarios (challenges)
extension caching scenario
no caching scenario
object caching scenarios
object handling
Dynamic-enablement 2nd
clean-up
Dynamic-ImportPackage
vs. buddy classloading
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Eclipse 2nd 3rd
cache management issues
Platform
plug-ins
product introduction
project evolution
Resources plug-in
usage
versions
Eclipse RCP (rich client platform)
component libraries
components
development tooling support
disconnected operation
intelligent install and update
middleware and infrastructure
native user experience
portability
tooling and applications development
uses
Maestro Project (NASA)
Workplace Client Technology (IBM)
Eclipse RCP concepts 2nd 3rd
classloading strategies
data areas
installed system overview
configuration location
install location
plug-in store
plug-ins collection (community)
preferences
product 2nd
products
RCP applications 2nd 3rd 4th
Runtime 2nd 3rd 4th
SDK (Software Development Kit) 2nd 3rd
Update component
Eclipse RCP SDK 2nd
Editors
and UI Workbench
and views 2nd
communication
customization [See Presentations]
in Hyperbola multiple products configuration
input
lifecycle
multiple-page editors
sharing
with drag and drop
Exporting
Hyperbola 2nd
other platforms
plug-ins with help
re-exporting
Extension registry
Extensions
dynamic extension scenarios
extension caching scenarios
no caching scenario
object caching scenarios
early activation
osgi.bundles
start levels
Extension configuration (IDE) in Hyperbola multiple product configuration scenario
extension factories in multiple product configurations
extension points in multiple product configurations
extension product
in Hello Hyperbola example
named and anonymous in multiple product configurations
perspective example
Workbench extension point reference
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Features 2nd 3rd 4th 5th 6th
and plug-ins
branding
choosing ids
content
directory structure
feature build properties
identifying/placing root files
top-level
Forms plug-in
Fragments
Framework plug-ins 2nd
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Hello Hyperbola 2nd 3rd
code review 2nd
ActionBarAdvisor
application 2nd
perspective
WorkbenchAdvisor
WorkbenchWindowAdvisor
Dependencies
Extensions
plug-in editor
project structure
running and debugging
breakpoints
debugging
launch configurations
stepping functions
simple shell application
comparison with multi-configuration structure
plug-in project creation 2nd
skeleton
Help [See Adding help]
Hyperbola
adding perspectives [See Perspectives; adding (Hyperbola examples)]
as instant messaging client [See Messaging support]
builder [See Managed build process example]
creating/using login dialog [See Login dialog creation/use]
delivering to end users [See Delivery mechanisms (Hyperbola and other Elipse-based products)]
evolution of
branded and packaged
Hello Hyperbola
Help and Update support
Messaging
UI sketch
help [See Adding help]
key bindings [See Adding key bindings]
making dynamic [See Plug-ins; dynamic]
multiple product configurations [See Multiple product configurations (Hyperbola)]
packaging the branded product configuration [See Packaging Hyperbola]
product branding [See Branding Hyperbola]
prototype (iterative development approach) [See Running Hyperbola prototype]
replacing prototype model with Smack [See Refactoring the prototype model]
simple shell application creation [See Hello Hyperbola]
timetable of events/interactions
version control [See Update component]
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
IAction
id
IBM Workplace Client Technology
and Eclipse RCP
Icons
in Hyperbola multiple products configuration
IDE (integrated development environment)
Identity theft
IDocument
IDocument
Images
adding
ImageDescriptors
in Hyperbola multiple products configuration
showing images and text (toolbar items)
Imports
of packages vs. requiring bundles
Infopop mechanism
Input and enablement
as basic responsibility of action
Install handlers (Update)
Install location
InstallShield
Instant messaging
IPageLayoutReference
Iterative software development approach
IWorkbenchAdapters
adding
IWorkbenchConfigurer
IWorkbenchWindowConfigurer
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Java Web Start (JNLP)
and Hyperbola
and Update
building manifests
exporting
function
JAR signing
Jface
JFace
in Hyperbola multiple product configuration scenario
JNLP [See Java Web Start (JNLP)]
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Key bindings [See Adding key bindings]
Key sequences
Kiosk
example in Hyperbola multiple product configuration scenario
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Label provider
Launch configurations
Hello Hyperbola example
Launcher
Launcher customization
License Agreement
Listeners
bundle listeners
Login dialog creation/use 2nd
adding auto-login preferences
accessing preferences
adding action
default preference values
preference page creation
preference setting by user in context
adding dialog
branding the dialog
saving (remembering) login settings
using preferences
testing with other servers
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Managed build process example
build.properties
base identification/locating
build naming/locating
customTargets.xmll
CVS access control
Java class libraries/compiler control
product/packaging control
building products
cross-platform building
customizations
fetching from CVS
fetching the maps
managing the base
output names (controlling)
quantifying version numbers
version numbers/auto-substitution
running the builder
debugging
Map files
Menus and toolbar creation/addition 2nd [See also Presentations/menus]
Add Contact action
add to toolbar
customizable toolbars
menu managers
top-level menu create
Messaging
Messaging support 2nd
reactoring Hyperbola to use Smack
testing facility
third-party library integration
bundling Smack
testing bundled Smack 2nd
UI updating
MUC installation
and Hyperbola plug-ins
Multiple product configurations (Hyperbola)
advantages of
code structures
actions
Hyperbola layering
icons and images
key bindings
optional dependencies
preferenceswizards
property pages
views and editors
wizards
Workbench contributions
comparison with simple shell application
configurations neeeded for scenario
Extension (IDE)
JFace
Kiosk
PDA
Workbench
platform designing
extension factories
extension points
named and anonymous extensions
rules
sample code
scenario
background
basic configurations
feature and plug-in projects
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
NASA
Maestro Project and Eclipse RCP
Native installers
NSIS
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Object handling
Offerings
standalone vs. extension
Open Services Gateway initiative [See OSGi framework]
OSGi framework 2nd 3rd
and bundle auto-starting
and Eclipse
Runtime
classloading strategies
console
data areas
Dynamic-ImportPackage
vs. buddy classloading
services
Outline and property view
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Packaging Hyperbola 2nd
exporting (other platforms)
exporting Hyperbola 2nd
Part list menu
PDA
in Hyperbola multiple product configuration scenario
PDE Build 2nd
Ant constructs/properties
setting up CVS
setting up Hyperbola builder [See Managed build process example]
Perspectives 2nd 3rd
adding (Hyperbola examples)
Debug perspective and Console view
IPageLyout Reference/methods
perspective bar
perspective menu
programmatic perspective control
extension points
perspective factory example (multiple views)
Placement
as basic responsibility of actions
Platform designing
extension factories
extension points
named and anonymous extensions
Plug-ins 2nd 3rd 4th 5th [See also Dependencies/Dependency Analysis tools] [See also Resources plug-in]
adding
and bundle 2nd
and features
uses of (by Update)
as JARs
browser
build properties
control properties
using custom build scripts
Console
dynamic 2nd
challenges
Eclipse RCP SDK 2nd
editor (Hello Hyperbola example)
Forms
framework 2nd
getting
ids and project names
plug-in store
project creation example
RCP-friendly
shape of
fragments
singletons
sources
update
variable
version numbering
view
Add to Java Search context menu
Preferences
adding auto-login preferences
accessing preferences
adding action
default preference values
preference page creation
preference setting by user in context
for keys
for login settings
in Hyperbola multiple products configuration
node
root node
scope
structure
Workbench
reusable preference pages and views
Presentations 2nd 3rd [See also StackPresentation coding examples]
implementation/customization steps
StackPresentation
widget hierarchy
menus
samples
plug-in
R21 presentation
Products 2nd [See also Projects/project naming]
configuration types
define product configuration 2nd
product
product vs. feature branding
Progress reporting
busy cursor
customizing progress
forking
non-modal progress
progress view
ProgressProvider
Project names
and plug-in ids
Projects/project naming
Property pages
in Hyperbola multiple products configuration
Prototypes 2nd
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
RCPs (rich client platforms) [See Eclipse RCP concepts]
Refactoring the prototype model [See also Smack]
adding chats
design objectives
protoype classes deletion
Rendering
as basic responsibility of action
Resources plug-in
and Workbench
resources key features
workspace access
Rich client (RC)
Root files
config.ini
the launcher
RPM
Running Hyperbola prototype 2nd 3rd
chat model
contact view addition
to a perspective
contacts view (addition)
Hello Hyperbola skeleton
saving window location/size
Runtime 2nd
application
extension registry
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Samples Manager
Scalability extension points
Scopes [See Preferences]
SDK [See Eclipse RCP concepts]
Serialization (of objects)
Services
Singletons (plug-ins)
Smack 2nd 3rd 4th 5th [See also Bundle/bundling Smack]
infrastructure integration in Hyperbola (approaches)
Splash screen 2nd
locale specific
StackPresentation coding examples
add/select/remove parts
PresentationFactory
requirements definition
size and position
stack creation
Startup
extension points
Status line
additions
contributions
Stepping functions in debugging
SWT (Standard Widget Toolkit) 2nd
FormLayout
Synchronizing
configuration
System menu
System tray integration
displays
tray item creation
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Target setup
Text editing 2nd
editing vs. editor
IDocument
text and StyledText
text plug-ins
TextViewers and TextEditors
Thin clients
Third-party library integration
bundling Smack
testing bundled Smack 2nd
Toolbar [See also Menus and toolbar creation/addition]
action tricks
adding controls
showing images and text
hide/show (Workbench windows customization)
TreeViewers
content providers overview
Tutorial and development set-up 2nd
checkpoint
development environment installation
examples (importance of)
Hyperbola
sample code
comparing
Samples Manager
target setup
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
UI decoupling
UI sketch
UI Workbench 2nd 3rd 4th [See also Adding key bindings]
Action extension points
adding IWorkbench adapters
and editors
and Resources plug-in
as an integration point
browsing
consolidating declarative action facilities
constomization capabilities
Contacts view creation
contribution extension points
customization capabilities
Workbench window customization [See also Custom window shapes]
default implementation features
extensibility (contribution-based)
extension point reference
functions (overview)
lifecycle
multiple windows
window navigation menu
perspective extension points
perspectives,views, and editors
retargetable actions
scalability extension points
standard (reusable) actions
startup extension points
Workbench configuration in Hyperbola multiple product configuration scenario
Workbench contributions in Hyperbola multiple product configuration scenario
UIs (user interfaces) 2nd [See also JFace]
and application development 2nd
elements
contacts list
SWT
UI Workbench
UI updating example
content provider
label provider
Update component 2nd 3rd
adding Update actions
extending Hyperbola
managing extensions
updating Hyperbola
and Java Web Start
automatic updates
configurator
configuring Update plug-ins
dynamic content handling
features 2nd 3rd
and plug-ins
branding 2nd
building
choosing ids
content
defining 2nd
directory structure
install handler
install/update
get plug-ins/add to target
Update site
creation
Update site
and delivering product function
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Variable plug-in
View pane menu
Views
and editors 2nd
communication
contributed views (showing)
customization [See Presentations]
in Hyperbola multiple products configuration
multiple instances/same view
progress view 2nd
registry
sharing
sticky
Workbench
reusable preference pages and views
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
Widget hierarchy
Window Images
Wizards
in Hyperbola multiple products configuration
Workbench [See UI Workbench]
Workbench advisors
ActionBarAdvisor
and product concept
configurers
types
Workbench preferences
WorkbenchAdvisor 2nd
closing the Workbench
configuration
exceptions and idleness
IWorkbenchConfigurer
lifecycle events
methods
WorkbenchWindowAdvisor 2nd
configuration
Workbench window customization 2nd
adding toggle actions
FormLayout
hide and show example
hide/show toolbar
quick search panel
Index
[A] [B] [C] [D] [E] [F] [H] [I] [J] [K] [L] [M] [N] [O] [P] [R] [S] [T] [U] [V] [W] [X]
x-internal/x-friends
Xalan
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement