Eclipse Plug-ins Third Edition

Eclipse Plug-ins Third Edition

Eclipse Plug-ins

Third Edition

Eric Clayberg

Dan Rubel

Upper Saddle River, NJ • Boston • Indianapolis • San Francisco

New York • Toronto • Montreal • London • Munich • Paris • Madrid

Capetown • Sydney • Tokyo • Singapore • Mexico City

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

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 United States please contact:

International Sales [email protected]

Visit us on the Web: informit.com/aw

Library of Congress Cataloging-in-Publication Data

Clayberg, Eric.

Eclipse : plug-ins / Eric Clayberg, Dan Rubel. -- 3rd ed.

p. cm.

Includes bibliographical references and index.

ISBN 0-321-55346-2 (pbk. : alk. paper)

1. Computer software--Development. 2. Eclipse (Electronic resource) 3.

Java (Computer program language) I. Rubel, Dan. II. Title.

QA76.76.D47C574 2008

005.13'3--dc22 2008047409

Copyright © 2009 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

501 Boylston Street, Suite 900

Boston, MA 02116

Fax: (617) 671-3447

ISBN-13: 978-0-321-55346-1

ISBN-10: 0-321-55346-2

Text printed in the United States on recycled paper at Courier in Stoughton, Massachusetts.

First printing, December 2008

(FOLSVH3OXJLQV7KLUG(GLWLRQ

7DEOHRI&RQWHQWV

&RS\ULJKW

3UDLVHIRU3UHYLRXV(GLWLRQV

7KH(FOLSVH6HULHV

)RUHZRUGE\6NLS0F*DXJKH\

)RUHZRUGE\6LPRQ$UFKHU

3UHIDFH

&KDSWHU8VLQJ(FOLSVH7RROV

6HFWLRQ*HWWLQJ6WDUWHG

6HFWLRQ7KH(FOLSVH:RUNEHQFK

6HFWLRQ6HWWLQJ8S<RXU(QYLURQPHQW

6HFWLRQ&UHDWLQJD3URMHFW

6HFWLRQ1DYLJDWLQJ

6HFWLRQ6HDUFKLQJ

6HFWLRQ:ULWLQJ&RGH

6HFWLRQ7HDP'HYHORSPHQW8VLQJ&96

6HFWLRQ5XQQLQJ$SSOLFDWLRQV

6HFWLRQ,QWURGXFWLRQWR'HEXJJLQJ

6HFWLRQ,QWURGXFWLRQWR7HVWLQJ

6HFWLRQ,QWURGXFWLRQWR0\O\Q

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU$6LPSOH3OXJLQ([DPSOH

6HFWLRQ7KH)DYRULWHV3OXJLQ

6HFWLRQ&UHDWLQJD3OXJLQ3URMHFW

6HFWLRQ5HYLHZLQJWKH*HQHUDWHG&RGH

6HFWLRQ%XLOGLQJD3URGXFW

6HFWLRQ,QVWDOOLQJDQG5XQQLQJWKH3URGXFW

6HFWLRQ'HEXJJLQJWKH3URGXFW

6HFWLRQ3'(9LHZV

6HFWLRQ:ULWLQJ3OXJLQ7HVWV

6HFWLRQ%RRN6DPSOHV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU(FOLSVH,QIUDVWUXFWXUH

6HFWLRQ6WUXFWXUDO2YHUYLHZ

6HFWLRQ3OXJLQ'LUHFWRU\RU-$5ILOH

6HFWLRQ3OXJLQ0DQLIHVW

6HFWLRQ$FWLYDWRURU3OXJLQ&ODVV

6HFWLRQ3OXJLQ0RGHO

6HFWLRQ/RJJLQJ

6HFWLRQ(FOLSVH3OXJLQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU7KH6WDQGDUG:LGJHW7RRONLW

6HFWLRQ6:7+LVWRU\DQG*RDOV

6HFWLRQ6:7:LGJHWV

6HFWLRQ/D\RXW0DQDJHPHQW

6HFWLRQ5HVRXUFH0DQDJHPHQW

6HFWLRQ*8,%XLOGHUV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU-)DFH9LHZHUV

6HFWLRQ/LVW2ULHQWHG9LHZHUV

(FOLSVH3OXJLQV7KLUG(GLWLRQ

6HFWLRQ7H[W9LHZHUV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU&RPPDQGVDQG$FWLRQV

6HFWLRQ&RPPDQGV

6HFWLRQ0HQXDQG7RROEDU&RQWULEXWLRQV

6HFWLRQ+DQGOHUV

6HFWLRQ.H\%LQGLQJV

6HFWLRQ,$FWLRQYHUVXV,$FWLRQ'HOHJDWH

6HFWLRQ:RUNEHQFK:LQGRZ$FWLRQV

6HFWLRQ2EMHFW$FWLRQV

6HFWLRQ9LHZ$FWLRQV

6HFWLRQ(GLWRU$FWLRQV

6HFWLRQ$FWLRQVDQG.H\%LQGLQJV

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU9LHZV

6HFWLRQ9LHZ'HFODUDWLRQ

6HFWLRQ9LHZ3DUW

6HFWLRQ9LHZ&RPPDQGV

6HFWLRQ/LQNLQJWKH9LHZ

6HFWLRQ6DYLQJ9LHZ6WDWH

6HFWLRQ7HVWLQJ

6HFWLRQ,PDJH&DFKLQJ

6HFWLRQ$XWRVL]LQJ7DEOH&ROXPQV

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU(GLWRUV

6HFWLRQ(GLWRU'HFODUDWLRQ

6HFWLRQ(GLWRU3DUW

6HFWLRQ(GLWLQJ

6HFWLRQ(GLWRU/LIHF\FOH

6HFWLRQ(GLWRU&RPPDQGV

6HFWLRQ/LQNLQJWKH(GLWRU

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU5HVRXUFH&KDQJH7UDFNLQJ

6HFWLRQ,5HVRXUFH&KDQJH/LVWHQHU

6HFWLRQ3URFHVVLQJ&KDQJH(YHQWV

6HFWLRQ%DWFKLQJ&KDQJH(YHQWV

6HFWLRQ3URJUHVV0RQLWRU

6HFWLRQ'HOD\HG&KDQJHG(YHQWV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU3HUVSHFWLYHV

6HFWLRQ&UHDWLQJD3HUVSHFWLYH

6HFWLRQ(QKDQFLQJDQ([LVWLQJ3HUVSHFWLYH

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU'LDORJVDQG:L]DUGV

6HFWLRQ'LDORJV

6HFWLRQ:L]DUGV

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

(FOLSVH3OXJLQV7KLUG(GLWLRQ

5HIHUHQFHV

&KDSWHU3UHIHUHQFH3DJHV

6HFWLRQ&UHDWLQJD3UHIHUHQFH3DJH

6HFWLRQ3UHIHUHQFH3DJH$3,V

6HFWLRQ3UHIHUHQFH$3,V

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU3URSHUWLHV

6HFWLRQ&UHDWLQJ3URSHUWLHV

6HFWLRQ'LVSOD\LQJ3URSHUWLHVLQWKH3URSHUWLHV'LDORJ

6HFWLRQ'LVSOD\LQJ3URSHUWLHVLQWKH3URSHUWLHV9LHZ

6HFWLRQ3URSHUW\3DJHV5HXVHGDV3UHIHUHQFH3DJHV

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU%XLOGHUV0DUNHUVDQG1DWXUHV

6HFWLRQ%XLOGHUV

6HFWLRQ0DUNHUV

6HFWLRQ1DWXUHV

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU,PSOHPHQWLQJ+HOS

6HFWLRQ8VLQJ+HOS

6HFWLRQ,PSOHPHQWLQJ+HOS

6HFWLRQ&RQWH[W6HQVLWLYH+HOS)

6HFWLRQ$FFHVVLQJ+HOS3URJUDPPDWLFDOO\

6HFWLRQ&KHDW6KHHWV

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU,QWHUQDWLRQDOL]DWLRQ

6HFWLRQ([WHUQDOL]LQJWKH3OXJLQ0DQLIHVW

6HFWLRQ([WHUQDOL]LQJ3OXJLQ6WULQJV

6HFWLRQ8VLQJ)UDJPHQWV

6HFWLRQ0DQXDO7HVWLQJ

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU&UHDWLQJ1HZ([WHQVLRQ3RLQWV

6HFWLRQ7KH([WHQVLRQ3RLQW0HFKDQLVP

6HFWLRQ'HILQLQJDQ([WHQVLRQ3RLQW

6HFWLRQ&RGH%HKLQGDQ([WHQVLRQ3RLQW

6HFWLRQ([WHQVLRQ3RLQW'RFXPHQWDWLRQ

6HFWLRQ8VLQJWKH([WHQVLRQ3RLQW

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU)HDWXUHV%UDQGLQJDQG8SGDWHV

6HFWLRQ)HDWXUH3URMHFWV

6HFWLRQ%UDQGLQJ

6HFWLRQ8SGDWH6LWHV

6HFWLRQ5)56&RQVLGHUDWLRQV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU%XLOGLQJD3URGXFW

6HFWLRQ$%ULHI,QWURGXFWLRQWR$QW

(FOLSVH3OXJLQV7KLUG(GLWLRQ

6HFWLRQ%XLOGLQJZLWK3'(

6HFWLRQ'HEXJJLQJWKH3'(%XLOGSURFHVV

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU*()*UDSKLFDO(GLWLQJ)UDPHZRUN

6HFWLRQ*()$UFKLWHFWXUH

6HFWLRQ*()0RGHO

6HFWLRQ*()&RQWUROOHU

6HFWLRQ*())LJXUHV

6HFWLRQ*()LQDQ(FOLSVH9LHZ

6HFWLRQ*()LQDQ(FOLSVH(GLWRU

6HFWLRQ3DOHWWH

6HFWLRQ6XPPDU\

5HIHUHQFHV

&KDSWHU$GYDQFHG7RSLFV

6HFWLRQ$GYDQFHG6HDUFK²5HIHUHQFH3URMHFWV

6HFWLRQ$FFHVVLQJ,QWHUQDO&RGH

6HFWLRQ$GDSWHUV

6HFWLRQ2SHQLQJD%URZVHURU&UHDWLQJDQ(PDLO

6HFWLRQ7\SHV6SHFLILHGLQDQ([WHQVLRQ3RLQW

6HFWLRQ0RGLI\LQJ(FOLSVHWR)LQG3DUW,GHQWLILHUV

6HFWLRQ/DEHO'HFRUDWRUV

6HFWLRQ%DFNJURXQG7DVNV²-REV$3,

6HFWLRQ3OXJLQ&ODVV/RDGHUV

6HFWLRQ(DUO\6WDUWXS

6HFWLRQ5LFK&OLHQW3ODWIRUP

6HFWLRQ&RQFOXVLRQ

5HIHUHQFHV

$SSHQGL[$(FOLSVH3OXJLQVDQG5HVRXUFHV

6HFWLRQ$3OXJLQV

6HFWLRQ$5HVRXUFHV

$SSHQGL[%5HDG\IRU5DWLRQDO6RIWZDUH

7UDGHPDUNV

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

To our wives, Karen and Kathy, and our children, Beth, Lauren, Lee, and David

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

Praise for Previous Editions

“I’m often asked, ‘What are the best books about Eclipse?’ Number one on my list, every time, is

Eclipse: Building Commercial-Quality Plug-ins

. I find it to be the clearest and most relevant book about Eclipse for the real-world software developer. Other Eclipse books focus on the internal Eclipse architecture or on repeating the Eclipse documentation, whereas this book is laser focused on the issues and concepts that matter when you’re trying to build a product.”

— Bjorn Freeman-Benson

Director, Open Source Process, Eclipse Foundation

“As the title suggests, this massive tome is intended as a guide to best practices for writing Eclipse plug-ins. I think in that respect it succeeds handily. Before you even think about distributing a plug-in you’ve written, read this book.”

— Ernest Friedman-Hill

Sheriff, JavaRanch.com

“If you’re looking for just one Eclipse plug-in development book that will be your guide, this is the one. While there are other books available on Eclipse, few dive as deep as

Eclipse: Building Commercial-Quality Plug-ins

.”

— Simon Archer

Eclipse: Building Commercial-Quality Plug-ins

was an invaluable training aid for all of our team members. In fact, training our team without the use of this book as a base would have been virtually impossible. It is now required reading for all our developers and helped us deliver a brand-new, very complex product on time and on budget thanks to the great job this book does of explaining the process of building plug-ins for Eclipse.”

— Bruce Gruenbaum

“This is easily one of the most useful books I own. If you are new to developing Eclipse plug-ins, it is a ‘must-have’ that will save you

lots

of time and effort. You will find lots of good advice in here, especially things that will help add a whole layer of professionalism and completeness to any plug-in. The book is very focused, well-structured, thorough, clearly written, and doesn’t contain a single page of ‘waffly page filler.’ The diagrams explaining the relationships between the different components and manifest sections are excellent and aid in understanding how everything fits together. This book goes well beyond Actions, Views, and Editors, and I think everyone will benefit from the authors’ experience. I certainly have.”

— Tony Saveski

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

“The authors of this seminal book have decades of proven experience with the most productive and robust software engineering technologies ever developed. Their experiences have now been well applied to the use of Eclipse for more effective Java development. A must-have for any serious software engineering professional!”

— Ed Klimas

“Just wanted to also let you know this is an excellent book! Thanks for putting forth the effort to create a book that is easy to read

and

technical at the same time!”

— Brooke Hedrick

“The key to developing great plug-ins for Eclipse is understanding where and how to extend the IDE, and that’s what this book gives you. It is a must for serious plug-in developers, especially those building commercial applications. I wouldn’t be without it.”

— Brian Wilkerson

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

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

David Carlson

Eclipse Distilled

0-321-28815-7

Eric Clayberg and Dan Rubel

Eclipse Plug-ins,Third Edition

0-321-55346-2

Adrian Colyer, Andy Clement, George Harley, and Matthew Webster

Eclipse AspectJ: Aspect-Oriented Programming with AspectJ and the Eclipse AspectJ Development Tools

0-321-24587-3

Naci Dai, Lawrence Mandel, and Arthur Ryman

Eclipse Web Tools Platform: Developing Java

Web Applications

0-321-39685-5

Erich Gamma and Kent Beck

Contributing to Eclipse: Principles, Patterns, and Plug-Ins

0-321-20575-8

Jeff McAffer and Jean-Michel Lemieux

Eclipse Rich Client Platform: Designing, Coding, and Packaging Java

Applications

0-321-33461-2

Diana Peh, Nola Hague, and Jane Tatchell

BIRT: A Field Guide to Reporting, Second Edition

0-321-58027-3

Dave Steinberg, Frank Budinsky, Marcelo Paternostro, and Ed Merks

EMF: Eclipse Modeling Framework

0-321-33188-5

Jason Weathersby,Tom Bondur, Iana Chatalbasheva, and Don French

Integrating and Extending BIRT, Second Edition

0-321-58030-3

For more information on books in this series visit www.awprofessional.com/series/eclipse

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

Foreword

To the millions of developers, engineers, and users all over the world, Eclipse is an extensible platform for tool integration. To the hundreds of thousands of commercial customers using it to develop plug-ins or complete tool platforms, Eclipse represents a proven, reliable, scalable technology on which commercial products can be quickly designed, developed, and deployed.

To the thousands of students and researchers, Eclipse represents a stable platform for innovation, freedom, and experimentation. To all these individuals, groups, and organizations, Eclipse is a vendor-neutral platform for tool integration supported by a diverse Eclipse Ecosystem.

The Eclipse vendor-neutral platform is built on industry standards, which support a wide range of tools, platforms, and languages. The Eclipse Technology is royalty-free and has worldwide redistribution rights. The platform was designed from a clean slate to be extensible and to provide exemplary tools.

Eclipse development is based on rules of open source engagements. This includes open, transparent, merit-based, and collaborative development. All individuals can participate and contribute. All plans are developed in the public arena. This platform and the open source development process creates an environment for creativity, originality, and freedom. Eclipse is unparalleled in today’s software-tool environment.

The software-tool industry is undergoing massive changes from the commoditization of the technology to the company consolidation. New technology efforts are being redesigned, while a common set of tooling infrastructure is adopted as an industry standard. Successful developers and development paradigms are being challenged to adopt new skills and new, more efficient methods. Old business models are being challenged with free software, and new business models are being developed. xxxiii

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

xxxiv Foreword

The software-tool industry is deeply appreciative of Eric Clayberg and

Dan Rubel for this authoritative book. This book provides the knowledge base so that developers, engineers, and users can learn and use the Eclipse

Technology. This enables them to respond to these technology and industry change agents.

Eric and Dan leverage long careers of building software tooling. They each have extensive experience with using Smalltalk for twenty years, Java for thirteen years, and Eclipse for nine years. They have developed extensive vendor and customer relationships that enable them to experience firsthand the necessary elements for building successful software. They are able to combine this direct knowledge of the technology with the experiences of the users to create a book that provides an in-depth description of the process to build commercial-quality Eclipse extensions.

This book provides an introduction and overview to the new developer of the entire process of plug-in development, including all the best practices to achieve high-quality results. This is a reference book for experienced Eclipse developers. It discusses the APIs and demonstrates many samples and examples. Detailed tutorials are provided for both new and experienced developers.

Eric and Dan leverage their broad knowledge of user interface (UI) development and present the Eclipse SWT UI. This establishes the building blocks for all Eclipse UI development. These authors articulate the development challenges of building tool software and establish proven in-depth solutions to the problems.

If you are a developer, engineer, or user wishing to build or use Eclipse, this book provides both a foundation and reference. It also provides the intellectual foundation to contribute to the open source Eclipse project and to develop commercial software.

—Skip McGaughey

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

Foreword

In the 1990s, when Java was in its infancy, learning the Java class libraries involved studying a handful of classes in four or five packages. The Java class libraries have grown in size and complexity, presenting a significant problem to developers wishing to learn Java today. Just like Java, the Eclipse platform has necessarily grown over the years, and therefore considerably more time and effort is required to learn Eclipse 3.4 than its predecessors. One of the principles of the Eclipse platform is that a plug-in should integrate seamlessly with the workbench and with other plug-ins. To achieve seamless integration, it is necessary for plug-in developers to understand the best practices, conventions, and strategies related to building software for Eclipse.

Eclipse Plug-ins

covers everything you need to know to develop Eclipse plug-ins of which you will be proud.

Through the development of a

Favorites

plug-in, the Eclipse Standard

Widget Toolkit (SWT) and JFace frameworks are thoroughly discussed, teaching you how to build professional-looking user interfaces such as views, editors, preferences pages, and dialogs. In addition to stock-in-trade subjects, such as user-interface design, lesser-understood Eclipse topics (for example, building features and product branding) are extensively covered, as well as the best discussion I have seen on using Ant to build a product from a single source that targets multiple versions of Eclipse.

Java developers new to Eclipse often have difficulty understanding the extension point mechanism and the critical link between a plug-in’s declarative manifest and the Java code necessary to implement a plug-in’s functional behavior. This book serves as a roadmap to using the Plug-in Development

Environment (PDE) and the extension points defined by the Eclipse platform.

It also provides the missing link that developers need to understand the xxxv

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

xxxvi Foreword

aspects of a plug-in that should be described in the manifest, how to develop a plug-in using existing extension points, and how to create new extension points to which other developers may further contribute.

When I first saw CodePro, I was both impressed with the productivity gains it brought to Eclipse and the extent to which its plug-ins integrated with the Eclipse platform. Having used CodePro for a while, it has become a part of my development toolkit that I cannot do without. By drawing on their extensive experience gained while developing CodePro, Eric and Dan have done an excellent job of capturing in this book those aspects of plug-in development necessary to create a high-quality and professional-looking Eclipse product.

—Simon Archer

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

Preface

When we were first exposed to Eclipse back in late 1999, we were struck by the magnitude of the problem IBM was trying to solve. IBM wanted to unify all its development environments on a single code base. At the time, the company was using a mix of technology composed of a hodgepodge of C/C++,

Java, and Smalltalk.

Many of IBM’s most important tools, including the award-winning Visual-

Age for Java IDE, were actually written in Smalltalk—a wonderful language for building sophisticated tools, but one that was rapidly losing market share to languages like Java. While IBM had one of the world’s largest collections of

Smalltalk developers, there wasn’t a great deal of industry support for it outside of IBM, and there were very few independent software vendors (ISVs) qualified to create Smalltalk-based add-ons.

Meanwhile, Java was winning the hearts and minds of developers worldwide with its promise of easy portability across a wide range of platforms, while providing the rich application programming interface (API) needed to build the latest generation of Web-based business applications. More important, Java was an object-oriented (OO) language, which meant that IBM could leverage the large body of highly skilled object-oriented developers it had built up over the years of creating Smalltalk-based tools. In fact, IBM took its premiere Object Technology International (OTI) group, which had been responsible for creating IBM’s VisualAge Smalltalk and VisualAge Java environments (VisualAge Smalltalk was the first of the VisualAge brand family and VisualAge Java was built using it), and tasked the group with creating a highly extensible integrated development environment (IDE) construction set based in Java. Eclipse was the happy result.

xxxvii

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

xxxviii Preface

OTI was able to apply its highly evolved OO skills to produce an IDE unmatched in power, flexibility, and extensibility. The group was able to replicate most of the features that had made Smalltalk-based IDEs so popular the decade before, while simultaneously pushing the state of the art in IDE development ahead by an order of magnitude.

The Java world had never seen anything as powerful or as compelling as

Eclipse, and it now stands, with Microsoft’s .NET, as one of the world’s premier development environments. That alone makes Eclipse a perfect platform for developers wishing to get their tools out to as wide an audience as possible.

The fact that Eclipse is completely free and open source is icing on the cake.

An open, extensible IDE base that is available for free to anyone with a computer is a powerful motivator to the prospective tool developer.

It certainly was to us. At Instantiations and earlier at ObjectShare, we had spent the better part of a decade as entrepreneurs focused on building add-on tools for various IDEs. We had started with building add-ons for Digitalk’s

Smalltalk/V, migrated to developing tools for IBM’s VisualAge Smalltalk, and eventually ended up creating tools for IBM’s VisualAge Java (including our award-winning VA Assist product and our jFactor product, one of the world’s first Java refactoring tools). Every one of these environments provided a means to extend the IDE, but they were generally not well-documented and certainly not standardized in any way. Small market shares (relative to tools such as VisualBasic) and an eclectic user base also afflicted these environments and, by extension, us.

As an Advanced IBM Business Partner, we were fortunate to have built a long and trusted relationship with the folks at IBM responsible for the creation of Eclipse. That relationship meant that we were in a unique position to be briefed on the technology and to start using it on a daily basis nearly a year and half before the rest of the world even heard about it. When IBM finally announced Eclipse to the world in mid-2001, our team at Instantiations had built some of the first demo applications IBM had to show. Later that year when IBM released its first Eclipse-based commercial tool, WebSphere Studio

Application Developer v4.0 (v4.0 so that it synchronized with its then current

VisualAge for Java v4.0), our CodePro product became the very first commercial add-on available for it (and for Eclipse in general) on the same day.

Currently, the CodePro product adds hundreds of enhancements to

Eclipse and any Eclipse-based IDE. Developing CodePro over the last several years has provided us with an opportunity to learn the details of Eclipse development at a level matched by very few others (with the obvious exception of the IBM and OTI developers, who eat, sleep, and breathe this stuff on a daily basis). CodePro has also served as a testbed for many of the ideas and tech-

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

Preface xxxix

niques presented in this book, providing us with a unique perspective from which to write.

Goals of the Book

This book, originally titled

Eclipse: Building Commercial-Quality Plug-ins

, provides an in-depth description of the process involved in building commercial-quality extensions for the Eclipse and the IBM Software Development

Platform (SDP)—IBM’s commercial version of Eclipse—development environments. To us, “commercial-quality” is synonymous with “commercialgrade” or “high-quality.” Producing a

commercial-quality

plug-in means going above and beyond the minimal requirements needed to integrate with

Eclipse. It means attending to all those details that contribute to the “fit and polish” of a commercial offering.

In the world of Eclipse plug-ins, very few people take the time to really go the extra mile, and most plug-ins fall into the open source, amateur category.

For folks interested in producing high-quality plug-ins (which would certainly be the case for any software company wanting to develop Eclipse-based products), there are many details to consider. Our book is meant to encompass the entire process of plug-in development, including all the extra things that need to be done to achieve high-quality results. This book has several complementary goals:

• Provide a quick introduction to using Eclipse for new users

• Provide a reference for experienced Eclipse users wishing to expand their knowledge and improve the quality of their Eclipse-based products

• Provide a detailed tutorial on creating sophisticated Eclipse plug-ins suitable for new and experienced users

The first three chapters introduce the Eclipse development environment and outline the process of building a simple plug-in. The intention of these chapters is to help developers new to Eclipse quickly pull together a plug-in they can use to experiment with.

The first chapter, in particular, introduces the reader to the minimum set of Eclipse tools that he or she will need to build plug-ins. It is a fairly quick overview of the Eclipse IDE and relevant tools (one could write an entire book on that topic alone), and we would expect expert Eclipse users to skip that chapter entirely.

The second chapter introduces the example that we will use throughout most of the book and provides a very quick introduction to building a working plug-in from start to finish. The third chapter presents a high-level overview of the Eclipse architecture and the structure of plug-ins and extension points.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

xl Preface

The fourth and fifth chapters cover the Standard Widget Toolkit (SWT) and JFace, which are the building blocks for all Eclipse user interfaces (UIs).

These chapters can act as a stand-alone reference; they are intended to provide just enough detail to get you going. Both of these topics are rich enough to warrant entire books and several are currently available.

The subsequent chapters, comprising the bulk of this book, focus on describing each of the various aspects of plug-in development and providing the reader with in-depth knowledge of how to solve the various challenges involved. Each chapter focuses on a different aspect of the problem, and includes an overview, a detailed description, a discussion of challenges and solutions, diagrams, screenshots, cookbook-style code examples, relevant API listings, and a summary.

We have structured the book so that the most important material required for every plug-in project appears in the first half of it. Some of the packagingand building-oriented material is placed at the end (for example, features and product builds). This organizational scheme left several topics that, while not critical to every plug-in, were important to the creation of commercial-quality plug-ins. These topics have been placed in the second half of the book in an order based on the importance of each and how it related to earlier material.

Internationalization, for example, is one of those topics. It isn’t critical, and it isn’t even all that complicated when you get right down to it. It is, however, important to the book’s premise, so we felt it was a topic we needed to include.

Since we aren’t assuming that the reader is an Eclipse expert (or even a plugin developer), we have tried to take the reader through each of the important steps in as much detail as possible. While it is true that this is somewhat introductory, it is also an area that most plug-in developers totally ignore and have little or no experience with.

Sometimes a developer needs a quick solution, while at other times that same developer needs to gain in-depth knowledge about a particular aspect of development. The intent is to provide several different ways for the reader to absorb and use the information so that both needs can be addressed. Relevant

APIs are included in several of the chapters so that the book can be used as a stand-alone reference during development without requiring the reader to look up those APIs in the IDE. Most API descriptions are copied or paraphrased from the Eclipse platform Javadoc.

As the originators of Eclipse and a major consumer of Eclipse-based technology, IBM is justifiably concerned that new plug-ins meet the same highquality standards that IBM adheres to. To that end, IBM has established a rigorous

Ready for Rational Software

(RFRS) certification program meant to

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

Preface xli

ensure the availability of high-quality add-ons to Eclipse and the IBM Software Development Platform. RFRS certification should be one of the ultimate goals for anyone wishing to build and market Eclipse plug-ins. Every chapter covers any relevant RFRS certification criteria and strategies.

The examples provided as part of the chapters describe building various aspects of a concrete Eclipse plug-in that you will see evolve over the course of the book. When used as a reference rather than read cover-to-cover, you will typically start to look in one chapter for issues that are covered in another.

To facilitate this type of searching, every chapter contains numerous cross-references to related material that appears in other chapters.

Intended Audience

The audience for this book includes Java tool developers wishing to build products that integrate with Eclipse and other Eclipse-based products, relatively advanced Eclipse users wishing to customize their environments, or anyone who is curious about what makes Eclipse tick. You do not need to be an expert Eclipse user to make use of this book because we introduce most of what you need to know to use Eclipse in Chapter 1, Using Eclipse Tools. While we don’t assume any preexisting Eclipse knowledge, we do anticipate that the reader is a fairly seasoned developer with a good grasp of Java and at least a cursory knowledge of extensible markup language (XML).

Conventions Used in This Book

The following formatting conventions are used throughout the book.

Bold

—the names of UI elements such as menus, buttons, field labels, tabs, and window titles

Italic—

emphasize new terms and Web site addresses

Courier

—code examples, references to class and method names, and filenames

Courier Bold

—emphasize code fragments

“Quoted text”—quotation marks surrounding text indicates words to be entered by the user

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

xlii Preface

What’s New in the Third Edition

In this edition, we use the same

Favorites

view example as in the first and second editions, but have reworked much of the content and recreated the code from scratch. Some Eclipse concepts, such as views and editors, are similar but with additional functionality and capabilities; other areas, such as commands,

GEF and PDE Build have been added. The following are some of the major changes in this third edition:

Eclipse Command Framework

The Eclipse command framework replaces the older Action framework.

Throughout the book, use of the older Action framework has been replaced with new content describing how to accomplish the same thing with the new command framework. This is most obvious in Chapter 6 where the first half of the chapter is entirely devoted to the command framework. Chapter 7 and

Chapter 8 also have lots of new material describing use of commands with views and editors.

Eclipse 3.4 and Java 5

All of the screen shots, text and code examples throughout the book have been updated to use the latest Eclipse 3.4 API and Java 5 syntax. Chapter 1 has been overhauled to include descriptions of new capabilities in Eclipse 3.4

including a new overview of using Mylyn and discussion of new preferences.

Both Chapter 1 and Chapter 2 cover cool PDE and SWT tools available in

Eclipse 3.4.

New GEF Chapter

GEF, the Graphical Editing Framework from Eclipse.org, provides a toolkit for building dynamic interactive graphical user interface elements. Chapter 20 takes you step by step through the process of building a GEF-based view for graphically presenting the relationships between the favorites items and their underlying resources. We then go further, building a GEF-based editor with the ability to add, move, resize, and delete the graphical elements representing those favorites items.

Put PDE Build through its paces

Over the past several years, the PDE build process has been steadily maturing.

The proprietary Ant scripts in Chapter 19 of earlier versions of our book have been completely replaced with step-by-step instructions for getting an Eclipse

PDE Build process up and running to automate assembly of your product for distribution.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

Preface xliii

New “p2” update site creation description

“p2” debuts in Eclipse 3.4, replacing the older Update Manager. We take you through the process of using update sites and then building your own in Section 18.3, Update Sites, on page 679.

Acknowledgments

The authors would like to thank all those who have had a hand in putting this book together or who gave us their support and encouragement throughout the many months it took to create.

To our comrades at Instantiations, who gave us the time and encouragement to work on this book: Rick Abbott, Brent Caldwell, Devon Carew, Jim

Christensen, Taylor Corey, Dianne Engles, Marta George, Nick Gilman, Seth

Hollyman, Mark Johnson, Ed Klimas, Tina Kvavle, Alexander Lobas, Warren

Martin, David Masulis, Nancy McClure, Steve Messick, Alexander Mitin,

Gina Nebling, John O’Keefe, Keerti Parthasarathy, Phil Quitslund, Mark

Russell, Rob Ryan, Andrey Sablin, Balaji Saireddy, Konstantin Scheglov,

Chuck Shawan, Bryan Shepherd, Julie Taylor, Mike Taylor, Solveig Viste,

Brian Wilkerson, and Jaime Wren.

A special thanks to Jaime Wren, who provided much of the basic research and initial content for Chapter 20.

To our agent, Laura Lewin, and the staff at Studio B, who encouraged us from day one and worked tirelessly on our behalf.

To our editors, Greg Doench and John Neidhart, our production editors,

Elizabeth Ryan and Kathleen Caren, our copy editors, Marilyn Rash and Camie

Goffi, our editorial assistants, Michelle Housley and Mary Kate Murray, our art director, Sandra Schroeder, our marketing managers, Brandon Prebynski and Beth Wickenhiser, and the staff at Pearson, for their encouragement and tremendous efforts in preparing this book for production.

To Simon Archer and Robert Konigsberg who contributed an unparalleled number of changes and suggestions to various editions of the book, and helped us improve them in almost every dimension.

To Linda Barney who helped us polish and edit the second edition.

To our technical reviewers who helped us enhance the book in many ways:

Matt Lavin, Kevin Hammond, Mark Russell, Keerti Parthasarathy, Jaime

Wren, Joe Bowbeer, Brian Wilkerson, Joe Winchester, David Whiteman, Boris

Pruesmann, Balaji Saireddy, and Raphael Enns.

To the many readers of the first and second editions who contributed errata that have gone into this third edition: Bruce Gruenbaum, Tony Saveski,

James Carroll, Tony Weddle, Karen Ploski, Lori Kissell, Brian Vosburgh, Peter

Nye, Chris Lott, David Watkins, Simon Archer, Mike Wilkins, Brian Penn,

Bernd Essann, Eric Hein, Dave Hewitson, Frank Parrott, Catherine Suess,

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

xliv Preface

William Beebe, Janine Kovack, David Masulis, Jim Norris, Jim Wingard, Roy

Johnston, David Karr, Chris Gage, Paul Tamminga, Asim Ullah, and Ebru

Aktuna.

To the series editors, Erich Gamma, Lee Nackman, and John Weigand, for their thoughtful comments and for their ongoing efforts to make Eclipse the best development environment in the world.

We would also like to thank our wives, Karen and Kathy, for their endless patience, and our children, Beth, Lauren, Lee, and David, for their endless inspiration.

About the Authors

Eric Clayberg

is Senior Vice President for Product Development for Instantiations, Inc. Eric is a seasoned software technologist, product developer, entrepreneur, and manager with more than seventeen years of commercial software development experience, including twelve years of experience with Java and nine years with Eclipse. He is the primary author and architect of more than a dozen commercial Java and Smalltalk add-on products, including the popular WindowBuilder Pro, CodePro, and the awardwinning VA Assist product lines. He has a B.S. from MIT, an MBA from

Harvard, and has cofounded two successful software companies—

ObjectShare and Instantiations.

Dan Rubel

is Chief Technology Officer for Instantiations,

Inc. He is an entrepreneur and an expert in the design and application of OO technologies with more than fifteen years of commercial software development experience, including thirteen years of experience with Java and nine years with Eclipse. He is the architect and product manager for several successful commercial products, including RCP

Developer, WindowTester, jFactor and jKit, and has played key design and leadership roles in other commercial products such as VA Assist, and CodePro. He has a B.S. from Bucknell and is a cofounder of Instantiations.

Instantiations is an Advanced IBM Business Partner and developer of many commercial add-ons for Eclipse and IBM’s VisualAge, WebSphere, and

Rational product lines. Instantiations is a member of the Eclipse Foundation and a contributor to the Eclipse open source effort with responsibility for the

Eclipse Collaboration Tools project known as Koi and for the Eclipse Pollinate project (Beehive).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

Preface xlv

How to Contact Us

While we have made every effort to make sure that the material in this book is timely and accurate, Eclipse is a rapidly moving target and it is quite possible that you may encounter differences between what we present here and what you experience using Eclipse. The Eclipse UI has evolved considerably over the years, and the latest 3.4 release and upcoming 3.5 release are no exceptions. While we have targeted it at Eclipse 3.4 and used it for all of our examples, this book was completed after Eclipse 3.4 was finished and during the initial phases of development of Eclipse 3.5. If you are using an older or newer version of Eclipse, this means that you may encounter various views, dialogs, and wizards that are subtly different from the screenshots herein.

• Questions about the book’s technical content should be addressed to:

[email protected]

• Sales questions should be addressed to Addison-Wesley at:

www.informit.com/store/sales.aspx

• Source code for the projects presented can be found at:

www.qualityeclipse.com/projects

• Errata can be found at:

www.qualityeclipse.com/errata

• Tools used and described can be found at:

www.qualityeclipse.com/tools

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1

Using Eclipse Tools

This chapter discusses using the Eclipse development environment to create

Java applications and, more to the point, to create enhancements for Eclipse itself. We begin with an explanation of where to get Eclipse and how to set it up. This is followed by a quick introduction to the Eclipse user interface (UI) and how it can be customized. Next, this chapter introduces a number of important Eclipse tools and describes how they are used to create an initial

Java project, navigate and search the environment, and create and edit Java code. Eclipse developers typically want to work as part of a team and share their code with other members of their team, so this chapter also includes the setup and use of the Concurrent Versions System (CVS), which ships as part of Eclipse. After creating an initial Java project and class, we follow up with details for executing, debugging, and testing the code that has been written.

1.1

Getting Started

Before using Eclipse, download it from the Web, install it, and set it up.

1.1.1

Getting Eclipse

The main Web site for Eclipse is

www.eclipse.org

(see Figure 1–1 on page 2).

On that page, you can see the latest Eclipse news and links to a variety of online resources, including articles, newsgroups, bug tracking (see Section

21.2.2, Bugzilla—Eclipse bug tracking system, on page 782), and mailing lists.

1

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

2

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–1

Eclipse.org

home page.

The latest version of Eclipse can be downloaded from the main download page at

www.eclipse.org/downloads

. Starting with Eclipse 3.3, multiple

Eclipse distributions are available ranging from distributions suitable for Java and J2EE application development to distributions suitable for RCP and plugin development. As you will be focusing on developing plug-ins, go ahead and download the

Eclipse for RCP/Plug-in Developers

distribution. Choose the download link corresponding to your platform and save the Eclipse zip file to your computer’s hard drive. This will generally be a very large file (>150 MB), so be patient unless you have sufficient bandwidth available to quickly download the file.

If you need access to other builds (including other releases as well as recent nightly and integration builds), a more detailed download page is available at

http://download.eclipse.org/eclipse/downloads

. Unless you are involved in the development of Eclipse itself, you should avoid

integration

or

nightly

builds.

The download page for each release includes various notes concerning that

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.2

The Eclipse Workbench 3

release as well as links to every platform version. Eclipse supports a large number of platforms, including Windows, Linux, Solaris, HP, Mac OS X, and others.

Java Runtime Environment

Eclipse is a Java program, but it does not include the Java Runtime Environment (JRE) necessary to make it run. Eclipse

3.4 can be used with any JRE newer than version 1.4, and most Java developers will already have a suitable JRE installed on their machines. If you don’t have a

JRE installed on your computer, you can download and install one from

java.sun.com.

1.2

1.1.2

Installation

Once the Eclipse zip file has been successfully downloaded, unzip it to your hard drive. Eclipse does not modify the Windows registry, so it does not matter where it is installed. For the purposes of this book, assume that it has been installed into

C:\eclipse

.

The Eclipse Workbench

To start Eclipse, double-click on the eclipse.exe

file in the

C:\eclipse directory. The first time Eclipse is launched, it displays a dialog in which you can select the location for your workspace directory (typically a directory underneath your user directory). To avoid seeing this dialog every time you start Eclipse, check the

Use this as the default and do not ask again

option.

Tip

: Creating a shortcut for launching Eclipse provides a way to specify an alternative workspace directory as well as increases the amount of memory allocated to the program. For example:

C:\eclipse\eclipse.exe -data C:\MyWorkspace -vmargs -Xms128M -Xmx512M

-XX:MaxPermSize=256m

In this example, the workspace has been set to

C:\MyWorkspace

, the starting amount of memory to 128 MB, the maximum amount of memory to 512 MB and the maximum PermSize to 256 MB. Setting the workspace location is helpful if you plan to migrate to newer versions of Eclipse in the future. You can specify these settings in the eclipse.ini

file located in the Eclipse installation directory. A complete list of these and other command-line switches, such as

-vm

and

-showlocation

, can be found in the online help

(see Chapter 15, Implementing Help) under

Workbench User Guide > Tasks >

Running Eclipse

. For more on memory usage, see

www.qualityeclipse.com/doc/memory.html

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4 CHAPTER 1 • Using Eclipse Tools

In a few moments, the main Eclipse workbench window appears (see Figure 1–2). Normally, it consists of a main menu bar and toolbar as well as a number of tiled panes known as views and editors (these will be discussed in great detail in Chapters 7, Views, and 8, Editors). Initially, only a full-screen welcome page, known as the

Welcome

view, is visible and fills the entire workbench window.

Figure 1–2

Eclipse workbench window.

The

Welcome

view opens automatically the first time that Eclipse is launched (it can be reopened at any time by using the

Help > Welcome

command). Take a moment to look it over as it provides links to other tools and resources to get you started with Eclipse such as an overview, tutorial, and a list of sample applications.

Closing the

Welcome

view (by clicking the “

X

” button on its title tab) will reveal several additional views (see Figure 1–3).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.2

The Eclipse Workbench

5HWXUQWR7DEOHRI&RQWHQWV

5

Figure 1–3

Workbench window with the Package Explorer view active.

1.2.1

Perspectives, views, and editors

Collectively, the combination of various views (e.g.,

Package Explorer, Hierarchy

,

Task List

,

Outline, Problems, Javadoc

, and

Declaration

) and editors

(used to work with various resources) visible within the Eclipse workbench are known as a

perspective

. A perspective can be thought of as one page within the Eclipse workbench. Multiple perspectives can be open at one time, and each has an icon (with or without a text description) visible in the perspective toolbar at the upper right corner of the workbench window. The perspective that is currently active has its name shown in the title bar of the window and its icon appears selected.

Views are typically used to navigate resources and modify properties of a resource. Any changes made within a view are saved immediately. By contrast, editors are used to view or modify a specific resource and follow the common open-save-close model.

Every perspective has its own set of views, but open editors are shared by all open perspectives. Only a single instance of any one view can be open in a given perspective, while any number of editors of the same type can be open at one time.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6 CHAPTER 1 • Using Eclipse Tools

The currently active view or editor has its title bar highlighted. This is the view or editor that will be the recipient of any global actions such as cut, copy, or paste. All the other panes are inactive and their title bars appear grayed out.

For instance, when you click on the

Package Explorer

view, its title bar becomes highlighted, indicating that it is active (see Figure 1–3), and the title bars of the other views turn gray, indicating that they are now inactive.

Views and editors can be resized by dragging the sizing border that appears on each side of the pane. Since Eclipse uses a tiled display for each of its panes, making one larger makes its neighbors smaller, and vice versa.

Panes can be moved around by dragging their individual title bars. If you drag a view onto another view, the two views will stack up with tabs indicating each of the views. Selecting a tab brings that view to the top of the stack.

If a view is dropped into the sizing area between views, the view grabs a portion of the available area and inserts itself next to the view that previously occupied that space. The views that originally occupied that space shrink in size to accommodate the new view.

Right-clicking on a view’s tab and selecting the

Fast View

command causes the view to dock to the

fast view

bar at the bottom edge of the window

(you can drag the fast view bar to the left or right side of the window as well).

Fast views remain docked to the fast view bar as icons until clicked on, at which point they expand to overlap most of the window area. Fast views are ideal for views that don’t need to be seen all the time, but require a great deal of screen area when they are visible.

Right-clicking and selecting the

Detached

command causes the view to open into a separate, floating window that can be moved outside of the workbench area or that can float on top of the workbench area. Selecting the

Detached

command a second time will reattach the view into the workbench window.

Many different views are defined within Eclipse and only a few are visible when the workbench first opens. To add views to a perspective, select the

Window > Show View

command and choose the view you would like to see

(or the

Other…

command to see a list of all views defined in the system).

Tip

: Many third-party plug-ins are available, providing enhancements to the various Eclipse perspectives. The

Eclipse Plugin Central (EPIC)

portal site at

www.eclipseplugincentral.com

provides access to hundreds of commercial and open source Eclipse plug-ins and services.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.2

The Eclipse Workbench 7

1.2.1.1

Java perspectives

At this point, we should quickly review the various perspectives you are most likely to use while developing plug-ins. The initial perspective shown in the workbench window is the

Java

perspective (see Figure 1–3).

Eclipse includes two perspectives for the development of Java code. Selecting the

Window > Open Perspective > Java

command opens the first, known as the

Java

perspective.

The primary view within the Java perspective is the

Package Explorer

. The

Package Explorer

shows the hierarchy of Java files and resources within the

Java projects loaded into your workbench, providing a very Java-centric view of resources rather than a file-centric view.

For example, rather than showing Java packages as nested folders as in the

Navigator

view (see Section 1.2.1.2, Resource perspective, on page 9), the

Package Explorer

shows each package as a separate element in a flattened hierarchy. Any JAR file that is referenced within a project can also be browsed this way.

The second major Java perspective is the

Java Browsing

perspective.

Selecting the

Window > Open Perspective > Java Browsing

command (see Figure 1–4) opens the

Java Browsing

perspective (see Figure 1–5).

Figure 1–4

Opening the Java Browsing perspective.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

8

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–5

The Java Browsing perspective.

The

Java Browsing

perspective includes a series of linked views reminiscent of the browsers found in various Smalltalk integrated development environments (IDEs) or within IBM’s VisualAge for Java IDE. The first view shows a list of loaded projects. Selecting a project shows its contained packages within the

Packages

view; selecting a package shows its types in the

Types

view; and selecting a type shows its members in the

Members

view. Selecting a method or field in the

Members

view will highlight that member in the corresponding editor.

Tip

: You can easily drag the individual views around to customize the layout to your taste. To get more vertical real estate associated with the editor area, consider stacking the four views vertically. Another common way to save some space in this perspective is to combine the

Projects

and

Packages

views into a single tabbed area, or drag the

Projects

view onto the fast view bar.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.2

The Eclipse Workbench 9

1.2.1.2

Resource perspective

While the

Java

perspectives provide tools for Java development, they are not ideal for reviewing all the resources in the workspace. The

Resource

perspective which you can access by selecting the

Window > Open Perspective >

Other...

command (see Figure 1–6) provides a hierarchical view of the files and folders within your system.

Figure 1–6

The Resource perspective.

The primary view within the

Resource

perspective is the

Project Explorer

, which presents a hierarchical view of the resources (projects, folders, and files) loaded in the workbench. The

Project Explorer

has its own toolbar and view menu (see Figure 1–7), which provide various viewing and filtering options.

The view menu is accessed by clicking on the small down arrow on the right side of the view’s toolbar.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

10

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–7

The Project Explorer view.

1.2.1.3

Debug perspective

Each perspective shown so far has been optimized for the purpose of writing code or editing resources. The next most common type of perspective you will encounter is the

Debug

perspective, which you can access by selecting the

Window > Open Perspective > Debug

command (see Figure 1–8).

Figure 1–8

The Debug perspective.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.2

The Eclipse Workbench 11

As its name suggests, the

Debug

perspective is used to debug programs and easily find and correct runtime errors in Java code. You can step through individual statements within your code, set breakpoints, and inspect the values associated with individual variables. This is discussed in more detail in

Section 1.10.1, Setting breakpoints, on page 59.

1.2.2

Actions

In addition to the views and editors that make up the bulk of the display area,

Eclipse includes a number of top-level and context menus and toolbar buttons that represent the various commands or actions available in the system.

1.2.2.1

Top-level menus

The basic Eclipse menu bar includes ten top-level menus:

File

,

Refactor

,

Navigate

,

Search

,

Project

,

Run

,

Window

, and

Help

Edit

,

Source

,

(see Figure 1–9).

Additional menus may also be present depending on which add-on tools you have loaded or which perspectives and views you are using.

• The

File

menu provides actions to create new resources; save, close, and print resources; refresh resources relative to their associated disk files; import and export resources; inspect the properties of resources; and exit the workbench.

• The

Edit

menu provides actions to work with the resources open in the editor area. It includes standard functions, such as cut, copy, and paste, as well as functions such as delete, select all, find, and replace.

• The

Source

menu provides commands to manipulate the current source such as adding and removing block comments, shifting left and right, formatting, organizing imports, and so on.

• The

Refactor

menu provides commands to refactor the selected Java elements. Sample refactorings include renaming fields and methods, moving elements, extracting methods and local variables, converting variables to fields, and so on.

Figure 1–9

The Java perspective menu bar and toolbar.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

12 CHAPTER 1 • Using Eclipse Tools

• The

Navigate

menu provides actions to traverse the resources loaded in the workbench. It provides commands that allow you to drill down into resources and then navigate within them much like you would with a

Web browser.

• The

Search

menu provides access to workbench-wide search tools such as global file search, help search, Java search, and plug-in search. We will discuss searching in more detail later.

• The

Project

menu provides actions to manipulate the projects loaded in the workbench. You can open any closed project, close any open projects, and manually build either an individual project or all the projects in the workbench.

• The

Run

menu contains perspective-specific items for running or debugging your Java applications. It also includes an

External Tools

option that allows you to run any arbitrary external tool on the resources in your workbench.

• The

Window

menu includes items to open additional workbench windows, open different perspectives, and add any view to the current perspective. It also allows you to customize the current perspective and to access preferences for the entire workbench (more on this in Section

1.2.2.2, Context menus, on page 12).

• The

Help

menu provides access to various tips and tricks, software updates, information about the current workbench configuration, and general help on the entire environment.

1.2.2.2

Context menus

Right-clicking on any view or editor (except on the title bar) will reveal a context-sensitive popup menu. The contents of the menu depend not only on the view or editor that was clicked on, but also on the resources that were selected at the time. For example, Figure 1–10 shows three sample context menus.

The first sample is the context menu from the

Navigator

view when nothing is selected, the second example is the same context menu when a Java file is selected in the

Navigator

view, and the third shows the context menu that appears when a Java file is selected in the

Package Explorer

view. Note that some options, such as the

Refactor

submenu, only appear in certain views under the right circumstances.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.2

The Eclipse Workbench

5HWXUQWR7DEOHRI&RQWHQWV

13

Figure 1–10

Context menus.

1.2.2.3

Toolbars

Much like the context menus that appear when right-clicking in a view, the toolbar items that appear are context-sensitive depending on which perspective is in use and which editor has focus. Standard, common items appear first on the toolbar, with any editor-specific items at the end. When using the

Resource

perspective, the standard toolbar items visible by default include icons for creating new files, saving and printing resources, running external tools, accessing the search functions, and navigating recently accessed resources in browser style (see Figure 1–11).

Switching to the

Java

perspective (see Figure 1–9) causes several new icons to appear for running or debugging your Java applications and for creating new Java projects, packages, and files.

Figure 1–11

The Resource perspective menu bar and toolbar.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

14 CHAPTER 1 • Using Eclipse Tools

1.2.2.4

Customizing available actions

You do have some limited control over which items appear on the toolbar and on the main menu bar. Many commands are part of

command groups

known as

action sets

that can be selectively enabled and disabled using the

Customize

Perspective

dialog. To customize the current perspective, select the

Window >

Customize Perspective...

command, which opens the

Customize Perspective

dialog (see Figure 1–12). The toolbar and menu command groups are shown on the

Commands

page. Check the command groups you want to keep and uncheck all others. Use the

Shortcuts

page of the dialog to customize the entries on the

New

,

Open Perspective

, and

Show View

menus.

Figure 1–12

Customize Perspective dialog.

1.3

Setting Up Your Environment

The previous section briefly touched on customizing the current perspective, while this section goes into more detail on how to customize your Eclipse environment by changing various preferences. To customize Eclipse preferences, select the

Window > Preferences...

command, which opens the

Preferences

dialog (see Figure 1–13). Dozens of individual preference pages are grouped together in the hierarchy pane on the left side of the dialog. General workbench preferences are in the

General

group, while Java preferences are in the

Java

group. At the top of the dialog, a convenient filter field makes it easy to quickly find specific preference pages.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.3

Setting Up Your Environment

5HWXUQWR7DEOHRI&RQWHQWV

15

Figure 1–13

Preferences dialog.

Many hundreds of individual preferences can be accessed from the

Preferences

dialog. Changing a value and clicking the

Apply

button locks in changes and allows you to continue setting other preferences. Clicking the

OK

button locks in changes and closes the dialog. The

Restore Defaults

button resets the preferences on the current page to the system’s default values.

1.3.1

Workbench preferences

Most general Eclipse preferences can be found in the

General

category. Some highlights include:

• The

General

page determines whether opening a resource requires single- or double-clicking and whether a heap status indicator is shown on the workbench status line.

• The

Appearance

page determines whether view and editor tabs appear on the top or bottom.

• The

Appearance > Colors and Fonts

page provides options for customizing colors and fonts for many different workspace elements such as standard text font, dialog font, header font, error colors, and many others.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

16 CHAPTER 1 • Using Eclipse Tools

• The

Appearance > Label Decorations

page provides access to options that can enhance an item’s icon and label. The

CVS

label decorator, for example, prefixes a “>” character to all changed resources.

• The

Capabilities

page contains options for enabling and disabling various capabilities. Capabilities allow you to enable and disable various product features as a group.

• The

Compare/Patch

page allows you to control the behavior of the text comparison views.

• The

Content Types

page contains options for associating various content types with various file types.

• The

Editors

page contains a variety of options controlling how editors are opened and closed and how many can be open at one time.

• The

Editors > File Associations

page associates different editor types

(both internal and external) with different file types. For example, if you wanted to associate Adobe Dreamweaver with HTML files, you would do that here.

• The

Editors > Text Editors

page contains options controlling the appearance of editors such as the visibility of line numbers, current line highlighting, various item colors, and annotations.

• The

Keys

page provides options for customizing the key bindings for many commands in the system. It includes a predefined standard set of key bindings as well as a set of Emacs key bindings.

• The

Network Connections

page allows you to set up proxy connections to the Internet that can be reused by multiple plug-ins.

• The

Perspectives

page allows you to control which perspective is your default perspective and whether new perspectives are opened in the current window or in a new window.

• The

Search

page allows you to control the behavior of the

Search

view.

• The

Startup and Shutdown

page shows a list of any plug-ins requiring early activation. Most plug-ins are activated on first use, but some need to be activated on startup. This page provides the option of preventing those plug-ins from starting up early.

• The

Web Browser

page allows you to configure which Web browser is used when you open a Web page.

• The

Workspace

page contains various build and save options.

• The

Workspace > Build Order

page controls the order in which projects in your workspace are built.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.3

Setting Up Your Environment 17

• The

Workspace > Linked Resources

page allows you to define path variables used to provide relative references to linked resources.

• The

Workspace > Local History

page (see Figure 1–51 on page 47) controls how many local changes are maintained. The default values are fairly small, so you should consider increasing them quite a bit. The more local history you keep, the more type and method versions you will be able to roll back to easily (see Section 1.7.4, Local history, on page 46 for tips on how to best use this feature).

1.3.2

Java preferences

Preferences specific to the Java development tools included in Eclipse can be found in the

Java

category of preferences. Some of the highlights include:

• The

Java

page provides options controlling the behavior of various Java views and editors.

• The

Appearance

page controls the appearance of Java elements in the various Java views.

• The

Build Path > Classpath Variables

page (see Figure 1–14) provides a place to define new classpath variables that can be added to a project’s classpath.

• The

Code Style > Formatter

page (see Figure 1–39 on page 37) controls the options the Eclipse Java code formatter uses to format Java code. It includes options for controlling brace position, new lines, line length, and white space usage.

• The

Code Style > Code Templates

page defines the naming conventions and default comments used in generated code for types, methods, fields, variables, and parameters.

• The

Compiler

page provides options for controlling the severity levels of various compilation and build path problems as well as various Java

Development Kit (JDK) compliance options.

• The

Editor

page controls numerous options dealing with the appearance of elements within the Java editor (such as bracket matching, print margin, and current line highlighting), color highlighting of Java syntax (see

Figure 1–38 on page 36), the behavior and appearance of code assistance

(see Figure 1–42 on page 39), and problem annotations.

• The

Editor > Templates

page provides a place to define and edit various

Javadoc and Java code templates (templates are common source code patterns that appear frequently in user-written code).

• The

Installed JREs

page provides options for specifying which JREs should be used with the workbench.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

18

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–14

Classpath Variables preference page.

1.3.3

Importing and exporting preferences

Setting up multiple Eclipse workspaces or migrating from one Eclipse version to another can be inconvenient due to the difficulty of moving workspace preferences from one version to another. Likewise, configuring multiple users’ workspaces with common settings, such as code formatting preferences and classpath variable settings, can also be very difficult.

The Eclipse

Export

and

Import

wizards include

Preferences

options that are intended to help solve this problem. Selecting

File > Export...

and then

Preferences

opens a wizard that prompts for the name of a preference export file (an

.epf

file) and records any non-default preference settings in it. Selecting

File > Import...

and then

Preferences

opens a wizard that is used to import a preference file. Options are provided to export your preferences at various levels of granularity. You can export all workspace preferences or specific ones.

This mechanism for exporting and importing preferences is less than ideal, however, because of problems handling various types of preferences such as classpath variables (which are exported using hard-coded paths rather than workspace-relative paths) and code templates (which are not exported at all).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.4

Creating a Project 19

1.4

Creating a Project

Earlier sections of this chapter introduced the Eclipse workbench and showed a number of ways to customize the environment. The next step is to actually use Eclipse to get some work done. This section takes you through the steps needed to create your first Eclipse project.

In the basic Eclipse environment, three different types of projects, simple,

Java, and plug-in development, can be created.

1.

Simple

projects, as their name implies, are the simplest type of Eclipse project. They can contain any type of arbitrary resource, including text files, HTML files, and so on.

2.

Java

projects are used to hold the Java source code and resources needed to create a Java application. The next section describes the process of creating a Java project.

3.

Plug-in development

projects are used to create Eclipse plug-ins. This is the type of project that this book primarily concerns itself with, and

Chapter 2, A Simple Plug-in Example, goes through a detailed example of how to create a plug-in project.

1.4.1

Using the new Java Project wizard

To create a new Java project, select the

File

>

New > Project...

command or click the

New Java Project

toolbar button in the

Java

perspective to open the

New Project

wizard (see Figure 1–15). On the first page, select

Java Project

from the list and click the

Next

button. A filter field is available at the top of the wizard to make it easy to find specific project types.

Figure 1–15

New Project wizard—selecting the project type.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

20 CHAPTER 1 • Using Eclipse Tools

On the second page of the wizard (see Figure 1–16), enter the name of the project (e.g., “First Project”) and click the

Next

button. Note that this page also includes options for specifying the location of the project and its structure. By default, the project will be placed within the workspace directory and will use the

src

and

bin

folders as the roots for sources and class files.

Tip

: When you create a Java project that you want to share with a team, it is a good idea to use an execution environment instead of a specific

JRE. Execution environments are symbolic representations of JREs with standardized entries like “J2SE-1.4,” “J2SE-1.5,” and “JavaSE-1.6.”

That means no file system path will go into the shared build path. JREs may be assigned to execution environments on the

Java > Installed JREs >

Execution Environments

preference page.

Figure 1–16

New Project wizard—naming the project.

The next page of the wizard (see Figure 1–17) contains build path settings for the Java project. The

Source

tab provides a place to add source folders, which act as roots for packages containing Java files.

Traditionally, source files are placed in a separate source folder named

src

and the compiler output is in a

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.4

Creating a Project 21

C

bin

directory. Placing these types of files in separate directories results in an easier build process. Use the

Java > Build Path

preference page to specify the default directory names used when creating new Java projects.

Figure 1–17

New Project wizard—specifying Java build settings.

The

Projects

tab allows you to set up project dependencies by selecting other projects in the workbench on which this project depends. The

Libraries

tab is the place to add JAR files (either stored in the workbench or out on the file system). The last tab,

Order and Export

, controls the order of build path elements and whether they are exported and visible to other projects that require this project.

The

Default output folder

field, at the bottom the page, is used to specify the default location where compilation output will be stored. When the

Finish

button is clicked, the new project is created and appears in the

Package

Explorer

view or the

Navigator

view, depending on which perspective is active.

Differences between Eclipse versions

As you can see from the last three screenshots, there are a number of minor differences between Eclipse 3.4 and earlier versions. For the most part, the differences won’t impact your use of this book; however, differences will be highlighted whenever they are relevant or interesting.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

22 CHAPTER 1 • Using Eclipse Tools

1.4.2

.classpath and .project files

In addition to creating the project itself, two additional files are created

.classpath

and project

. By default, both of those files as well as any other files beginning with “.” are hidden from view via a filter.

To show the files, select the

Filters

... command from the drop-down view menu in the

Package Explorer

(see Figure 1–18), uncheck the

.* resources

filter in the

Java Element Filters

dialog (see Figure 1–19) and click the

OK

button.

Figure 1–18

Filter menu.

Figure 1–19

Filter dialog.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.4

Creating a Project 23

The

.classpath

file stores the

Java build path

for the project. It should look something like the following for the project you just created:

<?xml version="1.0" encoding="UTF-8"?>

<classpath>

<classpathentry kind="src" path=""/>

<classpathentry kind="con"

path="org.eclipse.jdt.launching.JRE_CONTAINER/

org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/

JavaSE-1.6"/>

<classpathentry kind="output" path=""/>

</classpath>

Rather than editing the

.classpath

file directly, Eclipse provides a more user-friendly approach. Right-click on the project and select

Properties

. In the

Properties

dialog, selecting

Java Build Path

displays an interface similar to Figure 1–17 for editing the project’s classpath.

Java Build Path

“Java classpath” is a generic term describing both the classpath used at compile-time and the classpath used at runtime. In

Eclipse, the compile-time classpath is called the

Java build path

. When you are running or debugging Java application code, the runtime classpath is determined by the launch configuration (see Section 1.9.2, Launch configurations, on page 57). When you are developing Eclipse plug-ins, the runtime classpath is determined by the dependency declaration in the plug-in manifest (see Section 2.3.1, The Plug-in manifests, on page 77).

The

.project

file provides a complete description of the project suitable for recreating it in the workbench if it is exported and then imported. Your new project should look like the following:

<?xml version="1.0" encoding="UTF-8"?>

<projectDescription>

<name>First Project</name>

<comment></comment>

<projects>

</projects>

<buildSpec>

<buildCommand>

<name>org.eclipse.jdt.core.javabuilder</name>

<arguments></arguments>

</buildCommand>

</buildSpec>

<natures>

<nature>org.eclipse.jdt.core.javanature</nature>

</natures>

</projectDescription>

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

24 CHAPTER 1 • Using Eclipse Tools

The nature

tag indicates what kind of project this is. The nature, org.eclipse.jdt.core.javanature

, indicates that it is a Java project.

1.4.3

Using the Java Package wizard

To create a Java package, select

File

>

New > Package

or click the

New

Java Package

toolbar button to open the

New Java Package

wizard (see Figure

1–20). Enter the name of the package (e.g., “ com.qualityeclipse.sample

”) and click the

Finish

button.

Figure 1–20

New Java Package wizard.

Note that the icon next to the new Java package name (see Figure 1–21) is hollow, indicating that it is empty. When one or more Java files are added to the package, the icon will appear in color.

Figure 1–21

New Java package in the Package Explorer.

1.4.4

Using the Java Class wizard

To create a Java class, select

File

>

New > Class

or click the

New Java

Class

toolbar button to open the

New Java Class

wizard, as shown in Figure

1–22. Enter the name of the class (e.g., “HelloWorld”), check the

public static

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.4

Creating a Project 25

void main(String

[]

args)

checkbox, and click the

Finish

button. Note that the wizard presents numerous additional options for creating a new class, including its superclass, interfaces, and initial default methods.

Figure 1–22

New Java Class wizard.

This process creates a new Java class (see Figure 1–23). The entry,

HelloWorld.java

, represents the file itself. Expanding that item reveals elements representing the class and its single “main” method. Note that the icon next to the package name is now in color, indicating that it is no longer empty.

Figure 1–23

New Java class in the Package Explorer.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

26 CHAPTER 1 • Using Eclipse Tools

1.5

Navigating

Eclipse includes a number of tools designed to make it easy to navigate the system and find information. This section discusses some of the tools accessible from the Eclipse

Navigate

menu.

Tip

: Many third-party plug-ins are available that provide various navigational enhancements for Eclipse (see Appendix A). For example,

CodePro provides a

Java History

view that keeps track of any Java files you have accessed as well as a

Modified Type

view that track any types you have changed.

1.5.1

Open Type dialog

The

Open Type

dialog is used to quickly jump to any Java class in the system.

Select the

Navigate

>

Open Type...

command (

Ctrl+Shift+T

) to open the dialog (see Figure 1–24) or click the

Open Type

toolbar button, then enter the name of the type you want to find. The name field allows wildcards and will show a list of all types that match the entered pattern. The dialog also provides CamelCase support, so entering “NPE” will find the class

NullPointerException

. If nothing is entered into the field, the dialog shows a list of types found in the past (the first time you access the dialog, it will be empty).

Select the desired type from the list and click the

OK

button to open that type in an editor. If more than one type matches the name, the package name qualifier will be displayed to the right of the type name.

Figure 1–24

Open Type dialog.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.5

Navigating 27

1.5.2

Type Hierarchy view

The

Type Hierarchy

view shows the superclasses and subclasses of a given type (see Figure 1–25). The view also has options for showing just the supertype hierarchy (both superclasses and implemented interfaces) or subtype hierarchy (subclasses and interface implementers) of a type.

The

Type Hierarchy

view can be accessed in several different ways. The easiest way is to select the type name in an editor, then select the

Navigate

>

Open Type Hierarchy

command (or use the

F4

keyboard shortcut). Alternatively, select the

Navigate > Open Type in Hierarchy...

command

(

Ctrl+Shift+H

) to open the

Open Type

dialog, as shown in Figure 1–24.

Figure 1–25

Type Hierarchy view.

1.5.3

Go to Line

To jump to a specific line of code within a file, use the

Navigate > Go to Line...

command (

Ctrl+L

). This opens a prompter for entering the desired line number (see Figure 1–26). Clicking the

OK

button jumps to that line in the editor.

Figure 1–26

Line number prompter.

1.5.4

Outline view

The

Outline

view shows an outline of the structural elements of the selected editor. The contents vary depending on the type of editor in use. For example, when editing a Java class, the

Outline

view displays the classes, fields, and methods in the Java class being edited (see Figure 1–27).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

28 CHAPTER 1 • Using Eclipse Tools

The Java

Outline

view includes a number of options to control which elements are displayed within the outline. There are filters for hiding fields, static members, non-public members, and local types. In addition, there are options for sorting members (shown in definition order by default) and drilling down to the top-level type (normally, the outline starts at the file level).

Figure 1–27

Outline view.

1.6

1.5.5

Quick Access

To quickly access views, commands, menus, preference pages, and wizards, use the

Quick Access

dialog, available via the

Window > Navigation > Quick

Access

command (

Ctrl+3

). Start typing in the filter field to see matches, use the arrow keys to select a match, and press

Enter

to execute the command or open the view, perspective, or wizard.

Searching

In addition to the navigation tools available from the

Navigate

menu, Eclipse includes a number of powerful search tools accessible from the

Search

menu.

The Eclipse

Search

dialog, accessible via the

Search > Search...

command

(

Ctrl+H

) or the

Search

toolbar button, acts as a portal to a number of different searching tools, including

File Search

,

Task Search

,

Java Search

, and

Plug-in Search

. The two most important tools are

File Search

and

Java Search

.

1.6.1

File Search

The

File Search

tab (see Figure 1–28) of the

Search

dialog provides a way to find arbitrary files in the workbench by name or by the text they contain. To search for files containing a certain expression, enter that expression into the

Containing text

field. Various wildcards, such as “*” to match any set of characters and “?” to match any single character, are supported. By default, the search is case-sensitive; to make it case-insensitive, uncheck the

Case sensitive

option. To perform complex text searches using regular expressions, turn on the

Regular expression

option.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.6

Searching

5HWXUQWR7DEOHRI&RQWHQWV

29

Figure 1–28

File Search tab.

To search for files by name, leave the

Containing text

field blank. To restrict a search to certain types of files or files containing a certain naming pattern, enter the file name expression into the

File name patterns

field.

The

Scope

fields provide another way to further restrict a search. The

Workspace

scope encompasses the entire workspace, while the

Working set

scope limits a search to only those files contained in the selected working set.

The

Selected resources

scope limits a search to only those files that have been selected in the active view (for example, the

Navigator

view or

Package

Explorer

view), while the

Enclosing projects

scope limits a search to the projects containing the selected files.

For example, to search for all files containing the text “xml”, enter that text into the

Containing text

field and leave the

File name patterns

field and

Scope

fields unchanged. When ready, click the

Search

button to find the matching files and display them in the

Search

view (see Figure 1–29). Clicking the

Replace

button rather than the

Search

button will perform the same search, but it will open up a

Replace

dialog where you can enter replacement text.

Figure 1–29

File search results.

Tip

: If your goal is to search for various Java elements, such as types, methods, fields, and so on, the

Java Search

option is much more powerful than the

File Search

option .

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

30 CHAPTER 1 • Using Eclipse Tools

1.6.2

Java Search

The

Java Search

tab (see Figure 1–30) locates Java elements such as types, methods, constructors, fields, and packages. You can use it to find declarations of a specific Java element, references to the element, or implementors of the element (in the case of a Java interface).

Figure 1–30

Java Search tab.

To search for elements with a specific name, enter the name in the

Search string

field (wildcards are supported). Depending on the kind of Java element you are interested in, select the

Type

,

Method

,

Package

,

Constructor

, or

Field

radio button. You can further limit search results to

Declarations

,

References

,

Implementors

(of Java interfaces),

Match locations

(fine tune the declarations, expressions, or parameter types to search in),

All Occurrences

,

Read Access

(for fields), or

Write Access

(for fields). The

Search In

fields allow you to focus the search on your project sources, required projects, JRE, and application libraries.

As with the

File Search

tab, the

Scope

fields provide another way to restrict a search. The

Workspace

scope includes the entire workspace, the

Working set

scope limits the search to a specific working set, the

Selected resources

scope limits the search to the selected files in the active view, and the

Enclosing projects

scope limits the search to the projects containing the selected files.

Tip:

Consider building a reference project if you want to search the entire

Eclipse plug-in source (see Section 21.1, Advanced Search—Reference

Projects, on page 780).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.6

Searching 31

For example, to search for all methods named “toLowerCase”, enter that text into the

Search string

field, select the

Search For > Method

and

Limit To

> Declarations

radio buttons, check the

Search In > JRE Libraries

check box and leave the

Scope

fields unchanged. When ready, click the

Search

button to find the methods matching that name and to display them hierarchically in the

Search

view. Several options are available on the view toolbar for grouping the results by project, package, file, or class. Select the

Show as List

command from the view menu to see the results listed individually (see Figure 1–31).

Figure 1–31

Java search results.

Double-clicking on any search result opens an editor on the file containing that result, highlights the search match in the text, and places a search marker in the left gutter area (also known as the marker bar, or left vertical ruler) of the editor (see Figure 1–32). Clicking the

Show Next Match

or

Show Previous

Match

buttons (the up and down arrows) in the

Search

view selects the next or previous match (opening a new editor on a different file if necessary). You can also continue to search (“drill-down”) using the context menus in the

Search

view.

Figure 1–32

Editor showing search match and search marker.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

32 CHAPTER 1 • Using Eclipse Tools

1.6.2.1

Java Plug-in Search

When locating Java classes or methods, the search scope includes the workspace and any libraries or plug-ins referenced by projects in the workspace.

This makes it difficult to locate classes in plug-ins not referenced by projects in the workspace. To overcome this, you can explicitly add plug-ins to the search scope that are not referenced by any project in the workspace. Open the Plug-ins view (see Figure 2–25 on page 97), select one or more plug-ins in that view, then right click and select

Add to Java Search

. This will add the classes and methods in the selected plug-ins to the search scope for any subsequent Java searches.

Tip:

Selecting

Add to Java Search

creates a new project named

“External Plug-in Libraries” to be created and the selected plug-ins added to that project. To make this project visible in the

Package

Explorer

( see Section 1.2.1.1, Java perspectives, on page 7) pull down the view menu in the

Package Explorer

and select Filters... Uncheck

External plug-ins library project

and click

OK

.

Alternately, you can add plug-ins to the search scope by creating a reference project. The reference project itself is a plug-in project containing references to one or more plug-ins to be included in the search scope. For more on reference projects, see Section 21.1, Advanced Search—Reference Projects, on page 780

.

1.6.3

Other Search menu options

The

Search

menu contains a number of dedicated, easy-to-use Java search commands that replicate the options found on the

Java Search

page of the

Search

dialog (see Figure 1–33).

Figure 1–33

Dedicated Java search commands.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.6

Searching 33

Selecting a Java element either in a view or in a Java editor and then selecting the

Search > Declarations

command finds all elements with matching declarations in the workspace, the current project, the current type hierarchy, or a specific working set. Likewise, selecting the

Search > References

command finds all the places where the element is used. The

Search > Implementors,

Search > Declarations

, and

Search > Write Access

commands work similarly.

Note that the same commands are also available from the context menu in the

Java editor and the

Search

view.

1.6.4

Working sets

Working sets have been mentioned a number of times so far. They are used to create a group of elements to act either as a filter in various views, such as

Navigator

and

Package Explorer

, or as search scopes in the

Search

dialog or any search menu. Working sets are extremely useful when you have a large workspace containing many projects because they limit the scope of your code and make many tasks easier.

To select a working set or to create a new one, choose

Scope > Working

Set

in the

File Search

dialog, then click the

Choose

button. This opens the

Select Working Set

dialog (see Figure 1–34). To use an existing working set, select it from the list and click the

OK

button. To edit a working set, click the

Edit

button instead.

Tip:

Eclipse has the ability to select multiple working sets. This creates, in effect, a new

virtual

working set that merges the results from each of the selected sets. This makes it easier to create multiple, finer-grained working sets and then combine them in different combinations

.

Figure 1–34

Select Working Set dialog.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

34 CHAPTER 1 • Using Eclipse Tools

Click the

New…

button to create a new working set. This opens the

New

Working Set

dialog (see Figure 1–35). Four different types of working sets can be created: Resource, Breakpoint, Java, Task, and Plug-in working sets. Select the type of working set you want to create and click the

Next

button.

Figure 1–35

New Working Set dialog.

The next page of the

New Working Set

dialog facilitates defining new working sets (see Figure 1–36). Enter the desired name into the

Working set name

field and then select the contents in the

Workspace content

list and add it to the

Working set content

list. Move elements back and forth using the

Add

and

Remove

buttons. Clicking the

Finish

button closes the

New Working Set

dialog and adds the new working set to the

Select Working Set

dialog.

Figure 1–36

Define a New Working Set.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.7

Writing Code 35

1.7

Writing Code

Now that your first Java project has been created and you have explored different ways to navigate the system and find the items needed, it is time to start using Eclipse tools to write new code. Eclipse supports a number of different editor types, both internal and external, for editing different types of resources. Double- clicking on a Java file, for example, opens the Java editor

(see Figure 1–37).

Figure 1–37

Java editor.

1.7.1

Java editor

The Java editor provides many features that focus on the job of editing Java code, including the following:

• Colored syntax highlighting (see Figure 1–37)

• User-defined code formatting

• Import organization and correction

• Context-sensitive code assistance

• “Quick fix” automatic problem correction

Tip

: Many former VisualAge for Java users loved the ability of that IDE to show only a single method at a time rather than the entire Java file.

The Eclipse Java editor supports the same capability via the

Show

Source of Selected Element Only

toolbar button. For it to work, you must give focus to an editor since the button is not enabled until you’re actually editing some code. This is one of those options in Eclipse that should be a workspace preference rather than a toolbar button.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

36 CHAPTER 1 • Using Eclipse Tools

1.7.1.1

Colored syntax highlighting

The colored syntax highlighting feature controls how Java code will be depicted. Independent control over color and font style (plain or bold) is provided for multi- and single-line comments, keywords, strings, characters, task tags, and Javadoc elements via the

Java > Editor > Syntax Coloring

preference page (see Figure 1–38).

Figure 1–38

Syntax Coloring preference page.

1.7.1.2

User-defined code formatting

The code formatting feature controls how Java code will be formatted any time the

Source > Format

command is issued. A variety of options are provided for controlling brace position, new lines, line length, and white space usage through use of the

Java > Code Style > Formatter

preference page (see

Figure 1–39).

Tip

: Alternate code formatters are available, including Jalopy integration into Eclipse (

jalopy.sourceforge.net

).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.7

Writing Code

5HWXUQWR7DEOHRI&RQWHQWV

37

Figure 1–39

Code Formatter preference page.

1.7.1.3

Organizing Java import statements

The import organization feature provides an easy way to clean up the import statements within a Java file. New imports can be added using the

Source >

Add Import

command, and existing imports can be cleaned up using the

Source > Organize Imports

command. The

Java > Code Style > Organize

Imports

preference page (see Figure 1–40) provides a means to set the default order of import statements and the threshold above which wildcard imports will be used.

Tip

: Set the threshold to 1 to cause packages to be imported with “.*” immediately, or keep the default value at 99 to always import each type individually, depending on your coding style.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

38

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–40

Organize Imports preference page.

1.7.1.4

Context-sensitive content assist

The context-sensitive content assist feature can help speed up the creation of

Java code quite dramatically. It can complete class names, method names, parameter names, and more. CamelCase patterns, such as NPE, will expand to full class names such as

NullPointerException

.

To use it, position the cursor at a location in your Java code needing a suggestion and select either the

Edit > Content Assist > Default

command or hold the

Ctrl

key down while pressing the

Space

key. This opens the popup code assist window (see Figure 1–41).

Tip

: If the code assist window fails to open and the feature just beeps at you instead, check your Java build path and then check your code because it may have so many problems that the compiler cannot make sense of it.

Remember, the Java compiler is always working in the background!

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.7

Writing Code

5HWXUQWR7DEOHRI&RQWHQWV

39

Figure 1–41

Content assistance in action.

The

Java > Editor > Content Assist

preference page (see Figure 1–42) provides a number of options to control how the content assist feature acts when invoked.

Figure 1–42

Content Assist preference page.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

40 CHAPTER 1 • Using Eclipse Tools

1.7.1.5

“Quick fix” automatic problem correction

The “quick fix” feature provides a way to easily fix common problems within the Java editor. Any time a problem is detected that can be fixed, a light bulb icon is displayed in the marker bar (left vertical ruler) of the editor. Clicking on the icon or selecting the

Edit > Quick Fix

command opens a popup quick fix window (see Figure 1–43). Selecting the appropriate one from the list applies that fix to the Java source.

Figure 1–43

Quick fix in action.

Dozens of built-in quick fixes are available, including:

• Correcting missing or incorrect package declarations

• Removing unused and duplicate imports

• Changing the visibility of types, methods, and fields

• Renaming types, methods, and fields

• Removing unused private types, methods, and fields

• Creating new types, methods, and fields

• Fixing incorrect method arguments

• Adding or removing catch blocks

• Adding necessary cast operations

1.7.2

Templates

Templates are common source code patterns that appear frequently in userwritten code. Eclipse has dozens of built-in templates and new ones are very easy to add.

To use a template, position the cursor at the desired position in your Java code, start to type the name of the template, and press

Ctrl

+

Space

. This opens the popup content assist window (see Figure 1–44). Note that some templates are parameterized with user-defined variables. Once a template has been expanded, use the

Tab

key to move between variables.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.7

Writing Code

5HWXUQWR7DEOHRI&RQWHQWV

41

Figure 1–44

Template expansion in action.

As an example, open the

HelloWorld

class that was created in Section

1.4.4, Using the Java Class wizard, on page 24 then enter “sysout” and press

Ctrl+Space

. This expands the sysout template to

System.out.println(); with the cursor placed between the two parentheses. Type “Hello World” and press

Ctrl+S

to save your changes. This application will be run in Section 1.9,

Running Applications, on page 55.

The

Java > Editor > Templates

preference page (see Figure 1–45) provides a place to add new templates and edit existing ones.

Figure 1–45

Templates preference page.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

42 CHAPTER 1 • Using Eclipse Tools

To add a new template, click the

New

button to open the

Edit Template

dialog (see Figure 1–46). Enter the name for the pattern in the

Name

field, its description in the

Description

field, and the code pattern itself in the

Pattern

field (note that code assist is not case-sensitive).

Eclipse supports two types of patterns, Java and Javadoc. Select the pattern type from the

Context

drop-down list. The

Insert Variable

button pops up a list of variables that can be inserted into the template. Click the

OK

button to add the template to the template list.

Figure 1–46

Edit Template dialog.

Tip

: Some third-party plug-ins provide enhanced templates known as patterns (see Appendix A).

1.7.3

Refactoring

Refactoring

is the process of changing a software system to improve its internal structure and reusability, without altering the external behavior of the program

.

It is a disciplined way of cleaning up code that minimizes the chances of introducing bugs. In essence, when developers refactor, they are improving the design of the code. Eclipse provides a very powerful and comprehensive collection of refactoring tools that make refactoring operations quick, easy, and reliable.

The Eclipse refactoring commands are available either from the Java editor’s context menu or from the

Refactor

menu that is available from the main menu bar anytime a Java editor is open. The

Refactor

menu (see Figure 1–47) includes more than a dozen different refactoring commands that modify some aspect of a Java element and then update any references to it elsewhere in the workspace.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.7

Writing Code

5HWXUQWR7DEOHRI&RQWHQWV

43

Figure 1–47

Refactor menu.

Many of the refactoring commands that are supported include the following:

Rename—

Renames a Java element.

Move—

Moves a Java element.

Change Method Signature—

Changes method parameters (names, types, and order).

Extract Method—

Creates a new method based on the selected text in the current method and updates the current method to call the new method.

Extract Local Variable—

Creates a new local variable assigned to the selected expression and replaces the selection with a reference to the new variable.

Extract Constant—

Creates a static final field from the selected expression.

Inline—

Inlines methods, constants, and local variables.

Convert Anonymous Class to Nested—

Converts an anonymous inner class to a named nested class.

Convert Member Type to Top Level—

Converts a nested type into a toplevel type.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

44 CHAPTER 1 • Using Eclipse Tools

Convert Local Variable to Field—

Converts a local variable into a field.

Extract Superclass—

Creates a common superclass from a set of sibling types. The selected sibling types become direct subclasses of the extracted superclass after applying the refactoring.

Extract Interface—

Creates a new interface from a collection of selected methods and makes the selected class implement the interface.

Use Supertype Where Possible—

Replaces a type with one of its supertypes anywhere that transformation is possible.

Push Down—

Moves fields and methods from a class to one of its subclasses.

Pull Up—

Moves fields, methods, or member types from a class to one of its superclasses.

Extract Class—

Creates a new data class from a collection of fields.

Introduce Parameter Object—

Replaces a set of parameters with a new class, and updates all callers of the method to pass an instance of the new class as the value to the introduce parameter.

Introduce Indirection—

Creates a static indirection method delegating to the selected method.

Introduce Factory—

Replaces a constructor invocation with a call to a new factory method.

Introduce Parameter—

Replaces an expression with a parameter reference.

Encapsulate Field—

Replaces all direct references to a field with references to the field’s getter and setter methods and creates those methods as necessary.

Generalize Declared Type—

Generalizes the type of variable declarations, parameters, fields, and method return types.

Infer Generic Type Arguments—

Attempts to infer type parameters for all generic type references in a class, package, or project. This is especially useful when migrating from Java 1.4 code to Java 5.0 code.

To use any refactoring command, select the Java element or expression that you would like to refactor and then select the refactoring command. Each refactoring dialog collects information appropriate to the task it needs to do.

Once you have supplied that information (for example, the new method name as shown in Figure 1–48), click the

OK

button to complete the refactoring.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.7

Writing Code

5HWXUQWR7DEOHRI&RQWHQWV

45

Figure 1–48

Rename Method dialog.

To preview the transformations that will be made by a refactoring method before they are committed, click the

Preview

button prior to clicking the

OK

button. The refactoring preview shows a hierarchy (a checkbox tree list) of the changes that will be made with text panes showing a before and after view of the affected code (see Figure 1–49). If you want to exclude a particular change from the refactoring operation, uncheck it in the tree list.

Figure 1–49

Rename Method preview.

Clean Up Wizard

Eclipse has a

Clean Up

wizard that will fix multiple source problems (such as removing unused private fields and local variables) simultaneously. Access it using the

Source > Clean Up...

command

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

46 CHAPTER 1 • Using Eclipse Tools

1.7.4

Local history

Every time you make a change to a file and save it, a snapshot of that file at that moment in time is recorded to the Eclipse local history. This provides a way to revert back to an earlier version of a file or to compare the current version with an earlier version to see the changes that have been made. Every entry in the local history is identified by the date and time it was created.

Note that “local history” is really local to the machine; it is never stored in CVS or another source code repository. This means that the history is only available to you, not to other users. This might be a surprise to VisualAge for

Java or ENVY users who expect “method editions” to be available in the repository.

External File Warning

Local history is only saved for the files stored within your workspace. If you use Eclipse to edit external files, no local history is saved.

To compare the current state of a file with an earlier version, right-click on the file and select the

Compare With > Local History...

command from the context menu. This opens the

History

view showing a history for the file.

Double-click any item in the history list to see a comparison relative to the current state of the file (see Figure 1–50).

Figure 1–50

Compare with Local History dialog.

To replace the current version of a file with an earlier version, right-click on the file and select the

Replace With > Local History...

command from the context menu. This opens a

Compare

dialog with

Replace

and

Cancel

buttons. Select a revision and click the

Replace

button.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.7

Writing Code 47

The

General > Workspace > Local History

preference page (see Figure

1–51) determines how much information is stored in local history. You can control how many days worth of changes are maintained, how many unique changes per file are maintained, and how large the entire local history is allowed to grow.

Tip

: Many former VisualAge for Java users loved the ability of that IDE to revert to any prior version of any method or class. The Eclipse local history feature provides a way to emulate that behavior on a local scale. There is no reason (other than disk space) to keep the Eclipse local history settings at the low values to which they default. Increasing the

Days to keep files

setting to 365, the

Entries per file

to 10,000, and the

Maximum file size

(MB)

field to 100 will allow you to easily track an entire year of changes.

Figure 1–51

Local History preference page.

1.7.5

File extension associations

In addition to the built-in Java editor, Eclipse includes built-in editors for text files, plug-in development files (such as plugin.xml

, fragment.xml

, and feature.xml

), and others.

You can change which editor is assigned to a specific file type using the

General > Editors > File Associations

preference page (see Figure 1–52). To change the editor, select the file type in the

File types

list, select the desired editor type in the

Associated editors

list, and click the

Default

button. If the desired editor type is not shown, use the

File types > Add

button to add it to the list.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

48

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–52

File Associations preference page.

To add an editor association for a file type not listed in the

File types

list, click the

File types > Add

button to reveal the

New File Type

dialog, as shown in Figure 1–53. For example, to add an editor for XML files, enter “*.xml” into the

File type

field and click the

OK

button.

Figure 1–53

New File Type dialog.

Once the new file type has been added, an editor must be assigned to it.

Click the

Associated editors > Add...

button to open the

Editor Selection

dialog. By default, the various built-in editor types will be shown in the editor list.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.8

Team Development Using CVS 49

To see a list of available external editors, select the

External Programs

radio button (see Figure 1–52). If you have an XML editor (such as Dreamweaver) installed in your system, select it from the list and click the

OK

button. That editor will be added to the

Associated editors

list and automatically made the default (assuming that no other default was in place).

Figure 1–54

Editor Selection dialog.

Tip

: If you routinely edit XML files, the

XMLBuddy

plug-in, from

www.xmlbuddy.com

, is one of several XML editors integrated into

Eclipse (see Appendix A). The XMLBuddy editor provides userconfigurable syntax highlighting, Document Type Definition- (DTD-) driven code assist, XML validation, and many other features.

1.8

Team Development Using CVS

Typically, you will want to work as part of a team and share your code with other team members. This section shows how to set up and use CVS, which ships as part of Eclipse.

As team members work on different aspects of a project, changes are made locally to their own workspaces. When they are ready to share changes with other team members, they can commit them to the shared CVS repository. Likewise, when they want to get any changes made by other team members, they can update their workspaces with the contents of the repository. In the event of conflicts (e.g., changes made to the same resource), Eclipse provides comparison and merge tools to resolve and integrate such changes.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

50 CHAPTER 1 • Using Eclipse Tools

CVS supports multiple streams of development known as

branches

. Each branch represents an independent set of changes made to the same set of resources. There may be multiple concurrent branches for various maintenance updates, bug fixes, experimental projects, and so on. The main branch, known as the “HEAD,” represents the primary flow of work within a project.

Just as the Eclipse local history feature records changes made to various resources over time, the CVS repository maintains a history of every committed change made to every resource over time. A resource may be compared with or replaced with any prior revision using tools similar to those used with the local history feature.

1.8.1

Getting started with CVS

To start using CVS with Eclipse, you will need to connect your Eclipse workspace to your CVS repository (see

www.cvshome.org

for information on setting up the repository). Start by opening either the

CVS Repository Exploring

perspective using the

Window > Open Perspective > Other...

command or the

CVS Repositories

view using the

Window > Show View > Other...

command.

Next, right-click within the

CVS Repositories

view and select the

New >

Repository Location...

command from the context menu (see Figure 1–55).

Figure 1–55

CVS Repositories view.

Within the

Add CVS Repository

dialog (see Figure 1–56), you need to specify the location of your repository and your login information. Enter the address of your CVS host into the

Host

field (e.g., “cvs.qualityeclipse.com”) and the path of the repository relative to the host address into the

Repository path

field. Next, enter your user name and password into the

User

and

Password

fields, respectively, or leave them blank for anonymous access. If you need to use a different connect type than the default one, change that as well.

When done, click the

Finish

button. Assuming that the CVS repository is found, it will show up in the

CVS Repositories

view.

Tip

: The

New Project

wizard also provides a convenient option for creating a new repository. Select

New > Project...

and then the

CVS >

CVS Repository Location

wizard. The first page of the wizard will look like the dialog shown in Figure 1–56.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.8

Team Development Using CVS

5HWXUQWR7DEOHRI&RQWHQWV

51

Figure 1–56

Add CVS Repository dialog.

1.8.2

Checking out a project from CVS

To check out a project from your CVS repository, expand the repository location and then the

HEAD

item until you see the project you want to load.

Right-click on the project and select the

Check Out

command from the context menu (see Figure 1–57). This loads the project into your workspace.

Figure 1–57

Checking out a project.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

52 CHAPTER 1 • Using Eclipse Tools

To load the project into a specially configured project (e.g., a project outside your workspace), use the

Check Out As…

command instead.

Tip

: The

New Project

wizard also provides an option for checking out a project from an existing repository. Select

New > Project...

and then the

CVS > Projects from CVS

wizard. On the first page, choose the

Use existing repository location

option and the repository you wish to connect to and click

Next

. Selecting

Use an existing module

on the second page of the wizard will reveal a list of the projects in that repository. Select the desired projects and click the

Finish

button.

1.8.3

Synchronizing with the repository

Once changes have been made to the resources in the project, those changes should be committed back to the repository. Right-click on the resource (or the project containing the resource) and select the

Team > Synchronize with

Repository

command (see Figure 1–58).

Figure 1–58

Team context menu.

After comparing the resources in the workspace to those in the repository, the

Synchronize

view opens (see Figure 1–59). The

Incoming Mode

icon causes the view to only show incoming changes, while the

Outgoing Mode

icon causes it to only show outgoing changes (the

Incoming/Outgoing Mode

is a third option).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.8

Team Development Using CVS 53

Right-click on the outgoing changes and select the

Commit...

command to commit those changes to the repository. Right-click on any incoming changes and select the

Update

command to load the changes into your workspace.

Figure 1–59

Synchronize view.

If any conflicts occur (e.g., changes made by you and by another developer), you will need to use the merge tools that are provided in the

Synchronize

view to resolve the conflicts and then commit the merged version to the repository.

1.8.4

Comparing and replacing resources

To compare the current state of a file with an earlier revision stored in the repository, right-click on the file and select the

Compare With > History...

command from the context menu. This opens the

History

view, which shows earlier revisions of the file made to the

HEAD

stream or any branch (see Figure 1–60). Double-click any item in the revision list to see a comparison relative to the current state of the file.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

54

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–60

Compare with Revision editor.

A number of other resource comparison commands are also available.

The

Compare With > Latest from Head

command compares the current state of the resource with the most recent version committed to a repository, and the

Compare With > Another Branch or Version...

command provides a way to compare a resource with a specific version or branch.

To replace the current version of a file with an earlier revision, right-click on the file and select the

Replace With > History...

command from the context menu. This opens a

Replace

dialog with

Replace

and

Cancel

buttons. Select a revision and cick the

Replace

button to load that version into the workspace.

1.8.5

CVS label decorators

To make it easier to see which resources are under repository control and which might have been changed but not committed, CVS provides a number of label decorations to augment the icons and labels of CVS-controlled resources. To turn on the CVS label decorators, use the

General > Appearance

> Label Decorations

preference page (see Figure 1–61).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.9

Running Applications

5HWXUQWR7DEOHRI&RQWHQWV

55

Figure 1–61

Label Decorations preference page.

The actual decorations added are controlled by the options on the

Team

> CVS > Label Decorations

preference page. By default, outgoing changes are prefixed with “>”.

Tip

: To make outgoing changes easier to see, open the

General >

Appearance > Colors and Font

preference page and set the

CVS >

Outgoing Change (Foreground)

preference to blue. That changes the foreground color of any modified resource that is waiting to be committed to the repository.

1.9

Running Applications

Any Java application with a main()

method, including the

.java

file created in Section 1.4.4, Using the Java Class wizard, on page 24 and enhanced in Section 1.7.2, Templates, on page 40, is marked with the runnable icon decoration (a small green triangle), indicating that it is runnable. This section shows the different ways to launch (run) a Java application.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

56 CHAPTER 1 • Using Eclipse Tools

1.9.1

Launching Java applications

The easiest way to run a Java application is to select the class and then select the

Run As > Java Application

command (

Ctrl+Shift+X, J

) from the

Run

menu or from the

Run

toolbar button (see Figure 1–62). This executes the main()

method of the application and writes any output (in black) and error text (in red) to the

Console

view (see Figure 1–63).

Figure 1–62

Run As > Java Application command.

Figure 1–63

Console view.

Once an application has been run, it can be run again by selecting it from the

Run > Run History

menu or from the

Run

toolbar button (see Figure

1–64). Clicking the

Run

toolbar button or pressing

Ctrl+F11

relaunches the last application you ran.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.9

Running Applications

5HWXUQWR7DEOHRI&RQWHQWV

57

Figure 1–64

Run history.

1.9.2

Launch configurations

Whenever you run an application for the first time using the

Run As > Java

Application

command, a

launch configuration

is created. A launch configuration records the information needed to launch a specific Java application. In addition to specifying the name of the Java class, the launch configuration can also specify program and virtual machine (VM) arguments, the JRE and classpath to use, and even the default perspectives to use when running or debugging the application.

A launch configuration can be edited using the launch configuration

(

Run

) dialog accessible from the

Run > Run…

command (see Figure 1–65).

The

Main

tab specifies the project and class to be run; the

Arguments

tab records the program parameters (as space-separated strings) and VM arguments; the

JRE

tab specifies the JRE to use (it defaults to the JRE specified in your

Java > Installed JREs

preferences); the

Classpath

tab is used to override or augment the default classpath used to find the class files needed to run the application; the

Source

tab specifies the location of the source files used to display the source of an application while debugging; the

Environment

tab is used to set environment variables; and the

Common

tab records information about where the launch configuration is stored and where standard input and output should be directed.

The

Eclipse Application

configuration is used specifically to test Eclipse plug-ins you are developing. It provides a mechanism for starting up another workbench with full control over which plug-ins are loaded, active, and

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

58 CHAPTER 1 • Using Eclipse Tools

debuggable. This is discussed in more detail in Chapter 2, A Simple Plug-in

Example.

Tip

: The amount of memory necessary to run an

Eclipse Application

depends upon what you load in that environment. You might consider increasing the amount of memory allocated to this launch by clicking on the

Arguments

tab of the

Eclipse Application

launch configuration entering something like the following in the arguments field:

-Xms40m -Xmx256m -XX:MaxPermSize=256m

The

Java Applet

configuration type is very similar to the

Java Application

type, but it is specifically designed to be used with Java applets. In addition to the tabs available for Java applications, it adds a

Parameters

tab that specifies applet-specific information such as width, height, name, and applet parameters.

Figure 1–65

Launch configuration (Run) dialog.

The

JUnit

configuration is used to run JUnit test cases.

JUnit test cases

are a special type of Java application, so many of the configuration options are the same as for Java applications and applets. It adds a

Test

tab for specifying settings unique to the test case that will be executed. JUnit is discussed in more detail in Section 1.11, Introduction to Testing, on page 63.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.10

Introduction to Debugging 59

The

JUnit Plug-in Test

configuration is used to run JUnit tests associated with an Eclipse plug-in. It is similar to the

Eclipse Application

configuration with the addition of the

Test

tab used by the JUnit configuration.

Tip

: Eclipse supports hot code replacement during debug operations when using JDK 1.4 or above to run an application. If a JDK 1.4 compliant JRE isn’t your default, you can specify one to use when running or debugging an application by selecting it from the drop-down list on the

JRE

tab of the launch configurations dialog. If one isn’t in the list, you can add one via the

New

button.

1.10

Introduction to Debugging

The previous section showed how to run a Java application using the options available under the

Run

menu. Any output or errors are written to the

Console

view. Placing

System.out.println()

statements at appropriate places in your code will give you limited debugging capabilities. Fortunately, Eclipse provides a much more effective debugging solution in the form of its integrated Java debugger.

The easiest way to debug a Java application is to select the class and then select the

Run

>

Debug As > Java Application

command (

Ctrl+Shift+D, J

) or the

Debug

toolbar button. This opens the

Debug

perspective (see Figure

1–8 on page 10), which you can use to step through individual statements within your code, set breakpoints, and inspect and change the values associated with individual variables. After you’ve run your application under the debugger the first time, you can use the

Run > Debug History

list to quickly run it again. This list is also available from the

Debug

toolbar button’s dropdown menu.

1.10.1

Setting breakpoints

To stop the debugger at a particular location in the code, set a breakpoint. At the location where you would like to set the breakpoint, right-click in the marker bar of the editor and select the

Toggle Breakpoint

command (see Figure 1–66). In addition to right-clicking, you can double-click the marker bar to the left of the line at which you want to place the breakpoint. A breakpoint marker appears next to the appropriate source code line.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

60

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–66

Adding a breakpoint.

With one or more breakpoints set, the application runs until it encounters a breakpoint and then stops before executing the line with the breakpoint. The debugger shows which program threads are running and which ones have been suspended. It also shows the line of code at which execution has stopped and highlights that line of code in the editor (see Figure 1–67).

Tip

: If you are not hitting a breakpoint that you set, take a close look at how the breakpoint appears in the gutter. For an enabled breakpoint, you will see either a plain blue bullet or a blue bullet with a small checkmark. The checkmark icon appears only after launching the VM and the breakpoint exists in a loaded class.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.10

Introduction to Debugging

5HWXUQWR7DEOHRI&RQWHQWV

61

Figure 1–67

The debugger, stopping at a breakpoint.

Tip

: Check the

Remove terminated launches when a new launch is created

checkbox on the

Run/Debug > Launching

preference page to automatically clean up old launches.

1.10.2

Using the Debug view

After execution has stopped on a breakpoint, the

Debug

view presents various options for resuming execution, stepping through the program statement-bystatement, or terminating it altogether.

The

Resume

button (also the

F8

key) in the

Debug

view resumes the execution of a program until it either ends on its own or encounters another breakpoint, while the entirely.

Terminate

button stops execution of a program

The button (also the

F3

key) executes the next expression in the highlighted statement, while the

Step Over

button (also the

F6

key) steps over the highlighted statement and stops on the next statement.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

62 CHAPTER 1 • Using Eclipse Tools

1.10.3

Using the Variables view

The

Variables

view shows the state of the variables in the current stack frame

(see Figure 1–68). Selecting a variable shows its value in the details pane at the bottom of the view. Primitive variable types show their values directly, while object types can be expanded to show their individual elements. You can change the value of a primitive in this view, but you can’t change the value of object types unless you use the

Expressions

view (see Section 1.10.4, Using the

Expressions view). Note that the variables listed in the

Variables

view change as you step through your program.

Figure 1–68

Variables view.

1.10.4

Using the Expressions view

The

Expressions

view (see Figure 1–69) provides a place to inspect values in the debugger and discover the results of various expressions entered into the editor, the detail pane of the

Variables

view, or the detail pane of the

Expressions

view.

To use the

Expressions

view, first select the expression to execute. This can be an existing expression or one that you enter. Next, select the

Watch

,

Display,

or

Inspect

command from the popup menu in the editor, the

Variables

view, or the

Expressions

view.

Figure 1–69

Expressions view.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.11

Introduction to Testing 63

If you select the

Display

command while an expression is selected in an editor, the results will be shown in the

Display

view. When an expression is selected in the

Variables

view or

Expressions

view, the results will be shown in the detail pane of that view.

If you select the

Inspect

command, a popup window containing the results of the expression appears. Pressing

Ctrl+Shift+I

will move the results to the

Expressions

view. As with the

Variables

view, primitive variable types show their values directly while object types can be expanded to show their individual elements.

1.11

Introduction to Testing

In addition to manually testing an application by running or debugging it,

Eclipse supports the JUnit framework (see

www.junit.org

) for creating and running repeatable test cases.

1.11.1

Creating test cases

To create a test case, you first need to add the junit.jar

file (from the org.junit

plug-in) to your project as a library using the Java

Build Path >

Libraries

project property page. Click the

Add Library...

button, select

“JUnit”, click

Next

, select “JUnit 3” and click

Finish

.

Once this is done, select the class for which you want to create a test case, open the

New

wizard, and select the

Java > JUnit > JUnit Test Case

option.

This invokes the

JUnit Test Case

wizard (see Figure 1–70), which creates a

JUnit test case. If you forget to add the junit.jar

to your project, the wizard will offer to do this for you automatically.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

64

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–70

JUnit Test Case wizard.

By default, the name of the new test case is the name of the test class with the word “Test” added to the end. Optionally, you can have the wizard create main()

, setup()

, and teardown()

methods as well as test methods for any public or protected method in the test class.

Tip

: CodePro includes a more advanced

Test Case

wizard that provides a number of enhancements over the Eclipse Test Case wizard, such as the ability to specify arbitrary test cases, generate better default code, and support the creation of test fixtures.

1.11.2

Running test cases

After a test case is created, select the test case class (or the project or package containing the test case) and then select the

Run

>

Run As > JUnit Test

command (

Alt+Shift+X, T

). This opens the

JUnit

view (see Figure 1–71), which shows the results of the test run. The

Failures

tab shows a list of the failures that were recorded in the test case, and the

Hierarchy

tab shows the entire test suite as a tree.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

1.12

Introduction to Mylyn

5HWXUQWR7DEOHRI&RQWHQWV

65

Figure 1–71

JUnit view.

If there are any test failures, correct the problem(s) and rerun the test by clicking on the

Rerun Test

button in the

JUnit

view. Alternatively, re-run the last test by selecting it from the

Run

menu or toolbar button. If you need to customize the test configuration, select the

Run > Run…

command to open the launch configuration dialog (see Figure 1–65 on page 58).

1.12

Introduction to Mylyn

Mylyn is a built-in part of several Eclipse distributions including the

Java

Developers

,

Java EE Developers

and the

RCP/Plug-in Developers

editions.

Mylyn adds a powerful mechanism for managing tasks. A

task

is any unit of work that you want to remember or share with others such as a bug report or feature request. Mylyn allows you to store tasks locally in your workspace or work with tasks stored in repositories such as Bugzilla, Trac, or JIRA. In order to connect to a task repository, you need to have the appropriate Mylyn connector for that repository.

Once you have set up your tasks, Mylyn monitors your activity to identify information relevant to the current task. Mylyn creates a task context which is the set of all artifacts (files you have opened, methods you have edited, APIs you have referenced, etc.) related to your task. Mylyn uses the task context to focus the Eclipse UI only on relevant information while filtering out anything that isn’t important or related to the current task. By giving you quick access to just the information you need, Mylyn can improve your productivity by reducing the amount of time you spend searching, scrolling, or otherwise navigating the system. By making the task context concrete, Mylyn makes it easier to work on multiple tasks simultaneously, return to prior tasks, and share tasks with others.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

66

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–72

Mylyn views and editors.

Eventually, using Mylyn may result in a subtle but fundamental shift in the way you work. Proponents of the Mylyn task-focused interface report a significant increase in productivity because of the ability to easily manage multiple, collaborative tasks and track progress within all of them.

Figure 1–72 shows some of the Mylyn features at work such as the

Task

List

(showing a single active task and various Bugzilla reports), a rich task editor (showing bug attributes, description, comments, etc.), and task-focused filtering in effect within the Eclipse

Package Explorer

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.12

Introduction to Mylyn 67

1.12.1

Using Mylyn to search Bugzilla

Mylyn provides a convenient interface for searching the Eclipse Bugzilla tracking system (see Section 21.2.2, Bugzilla—Eclipse bug tracking system, on page 782). This makes it easy to search for existing bugs or report new bugs.

To search for a bug or feature request in Bugzilla, open the

New

wizard, and select the

Mylyn > Query

option and then the

Eclipse.org

repository and

Create query using form

options (see Figure 1–73).

Figure 1–73

New Bugzilla Query wizard.

This invokes the

New Bugzilla Query

wizard (see Figure 1–74) in which you can enter various query criteria. Start by entering a title for the query, which will be showing in the Mylyn

Task List

view. Next, choose the criteria you wish to drive the search. You can search for a specific bug number, look for a specific word in the bug summary or comments, restrict the search based on the product, component, version, status, resolution, etc., or search for a specific person associated with the bug such as the owner, reporter, or commenter. Click the

Finish

button to lock in the query and add it to the

Task List

view.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

68

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 1 • Using Eclipse Tools

Figure 1–74

Bugzilla query parameters.

The Mylyn

Task List

view (see Figure 1–75) shows list of query items you have created. Expand any query entry to see the Bugzilla entries that match.

The icons and fonts used to decorate each line item indicate its status, resolution, and severity.

Figure 1–75

Task List view showing bugs matching the query.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

1.13

Summary 69

Double-click on a task item to open a Mylyn-provided, task-specific editor

(see Figure 1–76). For Bugzilla entries, you will see a Bugzilla-specific editor that shows a description of the bug and its associated comments and attachments. The editor also provides an area for adding a new comment and a list of actions that can be taken against the bug.

1.13

Figure 1–76

Bugzilla Task editor showing bug details.

Summary

This chapter gave you a whirlwind tour of the major components of the

Eclipse IDE that you will need to use to develop Eclipse plug-ins. At this point, you should be comfortable navigating the Eclipse UI and using the built-in

Eclipse tools to create, edit, run, debug, and test your Java code.

The next chapter dives right in and gets our hands dirty creating the first

Eclipse plug-in. Each succeeding chapter will introduce more and more layers of detail and slowly convert the plug-in from a simple example into a powerful tool that can be used on a daily basis while doing Eclipse development.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

70 CHAPTER 1 • Using Eclipse Tools

References

Eclipse-Overview.pdf (available from the

eclipse.org

Web site).

D’Anjou, Jim, Scott Fairbrother, Dan Kehn, John Kellerman, and Pat McCarthy,

The Java Developer’s Guide to Eclipse, Second Edition

. Addison-Wesley,

Boston, 2004.

Arthorne, John, and Chris Laffra,

Official Eclipse 3.0 FAQs

. Addison-Wesley,

Boston, 2004.

Carlson, David,

Eclipse Distilled

. Addison-Wesley, Boston, 2005.

Eclipse Wiki (see

wiki.eclipse.org

).

CVS (see

www.cvshome.org

).

CVS Howto (see

wiki.eclipse.org/index.php/CVS_Howto

).

Mylyn (see

www.eclipse.org/mylyn

)

Mylyn Tutorial (see

www.ibm.com/developerworks/java/library/j-mylyn1

)

Mylyn Wiji (see

wiki.eclipse.org/index.php/Mylyn

)

Fowler, Martin,

Refactoring: Improving the Design of Existing Code

, Addison-Wesley, Boston, 1999 (

www.refactoring.com

).

Glezen, Paul, “Branching with Eclipse and CVS.” IBM, July 3, 2003

(

www.eclipse.org/articles/Article-CVS-branching/eclipse_branch.html

).

JUnit (see

www.junit.org

).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 2

A Simple Plug-in Example

Before covering the Eclipse infrastructure (see Chapter 3) and each area of plug-in construction in-depth, it is useful to create a simple plug-in on which discussion and examples can be based. This chapter takes a step-by-step approach to creating a simple but fully operational plug-in that will be enhanced bit-by-bit during the course of this book. This process provides valuable firsthand experience using the Eclipse IDE and touches on every aspect of building and maintaining a plug-in.

2.1

The Favorites Plug-in

The

Favorites

plug-in, which you’ll build over the course of this book, displays a list of resources, lets you add and remove resources from the list, easily opens an editor on a selected resource, updates the list automatically as a result of events elsewhere in the system, and more. Subsequent chapters discuss aspects of plug-in development in terms of enhancements to the

Favorites

plug-in.

This chapter starts the process by covering the creation of the

Favorites

plug-in in its simplest form using the following steps:

• Creating a plug-in project

• Reviewing the generated code

• Building a product

• Installing and running the product

71

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

72 CHAPTER 2 • A Simple Plug-in Example

2.2

Creating a Plug-in Project

The first step is to create a plug-in project using the Eclipse

New Project

wizard.

In addition to creating a new project, this wizard has a number of different code generation options, such as views, editors, and actions, for creating sample plug-in code. To keep things simple and focus only on the essentials of plug-in creation, select the

Plug-in with a view

option, which is discussed in the next subsection.

2.2.1

New Plug-in Project wizard

From the

File

menu, select

New > Project

to launch the

New Project

wizard

(see Figure 2–1). On this first page of the wizard, select

Plug-in Project

from the list and then click the

Next

button.

Figure 2–1

New Project wizard page 1—selecting a project type.

On the next page of the wizard (see Figure 2–2), enter the name of the project; in this case, it’s com.qualityeclipse.favorites

, which is the same as the Favorites plug-in identifier. Chapter 3, Eclipse Infrastructure, discusses

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.2

Creating a Plug-in Project 73

plug-in identifiers and other aspects of plug-in architecture in more detail. Fill in the other fields as shown and then click the

Next

button.

Figure 2–2

New Project wizard page 2—naming the project..

Tip:

A project can be named anything, but it is easier to name it the same as the plug-in identifier. By convention, this is the plug-in project-naming scheme that the Eclipse organization uses for most of its work. Because of this, the

New Project

wizard assumes that the project name and the plugin identifier are the same.

2.2.2

Define the plug-in

Every plug-in has a

META-INF/MANIFEST.MF

file. In addition, it may contain a plugin.xml

file and/or a Java class that represents the plug-in programmatically. The next wizard page displays options for generating both the plug-in manifest and plug-in Java class. Supply the

Plug-in ID

,

Plug-in Version

,

Plugin Name

and more for the plug-in as shown in Figure 2–3 then click the

Next

button.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

74

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 2 • A Simple Plug-in Example

Figure 2–3

New Project wizard page 3—describing the plug-in.

Next, the

New Plug-in Project

wizard next displays the various plug-in pieces that can be automatically generated by the wizard (see Figure 2–4).

There are many different options on this page for generating quite a bit of sample code. It is useful to try out each option and review the code that is generated; however for this example, select

Plug-in with a view

and then click the

Next

button.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

2.2

Creating a Plug-in Project

5HWXUQWR7DEOHRI&RQWHQWV

75

Figure 2–4

New Plug-in Project wizard page 4—selecting a plug-in type.

2.2.3

Define the view

Selecting view code generation options is the next step in this process. Enter the values for this page (see Figure 2–5), uncheck the

Add the view to the resource perspective

and

Add context help to the view

(Eclipse 3.4 only) checkboxes to simplify the generated plug-in manifest file.

If you are in Eclipse 3.3, then click the

Next

button and uncheck each of the code generation options (see Figure 2–6). Each of these checkboxes represents code that could be generated as part of the

Favorites

view. These are covered in subsequent chapters. This wizard page has been removed in Eclipse 3.4

and thus the

FavoritesView

class generated by the wizard will contain more code than is shown in the book (see Section 2.3.3, The Favorites view, on page 84).

When you click the

Finish

button, the new plug-in project is created and the plug-in manifest editor is automatically opened (see Figure 2–9).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

76

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 2 • A Simple Plug-in Example

Figure 2–5

New Plug-in Project wizard page 5—defining the view.

Figure 2–6

New Plug-in Project wizard page 6—code generation options for the view (Eclipse 3.3 only)

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.3

Reviewing the Generated Code 77

2.3

Reviewing the Generated Code

Reviewing the code generated by the

New Plug-in Project

wizard provides a brief look at the following major parts comprising the sample plug-in.

• The plug-in manifests

• The plug-in class

• The

Favorites

view

2.3.1

The Plug-in manifests

The plug-in manifest editor shows the contents of the two plug-in manifest files,

META-INF/MANIFEST.MF

and plugin.xml

, which define how this plugin relates to all the others in the system. This editor is automatically opened to its first page (see Figure 2–9) as a result of creating a new plug-in project.

If the plug-in manifest editor is closed, double-clicking on either the

META-

INF/MANIFEST.MF

or the plugin.xml

file reopens the editor. The following is an overview of the manifest editor, while more detail on the plug-in manifest itself can be found in Chapter 3.

Although the editor is a convenient way to modify the plug-in’s description, it’s still useful to peek at the source behind the scenes to see how the editor’s different parts relate to the underlying code. Click the

MANIFEST.MF

tab to display the source of the

META-INF/MANIFEST.MF

file that defines the runtime aspects of this plug-in (see Figure 2–7). The first two lines define it as an OSGi manifest file (see Section 3.3, Plug-in Manifest, on page 113). Subsequent lines specify plug-in name, version, identifier, classpath, and plug-ins on which this plug-in depends. All these aspects are editable using other pages in the plug-in manifest editor.

Figure 2–7

Plug-in manifest editor MANIFEST.MF page.

New in Eclipse 3.4

The

Eclipse-LazyStart: true

directive in the

MANIFEST.MF

file has been replaced with

Bundle-ActivationPolicy: lazy

. Both directives have the same semantics; only the name has changed.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

78 CHAPTER 2 • A Simple Plug-in Example

Clicking on the

plugin.xml

tab of the editor displays the plugin.xml

file that defines the extension aspects of this plug-in (see Figure 2–8). The first line declares this to be an XML file, while subsequent lines specify plug-in extensions.

Figure 2–8

Plug-in manifest editor plugin.xml page.

Figure 2–9

Plug-in manifest editor Overview page.

The

This plug-in is a singleton

option was added in Eclipse 3.4.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.3

Reviewing the Generated Code 79

The

Overview

page of the manifest editor shows a summary of the plugin manifest (see Figure 2–9). The section on this page describing general information, such as the plug-in identifier (ID), version, name, class, and provider, corresponds to the first chunk of source in the

META-INF/MANIFEST.MF

file:

Bundle-Name: Favorites Plug-in

Bundle-SymbolicName: com.qualityeclipse.favorites; singleton:=true

Bundle-Version: 1.0.0

Bundle-Activator: com.qualityeclipse.favorites.FavoritesActivator

Bundle-Vendor: QualityEclipse

Bundle-RequiredExecutionEnvironment: JavaSE-1.5

You can edit the information on the

Overview

page or switch to the

MANIFEST.MF

page and edit the source directly.

Tip:

Making changes to any page other than the

plugin.xml

and

MANIFEST.MF

pages may cause the manifest editor to reformat the source. If you are particular about the formatting of either manifest file, then either use only the

plugin.xml

and

MANIFEST.MF

pages to perform editing or use another editor.

Caution:

The formatting rules of

META-INF/MANIFEST.MF

include some quite nonintuitive rules related to line length and line wrapping. Edit plugin.xml

with care, and

META-INF/MANIFEST.MF

with caution!

The reliance of this plug-in on other plug-ins in the system appears on the

Dependencies

page of the plug-in manifest editor (see Figure 2–10).

Figure 2–10

Plug-in manifest editor Dependencies page.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

80 CHAPTER 2 • A Simple Plug-in Example

This corresponds to the

Require-Bundle

chunk of source in the

META-INF/MANIFEST.MF

file:

Require-Bundle: org.eclipse.ui,

org.eclipse.core.runtime

For the

Favorites

plug-in, this section indicates a dependency on the org.eclipse.core.runtime

and org.eclipse.ui

plug-ins. This dependency declaration differs from the

Favorites

project’s Java build path (also known as the compile-time classpath) because the Java build path is a compile-time artifact, while the plug-in dependency declaration comes into play during plug-in execution. Because the project was created as a plug-in project and has the org.eclipse.pde.PluginNature

nature (see Section

14.3, Natures, on page 561 for more on project natures), any changes to this dependency list will automatically be reflected in the Java build path, but not the reverse. If these two aspects of your plug-in get out of sync, then you can have a plug-in that compiles and builds but does not execute properly.

Tip

: Edit this dependency list rather than the Java build path so that the two are automatically always in sync.

Alternatively, the dependencies could have been expressed as

Imported

Packages

on the

Dependencies

page of the manifest editor (see Figure 2–10 and the end of Section 3.3.3, Plug-in dependencies, on page 116). This would correspond to an

Import-Package

chunk of source in the

META-INF/

MANIFEST.MF

file looking something like this:

Import-Package: org.eclipse.ui.views,

org.eclipse.core.runtime.model

The

Runtime

page of the manifest editor (see Figure 2–11) corresponds to the

Bundle-ClassPath

chunk of source in the

META-INF/MANIFEST.MF

file, which defines what libraries are delivered with the plug-in and used by the plug-in during execution, what package prefixes are used within each library

(used to speed up plug-in loading time), and whether other plug-ins can reference the code in the library (see Section 21.2.5, Related plug-ins, on page 783 for more on package visibility). For the

Favorites

plug-in, all the code is contained in the com.qualityeclipse.favorites_1.0.0.jar

itself, so no

Bundle-ClassPath

declaration is necessary.

The

Favorites

plug-in does not export any packages for other plug-ins to use or extend.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

2.3

Reviewing the Generated Code

5HWXUQWR7DEOHRI&RQWHQWV

81

Figure 2–11

Plug-in manifest editor Runtime page.

The

Extensions

page (see Figure 2–12) displays how this plug-in augments the functionality already provided by other plug-ins in the system, and corresponds to the

<extension point="org.eclipse.ui.views">

XML in the plugin.xml

file: chunk of

<extension

point="org.eclipse.ui.views">

<category

name="QualityEclipse"

id="com.qualityeclipse.favorites">

</category>

<view

name="Favorites"

icon="icons/sample.gif"

category="com.qualityeclipse.favorites"

class="com.qualityeclipse.favorites.views.FavoritesView"

id="com.qualityeclipse.favorites.views.FavoritesView">

</view>

</extension>

The

Favorites

plug-in declares an extension to the org.eclipse.ui

plug-in using the org.eclipse.ui.views

extension point by providing an additional category of views named

QualityEclipse

and a new view in that cat-

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

82 CHAPTER 2 • A Simple Plug-in Example

egory named

Favorites

. Selecting an item in the tree on the left in the

Extensions

page causes the properties for that item to appear on the right. In this case, selecting

Favorites (view)

on the

Extensions

page displays the name, identifier, class, and more information about the

Favorites

view that is being declared. This corresponds to the XML attributes defined in the

<view> chunk of XML shown previously.

Figure 2–12

Plug-in manifest editor Extensions page.

Finally, the

Extension Points

page of the manifest editor (see Figure 2–13) facilitates the definition of new extension points so that other plug-ins can augment the functionality provided by this plug-in. At this time, the

Favorites

plug-in doesn’t define any extension points and therefore cannot be augmented by other plug-ins (see Section 17.2, Defining an Extension Point, on page 639).

Figure 2–13

Plug-in manifest editor Extension Points page.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.3

Reviewing the Generated Code 83

2.3.2

The Activator or Plug-in class

Every plug-in optionally can declare a class that represents the plug-in from a programmatic standpoint as displayed on the manifest editor’s

Overview

page

(see Figure 2–9). This class is referred to as an

Activator

(or, in earlier versions of Eclipse, a

Plug-in

class). In the

Favorites

plug-in, this class is named com.qualityeclipse.favorites.FavoritesActivator

.

package com.qualityeclipse.favorites; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.BundleContext;

/**

* The activator class controls the plug-in life cycle

*/ public class FavoritesActivator extends AbstractUIPlugin {

// The plug-in ID

public static final String PLUGIN_ID

= "com.qualityeclipse.favorites";

// The shared instance

private static FavoritesActivator plugin;

/**

* The constructor

*/

public FavoritesActivator() {

}

/**

* This method is called upon plug-in activation.

*/

public void start(BundleContext context) throws Exception {

super.start(context);

plugin = this;

}

/**

* This method is called when the plug-in is stopped.

*/

public void stop(BundleContext context) throws Exception {

plugin = null;

super.stop(context);

}

/**

* Returns the shared instance

*/

public static FavoritesActivator getDefault() {

return plugin;

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

84 CHAPTER 2 • A Simple Plug-in Example

/**

* Returns an image descriptor for the image file at the given

* plug-in relative path

*

* @param path the path

* @return the image descriptor

*/

public static ImageDescriptor getImageDescriptor(String path) {

return imageDescriptorFromPlugin(PLUGIN_ID, path);

}

}

If the

Bundle-ActivationPolicy

in the

META-INF/MANIFEST.MF

file is lazy

, then when the plug-in is activated, the Eclipse system instantiates the activator class before loading any other classes in it. This corresponds to the

“Activate this plug-in when one of its classes is loaded.” checkbox on the

Overview

page of the manifest editor (see Figure 2–9). This single activator class instance is used by the Eclipse system throughout the life of the plug-in and no other instance is created.

Tip

: For more background on Bundle-ActivationPolicy, see http://wiki.eclipse.org/Lazy_Start_Bundles.

Typically, activator classes declare a static field to reference this singleton so that it can be easily shared throughout the plug-in as needed. In this case, the

Favorites

plug-in defines a field named plugin

that is assigned in the start method and accessed using the getDefault

method.

Tip

: The Eclipse system always instantiates exactly one instance of an active plug-in’s

Activator class. Do not create instances of this class yourself.

2.3.3

The Favorites view

In addition to the plug-in manifest and plug-in class, the

New Plug-in Project

wizard generated code for a simple view (in the following sample) called

Favorites

. At this point, the view creates and displays information from a sample model; in subsequent chapters, however, this view will be hooked up to a favorites model and will display information from the favorites items contained within that model. Eclipse 3.4 generates additional code unnecessary for this exercise, so adjust the generated code to appear as shown below.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.3

Reviewing the Generated Code

package com.qualityeclipse.favorites.views; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.part.*; import org.eclipse.jface.viewers.*; import org.eclipse.swt.graphics.Image; import org.eclipse.jface.action.*; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.ui.*; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.SWT;

/**

* This sample class demonstrates how to plug-in a new workbench

* view. The view shows data obtained from the model. The sample

* creates a dummy model on the fly, but a real implementation

* would connect to the model available either in this or another

* plug-in (e.g., the workspace). The view is connected to the

* model using a content provider.

* <p>

* The view uses a label provider to define how model objects

* should be presented in the view. Each view can present the

* same model objects using different labels and icons, if

* needed. Alternatively, a single label provider can be shared

* between views in order to ensure that objects of the same type

* are presented in the same way everywhere.

* <p>

*/ public class FavoritesView extends ViewPart {

private TableViewer viewer;

/*

* The content provider class is responsible for providing

* objects to the view. It can wrap existing objects in

* adapters or simply return objects as-is. These objects may

* be sensitive to the current input of the view, or ignore it

* and always show the same content (Task List, for

* example).

*/

class ViewContentProvider

implements IStructuredContentProvider

{

public void inputChanged(

Viewer v, Object oldInput, Object newInput) {

}

public void dispose() {

}

public Object[] getElements(Object parent) {

return new String[] { "One", "Two", "Three" };

}

}

85

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

86 CHAPTER 2 • A Simple Plug-in Example

/*

* The label provider class is responsible for translating

* objects into text and images that are displayed

* in the various cells of the table.

*/

class ViewLabelProvider extends LabelProvider

implements ITableLabelProvider

{

public String getColumnText(Object obj, int index) {

return getText(obj);

}

public Image getColumnImage(Object obj, int index) {

return getImage(obj);

}

public Image getImage(Object obj) {

return PlatformUI.getWorkbench().getSharedImages()

.getImage(ISharedImages.IMG_OBJ_ELEMENT);

}

}

/**

* The constructor.

*/

public FavoritesView() {

}

/**

* This is a callback that will allow us to create the viewer

* and initialize it.

*/

public void createPartControl(Composite parent) {

viewer = new TableViewer(

parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);

viewer.setContentProvider(new ViewContentProvider());

viewer.setLabelProvider(new ViewLabelProvider());

viewer.setInput(getViewSite());

}

/**

* Passing the focus request to the viewer's control.

*/

public void setFocus() {

viewer.getControl().setFocus();

}

}

2.4

Building a Product

Building a product involves packaging up only those elements to be delivered in a form that the customer can install into his or her environment. You can

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.4

Building a Product 87

build the product in several different ways, including manually or by using a

Windows batch script, a UNIX shell script, or an Apache Ant script. You can deliver the end product as a single compressed file or as a stand-alone executable. For our purposes, the

Favorites

plug-in will be delivered with source code as a single compressed zip file.

2.4.1

Building manually

Building a product manually involves launching an Eclipse

Export

wizard, filling out a few fields, and clicking the

Finish

button. Select the

File

>

Export

command to launch the desired export wizard. On the first wizard page (see

Figure 2–14), select

Deployable plug-ins and fragments

and then click the

Next

button.

Figure 2–14

Export wizard page 1—choosing the type of export.

On the second page of the

Export

wizard (see Figure 2–15), select the plug-ins to be exported, enter the name of the zip file to contain the result, and select the options shown. In addition, specify that this export operation be saved as an Ant script in a file named build-favorites.xml

in the com.qualityeclipse.favorites

project and check the

Include source code

option, then click

Finish

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

88

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 2 • A Simple Plug-in Example

Figure 2–15

Export wizard page 2—specifying the zip file’s contents.

The created zip file contains a single plug-in JAR file (a plug-in can be deployed as a single JAR file as of Eclipse 3.1): plugins/com.qualityeclipse.favorites_1.0.0.jar

And that plug-in JAR file contains the plug-in as specified in the

Export

wizard: com.qualityeclipse.favorites classes com.qualityeclipse.favorites source files plugin.xml

icons/sample.gif

META-INF/MANIFEST.MF

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.4

Building a Product 89

Unfortunately, this process is manual, and therefore prone to errors.

Manually building a product is fine once or twice, but what if a different person in the company needs to build the product? What happens as the product grows and encompasses more plug-ins? A product needs a repeatable and reliable method for building it.

2.4.2

Building with Apache Ant

An Apache Ant script provides a reliable, flexible, and repeatable process for building a plug-in project. There is a little more up-front work to set up an

Ant script, but it is much less error-prone over time than building a product manually. For more information about Ant and constructing more complex build scripts, see Chapter 19.

Eclipse can generate simple Ant scripts. The prior section specified that the

Export

wizard generates an Ant script file named build-favorites.xml

in the com.qualityeclipse.favorites

project:

<?xml version="1.0" encoding="UTF-8"?>

<project default="plugin_export" name="build">

<target name="plugin_export">

<pde.exportPlugins

destination="C:\Build\QualityEclipse"

exportSource="true"

exportType="zip"

filename="FavoritesProduct.zip"

plugins="com.qualityeclipse.favorites"

useJARFormat="true" />

</target>

</project>

The preceding simple script works well from the Eclipse UI; however, unfortunately, the pde.exportPlugins

and other pde.export*

tasks are asynchronous and cannot be used in a headless environment (see Bugzilla entry 58413 at

bugs.eclipse.org/bugs/show_bug.cgi?id=58413

) making it difficult to build more than simple scripts.

If you want your build script to do more than just export plug-ins (see Section 3.2.1, Link files, on page 111), then you’ll need a more complex Ant script similar to the following. For more on Ant and build scripts, see Chapter

19, Building a Product.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

90 CHAPTER 2 • A Simple Plug-in Example

<?xml version="1.0" encoding="UTF-8"?>

<project default="plugin_export" name="build">

<target name="plugin_export">

<!-- Define build directories -->

<property name="build.root"

location="/Build/QualityEclipse" />

<property name="build.temp"

location="${build.root}/temp" />

<property name="build.out"

location="${build.root}/product" />

<!-- Create build directories -->

<delete dir="${build.temp}" />

<mkdir dir="${build.temp}" />

<mkdir dir="${build.out}" />

<!-- Read the MANIFEST.MF -->

<copy file="META-INF/MANIFEST.MF" todir="${build.temp}" />

<replace file="${build.temp}/MANIFEST.MF">

<replacefilter token=":=" value="=" />

<replacefilter token=":" value="=" />

<replacetoken>;</replacetoken>

<replacevalue>

</replacevalue>

</replace>

<property file="${build.temp}/MANIFEST.MF"/>

<!-- Plugin locations -->

<property name="plugin.jarname" value=

"com.qualityeclipse.favorites_${Bundle-Version}" />

<property name="plugin.jar" location=

"${build.temp}/jars/plugins/${plugin.jarname}.jar" />

<property name="product.zip" value=

"${build.out}/Favorites_v${Bundle-Version}.zip" />

<!-- Assemble plug-in JAR -->

<mkdir dir="${build.temp}/jars/plugins" />

<zip destfile="${plugin.jar}">

<zipfileset dir="bin" />

<zipfileset dir="." includes="META-INF/MANIFEST.MF" />

<zipfileset dir="." includes="plugin.xml" />

<zipfileset dir="." includes="icons/*.gif" />

<zipfileset dir="." includes="src/**/*" />

</zip>

<!-- Assemble the product zip -->

<zip destfile="${product.zip}">

<fileset dir="${build.temp}/jars" />

</zip>

</target>

</project>

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.4

Building a Product 91

To execute this Ant script, right-click on the build-favorites.xml

file and select

Run Ant…

(see Figure 2–16). When the Ant wizard appears, click on the

JRE

tab and select the

Run in the same JRE as the workspace

option

(see Figure 2–17). Click the

Run

button to build the product.

Figure 2–16

The build.xml popup context menu.

Tip

: If your Ant script uses Eclipse-specific Ant tasks, such as pde. exportPlugins

, then you must select the

Run in the same JRE as the workspace

option for your Ant script to execute properly.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

92

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 2 • A Simple Plug-in Example

Figure 2–17

The Ant wizard.

2.5

Installing and Running the Product

To install the

Favorites

plug-in, do the following.

• Shut down Eclipse

• Unzip the

Favorites_v1.0.0.zip

file into your Eclipse directory

(e.g.,

C:/eclipse

)

• Verify that the favorites plug-in is in the

/plugins

directory

(e.g.,

C:/eclipse/plugins/com.qualityeclipse. favorites_1.0.0.jar

)

• Restart Eclipse

Tip

: Eclipse caches plug-in information in a configuration directory (see

Section 3.4.5, Plug-in configuration files, on page 123). If you are installing a new version of your plug-in over an already installed one without incrementing the version number, then use the

-clean command-line option when launching Eclipse so that it will rebuild its cached plug-in information.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.5

Installing and Running the Product 93

After Eclipse has restarted, from the

Window

menu, select

Show View >

Other...

(see Figure 2–18) to open the

Show View

dialog (see Figure 2–19). In the dialog, expand the

Quality Eclipse

category, select

Favorites

, and then click the

OK

button. This causes the

Favorites

view to open (see Figure 2–20).

Figure 2–18

Show View > Other… from the Window menu.

Figure 2–19

Show View dialog.

Figure 2–20

The Favorites view in its initial and simplest form.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

94 CHAPTER 2 • A Simple Plug-in Example

2.6

Debugging the Product

Inevitably, during the course of producing a product, you’ll need to debug a problem or you’ll simply have to gain a better understanding of the code through a means more enlightening than just reviewing the source code. You can use the

Runtime Workbench

to determine exactly what happens during product execution so that you can solve problems.

2.6.1

Creating a configuration

The first step in this process is to create a configuration in which the product can be debugged. Start by selecting

Debug Configurations…

in the

Debug

toolbar menu (see Figure 2–21).

Figure 2–21

Debug menu.

Figure 2–22

Defining a new configuration.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.6

Debugging the Product 95

In the dialog that appears (see Figure 2–22), select

Eclipse Application

and then click the configuration.

New

button. Next, enter “Favorites” as the name of the

2.6.2

Selecting plug-ins and fragments

After the preceding, select the

Plug-ins

tab and

plug-ins selected below only

in the

Launch with

combo box (see Figure 2–23). In the list of plug-ins, make sure that the

Favorites

plug-in is selected in the

Workspace Plug-ins

category but not in the

External Plug-ins

category.

Tip

: Plug-in projects specified in the configuration take precedence over plug-ins installed in Eclipse itself. If you have a plug-in project with the same identifier as a plug-in installed in Eclipse and want to use the installed plug-in in the

Runtime Workbench

rather than the plug-in project, uncheck the plug-in project in the

Workspace Plug-ins

category and check the installed plug-in in the

External Plug-ins

category.

Figure 2–23

Selecting plug-ins in the configuration.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

96 CHAPTER 2 • A Simple Plug-in Example

2.6.3

Launching the Runtime Workbench

Click the

Debug

button to launch the

Eclipse Application

in the

Runtime

Workbench

to debug the product. Now that you’ve defined the configuration and used it once, it appears in the

Debug

toolbar menu (see Figure 2–21).

Selecting it from that menu launches the

Runtime Workbench

without opening the

Configuration

wizard.

After clicking the

Debug

button in the

Configuration

wizard or selecting

Favorites

from the

Debug

toolbar menu, Eclipse opens a second workbench window (the

Runtime Workbench

, as opposed to the

Development Workbench

). This

Runtime Workbench

window executes the code in the projects contained in the

Development Workbench

. Making changes and setting breakpoints in the

Development Workbench

affects the execution of the

Runtime Workbench

(see Section 1.10, Introduction to Debugging, on page 59 for more about this).

2.7

PDE Views

The Plug-in Development Environment (PDE) provides several views for inspecting various aspects of plug-ins. To open the various PDE views, select

Window > Show View > Other…

; in the

Show View

dialog, expand both the

PDE

and

PDE Runtime

categories.

2.7.1

The Plug-in Registry view

The

Plug-in Registry

view displays a tree view of all plug-ins discovered in the current workspace (see Figure 2–24). Expanding the plug-in in the tree shows its components such as extension points, extensions, prerequisites, and runtime libraries.

Figure 2–24

The Plug-in Registry view.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.7

PDE Views 97

2.7.2

The Plug-ins view

The

Plug-ins

view shows a tree list of external plug-ins and plug-in projects in the current workspace and provides a quick way to review plug-ins that already exist (see Figure 2–25). In the tree, you can expand each external plugin to browse the files located in the plug-in directory. Unfortunately, if that plug-in is contained in a JAR file rather than a directory (new in Eclipse 3.1), the files are not displayed in this view (see Bugzilla entry 89143 at

bugs.eclipse.org/bugs/show_bug.cgi?id=89143

). Double-clicking on a file element opens that file in an editor for viewing, and there are several useful actions in the context menu such as

Add to Java Search

( see Section 1.6.2.1,

Java Plug-in Search, on page 32

), Find References and Open Dependencies.

Figure 2–25

The Plug-ins view.

2.7.3

The Plug-in Dependencies view

The

Plug-in Dependencies

view shows a hierarchy of which plug-ins are dependent on which other plug-ins, which in turn are dependent on other plug-ins, and so on (see Figure 2–26). When the view opens, first right-click on the com.qualityeclipse.favorites plug-in and select

Focus On

.

Double-clicking on an element in the tree opens the plug-in manifest editor for the corresponding plug-in.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

98

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 2 • A Simple Plug-in Example

Figure 2–26

The Plug-in Dependencies view.

2.7.4

Plug-in Artifact Search

In addition to the views described above, PDE provides the ability to search for extension references, extension point declarations, and plug-ins all in one place. Type Ctrl+Shift+A to open the PDE search dialog (see Figure 2–27), then enter the plug-in ID to filter the list. The dialog also includes filters for extensions and extension points to help you quickly and easily find what you are looking for.

Figure 2–27

PDE Plug-in Artifact dialog.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.8

Writing Plug-in Tests 99

2.7.5

Plug-in Spy

To find more information about the currently selected user interface element, open the Plug-in Spy (see Figure 2–28) by pressing Alt+Shift+F1. The Plug-in

Spy (also known as the PDE Spy) currently provides information about selections, editors, views, dialogs, preference pages, and wizards. When reviewing the information provided by the Plug-in Spy, clicking on the various hyperlinks opens the Plug-in Manifest editor on that plug-in.

Figure 2–28

Plug-in Spy popup.

2.8

Writing Plug-in Tests

Eclipse is a continually moving target, and when building plug-ins, tests are necessary to ensure that the product continues to function properly over multiple releases. If the goal was to develop and release a plug-in once, then manual testing would suffice; however, a combination of automated and manual tests are better at preventing regressions from creeping into the product over time.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

100 CHAPTER 2 • A Simple Plug-in Example

2.8.1

Test preparation

Before a test for the

Favorites

view can be created, you must modify the Favorites plug-in manifest so that the appropriate classes are visible to the test plugin. Open the plug-in manifest editor by double-clicking on the plugin.xml

file, then switch to the

Runtime

page (see Figure 2–11). In the

Exported Packages

section, click

Add...

, select the com.qualityeclipse.favorites.views

package and save the changes by selecting

File

>

Save

.

Tip

: You can limit the visibility of your exported packages by specifying which plug-ins can access a package in the

Package Visibility

section of the

Runtime

page in the plug-in manifest editor (see Section 21.2.5,

Related plug-ins, on page 783). Alternatively, you can place tests into a fragment so that no packages need to be exported (for more on fragments, see Section 16.3, Using Fragments, on page 629).

Next, add the appropriate accessor so that the test can validate the view content. In the

FavoritesView

class, add the following method:

/**

* For testing purposes only.

* @return the table viewer in the Favorites view

*/ public TableViewer getFavoritesViewer() {

return viewer;

}

2.8.2

Creating a Plug-in test project

Use the same procedure as outlined in Section 2.2, Creating a Plug-in Project, on page 72, to create a new plug-in project with the following exceptions:

• Name the project

com.qualityeclipse.favorites.test

• Uncheck the

Create a plug-in using one of these templates

checkbox

After the project has been created, use the

Dependencies

page of the plugin manifest editor (see Figure 2–10 on page 79) to add the following required plug-ins and then save the changes:

com.qualityeclipse.favorites

• org.junit4

2.8.3

Creating a Plug-in test

When a project has been created and the plug-in manifest modified, it’s time to create a simple test for the

Favorites

plug-in (see the following code example). The goal of the test is to show the

Favorites

view, validate its content, and then hide the view.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.8

Writing Plug-in Tests

package com.qualityeclipse.favorites.test; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.qualityeclipse.favorites.views.FavoritesView;

/**

* The class <code>FavoritesViewTest</code> contains tests

* for the class {@link

* com.qualityeclipse.favorites.views.FavoritesView}.

* @pattern JUnit Test Case

* @generatedBy CodePro Studio

*/ public class FavoritesViewTest

{

private static final String VIEW_ID =

"com.qualityeclipse.favorites.views.FavoritesView";

/**

* The object that is being tested.

*

* @see com.qualityeclipse.favorites.views.FavoritesView

*/

private FavoritesView testView;

/**

* Perform pre-test initialization.

*/

@Before

public void setUp() throws Exception {

// Initialize the test fixture for each test

// that is run.

waitForJobs();

testView = (FavoritesView)

PlatformUI

.getWorkbench()

.getActiveWorkbenchWindow()

.getActivePage()

.showView(VIEW_ID);

// Delay for 3 seconds so that

// the Favorites view can be seen.

waitForJobs();

delay(3000);

// Add additional setup code here.

}

101

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

102 CHAPTER 2 • A Simple Plug-in Example

/**

* Run the view test.

*/

@Test

public void testView() {

TableViewer viewer = testView.getFavoritesViewer();

Object[] expectedContent =

new Object[] { "One", "Two", "Three" };

Object[] expectedLabels =

new String[] { "One", "Two", "Three" };

// Assert valid content.

IStructuredContentProvider contentProvider =

(IStructuredContentProvider)

viewer.getContentProvider();

assertArrayEquals(expectedContent,

contentProvider.getElements(viewer.getInput()));

// Assert valid labels.

ITableLabelProvider labelProvider =

(ITableLabelProvider) viewer.getLabelProvider();

for (int i = 0; i < expectedLabels.length; i++)

assertEquals(expectedLabels[i],

labelProvider.getColumnText(expectedContent[i], 1));

}

/**

* Perform post-test cleanup.

*/

@After

public void tearDown() throws Exception {

// Dispose of test fixture.

waitForJobs();

PlatformUI

.getWorkbench()

.getActiveWorkbenchWindow()

.getActivePage()

.hideView(testView);

// Add additional teardown code here.

}

/**

* Process UI input but do not return for the

* specified time interval.

*

* @param waitTimeMillis the number of milliseconds

*/

private void delay(long waitTimeMillis) {

Display display = Display.getCurrent();

// If this is the UI thread,

// then process input.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.8

Writing Plug-in Tests 103

if (display != null) {

long endTimeMillis =

System.currentTimeMillis() + waitTimeMillis;

while (System.currentTimeMillis() < endTimeMillis)

{

if (!display.readAndDispatch())

display.sleep();

}

display.update();

}

// Otherwise, perform a simple sleep.

else {

try {

Thread.sleep(waitTimeMillis);

}

catch (InterruptedException e) {

// Ignored.

}

}

}

/**

* Wait until all background tasks are complete.

*/

public void waitForJobs() {

while (!Job.getJobManager().isIdle())

delay(1000);

}

}

2.8.4

Running a Plug-in test

The next step after creating a test class is to configure and execute the test.

Similar to creating a runtime configuration (see Section 2.6.1, Creating a configuration, on page 94), creating a test configuration involves right-clicking on the

FavoritesViewTest

in the

Package Explorer

and selecting the

Run As

> JUnit Plug-in Test

command. This automatically builds a test configuration and executes the test. You should then see the Runtime Workbench appear, the

Favorites

view open, and the Runtime Workbench close. The

JUnit

view indicates that your test executed successfully and the

Favorites

view content has been validated (see Figure 2–29).

Figure 2–29

The JUnit view.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

104 CHAPTER 2 • A Simple Plug-in Example

Right clicking on the

FavoritesViewTest

once again and selecting

Run As > Run Configurations...

opens the

Configuration

wizard (see Figure

2–30). Here you can specify whether a single test should be executed by itself or whether all tests in a project should be executed simultaneously.

Eclipse defaults to launching a product, which opens the Welcome view (see

Figure 1–2 on page 4) rather than an application. To change this, click on the

Main

tab and select the

Run an application

radio button.

Figure 2–30

The test Configuration wizard.

2.8.5

Uninstalling the Favorites plug-in

Use the following steps to delete the

Favorites

plug-in from the

Development

Workspace

:

1. Close the

Favorites

view.

2. Shut down Eclipse.

3. Delete the com.quality.favorites_1.0.0.jar

file in the Eclipse plug-ins directory.

4. Restart Eclipse. If you get an error message (see Figure 2–31) when restarting, at least one of the

Favorites

views was not closed when

Eclipse was shut down in Step 2.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

2.9

Book Samples 105

5. Verify that the

Favorites

view is no longer available by opening the

Show

View

dialog (see Figure 2–18) and verifying that the

Quality Eclipse

category is no longer present (see Figure 2–19).

Figure 2–31

Problems dialog when restarting Eclipse.

2.9

Book Samples

The sample code for each chapter in this book can be downloaded and installed into Eclipse for you to review. Download and install the book samples from

http://www.qualityeclipse.com

or using the update manager (see

Section 18.3.5, Accessing the update site, on page 685) by entering

http://www.qualityeclipse.com/update

” (see Figure 18–29 on page 687).

Once installed, open the view by selecting

Window > QualityEclipse Book

Samples

(see Figure 2–32).

Figure 2–32

QualityEclipse Book Samples View

Using the book samples view, you can compare your work to the sample code and load the code for particular chapters for review.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

106 CHAPTER 2 • A Simple Plug-in Example

2.10

Summary

This chapter covered the process of creating, running, debugging, inspecting, and testing a simple plug-in from start to finish. Subsequent chapters will cover every aspect of this process, plus more in much greater detail.

References

Gamma, Eric, and Kent Beck,

Contributing to Eclipse

. Addison-Wesley,

Boston, 2003.

McAffer, Jeff, and Jean-Michel Lemieux,

Eclipse Rich Client Platform:

Designing, Coding, and Packaging Java Applications

. Addison-Wesley,

Boston, 2005.

FAQ How do I find a particular class from an Eclipse plug-in?

(

http://wiki.eclipse.org/FAQ_How_do_I_find_a_particular_class_from_an_E clipse_plug-in%3F

)

Helpful tools in PDE Incubator (

http://www.eclipse.org/pde/incubator/

)

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 3

Eclipse Infrastructure

This chapter discusses the architecture behind the code generated in the previous chapter. Before diving deeper into every aspect of the program, it’s time to step back and look at Eclipse as a whole.

The simple example plug-in that was started and described in Chapter 2— the

Favorites

plug-in—provides a concrete basis on which to discuss the

Eclipse architecture.

3.1

Structural Overview

Eclipse isn’t a single monolithic program, but rather a small kernel containing a plug-in loader surrounded by hundreds (and potentially thousands) of plugins (see Figure 3–1). This small kernel is an implementation of the OSGi R4 specification and provides the environment in which plug-ins execute. Each plug-in contributes to the whole in a structured manner, may rely on services provided by another plug-in, and each may in turn provide services on which yet other plug-ins may rely.

This modular design lends itself to discrete chunks of functionality that can be more readily reused to build applications not envisioned by Eclipse’s original developers. The minimal set of plug-ins necessary to create a client application is called the Eclipse Rich Client Platform (RCP). Since the Eclipse infrastructure is now being used to build server side as well as client side applications, the Eclipse infrastructure is also known as the Eclipse Application

Framework (EAF).

107

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

108 CHAPTER 3 • Eclipse Infrastructure

• EAF -

http://www.eclipse.org/home/categories/frameworks.php

• OSGi -

http://www.eclipse.org/equinox/

• RCP -

http://www.eclipse.org/home/categories/rcp.php

Figure 3–1

Eclipse plug-in structure.

An example of how plug-ins depend on one another.

3.1.1

Plug-in structure

The behavior of every plug-in is in code, yet the dependencies and services of a plug-in (see Section 2.3.1, The Plug-in manifests, on page 77) are declared in the

MANIFEST.MF

and plugin.xml

files (see Figure 3–2). This structure facilitates lazy-loading of plug-in code on an as-needed basis, thus reducing both the startup time and the memory footprint of Eclipse.

On startup, the plug-in loader scans the

MANIFEST.MF

and plugin.xml

files for each plug-in and then builds a structure containing this information.

This structure takes up some memory, but it allows the loader to find a required plug-in much more quickly, and it takes up a lot less space than loading all the code from all the plug-ins all the time.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

3.1

Structural Overview

5HWXUQWR7DEOHRI&RQWHQWV

109

Figure 3–2

Declaring a new extension.

This is an example of how a new extension is declared in the plug-in manifest with lines highlighting how the plug-in manifest references various plug-in artifacts.

Plug-ins Are Loaded But Not Unloaded

As of Eclipse 3.1, plug-ins are loaded lazily during a session but not unloaded, causing the memory footprint to grow as the user requests more functionality. In future versions of Eclipse, this issue may be addressed by unloading plugins when they are no longer required (see

eclipse.org/equinox

; and for more specifics on deactivating plug-ins see

eclipse.org/equinox/incubator/ archive/dynamicPlugins/deactivatingPlugins.html

).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

110 CHAPTER 3 • Eclipse Infrastructure

3.2

3.1.2

Workspace

The Eclipse IDE displays and modifies files located in a

workspace

. The workspace is a directory hierarchy containing both user files such as projects, source code, and so on, and plug-in state information such as preferences (see

Section 3.4.4, Plug-in preferences, on page 122). The plug-in state information located in the workspace directory hierarchy is associated only with that workspace, yet the Eclipse IDE, its plug-ins, the plug-in static resources (see

Section 3.4.3, Static plug-in resources, on page 121) and plug-in configuration files (see Section 3.4.5, Plug-in configuration files, on page 123) are shared by multiple workspaces.

Plug-in Directory or JAR file

The

Favorites

plug-in JAR, com.qualityeclipse.favorites_1.0.0.jar

, contains files similar to a typical plug-in, including java class files, various images used by the plug-in, and the plug-in manifest.

java classes—

The actual Java classes comprising the plug-in are placed in a standard java directory/package structure within the plug-in JAR file.

icons—

Image files are typically placed in an icons

or images

directory and referenced in the plugin.xml

and by the plug-in’s various classes.

Image files and other static resource files that are shipped as part of the plug-in can be accessed using methods in the activator (see Section 3.4.3,

Static plug-in resources, on page 121).

META-INF/MANIFEST.MF—

A file describing the runtime aspects of the plug-in such as identifier, version, and plug-in dependencies (see Section 2.3.1, The Plug-in manifests, on page 77 and see Section 3.3.2, Plugin runtime, on page 116).

plugin.xml—

A file in XML format describing extensions and extension points (see Section 3.3.4, Extensions and extension points, on page 118).

Plug-in Directories

Alternately, content for the Favorites plugin can be stored in a folder named com.qualityeclipse.favorites_1.0.0. This folder would contain the same elements as the com.qualityeclipse.favorites_1.0.0.jar except that the java classes comprising the plugin would be stored in a JAR file within the plug-in directory. Typically, the

JAR file is named for the last segment in the plug-in’s identifier, but it could have any name, as long as that name is declared as the Bundle-ClassPath in the META-INF/MANIFEST.MF file. In this case, since the Favorites plug-in identifier is com.qualityeclipse.favorites, the JAR file would be named favorites.jar.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.2

Plug-in Directory or JAR file 111

The plug-in JAR must have a specific name and be placed inside a specific directory so that Eclipse can find and load it. The JAR name must be a concatenation of the plug-in identifier, an underscore, and the plug-in version in dot-separated form, as in: com.qualityeclipse.favorites_1.0.0.jar

The plug-in JAR must be located in the plugins

directory as a sibling to all the other Eclipse plug-ins, as is the case for the

Favorites

plug-in.

3.2.1

Link files

Plug-ins are added to an existing Eclipse installation in three ways:

1. Placing the plug-ins into the plugins

directory as a sibling to all the other Eclipse plug-ins as described in the prior section.

2. Creating an Eclipse update site (see Section 18.3, Update Sites, on page 679) so that Eclipse can download and manage the plug-ins.

3. Placing the plug-ins in a separate product-specific directory and creating a link file so that Eclipse can find and load these plug-ins.

This third approach not only satisfies Ready for Rational Software

(RFRS) requirements, but it also allows for multiple installations of Eclipse to be linked to the same set of plug-ins. You must make several modifications to the

Favorites

example so that it can use this alternate approach. the

To begin, remove the existing

Favorites

plug-in in its current form from

Development Workbench

using the steps outlined in Section 2.8.5,

Uninstalling the Favorites plug-in, on page 104. Next, modify the Ant-based build-favorites.xml

file so that the

Favorites

plug-in conforms to the new structure by inserting

QualityEclipse/Favorites/eclipse

as described below:

1. Replace the following:

<property name="plugin.jar" location=

"${build.temp}/jars/plugins/${plugin.dir}.jar" /> with this (location must be on a single line):

<property name="plugin.jar" location=

"${build.temp}/jars/QualityEclipse/Favorites/

eclipse/plugins/${plugin.dir}.jar" />

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

112 CHAPTER 3 • Eclipse Infrastructure

2. Replace this:

<mkdir dir="${build.temp}/jars/plugins" /> with this (all on a single line):

<mkdir dir="${build.temp}/jars/QualityEclipse/Favorites/

eclipse/plugins" />

When making these modifications, be sure that the location string is all on a single line; Ant does not handle paths that span multiple lines. When the modified build-favorites.xml

is executed, the resulting zip file contains a new structure:

QualityEclipse/Favorites/eclipse/plugins/

com.qualityeclipse.favorites_1.0.0.jar

The zip file can be unzipped to any location, but for this example, assume that the file is unzipped into the root directory of the C drive so that the plugin directory is:

C:\QualityEclipse\Favorites\eclipse\plugins\

com.qualityeclipse.favorites_1.0.0.jar

The locations for the Eclipse product directory and the Quality-Eclipse product directory are determined by the user and thus are not known at build time. Because of this, the link file that points to the Quality-Eclipse product directory must be manually created for now. Create the links

subdirectory in the Eclipse product directory (e.g.,

C:\eclipse\links

) and create a new file named com.qualityeclipse.favorites.link

that contains this single line: path=C:/QualityEclipse/Favorites

To do this in Windows, you can use Notepad to create and save the file as a txt

file, which you can then rename appropriately. Note that the path in the

*.link

file must use forward slashes rather than backslashes. The new

*.link

file will be used by Eclipse once Eclipse has been restarted.

No Relative Paths in Link Files

Eclipse 3.4 does not allow link files to contain relative paths. This restriction may be changed in future versions (see

bugs.eclipse.org/bugs/show_bug.cgi?id=35037

for Bugzilla entry

35037).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.3

Plug-in Manifest 113

3.2.2

Hybrid approach

Some products use a hybrid approach, delivering the product in multiple forms. When installing, the installer places product plug-ins directly in the

Eclipse plug-ins directory, whereas when installing into Rational Application

Developer or any of the other Rational IDE family of products, the product plug-ins are placed in a separate product directory and a link file is created. In addition, these products are available in various zip file formats, each targeted at a specific type and version of an Eclipse or WebSphere product. This hybrid approach facilitates a simpler and smaller zip-based installation for Eclipse where RFRS certification is not required, and a cleaner and easier installerbased installation for the Rational IDE family of products.

After you install the QualityEclipse product and create the link file as just described, the QualityEclipse product is ready for use. Verify that you have correctly installed the QualityEclipse product in its new location by restarting

Eclipse and opening the

Favorites

view. After you have installed and verified the product, be sure to uninstall it by deleting the link file so that the JUnit tests described in Section 2.8, Writing Plug-in Tests, on page 99 will still run successfully.

3.3

Plug-in Manifest

As stated earlier, there are two files—

MANIFEST.MF

and plugin.xml

—per plug-in defining various high-level aspects so that the plug-in does not have to load until you need its functionality. The format and content for these files can be found in the Eclipse help facility accessed by

Help > Help Contents

; look under

Platform Plug-in Developer Guide > Reference > Other Reference

Information > OSGi Bundle Manifest

and

Plug-in Manifest

.

What is OSGi?

Eclipse originally used a home-grown runtime model/mechanism that was designed and implemented specifically for

Eclipse. This was good because it was highly optimized and tailored to

Eclipse, but less than optimal because there are many complicated issues, and having a unique runtime mechanism prevented reusing the work done in other areas (e.g., OSGi, Avalon, JMX, etc.). As of Eclipse 3.0, a new runtime layer was introduced based upon technology from the OSGi

Alliance (

www.osgi.org

) that has a strong specification, a good component model, supports dynamic behavior, and is reasonably similar to the original

Eclipse runtime. With each new release of Eclipse, the Eclipse runtime API and implementation (e.g., “plug-ins”) continues to align itself more and more closely with the OSGi runtime model (e.g., “bundles”).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

114 CHAPTER 3 • Eclipse Infrastructure

3.3.1

Plug-in declaration

Within each bundle manifest, there are entries for name, identifier, version, activator, and provider.

Bundle-Name: Favorites Plug-in

Bundle-SymbolicName: com.qualityeclipse.favorites; singleton:=true

Bundle-Version: 1.0.0

Bundle-Activator: com.qualityeclipse.favorites.FavoritesActivator

Bundle-Vendor: Quality Eclipse

Strings in the plug-in manifest, such as the plug-in name, can be moved into a separate plugin.properties

file. This process facilitates internationalization as discussed in Chapter 16, Internationalization.

3.3.1.1

Plug-in identifier

The plug-in identifier (

Bundle-SymbolicName

) is designed to uniquely identify the plug-in and is typically constructed using Java package naming conventions

(e.g., com.<companyName>.<productName>,

or in our case, com.qualityeclipse.favorites

). If several plug-ins are all part of the same product, then each plug-in name can have four or even five parts to it as in com.qualityeclipse.favorites.core

and com.qualityeclipse.favorites.ui

.

3.3.1.2

Plug-in version

Every plug-in specifies its version (

Bundle-Version)

using three numbers separated by periods. The first number indicates the major version number, the second indicates the minor version number, and the third indicates the service level, as in

1.0.0

. You can specify an optional qualifier that can include alphanumeric characters as in

1.0.0.beta_1

or

1.0.0.2008-06-26

(no whitespace). At startup, if there are two plug-ins with the same identifier,

Eclipse will choose the “latest” by comparing the major version number first, then the minor version number, the service level and, if present, the qualifier.

Tip

: For an outline of the current use of version numbers and a proposed guideline for using plug-in version numbering to better indicate levels of compatibility, see

eclipse.org/equinox/documents/plugin-versioning.html

and

wiki.eclipse.org/index.php/Version_Numbering

.

3.3.1.3

Plug-in name and provider

Both the name and the provider are human-readable text, so they can be anything and are not required to be unique. To see the names, versions, and providers of the currently installed plug-ins, select

Help > About Eclipse SDK

to

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.3

Plug-in Manifest 115

open the

About

dialog (see Figure 3–3), and then click the

Plug-in Details

button to open the

Plug-ins

dialog (see Figure 3–4).

Figure 3–3

The About Eclipse SDK dialog, showing information about the Eclipse platform.

Figure 3–4

The About Eclipse SDK Plug-ins dialog, showing all the installed plug-ins with the Favorites plug-in highlighted.

Tip

: The first column in the About Eclipse SDK Plug-ins dialog shown above indicates whether a particular plug-in’s JAR file is signed. For more information about signing JARs, see

java.sun.com/docs/books/tutorial/deployment/jar/signindex.html

and

java.sun.com/developer/Books/javaprogramming/JAR/sign/signing.html

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

116 CHAPTER 3 • Eclipse Infrastructure

3.3.1.4

Plug-in activator declaration

Optionally, every plug-in can specify a plug-in activator (

Bundle-Activator)

as the

Favorites

plug-in does (see Section 3.4, Activator or Plug-in Class, on page 120).

3.3.2

Plug-in runtime

The

Bundle-ClassPath

declaration in the

MANIFEST.MF

file is a commaseparated list describing which libraries (

*.jar

files) contain the plug-in code. The

Export-Package

declaration is a comma-separated list indicating which packages within those libraries are accessible to other plug-ins (see

Section 21.2.4, How Eclipse is different, on page 783 and Section 21.2.5,

Related plug-ins, on page 783).

Bundle-ClassPath: favorites.jar

Export-Package: com.qualityeclipse.favorites.views

Tip

: When delivering your plug-in as a single JAR as the Favorites Plug-in does

, the

Bundle-ClassPath

declaration should be omitted so that Eclipse looks for classes in the plug-in

JAR

and not in a

JAR

inside your plug-in.

3.3.3

Plug-in dependencies

The plug-in loader instantiates a separate class loader for each loaded plug-in, and uses the

Require-Bundle

declaration of the manifest to determine which other plug-ins—thus which classes—will be visible to that plug-in during execution (see Section 21.9, Plug-in ClassLoaders, on page 811 for information about loading classes not specified in the

Require-Bundle

declaration).

Require-Bundle: org.eclipse.ui,

org.eclipse.core.runtime

If a plug-in has been successfully compiled and built but, during execution, throws a

NoClassDefFoundError

, it may indicate that the plug-in project’s Java classpath is out of sync with the

Require-Bundle

declaration in the

MANIFEST.MF

file. As discussed in Section 2.3.1, The Plug-in manifests, on page 77, it is important to keep the classpath and the

Require-Bundle declaration in sync.

When the plug-in loader is about to load a plug-in, it scans the

Require-

Bundle

declaration of a dependent plug-in and locates all the required plugins. If a required plug-in is not available, then the plug-in loader throws an

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.3

Plug-in Manifest 117

exception, generating an entry in the log file (see Section 3.6, Logging, on page 128) and does not load the dependent plug-in. When a plug-in gathers the list of plug-ins that extend an extension point it defines, it will not see any disabled plug-ins. In this circumstance, no exception or log entry will be generated for the disabled plug-ins.

If a plug-in can successfully execute without a required plug-in, then that required plug-in can be marked as optional in the plug-in manifest. To do so, open the plug-in manifest editor and then switch to the

Dependencies

tab (see

Figure 2–10 on page 79). Select the required plug-in, click the

Properties

button and then check the

Optional

checkbox in the Properties dialog (see Figure 3–5).

Figure 3–5

The required plug-in properties dialog.

Making this change in the plug-in manifest editor appends

;resolution:=optional

to the required plug-in in the

Require-Bundle declaration so that it now looks something like this:

Require-Bundle: org.eclipse.ui,

org.eclipse.core.runtime;resolution:=optional

If your plug-in requires not just any version of another plug-in, select that plug-in in the

Dependencies

tab of the plug-in manifest editor and and click the

Properties...

button. This opens the required plug-in properties dialog (see

Figure 3–5) where you can specify an exact version or a range of versions using the

Minimum Version

and

Maximum Version

fields. Changing one or both of these fields and clicking

OK

results in a modification to the

Require-

Bundle

declaration looking something like the following:

Require-Bundle: org.eclipse.ui,

org.eclipse.core.runtime;bundle-version="[3.3.0,3.4.0)"

The above indicates that any version of org.eclipse.ui

and version 3.3.x

of the org.eclipse.core.runtime

are required. Other examples include:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

118 CHAPTER 3 • Eclipse Infrastructure

[3.3.0.test,3.3.0.test]

—requires a specific version

[3.3.0,3.3.1)

—requires version 3.3.0.x

[3.3.0,3.4.0)

—requires version 3.3.x

[3.3.0,3.5.0)

—requires version 3.3.x or 3.4.x

[3.0.0,4.0.0)

—requires version 3.x

3.3.0

—requires version 3.3.0 or greater

The general syntax for a range is

[ floor , ceiling ) where floor

is the minimum version and ceiling

is the maximum version.

The first character can be

[

or

(

and the last character may be

]

or

)

where these characters indicate the following:

[

= floor is included in the range

(

= floor is

not

included in the range

]

= ceiling is included in the range

)

= ceiling is

not

included in the range

You can specify a floor or minimum version with no extra characters indicating that your plug-in needs any version greater than or equal to the specified version.

Finally, check the

Reexport this dependency

checkbox (see Figure 3–5) to specify that the dependent plug-in classes are made visible (are (re)exported) to users of this plug-in. By default, dependent classes are not exported (i.e., they are not made visible).

Import-Package

is similar to

Require-Bundle

except that

Import-

Package

specifies names of packages that are required for execution rather than names of bundles. Using

Import-Package

can be thought of as specifying the service required whereas using

Require-Bundle

is like specifying the service provider.

Import-Package

makes it easier to swap out one bundle for another that provides the same service, but harder to know who is providing that service.

3.3.4

Extensions and extension points

A plug-in declares extension points so that other plug-ins can extend the functionality of the original plug-in in a controlled manner (see Section 17.1, The

Extension Point Mechanism, on page 637). This mechanism provides a layer of separation so that the original plug-in does not need to know about the existence of the extending plug-ins at the time you build the original plug-in.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.3

Plug-in Manifest 119

Plug-ins declare extension points as part of their plug-in manifest, as in the views extension point declared in the org.eclipse.ui

plug-in:

<extension-point

id="views"

name="%ExtPoint.views"

schema="schema/views.exsd"/>

You can find documentation for this extension point in the Eclipse help

(select

Help > Help Contents

, then in the

Help

dialog, select

Platform Plug-in

Developer Guide > Reference > Extension Points Reference > org.eclipse.ui.views

). It indicates that any plug-in using this extension point must provide the name of a class that implements the interface org.

eclipse.ui.IViewPart

(see Section 21.5, Types Specified in an Extension

Point, on page 793).

Other plug-ins declare extensions to the original plug-in’s functionality similar to the

Favorites

plug-in’s view extensions. In this case, the

Favorites

plug-in declares a new category of views with the name

Quality Eclipse

and the class, com.qualityeclipse.favorites.views.FavoritesView

, as a new type of view as follows:

<extension point="org.eclipse.ui.views">

<category

name="QualityEclipse"

id="com.qualityeclipse.favorites">

</category>

<view

name="Favorites"

icon="icons/sample.gif"

category="com.qualityeclipse.favorites"

class="com.qualityeclipse.favorites.views.FavoritesView"

id="com.qualityeclipse.favorites.views.FavoritesView">

</view>

</extension>

Each type of extension point may require different attributes to define the extension. Typically, ID attributes take a form similar to the plug-in identifier.

The category ID provides a way for the

Favorites

view to uniquely identify the category that contains it. The name

attribute of both the category and view is human-readable text, while the icon

attribute specifies a relative path from the plug-in directory to the image file associated with the view.

This approach allows Eclipse to load information about the extensions declared in various plug-ins without loading the plug-ins themselves, thus reducing the amount of time and memory required for an operation. For example, selecting the

Windows > Show View > Other…

menu opens a dialog

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

120 CHAPTER 3 • Eclipse Infrastructure

showing all the views provided by all the plug-ins known to Eclipse (see Section 2.5, Installing and Running the Product, on page 92). Because each type of view is declared in its plug-in’s manifest, the Eclipse runtime can present a list of views to the user without actually loading each plug-in that contains the view.

3.4

Activator or Plug-in Class

By default, the

Bundle-Activator

or plug-in class provides methods for accessing static resources within the plug-in, and for accessing and initializing plug-in-specific preferences and other state information. An activator is not required, but if specified in the plug-in manifest, the activator is the first class notified after the plug-in loads and the last class notified when the plug-in is about to shut down (see Section 3.5.2, Plug-ins and Bundles, on page 127, and the source code listing in Section 2.3.2, The Activator or Plug-in class, on page 83).

Tip:

Historically, plug-ins have exposed their activator as an entry point.

To better control access to your plug-in’s initialization and internal resources, consider moving public access methods to a new class and hiding your activator.

3.4.1

Startup and shutdown

The plug-in loader notifies the activator when the plug-in is loaded via the start()

method and when the plug-in shuts down via the stop()

method.

These methods allow the plug-in to save and restore any state information between Eclipse sessions.

Be Careful When Overriding start() and stop()

When overriding these methods, be careful; always call the superclass implementation, and only take the minimum action necessary so that you do not impact the speed or memory requirements during Eclipse startup or shutdown.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.4

Activator or Plug-in Class 121

3.4.2

Early plug-in startup

Eclipse loads plug-ins lazily, so it may not call the start()

method when it launches. Eclipse can provide resource change information indicating the changes that occurred while the plug-in was inactive (see Section 9.5, Delayed

Changed Events, on page 420). If this is not enough and the plug-in

must

load and start when Eclipse launches, the plug-in can use the org.eclipse.ui.startup

extension point by inserting the following into its plug-in manifest:

<extension point="org.eclipse.ui.startup">

<startup class="myPackage.myClass"/>

</extension>

Doing this requires that the myPackage.myClass

class implement the org.eclipse.ui.IStartup

interface so that the workbench can call the earlyStartup()

method immediately after the UI completes its startup. For more on early startup and the issues involved, see Section 21.10, Early Startup, on page 816.

Like most plug-ins, the

Favorites

plug-in does not need to load and start when Eclipse launches, so it does not use this extension point. If there is a need for early startup, then place only what is necessary for it into a separate plugin and use the early startup extension point there so that the additional overhead of early startup has only a small impact on startup time and memory footprint.

3.4.3

Static plug-in resources

Plug-ins can include images and other file-based resources that are installed into the plug-in directory along with the plug-in manifest and library file.

These files are static in nature and shared between multiple workbench incarnations. Declarations, such as actions, views, and editors, in the plug-in manifest can reference resources such as icons stored in the plug-in installation directory. Originally, plug-ins would access these resources using activator methods in the

Plugin

class such as find (IPath path)

and openStream

(IPath file)

, but these methods have been deprecated. Instead, obtain the

Bundle

instance as described in Section 3.5.2, Plug-ins and Bundles, on page 127, then call org.eclipse.core.runtime.FileLocator

methods such as

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

122 CHAPTER 3 • Eclipse Infrastructure

find(Bundle bundle, IPath path, Map override)

—Returns a uniform resource locator (URL) for the given path in the given bundle or null

if the URL could not be computed or created.

openStream(Bundle bundle, IPath file, boolean substitute-

Args)

—Returns an input stream for the specified file. The file path must be specified relative to the plug-in’s installation location. If the plug-in is delivered as a single JAR, such as the Favorites plug-in, then this is the path to the resource within the plug-in JAR.

resolve(URL url)

—Resolves a plug-in-relative URL to a URL native to the Java class library (e.g., file, http, etc.). This replaces the deprecated

Platform

method resolve(URL url)

.

toFileURL(URL url)

—Converts a plug-in-relative URL into a URL that uses the file protocol. This replaces the deprecated

Platform method asLocalURL(URL url)

.

3.4.4

Plug-in preferences

Plug-in preferences and other workspace-specific state information are stored in the workspace metadata directory hierarchy. For example, if the workspace location is

C:\eclipse\workspace

, then the

Favorites

preferences would be stored in:

C:/eclipse/workspace/.metadata/.plugins/

org.eclipse.core.runtime/.settings/

com.qualityeclipse.favorites.prefs

The activator provides methods for accessing plug-in preferences and other state-related files as follows (see Section 12.3, Preference APIs, on page 501 for more ways to access preferences): getPreferenceStore()

—Returns the preferences for this plug-in (see

Section 3.4.6, Plugin and AbstractUIPlugin, on page 125 for information about saving preferences).

getStateLocation()

—Returns the location in the local file system of the plug-in state area for this plug-in (see Section 7.5.2, Saving global view information, on page 343). If the plug-in state area did not exist prior to this call, it is created.

You can supply default preferences to a plug-in using a preferences.ini

file located in the plug-in directory (see Section 12.3.4, Specifying default values in a file, on page 506). This approach lets you easily internationalize the plugin using a preferences.properties

file (see Section 16.1, Externalizing the

Plug-in Manifest, on page 618). Alternately, you can programmatically pro-

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.4

Activator or Plug-in Class 123

vide default preferences as discussed in Section 12.3.3, Specifying default values programmatically, on page 505.

Legacy Preference Storage Methods

Plugin

provides alternate preferences storage methods and classes that you should not use:

• getPluginPreferences() initializeDefaultPluginPreferences()

These methods exist only for backward compatibility, and if used, cause additional Eclipse “legacy” compatibility plug-ins to be loaded. Instead, use

AbstractUIPlugin

methods such as getPreferenceStore()

or access preferences directly using ScopedPreferenceStore as outlined in Section 12.3,

Preference APIs, on page 501.

3.4.5

Plug-in configuration files

If you need to store plug-in information that needs to be shared among all workspaces associated with a particular Eclipse installation, then use the method

Platform.getConfigurationLocation()

and create a plug-inspecific subdirectory. If Eclipse is installed in a read-only location, then

Platform.getConfigurationLocation()

will return null

. You could add the following field and method to the

FavoritesActivator

class to return a configuration directory for this plug-in. If Eclipse is installed in a read-only location, then this method would gracefully degrade by returning the workspace-specific state location rather than the configuration directory so that plug-in state information could still be stored and retrieved.

public File getConfigDir() {

Location location = Platform.getConfigurationLocation();

if (location != null) {

URL configURL = location.getURL();

if (configURL != null

&& configURL.getProtocol().startsWith("file")) {

return new File(configURL.getFile(), PLUGIN_ID);

}

}

// If the configuration directory is read-only,

// then return an alternate location

// rather than null or throwing an Exception.

return getStateLocation().toFile();

}

Preferences can also be stored in the configuration directory by adding the following field and method to the

FavoritesActivator

class.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

124

private IEclipsePreferences configPrefs;

CHAPTER 3 • Eclipse Infrastructure

public Preferences getConfigPrefs() {

if (configPrefs == null)

configPrefs = new ConfigurationScope().getNode(PLUGIN_ID);

return configPrefs;

}

Read-Only Installation

Be warned that if Eclipse is installed in a read-only location, then this method will return null

. In addition, neither the following code nor the Preferences object returned by the method below is thread safe.

If you add the preceding method to your activator, then you should also modify the stop()

method to flush the configuration preferences to disk when

Eclipse shuts down.

public void stop(BundleContext context) throws Exception {

saveConfigPrefs();

plugin = null;

super.stop(context);

} public void saveConfigPrefs() {

if (configPrefs != null) {

try {

configPrefs.flush();

} catch (BackingStoreException e) {

e.printStackTrace();

}

}

}

When you launch a Runtime Workbench (see Section 2.6, Debugging the

Product, on page 94), you can specify the configuration directory using the

Configration

page of the

Run

dialog (see Figure 3–6).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

3.4

Activator or Plug-in Class

5HWXUQWR7DEOHRI&RQWHQWV

125

Figure 3–6

The Launch Configuration page for specifying the configuration directory.

3.4.6

Plugin and AbstractUIPlugin

All activators must implement the

BundleActivator

interface. Typically, UIbased plug-ins (plug-ins requiring the org.eclipse.ui

plug-in) have an activator that subclasses

AbstractUIPlugin,

while non-UI plug-ins subclass

Plugin

. Both classes provide basic plug-in services for the plug-in programmer, but there are important differences.

When the plug-in shuts down,

AbstractUIPlugin

automatically saves any plug-in preferences that are accessed using the getPreferenceStore() method. When subclassing the

Plugin

class directly, modify the stop() method to save your preferences so that preferences will persist across sessions.

Other methods provided by

AbstractUIPlugin

include: createImageRegistry()

—Returns a new image registry for this plugin. You can use the registry to manage images that are used frequently by the plug-in. The default implementation of this method creates an empty registry. Subclasses can override this method if necessary.

getDialogSettings()

—Returns the dialog settings for this UI plug-in

(see Section 11.2.7, Dialog settings, on page 475). The dialog settings hold persistent state data for the various wizards and dialogs of this plug-in in the context of a workbench.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

126 CHAPTER 3 • Eclipse Infrastructure

getImageRegistry()

—Returns the image registry for this UI plug-in

(see Section 4.4.3, Images, on page 189, and Section 7.7, Image Caching, on page 346).

initializeImageRegistry(ImageRegistry reg)

—Initializes an image registry with images that are used frequently by the plug-in.

loadDialogSettings()

—Loads the dialog settings for this plug-in by looking first for a dialog_settings.xml

file in the plug-in’s metadata directory, then for a file with the same name in the plug-in directory; failing both of these, it creates an empty settings object. This method can be overridden, although this is typically unnecessary.

3.5

Plug-in Model

When Eclipse first launches, it scans each of the plug-in directories and builds an internal model representing every plug-in it finds. This occurs by scanning each plug-in manifest without loading the plug-ins. The methods in the next two subsections are useful if you want to display information about plug-ins or perform operations based on specific plug-in characteristics without taking the time and memory usage hit associated with loading plug-ins.

3.5.1

Platform

There are several classes such as org.eclipse.core.runtime.Platform

that provide information about the currently executing Eclipse environment.

You can obtain information about installed plug-ins (also known as Bundles), extensions, extension points, command line arguments, job manager (see Section 21.8, Background Tasks—Jobs API, on page 808), installation location, and more. The following are some methods of note.

org.eclipse.core.runtime.FileLocator

See Section 3.4.3, Static plug-in resources, on page 121.

org.eclipse.core.runtime.jobs.Job

getJobManager()

—Returns the platform job manager (see Section

21.8, Background Tasks—Jobs API, on page 808).

org.eclipse.core.runtime.Platform

getBundle(String)

—Returns the bundle with the specified unique identifier.

getBundleGroupProviders()

—Returns an array of bundle providers that contain bundle groups that contain currently installed bundles.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.5

Plug-in Model 127

getExtensionRegistry()

—Returns extension and extension point information.

getLog(Bundle)

—Returns the log for the specified bundle. For more on logging, see Section 3.6, Logging, on page 128.

getProduct()

—Returns the Eclipse product information.

inDebugMode()

—Returns true

if Eclipse is in debug mode, as it is when the user specifies the

-debug

command line argument.

org.eclipse.core.runtime.SafeRunner

run(ISafeRunnable)

—Runs the given runnable in a protected mode.

Exceptions thrown in the runnable are logged and passed to the runnable’s exception handler.

3.5.2

Plug-ins and Bundles

Information about the currently installed plug-ins, also known as Bundles, can be obtained using

Platform.getBundleGroupProviders()

or

Platform.

getBundle(String)

. Accessing an activator or plug-in class requires the containing plug-in to be loaded, whereas interacting with the

Bundle

interface does not carry such a penalty. If you already have a activator, such as the

Favorites

plug-in, then you can obtain the

Bundle

interface for that plug-in by using something like this:

FavoritesActivator.getDefault().getBundle()

After you obtain the

Bundle

object, several methods are of interest.

getBundleId()

—Returns the bundle’s unique identifier (a long

), assigned by Eclipse when the bundle was installed.

getEntry(String)

—Returns a URL for the specified

'/'

-separated bundle relative path name where getEntry("/")

returns the bundle root. This provides access to resoures supplied with the plug-in that are typically read-only. Relative plug-in information should be written to the location provided by

Plugin.getStateLocation()

.

getHeaders()

—Returns a dictionary of headers and values defined in the bundle’s

MANIFEST.MF

file (see Section 3.3.1, Plug-in declaration, on page 114).

getState()

—Returns the current state of a plug-in, such as

Bundle.UNINSTALLED

,

Bundle.STARTING

,

Bundle.INSTALLED

Bundle.STOPPING

,

,

Bundle.RESOLVED

Bundle.ACTIVE

.

,

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

128 CHAPTER 3 • Eclipse Infrastructure

getSymbolicName()

—Returns the unique plug-in identifier (a java.lang.String

), which is the same as the

Bundle-SymbolicName declaration in the

MANIFEST.MF

.

The plug-in version number can be obtained using the getHeaders()

method something like this: public Version getVersion() {

return new Version((String) getBundle().getHeaders().get(

org.osgi.framework.Constants.BUNDLE_VERSION));

}

3.5.3

Plug-in extension registry

You can access the plug-in extension registry using the

Plaform.

getExtensionRegistry()

method. It contains plug-in descriptors, each representing a plug-in. The registry provides the following methods for extracting information about the various plug-ins without loading them (see

Section 17.1, The Extension Point Mechanism, on page 637 for information on creating extension points).

getConfigurationElementsFor(String extensionPointId)

Returns all configuration elements from all extensions configured into the identified extension point.

getExtensionPoint(String extensionPointId)

—Returns the extension point with the given extension point identifier in this plug-in registry.

Previously, extensions and extension points did not change during execution, but that is slowly changing as the Eclipse plug-in model continues to align itself with OSGi. If you are interested in changes during execution, use addRegistryChangeListener(IRegistryChangeListener)

.

Tip

: For more on the plug-in registry, activation, and lifecycle, check out the Equinox project at

www.eclipse.org/equinox

.

3.6

Logging

The RFRS requirements indicate that exceptions and other service-related information should be appended to a log file. To facilitate this, the activator provides a method for accessing the plug-in logging mechanism via the get-

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.6

Logging 129

Log()

method. For convenience, the

FavoritesLog

wraps the

ILog

interface returned by the getLog()

method with several utility methods: package com.qualityeclipse.favorites; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; public class FavoritesLog {

The first group of methods that follow are for convenience, appending information, error messages, and exceptions to the log for the Favorites plug-in.

public static void logInfo(String message) {

log(IStatus.INFO, IStatus.OK, message, null);

} public static void logError(Throwable exception) {

logError("Unexpected Exception", exception);

} public static void logError(String message, Throwable exception) {

log(IStatus.ERROR, IStatus.OK, message, exception);

}

Each of the preceding methods ultimately calls the following methods which create a status object (see Section 3.6.1, Status objects, on page 130) and then append that status object to the log. public static void log(int severity, int code, String message,

Throwable exception) {

log(createStatus(severity, code, message, exception));

} public static IStatus createStatus(int severity, int code,

String message, Throwable exception) {

return new Status(severity, FavoritesActivator.PLUGIN_ID, code,

message, exception);

} public static void log(IStatus status) {

FavoritesActivator.getDefault().getLog().log(status);

}

The log()

and createStatus()

methods take the following parameters.

severity

—the severity; one of these:

IStatus.OK

,

IStatus.WARNING

,

IStatus.ERROR

,

IStatus.INFO

, or

IStatus.CANCEL

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

130 CHAPTER 3 • Eclipse Infrastructure

code

—the plug-in-specific status code or

IStatus.OK

message

—a human-readable message, localized to the current locale exception

—a low-level exception, or null

if not applicable

Tip:

For more on the evolution of logging beyond what is available in

Eclipse 3.4, see

https://bugs.eclipse.org/bugs/show_bug.cgi?id=147824

3.6.1

Status objects

The

IStatus

type hierarchy in the org.eclipse.core.runtime

package provides a mechanism for wrapping, forwarding, and logging the result of an operation, including an exception if there is one. A single error is represented using an instance of

Status

(see method createStatus

in the previous source code), while a

MultiStatus

object that contains zero or more child status objects represents multiple errors.

When creating a framework plug-in that will be used by many other plugins, it is helpful to create status subtypes similar to

IResourceStatus

and

ResourceStatus

; however, for the

Favorites

plug-in, the existing status types that follow will do:

IStatus

—A status object that represents the outcome of an operation.

All

CoreExceptions

carry a status object to indicate what went wrong.

Status objects are also returned by methods needing to provide details of failures (e.g., validation methods).

IJavaModelStatus

—Represents the outcome of a Java model operation. Status objects are used inside

JavaModelException

objects to indicate what went wrong.

IResourceStatus

—Represents a status related to resources in the

Resources

plug-in and defines the relevant status code constants. Status objects created by the

Resources

plug-in bear its unique identifier,

ResourcesPlugin.PI_RESOURCES

, and one of these status codes.

MultiStatus

—A concrete multistatus implementation, suitable either for instantiating or subclassing.

OperationStatus

—Describes the status of a request to execute, undo, or redo an operation (see Section 6.1, Commands, on page 216).

Status

—A concrete status implementation, suitable either for instantiating or subclassing.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.6

Logging 131

TeamStatus

—Returned from some Team operations or is the payload of some exceptions of type

TeamException

.

3.6.2

The Error Log view

Eclipse provides an

Error Log

view for inspecting the Eclipse log file. To open the

Error Log

view, select

Window > Show View > Other…

, and in the

Show

View

dialog, expand the

General

category to find the

Error Log

view (see Figure 3–7). Double-clicking on an entry opens a dialog showing details for the error log entry. If Eclipse is installed in

C:\Eclipse

and the workspace is located in a direct subdirectory, you can find the Eclipse log file at

C:\Eclipse\workspace\.metadata\.log

.

Figure 3–7

The Error Log view is provided by the Eclipse platform and displays information and exceptions generated while Eclipse is running.

3.6.3

Handling Errors (and other Status)

The org.eclipse.debug.core

plug-in decouples error generation from error handling thus allowing errors (and other

IStatus

objects) generated in non-

UI plug-ins to be optionally handled by UI plug-ins without forcing the non-

UI plug-ins to be dependent on the UI plug-ins. Any (typically UI) plug-in may register an org.eclipse.debug.core.IStatusHandler

using the org.eclipse.debug.core.statusHandlers

extension point. When another plug-in (typically non-UI) needs to handle an error or other

IStatus object, that plug-in calls the org.eclipse.debug.core.DebugPlugin.getStatusHandler(IStatus)

method to obtain the appropriate

IStatus-

Handler

. Although this status handling framework is specific to the org.eclipse.debug.core plug-in (not org.eclipse.core.runtime

) it is something to consider using (or extracting) for decoupling plug-ins.

Tip:

Best practices for logging and handling errors are continuing to evolve.

For more, see

https://bugs.eclipse.org/bugs/show_bug.cgi?id=200090

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

132 CHAPTER 3 • Eclipse Infrastructure

3.7

Eclipse Plug-ins

Plug-ins are built on one or more base plug-ins that are shipped as part of

Eclipse. They are broken down into several groups, and further separated into

UI and Core, as follows. UI plug-ins contain aspects of a user interface or rely on other plug-ins that do, while you can use Core plug-ins in a headless environment (an environment without a user interface).

Core—

A general low-level group of non-UI plug-ins comprising basic services such as extension processing (see Chapter 9, Resource Change

Tracking, on page 407), resource tracking (see Chapter 17, Creating

New Extension Points, on page 637), and so on.

SWT—

The Standard Widget Toolkit, a general library of UI widgets tightly integrated with the underlying operating system (OS), but with an

OS-independent API (see Chapter 4, The Standard Widget Toolkit, on page 135).

JFace—

A general library of additional UI functionality built on top of

SWT (see Chapter 5, JFace Viewers, on page 193).

GEF—Graphical Editing Framework facilitating development of rich graphical editors.

Workbench core—

Plug-ins providing non-UI behavior specific to the

Eclipse IDE itself, such as projects, project natures, and builders (see

Chapter 14, Builders, Markers, and Natures, on page 533).

Workbench UI—

Plug-ins providing UI behavior specific to the Eclipse

IDE itself, such as editors, views, perspectives, actions, and preferences

(see Chapters 6, 7, 8, 10, and 12).

Team—

A group of plug-ins providing services for integrating different types of source code control management systems (e.g., CVS) into the

Eclipse IDE.

Help—

Plug-ins that provide documentation for the Eclipse IDE as part of the Eclipse IDE (see Chapter 15, Implementing Help, on page 577).

JDT core—

Non-UI-based Java Development Tooling (JDT) plug-ins for the Eclipse IDE.

JDT UI—

JDT UI plug-ins for the Eclipse IDE.

PDE—Plug-in Development Environment

Mylyn

—Task focused UI enhancements for reducing information overload and associating information and the presentation of that information with user defined tasks.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

3.8

Summary 133

3.8

Summary

This chapter tried to give you a more in-depth understanding of Eclipse and its structure in relation to creating plug-ins. The next two chapters explore the user-interface elements that should be used to create your own plug-ins.

References

“Eclipse Platform Technical Overview,” Object Technology International,

Inc., February 2003, (

www.eclipse.org/whitepapers/eclipse-overview.pdf

).

Melhem, Wassim, et al., “PDE Does Plug-ins,” IBM, September 8, 2003

(

www.eclipse.org/articles/Article-PDE-does-plugins/PDE-intro.html

).

Xenos, Stefan, “Inside the Workbench: A Guide to the Workbench Internals,”

IBM, October 20, 2005 (

www.eclipse.org/articles/Article-UI-Workbench/ workbench.html

).

Bolour, Azad, “Notes on the Eclipse Plug-in Architecture,” Bolour Computing, July 3, 2003 (

www.eclipse.org/articles/Article-Plug-in-architecture/ plugin_architecture.html

).

Rufer, Russ, “Sample Code for Testing a Plug-in into Existence,” Yahoo

Groups Message 1571, Silicon Valley Patterns Group (

groups.yahoo.com/ group/siliconvalleypatterns/message/1571

).

Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides,

Design

Patterns, Elements of Reusable Object-Oriented Software

. Addison-Wesley,

Boston, 1995.

Buschmann, Frank, et al.,

Pattern-Oriented Software Architecture

. John

Wiley & Sons, Hoboken, NJ, 1996.

Estberg, Don, “How the Minimum Set of Platform Plug-ins Are Related,”

Eclipse Wiki (

eclipsewiki.editme.com/MinimumSetOfPlatformPlugins

).

Watson, Thomas, “Deprecation of Version-Match Attribute,” equinox-dev email, April 30, 2004.

“Interval Notation”

http://id.mind.net/~zona/mmts/miscellaneousMath/intervalNotation/intervalNotation.html

“Interval Mathematics” Wikipedia

http://en.wikipedia.org/wiki/Interval_(mathematics)

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 4

The Standard Widget Toolkit

The

Standard Widget Toolkit

(SWT) is a thin layer on top of the platform’s native controls. SWT provides the foundation for the entire Eclipse user interface (UI). This chapter begins with some history and philosophy of SWT, and then dives into using SWT to build applications. It covers most of the widgets commonly encountered and the layout managers used to arrange them within a window. The chapter concludes with a discussion of various resource management issues to be considered when using SWT.

4.1

SWT History and Goals

The roots of SWT go back more than fifteen years to work that Object Technology International, or OTI (then an independent pioneering OO software company and now a part of IBM), did when creating multiplatform, portable, native widget interfaces for Smalltalk (originally for OTI Smalltalk, which became

IBM Smalltalk in 1993). IBM Smalltalk’s Common Widget (CW) layer provided fast, native access to multiple platform widget sets while still providing a common API without suffering the “lowest common denominator” (LCD) problem typical of other portable graphical user interface (GUI) toolkits.

For many years, IBM had been using Smalltalk as its “secret weapon” when building development tools (even IBM’s first Java IDE, VisualAge for

Java, was written in Smalltalk); however, Smalltalk had deployment and configuration problems that ultimately doomed its long-term use at IBM.

Java’s promise of universal portability and ubiquitous virtual machines

(VMs) on every desktop was very appealing to the folks at IBM responsible

135

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

136 CHAPTER 4 • The Standard Widget Toolkit

for creating the next generation of development tools. In Java, OTI also saw another language to which it could apply its many talents.

Sun’s initial attempt at providing a portable widget API, the Abstract Windowing Toolkit (AWT), suffered from both an overly complex interface to the native widgets and the LCD problem. It provided access to a minimal set of widgets, such as buttons, labels, and lists, common across most platforms, but did not provide access to richer widgets such as tables, trees, and styled text.

That, coupled with an anemic API, destined it to failure in the marketplace.

To solve the problems of AWT and to provide Java with a more powerful, extensible GUI library, Sun decided to abandon native widget interfaces and developed its own portable, emulated widget library officially known as the

Java Foundation Classes (JFC)—more commonly known as Swing. Interestingly enough, this paralleled the developments in the Smalltalk world many years earlier when ParcPlace brought the world’s first truly portable, multiplatform GUI environment to market in a product called VisualWorks (many of the ex-ParcPlace engineers responsible for the portable, emulated GUI library in VisualWorks ended up working at Sun).

While Swing solved the LCD problem by providing a rich set of widgets, the emulation of the platform widgets left much to be desired. Swing applications ended up feeling like Swing applications, not the platform-native applications they were meant to replace. Swing applications also suffered from performance problems not present in their native counterparts.

While AWT was able to run on the Java 2 Platform, Micro Edition (J2ME) devices, Swing could not because of the large runtime Java virtual machine

(JVM) footprint and its reliance on fast native graphics to draw every emulated control. OTI was given the task within IBM of tooling for J2ME, and decided that AWT was not a good enough toolkit. It provided only a basic set of controls, and because its architecture necessitated using the JavaBeans component model, which allows null construction, it had a two-tiered object layer that used valuable JVM memory—something important to manage wisely on small devices.

Uncomfortable with the philosophy behind Swing and emulated widget libraries in general, and armed with extensive knowledge about how to correctly build native, portable, multiplatform widget libraries, OTI set out to correct the faults of both AWT and Swing and to produce the GUI library that

AWT should have been. The result was the Standard Widget Toolkit. OTI used the same developers who created CW for Smalltalk to create SWT for

Java.

SWT was designed to have as small a JVM footprint as possible. The CW had two layers, including an OS layer; however, for SWT, it was felt that a single layer was better, where each platform’s implementation would be a set

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.1

SWT History and Goals 137

of completely optimized Java classes that went straight to native as soon as possible. The public API was the same, but it was not directed through an intermediate layer.

OTI used SWT to build VisualAge Micro Edition (VAME), their first IDE written in Java. When IBM decided to build a common tools platform

(Eclipse) on which they could re-base their successful existing products, they initially built it using Swing. It was an early release of Swing in Java 1.2, and

IBM was greatly disappointed with its performance and look-and-feel. There were memory leaks in Swing in addition to other defects, which led to its eventual abandonment.

One of the reasons SWT was chosen was because IBM’s tooling effort was intended to compete head-to-head with Microsoft, and it was felt that SWT would give a rich enough UI experience. It was a huge risk at the time; SWT had not been ported to many platforms, and also by adopting SWT there was the potential that customers might say: “If Swing wasn’t good enough for your toolkit, why should we use it?” Additionally, anyone writing plug-ins would have to use SWT instead of Swing—the fear was that there would be a natural antagonism toward learning this new application programming interface

(API). There was also the possibility that SWT versus Swing would fragment the Java community. All these fears came true.

However, SWT has found a lot of favor with people who are now using it to program applications with the Eclipse Rich Client Platform (RCP) because they like its higher speed and platform integration. Arguably, Sun did take its eye off the ball with the 1.2 and 1.3 Swing releases. With JDK 1.4 through 1.6,

Sun’s Swing performance and its look-and-feel classes are much improved, so that developers who use it now have a greatly improved toolkit.

Without SWT threatening to become the new standard, it’s difficult to know whether Sun would have done this work to try and catch up, so having the two toolkits is actually good for users of both. In the past, interoperability between the two toolkits was poor, although this has improved dramatically since Eclipse 3.0.

SWT is the foundation on which the entire Eclipse UI is based. It is fast, native, and multiplatform, but it does not suffer the LCD problem present in

AWT or the look-and-feel problem present in Swing. SWT does this by taking a best-of-both-worlds approach: It uses native widgets whenever possible on a platform and supplements them with emulated widgets on platforms where they don’t exist; a good example of this is the tree widget that exists in native form under Windows, but is emulated under Linux. The result is a rich, portable API for building GUI applications that adhere very closely to the lookand-feel of each platform they support.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

138 CHAPTER 4 • The Standard Widget Toolkit

Note

: While providing a consistent, high-level, public API, under the covers SWT is very different from one platform to the next. SWT has a unique implementation for each platform and low-level SWT APIs map one to one with their platform counterparts. For a detailed discussion about how SWT interfaces to the native platform, see

www.eclipse.org/ articles/Article-SWT-Design-1/SWT-Design-1.html.

4.2

SWT Widgets

SWT provides a rich set of widgets that can be used to create either standalone Java applications or Eclipse plug-ins. Before going into detail about each of the widgets you are likely to use, it is instructive to explore a simple standalone SWT example.

4.2.1

Simple stand-alone example

Let’s start by revisiting the simple Java project and

HelloWorld

application created in Chapter 1, Using Eclipse Tools.

4.2.1.1

Adding SWT to your project’s classpath

Before you can start using SWT, the SWT libraries need to be added to your project’s classpath. To add SWT support, do the following:

1. Right-click on the project and select the

Properties

command to open the

Properties

dialog.

2. Select the

Java Build Path > Libraries

tab and click the

Add External JARs

button.

3. Drill down to your Eclipse

/plugins

directory.

4. Select

org.eclipse.swt.win32.win32.x86_3.

n

.

n

.v

nnnn

.jar

(or corresponding Linux or OS X JAR) and click

OK

to finish adding the SWT library to your project’s classpath (see Figure 4–1).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

4.2

SWT Widgets

5HWXUQWR7DEOHRI&RQWHQWV

139

Figure 4–1

Java Build Path > Libraries properties.

4.2.1.2

Standalone SWT code

Next, modify the

HelloWorld

class to convert it into a standalone SWT example. To do this, remove the contents of the main()

method and replace it with the following:

1

public static void

main(String[] args) {

2 Display display = new Display();

3 Shell shell = new Shell(display);

4 shell.setText("Hello World");

5 shell.setBounds(100, 100, 200, 50);

6 shell.setLayout(new FillLayout());

7 Label label = new Label(shell, SWT.CENTER);

8 label.setText("Hello World");

9 Color red = new Color(display, 255, 0, 0);

10 label.setForeground(red);

11 shell.open();

12 while (!shell.isDisposed()) {

13 if (!display.readAndDispatch()) display.sleep();

14 }

15 red.dispose();

16 display.dispose();

17 }

Note

: After entering the new method text, select the

Source > Organize

Imports

command (or press

Ctrl+Shift+O

) to add imports for all the referenced SWT classes.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

140 CHAPTER 4 • The Standard Widget Toolkit

The following examines each line in detail.

Line 2—

Each SWT-based application has one

Display

instance that represents the link between the underlying platform and SWT. In addition to managing the SWT event loop, it also provides access to the platform resources SWT needs. It will be disposed in Line 16.

Line 3—

Each window has a

Shell

representing the window frame with which the user interacts. It handles the familiar moving and sizing behavior common to all windows and acts as the parent for any widgets displayed within its bounds.

Line 4—

The frame.

setText()

method is used to set the title of the window

Line 5—

The setBounds()

method is used to set the size and position of the window frame. In the example, the window frame will be 200 pixels wide, 50 pixels tall, and will be positioned 100x100 pixels from the top left corner of the screen.

Line 6—

The setLayout()

method sets the layout manager for the window frame.

FillLayout

is a simple layout that causes the single child widget to fill the entire bounds of its parent. SWT layout managers will be discussed in detail in Section 4.3, Layout Management, on page 178.

Line 7—

This creates a simple label widget that has the shell as its parent and will display its text centered relative to itself.

Line 8—

The setText()

method is used to set the text of the label.

Line 9—

This creates a

Color

instance with the color red. Note that you could use the red system color here as well:

Color red = display.getSystemColor(SWT.COLOR_RED);

Line 10—

The setForeground()

method sets the foreground color of the label.

Line 11—

Up to this point, the window frame has not been visible. The open()

method causes it to appear.

Line 12—

The while

loop continually checks whether the window frame has been closed.

Line 13—

The display

manages the event loop. The readAndDispatch() method reads events from the platform’s event queue and dispatches them to the appropriate receiver. The method returns true

as long as there is more work to be done and false

when the event queue is empty (thus allowing the UI thread to sleep until there is more work to be done).

Lines 15 and 16—

When the loop detects that the window has been disposed, it is necessary to dispose of the color, display, and any associated platform resources. Note that system colors should not be disposed.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 141

4.2.1.3

Running the example

To launch a Java application, use the

Run As > Java Application

command.

This will create an

Java Application launch configuration

(see Figure 4–2) that can be selected in the

Run

dialog.

Figure 4–2

The Run dialog.

Click the dialog’s

Run

button to launch the Java application (see Figure 4–3).

Figure 4–3

Running the stand-alone SWT application.

4.2.2

Widget lifecycle

One of SWT’s goals is to be small and lean. To achieve this, a basic design decision was made that as much widget state as possible would be stored in the platform widget rather than in the SWT widget. This is in marked contrast to

Swing, which maintains the entire widget state within the widget. By not duplicating the information maintained at the platform level, SWT widgets are very small with modest memory requirements.

One trade-off to this approach is that SWT widgets cannot properly exist by themselves. When an SWT widget is created, its underlying platform coun-

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

142 CHAPTER 4 • The Standard Widget Toolkit

terpart is immediately created. Almost all requests for widget state information go to the platform widget.

Most platforms require that widgets be created within the context of a specific parent, so SWT requires that a parent widget be supplied as one of its constructor arguments. Another requirement of many platforms is that certain

style

settings must be supplied at creation time (for example, buttons can be checkboxes, radio buttons, or simple buttons and text fields can be singleor multi-line).

Style bits

are represented by int

constants defined in the

SWT

class. Styles are then OR’ed together and passed as another constructor argument to create the initial style of a widget. Note that all styles are not supported on all platforms, so in many cases, the requested styles are treated as suggestions that may or may not have any effect on a particular platform.

Another platform requirement imposed on SWT is that resources for the platform should be explicitly disposed when they are no longer needed. This applies to the widgets themselves and any resources (e.g., graphics, fonts, and colors) they have used. The basic rule is: if you create a widget, you must destroy the widget using its dispose()

method. If you use any system resources, such as system colors, you should not release them.

Fortunately, a widget that is a child of another widget is automatically destroyed when its parent is destroyed. This means that if you properly dispose of a shell, you do not need to dispose of each of its children because they will be disposed of automatically.

4.2.3

Widget events

An

event

is the mechanism that notifies an application when a user performs a mouse or keyboard action. The application can be notified about text entry, mouse clicks, mouse movements, focus changes, and so on. Events are handled by adding a listener to a widget. For example, a

SelectionListener

is used to inform the application that a

Button

has been pressed and released or that an item has been selected from a list box. As another example, all widgets support a

Dispose

event that is invoked just before a widget is destroyed.

For each type of event, SWT defines a listener interface (for example,

<EventName>Listener

); an event class; and, if necessary, an adapter class.

Note that adapter classes are only provided in cases where the listener interface defines more than one method. Furthermore, for each widget that implements a specific event, there are corresponding add<EventName>Listener and remove<EventName>Listener

methods.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 143

Table 4–1 presents a list of the event types defined by SWT along with a description of when each event is generated and a list of the widgets that generate that event.

Table 4–1

Widget Events

Event Name

Arm

Control

Dispose

Focus

Help

Key

Menu

Modify

Mouse

MouseMove

MouseTrack

Paint

Selection

Shell

Generated When

A menu item is armed

(highlighted)

A control is resized or moved

A control is destroyed

A control gains or loses focus

The user requests help (e.g., by pressing the

F1

key)

A key is pressed or released

A menu is hidden or shown

Text is modified

The mouse is pressed, released, or double-clicked

The mouse moves over the control

The mouse enters, leaves, or hovers over the control

A control needs to be repainted

An item is selected in the control

Widgets

MenuItem

Control, TableColumn, Tracker

Widget

Control

Control, Menu, MenuItem

Control

Menu

Combo, Text

Control

Control

Control

Control

Button, Combo, CoolItem, List,

MenuItem, Sash, Scale, ScrollBar,

Slider, StyledText, TabFolder,

Table, TableColumn, TableTree,

Text, ToolItem, Tree

Shell The shell is minimized, maximized, activated, deactivated, or closed

continued

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

144 CHAPTER 4 • The Standard Widget Toolkit

Table 4–1

Widget Events (continued)

Event Name

Traverse

Tree

Generated When

The control is traversed

(tabbed)

A tree item is collapsed or expanded

Widgets

Control

Tree, TableTree

Verify Text is about to be modified

Text, StyledText

Note:

This table was adapted from the

Platform Plug-in Developer Guide for Eclipse.

4.2.4

Abstract widget classes

All the UI objects in the system are derived from the abstract classes

Widget and

Control

(see Figure 4–4). This section and the ones immediately following it discuss the major widget types and their major APIs. API descriptions are taken from the Eclipse platform Javadoc.

Figure 4–4

SWT widget hierarchy.

Note

: For every event, there is an add<EventName>Listener

method and a corresponding remove<EventName>Listener

method. Likewise, for every widget property, there is a get<PropertyName>

and a set<PropertyName>

method. In the interest of space, only the add<EventName>Listener

and set<PropertyName> methods are listed. Each widget type has a constructor that requires the widget’s parent as the first argument and the style (an int

) as the second argument.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 145

4.2.4.1

Widget

The

Widget

class is the abstract superclass of the following classes:

Caret

,

Control

(discussed below),

DragSource

,

DropTarget

,

Item

,

Menu

(discussed in Section 4.2.7, Menus, on page 174),

ScrollBar,

and

Tracker

.

Useful APIs include: addDisposeListener(DisposeListener)

—Adds the listener to the collection of listeners that will be notified when the widget is disposed.

addListener(int, Listener)

—Adds the listener to the collection of listeners that will be notified when an event of the given type occurs.

dispose()

—Disposes of the OS resources associated with the receiver and all its descendents.

getData(String)

—Returns the application-defined property of the receiver with the specified name, or null

if it has not been set.

isDisposed()

—Returns true

if the widget has been disposed and false

otherwise.

notifyListeners(int, Event)

—Notifies all the receiver’s listeners for events of the given type that one such event has occurred by invoking the handleEvent()

method.

setData(String, Object)

—Sets the application-defined property of the receiver with the specified name to the given value.

toString()

—Returns a string containing a concise, human-readable description of the widget.

4.2.4.2

Control

The

Control

class is the abstract superclass of all the dialog and window component classes such as

Button

,

Label

,

ProgressBar

,

Sash

,

Scrollable

, and

Slider

(each of these is described later in this chapter). Useful APIs include: addControlListener(ControlListener)

—Adds the listener to the collection of listeners that will be notified when the control is moved or resized by sending it one of the messages defined in the

ControlListener interface.

addFocusListener(FocusListener)

—Adds the listener to the collection of listeners that will be notified when the control gains or loses focus by sending it one of the messages defined in the

FocusListener

interface.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

146 CHAPTER 4 • The Standard Widget Toolkit

addHelpListener(HelpListener)

—Adds the listener to the collection of listeners that will be notified when help events are generated for the control by sending it one of the messages defined in the

HelpListener interface.

addKeyListener(KeyListener)

—Adds the listener to the collection of listeners that will be notified when keys are pressed and released on the system keyboard by sending it one of the messages defined in the

KeyListener

interface.

addMouseListener(MouseListener)

—Adds the listener to the collection of listeners that will be notified when mouse buttons are pressed and released by sending it one of the messages defined in the

MouseListener interface.

addMouseMoveListener(MouseMoveListener)

—Adds the listener to the collection of listeners that will be notified when the mouse moves by sending it one of the messages defined in the

MouseMoveListener

interface.

addMouseTrackListener(MouseTrackListener)

—Adds the listener to the collection of listeners that will be notified when the mouse passes or hovers over controls by sending it one of the messages defined in the

MouseTrackListener

interface.

addPaintListener(PaintListener)

—Adds the listener to the collection of listeners that will be notified when the receiver needs to be painted by sending it one of the messages defined in the

PaintListener interface.

addTraverseListener(TraverseListener)

—Adds the listener to the collection of listeners that will be notified when traversal events occur by sending it one of the messages defined in the

TraverseListener

interface.

getDisplay()

—Returns the display on which the receiver was created.

getParent()

—Returns the receiver’s parent, which must be a

Composite or null

when the receiver is a shell that was created with null or a display for a parent.

getShell()

—Returns the receiver’s shell.

isDisposed()

—Returns true

if the widget has been disposed and false

otherwise.

isEnabled()

—Returns true

if the receiver is enabled and all the receiver’s ancestors are enabled and false

otherwise.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 147

isVisible()

—Returns true

if the receiver is visible and all the receiver’s ancestors are visible and false

otherwise.

pack()

—Causes the receiver to be resized to its preferred size.

redraw()

—Causes the entire bounds of the receiver to be marked as needing to be redrawn.

setBackground(Color)

—Sets the receiver’s background color to the color specified by the argument, or to the default system color for the control if the argument is null

.

setBounds(Rectangle)

—Sets the receiver’s size and location to the rectangular area specified by the argument.

setEnabled(boolean)

—Enables the receiver if the argument is true and disables it otherwise.

boolean setFocus()

—Causes the receiver to have the keyboard focus such that all keyboard events will be delivered to it.

setFont(Font)

—Sets the font that the receiver will use to paint textual information to the font specified by the argument, or to the default font for that kind of control if the argument is null

.

setForeground(Color)

—Sets the receiver’s foreground color to the color specified by the argument, or to the default system color for the control if the argument is null

.

setLayoutData(Object)

—Sets the layout data associated with the receiver to the argument.

setLocation(Point)

—Sets the receiver’s location to the point specified by the argument that is relative to the receiver’s parent (or its display if its parent is null

).

setRedraw(boolean)

—If the argument is false

, causes subsequent drawing operations in the receiver to be ignored.

setSize(Point)

—Sets the receiver’s size to the point specified by the argument.

setToolTipText(String)

—Sets the receiver’s tool tip text to the argument, which may be null

, indicating that no tool tip text should be shown.

setVisible(boolean)

—Marks the receiver as visible if the argument is true

, and marks it invisible otherwise.

update()

—Forces all outstanding paint requests for the widget to be processed before this method returns.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

148 CHAPTER 4 • The Standard Widget Toolkit

4.2.4.3

Scrollable

The

Scrollable

class is the abstract superclass of all controls that can have scrollbars such as

Composite

,

List

, and

Text

. Useful APIs include: getClientArea()

—Returns a rectangle describing the area of the receiver that is capable of displaying data (i.e., not covered by the

“trimmings”).

getHorizontalBar()

—Returns the receiver’s horizontal scrollbar if it has one, and null

if it does not.

getVerticalBar()

—Returns the receiver’s vertical scrollbar if it has one, and null

if it does not.

4.2.5

Top-level classes

As stated earlier, each SWT application needs a display and one or more shells

(representing each window frame).

4.2.5.1

Display

The display represents the link between the underlying platform, the UI thread, and SWT. Although the

Display

constructors are public, under normal circumstances, you should not be constructing new instances (unless you are creating a stand-alone SWT application); instead, the following two static

Display

methods return an instance.

getCurrent()

—Returns the display associated with the currently running thread or null

if the currently running thread is not a UI thread for any display.

getDefault()

—Returns the default display. This is the instance that was first created by the system.

Calls to SWT methods that create widgets or modify currently visible widgets must be made from the UI thread; otherwise, an

SWTException

is thrown indicating the call was made from a non-UI thread. A call to the previously listed getCurrent()

method can be used to quickly determine whether or not the current thread is UI or non-UI. If the thread is non-UI, the following

Display

methods can be used to queue execution on the UI thread at the next available time.

asyncExec(Runnable)

—Causes the run() method of the runnable to be invoked by the UI thread at the next reasonable opportunity, without blocking the calling thread.

syncExec(Runnable)

—Causes the run()

method of the runnable to be invoked by the UI thread at the next reasonable opportunity. The calling thread is blocked until the run()

method has completed execution.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 149

timerExec(int, Runnable)

—Causes the run()

method of the runnable to be invoked by the UI thread after the specified number of milliseconds have elapsed.

These methods, combined with the methods listed previously, can be used to update visible widgets when responding to resource change events (see the end of Section 9.2, Processing Change Events, on page 411), displaying error messages (see Section 21.4.3, OpenEmailAction, on page 790), or simply deferring execution until the widgets have been initialized (see Section 8.2.5,

Label provider, on page 370).

In addition to managing the UI event loop, it also provides access to platform resources that SWT needs. Useful APIs include: addListener(int, Listener)

—Adds the listener to the collection of listeners that will be notified when an event of the given type occurs.

beep()

—Causes the system hardware to emit a short sound (if it supports this capability).

close()

—Requests that the connection between SWT and the underlying OS be closed.

disposeExec(Runnable)

—Causes the run()

method of the runnable to be invoked by the UI thread just before the display is disposed.

findWidget(int)

—Given the OS handle for a widget, returns the instance of the

Widget

subclass, which represents it in the currently running application if such an instance exists, or null

if no matching widget can be found.

getActiveShell()

—Returns the currently active

Shell

, or null

if no shell belonging to the currently running application is active.

getBounds()

—Returns a rectangle describing the receiver’s size and location.

getClientArea()

—Returns a rectangle describing the area of the receiver that is capable of displaying data.

getCursorControl()

—Returns the control that the onscreen pointer is currently over, or null

if it is not currently over one of the controls built by the currently running application.

getCursorLocation()

—Returns the location of the onscreen pointer relative to the top left corner of the screen.

getData(String)

—Returns the application-defined property of the receiver with the specified name, or null

if it has not been set.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

150 CHAPTER 4 • The Standard Widget Toolkit

getDoubleClickTime()

—Returns the longest duration, in milliseconds, between two mouse button clicks that will be considered a double-click by the underlying OS.

getFocusControl()

—Returns the control that currently has keyboard focus, or null

if keyboard events are not currently going to any of the controls built by the currently running application.

getShells()

—Returns an array containing all shells that have not been disposed and have the receiver as their display.

getSystemColor(int)

—Returns the matching standard color for the given constant, which should be one of the color constants specified in the class SWT.

getSystemFont()

—Returns a reasonable font for applications to use.

readAndDispatch()

—Reads an event from the OS’s event queue, dispatches it appropriately, and returns true

if there is potentially more work to do, or false

if the caller can sleep until another event is placed on the event queue.

setCursorLocation(Point)

—Sets the location of the onscreen pointer relative to the top left corner of the screen.

setData(String, Object)

—Sets the application-defined property of the receiver with the specified name to the given argument.

sleep()

—Causes the UI thread to sleep (i.e., to be put in a state where it does not consume central processing unit [CPU] cycles) until an event is received or it is otherwise awakened.

update()

—Forces all outstanding paint requests for the display to be processed before this method returns.

4.2.5.2

Shell

Every window has a shell representing the window frame with which the user interacts. The shell handles the familiar moving and sizing behavior common to all windows and acts as the parent for widgets displayed within its bounds

(see Section 11.1.10, Opening a dialog—finding a parent shell, on page 462).

Useful APIs include: addShellListener(ShellListener)

—Adds the listener to the collection of listeners that will be notified when operations are performed on the receiver by sending the listener one of the messages defined in the

ShellListener

interface.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 151

close()

—Requests that the window manager close the receiver in the same way it would be closed if the user clicked on the “close box” or performed some other platform-specific key or mouse combination that indicated the window should be removed. dispose()

—Disposes of the OS resources associated with the receiver and all its descendents. getDisplay()

—Returns the display on which the receiver was created. getShell()

—Returns the receiver’s shell. getShells()

—Returns an array containing all shells that are descendents of the receiver. isEnabled()

—Returns true

if the receiver is enabled and all the receiver’s ancestors are enabled and false

otherwise. open()

—Moves the receiver to the top of the drawing order for the display on which it was created (so that all other shells on that display, which are not the receiver’s children, will be drawn behind it), marks it visible, sets focus to its default button (if it has one), and asks the window manager to make the shell active. setActive()

—Moves the receiver to the top of the drawing order for the display on which it was created (so that all other shells on that display, which are not the receiver’s children, will be drawn behind it) and asks the window manager to make the shell active. setEnabled(boolean enabled)

—Enables the receiver if the argument is true

and disables it otherwise. setVisible(boolean visible)

—Marks the receiver as visible if the argument is true

and marks it invisible otherwise.

4.2.6

Useful widgets

Dozens of widgets are defined within the SWT class hierarchy. This section discusses the widgets most commonly used in plug-in development, such as labels, buttons, text fields, lists, tables, trees, containers, and tab folders. It also provides a list of useful APIs and creation styles for each widget.

4.2.6.1

Label

Labels are static controls that display either strings or images as their contents.

They do not generate any special events and do not support any user interaction. Useful APIs include:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

152 CHAPTER 4 • The Standard Widget Toolkit

setAlignment(int)

—Controls how text and images will be displayed in the receiver. Valid arguments include

SWT.CENTER

.

SWT.LEFT

,

SWT.RIGHT

, and setImage(Image)

—Sets the receiver’s image to the argument, which may be null

, indicating that no image should be displayed.

setText(String)

—Sets the receiver’s text.

Useful creation styles include:

SWT.SHADOW_IN

—Creates an inset shadow around the widget.

SWT.SHADOW_OUT

—Creates an outset shadow around the widget.

SWT.SHADOW_NONE

—Creates a widget with no shadow.

SWT.WRAP

—Causes the text of the widget to wrap onto multiple lines, if necessary.

SWT.SEPARATOR

—Creates a single vertical or horizontal line.

SWT.HORIZONTAL

—Creates a horizontal line.

SWT.VERTICAL

—Creates a vertical line.

SWT.LEFT

—Left-justifies the widget within its bounding box.

SWT.RIGHT

—Right-justifies the widget within its bounding box.

SWT.CENTER

—Centers the widget within its bounding box.

4.2.6.2

Button

Buttons provide a mechanism to initiate an action when clicked. They generate a

Selection

event when pressed and released. Buttons can display either strings or images as their contents. Depending on their style settings, buttons can represent a number of common UI element types such as pushbuttons, checkboxes, radio buttons, toggle buttons, and arrow buttons. Useful APIs include: addSelectionListener(SelectionListener)

—Adds the listener to the collection of listeners that will be notified when the control is selected by sending it one of the messages defined in the

SelectionListener interface.

getSelection()

—Returns true

if the receiver is selected and false otherwise.

setAlignment(int)

—Controls how text, images, and arrows will be displayed in the receiver.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 153

setImage(Image)

—Sets the receiver’s image to the argument, which may be null

, indicating that no image should be displayed.

setSelection(boolean)

—Sets the selection state of the receiver if it is of type

SWT.CHECK

,

SWT.RADIO

, or

SWT.TOGGLE

.

setText(String)

—Sets the receiver’s text.

Useful creation styles include:

SWT.ARROW

—Creates an arrow button widget.

SWT.CHECK

—Creates a checkbox widget.

SWT.PUSH

—Creates a pushbutton widget.

SWT.RADIO

—Creates a radio button widget.

SWT.TOGGLE

—Creates a toggle button widget.

SWT.UP

—Creates an upward-pointing arrow button.

SWT.DOWN

—Creates a downward-pointing arrow button.

SWT.LEFT

—Creates a leftward-pointing arrow button or left-justifies the widget within its bounding box.

SWT.RIGHT

—Creates a rightward-pointing arrow button or rightjustifies the widget within its bounding box.

SWT.CENTER

—Centers the widget within its bounding box.

The example code that follows (shown without a package statement) creates a window with a single pushbutton. Clicking on the pushbutton will change the text of the button (see Figure 4–5).

Figure 4–5

Button example.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

154 CHAPTER 4 • The Standard Widget Toolkit

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class ButtonExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Button Example");

shell.setBounds(100, 100, 200, 100);

shell.setLayout(new FillLayout());

final Button button = new Button(shell, SWT.PUSH);

button.setText("Click Me Now");

button.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent event) {

}

button.setText("I Was Clicked");

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

Relative to the first example in this chapter, the interesting lines in the preceding example are highlighted in bold. After the creation of the button, a selection listener is added in which a

SelectionAdapter

is created that overrides the widgetSelected()

method.

4.2.6.3

Text

Text widgets provide text viewing and editing capabilities. If the user enters more text than can be accommodated within the widget, it will automatically scroll. Useful APIs include: addModifyListener(ModifyListener)

—Adds the listener to the collection of listeners that will be notified when the receiver’s text is modified by sending it one of the messages defined in the

ModifyListener interface.

addSelectionListener(SelectionListener)

—Adds the listener to the collection of listeners that will be notified when the control is selected by sending it one of the messages defined in the

SelectionListener interface.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 155

addVerifyListener(VerifyListener)

—Adds the listener to the collection of listeners that will be notified when the receiver’s text is verified by sending it one of the messages defined in the

VerifyListener

interface. A verify event occurs after the user has done something to modify the text (typically typed a key), but before the text is modified clearSelection()

—Clears the selection.

copy()

—Copies the selected text.

cut()

—Cuts the selected text.

getSelectionText()

—Gets the selected text.

getText()

—Gets the widget text.

getText(int start, int end)

—Gets a range of text.

insert(String)

—Inserts a string.

paste()

—Pastes text from the clipboard.

selectAll()

—Selects all the text in the receiver.

setEchoChar(char echo)

—Sets the echo character.

setEditable(boolean editable)

—Sets the editable state.

setSelection(int start, int end)

—Sets the selection.

setText(String)

—Sets the contents of the receiver to the given string.

setTextLimit(int)

—Sets the maximum number of characters that the receiver is capable of holding to be the argument.

setTopIndex(int)

—Sets the zero-relative index of the line that is currently at the top of the receiver.

Useful creation styles include:

SWT.SINGLE

—Creates a single-line text widget.

SWT.MULTI

—Creates a multi-line text widget.

SWT.WRAP

—Causes widget’s text to wrap onto multiple lines if necessary.

SWT.READ_ONLY

—Creates a read-only text widget that cannot be edited.

SWT.LEFT

—Creates a left-justified text widget.

SWT.RIGHT

—Creates a right-justified text widget.

SWT.CENTER

—Creates a center-justified text widget.

The example code that follows creates a window frame with a single-line text field, which only allows digits (0–9) to be entered (see Figure 4–6).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

156

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 4 • The Standard Widget Toolkit

Figure 4–6

Text example.

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class TextExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Text Example");

shell.setBounds(100, 100, 200, 100);

shell.setLayout(new FillLayout());

final Text text = new Text(shell, SWT.MULTI);

text.addVerifyListener(new VerifyListener() {

public void verifyText(VerifyEvent event) {

event.doit = event.text.length() == 0

|| Character.isDigit(event.text.charAt(0));

}

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

As in the previous example, interesting lines are highlighted in bold. After the creation of the text widget, a verify listener is added in which a

VerifyListener

is created that overrides the verifyText()

method to verify that the character entered is a digit. Note: If the user deletes or backspaces over some text, the event.text

will be empty.

4.2.6.4

List

List widgets present a list of items and allow the user to select one or more of them. Lists generate a

Selection

event when an item is selected. Useful APIs include: add(String)

—Adds the argument to the end of the receiver’s list.

addSelectionListener(SelectionListener)

—Adds the listener to the collection of listeners that will be notified when the receiver’s selection changes by sending it one of the messages defined in the

SelectionListener

interface.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 157

deselect(int)

—Deselects the item at the given zero-relative index in the receiver. deselectAll()

—Deselects all selected items in the receiver. getItem(int)

—Returns the item at the given, zero-relative index in the receiver. getItemCount()

—Returns the number of items contained in the receiver. getItems()

—Returns an array of strings that are items in the receiver. getSelection()

—Returns an array of strings that are currently selected in the receiver. getSelectionCount()

—Returns the number of selected items contained in the receiver. getSelectionIndex()

—Returns the zero-relative index of the item that is currently selected in the receiver, or -1 if no item is selected. getSelectionIndices()

—Returns the zero-relative indices of the items that are currently selected in the receiver. indexOf(String)

—Gets the index of an item. remove(int)

—Removes the item from the receiver at the given zerorelative index. remove(String)

—Searches the receiver’s list starting at the first item until an item is found that is equal to the argument and removes that item from the list. removeAll()

—Removes all the items from the receiver. select(int)

—Selects the item at the given zero-relative index in the receiver’s list. selectAll()

—Selects all the items in the receiver. setItems(String[] items)

—Sets the receiver’s items to be the given array of items. setSelection(int)

—Selects the item at the given zero-relative index in the receiver. setSelection(String[])

—Sets the receiver’s selection to be the given array of items. setTopIndex(int)

—Sets the zero-relative index of the line that is currently at the top of the receiver.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

158 CHAPTER 4 • The Standard Widget Toolkit

Useful creation styles include:

SWT.SINGLE

—Creates a single-selection list widget.

SWT.MULTI

—Creates a multiple-selection list widget.

The following example creates a window frame with a single-selection list box. Clicking or double-clicking on an item will print the selection to the console (see Figure 4–7).

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class ListExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("List Example");

shell.setBounds(100, 100, 200, 100);

shell.setLayout(new FillLayout());

final List list = new List(shell, SWT.SINGLE);

list.setItems(new String[]

{"First", "Second", "Third"});

list.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent event) {

String[] selected = list.getSelection();

if (selected.length > 0)

System.out.println(

"Selected: " + selected[0]);

}

public void widgetDefaultSelected(

SelectionEvent event) {

String[] selected = list.getSelection();

if (selected.length > 0)

System.out.println(

"Default Selected: " + selected[0]);

}

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

4.2

SWT Widgets

5HWXUQWR7DEOHRI&RQWHQWV

159

Figure 4–7

List example.

After the list widget is created, its contents are set using the setItems() method. Next, a selection listener is added in which a

SelectionAdapter

is created that overrides the widgetSelected()

and widgetDefaultSelected()

methods to print any items selected or double-clicked.

4.2.6.5

Combo

Similar to the list widget, the combo box widget allows the user to select a single item from a list of available items. Depending on how a combo is configured, it may also allow the user to enter a new value into the text field. The last selected or entered item is displayed in the text box. Useful APIs include: add(String)

—Adds the argument to the end of the receiver’s list.

addModifyListener(ModifyListener)

—Adds the listener to the collection of listeners that will be notified when the receiver’s text is modified by sending it one of the messages defined in the

ModifyListener interface. addSelectionListener(SelectionListener)

—Adds the listener to the collection of listeners that will be notified when the receiver’s selection changes by sending it one of the messages defined in the

SelectionListener

interface. clearSelection()

—Sets the selection in the receiver’s text field to an empty selection starting just before the first character. copy()

—Copies the selected text. cut()

—Cuts the selected text. deselect(int)

—Deselects the item at the given zero-relative index in the receiver’s list. deselectAll()

—Deselects all selected items in the receiver’s list.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

160 CHAPTER 4 • The Standard Widget Toolkit

getItem(int)

—Returns the item at the given, zero-relative index in the receiver’s list. getItemCount()

—Returns the number of items contained in the receiver’s list. getItems()

—Returns an array of strings that are items in the receiver’s list. getSelectionIndex()

—Returns the zero-relative index of the item that is currently selected in the receiver’s list, or -1 if no item is selected. getText()

—Returns a string containing a copy of the contents of the receiver’s text field. indexOf(String)

—Searches the receiver’s list starting at the first item

(index 0) until an item is found that is equal to the argument and returns the index of that item. paste()

—Pastes text from the clipboard. remove(int)

—Removes the item from the receiver’s list at the given zero-relative index. remove(String)

—Searches the receiver’s list starting at the first item until an item is found that is equal to the argument and removes that item from the list. removeAll()

—Removes all the items from the receiver’s list. select(int)

—Selects the item at the given zero-relative index in the receiver’s list. setItems(String[] items)

—Sets the receiver’s list to be the given array of items. setText(String)

—Sets the contents of the receiver’s text field to the given string. setTextLimit(int)

—Sets the maximum number of characters that the receiver’s text field is capable of holding to be the argument.

Useful creation styles include:

SWT.DROP_DOWN

—Creates a drop-down list widget. Editable drop-down list widgets are also known as combo boxes.

SWT.READ_ONLY

—Creates a read-only drop-down list widget.

SWT.SIMPLE

—Creates a combo widget in which the list is always present.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 161

The following example creates a window frame with two combo widgets and a label widget. Selecting an item from the first or second combo box or entering a new value into the second combo box will change the label’s contents to reflect the selection (see Figure 4–8).

Figure 4–8

Combo box example.

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class ComboExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Combo Example");

shell.setBounds(100, 100, 200, 100);

shell.setLayout(new FillLayout(SWT.VERTICAL));

final Combo combo1 = new Combo(shell,SWT.READ_ONLY);

final Combo combo2 = new Combo(shell,SWT.DROP_DOWN);

final Label label = new Label(shell, SWT.CENTER);

combo1.setItems(new String[]

{"First", "Second", "Third"});

combo1.setText("First");

combo1.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent event) {

label.setText("Selected: " + combo1.getText());

}

});

combo2.setItems(new String[]

{"First", "Second", "Third"});

combo2.setText("First");

combo2.addModifyListener(new ModifyListener() {

public void modifyText(ModifyEvent event) {

label.setText("Entered: " + combo2.getText());

}

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

After the creation of the combo widgets and the label widget, the contents of the combo widgets are set using the setItems()

method and their initial selections (the contents of their text fields) with the setText()

method. A

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

162 CHAPTER 4 • The Standard Widget Toolkit

selection listener is added to the first combo in which a

SelectionAdapter is created that overrides the widgetSelected()

method, and a modify listener is added to the second combo in which a

ModifyListener

is created that overrides the modifyText()

method. Both methods update the contents of the label widget when their respective combo changes its selection.

4.2.6.6

Table

The table widget provides a vertical, multicolumn list of items showing a row of cells for each item in the list. The columns of the table are defined by one or more

TableColumn

instances, each of which defines its own heading, width, and alignment. Useful APIs include: addSelectionListener(SelectionListener)

—Adds the listener to the collection of listeners that will be notified when the receiver’s selection changes by sending it one of the messages defined in the

SelectionListener

interface.

deselect(int)

—Deselects the item at the given zero-relative index in the receiver. deselectAll()

—Deselects all selected items in the receiver. getColumn(int)

—Returns the column at the given, zero-relative index in the receiver. getColumns()

—Returns an array of

TableColumns

that are columns in the receiver. getItem(int)

—Returns the item at the given, zero-relative index in the receiver. getSelection()

—Returns an array of

TableItems

that are currently selected in the receiver. getSelectionCount()

—Returns the number of selected items contained in the receiver. getSelectionIndex()

—Returns the zero-relative index of the item that is currently selected in the receiver, or -1 if no item is selected. getSelectionIndices()

—Returns the zero-relative indices of the items that are currently selected in the receiver. indexOf(TableColumn)

—Searches the receiver’s list starting at the first column (index 0) until a column is found that is equal to the argument and returns the index of that column. indexOf(TableItem)

—Searches the receiver’s list starting at the first item (index 0) until an item is found that is equal to the argument and returns the index of that item.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 163

remove(int)

—Removes the item from the receiver at the given zerorelative index. removeAll()

—Removes all the items from the receiver. select(int)

—Selects the item at the given zero-relative index in the receiver. selectAll()

—Selects all the items in the receiver. setHeaderVisible(boolean)

—Marks the receiver’s header as visible if the argument is true

, and marks it invisible otherwise. setLinesVisible(boolean)

—Marks the receiver’s lines as visible if the argument is true

, and marks it invisible otherwise. setSelection(int)

—Selects the item at the given zero-relative index in the receiver. setSelection(TableItem[])

—Sets the receiver’s selection to be the given array of items.

setTopIndex(int)

—Sets the zero-relative index of the item that is currently at the top of the receiver.

Useful creation styles include:

SWT.SINGLE

—Creates a single-selection table widget.

SWT.MULTI

—Creates a multiple-selection table widget.

SWT.CHECK

—Creates a checkbox table widget.

SWT.FULL_SELECTION

—Creates a table widget with row selection

(rather than cell selection).

Useful

TableColumn

APIs include: addControlListener(ControlListener)

—Adds the listener to the collection of listeners that will be notified when the control is moved or resized by sending it one of the messages defined in the

ControlListener interface. addSelectionListener(SelectionListener)

—Adds the listener to the collection of listeners that will be notified when the control is selected by sending it one of the messages defined in the

Selection-

Listener

interface. pack()

—Causes the receiver to be resized to its preferred size. setAlignment(int)

—Controls how text and images will be displayed in the receiver.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

164 CHAPTER 4 • The Standard Widget Toolkit

setImage(Image)

—Sets the receiver’s image to the argument, which may be null

, indicating that no image should be displayed. setResizable(boolean)

—Sets the resizable attribute. setText(String)

—Sets the receiver’s text. setWidth(int)

—Sets the width of the receiver.

Useful

TableItem

APIs include: getChecked()

—Returns true

if the receiver is checked and false

otherwise. getText(int)

—Returns the text stored at the given column index in the receiver, or empty string if the text has not been set. setBackground(Color)

—Sets the receiver’s background color to the color specified by the argument, or to the default system color for the item if the argument is null

.

setChecked(boolean)

—Sets the checked state of the checkbox for this item. setForeground(Color)

—Sets the receiver’s foreground color to the color specified by the argument, or to the default system color for the item if the argument is null

.

setGrayed(boolean)

—Sets the grayed state of the checkbox for this item. setImage(Image)

—Sets the receiver’s image to the argument, which may be null

, indicating that no image should be displayed. setImage(Image[])

—Sets the image for multiple columns in the table. setImage(int, Image)

—Sets the receiver’s image at a column. setImageIndent(int)

—Sets the image indent. setText(int, String)

—Sets the receiver’s text at a column. setText(String)

—Sets the receiver’s text. setText(String[])

—Sets the text for multiple columns in the table.

The following example creates a two-column, two-item table. Clicking on an item causes the cell’s contents to print to the console (see Figure 4–9).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class TableExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Table Example");

shell.setBounds(100, 100, 200, 100);

shell.setLayout(new FillLayout());

final Table table = new Table(shell,

SWT.SINGLE | SWT.BORDER | SWT.FULL_SELECTION);

table.setHeaderVisible(true);

table.setLinesVisible(true);

TableColumn column1 =

new TableColumn(table, SWT.NULL);

column1.setText("Name");

column1.pack();

TableColumn column2 =

new TableColumn(table, SWT.NULL);

column2.setText("Age");

column2.pack();

TableItem item1 = new TableItem(table, SWT.NULL);

item1.setText(new String[] {"Dan", "43"});

TableItem item2 = new TableItem(table, SWT.NULL);

item2.setText(new String[] {"Eric", "44"});

table.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent event) {

TableItem[] selected = table.getSelection();

if (selected.length > 0) {

System.out.println("Name: " +

selected[0].getText(0));

System.out.println("Age: " +

selected[0].getText(1));

}

}

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

165

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

166

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 4 • The Standard Widget Toolkit

Figure 4–9

Table example.

The table widget is created with full selection behavior. Its headers are made visible with the setHeaderVisble()

method and its lines are made visible with the setLinesVisible()

method. Next, each column is created and its column header is set with the setText()

method. The pack()

method sets the size of each column to the maximum size of its contents. Each table row item is created next and cell contents are set with the setText()

method

(which expects an array of strings, one for each column). Finally, a selection listener is added to the table in which a

SelectionAdapter

is created that overrides the widgetSelected() method to print any items that are selected.

4.2.6.7

Tree

The tree widget is useful for displaying information in a hierarchical manner.

A tree consists of a list of items composed of other items, which in turn can be composed of other items, and so on. A user navigates through a tree by expanding and collapsing items to view and hide their component items. Useful APIs include: addSelectionListener(SelectionListener)

—Adds the listener to the collection of listeners that will be notified when the receiver’s selection changes by sending it one of the messages defined in the

SelectionListener

interface. addTreeListener(TreeListener)

—Adds the listener to the collection of listeners that will be notified when an item in the receiver is expanded or collapsed by sending it one of the messages defined in the

TreeListener

interface. deselectAll()

—Deselects all selected items in the receiver. getItemCount()

—Returns the number of items contained in the receiver that are direct item children of the receiver.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 167

getItems()

—Returns the items contained in the receiver that are direct item children of the receiver. getSelection()

—Returns an array of

TreeItems

that are currently selected in the receiver. getSelectionCount()

—Returns the number of selected items contained in the receiver. removeAll()

—Removes all the items from the receiver. selectAll()

—Selects all the items in the receiver. setSelection(TreeItem[])

—Sets the receiver’s selection to be the given array of items. setTopItem(TreeItem)

—Sets the item that is currently at the top of the receiver.

Useful creation styles include:

SWT.SINGLE

—Creates a single-selection tree widget.

SWT.MULTI

—Creates a multiple-selection tree widget.

SWT.CHECK

—Creates a checkbox tree widget.

Useful

TreeItem

APIs include: getChecked()

—Returns true

if the receiver is checked and false

otherwise. getExpanded()

—Returns true

if the receiver is expanded and false otherwise. getItemCount()

—Returns the number of items contained in the receiver that are direct item children of the receiver. getItems()

—Returns an array of

TreeItems

that are the direct item children of the receiver. getParent()

—Returns the receiver’s parent, which must be a

Tree

.

getParentItem()

—Returns the receiver’s parent item, which must be a

TreeItem

or null

when the receiver is a root. setBackground(Color)

—Sets the receiver’s background color to the color specified by the argument, or to the default system color for the item if the argument is null

.

setChecked(boolean)

—Sets the checked state of the receiver. setExpanded(boolean)

—Sets the expanded state of the receiver.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

168 CHAPTER 4 • The Standard Widget Toolkit

setForeground(Color)

—Sets the receiver’s foreground color to the color specified by the argument, or to the default system color for the item if the argument is null

.

setGrayed(boolean grayed)

—Sets the grayed state of the receiver. setImage(Image)

—Sets the receiver’s image to the argument, which may be null

, indicating that no image should be displayed. setText(String)

—Sets the receiver’s text.

The following example creates a tree with three levels of items (see Figure

4–10). Clicking on an item causes its name to print to the console.

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class TreeExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Tree Example");

shell.setBounds(100, 100, 200, 200);

shell.setLayout(new FillLayout());

final Tree tree = new Tree(shell, SWT.SINGLE);

for (int i = 1; i < 4; i++) {

TreeItem grandParent = new TreeItem(tree, 0);

grandParent.setText("Grand Parent - " + i);

for (int j = 1; j < 4; j++) {

TreeItem parent = new TreeItem(grandParent,0);

parent.setText("Parent - " + j);

for (int k = 1; k < 4; k++) {

TreeItem child = new TreeItem(parent, 0);

child.setText("Child - " + k);

}

}

}

tree.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent event) {

TreeItem[] selected = tree.getSelection();

if (selected.length > 0) {

System.out.println("Selected: " +

selected[0].getText());

}

}

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

4.2

SWT Widgets

5HWXUQWR7DEOHRI&RQWHQWV

169

Figure 4–10

Tree example.

After the creation of the tree widget, new items are created and their labels are set with the setText()

method. Many of the items have child items of their own. Finally, a selection listener is added in which a

SelectionAdapter is created that overrides the widgetSelected()

method to print a selected item.

4.2.6.8

Composite

The composite widget is used as a container for other widgets. The widget’s children are widgets contained within the bounds of the composite and resize themselves relative to it. Useful APIs include: getChildren()

—Returns an array containing the receiver’s children. layout()

—If the receiver has a layout, it asks the layout to set the size and location of the receiver’s children. setLayout(Layout)

—Sets the layout that is associated with the receiver to be the argument, which may be null

.

setTabList(Control[])

—Sets the tabbing order for the specified controls to match the order in which they occur in the argument list.

Useful creation styles include:

SWT.BORDER

—Creates a composite widget with a border.

SWT.NO_RADIO_GROUP

—Prevents child radio button behavior.

SWT.H_SCROLL

—Creates a composite widget with a horizontal scrollbar.

SWT.V_SCROLL

—Creates a composite widget with a vertical scrollbar.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

170 CHAPTER 4 • The Standard Widget Toolkit

The following example expands on the earlier button example by inserting a composite widget between the shell and the button (see Figure 4–11).

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.widgets.*; public class CompositeExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Composite Example");

shell.setBounds(100, 100, 200, 200);

Composite composite = new Composite(

shell,SWT.BORDER);

composite.setBounds(25, 25, 150, 125);

final Button button = new Button(composite,SWT.PUSH);

button.setBounds(25, 25, 100, 75);

button.setText("Click Me Now");

button.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent event) {

button.setText("I Was Clicked");

}

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

Figure 4–11

Composite example.

A composite widget is created as a child of the shell, and then the composite acts as the parent of the button widget. Note that the button is positioned relative to the composite, not the shell.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 171

4.2.6.9

Group

Group widgets are a special type of composite widget that surround children with an etched border and an optional label. Each child widget is contained within the bounds of the group and resizes itself relative to it. Useful APIs include: getChildren()

—Returns an array containing the receiver’s children. layout()

—If the receiver has a layout, it asks the layout to set the size and location of the receiver’s children. setLayout(Layout)

—Sets the layout that is associated with the receiver to be the argument, which may be null

.

setTabList(Control[])

—Sets the tabbing order for the specified controls to match the order in which they occur in the argument list.

setText(String)

—Sets the receiver’s text, which is the string that will be displayed as the receiver’s title, to the argument, which may not be null

.

Useful creation styles include:

SWT.BORDER

—Creates a composite widget with a border.

SWT.NO_RADIO_GROUP

—Prevents child radio button behavior.

The example code that follows replaces the composite in the previous example with a group widget (see Figure 4–12).

Figure 4–12

Group example.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

172 CHAPTER 4 • The Standard Widget Toolkit

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.widgets.*; public class GroupExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Group Example");

shell.setBounds(100, 100, 200, 200);

Group group = new Group(shell, SWT.NULL);

group.setText("My Group");

group.setBounds(25, 25, 150, 125);

final Button button = new Button(group, SWT.PUSH);

button.setBounds(25, 25, 100, 75);

button.setText("Click Me Now");

button.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent event) {

button.setText("I Was Clicked");

}

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

A group widget is created as a child of the shell and acts as the parent of the button widget. In addition to the border, which is always present, the group widget also has a label.

4.2.6.10

Tab folder

The tab folder widget is used to organize information within a window frame into multiple pages that appear as a set of notebook tabs. Clicking on a tab brings that page to the front. Tabs can be labels with images and text. Useful

APIs include: addSelectionListener(SelectionListener)

—Adds the listener to the collection of listeners that will be notified when the receiver’s selection changes by sending it one of the messages defined in the

SelectionListener

interface.

TabItem getItem(int)

—Returns the item at the given, zero-relative index in the receiver. getItemCount()

—Returns the number of items contained in the receiver.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 173

getItems()

—Returns an array of

TabItems

that are items in the receiver. getSelection()

—Returns an array of

TabItems

that are currently selected in the receiver. getSelectionIndex()

—Returns the zero-relative index of the item that is currently selected in the receiver, or -1 if no item is selected. indexOf(TabItem item)

—Searches the receiver’s list starting at the first item (index 0) until an item is found that is equal to the argument, and returns the index of that item. setSelection(int)

—Selects the item at the given zero-relative index in the receiver.

Useful tab folder APIs include: getControl()

—Returns the control that is used to fill the client area of the tab folder when the user selects the tab item. setControl(Control control)

—Sets the control that is used to fill the client area of the tab folder when the user selects a tab item. setImage(Image)

—Sets the receiver’s image to the argument, which may be null

, indicating that no image should be displayed. setText(String)

—Sets the receiver’s text. setToolTipText(String)

—Sets the receiver’s tool tip text to the argument, which may be null

, indicating that no tool tip text should be shown.

The example code that follows creates a tab folder with several tabs. Each tab contains a composite containing a single button (see Figure 4–13).

Figure 4–13

Tab folder example.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

174 CHAPTER 4 • The Standard Widget Toolkit

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class TabFolderExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("TabFolder Example");

shell.setBounds(100, 100, 175, 125);

shell.setLayout(new FillLayout());

final TabFolder tabFolder =

new TabFolder(shell, SWT.BORDER);

for (int i = 1; i < 4; i++) {

TabItem tabItem =

new TabItem(tabFolder, SWT.NULL);

tabItem.setText("Tab " + i);

Composite composite =

new Composite(tabFolder, SWT.NULL);

tabItem.setControl(composite);

Button button = new Button(composite, SWT.PUSH);

button.setBounds(25, 25, 100, 25);

button.setText("Click Me Now");

button.addSelectionListener(

new SelectionAdapter(){

public void widgetSelected(

SelectionEvent event) {

((Button)event.widget)

.setText("I Was Clicked");

}

});

}

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

After the tab folder is created, several tab items are added. For each tab item, the setControl()

method is used to fill its client area with a composite widget. A button widget is then added to each composite.

4.2.7

Menus

Menus provide an easy way for the user to trigger a variety of commands and actions. Top-level menus contain any number of menu item children. Useful menu APIs include the following:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 175

addHelpListener(HelpListener)

—Adds the listener to the collection of listeners that will be notified when help events are generated for the control by sending it one of the messages defined in the

HelpListener interface. addMenuListener(MenuListener)

—Adds the listener to the collection of listeners that will be notified when menus are hidden or shown by sending it one of the messages defined in the

MenuListener

interface. getItem(int)

—Returns the item at the given, zero-relative index in the receiver. getItemCount()

—Returns the number of items contained in the receiver. getItems()

—Returns an array of menu items that are the items in the receiver. getParentItem()

—Returns the receiver’s parent item, which must be a menu item or null

when the receiver is a root. getParentMenu()

—Returns the receiver’s parent item, which must be a menu or null

when the receiver is a root. indexOf(MenuItem item)

—Searches the receiver’s list starting at the first item (index 0) until an item is found that is equal to the argument and returns the index of that item. setEnabled(boolean enabled)

—Enables the receiver if the argument is true

and disables it otherwise. setVisible(boolean visible)

—Marks the receiver as visible if the argument is true

and marks it invisible otherwise.

Useful menu creation styles include:

SWT.BAR

—Creates a menu bar.

SWT.DROP_DOWN

—Creates a drop-down menu.

SWT.POP_UP

—Creates a popup menu.

Useful menu item APIs include: addArmListener(ArmListener)

—Adds the listener to the collection of listeners that will be notified when the

Arm

events are generated for the control by sending it one of the messages defined in the

ArmListener interface.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

176 CHAPTER 4 • The Standard Widget Toolkit

addHelpListener(HelpListener)

—Adds the listener to the collection of listeners that will be notified when the help events are generated for the control by sending it one of the messages defined in the

HelpListener interface. addSelectionListener(SelectionListener)

—Adds the listener to the collection of listeners that will be notified when the control is selected by sending it one of the messages defined in the

SelectionListener interface. getParent()

—Returns the receiver’s parent, which must be a menu. getSelection()

—Returns true

if the receiver is selected and false otherwise. isEnabled()

—Returns true

if the receiver is enabled and all the receiver’s ancestors are enabled and false

otherwise. setAccelerator(int accelerator)

—Sets the widget accelerator. setEnabled(boolean enabled)

—Enables the receiver if the argument is true

and disables it otherwise. setImage(Image)

—Sets the image the receiver will display to the argument. setMenu(Menu)

—Sets the receiver’s pull-down menu to the argument. setSelection(boolean)

—Sets the selection state of the receiver. setText(String)

—Sets the receiver’s text.

Useful menu item creation styles include:

SWT.CHECK

—Creates a check menu that toggles on and off.

SWT.CASCADE

—Creates a cascade menu with a submenu.

SWT.PUSH

—Creates a standard menu item.

SWT.RADIO

—Creates a radio button menu.

SWT.SEPARATOR

—Creates a menu item separator.

The following example creates a menu bar with a single menu containing two menu items and a separator (see Figure 4–14).

Figure 4–14

Menu example.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.2

SWT Widgets 177

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.widgets.*; public class MenuExample {

public static void main(String[] args) {

Display display = new Display();

final Shell shell = new Shell(display);

shell.setText("Menu Example");

shell.setBounds(100, 100, 200, 100);

Menu bar = new Menu(shell, SWT.BAR);

shell.setMenuBar(bar);

MenuItem fileMenu = new MenuItem(bar, SWT.CASCADE);

fileMenu.setText("&File");

Menu subMenu = new Menu(shell, SWT.DROP_DOWN);

fileMenu.setMenu(subMenu);

MenuItem selectItem = new MenuItem(

subMenu, SWT.NULL);

selectItem.setText("&Select Me Now\tCtrl+S");

selectItem.setAccelerator(SWT.CTRL + ‘S’);

selectItem.addSelectionListener(

new SelectionAdapter() {

public void widgetSelected(SelectionEvent event) {

System.out.println("I was selected!");

}

});

MenuItem sep = new MenuItem(subMenu, SWT.SEPARATOR);

MenuItem exitItem = new MenuItem(subMenu, SWT.NULL);

exitItem.setText("&Exit");

exitItem.addSelectionListener(new SelectionAdapter(){

public void widgetSelected(SelectionEvent event) {

shell.dispose();

}

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

A menu widget is created as a child of the shell and set as the menu bar for the shell using the setMenuBar()

method. Next, a cascade menu item is created as the parent for the

File

menu. A drop-down menu is then created as a child of the shell and associated with the

File

menu using the setMenu() method. Three menu items are then created as children of the drop-down menu (the second as a separator using the

SWT.SEPARATOR

creation style). The text of a menu item is set using the setText()

method and the accelerator is set using the setAccelerator()

method. To add behavior to the menu item, a selection listener is added in which a

SelectionAdapter

is created that overrides the widgetSelected()

method.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

178 CHAPTER 4 • The Standard Widget Toolkit

4.3

4.2.8

Additional widgets

Many other widgets are provided with SWT in addition to the ones mentioned earlier. SWT supports a wide range of native widgets as well as custom, emulated widgets.

Many additional native widgets are available. ToolBars and CoolBars provide tool bars containing buttons and other widgets; ExpandBars implement an expandable and collapsable drawer metaphor; Spinners, Scales and

Sliders provide a convenient way to select a number from a range; Progress-

Bars show incremental progress within a task; DateTimes allow the user to choose a date; Browsers provide a means of displaying HTML content or a web page; Links implement a simple hyperlink to another window or dialog;

Canvases provide an empty drawing region suitable for drawing arbitrary graphics or implementing custom widgets; and SashForms provide a container where each child is separated from its neighbor by a moveable sash.

Many custom, emulated widgets are available as well. CLabels support text, images, various border styles and gradient color fills; CCombo is a more flexible version of the standard Combo widgets; CTabFolders are used to implement the tabbed elements of the Eclipse interface and support various styles and gradient color fills; and StylesTexts display lines of fully styled

(bold, italic, underlined, colored, etc.) text.

The Eclipse Nebula project (

www.eclipse.org/nebula

) is an additional source for custom SWT widgets and other UI components. Currently available widgets include Grid, CDateTime, CTableTree, CompositeTable, PGroup,

PShelf, Gallery, FormattedText, DateChooser, CollapsibleButtons, Calendar-

Combo, and GanttChart.

Layout Management

In each of the examples presented in the previous section, the widget layouts are very simple. Widgets were either positioned relative to their parents using the setBounds()

method ( null

layout) or they were designed to fill their parent entirely using a

FillLayout

. Eclipse provides several more powerful layout management algorithms that can be used to aesthetically place widgets under a variety of conditions.

Most layout managers in Eclipse trace their heritage to VisualAge for

Smalltalk, and in particular, to the layout managers used to construct the wizards and dialogs in VisualAge for Java. As such, they were well thought out and thoroughly tested before ever being converted into Java as part of the

Eclipse framework. Interestingly enough, the newest Eclipse layout manager,

FormLayout

, is based on the oldest and most powerful VisualAge for Smalltalk layout manager.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.3

Layout Management 179

4.3.1

FillLayout

As you have seen,

FillLayout

provides an easy way for a widget (e.g., a list or a table) to completely fill its parent (see Figure 4–5 or 4–6 for an example).

FillLayout

does more than this, however, because it provides a way to lay out a group of widgets in a single row or column such that each widget is the same size as all the other widgets in the group (see Figure 4–8 for an example).

The width and height of each widget matches the width and height of the widest and tallest widget in the group, and no options are provided to control the widget spacing, margins, or wrapping.

FillLayout

defines only this one significant attribute: type

—Determines the orientation of the layout. Valid values are

SWT.HORIZONTAL

(the default) and

SWT.VERTICAL

.

FillLayout

is ideal for creating a uniform row or column of widgets such as those found in a simple toolbar. The following example creates a row of buttons that are all the same size (see Figure 4–15).

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class FillLayoutExample {

public static void main(String[] args) {

Button button;

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("FillLayout Example");

shell.setBounds(100, 100, 400, 75);

shell.setLayout(new FillLayout());

for (int i = 1; i <= 8; i++) {

button = new Button(shell, SWT.PUSH);

button.setText("B" + i);

button.addSelectionListener(

new SelectionAdapter() {

public void widgetSelected(

SelectionEvent event) {

System.out.println(

((Button)event.widget).getText() +

" was clicked!");

}

});

}

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

180 CHAPTER 4 • The Standard Widget Toolkit

Figure 4–15

FillLayout example.

By default,

FillLayout

is oriented horizontally. When buttons are added to the shell, they line up left to right with uniform widths and heights.

4.3.2

RowLayout

RowLayout

is very similar to

FillLayout

in that it lays out widgets in columns or rows and has numerous additional options to control the layout. The spacing between widgets, as well as the margins between the widgets and the parent container, can be controlled. The widgets can be wrapped into multiple rows or columns or packed such that each widget will be the same size.

Row-

Layout

defines several significant attributes: justify

—Specifies whether the controls in a row should be fully justified, with any extra space placed between the controls.

marginBottom

—Specifies the number of pixels of vertical margin that will be placed along the bottom edge of the layout. The default value is

3

.

marginLeft

—Specifies the number of pixels of horizontal margin that will be placed along the left edge of the layout. The default value is

3

.

marginRight

—Specifies the number of pixels of horizontal margin that will be placed along the right edge of the layout. The default value is

3

.

marginTop

—Specifies the number of pixels of vertical margin that will be placed along the top edge of the layout. The default value is

3

.

pack

—Specifies whether all controls in the layout take their preferred size. If pack

is false

, all controls will have the same size, which is the size required to accommodate the largest preferred height and width of all controls in the layout.

spacing

—Specifies the number of pixels between the edge of one cell and the edge of its neighboring cell. The default value is

3

.

type

—Determines the orientation of the layout. Valid values are

SWT.HORIZONTAL

(the default) and

SWT.VERTICAL

.

wrap

—Specifies whether a control will be wrapped to the next row if there is insufficient space on the current row.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.3

Layout Management 181

The width and height of each widget in the layout can be controlled by using a

RowData

object, which can be assigned to widgets with the setLayoutData()

method.

RowData

objects have two significant attributes: width

—Specifies the width of the cell in pixels.

height

—Specifies the height of the cell in pixels.

The following example creates a row layout with 20 evenly spaced buttons inset from the edge of the window frame. Depending on the size and shape of the parent shell, the line of buttons wraps into one or more rows (see

Figure 4–16).

import

org.eclipse.swt.*;

import

org.eclipse.swt.events.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class RowLayoutExample {

public static void main(String[] args) {

Button button;

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("RowLayout Example");

shell.setBounds(100, 100, 400, 100);

RowLayout layout = new RowLayout();

layout.marginLeft = 10;

layout.marginRight = 10;

layout.marginTop = 10;

layout.marginBottom = 10;

layout.spacing = 10;

shell.setLayout(layout);

for (int i = 1; i <= 20; i++) {

button = new Button(shell, SWT.PUSH);

button.setText("B" + i);

button.addSelectionListener(

new SelectionAdapter() {

public void widgetSelected(

SelectionEvent event) {

System.out.println(

((Button)event.widget).getText() +

" was clicked!");

}

});

}

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

182

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 4 • The Standard Widget Toolkit

Figure 4–16

RowLayout example.

By default,

RowLayout

is oriented horizontally. The margin spacing between the buttons and the parent shell is set using the four margin attributes: marginLeft

, marginRight

, marginTop

, and marginBottom

. The spacing between widgets is set using the spacing

attribute. After all the attributes have been set, the layout is assigned to the shell using the setLayout()

method.

4.3.3

GridLayout

Most dialogs, wizards, and preference pages are laid out using

GridLayout

.

It is both one of Eclipse’s most frequently used layout classes and one of the most complicated.

GridLayout

arranges its children in a highly configurable grid of rows and columns, where many options are provided to control the sizing behavior of each child element.

GridLayout

defines the following significant attributes.

horizontalSpacing

—Specifies the number of pixels between the right edge of one cell and the left edge of its neighboring cell. The default value is

5

.

makeColumnsEqualWidth

—Specifies whether all columns should be forced to the same width. The default is false

.

marginWidth

—Specifies the number of pixels used for the margin on the right and the left edge of the grid. The default value is

5

.

marginHeight

—Specifies the number of pixels used for the margins on the top and bottom edge of the grid. The default value is

5

.

numColumns

Specifies the number of columns that should be used to make the grid. The default value is

1

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.3

Layout Management 183

verticalSpacing

—Specifies the number of pixels between the bottom edge of one cell and the top edge of its neighboring cell. The default value is

5

.

The layout characteristics of each widget in the layout can be controlled by using a

GridData

object, which can be assigned to the widgets with the setLayoutData()

method.

GridData

objects have the following significant attributes: grabExcessHorizontalSpace

—Specifies whether a cell should grow to consume extra horizontal space in the grid. After the cell sizes in the grid are calculated based on the widgets and their grid data, any extra space remaining in the composite will be allocated to those cells that grab excess space.

grabExcessVerticalSpace

—Specifies whether a cell should grow to consume extra vertical space in the grid.

heightHint

—Specifies a minimum height for the widget (and therefore for the row that contains it).

horizontalAlignment

—Specifies the horizontal alignment of the widget within the cell. Valid values are

SWT.BEGINNING

,

SWT.CENTER

,

SWT.END

, and

SWT.FILL

.

SWT.FILL

means that the widget will be sized to consume the entire width of its grid cell.

horizontalIndent

—Specifies the number of pixels between the widget and the left edge of its grid cell. The default value is

0

.

horizontalSpan

—Specifies the number of columns in the grid that the widget should span. By default, a widget consumes one cell in the grid. It can add additional cells horizontally by increasing this value. The default value is

1

.

verticalAlignment

—Specifies the vertical alignment of the widget within the cell. Valid values are

SWT.BEGINNING

,

SWT.CENTER

,

SWT.END

, and

SWT.FILL

.

SWT.FILL

means that the widget will be sized to consume the entire height of its grid cell.

verticalSpan

—Specifies the number of rows in the grid the widget should span. By default, a widget takes up one cell in the grid. It can add additional cells vertically by increasing this value. The default value is

1

.

widthHint

—Specifies a minimum width for the widget (and therefore the column that contains it).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

184 CHAPTER 4 • The Standard Widget Toolkit

The example code that follows creates a two-column grid layout containing a two-column spanning label and two sets of labels and fields (see Figure

4–17).

import

org.eclipse.swt.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class GridLayoutExample {

public static void main(String[] args) {

Label label;

Text text;

GridData gridData;

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("GridLayout Example");

shell.setBounds(100, 100, 200, 100);

GridLayout layout = new GridLayout();

layout.numColumns = 2;

shell.setLayout(layout);

label = new Label(shell, SWT.LEFT);

label.setText("Enter your first and last name");

gridData = new GridData();

gridData.horizontalSpan = 2;

label.setLayoutData(gridData);

label = new Label(shell, SWT.LEFT);

label.setText("First:");

text = new Text(shell, SWT.SINGLE | SWT.BORDER);

gridData = new GridData();

gridData.horizontalAlignment = GridData.FILL;

gridData.grabExcessHorizontalSpace = true;

text.setLayoutData(gridData);

label = new Label(shell, SWT.LEFT);

label.setText("Last:");

text = new Text(shell, SWT.SINGLE | SWT.BORDER);

gridData = new GridData();

gridData.horizontalAlignment = GridData.FILL;

gridData.grabExcessHorizontalSpace = true;

text.setLayoutData(gridData);

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

4.3

Layout Management

5HWXUQWR7DEOHRI&RQWHQWV

185

Figure 4–17

GridLayout example.

The numColumn

attribute specifies that the

GridLayout

should have two columns. The horizontalSpan

attribute of the

GridData

object created for the first label specifies that it should span both columns. The

GridData objects created have horizontalAlignment

attributes that specify that each text field should fill the entire cell and grabExcessHorizontalSpace attributes that specify that each field should grab any horizontal space that is left over.

4.3.4

FormLayout

Nowhere does Eclipse show its VisualAge for Smalltalk roots more than in the

FormLayout

class that implements an attachment-based layout manager.

FormLayout

is the most powerful Eclipse layout manager and is a close replica of the layout management system first used in VisualAge for Smalltalk more than a decade earlier.

With attachment-based layout, you have independent control over the sizing behavior of each of the four sides of a widget. The top, bottom, left, and right sides can be independently attached to the sides of the parent container or the sides of any sibling widget within the same container using either fixed or relative offsets. This proves to be surprisingly powerful and can be used to emulate almost any of the other layout managers.

The

FormLayout

class is very simple and only specifies the margins of the container. The real power is in the

FormData

object, which holds up to four different

FormAttachment

objects (one for each side).

FormLayout

defines two significant attributes: marginWidth

—Specifies the number of pixels of horizontal margin that will be placed along the left and right edges of the layout.

marginHeight

—Specifies the number of pixels of vertical margin that will be placed along the top and bottom edges of the layout.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

186 CHAPTER 4 • The Standard Widget Toolkit

FormData

specifies several significant attributes: top

—Specifies the attachment for the top side of the control.

bottom

—Specifies the attachment for the bottom side of the control.

left

—Specifies the attachment for the left side of the control.

right

—Specifies the attachment for the right side of the control.

width

—Specifies the preferred width in pixels of the control in the form.

height

—Specifies the preferred height in pixels of the control in the form.

FormAttachment

specifies several significant attributes: alignment

—Specifies the alignment of the control side attached to a control.

SWT.DEFAULT

indicates that the widget should be attached to the adjacent side of the specified control. For top and bottom attachments,

SWT.TOP

,

SWT.BOTTOM

, and

SWT.CENTER

are used to indicate attachment of the specified side of the widget to the specified side of the control. For left and right attachments,

SWT.LEFT

,

SWT.RIGHT

, and

SWT.CENTER

are used to indicate attachment of the specified side of the widget to the specified side of the control. For example, using

SWT.TOP

indicates that the top side of the attachment’s widget should be attached to the top side of the specified control.

control

—Specifies the target control to which the attachment’s widget is attached.

denominator

—Specifies the denominator of the “

a

” term in the equation

y = ax + b

, which defines the attachment.

numerator

—Specifies the numerator of the “

a

” term in the equation

y = ax + b

, which defines the attachment.

offset

—Specifies the offset in pixels of the control side from the attachment position; can be positive or negative. This is the “

b

” term in the equation

y = ax + b

, which defines the attachment.

The following example creates a simple form layout with two buttons in the lower right corner and a text field that fills the remaining space (see Figure

4–18 for a sketch of the window next to two examples of the running window at different sizes). The

Cancel

button is attached to the lower right corner while the

OK

button is attached to the bottom side of the window and to the

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.3

Layout Management 187

left side of the

Cancel

button. The text field is attached to the top, left, and right sides of the window and to the top of the

Cancel

button.

import

org.eclipse.swt.*;

import

org.eclipse.swt.layout.*;

import

org.eclipse.swt.widgets.*; public class FormLayoutExample {

public static void main(String[] args) {

FormData formData;

Display display = new Display();

final Shell shell = new Shell(display);

shell.setText("FormLayout Example");

shell.setBounds(100, 100, 220, 180);

shell.setLayout(new FormLayout());

Button cancelButton = new Button(shell, SWT.PUSH);

cancelButton.setText("Cancel");

formData = new FormData();

formData.right = new FormAttachment(100,-5);

formData.bottom = new FormAttachment(100,-5);

cancelButton.setLayoutData(formData);

Button okButton = new Button(shell, SWT.PUSH);

okButton.setText("OK");

formData = new FormData();

formData.right = new FormAttachment(cancelButton,-5);

formData.bottom = new FormAttachment(100,-5);

okButton.setLayoutData(formData);

Text text = new Text(shell, SWT.MULTI | SWT.BORDER);

formData = new FormData();

formData.top = new FormAttachment(0,5);

formData.bottom = new FormAttachment(

cancelButton,-5);

formData.left = new FormAttachment(0,5);

formData.right = new FormAttachment(100,-5);

text.setLayoutData(formData);

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

188

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 4 • The Standard Widget Toolkit

Figure 4–18

FormLayout example.

The

FormData

assigned to the

Cancel

button has a right

and bottom attachment to the lower right corner of the shell. The first argument to each

FormAttachment

object is the percentage of the shell to attach initially

(starting in the upper left corner with a 0% value). The value of 100 specifies the right and bottom sides, which are opposite the left and top sides.

The second argument represents the fixed offset from the attachment point (with positive values pointing right and down). The value of

-5

indicates that the widget should be offset 5 pixels from the bottom and right sides.

Note that the left and top attachments are not specified. Leaving them blank will cause the widget to assume its preferred width and height.

The

OK

button is also attached to the bottom of the shell. Its right side is attached to the left side of the

Cancel

button rather than to the shell itself. This provides a way for the

OK

button to position itself relative to the preferred size of the

Cancel

button. This pattern can be particularly effective for internationalized applications where the text of the buttons (and thus their preferred sizes) is not known at design time.

Finally, the text field is attached with a fixed offset of

5

pixels from the left, right, and top sides of the shell. The bottom of the text field is attached with a

5

-pixel offset to the top of the

Cancel

button.

4.4

Resource Management

Consistent with the design of the rest of SWT, colors, fonts, and images are also thin wrappers around their platform counterparts that must be explicitly destroyed when no longer needed.

The basic rule is: If you access a color, font, or image from somewhere else, you don’t need to worry about it. On the other hand, if you create the resource, then you must destroy it when you are done with it. For any

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.4

Resource Management 189

resources that you anticipate routinely accessing within your application, consider creating a resource manager to manage them and then destroy all the resources when your application exits.

4.4.1

Colors

Colors are created for a specific device (which can be null

, representing the default device) and are described by three integer values representing each color component (red, green, and blue) in the range of 0 to 255 (e.g., new

Color(null, 255, 0, 0)

creates the color red). The foreground and background colors of widgets can be set using the setForeground()

and set-

Background()

methods, respectively.

To use one of the colors predefined by the platform, such as window background color or button background color, you can use the

Display.getSystemColor(int)

method, which takes the identifier of the desired color as an argument. You don’t need to dispose of any colors that you get this way.

4.4.2

Fonts

As with colors, fonts are also created for a specific device and are described by a font name (e.g., Arial, Times, etc.), a height in points, and a style (and combination of

SWT.NORMAL

,

SWT.BOLD

, or

SWT.ITALIC

). Fonts can be either created by specifying the name, height, and style directly or by referencing a

FontData

object that encodes those three values. For example, new

Font(null, "Arial", 10, SWT.BOLD)

creates a 10-point, bold Arial font.

A widget’s font can be set using the setFont()

method.

4.4.3

Images

Images are frequently used in toolbars, buttons, labels, trees, and tables.

Eclipse supports loading and saving images in a variety of common file formats such as GIF, JPEG, PNG, BMP (Windows bitmap), and ICO (Windows icon). Some formats, such as GIF, support transparency, which makes them ideal for use in toolbars and as item decorators in lists and tables.

Images are created for a specific device and are usually either loaded from a specific file or created from a device-independent

ImageData

object. For example, both of the following are equivalent:

Image img = new Image(null, "c:\\my_button.gif")

ImageData data = new ImageData("c:\\my_button.gif");

Image img = new Image(null, data);

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

190 CHAPTER 4 • The Standard Widget Toolkit

4.5

On widgets that support images as part of their content, such as labels and buttons, use the setImage()

method to set the widget’s images. For information on image caching and

ImageDescriptor

, see Section 7.7, Image Caching, on page 346.

GUI Builders

Creating complex user interfaces by hand can be a very challenging task. This is just as true for SWT as it is for Swing. Graphical user interface (GUI) builders can dramatically reduce the amount of time needed to create the various user interface elements (views, editors, perspectives, preference pages, etc.) needed for most Eclipse plug-ins.

With a GUI builder, you can create complicated windows very quickly and the Java code will be generated for you. You can easily add widgets using drag-and-drop, add event handlers to your widgets, change various properties of widgets using a property editor, internationalize your app and more.

To be truly useful for Eclipse plug-in development, a GUI builder needs to support all of the widgets, layout managers and user interface elements supported by SWT, JFace, and RCP. In addition, a GUI builder should also be bi-directional, refactoring friendly, and have the ability to reverse-engineer hand-written Java GUI code.

Figure 4–19

WindowBuilder Pro.

While a couple of GUI builders are available for Eclipse, the only one that satisfies all of these criteria is Instantiations’

WindowBuilder Pro

(see Figure

4–19). WindowBuilder is a commerical add-on to Eclipse (and various

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

4.6

Summary 191

Eclipse-based IDEs such as MyEclipse and JBuilder) which is available for free to developers working on open-source projects. It builds an abstract syntax tree (AST) to navigate the source code and uses the Graphical Editor Framework (GEF) to display and manage the visual presentation. WindowBuilder can read and write almost any format and supports free form code editing

(make changes anywhere—not just in special areas) and most user refactorings (move, rename, and subdivide methods without a problem).

4.6

Summary

SWT is a well-designed native UI library for Java that is based on a long history of similar work done by IBM and OTI over the years. It is the native UI library of Eclipse itself and will be used extensively in any Eclipse plug-in that you create. SWT is also more than powerful enough to be used for creating standalone Java applications that don’t require any of the other Eclipse frameworks.

SWT includes a rich collection of built-in widgets that are mapped to native-platform widgets whenever possible and are emulated when an appropriate widget is not present on a specific platform. SWT also includes a wide array of layout management classes ranging from the simple

FillLayout

to the more complex

GridLayout

and

FormLayout

. With these widgets and layout managers, you can create any user interface that you want to use for your plug-in.

References

Chapter source (see Section 2.9, Book Samples, on page 105).

Eclipse SWT (

http://www.eclipse.org/swt

)

and Eclipse SWT snippets (

http://www.eclipse.org/swt/snippets

)

Northover, Steve, and Mike Wilson,

SWT: The Standard Widget Toolkit

.

Addison-Wesley, Boston, 2004.

Harris, Robert, and Rob Warner,

The Definitive Guide to SWT and JFACE.

Apress, Berkeley, CA, 2004.

Holder, Stephen, Stanford Ng, and Laurent Mihalkovic,

SWT/JFace in Action:

GUI Design with Eclipse 3.0

. Manning Publications, Greenwich, CT, 2004.

Cornu, Christophe, “A Small Cup of SWT,” IBM OTI Labs, September 19,

2003 (

www.eclipse.org/articles/Article-small-cup-of-swt/pocket-PC.html

).

SWT Graph (

http://swtgraph.sourceforge.net/examples.php

)

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

192 CHAPTER 4 • The Standard Widget Toolkit

Winchester, Joe, “Taking a Look at SWT Images,” IBM, September 10, 2003

(

www.eclipse.org/articles/Article-SWT-images/graphics-resources.html

).

Irvine, Veronika, “Drag and Drop—Adding Drag and Drop to an SWT Application,” IBM, August 25, 2003 (

www.eclipse.org/articles/Article-SWT-

DND/DND-in-SWT.html

).

Arthorne, John, “Drag and Drop in the Eclipse UI,” IBM, August 25, 2003

(

www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html

).

Bordeau, Eric, “Using Native Drag and Drop with GEF,” IBM, August 25,

2003 (

www.eclipse.org/articles/Article-GEF-dnd/GEF-dnd.html).

Savarese, Daniel F., “Eclipse vs. Swing,” JavaPro, December 2002

(

www.ftponline.com/javapro/2002_12/magazine/columns/proshop/default_ pf.aspx

).

Majewski, Bo, “Using OpenGL with SWT,” Cisco Systems, Inc., April 15,

2005 (

www.eclipse.org/articles/Article-SWT-OpenGL/opengl.html

).

Kues, Lynne, and Knut Radloff, “Getting Your Feet Wet with the SWT Styled-

Text Widget,” OTI, July 19, 2004 (

www.eclipse.org/articles/StyledText%201/ article1.html

).

Kues, Lynne, and Knut Radloff, “Into the Deep End of the SWT StyledText

Widget,” OTI, September 18, 2002 (

www.eclipse.org/articles/Styled-

Text%202/article2.html

).

Li, Chengdong, “A Basic Image Viewer,” University of Kentucky, March 15,

2004 (

www.eclipse.org/articles/Article-Image-Viewer/Image_viewer.html

).

MacLeod, Carolyn, and Shantha Ramachandran, “Understanding Layouts in

SWT,” OTI, May 2, 2002 (

www.eclipse.org/articles/Understanding%20

Layouts/Understanding%20Layouts.htm

).

Northover, Steve, “SWT: The Standard Widget Toolkit—PART 1: Implementation Strategy for Java™ Natives,” OTI, March 22, 2001 (

www.eclipse.org/ articles/Article-SWT-Design-1/SWT-Design-

1.html).

MacLeod, Carolyn, and Steve Northover, “SWT: The Standard Widget Toolkit—PART 2: Managing Operating System Resources,” OTI, November 27,

2001 (

www.eclipse.org/articles/swt-design-2/swt-design-2.html

).

Moody, James, and Carolyn MacLeod, “SWT Color Model,” OTI, April 24,

2001 (

www.eclipse.org/articles/Article-SWT-Color-Model/swt-colormodel.htm

).

Irvine, Veronika, “ActiveX Support In SWT: How Do I Include an OLE Document or ActiveX Control in My Eclipse Plug-in?,” OTI, March 22, 2001

(

www.eclipse.org/articles/Article-ActiveX%20Support%20in%20SWT/

ActiveX%20Support%20in%20SWT.html

).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 5

JFace Viewers

Although SWT provides a direct interface to the native platform widgets, it is limited to using simple data types—primarily strings, numbers, and images.

This is fine for a large number of applications, but it represents a severe impedance mismatch when dealing with object-oriented (OO) data that needs to be presented in lists, tables, trees, and text widgets. This is where JFace viewers step in to provide OO wrappers around their associated SWT widgets.

5.1

List-Oriented Viewers

JFace list viewers, such as

ListViewer

,

TableViewer

, and

TreeViewer

, allow you to directly use your domain model objects (e.g., business objects such as

Company, Person, Department, etc.) without needing to manually decompose them into their basic string, numerical, and image elements. The viewers do this by providing adapter interfaces for such things as retrieving an item’s label

(both image and text), for accessing an item’s children (in the case of a tree), for selecting an item from a list, for sorting items in the list, for filtering items in the list, and for converting an arbitrary input into a list suitable for the underlying SWT widget (see Figure 5–1).

193

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

194

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 5 • JFace Viewers

Figure 5–1

Relationship between viewers and adapters.

5.1.1

Label providers

A label provider is one of the most common adapter types used in list viewers.

This provider is used to map a domain model object into one or more images and text strings displayable in the viewer’s widget.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5.1

List-Oriented Viewers

5HWXUQWR7DEOHRI&RQWHQWV

195

Figure 5–2

LabelProvider hierarchy.

The two most common types of label providers are

ILabelProvider

(see

Figure 5–2), used in lists and trees, and

ITableLabelProvider

(see Figure 5–

3), used in tables. The former maps an item into a single image and text label while the latter maps an item into multiple images and text labels (one set for each column in the table). A label provider is associated with a viewer using the setLabelProvider()

method.

Useful APIs defined by

ILabelProvider

include: getImage(Object)

—Returns the image for the label of the given element.

getText(Object)

—Returns the text for the label of the given element.

Useful APIs defined by

ITableLabelProvider

include: getColumnImage(Object, int)

—Returns the label image for the given column of the given element.

getColumnText(Object, int)

—Returns the label text for the given column of the given element.

For an example of label providers, see Section 5.1.6, ListViewer class, on page 200.

Figure 5–3

TableLabelProvider hierarchy.

5.1.2

Content providers

A content provider is another common adapter type used in list viewers. This provider is used to map between a domain model object or a collection of

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

196 CHAPTER 5 • JFace Viewers

domain model objects used as the input to the viewer and the internal list structure needed by the viewer itself.

The two most common types of content providers are

IStructuredContentProvider

, used in lists and tables, and

ITreeContentProvider

, used in trees (see Figure 5–4). The former maps a domain model input into an array while the latter adds support for retrieving an item’s parent or children within a tree. A content provider is associated with a viewer using the setContent-

Provider()

method. A domain model input is associated with a viewer using the setInput()

method.

Figure 5–4

ContentProvider hierarchy.

Useful APIs defined by

IStructuredContentProvider

include: getElements(Object)

—Returns the elements to display in the viewer when its input is set to the given element.

inputChanged(Viewer, Object, Object)

—Notifies this content provider that the given viewer’s input has been switched to a different element.

Useful APIs added by

ITreeContentProvider

include:

Object[] getChildren(Object)

—Returns the child elements of the given parent element. The difference between this method and the previously listed getElements(Object)

method is that it is called to obtain the tree viewer’s root elements, whereas getChildren(Object) is used to obtain the children of a given parent element in the tree

(including a root).

getParent(Object)

—Returns either the parent for the given element or null

, indicating that the parent can’t be computed.

hasChildren(Object)

—Returns whether the given element has children.

For an example of content providers, see Section 5.1.6, ListViewer class, on page 200.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

5.1

List-Oriented Viewers 197

5.1.3

Viewer sorters

A viewer sorter (see Figure 5–5 for the

ViewerSorter

hierarchy) is used to sort the elements provided by the content provider (see Figure 5–1). If a viewer does not have a viewer sorter, the elements are shown in the order returned by the content provider. A viewer sorter is associated with a viewer using the setSorter()

method.

Figure 5–5

ViewerSorter hierarchy.

The default sorting algorithm uses a two-step process. First, it groups elements into categories (ranked

0

through

n

); and second, it sorts each category based on the text labels returned by the label provider. By default, all items are in the same category, so all the items are sorted relative to their text labels.

Your application can override the default categorization as well as the default comparison routine to use some criteria other than the item’s text label.

Useful APIs defined by

ViewerSorter

include: category(Object)

—Returns the category of the given element. compare(Viewer, Object, Object)

—Returns a negative, zero, or positive number depending on whether the first element is less than, equal to, or greater than the second element. getCollator()

—Returns the collator used to sort strings. isSorterProperty(Object, String)

—Returns whether this viewer sorter would be affected by a change to the given property of the given element. sort(Viewer viewer, Object[])

—Sorts the given elements in place, modifying the given array.

For an example of viewer sorters, see Section 5.1.6, ListViewer class, on page 200.

5.1.4

Viewer filters

A viewer filter (see Figure 5–6 for the

ViewerFilter

hierarchy) is used to display a subset of the elements provided by the content provider (see Figure

5–1). If a view does not have a viewer filter, all the elements are displayed. A viewer filter is associated with a viewer using the setFilter()

method.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

198

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 5 • JFace Viewers

Figure 5–6

ViewerFilter hierarchy.

Useful APIs defined by

ViewFilter

are listed next. Simple viewer filters need only to override the select(Viewer, Object, Object)

method to determine whether an object should be visible in the viewer. filter(Viewer, Object, Object[])

—Filters the given elements for the given viewer. The default implementation of this method calls the following select(Viewer, Object, Object)

method.

isFilterProperty(Object, String)

—Returns whether this viewer filter would be affected by a change to the given property of the given element. The default implementation of this method returns false

.

select(Viewer, Object, Object)

—Returns whether the given element makes it through this filter.

5.1.5

StructuredViewer class

The

StructuredViewer

class is the abstract superclass of list viewers, table viewers, and tree viewers (see Figure 5–7).

Figure 5–7

StructuredViewer hierarchy.

It defines a large number of useful APIs that are common to each class.

addDoubleClickListener(IDoubleClickListener)

—Adds a listener for double-clicks in this viewer. addDragSupport(int, Transfer[], DragSourceListener)

—Adds support for dragging items out of this viewer via a user drag-and-drop operation.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

5.1

List-Oriented Viewers 199

addDropSupport(int, Transfer[], DropTargetListener)

—Adds support for dropping items into this viewer via a user drag-and-drop operation. addFilter(ViewerFilter)

—Adds the given filter to this viewer and triggers refiltering and resorting of the elements. addHelpListener(HelpListener)

—Adds a listener for help requests in this viewer. addOpenListener(IOpenListener)

—Adds a listener for selection open in this viewer. addSelectionChangedListener(ISelectionChangedListener)

Adds a listener for selection changes in this selection provider. addPostSelectionChangedListener(ISelectionChangedListener)

—Adds a listener for post-selection in this viewer. getSelection()

—The

StructuredViewer

implementation of this method returns the result as an

IStructuredSelection

.

refresh()

—Refreshes this viewer completely with information freshly obtained from this viewer’s model. refresh(boolean)

—Refreshes this viewer with information freshly obtained from this viewer’s model. refresh(Object)

—Refreshes this viewer starting with the given element. refresh(Object, boolean)

—Refreshes this viewer starting with the given element. resetFilters()

—Discards this viewer’s filters and triggers refiltering and resorting of the elements. setComparer(IElementComparer)

—Sets the comparator to use for comparing elements, or null

to use the default equals

and hashCode methods on the elements themselves. setContentProvider(IContentProvider)

—The implementation,

StructuredViewer

, of this method checks to ensure that the content provider is an

IStructuredContentProvider

.

setData(String, Object)

—Sets the value of the property with the given name to the given value, or to null

if the property is to be removed. setInput(Object)

—The

ContentViewer

implementation of this viewer method invokes inputChanged

on the content provider and then the inputChanged

hook method. The content provider’s getElements(Object) method is called later with this input object as its argument to determine the root-level elements in the viewer.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

200 CHAPTER 5 • JFace Viewers

setSelection(ISelection, boolean)

—The

StructuredViewer implementation of this method updates the current viewer selection based on the specified selection.

setSorter(ViewerSorter)

—Sets this viewer’s sorter and triggers refiltering and resorting of this viewer’s element. setUseHashlookup(boolean)

—Configures whether this structured viewer uses an internal hash table to speed up the mapping between elements and SWT items. update(Object[], String[])

—Updates the given element’s presentation when one or more of its properties changes. update(Object, String[])

—Updates the given element’s presentation when one or more of its properties changes.

5.1.6

ListViewer class

The

ListViewer

class wraps the

List

widget and is used to view a collection of objects rather than a flat collection of strings. A list viewer needs to be configured with label and content providers. Useful APIs include: add(Object)

—Adds the given element to this list viewer. add(Object[])

—Adds the given elements to this list viewer. getControl()

—Returns the primary control associated with this viewer. getElementAt(int)

—Returns the element with the given index from this list viewer. getList()

—Returns this list viewer’s list control. remove(Object)

—Removes the given element from this list viewer. remove(Object[])

—Removes the given elements from this list viewer. reveal(Object)

—Ensures that the given element is visible, scrolling the viewer if necessary. setLabelProvider(IBaseLabelProvider)

—The list viewer implementation of this

Viewer

framework method ensures that the given label provider is an instance of

ILabelProvider

.

The

Person

domain model class for the next few examples looks like the following.

public class Person {

public String firstName = "John";

public String lastName = "Doe";

public int age = 37;

public Person[] children = new Person[0];

public Person parent = null;

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

5.1

List-Oriented Viewers 201

public Person(String firstName, String lastName,

int age) {

this.firstName = firstName;

this.lastName = lastName;

this.age = age;

}

public Person(String firstName, String lastName,

int age, Person[] children) {

this(firstName, lastName, age);

this.children = children;

for (int i = 0; i < children.length; i++) {

children[i].parent = this;

}

}

public static Person[] example() {

return new Person[] {

new Person("Dan", "Rubel", 41, new Person[] {

new Person("Beth", "Rubel", 11),

new Person("David", "Rubel", 6)}),

new Person("Eric", "Clayberg", 42, new Person[] {

new Person("Lauren", "Clayberg", 9),

new Person("Lee", "Clayberg", 7)}),

new Person("Mike", "Taylor", 55)

};

}

public String toString() {

return firstName + " " + lastName;

}

}

The example code that follows creates a list viewer with a label provider, content provider, and viewer sorter (see Figure 5–8). Note: To run the JFace demos standalone, you need to add the following entries to your

Java Build

Path

(plug-in version numbers should match those used in your Eclipse installation).

ECLIPSE_HOME/plugins/org.eclipse.equinox.common_3.X.X.vXXXXXXXX.jar

ECLIPSE_HOME/plugins/org.eclipse.core.runtime_3.X.X.vXXXXXXXX.jar

ECLIPSE_HOME/plugins/org.eclipse.core.commands.X.X.vXXXXXXXX.jar

ECLIPSE_HOME/plugins/org.eclipse.jface_3.X.X.vXXXXXXXX.jar

ECLIPSE_HOME/plugins/org.eclipse.jface.text_3.X.X.vXXXXXXXX.jar

ECLIPSE_HOME/plugins/org.eclipse.text_3.X.X.vXXXXXXXX.jar

Figure 5–8

ListViewer example.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

202 CHAPTER 5 • JFace Viewers

import org.eclipse.jface.viewers.*; import org.eclipse.swt.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; public class ListViewerExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("List Viewer Example");

shell.setBounds(100, 100, 200, 100);

shell.setLayout(new FillLayout());

final ListViewer listViewer =

new ListViewer(shell, SWT.SINGLE);

listViewer.setLabelProvider(

new PersonListLabelProvider());

listViewer.setContentProvider(

new ArrayContentProvider());

listViewer.setInput(Person.example());

listViewer.setSorter(new ViewerSorter() {

public int compare(

Viewer viewer, Object p1, Object p2) {

return ((Person) p1).lastName

.compareToIgnoreCase(((Person) p2).lastName);

}

});

listViewer.addSelectionChangedListener(

new ISelectionChangedListener() {

public void selectionChanged(

SelectionChangedEvent event) {

IStructuredSelection selection =

(IStructuredSelection) event.getSelection();

System.out.println("Selected: "

+ selection.getFirstElement());

}

});

listViewer.addDoubleClickListener(

new IDoubleClickListener() {

public void doubleClick(DoubleClickEvent event)

{

IStructuredSelection selection =

(IStructuredSelection) event.getSelection();

System.out.println("Double Clicked: " +

selection.getFirstElement());

}

});

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

5.1

List-Oriented Viewers 203

After the list viewer has been created, the label provider is set by using the setLabelProvider()

method and the content provider is set with the setContentProvider()

method.

PersonListLabelProvider

, the label provider, returns a text label composed of the person’s first and last names and does not return an icon. The class looks like this: public class PersonListLabelProvider extends LabelProvider {

public Image getImage(Object element) {

return null;

}

public String getText(Object element) {

Person person = (Person) element;

return person.firstName + " " + person.lastName;

}

}

For the content provider, use the built-in

ArrayContentProvider

class that maps an input collection to an array. The input object is set using the setInput()

method. The viewer sorter defines a custom compare()

method that sorts the elements based on a person’s last name. Finally, a selection-

Changed

listener and a doubleClick

listener are added that override the selectionChanged()

method and the doubleClick()

method, respectively.

5.1.7

TableViewer class

The

TableViewer

class wraps the

Table

widget. A table viewer provides an editable, vertical, multicolumn list of items, which shows a row of cells for each item in the list where each cell represents a different attribute of the item at that row. A table viewer needs to be configured with a label provider, a content provider, and a set of columns.

The

CheckboxTableViewer

enhances this further by adding support for graying out individual items and toggling on and off an associated checkbox with each item. Useful APIs include: add(Object)

—Adds the given element to this table viewer. This method should be called (by the content provider) when a single element has been added to the model to cause the viewer to accurately reflect the model. This method only affects the viewer, not the model.

add(Object[])

—Adds the given elements to this table viewer. This method should be called (by the content provider) when elements have been added to the model to cause the viewer to accurately reflect the model. This method only affects the viewer, not the model.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

204 CHAPTER 5 • JFace Viewers

cancelEditing()

—Cancels a currently active cell editor. editElement(Object, int)

—Starts editing the given element. getElementAt(int)

—Returns the element with the given index from this table viewer. getTable()

—Returns this table viewer’s table control. insert(Object, int)

—Inserts the given element into this table viewer at the given position. isCellEditorActive()

—Returns whether there is an active cell editor. remove(Object)

—Removes the given element from this table viewer.

This method should be called (by the content provider) when a single element has been removed from the model to cause the viewer to accurately reflect the model. This method only affects the viewer, not the model.

remove(Object[])

—Removes the given elements from this table viewer. This method should be called (by the content provider) when elements have been removed from the model in order to cause the viewer to accurately reflect the model. This method only affects the viewer, not the model.

reveal(Object)

—Ensures that the given element is visible, scrolling the viewer if necessary. setCellEditors(CellEditor[])

—Sets the cell editors of this table viewer. setCellModifier(ICellModifier)

—Sets the cell modifier of this table viewer. setColumnProperties(String[])

—Sets the column properties of this table viewer. setLabelProvider(IBaseLabelProvider

)—The table viewer implementation of this

Viewer

framework method ensures that the given label provider is an instance of either

ITableLabelProvider

or

ILabelProvider

.

The

CheckboxTableViewer

adds the following useful APIs: addCheckStateListener(ICheckStateListener)

—Adds a listener for changes to the checked state of elements in this viewer.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

5.1

List-Oriented Viewers 205

getChecked(Object)

—Returns the checked state of the given element. getCheckedElements()

—Returns a list of elements corresponding to checked table items in this viewer. getGrayed(Object)

—Returns the grayed state of the given element. getGrayedElements()

—Returns a list of elements corresponding to grayed nodes in this viewer. setAllChecked(boolean)

—Sets to the given value the checked state for all elements in this viewer. setAllGrayed(boolean)

—Sets to the given value the grayed state for all elements in this viewer. setChecked(Object, boolean)

—Sets the checked state for the given element in this viewer. setCheckedElements(Object[])

—Sets which nodes are checked in this viewer. setGrayed(Object, boolean)

—Sets the grayed state for the given element in this viewer. setGrayedElements(Object[])

—Sets which nodes are grayed in this viewer.

The example code that follows creates a table viewer with a label provider, content provider, and four columns (see Figure 5–9).

Figure 5–9

TableViewer example.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

206 CHAPTER 5 • JFace Viewers

import org.eclipse.jface.viewers.*; import org.eclipse.swt.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; public class TableViewerExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Table Viewer Example");

shell.setBounds(100, 100, 325, 200);

shell.setLayout(new FillLayout());

final TableViewer tableViewer = new TableViewer(

shell, SWT.SINGLE | SWT.FULL_SELECTION);

final Table table = tableViewer.getTable();

table.setHeaderVisible(true);

table.setLinesVisible(true);

String[] columnNames = new String[] {

"First Name", "Last Name", "Age", "Num Children"};

int[] columnWidths = new int[] {

100, 100, 35, 75};

int[] columnAlignments = new int[] {

SWT.LEFT, SWT.LEFT, SWT.CENTER, SWT.CENTER};

for (int i = 0; i < columnNames.length; i++) {

TableColumn tableColumn =

new TableColumn(table, columnAlignments[i]);

tableColumn.setText(columnNames[i]);

tableColumn.setWidth(columnWidths[i]);

}

tableViewer.setLabelProvider(

new PersonTableLabelProvider());

tableViewer.setContentProvider(

new ArrayContentProvider());

tableViewer.setInput(Person.example());

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

After creating the table viewer, the column headers and lines are made visible by calling the setHeaderVisible()

and setLinesVisible()

methods in the table viewer’s underlying table. Four columns are then added to the table with different alignments. The header text and width of each column are set with the setText()

and setWidth()

methods (see Section 7.8, Auto-sizing

Table Columns, on page 348).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

5.1

List-Oriented Viewers 207

The label provider is set using the setLabelProvider()

method and the content provider is set with the setContentProvider()

method. The label provider,

PersonTableLabelProvider

, returns a text label for each column in the table and does not return an icon. The class looks like this: import org.eclipse.jface.viewers.*; import org.eclipse.swt.graphics.*; public class PersonTableLabelProvider

extends LabelProvider

implements ITableLabelProvider {

public Image getColumnImage(Object element, int index) { return null;

}

public String getColumnText(Object element, int index) {

Person person = (Person) element;

switch (index) {

case 0 :

return person.firstName;

case 1 :

return person.lastName;

case 2 :

return Integer.toString(person.age);

case 3 :

return Integer.toString(person.children.length);

default :

return "unknown " + index;

}

}

}

5.1.8

TreeViewer class

The

TreeViewer

class wraps the

Tree

widget. A tree viewer displays a hierarchical list of objects in a parent–child relationship. This viewer needs to be configured with label and content providers. The

CheckboxTreeViewer enhances this further by adding support for graying out individual items and toggling on and off an associated checkbox with each item. Useful APIs include: add(Object, Object)

—Adds the given child element to this viewer as a child of the given parent element. add(Object, Object[])

—Adds the given child elements to this viewer as children of the given parent element. addTreeListener(ITreeViewerListener)

—Adds a listener for expanding and collapsing events in this viewer. collapseAll()

—Collapses all nodes of the viewer’s tree, starting with the root.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

208 CHAPTER 5 • JFace Viewers

collapseToLevel(Object, int)

—Collapses the subtree rooted at the given element to the given level. expandAll()

—Expands all nodes of the viewer’s tree, starting with the root. expandToLevel(int)

—Expands the root of the viewer’s tree to the given level. expandToLevel(Object, int)

—Expands all ancestors of the given element so that the given element becomes visible in this viewer’s tree control, and then expands the subtree rooted at the given element to the given level. getExpandedElements()

—Returns a list of elements corresponding to expanded nodes in this viewer’s tree, including currently hidden ones that are marked as expanded but are under a collapsed ancestor. getExpandedState(Object)

—Returns whether the node corresponding to the given element is expanded or collapsed.

Tree getTree()

—Returns this tree viewer’s tree control. getVisibleExpandedElements()

—Gets the expanded elements that are visible to the user. isExpandable(Object)

—Returns whether the tree node representing the given element can be expanded. remove(Object)

—Removes the given element from the viewer. remove(Object[])

—Removes the given elements from this viewer. reveal(Object)

—Ensures that the given element is visible, scrolling the viewer if necessary. scrollDown(int, int)

—Scrolls the viewer’s control down by one item from the given display-relative coordinates. scrollUp(int, int)

—Scrolls the viewer’s control up by one item from the given display-relative coordinates. setAutoExpandLevel(int)

—Sets the auto-expand level. setContentProvider(IContentProvider)

—The implementation,

AbstractTreeViewer

, of this method checks to ensure that the content provider is an

ITreeContentProvider

.

setExpandedElements(Object[])

—Sets which nodes are expanded in this viewer’s tree. setExpandedState(Object, boolean)

—Sets whether the node corresponding to the given element is expanded or collapsed.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

5.1

List-Oriented Viewers 209

setLabelProvider(IBaseLabelProvider)

—The tree viewer implementation of this

Viewer

framework method ensures that the given label provider is an instance of

ILabelProvider

.

CheckboxTreeViewer

adds the following useful APIs: addCheckStateListener(ICheckStateListener)

—Adds a listener for changes to the checked state of elements in this viewer. getChecked(Object)

—Returns the checked state of the given element. getCheckedElements()

—Returns a list of checked elements in this viewer’s tree, including currently hidden ones that are marked as checked but are under a collapsed ancestor. getGrayed(Object)

—Returns the grayed state of the given element. getGrayedElements()

—Returns a list of grayed elements in this viewer’s tree, including currently hidden ones that are marked as grayed but are under a collapsed ancestor. setChecked(Object, boolean)

—Sets the checked state for the given element in this viewer. setCheckedElements(Object[])

—Sets which elements are checked in this viewer’s tree. setGrayChecked(Object, boolean)

—Checks and grays the selection rather than calling both setGrayed

and setChecked

as an optimization. setGrayed(Object, boolean)

—Sets the grayed state for the given element in this viewer. setGrayedElements(Object[])

—Sets which elements are grayed in this viewer’s tree. setParentsGrayed(Object, boolean)

—Sets the grayed state for the given element and its parents in this viewer. setSubtreeChecked(Object, boolean)

—Sets the checked state for the given element and its visible children in this viewer.

The following example creates a tree viewer with a label provider and content provider (see Figure 5–10).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

210

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 5 • JFace Viewers

Figure 5–10

TreeViewer example.

import org.eclipse.jface.viewers.*; import org.eclipse.swt.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; public class TreeViewerExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Tree Viewer Example");

shell.setBounds(100, 100, 200, 200);

shell.setLayout(new FillLayout());

final TreeViewer treeViewer =

new TreeViewer(shell, SWT.SINGLE);

treeViewer.setLabelProvider(

new PersonListLabelProvider());

treeViewer.setContentProvider(

new PersonTreeContentProvider());

treeViewer.setInput(Person.example());

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

After creating the tree viewer, the label provider is set using the setLabelProvider()

method and the content provider with the setContentProvider()

method. The content provider,

PersonTree-

ContentProvider

, returns the parent and children of each item. The class looks like this:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

5.2

Text Viewers

import org.eclipse.jface.viewers.*; public class PersonTreeContentProvider

extends ArrayContentProvider

implements ITreeContentProvider {

public Object[] getChildren(Object parentElement) {

Person person = (Person) parentElement;

return person.children;

}

public Object getParent(Object element) {

Person person = (Person) element;

return person.parent;

}

public boolean hasChildren(Object element) {

Person person = (Person) element;

return person.children.length > 0;

}

}

211

5.2

Text Viewers

The

TextViewer

class wraps the

StyledText

widget (see Figure 5–11 for the

TextViewer

hierarchy). Individual runs of text may have different styles associated with them, including foreground color, background color, and bold. Text viewers provide a document model to the client and manage the conversion of the document to the styled text information used by the text widget.

Figure 5–11

TextViewer hierarchy.

Useful APIs include: addTextListener(ITextListener)

—Adds a text listener to this viewer. appendVerifyKeyListener(VerifyKeyListener)

—Appends a verify key listener to the viewer’s list of verify key listeners. canDoOperation(int)

—Returns whether the operation specified by the given operation code can be performed.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

212 CHAPTER 5 • JFace Viewers

changeTextPresentation(TextPresentation, boolean)

—Applies the color information encoded in the given text presentation. doOperation(int)

—Performs the operation specified by the operation code on the target. enableOperation(int, boolean)

—Enables/disables the given text operation. getSelectedRange()

—Returns the range of the current selection in coordinates of this viewer’s document. getSelection()

—Returns the current selection for this provider. getTextWidget()

—Returns the viewer’s text widget. isEditable()

—Returns whether the shown text can be manipulated. refresh()

—Refreshes this viewer completely with information freshly obtained from the viewer’s model. setDocument(IDocument)

—Sets the given document as the text viewer’s model and updates the presentation accordingly. setEditable(boolean)

—Sets the editable mode. setInput(Object)

—Sets or clears the input for this viewer. The

TextViewer implementation of this method calls setDocument(IDocument) with the input object if the input object is an instance of

IDocument or with null if the input object is not.

setRedraw(boolean)

—Enables/disables the redrawing of this text viewer. setSelectedRange(int, int)

—Sets the selection to the specified range. setSelection(ISelection, boolean)

—Sets a new selection for this viewer and optionally makes it visible. setTextColor(Color)

—Applies the given color to this viewer’s selection. setTextColor(Color, int, int, boolean)

—Applies the given color to the specified section of this viewer. setTextHover(ITextHover, String)

—Sets this viewer’s text hover for the given content type.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5.2

Text Viewers

5HWXUQWR7DEOHRI&RQWHQWV

213

Figure 5–12

TextViewer example.

The following example creates a text viewer containing styled text (see

Figure 5–12).

import org.eclipse.jface.text.*; import org.eclipse.swt.*; import org.eclipse.swt.custom.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; public class TextViewerExample {

public static void main(String[] args) {

Display display = new Display();

Shell shell = new Shell(display);

shell.setText("Text Viewer Example");

shell.setBounds(100, 100, 225, 125);

shell.setLayout(new FillLayout());

final TextViewer textViewer =

new TextViewer(shell, SWT.MULTI | SWT.V_SCROLL);

String string = "This is plain text\n"

+ "This is bold text\n"

+ "This is red text";

Document document = new Document(string);

textViewer.setDocument(document);

TextPresentation style = new TextPresentation();

style.addStyleRange(

new StyleRange(19, 17, null, null, SWT.BOLD));

Color red = new Color(null, 255, 0, 0);

style.addStyleRange(

new StyleRange(37, 16, red, null));

textViewer.changeTextPresentation(style, true);

shell.open();

while (!shell.isDisposed()) {

if (!display.readAndDispatch()) display.sleep();

}

display.dispose();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

214 CHAPTER 5 • JFace Viewers

After creating the text viewer, a

Document

object is created that holds a string of text and is then assigned to the viewer. Next, a

TextPresentation object is created to hold the style ranges. Two style ranges are added: one that sets a range of text to bold and a second that sets a range of text to the color red. The first argument to the

StyleRange

constructor is the index of the first character in the string to which the style should apply. The second argument is the number of characters that should be affected by the style. Finally, the style object is assigned to the viewer.

5.3

Summary

JFace viewers are used extensively in Eclipse plug-in development. List viewers provide OO wrappers around the basic Eclipse widgets, making it easier to directly deal with high-level domain objects rather than simple strings, numbers, and images. Likewise, text viewers make it easier to deal with text documents that require more complex text styling. Viewers are discussed in more detail in Chapter 7, Views.

References

Chapter source (see Section 2.9, “Book Samples” on page 105).

Gauthier, Laurent, “Building and Delivering a Table Editor with SWT/JFace,”

Mirasol Op’nWorks, July 3, 2003 (

www.eclipse.org/articles/Article-Tableviewer/table_viewer.html

).

Grindstaff, Chris, “How to Use the JFace Tree Viewer,” Applied Reasoning,

May 5, 2002 (

www.eclipse.org/articles/treeviewer-cg/TreeViewerArticle.htm

).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 6

Commands and Actions

Commands and actions are two different APIs for accomplishing the same thing: declaring and implementing functions that manifest as menu items and toolbar buttons. The action API has been around since before Eclipse 3.0, while the commands API has only just solidified as of Eclipse 3.3 with small refinements in Eclipse 3.4. It

appears

that at some point the actions API will be deprecated, moved into a compatibility layer and in the future removed, but much of the Eclipse tooling still makes extensive use of the actions API, not to mention third party tools and IDEs built on top of the Eclipse infrastructure.

Like everything else in Eclipse, commands and actions are defined through various extension points so that new functionality can be easily added at various points throughout the Eclipse framework. With actions, there are different extension points for each different area where one can appear within the

UI, yet no separation of presentation from implementation. In contrast, the commands API separates presentation from implementation by providing one extension point for specifying the command, another for specifying where it should appear in the UI, and a third for specifying the implementation. This, along with a richer declarative expression syntax, makes the commands API more flexible than the actions API.

The first half of this chapter discusses implementing our Favorites plug-in using the commands API while the second half has the same goal using the actions API (see Section 6.5, IAction versus IActionDelegate, on page 240). If you implement everything in the first half of the chapter using the commands

API

and

everything in the second half of the chapter using the actions API, then the resulting Favorites plug-in will have duplicate menu and toolbar items.

215

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

216 CHAPTER 6 • Commands and Actions

6.1

Commands

Declaring and implementing a menu or toolbar item using the command API involves declaring a command, at least one menu contribution for that command, and at least one handler for that command (see Figure 6–1). The command declaration is the abstract binding point associating one or more menu contributions with one or more handlers (see Figure 6–2).

Figure 6–1

Command overview

The first step is to declare the command itself (see Section 6.1.1, Defining a command, on page 216) which represents the concept of a UI function, such as “Paste”, without defining where that function should appear in the user interface or what happens when the user selects that function. A menu contribution declaration (see Section 6.2, Menu and Toolbar Contributions, on page 220) defines where in the user interface that command should appear, and the text and image associated with that representation. A handler declaration (see Section 6.3, Handlers, on page 236) associates a command with a concrete class implementing the behavior for that command.

6.1.1

Defining a command

The first step to add a menu or toolbar item is to declare the “intent” of that function using the org.eclipse.ui.commands

extension point (see Figure

6–2). Using this extension point, you declare both the category

of the command and the command

itself. Categories are useful for managing large numbers of commands more easily.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

6.1

Commands

5HWXUQWR7DEOHRI&RQWHQWV

217

Figure 6–2

Command extension point overview.

Open the

Favorites

plug-in manifest editor, select the

Extensions

tab, and click the

Add...

button (see Figure 6–3). You can also open the

New Extension

wizard by right-clicking to display the context menu, then select the

New >

Extension...

command.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

218

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 6 • Commands and Actions

Figure 6–3

The Extensions page of the Manifest editor.

Select

org.eclipse.ui.commands

from the list of all available extension points (see Figure 6–4). If you can’t locate

org.eclipse.ui.commands

in the list, then uncheck the

Show only extension points from the required plug-ins

checkbox. Click the

Finish

button to add this extension to the plug-in manifest.

Figure 6–4

The New Extensions wizard showing extension points.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.1

Commands 219

Now, back in the

Extensions

page of the plug-in manifest editor, rightclick on the

org.eclipse.ui.commands

extension and select

New > category

.

This adds a new command category named

com.qualityeclipse.favorites.category1

in the plug-in manifest. Selecting this new category displays the

properties on the right side of the editor

. Modify them as follows:

id—

“com.qualityeclipse.favorites.commands.category”

The unique identifier used to reference the command category.

name—

“Favorites”

The name of the category.

description—

“Commands related to the Favorites View”

A short description of the category.

After adding the category, right-click on the

org.eclipse.ui.commands

extension again and select

New > command

. This adds a new command named

com.qualityeclipse.favorites.command1

in the plug-in manifest. Selecting this new commands displays the

properties on the right side of the editor

. Modify them as follows:

id—

“com.qualityeclipse.favorites.commands.openView”

The unique identifier used to reference the command.

name—

“Open Favorites View”

The name of the command. This text serves as the label attribute for any menuContribution

associated with this command that does not explicitly declare a label attribute (see Section 6.2.1, Defining a top level menu, on page 220).

description—

“Open the Favorites view if it is not already visible”

A short description of the category.

categoryId—

“com.qualityeclipse.favorites.commands.category”

The unique identifier of the category in which the command should appear.

In the same manner, declare another command for adding new resources to the

Favorites

view:

id—

“com.qualityeclipse.favorites.commands.add”

name—

“Add”

description—

“Add selected items to the Favorites view”

categoryId—

“com.qualityeclipse.favorites.commands.category”

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

220 CHAPTER 6 • Commands and Actions

These commands will appear in the context menu of any view (see Section

6.2.5, Defining a selection-based context menu item, on page 223) only when the selection in that view includes a object that can be added to the

Favorites

view.

Tip:

You can add command parameters to pass additional information.

See Section 15.5.5, Adding command parameters, on page 609 for more on command parameters, and Section 15.5.4, Adding cheat sheet commands, on page 607 for an example of how they can be used.

6.2

Menu and Toolbar Contributions

Use the menuContribution

element of the org.eclipse.ui.menus

extension point to define where (and when) the command should appear in the user interface. The locationURI

attribute specifies where the command should appear, while a visibleWhen

child element specifies when the command should appear. Using these two aspects of menuContribution

, you can display a command in any menu, context menu, or toolbar and limit its visibility to when it is applicable.

6.2.1

Defining a top level menu

In the

Favorites

plug-in manifest editor, select the

Extensions

tab, and click the

Add...

button. Select

org.eclipse.ui.menus

from the list of all available extension points. If you can’t locate

org.eclipse.ui.menus

in the list, then uncheck the

Show only extension points from the required plug-ins

checkbox. Click the

Finish

button to add this extension to the plug-in manifest.

Right-click on the

org.eclipse.ui.menus

extension you just added and select

New > menuContribution

. Selecting the new menu contribution

com.qualityeclipse.favorites.menuContribution1

displays the locationURI

property on the right side of the editor

. Modify it as follows:

locationURI—

“menu:org.eclipse.ui.main.menu?after=additions”

Identifies the location in the user interface where commands associated with this menu contribution will appear (see Section 6.2.9, locationURI, on page 230 for more details).

Right-click on the menuContribution

you just modified and select

New > menu

. Selecting the new menu

displays the

properties on the right side of the editor

. Modify them as follows:

label—

“Favorites”

The label used when displaying the menu.

id—

“com.qualityeclipse.favorites.menus.favoritesMenu”

The unique identifier for the menu.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.2

Menu and Toolbar Contributions 221

mnemonic—

“v”

The charater in the label

that is underlined, indicating keyboard accessibility (see Section 6.4, Key Bindings, on page 238).

Right-click on the menu

you just modified and select

New > command

. Selecting the new command

displays the

properties on the right side of the editor

.

Modify them as follows:

commandId—

“com.qualityeclipse.favorites.commands.openView”

The identifier for the command that is to be triggered when the user selects this menu item.

id—

“com.qualityeclipse.favorites.menus.openFavoritesView”

The unique identifier for this menu item.

mnemonic—

“O”

The charater in the label

that is underlined, indicating keyboard accessibility (see Section 6.4, Key Bindings, on page 238).

icon—

“icons/sample.gif”

A relative path to the image file containing the icon that is displayed to the left of the menu item’s label.

label—

leave blank

The menu item’s text. If this is unspecified, then the name of the command will be displayed instead.

6.2.2

Adding to an existing top level menu

The steps above add an entirely new menu to the Eclipse menu bar. If you wish to add a menu item or submenu to an already existing menu in the Eclipse menu bar, then use that menu’s identifier rather than the Eclipse menu bar’s identifier in the locationURL

. Searching for menuContributions

in the various org.eclipse

plug-ins turns up locationURL s such as these for adding commands to already existing Eclipse menus: menu:help?after=additions menu:navigate?after=open.ext2

menu:window?after=newEditor menu:file?after=open.ext

See Section 6.2.7, Defining an editor-specific menu or toolbar item, on page 229 for a concrete example of adding a menu item to an existing top level menu.

6.2.3

Defining a top level toolbar item

Defining a top level toolbar item is very similar to defining a top level menu item. As in the prior section, create a menuContribution

but with a different locationURI

:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

222 CHAPTER 6 • Commands and Actions

locationURI—

“toolbar:org.eclipse.ui.main.toolbar?after=additions”

Identifies the location in the user interface where commands associated with this menu contribution will appear (see Section 6.2.9, locationURI, on page 230 for more details).

Right-click on the new menuContribution

and select

New > toolbar

. Selecting the new toolbar

displays the

properties on the right side of the editor

.

Modify them as follows:

id—

“com.qualityeclipse.favorites.toolbars.main”

The unique identifier for the toolbar.

Right-click on the toolbar

you just modified and select

New > command

.

Selecting the new command

displays the

properties on the right side of the editor

. Modify them as follows:

commandId—

“com.qualityeclipse.favorites.commands.openView”

The identifier for the command that is to be triggered when the user selects this toolbar item.

id—

“com.qualityeclipse.favorites.toolbars.openFavoritesView”

The unique identifier for this toolbar item.

icon—

“icons/sample.gif”

A relative path to the image file containing the icon that is displayed in the toolbar.

tooltip—

“Open the Favorites view”

The text shown when the user hovers over the toolbar item.

6.2.4

Limiting top level menu and toolbar item visibility

Top level menus, menu items, and toolbar items are a great way to promote new functionality but can quickly clutter the user interface rendering it unusable (see Section 6.6.9, Discussion, on page 255). One way to provide user configurable visibility is to group menus, menu items, and toolbar items into

ActionSets

. Using the

Customize Perspective

dialog (see Figure 1–12 on page 14), the user can control whether a particular ActionSet is visible in the current perspective.

To limit the visibility of the top level menu and toolbar items that we created in prior sections, create an empty ActionSet as described in the first half of Section 6.6.1, Defining a workbench window menu, on page 243. Next add a visibleWhen

expression (see Section 6.2.10, visibleWhen expression, on page 231) to the top level menu item as described below. Repeat the same steps to limit the visibility of the top level toolbar item as well.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.2

Menu and Toolbar Contributions 223

Right-click on the menu item (or toolbar item) and select

New > visible-

When

. Right-click on the new visibleWhen

element and select

New > with

.

Selecting the new with

displays the

properties on the right side of the editor

.

Modify them as follows:

variable—

“activeContexts”

The name of the variable to be resolved at runtime and used when evaluating the child element. See Section 6.2.10, visibleWhen expression, on page 231 for known variables.

Right-click on the with

element and select

New > iterate

. Select the new iterate

element and modify its properties as follows so that any matching element will cause the expression to evaluate true

but that an empty collection will evaluate false

:

operator —

“or”

ifEmpty —

“false”

Finally right-click on the iterate

element and select

New > equals

, then modify the properties of the new equals

element as follows: value = “com.qualityeclipse.favorites.workbenchActionSet”

The identifier of the empty ActionSet referenced above.

This new visibleWhen

expression evaluates true only if the “activeContexts” collection contains the empty ActionSet’s identifier.

6.2.5

Defining a selection-based context menu item

Using the same mechanism as in prior sections, we create a context menu and menu item that is visible only when it is applicable for the user. Start by creating a menuContribution

but with a different locationURI

:

locationURI—

“popup:org.eclipse.ui.popup.any?after=additions”

The org.eclipse.ui.popup.any

identifier signals to Eclipse that the associated menus and menu items should appear in

all

context menus

(see Section 6.2.9, locationURI, on page 230 for more details).

Right-click on the menuContribution

you just modified and select

New > menu

. Selecting the new menu

displays the

properties on the right side of the editor

. Modify them as follows:

label—

“Favorites”

Right-click on the menu

you just modified and select

New > command

. Selecting the new command

displays the

properties on the right side of the editor

.

Modify them as follows:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

224 CHAPTER 6 • Commands and Actions

commandId—

“com.qualityeclipse.favorites.commands.add”

icon—

“icons/sample.gif”

6.2.5.1

Limiting context menu item visibility

If we stopped here, then the

Favorites > Add

menu item would appear in

every

context menu, even if it was not appropriate. We want the menu item to appear for resources and java elements so that the user can add those selected objects to the

Favorites

view, but not be visible when the user right-clicks on an item in the

Problems

view because it is inappropriate to add those objects to the

Favorites

view. To accomplish this we add a visibleWhen

expression

(see Section 6.2.10, visibleWhen expression, on page 231) that evaluates true only when one or more currently selected objects is either an instance of

IResource

or an instance of

IJavaElement

.

Figure 6–5

popup-menu with visibleWhen expression.

In the visibleWhen

expression above (see Figure 6–5), the selection (with)

element resolves to the collection of currently selected objects. The iterate

element is a child of selection (with)

, and evaluates its child element with each object in the collection. The or

element and its two child elements express that the object must be an instance of either

IResource or

IJavaElement

.

To build this visibleWhen

expression, start by right-clicking on the menu item that we just added and select

New > visibleWhen

. Right-click on the visibleWhen

element just created and select

New > with

. Selecting the new with

element displays the

properties on the right side of the editor

. Modify them as follows:

variable—

“selection”

The name of the variable to be resolved at runtime and used when evaluating the child element. See Section 6.2.10, visibleWhen expression, on page 231 for known variables.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.2

Menu and Toolbar Contributions 225

Right-click on the with

element and select

New > iterate

. Select the new iterate

element and modify its properties as follows so that any matching element will cause the expression to evaluate true

but that an empty collection will evaluate false

:

operator —

“or”

ifEmpty —

“false”

Continue to build the expression in the same manner so that the iterate

element has an or

child element which in turn has the following two child elements:

instanceof—

value = “org.eclipse.core.resources.IResource”

instanceof—

value = “org.eclipse.jdt.core.IJavaElement”

The or

expression that we just defined with its two children will evaluate true if the object being tested is an instance of

IResource

or an instance of

IJavaElement

.

After completing this, note the new warning appearing in the

Problems

view labeled “Referenced class 'org.eclipse.jdt.core.IJavaElement' in attribute

'value' is not on the plug-in classpath”. To add the necessary plug-in to the classpath and clear up this warning, switch to the

Dependencies

tab (see Figure 2–10 on page 79), click

Add

to add a new required plug-in, and select org.eclipse.jdt.core

.

6.2.5.2

Creating a new propertyTester

Our next goal is to further reduce the visibility of the context menu item added in the prior section by testing whether an object is already contained in the favorites collection. Eclipse provides many

propertyTesters

for evaluating properties of selected objects (see Section 6.2.10.3, propertyTester, on page 234), but does not provide what we need in this case. We must create a new

propertyTester

to accomplish our goal.

In the

Favorites

plug-in manifest editor, select the

Extensions

tab, and click the

Add...

button. Select

org.eclipse.core.expressions.propertyTesters

from the list of all available extension points. If you can’t locate

org.eclipse.core.expressions.propertyTesters

in the list, then uncheck the

Show only extension points from the required plug-ins

checkbox. Click the

Finish

button to add this extension to the plug-in manifest. Selecting the new propertyTester

displays the properties on the right side of the editor. Modify them as follows:

id —

“com.qualityeclipse.favorites.propertyTester”

The unique identifier for the property tester.

type —

“java.lang.Object”

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

226 CHAPTER 6 • Commands and Actions

The type of object to be tested by this property tester. Only objects of this type will be passed into the propert tester’s test

method.

namespace—

“com.qualityeclipse.favorites”

A unique id determining the name space the properties are added to.

properties—

“isFavorite, notFavorite”

A comma-separated list of properties provided by this property tester.

class—

“com.qualityeclipse.favorites.propertyTester.FavoritesTester”

The fully qualified name of the class providing the testing behavior. The class must be public

and extend org.eclipse.core.expressions.PropertyTester

with a public 0-argument constructor.

Once you have entered the

class

property specified above, click on the

class

label to the left of the property field. Once the

New Java Class

wizard opens, click

Finish

to create the new

propertyTester

class. The FavoritesTester#test(...) method should look something like this: public boolean test(Object receiver, String property, Object[] args,

Object expectedValue) {

if ("isFavorite".equals(property)) {

// determine if the favorites collection contains the receiver

return false;

}

if ("notFavorite".equals(property)) {

// determine if the favorites collection contains the receiver

return true;

}

return false;

}

Once the favorites model is defined, we can finish implementing the property tester test(...)

method (see Section 7.2.9, Implementing a propertyTester, on page 312), but until then this method will remain a skeletal implementation that always indicates the object being tested is not part of the favorites collection.

Tip:

A screencast demonstrating creating a property tester is available at:

http://konigsberg.blogspot.com/2008/06/screencast-using-propertytesters-in.html

6.2.5.3

Limiting visibility by testing object properties

Once our new

propertyTester

is complete, we can further reduce the visibility of the

Add

context menu item by testing whether an object is already contained in the favorites collection. This test is added to the already existing instanceof

elements added earlier in this chapter (see Figure 6–6).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

6.2

Menu and Toolbar Contributions

5HWXUQWR7DEOHRI&RQWHQWV

227

Figure 6–6

popup-menu with visibleWhen expression.

To modify the visibleWhen

expression, right-click on the iterate

element and select

New > and

. Drag the or

element onto the and

element so that it becomes the and

element’s first child element. Right-click on the and

element just created and select

New > test

, then modify the test element’s properties as follows:

property

= “com.qualityeclipse.favorites.notFavorite”

The fully qualified name of the property to be tested. The fully qualified name is the

propertyTester

’s namespace followed by the name of the property to be tested separated by a dot.

args

= leave this blank

A comma-separated list of additional arguments. No additional arguments need to be passed to our property tester.

value

= leave this blank

The expected value for the property. Our tester tests a boolean property and needs no expected value.

forcePluginActivation

= leave this blank

Agressively activates the plug-in if it is not already active. See discussion below for more information.

6.2.5.4

forcePluginActivation property

Whenever the user right-clicks on a selection, the visibleWhen

expression defined in the previous section will be evaluated to determine if our

Add

menu item should be visible to the user. As defined in Section 6.2.10, visibleWhen expression, on page 231, each element in a visibleWhen

expression evaluates to one of three possible values: true

, false

, or not-loaded

. If the overall visibleWhen

expression evaluates to either true

or not-loaded

, then the associated menu items and toolbar items will be visible.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

228 CHAPTER 6 • Commands and Actions

Our visibleWhen

expression includes a reference to our

propertyTester

which is implemented in our plug-in, and thus our plug-in must be active for the expression to be properly evaluated as either true

or false

. If our plugin is not active then the test

element that references our

propertyTester

will evaluate to not-loaded

. According to the logic outlined in Section 6.2.10, visibleWhen expression, on page 231, if the object is an instance of either

IResource

or

IJavaElement

, then the visibleWhen

expression will be evaluated to not-loaded

, otherwise the visibleWhen

expression will be evaluated to false

.

The result of all this is that if our plug-in is not loaded, our

Add

menu item will be optimistically visible without forcing our plug-in to be loaded. This is the preferred approach to reduce startup time and decrease memory footprint, but there are times when the need for accuracy outweighs other considerations. In that case, setting the test

’s

forcePluginActivation

property to true causes the plug-in defining the

propertyTester

to be loaded and activated immediately if it has not already.

6.2.6

Defining a view-specific menu or toolbar item

Adding a command to a specific view is similar to adding a command to the top level Eclipse menu bar or toolbar. Simply determine the identifier for that view’s context menu, toolbar, or pulldown menu and use that identifier as the

id

in the locationURL

(see Section 6.2.9, locationURI, on page 230). Typically, the view’s identifier, context menu identifier, toolbar identifier, and pulldown menu identifier are all the same.

Not that it is appropriate, but to demonstrate this ability we add the

Open

Favorites View

command to the

Problems

view’s pulldown menu, toolbar, and context menu. Follow the steps in Section 6.2.1, Defining a top level menu, on page 220, to create three new menuContribution

extensions, using the following locationURL

(For more on locationURL, see Section 6.2.9, locationURI, on page 230):

pulldown —

“menu:org.eclipse.ui.views.ProblemView?after=additions”

toolbar —

“toolbar:org.eclipse.ui.views.ProblemView?after=additions”

context —

“popup:org.eclipse.ui.views.ProblemView?after=additions”

Right-click on each of the new menuContribution s and select

New > command

. Selecting the new command

displays the

properties on the right side of the editor

. Modify those command

properties as outlined in Section 6.2.1,

Defining a top level menu, on page 220. See the end of Section 7.3.7.1, Copy, on page 322 for another example of adding a command to a context menu.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.2

Menu and Toolbar Contributions 229

6.2.7

Defining an editor-specific menu or toolbar item

Adding a command to an existing editor’s menu or toolbar is similar to adding a top level menu item (see Section 6.2.1, Defining a top level menu, on page 220) or toolbar item (see Section 6.2.3, Defining a top level toolbar item, on page 221). The difference is that each of the above must have a visible-

When

expression so that the command is only visible when a particular type of editor is active.

Not that its needed, but as a way to showcase this technique, we add the

Open Favorites View

command to the

Window

menu when the default text editor is active. Start by adding a menuContribution

as in Section 6.2.1,

Defining a top level menu, on page 220, except with the following location-

URL

:

locationURI—

“menu:window?after=additions”

The window identifier in the locationURL specifies that the new menu item should appear in the Eclipse

Window

menu (see Section 6.2.9, locationURI, on page 230 for more details).

Right-click on the new menuContribution

and select

New > command

, then modify the properties of the new command

as follows:

commandId—

“com.qualityeclipse.favorites.commands.openView”

icon—

“icons/sample.gif”

Next, right-click on the new command

and select

New > visibleWhen

. Rightclick on the new visibleWhen

element and select

New > with

, then modify the properties of the new with

element as follows:

variable—

“activeEditorId”

The name of the variable to be resolved at runtime and used when evaluating the child element. At runtime, the activeEditorId

resolves to the identifier of the active editor. See Section 6.2.10, visibleWhen expression, on page 231 for known variables.

Right-click on the with

element and select

New > equals

, then modify the properties of the new equals

element as follows: value = “org.eclipse.ui.DefaultTextEditor”

Once complete, this visibleWhen

expression resolves true

only when the active editor is the Eclipse default text editor.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

230 CHAPTER 6 • Commands and Actions

6.2.8

Dynamic menu contributions

Sometimes you need more control over the contributed menu items than is available via the previously mentioned declarations. For example, if you don’t know the menu items to be added to a menu or you need a menu item with a checkmark next to it, then declare a dynamic

menu contribution (see Section

14.3.7, Associating a nature with a project, on page 568 for an example of a menu item with a checkmark).

6.2.9

locationURI

As seen in the prior sections, the menuContribution

’s locationURI

attribute is used to specify where in the user interface the subsequent elements should appear (see Figure 6–7). The attribute is broken into three distinct parts:

scheme

,

identifier

, and

argument list

.

Figure 6–7

LocationURI Specification.

The

scheme

identifies the type of the UI component into which the contributions will be added. It can be one of the following values:

menu

—the main application menu or a view pull-down menu

popup

—a context menu in a view or editor

toolbar

—the main application toolbar or toolbar in view

The identifier or “

id

” of the locationURI specifies the unique identifier of menu, popup or toolbar into which the contributions should be added. For example, to add an item to a view’s toolbar, specify “toolbar” as the scheme and the view’s toolbar’s identifer as the id portion of the locationURI

. The convention is that for any view, its identifier should match its toolbar’s identifier, it’s context menu identifier and its pulldown menu identifier. Some common identifiers include:

org.eclipse.ui.main.menu

—the identifier for the Eclipse main menu

org.eclipse.ui.main.toolbar

—the identifier for the Eclipse main toolbar

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.2

Menu and Toolbar Contributions 231

org.eclipse.ui.popup.any

—the identifier for

any

context menu. When this identifer is used with the “popup” scheme, then the menu and menu items will be visible in

all

view and editor context menus. This is very similar to actions in context menus as discussed in Section 6.7, Object

Actions, on page 257.

The third and final portion of the locationURI

is the

argument list

, which allows fine-grained definition of the specific location within a given menu, popup or toolbar. The argument list is composed of the placement, which can be either “before” or “after”, an equals sign (“=”), and the identifier for some item in the menu, popup, or toolbar. The identifier can also be “additions” indicating that the elements should be placed in the default location within the given menu, popup, or toolbar.

6.2.10

visibleWhen expression

As seen in the prior sections, the visibleWhen

expression controls when the particular menu or toolbar items are visible in the user interface. Elements in a visibleWhen

expression evaluate to one of three states:

• true

• false

• not-loaded

When you are constructing an expression, there are several logical elements you can use when composing the expression:

and

—evaluates true

only if all child elements evaluate true

, false

if at least one child element evaluates false

, and not-loaded

in all other cases.

or

—evaluates false

only if all child elements evaluate false

, true

if at least one child element evaluates true

, and not-loaded

in all other cases.

not

—evaluates true

if its child element evaluates false false

if its child element evaluates true and not-loaded

if its child element evaluates not-loaded

.

Typically when declaring context menu contributions as seen in Section 6.2.5,

Defining a selection-based context menu item, on page 223, the with

element as the parent is used together with the iterate

element as the child to test each object in the current selection.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

232 CHAPTER 6 • Commands and Actions

with

—variable = “selection”

Evaluates to the current selection (a collection) of the currently active view or editor in the currently active window. See Section 6.2.10.1, with variables, on page 233 for a list of other variables defined by the Eclipse framework.

count

—value = integer, *, ?, +, !

Evaluates true

if the collection specified by the parent element contains the specified number of elements. The wild cards allowed include:

*

= Any number of elements

?

= No element or one element

+

= One or more elements

!

= No elements

(see Section 7.3.7, Clipboard commands, on page 322 for an example).

iterate

—operator = “and/or”, ifEmpty = “true/false”

Evaluates the child expression with each element in the collection specified by the parent. The operator property specifies whether all elements must evaluate true

for the iterate element to evaluate true

(operator =

“and”) or if only one element in the collection must evaluate true

for the iterate element to evaluate true

(operator = “or”). The ifEmpty property specifies the value returned by the iterate element if the collection is empty.

When iterating over a selection of objects, you’ll typically be probing one or more aspects of each object to determine if the visibleWhen

expression should evaluate true

and thus the associated user interface elements should be visible. Some of the aspects that can be evaluated include:

adapt

—Evaluates true

if the object specified by the parent element adapts (see Section 21.3, Adapters, on page 784) to the specified type.

The adapt

element may have child elements to further refine the expression by probing the adapted object. The children of an adapt expression are combined using the and

operator.

Tip:

The adapt

element currently recognizes objects that adapt using an

AdapterFactory

, but do

not

recognize objects that implement the

IAdaptable

interface. For more details, see Bugzilla entry # 201743.

equals

—Evaluates true

if the object specified by the parent element is equal to the specified value. The string value is converted to a java element as specified in Section 6.2.10.2, conversion of strings to java elements, on page 234.

instanceof

—Evaluates true

if the object specified by the parent element is an instance of the specified type.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.2

Menu and Toolbar Contributions 233

systemTest

—Tests a system property by calling the

System.getProperty

method and compares the result with the value specified through the value attribute.

test

—Evaluates true

if an object property equals a specified value, false

if not, or not-loaded

if the plug-in defining the propertyTester being referenced is not yet activated. The object used in the expression is from the parent element, while a propertyTester (see Section 6.2.10.3, propertyTester, on page 234) is used to perform the actual test. The attributes associated with this element include:

property

= The name of an object’s property to test.

args

= Additional arguments passed to the property tester. Multiple arguments are separated by commas. Each individual argument is converted into a Java element as outlined in Section 6.2.10.2, conversion of strings to java elements, on page 234.

value

= The expected value of the property which can be omitted if the property is a boolean property. The value attribute is converted into a

Java element as outlined in Section 6.2.10.2, conversion of strings to java elements, on page 234.

forcePluginActivation

= A flag indicating whether the plug-in contributing the property tester should be loaded if necessary. Don’t specify this attribute unless absolutely necessary (see Section 6.2.5.4, forcePluginActivation property, on page 227).

6.2.10.1

with variables

You can specify other variables in the with

element as shown in Section 6.2.7,

Defining an editor-specific menu or toolbar item, on page 229. Some of the possible variables for the with

element are shown below. For more variables, see the org.eclipse.ui.ISources

class. If you are really feeling adventuresome, search for callers of org.eclipse.core.expressions.Evaluation-

Context#addVariable(...)

.

“activeContexts”—Evaluates to a collection of ActionSet and context identifiers as discussed in Section 6.2.4, Limiting top level menu and toolbar item visibility, on page 222.

“activeEditorId”—Evaluates to the identifier of the currently active editor as discussed in Section 6.2.7, Defining an editor-specific menu or toolbar item, on page 229.

“activePartId”—Evaluates to the identifier of the currently active part

(see Section 7.3.2.5, Filtering unwanted actions, on page 317).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

234 CHAPTER 6 • Commands and Actions

“selection”—Evaluates to the current selection (a collection) of the currently active part (view or editor) in the currently active window as discussed above and in Section 6.2.5, Defining a selection-based context menu item, on page 223.

Tip:

For more on command expressions and variables used in the

with

statement, see

http://wiki.eclipse.org/Command_Core_Expressions

6.2.10.2

conversion of strings to java elements

When evaluating an expression involving a string specified in the plugin manifest (see

equals

in Section 6.2.10, visibleWhen expression, on page 231), the following rules are used to convert that string to a java element before comparison:

• the string “true” is converted into

Boolean.TRUE

.

• the string “false” is converted into

Boolean.FALSE

.

• if the string contains a dot then the interpreter tries to convert the value into a

Float

object. If this fails the string is treated as a java.lang.String

.

• if the string only consists of numbers then the interpreter converts the value in an

Integer

object.

• in all other cases the string is treated as a java.lang.String

.

• the conversion of the string into a

Boolean

,

Float

, or

Integer

can be suppressed by surrounding the string with single quotes. For example, the attribute value “true” is converted into the string “true.”

6.2.10.3

propertyTester

A propertyTester is an extension to the expression framework for evaluating the state of a particular object. When used in conjunction with either the

with

or

iterate

expressions (see Section 6.2.10, visibleWhen expression, on page 231), the propertyTester receives the object specified in the expression along with the name of the property to be tested as arguments to its test method (see Section 6.2.5.2, Creating a new propertyTester, on page 225) and returns a boolean indicating whether the object is in the expected state.

Eclipse provides many property testers (see list below) for use with the

test

expression (see Section 6.2.5.3, Limiting visibility by testing object properties, on page 226). You can also define your own property testers (see Section

6.2.5.2, Creating a new propertyTester, on page 225). Search for all extensions of the org.eclipse.core.expressions.propertyTesters

exten-

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.2

Menu and Toolbar Contributions 235

sion point for a more complete list of property testers. All property testers must extend org.eclipse.core.expressions.PropertyTester

, so for more detail, open a type hierarchy view and browse its various subclasses.

org.eclipse.core.runtime.Platform

org.eclipse.core.runtime.isBundleInstalled

—Evaluates true

if a bundle with an identifier equal to the first argument is loaded.

org.eclipse.core.runtime.product

—Evaluates true

if the identifier of the current product equals the specified string.

org.eclipse.core.resources.IResource

org.eclipse.core.resources.name

—Evaluates true

if the

IResource

’s name matches the specified pattern (the expected value).

org.eclipse.core.resources.path

—Evaluates true

if the

IResource

’s path matches the specified pattern (the expected value).

org.eclipse.core.resources.extension

—Evaluates true

if the

IResource

’s extension matches the specified pattern (the expected value).

org.eclipse.core.resources.readOnly

—Evaluates true

if the

IResource

’s read-only flag equals the specified boolean value.

org.eclipse.core.resources.IFile

org.eclipse.core.resources.contentTypeId

—Evaluates true

if the

IFile

’s content type identifier equals the expected value.

org.eclipse.core.resources.IProject

org.eclipse.core.resources.open

—Evaluates true

if the

IProject

is open.

org.eclipse.ui.IWorkbench

org.eclipse.ui.isActivityEnabled

—Evaluates true

if an activity with an identifier equal to the first argument can be found and is currently enabled.

org.eclipse.ui.isCategoryEnabled

—Evaluates true

if a category with an identifier equal to the first argument can be found and is currently enabled.

org.eclipse.ui.IWorkbenchWindow

org.eclipse.ui.workbenchWindow.isPerspectiveOpen

—Evaluates true

if the active workbench window page is a perspective.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

236

6.3

Handlers

CHAPTER 6 • Commands and Actions

For a command to be useful, it must have some behavior associated with it.

Using the org.eclipse.ui.handlers

extension point, you associate one or more concrete classes implementing the command behavior with the command itself. Alternately, you can use

IHandlerService

to programmatically associate a handler with a command as shown in Section 7.3.6, Global commands, on page 321.

Most handlers have activeWhen

and enabledWhen

expressions to specify under what conditions it is appropriate to execute the handler (See Section

7.3.7.1, Copy, on page 322 for an example). These expressions have a format similar to the visibleWhen

expression described in Section 6.2.10, visible-

When expression, on page 231. If a handler does not have any expressions, it is considered a

default handler

. A default handler is only active if no other handler has all of its conditions satisfied.

If the user selects a command and two or more handlers have conditions that are satisfied, then the conditions are compared. The idea is to select a handler whose condition is more specific or more local. To do this, the variables referred to by the condition are inspected and the condition that refers to the most specific variable “wins” (see org.eclipse.ui.ISources). If this still doesn't resolve the conflict, then no handler is active. A conflict can also occur if there are two or more default handlers.

Tip

: We highly recommended specifying an activeWhen

expression to avoid unnecessary plug-in loading. For a handler (and the plug-in defining it) to be loaded, the command must be selected by the user and the activeWhen

and enabledWhen

expressions must be satisfied.

In the

Favorites

plug-in manifest editor, select the

Extensions

tab, and click the

Add...

button. Select

org.eclipse.ui.handlers

from the list of all available extension points. If you can’t locate

org.eclipse.ui.handlers

in the list, then uncheck the

Show only extension points from the required plug-ins

checkbox.

Click the

Finish

button to add this extension to the plug-in manifest.

Right-click on the

org.eclipse.ui.handlers

extension in the

Extensions

page of the plug-in manifest editor and select

New > handler

. Selecting the new handler

com.qualityeclipse.favorites.handler1

displays the

properties on the right side of the editor

. Modify them as follows:

commandId—

“com.qualityeclipse.favorites.commands.openView”

The unique identifier used to reference the command.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.3

Handlers 237

class—

“com.qualityeclipse.favorites.handlers.OpenFavoritesViewHandler”

The org.eclipse.core.commands.IHandler

object (see Section 6.3.1,

Creating a new IHandler, below) used to perform the operation. The class is instantiated using its no argument constructor, but can be parameterized using the

IExecutableExtension

interface (see Section

21.5, Types Specified in an Extension Point, on page 793)

Tip

: If you have an existing action (see Section 7.3.4, Pull-down menu, on page 319) use org.eclipse.jface.commands.ActionHandler

to convert it into an instance of

IHandler

.

Clicking the

class:

label to the left of this property’s text field opens the New

Java Class wizard if the class does not exist, or opens the java editor if the class already exists.

6.3.1

Creating a new IHandler

The handler contains the behavior associated with the command. The following are several ways that you can associate a handler with a command:

• Enter the fully qualified class name of handler in the

class

field.

• Click on the

class:

label that appears to the left of the

class

field to create a new handler class.

• Click on the

Browse...

button to the right of the

class

field to select an already existing handler.

Since you have not already created a handler class for the command, have

Eclipse generate one that can be customized. Select the handler created in the section above and click the

class:

label that appears to the left of the

class

field to open the

New Java Class Wizard

for the action’s class.

After the class has been created and the editor opened, modify the execute(...)

method as follows so that the

Favorites

view will open when a user selects the action. The org.eclipse.ui.handlers.HandlerUtil

class provides several convenience methods useful from within the execute(...) method.

public Object execute(ExecutionEvent event)

throws ExecutionException {

// Get the active window

IWorkbenchWindow window = HandlerUtil

.getActiveWorkbenchWindowChecked(event);

if (window == null)

return null;

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

238 CHAPTER 6 • Commands and Actions

// Get the active page

IWorkbenchPage page = window.getActivePage();

if (page == null)

return null;

// Open and activate the Favorites view

try {

page.showView(FavoritesView.ID);

} catch (PartInitException e) {

FavoritesLog.logError("Failed to open the Favorites view", e);

}

return null;

}

Currently, the Eclipse API specifies that the execute(...)

method’s return value is reserved for future use and must be null

.

Next, add a constant to the

FavoritesView

class representing the unique identifier used to open the

Favorites

view.

public static final String ID =

"com.qualityeclipse.favorites.views.FavoritesView";

In the same manner, we need to add a handler for the com.qualityeclipse.favorites.commands.add

command introduced in Section 6.1.1,

Defining a command, on page 216. Repeat the steps above to add a new

AddToFavoritesHandler

handler.

Tip:

You can add command parameters to pass additional information.

See Section 15.5.5, Adding command parameters, on page 609 for an example of processing command parameters in a handler.

6.4

Key Bindings

Commands do not reference key bindings; rather, key bindings are declared separately and reference commands. This allows for multiple key bindings to be associated with the same command. For example, the default accelerator for saving the contents of a file is

Ctrl+S

, but after switching to the Emacs configuration, the save accelerator becomes

Ctrl+X Ctrl+S

.

To add a key binding for the

Add

command, create a new org.eclipse.ui.bindings

extension (see Section 6.1.1, Defining a command, on page 216 for an example of how extensions are added), then rightclick and select

New > key

. Enter the following attributes for the new key binding. Once complete, the

Favorites

key binding appears in the

Keys

preference page (see Figure 6–8) and

Ctrl+Shift+A

triggers the

Add

command whenever a text editor has focus.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.4

Key Bindings 239

commandId—

“com.qualityeclipse.favorites.commands.add”

The command triggered by the key binding.

contextId—

“org.eclipse.ui.textEditorScope”

The context in which the key binding is available to the user. Some of the predefined scopes include:

org.eclipse.ui.contexts.window

—the workbench window

org.eclipse.ui.textEditorScope

—text editors

org.eclipse.ui.contexts.dialog

—dialogs

org.eclipse.jdt.ui.javaEditorScope

—java editors

org.eclipse.debug.ui.debugging

—debugging views

org.eclipse.debug.ui.console

—console view

New contexts can be defined using the org.eclipse.ui.contexts

extension point. If

contextId

is not specified, then it defaults to org.eclipse.ui.contexts.window

.

schemeId—

“org.eclipse.ui.defaultAcceleratorConfiguration”

The user selectable scheme containing the key binding. Typically, key bindings are added to the default Eclipse configuration, but alternate key bindings, such as “org.eclipse.ui.emacsAcceleratorConfiguration,” can be added to other configurations. New schemes can be defined by declaring a new scheme

element in the org.eclipse.ui.bindings

extension.

sequence—

“Ctrl+Shift+A”

The key sequence to assign to the command. Key sequences consist of one or more keystrokes, where a keystroke consists of a key on the keyboard, optionally pressed in combination with one or more of the following modifiers: Ctrl, Alt, Shift, Command, M1 (mapped to Ctrl or Command as appropriate on that platform), M2 (Shift), and M3 (Alt or Option as appropriate on that platform). Keystrokes are separated by spaces, and modifiers are separated by “+” characters. For example a key sequence of holding down the control key while pressing

X

followed by holding down the control key while pressing

S

would be “

Ctrl+X Ctrl+S

”. Special keys are represented by 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. There are some alternative names for some common special keys. For example, both ESC and ESCAPE are the same, and CR,

ENTER, and RETURN are all the same.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

240 CHAPTER 6 • Commands and Actions

Other key binding attributes that are not used in the

Favorites

example include:

locale—

An optional attribute indicating that the key binding is only defined for a specified locale. Locales are specified according to the format declared in java.util.Locale

.

platform—

An optional attribute indicating that the key binding is only defined for the specified platform. The possible values of the platform attribute are the set of the possible values returned by org.eclipse. swt.SWT.getPlatform()

.

Figure 6–8

Keys preference page showing

Favorites

key binding.

6.5

IAction versus IActionDelegate

An Eclipse

action

is composed of several parts, including the XML declaration of the action in the plug-in’s manifest, the

IAction

object instantiated by the

Eclipse UI to represent the action, and the

IActionDelegate

defined in the plug-in library containing the code to perform the action (see Figure 6–9).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

6.5

IAction versus IActionDelegate

5HWXUQWR7DEOHRI&RQWHQWV

241

Figure 6–9

Action versus IActionDelegate.

This separation of the

IAction

object, defined and instantiated by the

Eclipse user interface based on the plug-in’s manifest and the

IAction-

Delegate

defined in the plug-in’s library, allows Eclipse to represent the action in a menu or toolbar without loading the plug-in that contains the operation until the user selects a specific menu item or clicks on the toolbar.

Again, this approach represents one of the overarching themes of Eclipse: lazy plug-in initialization.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

242 CHAPTER 6 • Commands and Actions

There are several interesting subtypes of

IActionDelegate

.

IActionDelegate2

—Provides lifecycle events to action delegates; if you are implementing

IActionDelegate

and need additional information, such as when to clean up before the action delegate is disposed, then implement

IActionDelegate2

instead. In addition, an action delegate implementing

IActionDelegate2

will have runWithEvent(IAction,

Event)

called instead of run(IAction)

.

IEditorActionDelegate

—Provides lifecycle events to action delegates associated with an editor (see Section 6.9.3, IEditorActionDelegate, on page 279).

IObjectActionDelegate

—Provides lifecycle events to action delegates associated with a context menu (see Section 6.7.3, IObjectActionDelegate, on page 266).

IViewActionDelegate

—Provides lifecycle events to action delegates associated with a view (see Section 6.8.3, IViewActionDelegate, on page 273).

IWorkbenchWindowActionDelegate

—Provides lifecycle events to action delegates associated with the workbench window menu bar or toolbar.

6.6

Workbench Window Actions

Where and when an action appears is dependent on the extension point and filter used to define the action. This section discusses adding a new menu to the workbench menu bar and a new button to the workbench toolbar using the org.eclipse.ui.actionSets

extension point (see Figure 6–9). These actions are very similar to menu contributions with a locationURI id (see Section 6.2.9, locationURI, on page 230) equal to “org.eclipse.ui.main.menu”.

Both the menu item and toolbar button open the

Favorites

view when selected by a user. The user can already open the

Favorites

view (as outlined in

Section 2.5, Installing and Running the Product, on page 92) but a top-level menu will really show off the new product by providing an easy way to find it.

Tip:

A top-level menu is a great way to show off a new product to a user, but be sure to read Section 6.6.9, Discussion, on page 255 concerning the pitfalls of this approach.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.6

Workbench Window Actions 243

6.6.1

Defining a workbench window menu

To create a new menu to appear in the workbench menu bar, you have to create an

actionSet

extension in the

Favorites

plug-in manifest describing the new actions. That declaration must describe the location and content of the new menu and reference the action delegate class that performs the operation.

Open the

Favorites

plug-in manifest editor, select the

Extensions

tab, and click the

Add...

button (see Figure 6–3). You can also open the

New Extension

wizard by right-clicking to display the context menu, then select the

New >

Extension...

command.

Select

org.eclipse.ui.actionSets

from the list of all available extension points (see Figure 6–10). If you can’t locate

org.eclipse.ui.actionSets

in the list, then uncheck the

Show only extension points from the required plug-ins

checkbox. Click the

Finish

button to add this extension to the plug-in manifest.

Figure 6–10

The New Extension wizard showing extension points.

Now, back in the

Extensions

page of the plug-in manifest editor, rightclick on the

org.eclipse.ui.actionSets

extension and select

New > actionSet

.

This immediately adds a new action set named

com.qualityeclipse.favorites.actionSet1

in the plug-in manifest. Selecting this new action set displays the

properties on the right side of the editor

. Modify them as follows:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

244 CHAPTER 6 • Commands and Actions

id—

“com.qualityeclipse.favorites.workbenchActionSet”

The unique identifier used to reference the action set.

label—

“Favorites ActionSet”

The text that appears in the

Customize Perspective

dialog.

visible—

“true”

Determines whether the action set is initially visible. The user can show or hide an action set by selecting

Window > Customize Perspective...

, expanding the

Other

category in the

Customize Perspective

dialog, and checking or unchecking the various action sets that are listed.

Next, add a menu that will appear in the workbench menu bar by rightclicking on the action set you just added and selecting

New > menu

. Note that the name of the new action set changes to

Favorites ActionSet

when the tree selection changes. Select the new menu and set its attributes as follows (see

Figure 6–11):

id—

“com.qualityeclipse.favorites.workbenchMenu”

The unique identifier used to reference this menu.

label—

“Fa&vorites”

The name of the menu appearing in the workbench menu bar. The “&” is for keyboard accessibility (see Section 6.10.2, Keyboard accessibility, on page 285).

path—

“additions”

The insertion point indicating where the menu will be positioned in the menu bar. For more information about “additions” and insertion points, see Section 6.6.5, Insertion points, on page 248.

Figure 6–11

The Extensions page showing the Favorites menu’s attributes.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.6

Workbench Window Actions 245

6.6.2

Groups in a menu

Actions are added not to the menu itself, but to groups within the menu, so first some groups need to be defined. Right-click on the new

Favorites

menu and select

New > groupMarker

. Select the new

groupMarker

and change the

name

to “content” to uniquely identify that group within the

Favorites

menu.

Add a second group to the

Favorites

menu; however, this time select

New > separator

and give it the name “additions”.

A

separator

group has a horizontal line above the first menu item in the group, whereas a

groupMarker

does not have any line. The additions group is not used here, but it exists as a matter of course in case another plug-in wants to contribute actions to the plug-in’s menu.

6.6.3

Defining a menu item and toolbar button

Finally, its time to define the action that appears in both the

Favorites

menu and the workbench toolbar. Right-click on the

Favorites ActionSet

and select

New > action

. Select this new action and enter the following values:

id—

“com.qualityeclipse.favorites.openFavoritesView”

The unique identifier used to reference the action.

label—

“Open Favo&rites View”

The text appearing in the

Favorites

menu. The “&” is for keyboard accessibility (see Section 6.10.2, Keyboard accessibility, on page 285).

menubarPath—

“com.qualityeclipse.favorites.workbenchMenu/content”

The insertion point indicating where the action will appear in the menu

(see Section 6.6.5, Insertion points, on page 248).

toolbarPath—

“Normal/additions”

The insertion point indicating where the button will appear in the toolbar (see Section 6.6.5, Insertion points, on page 248).

tooltip—

“Open the favorites view in the current workbench page”

The text that appears when the mouse hovers over the action’s icon in the workbench toolbar.

Other attributes, which are discussed in subsequent sections, include the following:

allowLabelUpdate—

Optional attribute indicating whether the retarget action allows the handler to override its label and tooltip. Only applies if the retarget attribute is true

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

246 CHAPTER 6 • Commands and Actions

class—

The org.eclipse.ui.IWorkbenchWindowActionDelegate delegate used to perform the operation is covered later (see Section 6.6.6,

Creating an action delegate, on page 249). If the pulldown style is specified, then the class must implement the org.eclipse.ui.

IWorkbenchWindowPulldownDelegate

interface. The class is instantiated using its no argument constructor, but may be parameterized using the

IExecutableExtension

interface (see Section 21.5, Types Specified in an Extension Point, on page 793).

definitionId—

The command identifier for the action, which allows a key sequence to be associated with it (see Section 6.10.1, Associating commands with actions, on page 284).

disabledIcon—

The image displayed when the action is disabled. For more detail, see Section 6.6.4, Action images, on page 247.

enablesFor—

An expression indicating when the action will be enabled

(see Section 6.7.2, Action filtering and enablement, on page 260). If blank, then the action is always active unless overridden programmatically via the

IAction

interface.

helpContextId—

The identifier for the help context associated with the action (covered in Chapter 15, Implementing Help).

hoverIcon—

An image displayed when the cursor

hovers

over the action without being clicked. For more detail, see Section 6.6.4, Action images, on page 247.

icon—

The associated image. For more detail, see Section 6.6.4, Action images, on page 247.

retarget—

An optional attribute to retarget this action. When true

, view and editor parts may supply a handler for this action using the standard mechanism for setting a global action handler (see Section 8.5.2.2, Toplevel menu, on page 387) on their site using this action’s identifier. If this attribute is true

, the class attribute should not be supplied.

state—

For an action with either the radio

or toggle

style, set the initial state to true

or false

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.6

Workbench Window Actions 247

style—

An attribute defining the visual form of the action and having one of the following values: push

A normal menu or toolbar item (the default style).

radio

A radio button-style menu or toolbar item where only one item at a time in a group of items all having the radio style can be active. See the

state

attribute.

toggle

A checked menu item or toggle tool item. See the

state

attribute.

pulldown

A submenu or drop-down toolbar menu. See the

class

attribute.

6.6.4

Action images

Next, associate an icon with the action that will appear in the workbench toolbar. Select the

Open Favorites View

action added in the previous section, then click the

Browse...

button that appears to the right of the

icon

field. In the resulting dialog, expand the tree and select the

sample.gif

item from the

icons

folder (see Figure 6–12). Click the

OK

button and

icons/sample.gif

will appear in the

icon

field.

The path appearing in the

icon

field and in the plugin.xml

is relative to the plug-in’s installation directory. Other image-related attributes include

hoverIcon

and

disabledIcon

for specifying the image that will be used when the mouse is hovered over the toolbar button and when the action is disabled, respectively.

Figure 6–12

The Resource Attribute Value dialog for selecting an icon.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

248 CHAPTER 6 • Commands and Actions

Creating Your Own Icons

Several programs are available for creating and modifying images such as Jasc’s Paint Shop Pro and Adobe’s Photoshop Elements. Using one of these programs, you can create an icon from scratch or start with one of the many icons provided by Eclipse (for starters, see the

\icons\full

directories located within the

org.eclipse.ui

or

org.eclipse.jdt.ui

JARs). Icons are typically

*.gif files with a transparency color.

6.6.5

Insertion points

Because Eclipse is composed of multiple plug-ins—each one capable of contributing actions but not necessarily knowing about one another at buildtime—the absolute position of an action or submenu within its parent is not known until runtime. Even during the course of execution, the position might change due to a sibling action being added or removed as the user changes a selection. For this reason, Eclipse uses

identifiers

to reference a menu, group, or action, and a path, known as an

insertion point

, for specifying where a menu or action will appear.

Every insertion point is composed of one or two identifiers separated by a forward slash, indicating the parent (a menu in this case) and group where the action will be located. For example, the

Open Favorites View

action’s

menubar

attribute (see Section 6.6.3, Defining a menu item and toolbar button, on page 245 and Figure 6–1) is composed of two elements separated by a forward slash.

The first element, com.qualityeclipse.favorites.workbenchMenu

, identifies the

Favorites

menu, while the second element, content

, identifies the group within the

Favorites

menu. In some cases, such as when the parent is the workbench menu bar or a view’s context menu, the parent is implied and thus only the group is specified in the insertion point.

Typically, plug-ins make allowances for other plug-ins to add new actions to their own menus by defining an empty group labeled “

additions”

in which the new actions will appear. The “

additions”

identifier is fairly standard throughout Eclipse, indicating where new actions or menus will appear, and is included in it as the

IWorkbenchActionConstants.MB_ADDITIONS

constant. For example, the

Favorites

menu specifies a

path

attribute (see Section

6.6.1, Defining a workbench window menu, on page 243) having the value

additions”

that causes the

Favorites

menu to appear to the left of the

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.6

Workbench Window Actions 249

Window

menu. Because the identifier for the

Window

menu is

window

, and if the

path

attribute of the

Favorites

menu is set to “

window/additions”,

then the

Favorites

menu will appear as a submenu in the

Window

menu itself rather than in the workbench menu bar.

Nested ActionSet Problem

Defining an action in an actionSet that contributes to a menu defined in a

different

actionSet can result in the following error in the Eclipse log file:

Invalid Menu Extension (Path is invalid): some.action.id

To work around this issue, define the menu in

both

actionSets. For more information, see Bugzilla entries #36389 and #105949.

The

toolbarPath

attribute is also an insertion point and has a structure identical to the

menubarPath

attribute, but indicates where the action will appear in the workbench toolbar rather than the menu bar. For example, the

toolbarPath

attribute of the

Open Favorites View

action (see Section 6.6.3,

Defining a menu item and toolbar button, on page 245) is also composed of two elements separated by a forward slash: The first element,

Normal

, is the identifier of the workbench toolbar, while

additions

, the second element, is the group within that toolbar where the action will appear.

6.6.6

Creating an action delegate

The action is almost complete except for the action delegate, which contains the behavior associated with the action. The following are several ways that you can specify the action delegate associated with an action.

• Enter the fully qualified class name of the action delegate in the

class

field.

• Click on the

class:

label that appears to the left of the

class

field to create a new action delegate class.

• Click on the

Browse...

button to the right of the

class

field to select an already existing action delegate.

Since you have not already created a class for the action, have Eclipse generate one that can be customized. Select the

Open Favorites View

action and click the

class:

label that appears to the left of the

class

field to open the

New Java

Class

wizard for the action’s class (see Figure 6–13).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

250

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 6 • Commands and Actions

Figure 6–13

The New Java Class wizard for an action’s class.

Enter “com.qualityeclipse.favorites.actions” in the

Package

field and

“OpenFavoritesViewActionDelegate” in the

Name

field. Click the

Finish

button to generate the new action delegate and open an editor on the new class.

After the class has been created and the editor opened, modify the class as follows so that the

Favorites

view will open when a user selects the action.

Start by adding a new field and modifying the init()

method to cache the window in which this action delegate is operating.

private IWorkbenchWindow window; public void init(IWorkbenchWindow window) {

this.window = window;

}

Next, add a constant to the

FavoritesView

class representing the unique identifier used to open the

Favorites

view.

public static final String ID =

"com.qualityeclipse.favorites.views.FavoritesView";

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.6

Workbench Window Actions 251

Finally, modify the run()

method of the

OpenFavoritesViewAction-

Delegate

class to actually open the

Favorites View

.

public void run(IAction action) {

// Get the active page.

if (window == null)

return;

IWorkbenchPage page = window.getActivePage();

if (page == null)

return;

// Open and activate the Favorites view.

try {

page.showView(FavoritesView.ID);

}

catch (PartInitException e) {

FavoritesLog.logError("Failed to open the Favorites view", e);

}

}

6.6.6.1

selectionChanged method

While the action declaration in the plug-in manifest provides the initial state of the action, the selectionChanged()

method in the action delegate provides an opportunity to dynamically adjust the state, enablement, or even the text of the action using the

IAction

interface.

For example, the

enablesFor

attribute (see Section 6.7.2, Action filtering and enablement, on page 260) is used to specify the number of objects to select for an action to be enabled, but further refinement of this enablement can be provided by implementing the selectionChanged()

method. This method can interrogate the current selection and call the

IAction.setEnabled()

method as necessary to update the action enablement.

In order for the action delegate’s selectionChanged()

method to be called, you need to call getViewSite().setSelectionProvider(viewer) in your view’s createPartControl()

method.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

252 CHAPTER 6 • Commands and Actions

6.6.6.2

run method

The run()

method is called when a user selects an action and expects an operation to be performed. Similar to the selectionChanged()

method, the

IAction

interface can be used to change the state of an action dependent on the outcome of an operation.

Guard Code Needed

Be aware that if the plug-in is not loaded and the user selects a menu option causing the plug-in to be loaded, the selectionChanged()

method

may not be called

before the run()

method, so the run()

method still needs the appropriate guard code. In addition, the run()

method executes in the main UI thread, so consider pushing long running operations into a background thread (see Section 21.8, Background Tasks—Jobs API, on page 808)

.

6.6.7

Manually testing the new action

Testing the modifications you have just made involves launching the

Runtime

Workbench

as discussed in Chapter 2, A Simple Plug-in Example. If the

Favorites

menu does not appear in the

Runtime Workbench

menu bar or the

Favorites

icon cannot be found in the toolbar, try the following suggestions:

• Enable the action set by selecting

Window > Customize Perspective...

to open the

Customize Perspective

dialog. In the dialog, select the

Commands

tab, locate

Favorites ActionSet,

and make sure it is checked

(see Figure 6–14).

• Reinitialize the perspective using

Window > Reset Perspective

.

• Close and reopen the perspective.

• If nothing else works, then try clearing the workspace data before launching the

Runtime Workbench

. To do this, select

Run...

in the launch menu, select the

Favorites

launch configuration, and check the

Clear workspace data before launching

checkbox. Click the

Run

button to launch the

Runtime Workbench

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

6.6

Workbench Window Actions

5HWXUQWR7DEOHRI&RQWHQWV

253

Figure 6–14

Customize Perspective dialog.

6.6.8

Adding a test for the new action

Before the work is complete, you need to devise a test for the new

Open Favorites View

action. You already have a

FavoritesViewTest

(see Section 2.8.3,

Creating a Plug-in test, on page 100) from which to extract common test functionality.

Create a new superclass for all the tests called

AbstractFavoritesTest

, then pull up the delay()

, assertEquals()

, and waitForJobs()

methods from the existing

FavoritesViewTest

. The

VIEW_ID constant is the same as the

FavoritesView.ID

constant, so replace it with

FavoritesView.ID

.

Next, create a new test subclassing

AbstractFavoritesTest

that exercises the new

OpenFavoritesViewActionDelegate

class.

package com.qualityeclipse.favorites.test; import ...

public class OpenFavoritesViewTest extends AbstractFavoritesTest {

public OpenFavoritesViewTest(String name) {

super(name);

}

Override the setUp()

method to ensure that the system is in the appropriate state before the test executes.

protected void setUp() throws Exception {

super.setUp();

// Ensure that the view is not open.

waitForJobs();

IWorkbenchPage page = PlatformUI.getWorkbench()

.getActiveWorkbenchWindow().getActivePage();

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

254 CHAPTER 6 • Commands and Actions

IViewPart view = page.findView(FavoritesView.ID);

if (view != null)

page.hideView(view);

// Delay for 3 seconds so that

// the Favorites view can be seen.

waitForJobs();

delay(3000);

}

Finally, create the test method that exercises the

OpenFavoritesView-

ActionDelegate

class.

public void testOpenFavoritesView() {

// Execute the operation.

(new Action("OpenFavoritesViewTest") {

public void run() {

IWorkbenchWindowActionDelegate delegate =

new OpenFavoritesViewActionDelegate();

delegate.init(PlatformUI.getWorkbench()

.getActiveWorkbenchWindow());

delegate.selectionChanged(this, StructuredSelection.EMPTY);

delegate.run(this);

}

}).run();

// Test that the operation completed successfully.

waitForJobs();

IWorkbenchPage page = PlatformUI.getWorkbench()

.getActiveWorkbenchWindow().getActivePage();

assertTrue(page.findView(FavoritesView.ID) != null);

}

After entering the preceding test, the following error will appear in the

Problems

view:

Access restriction: The type OpenFavoritesViewActionDelegate is not accessible due to restriction on required project com.qualityeclipse.favorites.

This indicates that the

com.qualityeclipse.favorites

plug-in does not provide access to the

OpenFavoritesViewActionDelegate

class to other plug-ins.

To remedy this situation, open the plug-in manifest editor to the

Exported

Packages

section (see Section 2.8.1, Test preparation, on page 100), click

Add...

, select the

com.qualityeclipse.favorites.actions

package, and save the changes.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.6

Workbench Window Actions 255

Now everything is ready to execute the tests. Rather than launching each test individually, the

FavoritesViewTest

and

OpenFavoritesViewTest

can be combined into a single test suite named

FavoritesTestSuite

, which can be launched to execute both tests at once: package com.qualityeclipse.favorites.test; import ...

public class FavoritesTestSuite

{

public static Test suite() {

TestSuite suite =

new TestSuite("Favorites test suite");

suite.addTest(

new TestSuite(FavoritesViewTest.class));

suite.addTest(

new TestSuite(OpenFavoritesViewTest.class));

return suite;

}

}

While individually launching tests is not a problem now with just two tests, in the future, as more tests are added for the

Favorites

plug-in, it can save time to have a single test suite. To launch the test suite, select

Run...

in the launch menu, select the

FavoritesViewTest launch configuration that was created in Section 2.8.4, Running a Plug-in test, on page 103, and modify the target to be the new

FavoritesTestSuite

test suite.

6.6.9

Discussion

To define a top-level menu or not... that is the question. On the one hand, a top-level menu is a great way to promote a new product that has just been installed, providing a good way for a potential customer to become accustomed to new functionality. On the other hand, if every plug-in defined a toplevel menu, then the menu bar would be cluttered and Eclipse would quickly become unusable. Additionally, the customer may become annoyed if he or she does not want to see the menu and continually has to use the multistep process outlined in Section 1.2.2.4, Customizing available actions, on page 14 to remove the menu. What to do?

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

256 CHAPTER 6 • Commands and Actions

Action sets are one answer to this question. They can be specified in the plugin.xml

as visible everywhere in every perspective. Using the new

IActionSetDescriptor.setInitiallyVisible()

method, you can programmatically override the visibility specified in the plugin.xml

so that the top-level menu no longer shows up in any newly opened perspectives. You can create a new action that removes your top-level menu from all current and future perspectives, by using setInitiallyVisible()

in conjunction with

IWorkbenchPage.hideActionSet()

. Your product could contain a checkbox option in your Preference page (see Section 12.2, Preference Page APIs, on page 487) that uses this action to show or hide your top-level menu.

Note:

We submitted a feature request and patch to Eclipse (see Bugzilla entry #39455 at

bugs.eclipse.org/bugs/show_bug.cgi?id=39455

) for the new

IActionSetDescriptor

API discussed here, and it was accepted and integrated into Eclipse 3.0 and 3.1. This is a good example of how users can contribute back to Eclipse (see Section 21.6.4, Submitting the change to Eclipse, on page 801), making it a better platform for everyone.

Another option is to tie your top-level menu or action set to a particular perspective (see Section 10.2.3, Adding action sets, on page 436). In this way, the menu and actions are only visible when that particular perspective is active. If one or more perspectives are particularly suited for the functionality added by your plug-in, then this may be your best approach.

What if an action is editor-related? Section 6.9.2, Defining an editor context action, on page 279, and Section 6.9.5, Defining an editor top-level action, on page 281, discuss adding menus and actions tied to a specific type of editor. With this approach, the top-level menu is only visible when an editor of that type is open.

The org.eclipse.ui.actionSetPartAssociations

extension point provides yet another option, allowing an action set to be displayed whenever one or more specific types of views or editors are open, regardless of the perspective in which they are opened. This is an excellent way to ensure that specific actions appear in a wide range of perspectives without having to explicitly add the actions to those perspectives.

The remainder of this chapter focuses on providing actions in view-specific menus, or as operations directed at specific types of objects rather than toplevel menus. In this way, the action will only be visible when it is needed and on the types of objects to which it applies. This approach avoids the top-level menu issue and prevents Eclipse from becoming cluttered. Various approaches for locally scoped actions are covered in subsequent sections.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.7

Object Actions

6.7

Object Actions

257

Suppose you want to make it easy for the user to add files and folders to the

Favorites

view. Object contributions are ideal for this because they appear in context menus only when the selection in the current view or editor contains an object compatible with that action (see Figure 6–15). In this manner, an object contribution is available to the user when he or she needs the action, yet not intrusive when the action does not apply. Object actions are very similar to menu contributions that have a locationURI

id (see Section 6.2.9, locationURI, on page 230) equal to “org.eclipse.ui.any.popup.”

6.7.1

Defining an object-based action

As in Section 6.6.1, Defining a workbench window menu, on page 243 and subsequent sections, use the

Extensions

page of the plug-in manifest editor to create the new object contribution. Click on the

Add

button to add an

org.eclipse.ui.popupMenus

extension, then add an

objectContribution

with the following attributes:

adaptable—

“true”

Indicates that objects that adapt to

IResource

are acceptable targets

(see Section 21.3, Adapters, on page 784).

id—

“com.qualityeclipse.favorites.popupMenu”

The unique identifier for this contribution.

nameFilter—

Leave blank

A wildcard filter specifying the names that are acceptable targets. For example, entering “*.java” would target only those files with names ending with

.java

. More on this in Section 6.7.2, Action filtering and enablement, on page 260.

objectClass—

“org.eclipse.core.resources.IResource”

The type of object that is an acceptable target. Use the

Browse...

button at the right of the

objectClass

field to select the existing org.eclipse. core.resources.IResource

class. If you want to create a new class, then click the

objectClass:

label to the left of the

objectClass

field.

Next, add an action to the new objectContribution

with the following attribute values, which are very similar to the action attributes covered in Section 6.6.3, Defining a menu item and toolbar button, on page 245.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

258

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 6 • Commands and Actions

Figure 6–15

Object action.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.7

Object Actions 259

class—

“com.qualityeclipse.favorites.actions. AddToFavoritesAction-

Delegate” The action delegate for the action that implements the org.eclipse.ui.IObjectActionDelegate

interface (see Section 6.7.3,

IObjectActionDelegate, on page 266). The class is instantiated using its no argument constructor, but can be parameterized using the

IExecutable-

Extension

interface (see Section 21.5, Types Specified in an Extension

Point, on page 793). The class can be specified in one of three different ways as in Section 6.6.6, Creating an action delegate, on page 249.

enablesFor—

“+”

An expression indicating when the action will be enabled (see Section

6.7.2, Action filtering and enablement, on page 260).

id—

“com.qualityeclipse.favorites.addToFavorites”

The unique identifier for the action.

label—

“Add to Favorites”

The text that appears in the context menu for the action.

menubarPath—

“additions”

The insertion point for the action (see Section 6.6.5, Insertion points, on page 248).

tooltip—

“Add the selected resource(s) to the Favorites view”

The text that appears when the mouse hovers over the menu item in the context menu.

Multiple actions appear in reverse order:

Buried in the org.eclipse.ui. popupMenu

extension point documentation is the following nugget of information: “If two or more actions are contributed to a menu by a single extension, the actions will appear in the reverse order of how they are listed in the plugin.xml

file. This behavior is admittedly unintuitive. However, it was discovered after the Eclipse Platform API was frozen. Changing the behavior now would break every plug-in that relies on the existing behavior.”

Other available action attributes not used in this example include:

helpContextId—

The identifier for the help context associated with the action (see Chapter 15, Implementing Help).

icon—

The associated image (see Section 6.6.4, Action images, on page 247).

overrideActionId—

An optional attribute specifying the identifier for an action that the action overrides.

state—

For an action with either the radio

or toggle

style, set the initial state to true

or false

(see Section 6.6.3, Defining a menu item and toolbar button, on page 245).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

260 CHAPTER 6 • Commands and Actions

style—

An attribute defining the visual form of the action. This is covered in Section 6.6.3, Defining a menu item and toolbar button, on page 245, with the exception that the pulldown

style does not apply to object contributions.

6.7.2

Action filtering and enablement

In keeping with the lazy loading plug-in theme, Eclipse provides multiple declarative mechanisms for filtering actions based on the context, and enabling visible actions only when appropriate. Because they are declared in the plug-in manifest, these mechanisms have the advantage that they do not require the plug-in to be loaded for Eclipse to use them.

6.7.2.1

Basic filtering and enablement

In Section 6.7.1, Defining an object-based action, on page 257, the

nameFilter

and

objectClass

attributes are examples of filters, while the

enablesFor

attribute determines when an action will be enabled. When the context menu is activated, if a selection does not contain objects with names that match the wildcard

nameFilter

or are not of a type specified by the

objectClass

attribute, none of the actions defined in that object contribution will appear in the context menu. In addition, the

enablesFor

attribute uses the syntax in Table 6–1 to define exactly how many objects need to be selected for a particular action to be enabled:

Table 6–1 enabledFor

attribute options

Syntax

!

?

+

multiple, 2+ n

*

Description

0 items selected.

0 or 1 items selected.

1 or more items selected.

2 or more items selected.

A precise number of items selected; for example, enablesFor="4" enables the action only when 4 are selected.

Any number of items selected.

The techniques listed in this table represent those most commonly used for limiting visibility and the enablement of actions; occasionally, a more refined approach is needed. The

visibility

and

filter

elements provide an additional means to limit an action’s visibility, while the

selection

and

enablement

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.7

Object Actions 261

elements provide a more flexible way to specify when an action is enabled.

Still further refinement of action enablement can be provided by using the selectionChanged()

method in the action delegate, as discussed in Section

6.6.6, Creating an action delegate, on page 249.

6.7.2.2

The visibility element

The

visibility

element provides an alternate and more powerful way to specify when an object contribution’s actions will be available to the user as compared with the object contribution’s

nameFilter

and

objectClass

. For example, an alternate way to specify filtering for the object contribution just described would be:

<objectContribution ...>

<visibility>

<objectClass

name="org.eclipse.core.resources.IResource"/>

</visibility>

...the other stuff here...

</objectContribution>

If the action is to be visible only for resources that are not read-only, then the visibility

object contribution might look like this:

<objectContribution ...>

<visibility>

<and>

<objectClass

name="org.eclipse.core.resources.IResource"/>

<objectState name="readOnly" value="false"/>

</and>

</visibility>

... the other stuff here ...

</objectContribution>

As part of the

<visibility>

element declaration, you can use nested

<and>

,

<or>

, and

<not>

elements for logical expressions, plus the following

Boolean expressions.

adapt—

Adapts the selected object to the specified type then uses the new object in any child expressions. For example, if you wanted to adapt the selected object (see Section 21.3, Adapters, on page 784) to a resource and then test some resource object state, the expression would look like this:

<adapt type="org.eclipse.core.resources.IResource">

<objectState name="readOnly" value="false"/>

</adapt>

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

262 CHAPTER 6 • Commands and Actions

The children of an

adapt

expression are combined using the

and

operator. The expression returns

EvaluationResult.NOT_LOADED

if either the adapter or the type referenced isn’t loaded yet. It throws an

ExpressionException

during evaluation if the type name doesn’t exist.

and—

Evaluates true

if all subelement expressions evaluate true

.

instanceof—

Compares the class of the selected object against a name.

This is identical to the

objectClass

element, except that

instanceof

can be combined with other elements using the

and

and

or

elements.

not—

Evaluates true

if its subelement expressions evaluate false

.

objectClass—

Compares the class of the selected object against a name as shown above.

objectState—

Compares the state of the selected object against a specified state similar to the

filter

element (see Section 6.7.2.3, The filter element, on page 263).

or—

Evaluates true

if one subelement expression evaluates true

.

pluginState—

Compares the plug-in state, indicating whether it is installed

or activated

. For example, an expression such as

<pluginState id="org.eclipse.pde" value="installed"/> would cause an object contribution to be visible only if the org.eclipse.pde

plug-in is installed, and an expression such as

<pluginState id="org.eclipse.pde" value="activated"/> would cause an object contribution to be visible only if the org.eclipse.pde

plug-in has been activated in some other manner.

systemProperty—

Compares the system property. For example, if an object contribution should only be visible when the language is English, then the expression would be:

<systemProperty name="user.language" value="en"/>

systemTest—

Identical to the

systemProperty

element, except that

systemTest

can be combined with other elements using the

and

and

or

elements.

test—

Evaluate the property state of the object. For example, if an object contribution should only be visible when a resource in a Java project is selected, then the expression would be:

<test

property="org.eclipse.debug.ui.projectNature"

value="org.eclipse.jdt.core.javanature"/>

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.7

Object Actions 263

The test expression returns

EvaluationResult.NOT_LOADED

if the property tester doing the actual testing isn’t loaded yet. The set of testable properties can be extended using the org.eclipse.core. expressions.propertyTesters

extension point. One example of this is the org.eclipse.debug.internal.ui.ResourceExtender

class.

6.7.2.3

The filter element

The

filter

element is a simpler form of the

objectState

element discussed previously. For example, if the object contribution was to be available for any file that is not read-only, then the object contribution could be expressed like this:

<objectContribution ...>

<filter name="readOnly" value="false"/>

... the other stuff here ...

</objectContribution>

As with the

objectState

element, the

filter

element uses the

IAction-

Filter

interface to determine whether an object in the selection matches the criteria. Every selected object must either implement or adapt to the

IAction-

Filter

interface (there is more on adapters in Chapter 20, Advanced Topics) and implement the appropriate behavior in the testAttribute()

method to test the specified name/value pair against the state of the specified object. For resources, Eclipse provides the following built-in state comparisons as listed in the org.eclipse.ui.IResourceActionFilter

class:

name—

Comparison of the filename. “*” can be used at the start or end to represent “one or more characters.”

extension—

Comparison of the file extension.

path—

Comparison against the file path. “*” can be used at the start or end to represent “one or more characters.”

readOnly—

Comparison of the read-only attribute of a file.

projectNature—

Comparison of the project nature.

persistentProperty—

Comparison of a persistent property on the selected resource. If the value is a simple string, then this tests for the existence of the property on the resource. If it has the format propertyName= propertyValue

, this obtains the value of the property with the specified name and tests it for equality with the specified value.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

264 CHAPTER 6 • Commands and Actions

projectPersistentProperty—

Comparison of a persistent property on the selected resource’s project with similar semantics to the persistentProperty listed above.

sessionProperty—

Comparison of a session property on the selected resource with similar semantics to the persistentProperty

just listed.

projectSessionProperty—

Comparison of a session property on the selected resource’s project with similar semantics to persistentProperty

.

6.7.2.4

The selection element

The

selection

element is a technique for enabling an individual action based on its name and type, similar to the way that the

nameFilter

and

objectClass

attributes determine whether all actions in an object contribution are visible.

For example, an alternate form for the object contribution using the

selection

element would be:

<objectContribution

objectClass="java.lang.Object"

id="com.qualityeclipse.favorites.popupMenu">

<action

label="Add to Favorites"

tooltip="Add the selected resource(s) to the

Favorites view"

class="com.qualityeclipse.favorites.actions.

AddToFavoritesActionDelegate"

menubarPath="additions"

enablesFor="+"

id="com.qualityeclipse.favorites.addToFavorites">

<selection

class="org.eclipse.core.resources.IResource"

name="*.java"/>

</action>

</objectContribution>

With this declaration, the object contribution’s actions would always be visible, but the

Add to Favorites

action would only be enabled if the selection contained only implementers of

IResource

that matched the name filter

*.java

.

6.7.2.5

The enablement element

The

enablement

element is a more powerful alternative to the

selection

element, supporting the same complex conditional logic expressions and comparisons as the

visibility

element (see Section 6.7.2.2, The visibility element,

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.7

Object Actions 265

on page 261). For example, an alternate object contribution declaration to the one outlined in the previous section, but that produces the same behavior would be:

<objectContribution

objectClass="java.lang.Object"

id="com.qualityeclipse.favorites.popupMenu">

<action

label="Add to Favorites"

tooltip="Add the selected resource(s)

to the Favorites view"

class="com.qualityeclipse.favorites.actions.

AddToFavoritesActionDelegate"

menubarPath="additions"

enablesFor="+"

id="com.qualityeclipse.favorites.addToFavorites">

<enablement>

<and>

<objectClass

name="org.eclipse.core.resources.IResource"/>

<objectState name="name" value="*.java"/>

</and>

</enablement>

</action>

</objectContribution>

6.7.2.6

Content-sensitive object contributions

There is a new mechanism for filtering actions based on resource content. This filtering is specified in the plug-in manifest (does not load your plug-in) and determines whether an action should be visible or enabled by inspecting a file’s content. For example, the

Run Ant...

command is associated with resources named build.xml

, but no others; what if your Ant script is located in a file called export.xml

? This new mechanism can determine whether the

Run

Ant...

command should be visible based on the first XML tag or DTD specified in the file. In this case, the org.eclipse.ant.core

plug-in defines a new antBuildFile

content type:

<extension point="org.eclipse.core.runtime.contentTypes">

<content-type

id="antBuildFile"

name="%antBuildFileContentType.name"

base-type="org.eclipse.core.runtime.xml"

file-names="build.xml"

file-extensions="macrodef,ent,xml"

priority="normal">

<describer

class="org.eclipse.ant.internal.core.

contentDescriber.AntBuildfileContentDescriber">

</describer>

</content-type>

</extension>

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

266 CHAPTER 6 • Commands and Actions

The preceding declaration associates the antBuildFile

content type with the

AntBuildfileContentDescriber

class, which determines whether XML content is Ant content. The antBuildFile

content type can then be used to specify action visibility and enablement, editor association, and more. For more about declaring and using your own content types, see the following:

• “Content Sensitive Object Contributions” at

eclipse.org > projects > The

Eclipse Project > Platform > UI > Development Resources > Content

Sensitive Object Contributions

, or browse

dev.eclipse.org/viewcvs/index. cgi/~checkout~/platform-ui-home/object-aware-contributions/ objCont.htm

.

• “Content types” in the Eclipse Help System at

Help > Help Contents >

Platform Plug-in Developer Guide > Programmer’s Guide > Runtime overview > Content types

• “A central content type catalog for Eclipse” at

dev.eclipse.org/viewcvs/ index.cgi/platform-core-home/documents/content_types.html?rev=1.11

• “Content types in Eclipse” at

eclipse.org/eclipse/platform-core/ planning/3.0/plan_content_types.html

6.7.3

IObjectActionDelegate

Getting back to the

Favorites

plug-in, the next task is to create an action delegate that implements the

IObjectActionDelegate

interface, which performs the operation behind the new

Add to Favorites

menu item. Create a new

AddToFavoritesActionDelegate

class as described next. Since the

Favorites

view is not fully functional, the action we are about to create will display a message rather than adding the selected items to the view (see Section 7.3.1,

Model command handlers, on page 313 for more implementation details).

Start by selecting the action defined in Section 6.7.1, Defining an objectbased action, on page 257 and then clicking on the

class:

label to the left of the class field. This opens the

New Java Class

wizard for creating a new Java class. Fill in the package and class name fields as necessary and be sure to add

IObjectActionDelegate

as the interface to implement, then click

Finish

to generate the new class.

Next, add a new field and modify the setActivePart()

method to cache the view or editor in which the action appears: private IWorkbenchPart targetPart; public void setActivePart(IAction action, IWorkbenchPart part) {

this.targetPart = part;

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.7

Object Actions 267

Finally, modify the run()

method to open a message dialog indicating that this action was successfully executed. As mentioned before, this action delegate will be fleshed out in Section 7.3.1, Model command handlers, on page 313.

public void run(IAction action) {

MessageDialog.openInformation(

targetPart.getSite().getShell(),

"Add to Favorites",

"Triggered the " + getClass().getName() + " action");

}

6.7.4

Creating an object-based submenu

Menus can be contributed to a context menu in a manner similar to adding actions. If three or more similar actions are contributed, then think about placing those actions in a submenu rather than in the context menu itself. The

Favorites

plug-in only adds one action to the context menu, but let’s place the action in a submenu rather than in the context menu itself.

To create the

Favorites

menu, right-click on the

com.qualityeclipse.favorites.popupMenu

object contribution in the

Extensions

page of the plugin manifest editor, and select

New > menu

. Enter the following values for this new menu:

id—

“com.qualityeclipse.favorites.popupSubMenu”

The identifier for the submenu.

label—

“Favorites”

The text appearing in the context menu as the name of the submenu.

path—

“additions”

The insertion point that determines the location in the context menu where the submenu will appear (see Section 6.6.5, Insertion points, on page 248).

Next, add a

groupMarker

to the menu with the name “content” and a

separator

with the name “additions” (see Section 6.6.2, Groups in a menu, on page 245). Finally, modify the

Add to Favorites

action’s attributes as follows so that the action will now be part of the new

Favorites

submenu:

label—

“Add”

The text appearing in the submenu as the name of the action.

menubarPath—

“com.qualityeclipse.favorites.popupSubMenu/content”

The insertion point that determines where the

Favorites

submenu action will appear (see Section 6.6.5, Insertion points, on page 248).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

268 CHAPTER 6 • Commands and Actions

6.7.5

Manually testing the new action

When the

Favorites Runtime Workbench

configuration is launched (see Section 2.6, Debugging the Product, on page 94), any context menu activated on a workbench resource will contain the

Favorites

menu with the

Add

submenu item. Selecting this submenu item displays a message box notifying you that the action was indeed triggered correctly.

The

Favorites

action is only displayed when one or more objects are selected due to enablesFor="+"

in the declaration shown in Section 6.7.2.5,

The enablement element, on page 264. This means that when you test the

Favorites

menu, you must have at least one project created in your runtime workbench and select at least one resource in the

Navigator

view when you activate the context menu. If you right click without selecting anything, you will only see an abbreviated context menu that does not have the

Favorites

menu.

6.7.6

Adding a test for the new action

The last task is to create an automated test that triggers the action and validates the result. Because this operation displays a message rather than adding a resource to the

Favorites

view, the code that validates the results of this test will have to wait until the next chapter (see Section 7.6, Testing, on page 345), where the

Favorites

view will be more fully developed. For now, create the following new test case in the

Favorites

test project and then modify the

Favorites

test suite to include this new test (see Section 6.6.8, Adding a test for the new action, on page 253).

You can begin by creating a new

AddToFavoritesTest

class that extends

AbstractFavoritesTest

and adds it to the

Favorites

test suite.

package com.qualityeclipse.favorites.test; import ...

public class AddToFavoritesTest extends AbstractFavoritesTest {

public AddToFavoritesTest(String name) {

super(name);

}

Next add a field and override the setUp()

method to create a temporary project called “TestProj” for the duration of this test. The tearDown() method deletes this temporary project when the test is complete.

To get these changes to properly compile, edit the

Favorites

test project’s plug-in manifest and add the org.eclipse.core.resources

plug-in to the

Required Plug-ins

list (see Figure 2–10 on page 79).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.7

Object Actions 269

protected IProject project; protected void setUp() throws Exception {

super.setUp();

IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();

project = root.getProject("TestProj");

project.create(null);

project.open(null);

} protected void tearDown() throws Exception {

super.tearDown();

// Wait for a bit for the system to catch up

// so that the delete operation does not collide

// with any background tasks.

delay(3000);

waitForJobs();

project.delete(true, true, null);

}

Finally, add the method that exercises the new menu item for adding objects to the

Favorites

view.

public void testAddToFavorites() throws CoreException {

// Show the resource navigator and select the project.

IViewPart navigator = PlatformUI.getWorkbench()

.getActiveWorkbenchWindow().getActivePage().showView(

"org.eclipse.ui.views.ResourceNavigator");

StructuredSelection selection = new StructuredSelection(project);

((ISetSelectionTarget) navigator).selectReveal(selection);

// Execute the action.

final IObjectActionDelegate delegate

= new AddToFavoritesActionDelegate();

IAction action = new Action("Test Add to Favorites") {

public void run() {

delegate.run(this);

}

};

delegate.setActivePart(action, navigator);

delegate.selectionChanged(action, selection);

action.run();

// Add code here at a later time to verify that the

// Add to Favorites action correctly added the

// appropriate values to the Favorites view.

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

270 CHAPTER 6 • Commands and Actions

6.8

View Actions

There are several ways in which actions can be manifested as part of a view.

For example, the

Members

view (part of the

Java Browsing

perspective; see

Figure 1–5) has toolbar buttons that appear in its title bar, a pull-down menu appearing at the right of the toolbar buttons, and a context menu containing yet more actions (see Figure 6–16). Actions are added to views using the extension point mechanism, similar to the discussions in the previous two sections. In addition, views can programmatically provide their own actions, bypassing the extension point mechanism (see Section 7.3, View Commands, on page 313).

Figure 6–16

View actions.

6.8.1

Defining a view context submenu

Similar to an objectContribution

, a viewerContribution

is used to add a menu item to a context menu. Whereas an objectContribution

causes a menu item to appear based on the selection in the viewer, a viewer-

Contribution

causes a menu item to appear based on the type of viewer. As with an objectContribution

, the viewerContribution

element can have a single visibility

subelement that takes control when all its other subelements are visible to the user (see Section 6.7.2.2, The visibility element, on page 261).

The

Favorites

submenu shows up in several different types of views, but not in the

Members

view. It would probably be more appropriate to use the objectContribution

approach discussed in Section 6.7, Object Actions, on page 257 to target objects contained in the

Members

view; however, use the viewerContribution

instead as an example.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.8

View Actions 271

Start by right-clicking on the popupMenu

extension that was added as part of Section 6.7.1, Defining an object-based action, on page 257 and select

New

> viewerContribution

. Fill in the following attributes for the newly added viewerContribution

.

id—

“com.qualityeclipse.favorites.membersViewPopup”

The identifier for this view contribution.

targetID—

“org.eclipse.jdt.ui.MembersView”

The identifier of the view’s context menu to which the submenu will be added (see Section 21.6, Modifying Eclipse to Find Part Identifiers, on page 797).

Add the

Favorites

menu to the

Members

view context menu by right-clicking on the viewerContribution

and selecting

New > menu

. Enter the following attributes for the new menu:

id—

“com.qualityeclipse.favorites.membersViewPopupSubMenu”

The identifier for the

Favorites

menu in the

Members

view context menu.

label—

“Favorites”

The text appearing in the

Members

view context menu as the name of the

Favorites

submenu.

path—

“additions”

The insertion point that determines the location in the

Members

view context menu where the

Favorites

submenu will appear (see Section

6.6.5, Insertion points, on page 248).

Next, add a

groupMarker

to the menu with the name “content” and a

separator

with the name “additions” (see Section 6.6.2, Groups in a menu, on page 245).

6.8.2

Defining a view context menu action

Finally, add an action to the

Favorites

submenu by right-clicking on the viewerContribution

, selecting

New > action

, and entering the following attributes for the new action:

class—

“com.qualityeclipse.favorites.actions.

AddToFavoritesActionDelegate”

The fully qualified name of the class that implements the org.eclipse. ui.IViewActionDelegate

interface and performs the action. In this case, the same action delegate used in the object contribution is used here as well, with a few modifications (see Section 6.8.3, IViewActionDelegate,

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

272 CHAPTER 6 • Commands and Actions

on page 273). The class is instantiated using its no argument constructor, but may be parameterized using the

IExecutableExtension

interface

(see Section 21.5, Types Specified in an Extension Point, on page 793).

id—

“com.qualityeclipse.favorites.addToFavoritesInMembersView”

The identifier for the action.

label—

“Add”

The name of the action as it appears in the

Favorites

submenu.

menubarPath—

“com.qualityeclipse.favorites.membersViewPopup

SubMenu/content”

The insertion point that determines the location in the

Favorites

submenu where the action will appear (see Section 6.6.5, Insertion points, on page 248). If the action is to appear directly in the

Members

view context menu rather than in the

Favorites

submenu, use the value

“additions” instead.

tooltip—

“Add selected member’s compilation unit to the Favorites view”

The text describing the action.

Other action attributes applicable but not used here include the following.

enablesFor—

An expression indicating when the action will be enabled

(see Section 6.7.2, Action filtering and enablement, on page 260).

helpContextId—

The identifier for the help context associated with the action (see Chapter 15, Implementing Help).

icon—

The associated image (see Section 6.6.4, Action images, on page 247).

overrideActionId—

An optional attribute specifying the identifier for an action that the action overrides.

state—

For an action with either the radio

or toggle

style, set the initial state to true

or false

(see Section 6.6.3, Defining a menu item and toolbar button, on page 245).

style—

An attribute defining the visual form of the action. This is covered in Section 6.6.3, Defining a menu item and toolbar button, on page 245, with the exception that the pulldown

style does not apply to object contributions.

You can also specify

selection

and

enablement

subelements to the action element similar to Section 6.7.2.4, The selection element, on page 264, and

Section 6.7.2.5, The enablement element, on page 264.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.8

View Actions 273

6.8.3

IViewActionDelegate

The action delegate for a view contribution must implement the org.eclipse.

ui.IViewActionDelegate

interface, so you need to modify the class

AddTo-

FavoritesActionDelegate

first introduced in Section 6.7.3, IObjectAction-

Delegate, on page 266. First, add the

IViewActionDelegate

interface to the implements clause, and then add the following init()

method to cache the target part. All other aspects of the action delegate stay the same.

public void init(IViewPart view) {

this.targetPart = view;

}

6.8.4

Defining a view toolbar action

In addition to being in the

Favorites

submenu of the view context menu, the action should appear as a toolbar button in the

Members

view (see Section

7.3.3, Toolbar buttons, on page 318, to programmatically add a toolbar button to a view). As in Section 6.6.1, Defining a workbench window menu, on page 243, and subsequent sections, use the

Extensions

page of the plug-in manifest editor to create the new view contribution. Click the

Add

button to add an

org.eclipse.ui.viewActions

extension, then add a

viewContribution

to that with the following attributes.

id—

“com.qualityeclipse.favorites.membersViewActions”

The identifier for the view contribution.

targeted—

“org.eclipse.jdt.ui.MembersView”

The identifier of the view to which the actions are added.

Next, add an action to the

Members

view toolbar by right-clicking on the viewContribution

, selecting

New > action

, and then entering the attributes shown below for the new action. All the objectContribution

action attributes listed in Section 6.7.1, Defining an object-based action, on page 257, also apply to viewContribution

actions.

class—

“com.qualityeclipse.favorites.actions.

AddToFavoritesActionDelegate”

The fully qualified name of the class that implements the org.eclipse. ui.IViewActionDelegate

interface and performs the action. In this case, the same action delegate used in the object contribution is used here as well, with a few modifications (see Section 6.8.3, IViewActionDelegate, on page 273).

icon—

“icons/sample.gif”

The icon displayed in the view’s toolbar for the action.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

274 CHAPTER 6 • Commands and Actions

id—

“com.qualityeclipse.favorites.addToFavoritesInMembersView”

The identifier for the action.

toolbarPath—

“additions”

The insertion point that determines the location in the

Members

view’s toolbar where the action will appear (see Section 6.6.5, Insertion points, on page 248).

tooltip—

“Add the selected items in the Members view to the Favorites view”

The text describing the action appearing in the hover help when the cursor is positioned over the toolbar button associated with the action.

6.8.5

Defining a view pull-down submenu and action

The same viewContribution

extension described in the previous section is used to add a view pull-down submenu (see Section 7.3.2, Context menu, on page 314 to programmatically create a view pull-down menu). Typically, a view pull-down menu contains actions, such as sorting and filtering, specific to that view. To add the

Favorites

submenu and action to the

Members

view pull-down menu (not that it really needs to be there in addition to everywhere else its been added), right-click on the viewContribution

extension, select

New > menu,

and then set the attributes of the newly created menu as follows:

id—

“com.qualityeclipse.favorites.membersViewPulldownSubMenu”

The identifier for the

Favorites

menu in the

Members

view.

label—

“Favorites”

The text appearing in the

Members

view pull-down menu as the name of the

Favorites

submenu.

path—

“additions”

The insertion point, which determines the location in the

Members

view pull-down menu, where the

Favorites

submenu will appear (see Section

6.6.5, Insertion points, on page 248).

Next, add a

groupMarker

to the menu with the name “content” and a

separator

with the name “additions” (see Section 6.6.2, Groups in a menu, on page 245). Finally, the action defined in Section 6.8.4, Defining a view toolbar action, on page 273 can be modified to define a menu item in the menu just created as well as the toolbar button it already described by modifying some of its attributes.

label—

“Add”

The name of the action appearing in the

Favorites

submenu.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.8

View Actions 275

menubarPath—

“com.qualityeclipse.favorites.

membersViewPulldownSubMenu/content”

The insertion point, which determines the location in the

Favorites

submenu, where the action will appear (see Section 6.6.5, Insertion points, on page 248). If the action was to appear directly in the

Members

view pull-down menu rather than in the

Favorites

submenu, you would have to use a value of “additions” instead.

6.8.6

Manually testing the new actions

When the modifications to the plug-in manifest and the action delegate are complete, launching the

Runtime Workbench

and inspecting the

Members

view will show the new

Favorites

submenu and the

Add to Favorites

toolbar button.

6.8.7

Adding tests for the new actions

There is no need for any additional test cases other than the ones created in

Section 6.7.6, Adding a test for the new action, on page 268 because the same action delegate is being reused. After the

Favorites

view is fleshed out as part of Chapter 7, Views, more tests for new types of selections can be added.

6.8.8

View context menu identifiers

The context menu identifiers for some Eclipse views follow. For more information on how this list was generated, see Section 21.6, Modifying Eclipse to

Find Part Identifiers, on page 797.

Ant

id = org.eclipse.ant.ui.views.AntView

menuId = org.eclipse.ant.ui.views.AntView

Bookmarks

id = org.eclipse.ui.views.BookmarkView

menuId = org.eclipse.ui.views.BookmarkView

Breakpoints

id = org.eclipse.debug.ui.BreakpointView

menuId = org.eclipse.debug.ui.BreakpointView

Console

id = org.eclipse.ui.console.ConsoleView

menuId = org.eclipse.ui.console.ConsoleView

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

276 CHAPTER 6 • Commands and Actions

Debug

id = org.eclipse.debug.ui.DebugView

menuId = org.eclipse.debug.ui.DebugView

Display

id = org.eclipse.jdt.debug.ui.DisplayView

menuId = org.eclipse.jdt.debug.ui.DisplayView

Expressions

id = org.eclipse.debug.ui.ExpressionView

menuId = org.eclipse.debug.ui.VariableView.detail

menuId = org.eclipse.debug.ui.ExpressionView

Members

id = org.eclipse.jdt.ui.MembersView

menuId = org.eclipse.jdt.ui.MembersView

Memory

id = org.eclipse.debug.ui.MemoryView

menuId = org.eclipse.debug.ui.MemoryView.MemoryBlocksTreeViewPane

Navigator

id = org.eclipse.ui.views.ResourceNavigator

menuId = org.eclipse.ui.views.ResourceNavigator

Package Explorer

id = org.eclipse.jdt.ui.PackageExplorer

menuId = org.eclipse.jdt.ui.PackageExplorer

Packages

id = org.eclipse.jdt.ui.PackagesView

menuId = org.eclipse.jdt.ui.PackagesView

Problems

id = org.eclipse.ui.views.ProblemView

menuId = org.eclipse.ui.views.ProblemView

Projects

id = org.eclipse.jdt.ui.ProjectsView

menuId = org.eclipse.jdt.ui.ProjectsView

Registers

id = org.eclipse.debug.ui.RegisterView

menuId = org.eclipse.debug.ui.VariableView.detail

menuId = org.eclipse.debug.ui.RegisterView

Tasks

id = org.eclipse.ui.views.TaskList

menuId = org.eclipse.ui.views.TaskList

Threads and Monitors

id = org.eclipse.jdt.debug.ui.MonitorsView

menuId = org.eclipse.jdt.debug.ui.MonitorsView

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.9

Editor Actions 277

Types

id = org.eclipse.jdt.ui.TypesView

menuId = org.eclipse.jdt.ui.TypesView

Variables

id = org.eclipse.debug.ui.VariableView

menuId = org.eclipse.debug.ui.VariableView.detail

menuId = org.eclipse.debug.ui.VariableView

6.9

Editor Actions

Actions can be added to editors in a way that is similar to how they are added to views. For example, the Java editor has a context menu, so naturally the

Favorites

action should show up there regardless of whether it’s really needed

(see Figure 6–17). In addition, editors can add actions to themselves bypassing the standard extension point mechanism. Some related sections include the following:

Figure 6–17

Editor actions.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

278 CHAPTER 6 • Commands and Actions

• Section 8.5, Editor Commands, on page 381 for more on editor actions

• Section 14.2.4, Marker resolution—quick fix, on page 556 for an example of manipulating text in an editor

• Chapter 17, Creating New Extension Points, for more on extension points

6.9.1

Defining an editor context menu

To add the

Favorites

menu to the Java editor’s context menu, revisit the

popupMenus

extension declared in Section 6.7.1, Defining an object-based action, on page 257, right-click, and then select

New > viewerContribution

.

Enter the following attributes for the new viewer contribution. As with object contributions, the visibility

subelement can be used to control when the menu and actions appear in the editor’s context menu (see Section 6.7.2.2,

The visibility element, on page 261).

id—

“com.qualityeclipse.favorites.compilationUnitEditorPopup”

The identifier for the viewer contribution.

targetID—

“#CompilationUnitEditorContext”

The identifier of the editor’s context menu to which the actions will be added (see Section 21.6, Modifying Eclipse to Find Part Identifiers, on page 797).

Next, create the

Favorites

submenu in the editor’s context menu by rightclicking on the new viewer contribution extension and selecting

New > menu

.

Enter the following attributes for the new menu declaration.

id—

“com.qualityeclipse.favorites.compilationUnitEditorPopup

SubMenu”

The identifier for the

Favorites

menu in the editor’s context menu.

label—

“Favorites”

The text appearing in the editor’s context menu as the name of the

Favorites

submenu.

path—

“additions”

The insertion point that determines the location in the editor’s context menu where the

Favorites

submenu will appear (see Section 6.6.5, Insertion points, on page 248).

Finally, add a

groupMarker

to the menu with the name “content” and a

separator

with the name “additions” (see Section 6.6.2, Groups in a menu, on page 245).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.9

Editor Actions 279

6.9.2

Defining an editor context action

Add the

Add to Favorites

action to the

Favorites

submenu by right-clicking on the viewer contribution defined in Section 6.9.1, Defining an editor context menu, on page 278 and selecting

New > action

. Enter the following action attributes.

class—

“com.qualityeclipse.favorites.actions.

AddToFavoritesActionDelegate”

The fully qualified name of the class that implements the org.eclipse. ui.IEditorActionDelegate

interface and performs the action. The same action delegate used in the object contribution is used here as well, with a few modifications (see Section 6.7.3, IObjectActionDelegate, on page 266). The class is instantiated using its no argument constructor, but can be parameterized using the

IExecutableExtension

interface (see

Section 21.5, Types Specified in an Extension Point, on page 793).

id—

“com.qualityeclipse.favorites.

addToFavoritesInCompilationUnitEditor”

The identifier for the action.

label—

“Add”

The name of the action appearing in the

Favorites

submenu.

menubarPath—

“com.qualityeclipse.favorites.

compilationUnitEditorPopupSubMenu/content”

The insertion point that determines the location in the

Favorites

submenu where the action will appear (see Section 6.6.5, Insertion points, on page 248). To make the action appear directly in the editor’s context menu rather than in the

Favorites

submenu, use a value of “additions” instead.

Other action attributes not listed here are the same as for the viewer contributions outlined in Section 6.8.2, Defining a view context menu action, on page 271.

6.9.3

IEditorActionDelegate

The action delegate for an editor contribution must implement the org.eclipse.ui.IEditorActionDelegate

interface, so you must modify the

AddToFavoritesActionDelegate

class first introduced in Section 6.7.3,

IObjectActionDelegate, on page 266.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

280 CHAPTER 6 • Commands and Actions

First add the

IEditorActionDelegate

interface to the implements clause, and then add the following setActiveEditor()

method to cache the target part. All other aspects of the action delegate can stay the same.

public void setActiveEditor(IAction action, IEditorPart editor) {

this.targetPart = editor;

}

6.9.4

Defining an editor top-level menu

Using the org.eclipse.ui.editorActions

extension point, you can define a workbench window menu and toolbar button that are only visible when an editor of a particular type is open. As discussed in Section 6.6.9, Discussion, on page 255, think twice before adding menus or toolbar buttons to the workbench window itself. The

Favorites

example doesn’t really need this, but the following takes you through the process as a matter of course.

To start, click the

Add

button in the

Extensions

page of the plug-in manifest editor and add a new org.eclipse.ui.editorActions

extension.

Right-click on the new extension and select

New > editorContribution,

then enter the following editorContribution

attributes.

id—

“com.qualityeclipse.favorites.compilationUnitEditorActions”

The identifier for the editor contribution.

targetID—

“org.eclipse.jdt.ui.CompilationUnitEditor”

The identifier of the type of editor that should be open for these menus and actions to be visible.

Add the

Favorites

menu by right-clicking on editorContribution

and selecting

New > menu

. Enter the following attributes for the new menu.

id—

“com.qualityeclipse.favorites.compilationUnitEditorPopup

SubMenu”

The identifier for the

Favorites

menu.

label—

“Favorites”

The text appearing in the workbench window menu bar as the name of the

Favorites

submenu.

path—

“additions”

The insertion point that determines the location in the workbench window menu bar where the

Favorites

submenu will appear (see Section

6.6.5, Insertion points, on page 248).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.9

Editor Actions 281

Finally, add a

groupMarker

to the menu with the name “content” and a

separator

with the name “additions” (see Section 6.6.2, Groups in a menu, on page 245).

6.9.5

Defining an editor top-level action

Add an action to the

Favorites

menu by right-clicking on the editor-

Contribution

, selecting

New > action

and entering the following attributes shown for the new action. Similar to object contributions, the selection

and enablement

elements can be used to limit the visibility and enablement of the action (see Section 6.7.2.4, The selection element, on page 264, and Section

6.7.2.5, The enablement element, on page 264).

class—

“com.qualityeclipse.favorites.actions.

AddToFavoritesActionDelegate”

The fully qualified name of the class that implements the org.eclipse. ui.IEditorActionDelegate

interface and performs the action. In this case, the action delegate used in the object contribution was modified in

Section 6.9.3, IEditorActionDelegate, on page 279, and thus can be used here as well.

id—

“com.qualityeclipse.favorites.

addToFavoritesInCompilationUnitEditor”

The identifier for the action.

label—

“Add”

The text appearing in the

Favorites

menu for the action.

menubarPath—

“com.qualityeclipse.favorites.

compilationUnitEditorPopupSubMenu/content”

The insertion point that indicates where the menu will be positioned in the menu bar (see Section 6.6.5, Insertion points, on page 248).

Other available action attributes that are not used in this example include the following:

definitionId—

The command identifier for the action, allowing a key sequence to be associated with the action. For more details, see Section

6.11, RFRS Considerations, on page 286.

enablesFor—

An expression indicating when the action will be enabled

(see Section 6.7.2, Action filtering and enablement, on page 260). If blank, then the action is always active unless overridden programmatically by using the

IAction

interface.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

282 CHAPTER 6 • Commands and Actions

helpContextId—

The identifier for the help context associated with the action (see Section 15.3.1, Associating context IDs with items, on page 592).

hoverIcon—

An image displayed when the mouse

hovers

over the action without being clicked (see Section 6.6.4, Action images, on page 247 for more detail).

icon—

The associated image (see Section 6.6.4, Action images, on page 247 for more detail).

state—

For an action with either the radio

or toggle

style, set the initial state to true

or false

(see Section 6.6.3, Defining a menu item and toolbar button, on page 245).

style—

An attribute that defines the visual form of the action and that has one of the following values: push

—A normal menu or toolbar item (the default style).

radio

—A radio button-style menu or toolbar item where only one item at a time in a group can be active (see the state

attribute).

toggle

—A checked menu item or toggle tool item (see the state attribute).

toolbarPath—

The insertion point that indicates where the button will appear in the toolbar (see Section 6.6.5, Insertion points, on page 248 for more detail).

tooltip—

The text appearing when the mouse hovers over the action’s icon in the workbench toolbar.

6.9.6

Defining an editor toolbar action

Similar to the way that workbench menu actions can be displayed as toolbar buttons, the editor action defined in Section 6.9.5, Defining an editor top-level action, on page 281 can be modified to show up in the workbench window toolbar by making the following modifications to its attributes: icon

“icons/sample.gif” toolbarPath

“Normal/additions” tooltip

“Add the editor selection to the Favorites view”

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.9

Editor Actions 283

6.9.7

Adding tests for the new actions

As stated in Section 6.8.7, Adding tests for the new actions, on page 275, tests will be added in Chapter 7, Views, for new types of selections to the same test case that was outlined in Section 6.7.6, Adding a test for the new action, on page 268.

6.9.8

Editor context menu identifiers

The following are the context menu identifiers for some Eclipse editors. For more information on how this list was generated, see Section 21.6, Modifying

Eclipse to Find Part Identifiers, on page 797.

Ant Editor

( build.xml

) id = org.eclipse.ant.ui.internal.editor.AntEditor

menuId = #TextEditorContext menuId = #TextRulerContext

Class File Editor

(

*.class

) id = org.eclipse.jdt.ui.ClassFileEditor

menuId = #ClassFileEditorContext menuId = #ClassFileRulerContext

Compilation Unit Editor

(

*.java

) id = org.eclipse.jdt.ui.CompilationUnitEditor

menuId = #CompilationUnitEditorContext menuId = #CompilationUnitRulerContext

Default Text Editor

id = org.eclipse.ui.DefaultTextEditor

menuId = #TextEditorContext menuId = #TextRulerContext

Snippet Editor

(

*.jpage

) id = org.eclipse.jdt.debug.ui.SnippetEditor

menuId = #JavaSnippetEditorContext menuId = #JavaSnippetRulerContext

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

284 CHAPTER 6 • Commands and Actions

6.10

Actions and Key Bindings

Both workbench actions and editor actions can have accelerator keys associated with them (see Section 7.3.5, Keyboard commands, on page 320 for how to programmatically associate accelerator keys). Originally, the accelerator was specified as part of the action declaration, but that approach did not prevent multiple actions from declaring the same accelerator and did not allow the user to change key bindings. The new approach involves associating key bindings (see Section 6.4, Key Bindings, on page 238) and actions (see Figure

6–18).

Figure 6–18

Key binding declaration.

6.10.1

Associating commands with actions

Defining an accelerator for the

Favorites

example involves

• creating a new command (see Section 6.1, Commands, on page 216)

• creating a new key binding (see Section 6.4, Key Bindings, on page 238)

• modifying the editor action defined in Section 6.9.5, Defining an editor top-level action, on page 281 to reference the command

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.10

Actions and Key Bindings 285

Once the first two steps are complete, select the editor action in the plug-in manifest and modify the

definitionId

attribute to have the value “com.qualityeclipse.favorites.commands.add” so that the action now references the command and associated key binding.

6.10.2

Keyboard accessibility

The keyboard can be used to select menu items in the workbench window. For example, if you press and release the

Alt

key and then press and release

F

(or press

Alt+F

), you will see the workbench window

File

menu drop down. If you look closely, you will see an underscore under at most one letter in each menu label and menu item label. When you are in this menu selection mode, pressing the letter with the underscore will activate that menu or menu command.

Under some platforms, such as Windows XP, these underscores are not visible unless you activate menu selection mode.

In your plug-in manifest, you can specify which character in a menu’s or menu item’s label should have an underscore by preceding that character with the “&” character. For example, in the following declaration, the “&” before the letter “r” in the word “Favorites” causes that letter to have an underscore when you activate the menu selection mode (see Figure 6–19).

Figure 6–19

Plug-in manifest editor showing

&

for keyboard accessibility.

When viewing the XML for this same declaration, the

&

character appears as

&amp;

because the

&

character has special meaning in XML.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

286 CHAPTER 6 • Commands and Actions

<action

class="com.qualityeclipse.....OpenFavoritesViewActionDelegate"

icon="icons/sample.gif"

id="com.qualityeclipse.favorites.openFavoritesView"

label="Open Favo

&amp;

rites View"

menubarPath="com.qualityeclipse.favorites.workbenchMenu/content"

style="push"

toolbarPath="Normal/additions"

tooltip="Open the Favorites view in the current workbench page"/>

If you use this same approach with the

Favorites

menu declaration (see Section

6.6.1, Defining a workbench window menu, on page 243), you can use a sequence of keystrokes to open the

Favorites

view without touching the mouse.

• Press and release the

Alt

key to enter menu selection mode.

• Press and release “v” to get the

Favorites

menu to drop down.

• Press and release “r” to activate the

Open Favorites View

action.

or

• Press

Alt+V

to get the

Favorites

menu to drop down.

• Press and release “r” to activate the

Open Favorites View

action.

Ready for Rational Software

Starting with this chapter, we will list IBM’s relevant RFRS certification requirements and briefly discuss what is required to pass each test. We will also endeavor to make sure that the ongoing

Favorites

example complies with any relevant requirements. The rule definitions themselves are quoted with permission from IBM’s official

Ready for IBM Rational Software Integration Requirements

document. To obtain more information about the RFRS program, see Appendix B, Ready for Rational Software, or visit the IBM Web site at

www.developer.ibm.com/ isv/rational/readyfor.html

.

6.11

RFRS Considerations

The “User Interface” section of the

RFRS Requirements

includes one best practice dealing with actions. It is derived from the Eclipse UI Guidelines.

6.11.1

Global action labels

User Interface Guideline #3.3

is a

best practice

that states:

(RFRS 5.3.5.1)

Adopt the labeling terminology of the workbench for

new

,

delete

,

add

, and

remove

actions. For consistency, any action that has a similar behavior to existing actions in the workbench should adopt the same terminology. When creating a resource, the term “

New

” should be used in an

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

6.12

Summary 287

6.12

action or wizard. For instance, “

New File

”, “

New Project

”, and “

New

Java Class

”. The term “

Delete

” should be used when deleting an existing resource. When creating an object inside a resource (e.g., a tag in an XML file), the term “

Add

” should be used; the user is adding something to an existing resource. The term “

Remove

” should be used to remove an object from a resource.

To pass this test, create a list of the actions defined by your application and demonstrate their use. Show that the terms

New

,

Delete

,

Add

, and

Remove

are used properly and consistently with the workbench. In the case of the examples presented earlier in this chapter, it is preferable to show the

Favorites

editor actions (see Figure 6–17) and describe their use to the reviewers.

Summary

An Eclipse user can trigger commands by using the workbench’s pull-down menus or toolbar or by using the context menus defined for various views.

Each of these is an example of an action. This chapter discussed how to create various actions and how to control their visibility and enablement state using filters.

References

Chapter source (see Section 2.9, Book Samples, on page 105).

Menus Extension Mapping

http://wiki.eclipse.org/Menus_Extension_Mapping

Menu Contributions

http://wiki.eclipse.org/Menu_Contributions

Command Core Expressions

http://wiki.eclipse.org/Command_Core_Expressions

Platform Command Framework

http://wiki.eclipse.org/Platform_Command_Framework

Configuring and adding menu items

https://www.ibm.com/developerworks/library/os-eclipse-3.3menu

Screencast: Using Property Testers in the Eclipse Command Framework

http://konigsberg.blogspot.com/2008/06/

screencast-using-property-testers-in.html

D’Anjou, Jim, Scott Fairbrother, Dan Kehn, John Kellerman, and Pat McCarthy,

The Java Developer’s Guide to Eclipse, Second Edition

. Addison-Wesley,

Boston, 2004.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 7

Views

Many plug-ins either add a new Eclipse view or enhance an existing one as a way to provide information to the user. This chapter covers creating a new view, modifying the view to respond to selections in the active editor or other views, and exporting the view’s selection to the rest of Eclipse. In addition, it briefly touches on the differences between editors and views, and when one should be used instead of the other.

Views must implement the org.eclipse.ui.IViewPart

interface. Typically, views are subclasses of org.eclipse.ui.part.ViewPart

and thus indirectly subclasses of org.eclipse.ui.part.WorkbenchPart

, inheriting much of the behavior needed to implement the

IViewPart

interface (see Figure 7–1).

Views are contained in a

view site

(an instance of the type, org.eclipse.ui.IViewSite

), which in turn is contained in a

workbench page

(an instance of org.eclipse.ui.IWorkbenchPage

). In the spirit of lazy initialization, the

IWorkbenchPage

instance holds on to instances of org.eclipse.ui.IViewReference

rather than the view itself so that views can be enumerated and referenced without actually loading the plug-in that defines the view.

289

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

290

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 7 • Views

Figure 7–1

ViewPart classes.

Views share a common set of behaviors with editors via the superclass org.eclipse.ui.part.WorkbenchPart

and the org.eclipse.ui.

IWorkbenchPart

interface but have some very important differences. Any action performed in a view should immediately affect the state of the workspace and the underlying resource(s), whereas editors follow the classic openmodify-save paradigm.

Editors appear in one area of Eclipse, while views are arranged around the outside of the editor area (see Section 1.2.1, Perspectives, views, and editors, on page 5). Editors are typically resource-based, while views may show information about one resource, multiple resources, or even something totally unrelated to resources at all.

Because there are potentially hundreds of views in the workbench, they are organized into categories. The

Show View

dialog presents a list of views organized by category (see Section 2.5, Installing and Running the Product, on page 92) so that a user can more easily find a desired view.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.1

View Declaration 291

7.1

View Declaration

Three steps are involved in creating a new view:

• Define the view category in the plug-in manifest file.

• Define the view in the plug-in manifest file.

• Create the view part containing the code.

One way to do all this at once is to create the view when the plug-in itself is being created (see Section 2.2.3, Define the view, on page 75). If the plug-in already exists, then this becomes a three-step process.

7.1.1

Declaring a view category

First, to define a new view category, edit the plug-in manifest and navigate to the

Extensions

page. Click the

Add…

button to add the org.eclipse.

ui.views

extension if it is not already present (see Figure 7–2). Right-click the org.eclipse.ui.views

extension and select

New > category

to add a new category if one does not exist already.

Figure 7–2

The New Extension wizard with the org.eclipse.ui.views extension point selected.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

292 CHAPTER 7 • Views

The properties for this category can be modified in the plug-in manifest editor (see Figure 7–3). For the category containing the

Favorites

view, the attributes would be as follows:

id—

“com.qualityeclipse.favorites”

The unique identifier for the category.

name—

“QualityEclipse”

The human-readable name for the category that appears in the

Show

View

dialog (see Figure 2–20 on page 93).

Figure 7–3

The Plug-in manifest editor showing the Quality Eclipse view category.

7.1.2

Declaring a view

When the view category has been defined, right-click again on the org.eclipse.ui.views

extension in the

Extensions

page and select

New > view

to define a new view. Use the

Extension Element Details

section of the editor (see Figure 7–4) to modify the attributes of the view. For the

Favorites

view, the attributes would be as follows:

category—

“com.qualityeclipse.favorites”

The unique identifier for the view category that contains this view.

class—

“com.qualityeclipse.favorites.views.FavoritesView”

The fully qualified name of the class defining the view and implementing the org.eclipse.ui.IViewPart

interface. The class is instantiated using its no argument constructor, but can be parameterized using the

IExecutableExtension

interface (see Section 21.5, Types Specified in an Extension Point, on page 793).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part 293

icon—

“icons/sample.gif”

The image displayed in the upper left corner of the view and in the

Show

View

dialog (see Figure 2–20 on page 93). Similar to an action image (see

Section 6.6.4, Action images, on page 247), this path is relative to the plug-in’s installation directory.

id—

“com.qualityeclipse.favorites.views.FavoritesView”

The unique identifier for this view.

name—

“Favorites”

The human-readable name for the view displayed in the view’s title bar and the

Show View

dialog (see Figure 2–20 on page 93).

Figure 7–4

The Plug-in manifest editor view showing the Favorites view.

7.2

View Part

The code defining a view’s behavior is found in a class implementing the org.eclipse.ui.IViewPart

interface, typically by subclassing the org.eclipse.ui.part.ViewPart

abstract class.

Section 2.3.3, The Favorites view, on page 84 reviewed the

Favorites

view in its simplest form.

7.2.1

View methods

IViewPart and its supertypes define the following methods.

createPartControl(Composite)

—This method is

required

because it creates the controls comprising the view. Typically, this method simply calls more finely grained methods such as createTable

, createSort-

Actions

, createFilters

, and so on (see the next section).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

294 CHAPTER 7 • Views

dispose()

—Cleans up any platform resources, such as images, clipboard, and so on, that were created by this class. This follows the

if you create it, you destroy it

theme that runs throughout Eclipse.

getAdapter(Class)

—Returns the adapter associated with the specified interface so that the view can participate in various workbench behaviors (see below

and

see Section 21.3, Adapters, on page 784).

saveState(IMemento)

—Saves the local state of this view, such as the current selection, current sorting, current filter, and so on (see Section

7.5.1, Saving local view information, on page 340).

setFocus()

—This method is

required

because it sets focus to the appropriate control within the view (see the next section).

A view can participate in various workbench behaviors by either directly implementing or having a getAdapater(Class)

method that returns an instance of a specific interface. A few of these interfaces are listed below:

IContextProvider

—Dynamic context providers are used for providing focused dynamic help that changes depending on the various platform states (see Section 15.3.4, Context extension point, on page 595 for more on help contexts).

IContributedContentsView

—Used by

PropertySheet

used to identify workbench views which allow other parts (typically the active part) to supply their contents.

IShowInSource

—Enables a view to provide a context when the user selects

Navigate > Show In > ...

IShowInTarget

—Causes a view to appear in the

Navigate > Show In

submenu and the

IShowInTarget#show(ShowInContext)

method to be called when the user makes a selection in that submenu.

IShowInTargetList

—Specifies identifiers of views that should appear in the

Navigate > Show In

submenu.

7.2.2

View controls

Views can contain any type and number of controls, but typically, a view such as the

Favorites

view contains a single table or tree control. The

Favorites

view could use the SWT table widget directly ( org.eclipse.swt.widgets.Table

—see Section 4.2.6.6, Table, on page 162); however, the higher-level JFace table viewer ( org.eclipse.jface.viewers.TableViewer

—see Section

5.1.7, Table Viewer class, on page 203) wraps the SWT table widget and is easier to use. It handles much of the underlying grunt work, allowing you to

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part 295

add, select, and remove model objects directly rather than dealing with the underlying instances of

TableItem

.

With this in mind, let’s start by adding three new fields to the

FavoritesView

class: private TableColumn typeColumn; private TableColumn nameColumn; private TableColumn locationColumn;

You should continue to enhance the createPartControl()

method that was generated as part of building the

Favorites

plug-in (see Section 2.3.3, The

Favorites view, on page 84) so that the table has three columns. The

SWT.FULL_SELECTION

style bit causes the entire row to be highlighted when the user makes a selection.

viewer = new TableViewer(parent,

SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION); final Table table = viewer.getTable(); typeColumn = new TableColumn(table, SWT.LEFT); typeColumn.setText(""); typeColumn.setWidth(18); nameColumn = new TableColumn(table, SWT.LEFT); nameColumn.setText("Name"); nameColumn.setWidth(200); locationColumn = new TableColumn(table, SWT.LEFT); locationColumn.setText("Location"); locationColumn.setWidth(450); table.setHeaderVisible(true); table.setLinesVisible(false); viewer.setContentProvider(new ViewContentProvider()); viewer.setLabelProvider(new ViewLabelProvider()); viewer.setInput(getViewSite());

Later, when you want to get more involved, auto-size the columns in the table (see Section 7.8, Auto-sizing Table Columns, on page 348).

7.2.3

View model

A view can have its own internal model such as the

Favorites

view, it can use existing model objects such as an

IResource

and its subtypes, or it may not have a model at all. In this case, create:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

296 CHAPTER 7 • Views

IFavoriteItem

An interface used to abstract the differences between different types of

Favorites

objects.

FavoritesManager

Holds

Favorites

model objects.

FavoriteResource

A class adapting a resource to the

IFavoriteItem interface.

FavoriteJavaElement

A class adapting a Java element to the

IFavoriteItem

interface.

The

IFavoriteItem

interface hides the differences between various types of

Favorites

objects. This enables the

FavoritesManager

and

Favorites-

View

to deal with all

Favorites

items in a uniform manner. The naming convention followed, which is used in many places throughout Eclipse, is to prefix an interface with a capital “I” so that the interface name is

IFavoriteItem rather than

FavoriteItem

, as one would expect (see Section 7.4.2, Adaptable objects, on page 337 for more on

IAdaptable

).

package com.qualityeclipse.favorites.model; public interface IFavoriteItem

extends IAdaptable

{

String getName();

void setName(String newName);

String getLocation();

boolean isFavoriteFor(Object obj);

FavoriteItemType getType();

String getInfo();

static IFavoriteItem[] NONE = new IFavoriteItem[] {};

}

Later,

Favorites

items will be serialized so that they can be placed on the clipboard (see Section 7.3.7, Clipboard commands, on page 322) and saved to disk between Eclipse workbench sessions (see Section 7.5.2, Saving global view information, on page 343). To this end, the getInfo()

method for each item must return enough state so that the item can be correctly reconstructed later.

The

FavoriteItemType

object returned by the getType()

method is a type-safe enumeration that can be used for sorting and storing

Favorites

objects. It has a human-readable name associated with it for display purposes.

Introducing the

FavoriteItemType

rather than a simple

String

or int allows the sort order to be separated from the human-readable name associated with a type of

Favorites

object.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part 297

package com.qualityeclipse.favorites.model; import ...

public abstract class FavoriteItemType

implements Comparable

{

private static final ISharedImages PLATFORM_IMAGES =

PlatformUI.getWorkbench().getSharedImages();

Next, you need to add a constructor plus some fields and accessors to the

FavoriteItemType

used by the

Favorites

view to sort and display

Favorites

items. Since the workbench already provides images for various types of resources, the

FavoriteItemType

object simply returns the appropriate shared image. To return custom images for other types of

Favorites

objects, you could cache those images during the life of the plug-in and dispose of them when the plug-in is shut down (see Section 7.7, Image Caching, on page 346).

private final String id; private final String printName; private final int ordinal; private FavoriteItemType(String id, String name, int position) {

this.id = id;

this.ordinal = position;

this.printName = name;

} public String getId() {

return id;

} public String getName() {

return printName;

} public abstract Image getImage(); public abstract IFavoriteItem newFavorite(Object obj); public abstract IFavoriteItem loadFavorite(String info);

FavoriteItemType

implements the

Comparable<FavoriteItemType> interface, for sorting purposes, so must implement the compareTo

method.

public int compareTo(

FavoriteItemType

other) {

return this.ordinal - other.ordinal;

}

Next, add public static fields for each of the known types of Favorites. For now, these instances are hard-coded; however, in the future, these instances will be defined by an extension point so that others can introduce new types

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

298 CHAPTER 7 • Views

of Favorites (see Section 17.3, Code Behind an Extension Point, on page 649).

These new public static fields depend on the org.eclipse.core.resources

, org.eclipse.ui.ide

, and org.eclipse.jdt.core

plug-ins, so use the

Dependencies

page of the plug-in manifest editor (see Figure 2–10 on page 79) to add these required plug-ins, and then save the changes.

public static final FavoriteItemType

UNKNOWN

= new FavoriteItemType("Unknown", "Unknown", 0)

{

public Image getImage() {

return null;

}

public IFavoriteItem newFavorite(Object obj) {

return null;

}

public IFavoriteItem loadFavorite(String info) {

return null;

}

}; public static final FavoriteItemType

WORKBENCH_FILE

= new FavoriteItemType("WBFile", "Workbench File", 1)

{

public Image getImage() {

return PLATFORM_IMAGES

.getImage(org.eclipse.ui.ISharedImages.IMG_OBJ_FILE);

}

public IFavoriteItem newFavorite(Object obj) {

if (!(obj instanceof IFile))

return null;

return new FavoriteResource(this, (IFile) obj);

}

public IFavoriteItem loadFavorite(String info) {

return FavoriteResource.loadFavorite(this, info);

}

}; public static final FavoriteItemType

WORKBENCH_FOLDER

= new FavoriteItemType("WBFolder", "Workbench Folder", 2)

{

public Image getImage() {

return PLATFORM_IMAGES

.getImage(org.eclipse.ui.ISharedImages.IMG_OBJ_FOLDER);

}

public IFavoriteItem newFavorite(Object obj) {

if (!(obj instanceof IFolder))

return null;

return new FavoriteResource(this, (IFolder) obj);

}

public IFavoriteItem loadFavorite(String info) {

return FavoriteResource.loadFavorite(this, info);

}

};

... more of the same ...

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part 299

Finally, create a static array containing all known types and a getTypes() method that will return all known types.

private static final FavoriteItemType[] TYPES = {

UNKNOWN, WORKBENCH_FILE, WORKBENCH_FOLDER, WORKBENCH_PROJECT,

JAVA_PROJECT, JAVA_PACKAGE_ROOT, JAVA_PACKAGE,

JAVA_CLASS_FILE, JAVA_COMP_UNIT, JAVA_INTERFACE, JAVA_CLASS}; public static FavoriteItemType[] getTypes() {

return TYPES;

}

All

Favorites

views should show the same collection of

Favorites

objects, so the

FavoritesManager

is a singleton responsible for maintaining this global collection.

package com.qualityeclipse.favorites.model; import ...

public class FavoritesManager {

private static FavoritesManager manager;

private Collection<IFavoriteItem> favorites;

private FavoritesManager() {}

public static FavoritesManager getManager() {

if (manager == null)

manager = new FavoritesManager();

return manager;

}

public IFavoriteItem[] getFavorites() {

if (favorites == null)

loadFavorites();

return favorites.toArray(new IFavoriteItem[favorites.size()]);

}

private void loadFavorites() {

// temporary implementation

// to prepopulate list with projects

IProject[] projects = ResourcesPlugin.getWorkspace().getRoot()

.getProjects();

favorites = new HashSet(projects.length);

for (int i = 0; i < projects.length; i++)

favorites.add(new FavoriteResource(

FavoriteItemType.WORKBENCH_PROJECT, projects[i]));

}

The manager needs to look up existing

Favorites

objects and create new ones.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

300 CHAPTER 7 • Views

public void addFavorites(Object[] objects) {

if (objects == null)

return;

if (favorites == null)

loadFavorites();

Collection<IFavoriteItem> items =

new HashSet<IFavoriteItem>(objects.length);

for (int i = 0; i < objects.length; i++) {

IFavoriteItem item = existingFavoriteFor(objects[i]);

if (item == null) {

item = newFavoriteFor(objects[i]);

if (item != null && favorites.add(item))

items.add(item);

}

}

if (items.size() > 0) {

IFavoriteItem[] added =

items.toArray(new IFavoriteItem[items.size()]);

fireFavoritesChanged(added, IFavoriteItem.NONE);

}

} public void removeFavorites(Object[] objects) {

if (objects == null)

return;

if (favorites == null)

loadFavorites();

Collection<IFavoriteItem> items =

new HashSet<IFavoriteItem>(objects.length);

for (int i = 0; i < objects.length; i++) {

IFavoriteItem item = existingFavoriteFor(objects[i]);

if (item != null && favorites.remove(item))

items.add(item);

}

if (items.size() > 0) {

IFavoriteItem[] removed =

items.toArray(new IFavoriteItem[items.size()]);

fireFavoritesChanged(IFavoriteItem.NONE, removed);

}

} public IFavoriteItem newFavoriteFor(Object obj) {

FavoriteItemType[] types = FavoriteItemType.getTypes();

for (int i = 0; i < types.length; i++) {

IFavoriteItem item = types[i].newFavorite(obj);

if (item != null)

return item;

}

return null;

} private IFavoriteItem existingFavoriteFor(Object obj) {

if (obj == null)

return null;

if (obj instanceof IFavoriteItem)

return (IFavoriteItem) obj;

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part 301

Iterator<IFavoriteItem> iter = favorites.iterator();

while (iter.hasNext()) {

IFavoriteItem item = iter.next();

if (item.isFavoriteFor(obj))

return item;

}

return null;

} public IFavoriteItem[] existingFavoritesFor(Iterator<?> iter) {

List<IFavoriteItem> result = new ArrayList<IFavoriteItem>(10);

while (iter.hasNext()) {

IFavoriteItem item = existingFavoriteFor(iter.next());

if (item != null)

result.add(item);

}

return (IFavoriteItem[]) result.toArray(

new IFavoriteItem[result.size()]);

}

Since more than one view will be accessing the information, the manager must be able to notify registered listeners when the information changes. The

FavoritesManager

will only be accessed from the UI thread, so you do not need to worry about thread safety (see Section 4.2.5.1, Display, on page 148 for more about the UI thread). private List<FavoritesManagerListener> listeners

= new ArrayList<FavoritesManagerListener>(); public void addFavoritesManagerListener(

FavoritesManagerListener listener

) {

if (!listeners.contains(listener))

listeners.add(listener);

} public void removeFavoritesManagerListener(

FavoritesManagerListener listener

) {

listeners.remove(listener);

} private void fireFavoritesChanged(

IFavoriteItem[] itemsAdded, IFavoriteItem[] itemsRemoved

) {

FavoritesManagerEvent event = new FavoritesManagerEvent(

this, itemsAdded, itemsRemoved);

for (Iterator<FavoritesManagerListener>

iter = listeners.iterator(); iter.hasNext();)

iter.next().favoritesChanged(event);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

302 CHAPTER 7 • Views

The

FavoritesManager

uses the

FavoritesManagerListener

and

FavoritesManagerEvent

classes to notify interested objects of changes.

package com.qualityeclipse.favorites.model; public interface FavoritesManagerListener

{

public void favoritesChanged(FavoritesManagerEvent event);

} package com.qualityeclipse.favorites.model; import java.util.EventObject; public class FavoritesManagerEvent extends EventObject

{

private static final long serialVersionUID = 3697053173951102953L;

private final IFavoriteItem[] added;

private final IFavoriteItem[] removed;

public FavoritesManagerEvent(

FavoritesManager source,

IFavoriteItem[] itemsAdded, IFavoriteItem[] itemsRemoved

) {

super(source);

added = itemsAdded;

removed = itemsRemoved;

}

public IFavoriteItem[] getItemsAdded() {

return added;

}

public IFavoriteItem[] getItemsRemoved() {

return removed;

}

}

In the future, the

FavoritesManager

will be enhanced to allow the list to persist between Eclipse sessions (see Section 7.5.2, Saving global view information, on page 343), but for now, the list will be initialized with current workspace projects every time Eclipse starts. In addition, the current implementation will be extended in future chapters to include

Favorites

types added by other plug-ins (see Section 17.3, Code Behind an Extension Point, on page 649).

The

FavoriteResource

wraps an

IResource

object, adapting it to the

IFavoriteItem

interface. For more on the

IAdaptable

interface referenced below, see Section 21.3, Adapters, on page 784.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part

package com.qualityeclipse.favorites.model; import ...

public class FavoriteResource

implements IFavoriteItem

{

private FavoriteItemType type;

private IResource resource;

private String name;

FavoriteResource(FavoriteItemType type, IResource resource) {

this.type = type;

this.resource = resource;

}

public static FavoriteResource loadFavorite(

FavoriteItemType type, String info)

{

IResource res = ResourcesPlugin.getWorkspace().getRoot()

.findMember(new Path(info));

if (res == null)

return null;

return new FavoriteResource(type, res);

}

public String getName() {

if (name == null)

name = resource.getName();

return name;

}

public void setName(String newName) {

name = newName;

}

public String getLocation() {

IPath path = resource.getLocation().removeLastSegments(1);

if (path.segmentCount() == 0)

return "";

return path.toString();

}

public boolean isFavoriteFor(Object obj) {

return resource.equals(obj);

}

public FavoriteItemType getType() {

return type;

}

public boolean equals(Object obj) {

return this == obj || (

(obj instanceof FavoriteResource)

&& resource.equals(((FavoriteResource) obj).resource));

}

303

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

304 CHAPTER 7 • Views

public int hashCode() {

return resource.hashCode();

}

public Object getAdapter(Class adapter) {

if (adapter.isInstance(resource))

return resource;

return Platform.getAdapterManager().getAdapter(this, adapter);

}

public String getInfo() {

return resource.getFullPath().toString();

}

}

Similar to the

FavoriteResource

, the

FavoriteJavaElement

adapts an

IJavaElement

object to the

IFavoriteItem

interface. Before creating this class, you’ll need to add the org.eclipse.jdt.ui

plug-in to the

Favorites

plug-in’s manifest (see Figure 7–5).

Figure 7–5

Plug-in manifest editor Dependencies page.

If the project is a plug-in project (see Section 2.2, Creating a Plug-in

Project, on page 72), modifying the plug-in’s manifest causes the project’s Java build path to be automatically updated.

package com.qualityeclipse.favorites.model; import ...

public class FavoriteJavaElement

implements IFavoriteItem

{

private FavoriteItemType type;

private IJavaElement element;

private String name;

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part

public FavoriteJavaElement(

FavoriteItemType type, IJavaElement element

) {

this.type = type;

this.element = element;

}

public static FavoriteJavaElement loadFavorite(

FavoriteItemType type, String info

) {

IResource res = ResourcesPlugin.getWorkspace().getRoot()

.findMember(new Path(info));

if (res == null)

return null;

IJavaElement elem = JavaCore.create(res);

if (elem == null)

return null;

return new FavoriteJavaElement(type, elem);

}

public String getName() {

if (name == null)

name = element.getElementName();

return name;

}

public void setName(String newName) {

name = newName;

}

public String getLocation() {

try {

IResource res = element.getUnderlyingResource();

if (res != null) {

IPath path = res.getLocation().removeLastSegments(1);

if (path.segmentCount() == 0)

return "";

return path.toString();

}

}

catch (JavaModelException e) {

FavoritesLog.logError(e);

}

return "";

}

public boolean isFavoriteFor(Object obj) {

return element.equals(obj);

}

public FavoriteItemType getType() {

return type;

}

public boolean equals(Object obj) {

return this == obj || (

(obj instanceof FavoriteJavaElement)

&& element.equals(((FavoriteJavaElement) obj).element));

}

305

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

306 CHAPTER 7 • Views

public int hashCode() {

return element.hashCode();

}

public Object getAdapter(Class adapter) {

if (adapter.isInstance(element))

return element;

IResource resource = element.getResource();

if (adapter.isInstance(resource))

return resource;

return Platform.getAdapterManager().getAdapter(this, adapter);

}

public String getInfo() {

try {

return element.getUnderlyingResource().getFullPath()

.toString();

}

catch (JavaModelException e) {

FavoritesLog.logError(e);

return null;

}

}

}

7.2.4

Content provider

When the model objects have been created, they need to be linked into the view. A content provider is responsible for extracting objects from an input object—in this case, the

FavoritesManager

—and handing them to the table viewer for displaying, one object in each row. Although the

IStructured-

ContentProvider

does not specify this, the content provider has also been made responsible for updating the viewer when the content of

Favorites-

Manager

changes.

After extracting the content provider that was automatically generated as part of the

FavoritesView

class (see Section 2.3.2, The Activator or Plug-in class, on page 83) and reworking it to use the newly created

FavoritesManager

, it looks something like the following code.

package com.qualityeclipse.favorites.views; import ...

class FavoritesViewContentProvider

implements IStructuredContentProvider, FavoritesManagerListener

{

private TableViewer viewer;

private FavoritesManager manager;

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part

public void inputChanged(

Viewer viewer, Object oldInput, Object newInput

) {

this.viewer = (TableViewer) viewer;

if (manager != null)

manager.removeFavoritesManagerListener(this);

manager = (FavoritesManager) newInput;

if (manager != null)

manager.addFavoritesManagerListener(this);

}

public void dispose() {

}

public Object[] getElements(Object parent) {

return manager.getFavorites();

}

public void favoritesChanged(FavoritesManagerEvent event) {

viewer.getTable().setRedraw(false);

try {

viewer.remove(event.getItemsRemoved());

viewer.add(event.getItemsAdded());

}

finally {

viewer.getTable().setRedraw(true);

}

}

}

307

Tip

: The preceding method uses the setRedraw

method to reduce the flicker when adding and removing multiple items from the viewer.

Extracting and modifying the content provider means that the calls to setContentProvider

and setInput

in the createPartControl

method have changed as follows: viewer.setContentProvider(new FavoritesViewContentProvider()); viewer.setInput(FavoritesManager.getManager());

7.2.5

Label provider

The label provider takes a table row object returned by the content provider and extracts the value to be displayed in a column. After refactoring the

FavoritesView.ViewLabelProvider

inner class (see Section 2.3.3, The

Favorites view, on page 84) into a top-level class and reworking it to extract values from the newly created model object, it looks something like the following code.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

308 CHAPTER 7 • Views

class FavoritesViewLabelProvider extends LabelProvider

implements ITableLabelProvider

{

public String getColumnText(Object obj, int index) {

switch (index) {

case 0: // Type column

return "";

case 1: // Name column

if (obj instanceof IFavoriteItem)

return ((IFavoriteItem) obj).getName();

if (obj != null)

return obj.toString();

return "";

case 2: // Location column

if (obj instanceof IFavoriteItem)

return ((IFavoriteItem) obj).getLocation();

return "";

default:

return "";

}

}

public Image getColumnImage(Object obj, int index) {

if ((index == 0) && (obj instanceof IFavoriteItem))

return ((IFavoriteItem) obj).getType().getImage();

return null;

}

}

To enhance the

Favorites

view with different fonts and colors, implement

IFontProvider

and

IColorProvider

respectively (see

Section 13.2.5, IColorProvider, on page 523).

Tip

: If you are displaying workbench-related objects,

WorkbenchLabelProvider

and

WorkbenchPartLabelProvider contain behavior for determining text and images for workbench resources implementing the

IWorkbenchAdapter

interface (see Section 21.3.4,

IWorkbenchAdapter, on page 788). For lists and single-column trees and tables, implement

IViewerLabelProvider

to efficiently set text, image, font, and color by implementing a single updateLabel()

method.

7.2.6

Viewer sorter

Although a content provider serves up row objects, it is the responsibility of the

ViewerSorter

to sort the row objects before they are displayed. In the

Favorites

view, there are currently three criteria by which items can be sorted in either ascending or descending order:

• Name

• Type

• Location

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part 309

The

FavoritesViewSorter

delegates sorting to three comparators, one for each of the criteria just listed. In addition, the

FavoritesViewSorter

listens for mouse clicks in the column headers and resorts the table content based on the column that was selected. Clicking on a column a second time toggles the sort order.

package com.qualityeclipse.favorites.views; import ...

public class FavoritesViewSorter extends ViewerSorter

{

// Simple data structure for grouping

// sort information by column.

private class SortInfo {

int columnIndex;

Comparator<Object> comparator;

boolean descending;

}

private TableViewer viewer;

private SortInfo[] infos;

public FavoritesViewSorter(

TableViewer viewer,

TableColumn[] columns,

Comparator<Object>[] comparators

) {

this.viewer = viewer;

infos = new SortInfo[columns.length];

for (int i = 0; i < columns.length; i++) {

infos[i] = new SortInfo();

infos[i].columnIndex = i;

infos[i].comparator = comparators[i];

infos[i].descending = false;

createSelectionListener(columns[i], infos[i]);

}

}

public int compare(

Viewer viewer, Object favorite1, Object favorite2

) {

for (int i = 0; i < infos.length; i++) {

int result = infos[i].comparator

.compare(favorite1, favorite2);

if (result != 0) {

if (infos[i].descending)

return -result;

return result;

}

}

return 0;

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

310 CHAPTER 7 • Views

private void createSelectionListener(

final TableColumn column, final SortInfo info

) {

column.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent e) {

sortUsing(info);

}

});

}

protected void sortUsing(SortInfo info) {

if (info == infos[0])

info.descending = !info.descending;

else {

for (int i = 0; i < infos.length; i++) {

if (info == infos[i]) {

System.arraycopy(infos, 0, infos, 1, i);

infos[0] = info;

info.descending = false;

break;

}

}

}

viewer.refresh();

}

}

A new field in

FavoritesView

is introduced now to hold the sorter instance: private FavoritesViewSorter sorter; and the

Favorites

view createPartControl(Composite) method is modified to call the new method shown below. Later, the current sort order, as chosen by the user, must be preserved between Eclipse sessions (see Section 7.5.1,

Saving local view information, on page 340).

private void createTableSorter() {

Comparator<IFavoriteItem> nameComparator

= new Comparator<IFavoriteItem>() {

public int compare(IFavoriteItem i1, IFavoriteItem i2) {

return i1.getName().compareTo(i2.getName());

}

};

Comparator<IFavoriteItem> locationComparator

= new Comparator<IFavoriteItem>() {

public int compare(IFavoriteItem i1, IFavoriteItem i2) {

return i1.getLocation().compareTo(i2.getLocation());

}

};

Comparator<IFavoriteItem> typeComparator

= new Comparator<IFavoriteItem>() {

public int compare(IFavoriteItem i1, IFavoriteItem i2) {

return i1.getType().compareTo(i2.getType());

}

};

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.2

View Part

sorter = new FavoritesViewSorter(

viewer,

new TableColumn[] {

nameColumn, locationColumn, typeColumn },

new Comparator[] {

nameComparator, locationComparator, typeComparator }

);

viewer.setSorter(sorter);

}

311

7.2.7

Viewer filters

ViewerFilter

subclasses determine which of the row objects returned by a content provider will be displayed and which will not. While there can be only one content provider, only one label provider, and only one sorter, there can be any number of filters associated with a viewer. When multiple filters are applied, only those items that satisfy all the applied filters will be displayed.

Similar to the sorting just discussed, the

Favorites

view can be filtered by:

• Name

• Type

• Location

Eclipse provides the org.eclipse.ui.internal.misc.StringMatcher

type, which is ideal for wildcard filtering, but since the class is in an internal package, the first step is to copy the class into the com.qualityeclipse.

favorites.util

package. Although copying sounds horrid, there are already 10 copies of this particular class in various locations throughout

Eclipse, all of them internal (see Section 21.2, Accessing Internal Code, on page 781 for more on internal packages and the issues that surround them).

After that is complete, the

ViewerFilter

class for filtering the

Favorites

view by name looks like this (see below). This viewer filter is hooked up to the

Favorites

view using a command in Section 7.3.4, Pull-down menu, on page 319.

package com.qualityeclipse.favorites.views; import ...

public class FavoritesViewNameFilter extends ViewerFilter

{

private final StructuredViewer viewer;

private String pattern = "";

private StringMatcher matcher;

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

312 CHAPTER 7 • Views

public FavoritesViewNameFilter(StructuredViewer viewer) {

this.viewer = viewer;

}

public String getPattern() {

return pattern;

}

public void setPattern(String newPattern) {

boolean filtering = matcher != null;

if (newPattern != null && newPattern.trim().length() > 0) {

pattern = newPattern;

matcher = new StringMatcher(pattern, true, false);

if (!filtering)

viewer.addFilter(this);

else

viewer.refresh();

}

else {

pattern = "";

matcher = null;

if (filtering)

viewer.removeFilter(this);

}

}

public boolean select(

Viewer viewer,

Object parentElement,

Object element

) {

return matcher.match(

((IFavoriteItem) element).getName());

}

}

7.2.8

View selection

Now that the model objects and view controls are in place, other aspects of the view, specifically commands and actions, need a way to determine which

Favorites

items are currently selected. Add the following method to the

FavoritesView

so that operations can be performed on the selected items.

public IStructuredSelection getSelection() {

return (IStructuredSelection) viewer.getSelection();

}

7.2.9

Implementing a propertyTester

Now that the model exists, we can finish implementing the propertyTester started in Section 6.2.5.2, Creating a new propertyTester, on page 225. Modify

FavoritesTester

to test whether the

FavoritesManager

contains the specified object:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 313

public boolean test(Object receiver, String property, Object[] args,

Object expectedValue) {

boolean found = false;

IFavoriteItem[] favorites

= FavoritesManager.getManager().getFavorites();

for (int i = 0; i < favorites.length; i++) {

IFavoriteItem item = favorites[i];

found = item.isFavoriteFor(receiver);

if (found)

break;

}

if ("isFavorite".equals(property))

return found;

if ("notFavorite".equals(property))

return !found;

return false;

}

7.3

View Commands

A view command can appear as a menu item in a view’s context menu, as a toolbar button on the right side of a view’s title bar, and as a menu item in a view’s pull-down menu (see Figure 6–16 on page 270). This section covers adding commands to a view programmatically and registering that view so that others can contribute their own commands and actions via the plug-in manifest. In contrast, Section 6.1, Commands, on page 216 and Section 6.8,

View Actions, on page 270 discuss adding commands and actions using declarations in the plug-in manifest.

7.3.1

Model command handlers

Now that the model objects are in place, the

AddToFavoritesHandler

class introduced in Section 6.3.1, Creating a new IHandler, on page 237 can be completed (implementing the

AddToFavoritesActionDelegate

introduced in Section 6.7.3, IObjectActionDelegate, on page 266 is very similar). With the modifications outlined below, the handler adds the selected items to the

FavoritesManager

, which then notifies the

FavoritesViewContentProvider

, which then refreshes the table to display the new information.

public Object execute(ExecutionEvent event)

throws ExecutionException {

ISelection selection = HandlerUtil.getCurrentSelection(event);

if (selection instanceof IStructuredSelection)

FavoritesManager.getManager().addFavorites(

((IStructuredSelection) selection).toArray());

return null;

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

314 CHAPTER 7 • Views

7.3.2

Context menu

Typically, views have context menus populated by commands targeted at the view or selected objects within it. There are several steps to create a view’s context menu programmatically. If you want other plug-ins to contribute commands to your view’s context menu via declarations in the plug-in manifest (see Section 6.2.5, Defining a selection-based context menu item, on page 223), then you must take several more steps to register your view.

7.3.2.1

Creating contributions

The first step is to create the contribution that will appear in the context menu. For the

Favorites

view, a contribution that will remove the selected elements from the view is needed. If isDynamic()

returns true

, then fill(...) will be called every time the context menu is displayed rather than only the first time the context menu is displayed. This is useful if your contribution changes visibility. In our case we do not override isDynamic()

because our contribution changes enablement but not visibility.

package com.qualityeclipse.favorites.contributions; import ...

public class RemoveFavoritesContributionItem

extends ContributionItem

{

private final FavoritesView view;

private final IHandler handler;

boolean enabled = false;

private MenuItem menuItem;

public RemoveFavoritesContributionItem(FavoritesView view,

IHandler handler) {

this.view = view;

this.handler = handler;

view.addSelectionChangedListener(

new ISelectionChangedListener() {

public void selectionChanged(SelectionChangedEvent event) {

enabled = !event.getSelection().isEmpty();

updateEnablement();

}

});

}

public void fill(Menu menu, int index) {

menuItem = new MenuItem(menu, SWT.NONE, index);

menuItem.setText("Remove");

menuItem.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent e) {

run();

}

});

updateEnablement();

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 315

private void updateEnablement() {

Image image =

PlatformUI.getWorkbench().getSharedImages().getImage(

enabled ? ISharedImages.IMG_TOOL_DELETE

: ISharedImages.IMG_TOOL_DELETE_DISABLED);

if (menuItem != null) {

menuItem.setImage(image);

menuItem.setEnabled(enabled);

}

}

public void run() {

final IHandlerService handlerService = (IHandlerService)

viewSite.getService(IHandlerService.class);

IEvaluationContext evaluationContext =

handlerService.createContextSnapshot(true);

ExecutionEvent event =

new ExecutionEvent(null, Collections.EMPTY_MAP, null,

evaluationContext);

try {

handler.execute(event);

}

catch (ExecutionException e) {

FavoritesLog.logError(e);

}

}

}

Tip

: It is easier to contribute a command via the plug-in manifest per

Section 6.2.6, Defining a view-specific menu or toolbar item, on page 228 than create a contribution as shown above unless you need the extra flexibility that creating your own contribution provides.

RemoveFavoritesContributionItem

uses a new handler to perform the operation. We separate this functionality from the contribution item so that it can be utilized elsewhere.

public class RemoveFavoritesHandler extends AbstractHandler

{

public Object execute(ExecutionEvent event)

throws ExecutionException {

ISelection selection = HandlerUtil.getCurrentSelection(event);

if (selection instanceof IStructuredSelection)

FavoritesManager.getManager().removeFavorites(

((IStructuredSelection) selection).toArray());

return null;

}

}

In the

FavoritesView

class, provide access for the contribution to update its enablement state

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

316 CHAPTER 7 • Views

public void addSelectionChangedListener(

ISelectionChangedListener listener) {

viewer.addSelectionChangedListener(listener);

} and create a new fields private IHandler removeHandler; private RemoveFavoritesContributionItem removeContributionItem; and call the following new method from createPartControl(Composite) to initialize the field.

private void createContributions() {

removeHandler = new RemoveFavoritesHandler();

removeContributionItem =

new RemoveFavoritesContributionItem(getViewSite(),

removeHandler);

}

This menu contribution is later associated with a keystroke in Section

7.3.5, Keyboard commands, on page 320, and Section 7.3.6, Global commands, on page 321.

Checked menu item

: For an example of a dynamically contributed menu item that has a checkmark, see Section 14.3.7, Associating a nature with a project, on page 568.

7.3.2.2

Creating the context menu

The context menu must be created at the same time that the view is created, but because contributors add and remove menu items based on the current selection, its contents cannot be determined until just after the user clicks the right mouse button and just before the menu is displayed. To accomplish this, set the menu’s

RemoveAllWhenShown

property to true

so that the menu will be built from scratch every time, and add a menu listener to dynamically build the menu. In addition, the menu must be registered with the control so that it will be displayed and with the view site so that other plug-ins can contribute actions to it (see Section 6.2.5, Defining a selection-based context menu item, on page 223). For the

Favorites

view, modify createPartControl()

to call the following new createContextMenu()

method.

private void createContextMenu() {

MenuManager menuMgr = new MenuManager("#PopupMenu");

menuMgr.setRemoveAllWhenShown(true);

menuMgr.addMenuListener(new IMenuListener() {

public void menuAboutToShow(IMenuManager m) {

FavoritesView.this.fillContextMenu(m);

}

});

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 317

Menu menu =

menuMgr.createContextMenu(viewer.getControl());

viewer.getControl().setMenu(menu);

getSite().registerContextMenu(menuMgr, viewer);

}

7.3.2.3

Dynamically building the context menu

Every time the user clicks the right mouse button, the context menu’s content must be rebuilt from scratch because contributors can add or remove menu items based on the selected items. In addition, the context menu must contain a separator with the

IWorkbenchActionConstants.MB_ADDITIONS

constant, indicating where contributed actions can appear in the menu. The createContextMenu()

method (see Section 7.3.2.2, Creating the context menu, on page 316) calls the new fillContextMenu(IMenuManager)

method shown here: private void fillContextMenu(IMenuManager menuMgr) {

menuMgr.add(removeContributionItem);

menuMgr.add(new Separator(

IWorkbenchActionConstants.MB_ADDITIONS));

}

Tip

: Add multiple named

Separators

and

GroupMarkers

in fillContextMenu

(see above and Section 7.3.7.2, Copy Command in

Context Menu, on page 325) to more exactly specify where a menu command contributed via the plug-in manifest should appear (see Section

6.2.6, Defining a view-specific menu or toolbar item, on page 228).

7.3.2.4

Selection provider

When selection-based contributions are defined (see Section 6.2.5, Defining a selection-based context menu item, on page 223), they are targeted at the selected object rather than at the view. For selection-based contributions to appear in a view’s context menu, the view must not only register the context menu (see Section 7.3.2.1, Creating contributions, on page 314), but it must also publish its selection for any other registered listeners (see Section 7.4.1,

Selection provider, on page 337). In addition, selection-based contributions are typically targeted at specific types of objects rather than all objects. This means that the selected object must implement the

IAdaptable

interface so that contributors can adapt the selected objects to any object they can interrogate and manipulate (see Section 7.4.2, Adaptable objects, on page 337).

7.3.2.5

Filtering unwanted actions

If a view registers its context menu (see Section 7.3.2.2, Creating the context menu, on page 316) then any menu contributions that have the locationURI

“popup:org.eclipse.ui.popup.any” will appear in that view’s context menu. At

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

318 CHAPTER 7 • Views

this point, because of the way the visibleWhen

expression for the

Favorites

submenu is defined, the

Favorites

submenu appears as it should in every view

except

in the

Favorites

view. If this were not the case, then one way to suppress the

Favorites

submenu from appearing in the

Favorites

view would be to modify the menu contribution’s visibleWhen

expression:

<visibleWhen checkEnabled="false">

<and>

<not>

<with variable="activePartId">

<equals value =

"com.qualityeclipse.favorites.views.FavoritesView">

</equals>

</with>

</not>

<with variable="selection">

<iterate ...

The plug-in manifest editor will not allow you to insert the

<and>

XML element as the top element in an already existing visibleWhen expression on the

Extensions

page. Instead, you must switch to the

plugin.xml

page and edit the XML expression itself.

7.3.3

Toolbar buttons

Next, programmatically add the

Remove

action to the toolbar (see Section

6.8.4, Defining a view toolbar action, on page 273 for declaring a toolbar button using the plug-in manifest rather than programmatically). In addition, the state of this toolbar button needs to change based on the selection in the

Favorites

view. In the

FavoritesView

class, call the following new method from the createPartControl(Composite)

method.

private void createToolbarButtons() {

IToolBarManager toolBarMgr =

getViewSite().getActionBars().getToolBarManager();

toolBarMgr.add(new GroupMarker("edit"));

toolBarMgr.add(removeContributionItem);

}

In

RemoveFavoritesContributionItem

, add an additional field private ToolItem toolItem; and add the following new method.

public void fill(ToolBar parent, int index) {

toolItem = new ToolItem(parent, SWT.NONE, index);

toolItem.setToolTipText("Remove the selected favorite items");

toolItem.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent e) {

run();

}

});

updateEnablement();

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 319

The toolbar item must be enabled and disabled based upon the view’s current selection, so add the following to the updateEnablement(...)

method if (toolItem != null) {

toolItem.setImage(image);

toolItem.setEnabled(enabled);

}

Tip

: Alternately, contribute items to your own view’s toolbar using declarations in the plug-in manifest. For more on menuContribution and locationURI, see Section 6.2.6, Defining a view-specific menu or toolbar item, on page 228 and Section 6.2.9, locationURI, on page 230.

7.3.4

Pull-down menu

This section will programmatically add an action to the

Favorites

view pulldown menu so that the name filter can be enabled and disabled (see Section

6.8.5, Defining a view pull-down submenu and action, on page 274 for defining a pull-down menu item in the plug-in manifest rather than programmatically). We could create a subclass of

ContributionItem

as we did with

RemoveFavoritesContributionItem

in the previous section, but instead we subclass

Action

to illustrate the older approach. For now, the action will use a simple

InputDialog

to prompt for the name filter pattern, but this will be replaced with a specialized

Favorites

view filter dialog later in the book (see

Section 11.1.2, Common SWT dialogs, on page 442).

package com.qualityeclipse.favorites.views; import ...

public class FavoritesViewFilterAction extends Action {

private final Shell shell;

private final FavoritesViewNameFilter nameFilter;

public FavoritesViewFilterAction(

StructuredViewer viewer,

String text

) {

super(text);

shell = viewer.getControl().getShell();

nameFilter = new FavoritesViewNameFilter(viewer);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

320 CHAPTER 7 • Views

public void run() {

InputDialog dialog = new InputDialog(

shell,

"Favorites View Filter",

"Enter a name filter pattern"

+ " (* = any string, ? = any character)"

+ System.getProperty("line.separator")

+ "or an empty string for no filtering:",

nameFilter.getPattern(),

null);

if (dialog.open() == InputDialog.OK)

nameFilter.setPattern(dialog.getValue().trim());

}

}

The createPartControl()

method is getting quite long and is in need of refactoring. After extracting the table columns as fields and extracting table creation and sorting into separate methods, the createPartControl() method is modified to call a new createViewPulldownMenu()

method. This new method programmatically creates and initializes the

filter

field, and adds the new filter action to the

Favorites

view’s pull-down menu (see Figure 7–6).

private FavoritesViewFilterAction filterAction; private void createViewPulldownMenu() {

IMenuManager menu =

getViewSite().getActionBars().getMenuManager();

filterAction =

new FavoritesViewFilterAction(viewer, "Filter...");

menu.add(filterAction);

}

Figure 7–6

Favorites view showing the view’s pull-down menu.

7.3.5

Keyboard commands

Rather than using the mouse to activate the context menu and then selecting the

Remove

command to remove an item from the

Favorites

view (see Section

7.3.2, Context menu, on page 314), it would be quicker just to press the

Delete

key. This approach programmatically associates the

Delete

key with the

RemoveFavoritesContributionItem

rather than defining the command

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 321

via the plug-in manifest as in Section 6.4, Key Bindings, on page 238. For this to work, call the following hookKeyboard()

method from the createPartControl()

method.

private void hookKeyboard() {

viewer.getControl().addKeyListener(new KeyAdapter() {

public void keyReleased(KeyEvent event) {

handleKeyReleased(event);

}

});

} protected void handleKeyReleased(KeyEvent event) {

if (event.character == SWT.DEL && event.stateMask == 0) {

removeContributionItem.run();

}

}

7.3.6

Global commands

Now that the

RemoveFavoritesContributionItem

is available both in the context menu (see Section 7.3.2.1, Creating contributions, on page 314) and by pressing the

Delete

key (see Section 7.3.5, Keyboard commands, on page 320), that same contribution needs to be triggered when the user selects

Delete

from the

Edit

menu. The interface org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds

defines a number of constants, such as the following, for just this purpose.

• Undo

• Redo

• Cut

• Copy

• Paste

• Delete

Calling the following new method from the createPartControl()

method associates

Edit > Delete

with

RemoveFavoritesAction

when the

Favorites

view is active. The

IWorkbenchActionDefinitionIds

interface resides in the org.eclipse.ui.workbench.texteditor

plug-in, so that must be added to the dependencies in the plug-in manifest before the class can be accessed. The selection listener activates and deactivates the handler based upon the view’s current selection.

private void hookGlobalHandlers() {

IHandlerService handlerService =

(IHandlerService) getViewSite().getService(

IHandlerService.class);

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

322 CHAPTER 7 • Views

viewer.addSelectionChangedListener(

new ISelectionChangedListener() {

private IHandlerActivation removeActivation;

public void selectionChanged(SelectionChangedEvent event) {

if (event.getSelection().isEmpty()) {

if (removeActivation != null) {

handlerService.deactivateHandler(removeActivation);

removeActivation = null;

}

}

else {

if (removeActivation == null) {

removeActivation =

handlerService.activateHandler(

IWorkbenchActionDefinitionIds.DELETE,

removeHandler);

}

}

}

});

}

Alternately, you can hook the global edit menu items using a declaration in the plug-in manifest (see Section 6.3, Handlers, on page 236) and the

IWorkbenchActionDefinitionIds

constants. See the end of Section 7.3.7.1,

Copy, on page 322 for an example.

7.3.7

Clipboard commands

The three clipboard-related actions are cut, copy, and paste. For the

Favorites

view, you need to provide the ability to cut selected items out of the view, copy selected items, and paste new items into the view using three separate actions.

7.3.7.1

Copy

The copy action translates selected

Favorites

items into various formats such as resources, and places that information into the clipboard. Start by creating a new CopyFavoritesHandler class with a method to safely create and dispose of a clipboard object. The clipboard object exists only for the duration of the call to a second execute(...)

method, after which it is disposed.

public class CopyFavoritesHandler extends AbstractHandler

{

public Object execute(ExecutionEvent event)

throws ExecutionException {

Clipboard clipboard =

new Clipboard(HandlerUtil.getActiveShell(event)

.getDisplay());

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 323

try {

return execute(event, clipboard);

}

finally {

clipboard.dispose();

}

}

}

Transfer objects convert various formats, such as resources, into platform-specific byte streams and back so that information can be exchanged between different applications (see Section 7.3.8.3, Custom transfer types, on page 330 for more on transfer types). The following

CopyFavoritesHandler

methods translate favorite items into resources and text public static IResource[] asResources(Object[] objects) {

Collection<IResource> resources =

new HashSet<IResource>(objects.length);

for (int i = 0; i < objects.length; i++) {

Object each = objects[i];

if (each instanceof IAdaptable) {

IResource res = (IResource)

((IAdaptable) each).getAdapter(IResource.class);

if (res != null)

resources.add(res);

}

}

return resources.toArray(new IResource[resources.size()]);

} public static String asText(Object[] objects) {

StringBuffer buf = new StringBuffer();

for (int i = 0; i < objects.length; i++) {

Object each = objects[i];

if (each instanceof IFavoriteItem) {

buf.append("Favorite: ");

buf.append(((IFavoriteItem) each).getName());

}

else if (each != null)

buf.append(each.toString());

buf.append(System.getProperty("line.separator"));

}

return buf.toString();

}

And finally, the

CopyFavoritesHandler execute(...)

method performs the actual operation.

protected Object execute(ExecutionEvent event, Clipboard clipboard)

throws ExecutionException {

ISelection selection = HandlerUtil.getCurrentSelection(event);

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

324 CHAPTER 7 • Views

if (selection instanceof IStructuredSelection) {

Object[] objects =

((IStructuredSelection) selection).toArray();

if (objects.length > 0) {

try {

clipboard.setContents(

new Object[] {

asResources(objects), asText(objects), },

new Transfer[] {

ResourceTransfer.getInstance(),

TextTransfer.getInstance(), });

}

catch (SWTError error) {

// Copy to clipboard failed.

}

}

}

return null;

}

This copy handler will be attached at the global level to the

Edit > Copy

command and at the local level to a new

Copy

command in the

Favorites

view context menu. Declare the copy handler in the plug-in manifest (see Section 6.3,

Handlers, on page 236) to associate it with the global

Edit > Copy

command.

Command identifiers for the

Edit

menu are found in org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds

. Similar to the expressions described in Section 6.2.10, visibleWhen expression, on page 231, the activeWhen

expression specifies that the handler should only be active when the

Favorites

view is active and the enabledWhen

expression specifies that the handler should only be enabled when there are one or more objects selected.

<handler

commandId="org.eclipse.ui.edit.copy"

class=

"com.qualityeclipse.favorites.handlers.CopyFavoritesHandler">

<activeWhen>

<with variable="activePartId">

<equals value=

"com.qualityeclipse.favorites.views.FavoritesView" />

</with>

</activeWhen>

<enabledWhen>

<with variable="selection">

<count value="+" />

</with>

</enabledWhen>

</handler>

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 325

7.3.7.2

Copy Command in Context Menu

We also want the

Copy

command to appear in the

Favorites

view context menu.

We want the Cut, Copy, and Paste commands to appear next to one another in the context, so start by adding a new named separator to the fillContext-

Menu()

method (see Section 7.3.2.3, Dynamically building the context menu, on page 317).

menuMgr.add(new Separator("edit"));

Then we declare a new command

(see Section 6.1.1, Defining a command, on page 216), a new menuContribution

(see Section 6.2.6, Defining a view-specific menu or toolbar item, on page 228) and a new handler

(see Section 6.3,

Handlers, on page 236) in the plug-in manifest. Use the following locatorURI so that the

Copy

command shows up only in the

Favorites

view context menu in the correct position.

popup:com.qualityeclipse.favorites.views.FavoritesView?before=edit

In the

Favorites

view context menu, the

Copy

command is enabled even when there is nothing selected. To clean this up, add this enabledWhen expression to the handler so that the menu item is only enabled if there are one or more items selected in the

Favorites

view.

<handler class=

"com.qualityeclipse.favorites.handlers.CopyFavoritesHandler"

commandId="com.qualityeclipse.favorites.commands.copy">

<enabledWhen>

<with variable="selection">

<count value="+"/>

</with>

</enabledWhen>

</handler>

7.3.7.3

Cut

The cut handler is based on the copy and remove handlers, first using the copy handler to copy the selected

Favorites

items to the clipboard and then the remove handler to remove the selected items from the

Favorites

view. It is initialized and used much like the copy handler described in the previous section.

public class CutFavoritesHandler extends AbstractHandler

{

IHandler copy = new CopyFavoritesHandler();

IHandler remove = new RemoveFavoritesHandler();

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

326 CHAPTER 7 • Views

public Object execute(ExecutionEvent event)

throws ExecutionException {

copy.execute(event);

remove.execute(event);

return null;

}

public void dispose() {

copy.dispose();

remove.dispose();

super.dispose();

}

}

7.3.7.4

Paste

The paste operation takes information that was previously added to the clipboard by another operation and adds it to the

Favorites

view. As with the copy operation (see Section 7.3.7.1, Copy, on page 322), transfer objects facilitate translation from platform-specific byte streams to objects, and the paste operation converts those objects into items that are added to the

Favorites

view.

Pull the execute(ExecutionEvent)

method up into a new class named

ClipboardHandler

so that it can be shared by the

CopyFavoritesHandler and the new

PasteFavoritesHandler

. The initialization and use of the paste operation is much like the copy operation discussed in Section 7.3.7.1, Copy, on page 322.

public class PasteFavoritesHandler extends ClipboardHandler

{

protected Object execute(ExecutionEvent evt, Clipboard clipboard)

throws ExecutionException {

if (!paste(clipboard, JavaUI.getJavaElementClipboardTransfer()))

paste(clipboard, ResourceTransfer.getInstance());

return null;

}

private void paste(Clipboard clipboard, Transfer transfer) {

Object[] elements = (Object[]) clipboard.getContents(transfer);

if (elements != null && elements.length != 0) {

FavoritesManager.getManager().addFavorites(elements);

return true;

}

return false;

}

}

7.3.8

Drag-and-drop support

The ability to add objects to the

Favorites

view from another view using the copy/paste actions is available, but it would be nice to allow objects to be dragged into and out of the

Favorites

view. To accomplish this, add

drag source

and

drop target

objects to the

Favorites

view by calling the following new method from the createPartControl()

method. The

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 327

FavoritesDragSource

and

FavoritesDropTarget

types are defined in the next two sections.

private void hookDragAndDrop() {

new FavoritesDragSource(viewer);

new FavoritesDropTarget(viewer);

}

7.3.8.1

Dragging objects out of the Favorites view

The

FavoritesDragSource

type initializes the drag source operation and handles conversions of

Favorites

items into resource objects and text. This allows the user to drag and drop selected

Favorites

items elsewhere within

Eclipse or into another drag-and-drop-enabled application such as Microsoft

Word (see Figure 7–7).

Figure 7–7

Drag-and-drop operation from Microsoft Word to Eclipse.

The constructor, called from the hookDragAndDrop()

method (see Section 7.3.8, Drag-and-drop support, on page 326), initializes the drag source by:

• Creating a drag source— new DragSource()

• Specifying available operations—

DND.DROP_COPY

Multiple operations can be specified as in

DND.DROP_MOVE |

DND.DROP_COPY

if items can be both moved and copied.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

328 CHAPTER 7 • Views

• Specifying available data types—Resources and text

(For more, see Section 7.3.8.3, Custom transfer types, on page 330).

• Adding itself as a

DragSourceListener

to handle the data conversion from

Favorites

items to either resources or text.

When a user initiates a drag operation from the

Favorites

view, the dragStart()

method is called to determine whether the drag operation can be performed. In this case, set the event.doit

field to be true

if there are

Favorites

items selected, otherwise set event.doit

to false

since the operation can only be performed when at least one

Favorites

item is selected. When the user drops the objects, the dragSetData()

method is called to convert the selected items before the transfer occurs, and then the dragFinish()

method is called after the transfer is complete.

public class FavoritesDragSource

implements DragSourceListener

{

private final TableViewer viewer;

public FavoritesDragSource(TableViewer viewer) {

this.viewer = viewer;

DragSource source =

new DragSource(viewer.getControl(), DND.DROP_COPY);

source.setTransfer(new Transfer[] {

TextTransfer.getInstance(),

ResourceTransfer.getInstance() });

source.addDragListener(this);

}

public void dragStart(DragSourceEvent event) {

event.doit = !viewer.getSelection().isEmpty();

}

public void dragSetData(DragSourceEvent event) {

Object[] objects =

((IStructuredSelection) viewer.getSelection()).toArray();

if (ResourceTransfer.getInstance().isSupportedType(

event.dataType)) {

event.data = CopyFavoritesHandler.asResources(objects);

}

else if (TextTransfer.getInstance().isSupportedType(

event.dataType)) {

event.data = CopyFavoritesHandler.asText(objects);

}

}

public void dragFinished(DragSourceEvent event) {

// If this was a MOVE operation,

// then remove the items that were moved.

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 329

7.3.8.2

Dragging objects into the Favorites view

The

FavoritesDropTarget

type allows items to be added to the

Favorites

view by dragging them from another view. This allows the user to drag resources or Java elements from the

Resource Navigator

view or the

Java

Package

view into the

Favorites

view.

The constructor, called from the hookDragAndDrop()

method (see Section 7.3.8, Drag-and-drop support, on page 326), initializes the drop target by:

• Creating the drop target— new DropTarget()

• Specifying accepted operations—

DND.DROP_MOVE | DND.DROP_COPY

For convenience, specify that a move operation is allowed, but when the actual operation is performed, convert it to a copy operation.

• Specifying accepted data types—Resources and Java elements

(For more, see Section 7.3.8.3, Custom transfer types, on page 330.)

• Adding itself as a

DropTargetListener

to handle data conversion from objects to

Favorites

items.

During the drag operation, there are several events that occur so that various drop targets can provide feedback to the user when the cursor enters, moves over, and exits a drag target. Since you need to add items to the

Favorites

view without removing them from their original location, and to make it convenient for the user so that he or she does not have to hold down the

Ctrl

key to perform the drag operation, implement the dragEnter() method to convert a move operation into a copy operation. The conversion from a move operation to a copy operation is done in the dragEnter() method in addition to the drop()

method so that the user gets visual feedback indicating that a copy will occur before the operation is performed.

When the user drops the objects on the

Favorites

view, the drop()

method is called to perform the operation. It converts the objects into

Favorites

items and ensures that the operation is indeed a copy operation so that the objects are not removed from their original locations.

public class FavoritesDropTarget extends DropTargetAdapter

{

public FavoritesDropTarget(TableViewer viewer) {

DropTarget target =

new DropTarget(viewer.getControl(), DND.DROP_MOVE

| DND.DROP_COPY);

target.setTransfer(new Transfer[] {

ResourceTransfer.getInstance(),

JavaUI.getJavaElementClipboardTransfer() });

target.addDropListener(this);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

330 CHAPTER 7 • Views

public void dragEnter(DropTargetEvent event) {

if (event.detail == DND.DROP_MOVE

|| event.detail == DND.DROP_DEFAULT) {

if ((event.operations & DND.DROP_COPY) != 0)

event.detail = DND.DROP_COPY;

else

event.detail = DND.DROP_NONE;

}

}

public void drop(DropTargetEvent event) {

FavoritesManager manager = FavoritesManager.getManager();

if (JavaUI.getJavaElementClipboardTransfer().isSupportedType(

event.currentDataType)

&& (event.data instanceof IJavaElement[])) {

manager.addFavorites((IJavaElement[]) event.data);

event.detail = DND.DROP_COPY;

}

else if (ResourceTransfer.getInstance().isSupportedType(

event.currentDataType)

&& (event.data instanceof IResource[])) {

manager.addFavorites((IResource[]) event.data);

event.detail = DND.DROP_COPY;

}

else

event.detail = DND.DROP_NONE;

}

}

7.3.8.3

Custom transfer types

Transfer objects convert various formats, such as resources, into platformspecific byte streams and back so that information can be exchanged between different applications. Eclipse provides several transfer types, including:

• ByteArrayTransfer

• EditorInputTransfer

• FileTransfer

• JavaElementTransfer

• MarkerTransfer

• PluginTransfer

• ResourceTransfer

• RTFTransfer

• TextTransfer

These transfer objects are useful for generic types of objects such as resources. If you are dragging objects specific to your application from one view to another, however, the transfered objects may not completely capture the information of the object being dragged. For example, if you were to drag

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 331

a

Favorites

item from one

Favorites

view to another, and there was additional state information associated with the item, and a

ResourceTransfer

object was being used, then that additional state information would be lost.

Solving this problem requires building a custom transfer type such as the one that follows. A transfer type must be a subclass of the org.eclipse.swt.

dnd.Transfer

class, but subclassing org.eclipse.swt.dnd.ByteArray-

Transfer

is easier because of the additional behavior it provides. If a custom transfer type for

Favorites

items is built, then it would rely on functionality introduced in Section 7.5.2, Saving global view information, on page 343 and might be similar to the existing

ResourceTransfer

type.

package com.qualityeclipse.favorites.views; import ...; public class FavoritesTransfer extends ByteArrayTransfer

{

private static final FavoritesTransfer INSTANCE =

new FavoritesTransfer();

public static FavoritesTransfer getInstance() {

return INSTANCE;

}

private FavoritesTransfer() {

super();

}

Each

FavoritesTransfer

class must have a unique identifier to ensure that different Eclipse applications use

FavoritesTransfer

classes of different “types.” The getTypeIds()

and getTypeNames()

methods return the platform-specfic IDs and names of the data types that can be converted using this transfer agent.

private static final String TYPE_NAME =

"favorites-transfer-format:"

+ System.currentTimeMillis()

+ ":"

+ INSTANCE.hashCode();

private static final int TYPEID =

registerType(TYPE_NAME);

protected int[] getTypeIds() {

return new int[] { TYPEID };

}

protected String[] getTypeNames() {

return new String[] { TYPE_NAME };

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

332 CHAPTER 7 • Views

The javaToNative()

method converts a Java representation of data to a platform-specific representation of the data, then returns that information by placing it in the

TransferData

argument.

protected void javaToNative(

Object data,

TransferData transferData) {

if (!(data instanceof IFavoriteItem[])) return;

IFavoriteItem[] items = (IFavoriteItem[]) data;

/**

* The serialization format is:

* (int) number of items

* Then, the following for each item:

* (String) the type of item

* (String) the item-specific info glob

*/

try {

ByteArrayOutputStream out =

new ByteArrayOutputStream();

DataOutputStream dataOut =

new DataOutputStream(out);

dataOut.writeInt(items.length);

for (int i = 0; i < items.length; i++) {

IFavoriteItem item = items[i];

dataOut.writeUTF(item.getType().getId());

dataOut.writeUTF(item.getInfo());

}

dataOut.close();

out.close();

super.javaToNative(out.toByteArray(), transferData);

}

catch (IOException e) {

// Send nothing if there were problems.

}

}

The nativeToJava()

method converts a platform-specific representation of data to a Java representation.

protected Object nativeToJava(TransferData transferData) {

/**

* The serialization format is:

* (int) number of items

* Then, the following for each item:

* (String) the type of item

* (String) the item-specific info glob

*/

byte[] bytes =

(byte[]) super.nativeToJava(transferData);

if (bytes == null)

return null;

DataInputStream in =

new DataInputStream(

new ByteArrayInputStream(bytes));

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands

try {

FavoritesManager mgr =

FavoritesManager.getManager();

int count = in.readInt();

List<IFavoriteItem> items =

new ArrayList<IFavoriteItem>(count);

for (int i = 0; i < count; i++) {

String typeId = in.readUTF();

String info = in.readUTF();

items.add(mgr.newFavoriteFor(typeId, info));

}

return items.toArray(new IFavoriteItem[items.size()]);

}

catch (IOException e) {

return null;

}

}

333

Tip

: In input/output (I/O) code like the preceding, consider using a

BufferedOutputStream between the

ByteArrayOutputStream and the

DataOutputStream

. While not always necessary, this can be a useful performance improvement.

7.3.9

Inline editing

Another feature you need to have is the ability to edit the name of the

Favorites

items directly in the

Favorites

view quickly and easily. It is arguable that it should trigger the rename handler or refactoring so that the underlying resource or Java element will be renamed rather than just editing the name of the item itself, but things are kept simple for the purposes of demonstrating the inline editing function.

To perform inline editing of a

Favorites

item’s name, a new handler named

RenameFavoriteHandler

is needed. When the user selects the

Rename

command in the context menu, a text field opens over the selected item’s name in the

Favorites

view (see Figure 7–8). The user enters the new name into the text field and presses the

Return

key, which closes the editor and updates the item’s name.

Figure 7–8

Favorites view showing the inline text field.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

334 CHAPTER 7 • Views

This new handler obtains the current selection and open a cell editor on the name of the first selected element.

public class RenameFavoritesHandler extends AbstractHandler

{

private static final int COLUMN_TO_EDIT = 1;

public Object execute(ExecutionEvent event)

throws ExecutionException {

IWorkbenchPart part = HandlerUtil.getActivePart(event);

if (!(part instanceof FavoritesView))

return null;

editElement((FavoritesView) part);

return null;

}

public void editElement(FavoritesView favoritesView) {

TableViewer viewer =

favoritesView.getFavoritesViewer();

IStructuredSelection selection =

(IStructuredSelection) viewer.getSelection();

if (!selection.isEmpty())

viewer.editElement(

selection.getFirstElement(), COLUMN_TO_EDIT);

}

}

To make the

Rename...

command appear in the context menu, we declare a new command

(see Section 6.1.1, Defining a command, on page 216), a new menuContribution

(see Section 6.2.6, Defining a view-specific menu or toolbar item, on page 228), and the handler

(see Section 6.3, Handlers, on page 236) in the plug-in manifest. Use the following locatorURI so that the

Rename...

command shows up only in the

Favorites

view context menu in the correct position.

popup:com.qualityeclipse.favorites.views.FavoritesView?before=edit

In the

Favorites

view context menu, the

Rename...

command is enabled even when there is nothing selected. To clean this up, add this enabledWhen expression to the handler so that the menu item is only enabled if there are one or more items selected in the

Favorites

view.

<handler class=

"com.qualityeclipse.favorites.handlers.RenameFavoritesHandler"

commandId="com.qualityeclipse.favorites.commands.rename">

<enabledWhen>

<with variable="selection">

<count value="+"/>

</with>

</enabledWhen>

</handler>

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.3

View Commands 335

Next, we must modify the

FavoritesView

table to open a text cell editor when the

Rename...

command is selected, display the appropriate text in that cell editor, and store the modified value back into the underlying model. Start by modifying createPartControl

to call a new createInlineEditor method. This method initializes a new

TableViewerColumn

that is responsible for positioning and sizing the cell editor over the item being renamed and managing that cell editor’s lifecycle. An

EditingSupport

object instantiates the cell editor, in this case a

Text

field, initializes its value and stores the resulting user modification back into the Favorites model.

private void createInlineEditor() {

TableViewerColumn column =

new TableViewerColumn(viewer, nameColumn);

column.setLabelProvider(new ColumnLabelProvider() {

public String getText(Object element) {

return ((IFavoriteItem) element).getName();

}

});

column.setEditingSupport(new EditingSupport(viewer) {

TextCellEditor editor = null;

protected boolean canEdit(Object element) {

return true;

}

protected CellEditor getCellEditor(Object element) {

if (editor == null) {

Composite table = (Composite) viewer.getControl();

editor = new TextCellEditor(table);

}

return editor;

}

protected Object getValue(Object element) {

return ((IFavoriteItem) element).getName();

}

protected void setValue(Object element, Object value) {

((IFavoriteItem) element).setName((String) value);

viewer.refresh(element);

}

});

}

At this point, the user can select the

Rename...

command in the context menu or click on the name in the

Favorites

view to rename a particular Favorites item. The

TableViewerColumn

provides the “click to rename” behavior by default which is not quite what we want. To modify this behavior, we add the following at the end of the createInlineEditor

method to allow cell

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

336 CHAPTER 7 • Views

editing only when it is triggered programmatically, such as from the

Rename-

FavoritesHandler

, or triggered when the user

Alt-Click

s on the name in the

Favorites

view.

viewer.getColumnViewerEditor().addEditorActivationListener(

new ColumnViewerEditorActivationListener() {

public void beforeEditorActivated(

ColumnViewerEditorActivationEvent event) {

if (event.eventType == event.MOUSE_CLICK_SELECTION) {

if (!(event.sourceEvent instanceof MouseEvent))

event.cancel = true;

else {

MouseEvent m = (MouseEvent) event.sourceEvent;

if ((m.stateMask & SWT.ALT) == 0)

event.cancel = true;

}

}

else if (event.eventType != event.PROGRAMMATIC)

event.cancel = true;

}

public void afterEditorActivated(

ColumnViewerEditorActivationEvent event) {

}

public void beforeEditorDeactivated(

ColumnViewerEditorDeactivationEvent event) {

}

public void afterEditorDeactivated(

ColumnViewerEditorDeactivationEvent event) {

}

});

We also want to hook this up so that the user can press

F2

to directly edit the item name, similar to the way that the

Delete

key was previously hooked to the delete action (see Section 7.3.5, Keyboard commands, on page 320).

Add the following to the handleKeyReleased

method to accomplish this.

if (event.keyCode == SWT.F2 && event.stateMask == 0) {

new RenameFavoritesHandler().editElement(this);

}

7.4

Linking the View

In many situations, the current selection in the active view can affect the selection in other views, cause an editor to open, change the selected editor, or change the selection within an already open editor. For example, in the Java

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.4

Linking the View 337

browsing perspective (see Section 1.2.1.1, Java perspectives, on page 7), changing the selection in the

Types

view changes the selection in both the

Projects

and the

Packages

views, changes the content displayed in the

Members

view, and changes the active editor. For a view to both publish its own selection and to consume the selection of the active part, it must be both a

selection provider

and a

selection listener

.

7.4.1

Selection provider

For a view to be a selection provider, it must register itself as a selection provider with the view site. In addition, each of the objects contained in the view should be adaptable (see the next section) so that other objects can adapt the selected objects into objects they can understand. In the

Favorites

view, register the view as a selection provider by adding the following to the createTableViewer()

method: getSite().

setSelectionProvider

(viewer);

7.4.2

Adaptable objects

The org.eclipse.core.runtime.IAdaptable

interface allows an object to convert one type of object that it may not understand to another type of object that it can interrogate and manipulate (more on adapters in Section 21.3,

Adapters, on page 784). For the

Favorites

view, this means that the

IFavoritesItem

interface must extend the

IAdaptable

interface, and the following two getAdapter()

methods must be added to

FavoriteResource and

FavoriteJavaElement

, respectively.

public Object getAdapter(Class adapter) {

if (adapter.isInstance(resource))

return resource;

return Platform.getAdapterManager().getAdapter(this, adapter);

} public Object getAdapter(Class adapter) {

if (adapter.isInstance(element))

return element;

IResource resource = element.getResource();

if (adapter.isInstance(resource))

return resource;

return Platform.getAdapterManager().getAdapter(this, adapter);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

338 CHAPTER 7 • Views

7.4.3

Selection listener

For a view to consume the selection of another part, it must add a selection listener to the page so that when the active part changes or the selection in the active part changes, it can react by altering its own selection appropriately. For the

Favorites

view, if the selection contains objects that can be adapted to the objects in the view, then the view should adjust its selection. To accomplish this, add a call at the end of the createPartControl()

method to the following new hookPageSelection()

method.

private ISelectionListener pageSelectionListener; private void hookPageSelection() {

pageSelectionListener = new ISelectionListener() {

public void selectionChanged(

IWorkbenchPart part,

ISelection selection) {

pageSelectionChanged(part, selection);

}

};

getSite().getPage().addPostSelectionListener(

pageSelectionListener);

} protected void pageSelectionChanged(

IWorkbenchPart part,

ISelection selection

) {

if (part == this)

return;

if (!(selection instanceof IStructuredSelection))

return;

IStructuredSelection sel = (IStructuredSelection) selection;

IFavoriteItem[] items = FavoritesManager.getManager()

.existingFavoritesFor(sel.iterator());

if (items.length > 0)

viewer.setSelection(new StructuredSelection(items), true);

}

Then add the following to the dispose()

method to clean up when the

Favorites

view is closed.

if (pageSelectionListener != null)

getSite().getPage().removePostSelectionListener(

pageSelectionListener);

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.4

Linking the View 339

7.4.4

Opening an editor

When a user double-clicks on a file in the

Favorites

view, a file editor should open. To accomplish this, add a new

FavoritesView

method that is called from the createPartControl()

method.

private void hookMouse() {

viewer.getTable().addMouseListener(new MouseAdapter() {

public void mouseDoubleClick(MouseEvent e) {

EditorUtil.openEditor(getSite().getPage(),

viewer.getSelection());

}

});

}

This method references a new static method in a new

EditorUtil

class.

The new static method examines the first element in the current selection, and if that element is an instance of

IFile

, opens an editor on that file.

public static void openEditor(

IWorkbenchPage page, ISelection selection)

{

// Get the first element.

if (!(selection instanceof IStructuredSelection))

return;

Iterator<?> iter = ((IStructuredSelection) selection).iterator();

if (!iter.hasNext())

return;

Object elem = iter.next();

// Adapt the first element to a file.

if (!(elem instanceof IAdaptable))

return;

IFile file = (IFile) ((IAdaptable) elem).getAdapter(IFile.class);

if (file == null)

return;

// Open an editor on that file.

try {

IDE.openEditor(page, file);

}

catch (PartInitException e) {

FavoritesLog.logError(

"Open editor failed: " + file.toString(), e);

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

340

7.5

Saving View State

CHAPTER 7 • Views

Up to this point, the

Favorites

view contains only the current list of projects when the Eclipse session starts up. Items can be added to the

Favorites

view during the course of the session, but as soon as Eclipse is shut down, the changes are lost. In addition, the view’s sort and filter information should be saved so that the view will be returned to the same state when the session is restarted. To accomplish all this, two different mechanisms are used.

7.5.1

Saving local view information

Eclipse provides a memento-based mechanism for saving view and editor state information. In this case, this mechanism is good for saving the sorting and filter state of a view because that information is specific to each individual view. It is not good for saving global information shared by multiple views, so that is tackled in the next section.

To save the sorting state, two methods should be added to the

FavoritesViewSorter

. The first method saves the current sort state as an instance of

IMemento

by converting the sort order and ascending/descending state into an XML-like structure. The second method takes a very guarded approach to reading and resetting the sort order and ascending/descending state from

IMemento

so that the sort state will be valid even if

IMemento

is not what was expected.

private static final String TAG_DESCENDING = "descending"; private static final String TAG_COLUMN_INDEX = "columnIndex"; private static final String TAG_TYPE = "SortInfo"; private static final String TAG_TRUE = "true"; public void saveState(IMemento memento) {

for (int i = 0; i < infos.length; i++) {

SortInfo info = infos[i];

IMemento mem = memento.createChild(TAG_TYPE);

mem.putInteger(TAG_COLUMN_INDEX, info.columnIndex);

if (info.descending)

mem.putString(TAG_DESCENDING, TAG_TRUE);

}

} public void init(IMemento memento) {

List<SortInfo> newInfos = new ArrayList<SortInfo>(infos.length);

IMemento[] mems = memento.getChildren(TAG_TYPE);

for (int i = 0; i < mems.length; i++) {

IMemento mem = mems[i];

Integer value = mem.getInteger(TAG_COLUMN_INDEX);

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.5

Saving View State 341

if (value == null)

continue;

int index = value.intValue();

if (index < 0 || index >= infos.length)

continue;

SortInfo info = infos[index];

if (newInfos.contains(info))

continue;

info.descending =

TAG_TRUE.equals(mem.getString(TAG_DESCENDING));

newInfos.add(info);

}

for (int i = 0; i < infos.length; i++)

if (!newInfos.contains(infos[i]))

newInfos.add(infos[i]);

infos = newInfos.toArray(new SortInfo[newInfos.size()]);

}

In addition to saving the sort state, the filter state needs to be saved. This is accomplished by adding the following two methods to the

Favorites-

ViewFilterAction

type.

public void saveState(IMemento memento) {

nameFilter.saveState(memento);

} public void init(IMemento memento) {

nameFilter.init(memento);

}

Then add two new methods to

FavoritesViewNameFilter: private static final String TAG_PATTERN = "pattern"; private static final String TAG_TYPE = "NameFilterInfo"; public void saveState(IMemento memento) {

if (pattern.length() == 0)

return;

IMemento mem = memento.createChild(TAG_TYPE);

mem.putString(TAG_PATTERN, pattern);

} public void init(IMemento memento) {

IMemento mem = memento.getChild(TAG_TYPE);

if (mem == null)

return;

setPattern(mem.getString(TAG_PATTERN));

}

These new methods are hooked to the view by adding the following field and methods to the

FavoritesView

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

342

private IMemento memento; public void saveState(IMemento memento) {

super.saveState(memento);

sorter.saveState(memento);

filterAction.saveState(memento);

} public void init(IViewSite site, IMemento memento)

throws PartInitException

{

super.init(site, memento);

this.memento = memento;

}

CHAPTER 7 • Views

The sorting and filter state cannot be restored immediately in the init() method shown above because the part control has not been created. Instead, the method caches

IMemento

for use later during the initialization process.

You must then modify both the createTableSorter()

method and the createViewPulldownMenu()

method as shown next to restore the sorting and filter state before associating the sorter with the viewer and the filter action with the menu, respectively.

private void createTableSorter() {

... same code as in Section 7.2.6 on page 308 ...

if (memento != null)

sorter.init(memento);

viewer.setSorter(sorter);

} private void createViewPulldownMenu() {

... same code as in a Section 7.3.4 on page 319 ...

if (memento != null)

filterAction.init(memento);

menu.add(filterAction);

}

Eclipse stores all memento-based view and editor state information in a single file:

<workspace>\.metadata\.plugins\org.eclipse.ui.workbench\workbench.xml

For example (reformatted so that it’s easier to read):

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.5

Saving View State 343

<views>

<view

id="com.qualityeclipse.favorites.views.FavoritesView"

partName="Favorites">

<viewState>

<SortInfo columnIndex="0" descending="true"/>

<SortInfo columnIndex="1"/>

<SortInfo columnIndex="2"/>

</viewState>

</view>

<view id="org.eclipse.ui.views.TaskList" partName="Tasks">

<viewState

columnWidth0="19" columnWidth1="19" columnWidth2="288"

columnWidth3="108" columnWidth4="216" columnWidth5="86"

horizontalPosition="0" verticalPosition="0">

<selection/>

</viewState>

</view>

...

</views>

7.5.2

Saving global view information

Now you need to save the state of the

FavoritesManager

, which is shared by all

Favorites

views. For this to occur, augment the

FavoritesActivator

, the

FavoritesManager

, and each

Favorites

item with the ability to save their information so that they can be recreated later. In the

FavoritesActivator

, augment the stop()

method to call a new saveFavorites()

method in the

FavoritesManager

.

FavoritesManager.getManager().saveFavorites();

The existing loadFavorites()

method in the

FavoritesManager

must be revised as follows and new methods added so that the

Favorites

items will be lazily loaded when needed. Lazy initialization is the Eclipse theme, so the list will not be built until it is needed. In addition, a new saveFavorites() method must be added to store the

Favorites

items so that they can be restored when Eclipse is restarted. private static final String TAG_FAVORITES = "Favorites"; private static final String TAG_FAVORITE = "Favorite"; private static final String TAG_TYPEID = "TypeId"; private static final String TAG_INFO = "Info"; private void loadFavorites() {

favorites = new HashSet<IFavoriteItem>(20);

FileReader reader = null;

try {

reader = new FileReader (getFavoritesFile());

loadFavorites(XMLMemento.createReadRoot(reader));

}

catch (FileNotFoundException e) {

// Ignored... no Favorites items exist yet.

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

344 CHAPTER 7 • Views

catch (Exception e) {

// Log the exception and move on.

FavoritesLog.logError(e);

}

finally {

try {

if (reader != null) reader.close();

} catch (IOException e) {

FavoritesLog.logError(e);

}

}

} private void loadFavorites(XMLMemento memento) {

IMemento [] children = memento.getChildren(TAG_FAVORITE);

for (int i = 0; i < children.length; i++) {

IFavoriteItem item =

newFavoriteFor(

children[i].getString(TAG_TYPEID),

children[i].getString(TAG_INFO));

if (item != null)

favorites.add(item);

}

} public IFavoriteItem newFavoriteFor(String typeId, String info) {

FavoriteItemType[] types = FavoriteItemType.getTypes();

for (int i = 0; i < types.length; i++)

if (types[i].getId().equals(typeId))

return types[i].loadFavorite(info);

return null;

} public void saveFavorites() {

if (favorites == null)

return;

XMLMemento memento = XMLMemento.createWriteRoot(TAG_FAVORITES);

saveFavorites(memento);

FileWriter writer = null;

try {

writer = new FileWriter(getFavoritesFile());

memento.save(writer);

}

catch (IOException e) {

FavoritesLog.logError(e);

}

finally {

try {

if (writer != null)

writer.close();

}

catch (IOException e) {

FavoritesLog.logError(e);

}

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.6

Testing 345

private void saveFavorites(XMLMemento memento) {

Iterator<IFavoriteItem> iter = favorites.iterator();

while (iter.hasNext()) {

IFavoriteItem item = (IFavoriteItem) iter.next();

IMemento child = memento.createChild(TAG_FAVORITE);

child.putString(TAG_TYPEID, item.getType().getId());

child.putString(TAG_INFO, item.getInfo());

}

} private File getFavoritesFile() {

return FavoritesActivator

.getDefault()

.getStateLocation()

.append("favorites.xml")

.toFile();

}

The load

and save

methods interact with a file named favorites.xml

, which is located in the following workspace metadata subdirectory:

<workspace>\.metadata\.plugins\com.qualityeclipse.favorites

.

The file content is in XML format and might look something like this:

<?xml version="1.0" encoding="UTF-8"?>

<Favorites>

<Favorite

Info="/First Project/com/qualityeclipse/sample

/HelloWorld.java"

TypeId="WBFile"/>

<Favorite

Info="/com.qualityeclipse.favorites/src"

TypeId="WBFolder"/>

...

</Favorites>

Tip

: Eclipse can crash or lock up…not often, if ever, but it can. If it does, then the normal shutdown sequence is preempted and your plug-in will not get a chance to save its model state. To protect your data, you can register a save participant (

ISaveParticipant

) and store critical model states (“snapshots”) at various times during the Eclipse session. The mechanism is the same as that used to receive resource change events when your plug-in is inactive (see Section 9.5, Delayed Changed Events, on page 420).

7.6

Testing

Now that the

Favorites

view has been modified, the JUnit tests for the

Favorites

view need to be updated to take the modifications into account. If the tests are run as they stand, you’ll get the following failure.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

346 CHAPTER 7 • Views

testView(com.qualityeclipse.favorites.test.FavoritesViewTest) java.lang.AssertionError: array lengths differed, expected.length=3 actual.length=0

at org.junit.Assert.fail(Assert.java:71)

at org.junit.Assert.internalArrayEquals(Assert.java:293)

at org.junit.Assert.assertArrayEquals(Assert.java:129)

at org.junit.Assert.assertArrayEquals(Assert.java:140)

at com.qualityeclipse.favorites.test.

FavoritesViewTest.testView(FavoritesViewTest.java:63)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

... etc ...

On closer inspection, this test is looking for the default viewer content (see

Section 2.8.3, Creating a Plug-in test, on page 100). Since this default content has been removed in favor of real content (see Section 7.2.4, Content provider, on page 306), the test should be modified as follows: public void testView() {

TableViewer viewer = testView.getFavoritesViewer();

Object[] expectedContent = new Object[] { };

Object[] expectedLabels = new String[] { };

... code for the rest of the test ...

}

In a similar fashion, add code to the

AddToFavoritesTest

(see Section

6.7.6, Adding a test for the new action, on page 268) to assert the

Favorites

view content before and after the test. Since this type of assertion is duplicated in several places, it can be extracted into a new assertFavorites-

ViewContent

method and pushed up into the

AbstractFavoritesTest class.

Tip

: Add org.junit.Assert

to the Java > Editor > Content Assist >

Favorites preference page so that Ctrl-Space content assist suggestions will include the various JUnit Assert static methods.

7.7

Image Caching

Image is a Java construct that wraps a native resource and thus must be properly managed. As with all other native wrappers in Eclipse, the rule is that if you create it, you must dispose of it to prevent memory leaks.

ImageDescriptor

, on the other hand, is a pure Java type that identifies a particular image without its associated native resource. It does not need to be

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.7

Image Caching 347

managed and removed properly; rather, it will be automatically managed and disposed of by the Java garbage collector.

When a plug-in creates an instance of

Image

, it typically caches it in an object that maps the identifier for the image—typically an

ImageDescriptor

— to a particular image. Not only does the cache provide a way to remember which

Image

instances were created and thus need to be cleaned up, but it also keeps the same image from being loaded into memory more than once, preventing unnecessary usage of limited OS resources. Depending on where and when the image is used, the image cache may be disposed when the view closes, or it may be kept around for the life of the plug-in.

In the

Favorites

plug-in, if you need to load your own images (see Section

7.2.3, View model, on page 295), instantiate a class similar to the one below to cache loaded images. This class follows the Eclipse approach by lazily loading the images as they are requested rather than loading all images immediately when the plug-in starts or when the view is first opened. The plug-in’s stop()

method would be modified to call the dispose()

method of this instance so that the images would be cleaned up when the plug-in is shut down.

public class ImageCache {

private final Map<ImageDescriptor, Image> imageMap =

new HashMap<ImageDescriptor, Image>();

public Image getImage(ImageDescriptor imageDescriptor) {

if (imageDescriptor == null)

return null;

Image image = (Image) imageMap.get(imageDescriptor);

if (image == null) {

image = imageDescriptor.createImage();

imageMap.put(imageDescriptor, image);

}

return image;

}

public void dispose() {

Iterator<Image> iter = imageMap.values().iterator();

while (iter.hasNext())

iter.next().dispose();

imageMap.clear();

}

}

Alternatively, you can use the class org.eclipse.jface.resource.

ImageRegistry

or the

Plugin.getImageRegistry()

method.

Tip:

WindowBuilder Pro (see Appendix A, Eclipse Plug-ins and Resources) provides a

ResourceManager

that caches images, fonts, cursors, and so on.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

348 CHAPTER 7 • Views

7.8

Auto-sizing Table Columns

Another nice enhancement to the

Favorites

view is for the columns in the table to be automatically resized to fit the current space. Eclipse provides

Table-

ColumnLayout

for auto-sized tables and

TreeColumnLayout

for auto-sized trees. Replace the

Favorites

view table layout by modifying createTable-

Viewer()

as shown below.

TableColumnLayout layout = new TableColumnLayout(); parent.setLayout(layout);

typeColumn = new TableColumn(table, SWT.LEFT); typeColumn.setText("");

layout.setColumnData(typeColumn, new ColumnPixelData(18));

nameColumn = new TableColumn(table, SWT.LEFT); nameColumn.setText("Name");

layout.setColumnData(nameColumn, new ColumnWeightData(4));

locationColumn = new TableColumn(table, SWT.LEFT); locationColumn.setText("Location");

layout.setColumnData(locationColumn, new ColumnWeightData(9));

7.9

RFRS Considerations

The “User Interface” section of the

RFRS Requirements

includes seven items—five requirements and two best practices—dealing with views. All of them are derived from the Eclipse UI Guidelines.

7.9.1

Views for navigation (RFRS 3.5.15)

User Interface Guideline #7.1

is a

requirement

that states:

Use a view to navigate a hierarchy of information, open an editor, or display the properties of an object.

To pass this test, create a list of the views defined by your application and demonstrate how they are used to navigate information, open editors, or display the properties of some object. In the case of the examples presented earlier in this chapter, show the

Favorites

view (see Figure 10–4 on page 427) and describe its use to the reviewers. In particular, double-clicking on a file in the

Favorites

view will open the file in an editor.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.9

RFRS Considerations 349

7.9.2

Views save immediately

User Interface Guideline #7.2

is a

requirement

that states:

(RFRS 3.5.16)

Modifications made within a view must be saved immediately. For instance, if a file is modified in the

Navigator

, the changes must be committed to the workspace immediately. A change made in the

Outline

view must be committed to the edit model of the active editor immediately. For changes made in the

Properties

view, if the property is a property of an open edit model, it should be persisted to the edit model. If it is a property of a file, persist it to file. In the past, some views have tried to implement an editor-style lifecycle with a save action. This can cause confusion. The

File

menu within a workbench window contains a

Save

action, but it only applies to the active editor. It will not target the active view. This can lead to a situation where the

File > Save

action is in contradiction with the

Save

action within the view.

For this test, show how changes made in your view are saved immediately.

If your view updates an existing editor, make sure that the editor is immediately marked as dirty and shows the modification indicator (

*

). Further, show that the

Save

menu does not need to be invoked for the view to save its changes.

7.9.3

View initialization (RFRS 3.5.17)

User Interface Guideline #7.8

is a

requirement

that states:

When a view first opens, derive the view input from the state of the perspective. The view may consult the perspective input or selection, or the state of another view. For instance, if the

Outline

view is opened, it will determine the active editor, query the editor for an outline model, and display the outline model.

To pass this test, show that your view reflects the input state of the perspective (if appropriate). If your view is meant to show some attribute of the selected editor, make sure that when it is opened it displays the appropriate information. For the

Favorites

view, this requirement probably does not apply.

The

Favorites

view could be extended to update its own selection to reflect the currently active editor.

7.9.4

View global actions

User Interface Guideline #7.19

is a

requirement

that states:

(RFRS 3.5.18)

If a view has support for

cut

,

copy

,

paste

, or any of the global actions, the same actions must be executable from the same actions in the window

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

350 CHAPTER 7 • Views menu and toolbar. The window menu contains a number of global actions, such as

cut

,

copy

, and

paste

in the

Edit

menu. These actions target the active part, as indicated by a shaded title area. If these actions are supported within a view, the view should hook these window actions so that selection in the window menu or toolbar produces the same result as selection of the same action in the view. The following are the supported global actions:

undo

,

redo

,

cut

,

copy

,

paste

,

print

,

delete

,

find

,

select all

, and

bookmark

.

For this requirement, if your view implements any of the items on the global action list, show that those commands can also be invoked from the window menus and toolbars. For the

Favorites

view, show that the

Cut

,

Copy

,

Paste

, and

Delete

(

Remove

) commands can be invoked from the platform

Edit

menu.

7.9.5

Persist view state (RFRS 3.5.19)

User Interface Guideline #7.20

is a

requirement

that states:

Persist the state of each view between sessions. If a view is self-starting in the sense that its input is not derived from selection in other parts, the state of the view should be persisted between sessions. Within the workbench, the state of the

Navigator

view, including the input and expansion state, is saved between sessions.

Show that your view persists its state between sessions. For the

Favorites

view, shut down and restart the workbench and show that the

Favorites

items appearing in the list are the same ones that were there when the workbench was shut down.

7.9.6

Register context menus (RFRS 5.3.5.8)

User Interface Guideline #7.17

is a

best practice

that states:

Register all context menus in the view with the platform. In the platform, the menu and toolbar for a view are automatically extended by the platform. By contrast, the context menu extension is supported in collaboration between the view and the platform. To achieve this collaboration, a view must register each context menu it contains with the platform.

Show that the context menu of your view is extensible by the platform. If the platform defines commands that are appropriate for the objects contained in your view, those commands should appear in the view’s context menu. For the

Favorites

view, show that common Eclipse commands such as “Replace

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

7.9

RFRS Considerations 351

With” and “Compare With” appear when you right-click on a

Favorites

item

(see Figure 7–9).

Figure 7–9

Favorites view showing platform contributions to the context menu.

7.9.7

Action filters for views (RFRS 5.3.5.9)

User Interface Guideline #7.18

is a

best practice

that states:

Implement an action filter for each object type in the view. An action filter makes it easier for one plug-in to add an action to objects in a view defined by another plug-in. An action target is described using object type and attributes.

As with the previous best practice, show that any commands contributed to your view’s context menu are appropriate to the type of the selected object.

Commands that don’t apply should be filtered out. For the

Favorites

view, show that the platform commands contributed to the context menu are context-sensitive based on the type of object selected (see Figure 7–10).

Figure 7–10

Favorites view showing that context menu items are filtered based on their type (projects show items other than files).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

352 CHAPTER 7 • Views

7.10

Summary

This chapter covered creating new views, modifying a view to respond to selections in the active editor or other views, and exporting a view’s selection to the rest of Eclipse. The next chapter discusses editors, which are used to edit the state of individual resources.

References

Chapter source (see Section 2.9, Book Samples, on page 105).

D’Anjou, Jim, Scott Fairbrother, Dan Kehn, John Kellerman, and Pat McCarthy,

The Java Developer’s Guide to Eclipse, Second Edition

. Addison-Wesley,

Boston, 2004.

McAffer, Jeff, and Jean-Michel Lemieux,

Eclipse Rich Client Platform:

Designing, Coding, and Packaging Java Applications

. Addison-Wesley, Boston, 2005.

Springgay, Dave, “Creating an Eclipse View,” OTI, November 2, 2001

(

www.eclipse.org/articles/viewArticle/ViewArticle2.html

).

Liotta, Matt, “Extending Eclipse with Helpful Views,” July 20, 2004

(

www.devx.com/opensource/Article/21562

).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 8

Editors

Editors are the primary mechanism for users to create and modify resources

(e.g., files). Eclipse provides some basic editors such as text and Java source editors, along with some more complex multipage editors such as the plug-in manifest editor. Products that need to present their own editors can use the same extension points used by the built-in Eclipse editors. This chapter discusses creating a new

Properties

editor, hooking up commands to it, and linking the editor to the

Outline

view.

Editors must implement the org.eclipse.ui.IEditorPart

interface.

Typically, editors are subclasses of org.eclipse.ui.part.EditorPart

and thus indirectly subclasses of org.eclipse.ui.part.WorkbenchPart

, inheriting much of the behavior needed to implement the

IEditorPart

interface

(see Figure 8–1).

Editors are contained in an org.eclipse.ui.IEditorSite

, which in turn is contained in an org.eclipse.ui.IWorkbenchPage

. In the spirit of lazy initialization,

IWorkbenchPage

holds on to instances of org.eclipse.

ui.IEditorReference

rather than the editor itself so that editors can be enumerated and referenced without actually loading the plug-in defining the editor.

353

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

354

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 8 • Editors

Figure 8–1

EditorPart classes.

Editors share a common set of behaviors with views via the org.eclipse.ui.part.WorkbenchPart

superclass and org.eclipse.ui.

IWorkbenchPart

interface, but have some very important differences. Editors follow the classic open-modify-save paradigm, whereas any command performed in a view should immediately affect the state of the workspace and underlying resource(s).

Editors appear in one area of Eclipse, while views are arranged around the outside of the editor area. Editors are typically resource-based, while views can show information about one resource, multiple resources, or even something totally unrelated to resources such as available memory, network status, or builder errors.

8.1

Editor Declaration

There are two steps involved in creating a new editor:

• Define the editor in the plug-in manifest file (see Figure 8–2).

• Create the editor part containing the code.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.1

Editor Declaration 355

One way to do all this at once is to create the editor when the plug-in is being created, similar to the way that views can be created (see Section 2.2.3,

Define the view, on page 75). If the plug-in already exists, then this becomes a two-step process.

Figure 8–2

Editor declaration in plug-in manifest.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

356 CHAPTER 8 • Editors

The first step in creating an editor is to define the editor in the plug-in manifest (see Figure 8–2). On the

Extensions

page of the plug-in manifest editor, click the

Add…

button in the upper right corner, select

org.eclipse.ui.

editors

, and finally click

Finish

. Select the new editor extension to display the properties on the right and then enter the following values.

class—

“com.qualityeclipse.favorites.editors.PropertiesEditor”

The fully qualified name of the class defining the editor and implementing org.eclipse.ui.IEditorPart

(see Section 8.2, Editor Part, on page 358). Click the

Browse…

button to the right of the field to open a dialog and select an existing editor part. Click the

class

label on the left to generate a new one. The attribute’s class, command, and launcher are mutually exclusive. The class is instantiated using its no argument constructor, but can be parameterized using the

IExecutableExtension interface (see Section 21.5, Types Specified in an Extension Point, on page 793).

contributorClass—

“com.qualityeclipse.favorites.editors.

PropertiesEditorContributor”

The fully qualified name of a class that implements org.eclipse.ui.

IEditorActionBarContributor

and adds new actions to the workbench menu and toolbar, reflecting the features of the editor type (see

Section 8.5.2, Editor contributor, on page 384). This attribute should only be defined if the class attribute is defined. Click the

Browse…

button to the right of the field to open a dialog for selecting an existing editor contributor. Click the

contributorClass

on the left to generate a new one.

extensions—

“properties”

A string of comma-separated file extensions indicating file types understood by the editor.

icon—

“icons/sample.gif”

The image displayed at the upper left corner of the editor. Similar to action images (see Section 6.6.4, Action images, on page 247), this path is relative to the plug-in’s installation directory.

id—

“com.qualityeclipse.properties.editor”

The unique identifier for this editor.

name—

“Properties Editor”

The human-readable name for the editor.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.1

Editor Declaration 357

Other attributes that are not used in this example include:

command—

A command to run to launch an external editor. The executable command must be located on the system path or in the plug-in’s directory. The attribute’s

class, command, and launcher are mutually exclusive.

default—

“true” or “false” (blank = false

)

If true

, this editor will be used as the default editor for the type. This is only relevant in the case where more than one editor is registered for the same type. If an editor is not the default for the type, it can still be launched using the

Open with...

submenu for the selected resource.

filenames—

A string containing comma-separated filenames indicating filenames understood by the editor. For instance, an editor that understands plug-in and fragment manifest files can register plugin.xml

, fragment.xml

.

launcher—

The name of a class that implements org.eclipse.ui.

IEditorLauncher

and opens an external editor. The attribute’s

class, command, and launcher are mutually exclusive.

matchingStrategy—

the name of a class that implements org.eclipse. ui.IEditorMatchingStrategy

. This attribute should only be defined if the class attribute is defined and allows the editor extension to provide its own algorithm for matching the input of one of its editors to a given editor input. This is used to find a matching editor during openEditor()

and findEditor()

.

In addition, the

editor

element can have one or more

contentTypeBinding

subelements, each specifying a

contentTypeId

. The

contentTypeId

references an org.eclipse.core.runtime.contentTypes

extension and indicates that the editor can contain that type of content. The

contentTypes

extension can more accurately define whether a file should be associated with a partiular editor than by file extension alone.

After filtering files by name and extension, the content type uses a

describer

—an instance of

IContentDescriber

or

ITextContentDescriber

—to scan the content of a file before determining whether a file contains a particular type of content. Eclipse provides several built-in describers including the following:

BinarySignatureDescriber

A content describer for binary formats that present some simple signature at a known, fixed offset. There are three parameters: “signature,” “offset,” and “required”—the first one being mandatory.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

358 CHAPTER 8 • Editors

signature—

a sequence of hex codes, one for each byte. For example,

“CA FE BA BE” would be a signature for Java class files.

offset—

an integer indicating the offset where the signature’s first byte is to be found.

required—

a boolean (default is true

) indicating whether the absence of a signature should deem the contents validity status as

IContent-

Describer.INVALID

or

IContentDescriber.INDETERMINATE

.

XMLRootElementContentDescriber

A content describer for detecting the name of the top-level element or the DTD system identifier in an

XML file. Two parameters are supported: “dtd” and “element.”

8.2

Editor Part

The code defining the editor’s behavior is found in a class implementing the org.eclipse.ui.IEditorPart

interface, typically by subclassing one of the following concrete classes:

• org.eclipse.ui.part.EditorPart

—Abstract base implementation of the org.eclipse.ui.IEditorPart

interface

• org.eclipse.ui.part.MultiPageEditorPart

—Abstract base implementation extending

EditorPart

for multi-page editors.

• org.eclipse.ui.forms.editor.FormEditor

—Abstract base implementation extending

MultiPageEditorPart

for multi-page editors that typically use one or more pages with forms and one page for raw source of the editor input.

The

Properties

editor subclasses

MultiPageEditorPart

and provides two pages for the user to edit its content.

8.2.1

Editor methods

Here are the

EditorPart methods.

createPartControl(Composite)

—Creates the controls comprising the editor. Typically, this method simply calls more finely grained methods such as createTree

, createTextEditor

, and so on.

dispose()

—This method is automatically called when the editor is closed and marks the end of the editor’s lifecycle. It cleans up any platform resources, such as images, clipboard, and so on, which were created by this class. This follows the

if you create it, you destroy it

theme that runs throughout Eclipse.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.2

Editor Part 359

doSave(IProgressMonitor)

—Saves the contents of this editor. If the save is successful, the part should fire a property changed event

(

PROP_DIRTY

property), reflecting the new dirty state. If the save is canceled via user action, or for any other reason, the part should invoke setCanceled

on the

IProgressMonitor

to inform the caller

(see Section 9.4, Progress Monitor, on page 415).

doSaveAs()

—This method is

optional

. It opens a

Save As

dialog and saves the content of the editor to a new location. If the save is successful, the part should fire a property changed event (

PROP_DIRTY

property), reflecting the new dirty state.

gotoMarker(IMarker)

—Sets the cursor and selection state for this editor as specified by the given marker.

init(IEditorSite, IEditorInput)

—Initializes this editor with the given editor site and input. This method is automatically called shortly after editor construction; it marks the start of the editor’s lifecycle.

isDirty()

—Returns whether the contents of this editor have changed since the last save operation.

isSaveAsAllowed()

—Returns whether the “Save As” operation is supported by this part.

setFocus()

—Asks this part to take focus within the workbench. Typically, this method simply calls setFocus()

on one of its child controls.

MultiPageEditorPart provides the following additional methods: addPage(Control)

—Creates and adds a new page containing the given control to this multipage editor. The control may be null

, allowing it to be created and set later using setControl

.

addPage(IEditorPart, IEditorInput)

—Creates and adds a new page containing the given editor to this multipage editor. This also hooks a property change listener onto the nested editor.

createPages()

—Creates the pages of this multipage editor. Typically, this method simply calls more finely grained methods such as createPropertiesPage

, createSourcePage

, and so on.

getContainer()

—Returns the composite control containing this multipage editor’s pages. This should be used as the parent when creating controls for individual pages. That is, when calling addPage(Control)

, the passed control should be a child of this container.

setPageImage(int, Image)

—Sets the image for the page with the given index.

setPageText(int, String)

—Sets the text label for the page with the given index.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

360 CHAPTER 8 • Editors

8.2.2

Editor controls

The new

PropertiesEditor

is a multipage editor containing

Properties

and

Source

pages. The

Properties

page contains a tree displaying the property key/value pairs, while the

Source

page displays the text as it appears in the file itself. These pages showcase building an editor out of individual controls

(

Properties

page) and nesting one type of editor inside another (

Source

page).

Start by creating a new subclass of

MultiPageEditorPart

. The new

PropertiesEditor

class contains an init()

method ensuring that the appropriate type of content is being edited.

package com.qualityeclipse.favorites.editors; import ...

import com.qualityeclipse.favorites.FavoritesLog; public class PropertiesEditor extends MultiPageEditorPart

{

public void init(IEditorSite site, IEditorInput input)

throws PartInitException

{

if (!(input instanceof IFileEditorInput))

throw new PartInitException(

"Invalid Input: Must be IFileEditorInput");

super.init(site, input);

}

Next, add two fields plus methods to create the

Source

and

Properties

pages.

private TreeViewer treeViewer; private TextEditor textEditor; protected void createPages() {

createPropertiesPage();

createSourcePage();

updateTitle();

} void createPropertiesPage() {

treeViewer = new TreeViewer(

getContainer(), SWT.MULTI | SWT.FULL_SELECTION);

int index = addPage(treeViewer.getControl());

setPageText(index, "Properties");

} void createSourcePage() {

try {

textEditor = new TextEditor();

int index = addPage(textEditor, getEditorInput());

setPageText(index, "Source");

}

catch (PartInitException e) {

FavoritesLog.logError("Error creating nested text editor", e);

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.2

Editor Part 361

void updateTitle() {

IEditorInput input = getEditorInput();

setPartName(input.getName());

setTitleToolTip(input.getToolTipText());

}

When the focus shifts to the editor, the setFocus()

method is called; it must then redirect focus to the appropriate editor based on which page is currently selected.

public void setFocus() {

switch (getActivePage()) {

case 0:

treeViewer.getTree().setFocus();

break;

case 1:

textEditor.setFocus();

break;

}

}

When the user directly or indirectly requests that a marker be revealed, ensure that the

Source

page is active, then redirect the request to the text editor. You could do something different when the

Properties

page is active, but that would require additional editor model infrastructure.

public void gotoMarker(IMarker marker) {

setActivePage(1);

((IGotoMarker) textEditor.getAdapter(IGotoMarker.class))

.gotoMarker(marker);

}

Three methods are involved in saving editor content. If the isSaveAsAllowed()

method returns false

, then the doSaveAs()

method is never called.

public boolean isSaveAsAllowed() {

return true;

} public void doSave(IProgressMonitor monitor) {

textEditor.doSave(monitor);

} public void doSaveAs() {

textEditor.doSaveAs();

setInput(textEditor.getEditorInput());

updateTitle();

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

362 CHAPTER 8 • Editors

This code defines a very simple editor. When the editor is opened, the first page is an empty tree (the content will be added in the next section), while the second page is an embedded text editor (see Figure 8–3). The editor handles all the normal text editing operations on the second page thanks to the embedded text editor, but the first page needs work.

Figure 8–3

The Properties editor’s Source page.

First, you need to add columns to the tree by adding two new fields plus additional functionality to the createPropertiesPage()

method. To make the display to look more polished, auto-size the columns in the tree similar to the way the

Favorites

view is auto-sized (see Section 7.8, Auto-sizing Table Columns, on page 348).

private TreeColumn keyColumn; private TreeColumn valueColumn; void createPropertiesPage() {

Composite treeContainer = new Composite(getContainer(), SWT.NONE);

TreeColumnLayout layout = new TreeColumnLayout();

treeContainer.setLayout(layout);

treeViewer = new TreeViewer(

treeContainer, SWT.MULTI | SWT.FULL_SELECTION);

Tree tree = treeViewer.getTree();

tree.setHeaderVisible(true);

keyColumn = new TreeColumn(tree, SWT.NONE);

keyColumn.setText("Key");

layout.setColumnData(keyColumn, new ColumnWeightData(2));

valueColumn = new TreeColumn(tree, SWT.NONE);

valueColumn.setText("Value");

layout.setColumnData(valueColumn, new ColumnWeightData(3));

int index = addPage(treeContainer);

setPageText(index, "Properties");

}

When run, the

Properties

editor now displays two empty columns on the

Properties

page (see Figure 8–4).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

8.2

Editor Part

5HWXUQWR7DEOHRI&RQWHQWV

363

Figure 8–4

Properties editor’s Properties page.

8.2.3

Editor model

The next step is to hook up the tree so that content in the text editor appears in the tree. To accomplish this, you need to build a model capable of parsing the text editor’s content, and then attach that model, along with a label provider, to the tree. Of course, there is lots of room for improvement in this model, such as splitting out the parsing, refactoring code into a separate class, and enhancing the parser to handle multiline values; however, it will do for the purposes of this demonstration.

Start this process by introducing a new

PropertyElement

superclass for all property model objects.

public abstract class PropertyElement

{

public static final PropertyElement[] NO_CHILDREN = {};

private PropertyElement parent;

public PropertyElement(PropertyElement parent) {

this.parent = parent;

}

public PropertyElement getParent() {

return parent;

}

public abstract PropertyElement[] getChildren();

public abstract void removeFromParent();

}

A

PropertyEntry

object represents a key/value pair in the property file. Note that the next three classes are all interdependent and should be added to your project at the same time.

public class PropertyEntry extends PropertyElement

{

String key;

String value;

public PropertyEntry(

PropertyCategory parent, String key, String value

) {

super(parent);

this.key = key;

this.value = value;

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

364 CHAPTER 8 • Editors

public String getKey() {

return key;

}

public String getValue() {

return value;

}

public PropertyElement[] getChildren() {

return NO_CHILDREN;

}

public void setKey(String text) {

if (key.equals(text))

return;

key = text;

((PropertyCategory) getParent()).keyChanged(this);

}

public void setValue(String text) {

if (value.equals(text))

return;

value = text;

((PropertyCategory) getParent()).valueChanged(this);

}

public void removeFromParent() {

((PropertyCategory) getParent()).removeEntry(this);

}

}

A

PropertyCategory

represents a group of related property entries with a comment preceding the group indicating the name. The category can extract its name and entries from a reader object.

package com.qualityeclipse.favorites.editors; import ...

public class PropertyCategory extends PropertyElement

{

private String name;

private List<PropertyEntry> entries;

public PropertyCategory(

PropertyFile parent, LineNumberReader reader

) throws IOException {

super(parent);

// Determine the category name from comments.

while (true) {

reader.mark(1);

int ch = reader.read();

if (ch == -1)

break;

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.2

Editor Part

reader.reset();

if (ch != '#')

break;

String line = reader.readLine();

if (name == null) {

line = line.replace('#', ' ').trim();

if (line.length() > 0)

name = line;

}

}

if (name == null)

name = "";

// Determine the properties in this category.

entries = new ArrayList<PropertyEntry>();

while (true) {

reader.mark(1);

int ch = reader.read();

if (ch == -1)

break;

reader.reset();

if (ch == '#')

break;

String line = reader.readLine();

int index = line.indexOf('=');

if (index != -1) {

String key = line.substring(0, index).trim();

String value = line.substring(index + 1).trim();

entries.add(new PropertyEntry(this, key, value));

}

}

}

public String getName() {

return name;

}

public Collection<PropertyEntry> getEntries() {

return entries;

}

public PropertyElement[] getChildren() {

return (PropertyElement[]) entries.toArray(

new PropertyElement[entries.size()]);

}

public void setName(String text) {

if (name.equals(text))

return;

name = text;

((PropertyFile) getParent()).nameChanged(this);

}

365

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

366 CHAPTER 8 • Editors

public void addEntry(PropertyEntry entry) {

if (!entries.contains(entry)) {

entries.add(entry);

((PropertyFile) getParent()).entryAdded(

this, entry);

}

}

public void removeEntry(PropertyEntry entry) {

if (entries.remove(entry))

((PropertyFile) getParent()).entryRemoved(

this, entry);

}

public void removeFromParent() {

((PropertyFile) getParent()).removeCategory(this);

}

public void keyChanged(PropertyEntry entry) {

((PropertyFile) getParent()).keyChanged(this, entry);

}

public void valueChanged(PropertyEntry entry) {

((PropertyFile) getParent()).valueChanged(this, entry);

}

}

The

PropertyFile

object ties it all together.

package com.qualityeclipse.favorites.editors; import ...

import com.qualityeclipse.favorites.FavoritesLog; public class PropertyFile extends PropertyElement

{

private PropertyCategory unnamedCategory;

private List<PropertyCategory> categories;

private List<PropertyFileListener> listeners;

public PropertyFile(String content) {

super(null);

categories = new ArrayList<PropertyCategory>();

listeners = new ArrayList<PropertyFileListener>();

LineNumberReader reader =

new LineNumberReader(new StringReader(content));

try {

unnamedCategory = new PropertyCategory(this, reader);

while (true) {

reader.mark(1);

int ch = reader.read();

if (ch == -1)

break;

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.2

Editor Part 367

reader.reset();

categories.add(

new PropertyCategory(this, reader));

}

}

catch (IOException e) {

FavoritesLog.logError(e);

}

}

public PropertyElement[] getChildren() {

List<PropertyElement> children

= new ArrayList<PropertyElement>();

children.addAll(unnamedCategory.getEntries());

children.addAll(categories);

return children.toArray(new PropertyElement[children.size()]);

}

public void addCategory(PropertyCategory category) {

if (!categories.contains(category)) {

categories.add(category);

categoryAdded(category);

}

}

public void removeCategory(PropertyCategory category) {

if (categories.remove(category))

categoryRemoved(category);

}

public void removeFromParent() {

// Nothing to do.

}

void addPropertyFileListener(

PropertyFileListener listener) {

if (!listeners.contains(listener))

listeners.add(listener);

}

void removePropertyFileListener(

PropertyFileListener listener) {

listeners.remove(listener);

}

void keyChanged(PropertyCategory category,PropertyEntry entry) {

Iterator<PropertyFileListener> iter = listeners.iterator();

while (iter.hasNext())

iter.next().keyChanged(category, entry);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

368 CHAPTER 8 • Editors

void valueChanged(PropertyCategory category, PropertyEntry entry)

{

Iterator<PropertyFileListener> iter = listeners.iterator();

while (iter.hasNext())

iter.next().valueChanged(category, entry);

}

void nameChanged(PropertyCategory category) {

Iterator<PropertyFileListener> iter = listeners.iterator();

while (iter.hasNext())

iter.next().nameChanged(category);

}

void entryAdded(PropertyCategory category,PropertyEntry entry) {

Iterator<PropertyFileListener> iter = listeners.iterator();

while (iter.hasNext())

iter.next().entryAdded(category, entry);

}

void entryRemoved(PropertyCategory category, PropertyEntry entry)

{

Iterator<PropertyFileListener> iter = listeners.iterator();

while (iter.hasNext())

iter.next().entryRemoved(category, entry);

}

void categoryAdded(PropertyCategory category) {

Iterator<PropertyFileListener> iter = listeners.iterator();

while (iter.hasNext())

iter.next().categoryAdded(category);

}

void categoryRemoved(PropertyCategory category) {

Iterator<PropertyFileListener> iter = listeners.iterator();

while (iter.hasNext())

iter.next().categoryRemoved(category);

}

}

The

PropertyFileListener

interface is used by the

ProperyFile

to notify registered listeners, such as

PropertiesEditor

, that changes have occurred in the model.

package com.qualityeclipse.favorites.editors; public interface PropertyFileListener

{

void keyChanged(

PropertyCategory category,

PropertyEntry entry);

void valueChanged(

PropertyCategory category,

PropertyEntry entry);

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.2

Editor Part

void nameChanged(

PropertyCategory category);

void entryAdded(

PropertyCategory category,

PropertyEntry entry);

void entryRemoved(

PropertyCategory category,

PropertyEntry entry);

void categoryAdded(

PropertyCategory category);

void categoryRemoved(

PropertyCategory category);

}

369

8.2.4

Content provider

All these model objects are useless unless they can be properly displayed in the tree. To accomplish this, you need to create a content provider and label provider. The content provider provides the rows appearing in the tree along with parent/child relationships, but not the actual cell content.

package com.qualityeclipse.favorites.editors; import ...

public class PropertiesEditorContentProvider

implements ITreeContentProvider

{

public void inputChanged(

Viewer viewer, Object oldInput, Object newInput

) { }

public Object[] getElements(Object element) {

return getChildren(element);

}

public Object[] getChildren(Object element) {

if (element instanceof PropertyElement)

return ((PropertyElement) element).getChildren();

return null;

}

public Object getParent(Object element) {

if (element instanceof PropertyElement)

return ((PropertyElement) element).getParent();

return null;

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

370 CHAPTER 8 • Editors

public boolean hasChildren(Object element) {

if (element instanceof PropertyElement)

return ((PropertyElement) element).getChildren().length > 0;

return false;

}

public void dispose() {

}

}

8.2.5

Label provider

The label provider converts the row element object as returned by the content provider into images and text that can be displayed in the table cells.

package com.qualityeclipse.favorites.editors; import ...

public class PropertiesEditorLabelProvider extends LabelProvider

implements ITableLabelProvider

{

public Image getColumnImage(Object element, int columnIndex) {

return null;

}

public String getColumnText(Object element, int columnIndex) {

if (element instanceof PropertyCategory) {

PropertyCategory category =

(PropertyCategory) element;

switch (columnIndex) {

case 0 :

return category.getName();

case 1 :

return "";

}

}

if (element instanceof PropertyEntry) {

PropertyEntry entry = (PropertyEntry) element;

switch (columnIndex) {

case 0 :

return entry.getKey();

case 1 :

return entry.getValue();

}

}

if (element == null)

return "<null>";

return element.toString();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.2

Editor Part 371

Finally, you need to add a new initTreeContent()

method, called from the createPages()

method, to associate the new content and label providers with the tree. This method is followed by another new method to synchronize the text editor’s content with the tree’s content. The call to asyncExec() ensures that the updateTreeFromTextEditor

method is executed in the UI thread (see Section 4.2.5.1, Display, on page 148 for more on the UI thread).

The updateTreeFromTextEditor()

method indirectly references code in the org.eclipse.jface.text

plug-in, so it must be added to the

Favorites

plugin’s manifest (see Figure 2–10 on page 79).

private PropertiesEditorContentProvider treeContentProvider; private PropertiesEditorLabelProvider treeLabelProvider; void initTreeContent() {

treeContentProvider = new PropertiesEditorContentProvider();

treeViewer.setContentProvider(treeContentProvider);

treeLabelProvider = new PropertiesEditorLabelProvider();

treeViewer.setLabelProvider(treeLabelProvider);

// Reset the input from the text editor’s content

// after the editor initialization has completed.

treeViewer.setInput(new PropertyFile(""));

treeViewer.getTree().getDisplay().asyncExec(new Runnable() {

public void run() {

updateTreeFromTextEditor();

}

});

treeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);

} void updateTreeFromTextEditor() {

PropertyFile propertyFile = new PropertyFile(

textEditor

.getDocumentProvider()

.getDocument(textEditor.getEditorInput())

.get());

treeViewer.setInput(propertyFile);

}

When all this has been accomplished, the

Properties

editor’s

Properties

page will have some content (see Figure 8–5).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

372

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 8 • Editors

Figure 8–5

Properties editor with new content.

8.3

Editing

When the

Properties

page displays the content in a tree, it is important to edit the content without having to switch to the

Source

page (see Section 14.2.4,

Marker resolution—quick fix, on page 556 for an example of manipulating the content in an existing text editor).

8.3.1

Cell editors

Similar to the createInlineEditor

method (Section 7.3.9, Inline editing, on page 333), create a new initTreeEditors()

method, which is called from createPages()

. This method initializes two

TreeViewerColumn

instances responsible for managing the cell editor in the key and value columns respectively:

TreeViewerColumn column1 =

new TreeViewerColumn(treeViewer, keyColumn);

TreeViewerColumn column2 =

new TreeViewerColumn(treeViewer, valueColumn);

Each TreeViewerColumn has a ColumnLabelProvider associated with it:

column1.setLabelProvider(new ColumnLabelProvider() {

public String getText(Object element) {

return treeLabelProvider.getColumnText(element, 0);

}

});

column2.setLabelProvider(new ColumnLabelProvider() {

public String getText(Object element) {

return treeLabelProvider.getColumnText(element, 1);

}

});

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.3

Editing 373

In the first column, the user can edit either the category name or the key of a key/value pair.

EditingSupport

is responsible for instantiating an appropriate cell editor, obtaining the appropriate text for cell editor, and saving the modified text back into the model.

column1.setEditingSupport(new EditingSupport(treeViewer) {

TextCellEditor editor = null;

protected boolean canEdit(Object element) {

return true;

}

protected CellEditor getCellEditor(Object element) {

if (editor == null) {

Composite tree = (Composite) treeViewer.getControl();

editor = new TextCellEditor(tree);

}

return editor;

}

protected Object getValue(Object element) {

return treeLabelProvider.getColumnText(element, 0);

}

protected void setValue(Object element, Object value) {

String text = ((String) value).trim();

if (element instanceof PropertyCategory)

((PropertyCategory) element).setName(text);

if (element instanceof PropertyEntry)

((PropertyEntry) element).setKey(text);

}

});

We create a similar

EditingSupport

object for the second column, but modify it to allow editing of the value in a key/value pair: column2.setEditingSupport(new EditingSupport(treeViewer) {

TextCellEditor editor = null;

protected boolean canEdit(Object element) {

return element instanceof PropertyEntry;

}

protected CellEditor getCellEditor(Object element) {

if (editor == null) {

Composite tree = (Composite) treeViewer.getControl();

editor = new TextCellEditor(tree);

}

return editor;

}

protected Object getValue(Object element) {

return treeLabelProvider.getColumnText(element, 1);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

374 CHAPTER 8 • Editors

protected void setValue(Object element, Object value) {

String text = ((String) value).trim();

if (element instanceof PropertyEntry)

((PropertyEntry) element).setValue(text);

}

});

The setValue

method changes the editor model (see Figure 8–6), which then calls a new treeModified()

method in the

PropertiesEditor

class to notify any interested members that the editor’s content has been modified.

This happens via a new

PropertyFileListener

listener created in the next section. public void treeModified() {

if (!isDirty())

firePropertyChange(IEditorPart.PROP_DIRTY);

}

Figure 8–6

Properties editor with modified cell value.

8.3.2

Change listeners

When a user edits a value, the model generates a change event to notify registered listeners. The next step is to hook up a change listener in the

PropertiesEditor

class so that you can be notified of events and update the tree appropriately. First, add a new

PropertyFileListener

.

private final PropertyFileListener propertyFileListener =

new PropertyFileListener()

{

public void keyChanged(

PropertyCategory category, PropertyEntry entry

) {

treeViewer.refresh(entry);

treeModified();

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.3

Editing 375

public void valueChanged(

PropertyCategory category, PropertyEntry entry

) {

treeViewer.refresh(entry);

treeModified();

}

public void nameChanged(PropertyCategory category) {

treeViewer.refresh(category);

treeModified();

}

public void entryAdded(

PropertyCategory category, PropertyEntry entry

) {

treeViewer.refresh();

treeModified();

}

public void entryRemoved(

PropertyCategory category, PropertyEntry entry

) {

treeViewer.refresh();

treeModified();

}

public void categoryAdded(PropertyCategory category) {

treeViewer.refresh();

treeModified();

}

public void categoryRemoved(PropertyCategory category) {

treeViewer.refresh();

treeModified();

}

};

Next, modify the updateTreeFromTextEditor()

method, as folows so that the listener is removed from the old editor model before it is discarded and added to the new editor model.

void updateTreeFromTextEditor() {

PropertyFile propertyFile = (PropertyFile) treeViewer.getInput();

propertyFile.removePropertyFileListener(propertyFileListener);

propertyFile = new PropertyFile(

textEditor

.getDocumentProvider()

.getDocument(textEditor.getEditorInput())

.get());

treeViewer.setInput(propertyFile);

propertyFile.addPropertyFileListener(propertyFileListener);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

376 CHAPTER 8 • Editors

8.3.3

Cell validators

Cell editors have validators to prevent invalid input from reaching model objects. Whenever a user modifies a cell editor’s content, the isValid(Object)

method returns an error message if the object represents an invalid value, or null

if the value is valid. Assign a validator to the cell editor in column in the initTreeEditors()

method as follows: column1.setEditingSupport(new EditingSupport(treeViewer) {

...

protected CellEditor getCellEditor(Object element) {

if (editor == null) {

Composite tree = (Composite) treeViewer.getControl();

editor = new TextCellEditor(tree);

editor.setValidator(new ICellEditorValidator() {

public String isValid(Object value) {

if (((String) value).trim().length() == 0)

return "Key must not be empty string";

return null;

}

});

}

return editor;

}

Whenever the cell validator returns an error message, the setValue method is called with a null

value. Add code to the setValue

method to guard against this situation.

column1.setEditingSupport(new EditingSupport(treeViewer) {

...

protected void setValue(Object element, Object value) {

if (value == null)

return;

String text = ((String) value).trim();

if (element instanceof PropertyCategory)

((PropertyCategory) element).setName(text);

if (element instanceof PropertyEntry)

((PropertyEntry) element).setKey(text);

}

...

Whenever a user enters an invalid value, you have to decide how the user will be notified that the value is invalid. In this case, add an

ICellEditor-

Listener

in the initTreeEditors()

method so that the error message will appear in the window’s status line (see Figure 8–7). For a more prominent error message, the editor’s header area could be redesigned to allow an error image and message to be displayed just above the tree rather than in the workbench’s status line.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.3

Editing

column1.setEditingSupport(new EditingSupport(treeViewer) {

...

protected CellEditor getCellEditor(Object element) {

if (editor == null) {

Composite tree = (Composite) treeViewer.getControl();

editor = new TextCellEditor(tree);

...

editor.addListener(new ICellEditorListener() {

public void applyEditorValue() {

setErrorMessage(null);

}

public void cancelEditor() {

setErrorMessage(null);

}

public void editorValueChanged(

boolean oldValidState, boolean newValidState) {

setErrorMessage(editor.getErrorMessage());

}

private void setErrorMessage(String errorMessage) {

getEditorSite().getActionBars()

.getStatusLineManager()

.setErrorMessage(errorMessage);

}

});

...

377

Figure 8–7

Error message in status line indicating invalid input.

8.3.4

Editing versus selecting

Before editing is added in the tree, a user could easily select one or more rows, but now the cell editor is always open. One possible solution is to only open the editor when the

Alt

key is held down, but select one or more rows when it is not. To accomplish this, extract the

ColumnViewerEditorActivationListener

from the Favorites view (see end of Section 7.3.9, Inline

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

378 CHAPTER 8 • Editors

editing, on page 333) into a new

AltClickCellEditListener

class, then add the following to the end of the initTreeEditors()

method.

treeViewer.getColumnViewerEditor().addEditorActivationListener(

new AltClickCellEditListener());

8.4

Editor Lifecycle

Typical editors go through an open-modify-save-close lifecycle. When the editor is opened, the init(IEditorSite, IEditorInput)

method is called to set the editor’s initial content. When the user modifies the editor’s content, the editor must notify others that its content is now “dirty” by using the firePropertyChange(int)

method. When a user saves the editor’s content, the firePropertyChange(int)

method must be used again to notify registered listeners that the editor’s content is no longer dirty. Eclipse automatically registers listeners to perform various tasks based on the value returned by the isDirty()

method, such as updating the editor’s title, adding or removing an asterisk preceding the title, and enabling the

Save

menu. Finally, when the editor is closed, the editor’s content is saved if the isDirty()

method returns true

.

8.4.1

Dirty editors

You need to ensure that the editor knows whether its content has been modified by the user since the last save operation. To do this, introduce this new field to track whether the current page has been modified relative to the other pages: private boolean isPageModified;

Whenever the current page’s content has been modified, you need to set the new isPageModified

field. Whenever the tree is modified, the cell modifier calls the treeModified()

method (see Section 8.3.1, Cell editors, on page 372), where the new isPageModified

field can be set. public void treeModified() {

boolean wasDirty = isDirty();

isPageModified = true;

if (!wasDirty)

firePropertyChange(IEditorPart.PROP_DIRTY);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.4

Editor Lifecycle 379

Whenever the text editor is modified, the

MultiPageEditorPart

’s addPage()

method uses the handlePropertyChange(int)

method (see the createSourcePage()

method in Section 8.2.2, Editor controls, on page 360) to notify others when the editor’s content has changed. You can override this method to set the isPageModified

field as appropriate: protected void handlePropertyChange (int propertyId) {

if (propertyId == IEditorPart.PROP_DIRTY)

isPageModified = isDirty();

super.handlePropertyChange(propertyId);

}

Finally, you need to let other registered listeners know when the editor’s content is dirty. The

MultiPageEditorPart

’s isDirty()

method appropriately returns true

for the nested text editor on the

Source

page, but knows nothing about modifications to the tree. Overriding this method to add this knowledge causes the

Save

menu item to be enabled and the editor’s title to be updated at the appropriate time.

public boolean isDirty() {

return isPageModified || super.isDirty();

}

8.4.2

Switching pages

When switching between the

Properties

and

Source

pages, any edits made in the

Properties

page must automatically carry over to the

Source

page, and vice versa. To accomplish this, override the pageChange(int)

method to update the page content as follows: protected void pageChange(int newPageIndex) {

switch (newPageIndex) {

case 0 :

if (isDirty())

updateTreeFromTextEditor();

break;

case 1 :

if (isPageModified)

updateTextEditorFromTree();

break;

}

isPageModified = false;

super.pageChange(newPageIndex);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

380 CHAPTER 8 • Editors

The updateTreeFromTextEditor()

method has already been defined

(see Section 8.2.3, Editor model, on page 363), but the updateText-

EditorFromTree()

method has not, so add it now.

void updateTextEditorFromTree() {

textEditor

.getDocumentProvider()

.getDocument(textEditor.getEditorInput())

.set(((PropertyFile) treeViewer.getInput()).asText());

}

The updateTextEditorFromTree()

method calls a new asText()

method in the

PropertyFile

. The new asText()

method reverses the parsing process in the

PropertyFile

’s constructor (see Section 8.2.3, Editor model, on page 363) by reassembling the model into a textual representation.

public String asText() {

StringWriter stringWriter = new StringWriter(2000);

PrintWriter writer = new PrintWriter(stringWriter);

unnamedCategory.appendText(writer);

Iterator<PropertyCategory> iter = categories.iterator();

while (iter.hasNext()) {

writer.println();

iter.next().appendText(writer);

}

return stringWriter.toString();

}

The asText()

method calls a new appendText(PrintWriter)

method in

PropertyCategory

: public void appendText(PrintWriter writer) {

if (name.length() > 0) {

writer.print("# ");

writer.println(name);

}

Iterator<PropertyEntry> iter = entries.iterator();

while (iter.hasNext())

iter.next().appendText(writer);

} which then calls a new appendText(PrintWriter)

method in

Property-

Entry

: public void appendText(PrintWriter writer) {

writer.print(key);

writer.print(" = ");

writer.println(value);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.5

Editor Commands 381

8.4.3

Saving content

Because the current implementation uses the nested text editor to save content into the file being edited, changes on the

Properties

page will not be noticed unless the user switches to the

Source

page. The following methods must be modified to update the nested text editor before saving. Since save operations are typically long-running operations, the progress monitor is used to communicate progress to the user (see Section 9.4, Progress Monitor, on page 415).

public void doSave(IProgressMonitor monitor) {

if (getActivePage() == 0 && isPageModified)

updateTextEditorFromTree();

isPageModified = false;

textEditor.doSave(monitor);

} public void doSaveAs() {

if (getActivePage() == 0 && isPageModified)

updateTextEditorFromTree();

isPageModified = false;

textEditor.doSaveAs();

setInput(textEditor.getEditorInput());

updateTitle();

}

8.5

Editor Commands

Editor commands can appear as menu items in the editor’s context menu, as toolbar buttons in the workbench’s toolbar, and as menu items in the workbench’s menu (see Figure 6–17 on page 277). This section covers adding actions to an editor programmatically, whereas Section 6.9, Editor Actions, on page 277 discusses adding actions by using declarations in the plug-in manifest (see Section 14.2.4, Marker resolution—quick fix, on page 556 for an example of manipulating the content in an existing text editor).

8.5.1

Context menu

Typically, editors have context menus populated by commands targeted at the editor or at selected objects within the editor. There are several steps to creating an editor’s context menu and several more steps to register the editor so that others can contribute commands (see Section 6.2.5, Defining a selectionbased context menu item, on page 223, and Section 6.2.7, Defining an editorspecific menu or toolbar item, on page 229 for information concerning how commands are contributed to an editor’s context menus via the plug-in manifest).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

382 CHAPTER 8 • Editors

8.5.1.1

Creating the context menu

The context menu must be created at the same time as the editor. However, because contributors can add and remove menu items based on the selection, its contents cannot be determined until just after the user clicks the right mouse button and just before the menu is displayed. To accomplish this, set the menu’s

RemoveAllWhenShown

property to true

so that the menu will be built from scratch every time, and add a menu listener to dynamically build the menu. In addition, the menu must be registered with the control so that it will be displayed, and with the editor site so that other plug-ins can contribute commands to it (see Section 6.2, Menu and Toolbar Contributions, on page 220).

For the

Properties

editor, modify createPages()

to call this new createContextMenu()

method. The last statement in this method registers the menu so that other plug-ins can contribute to it.

private void createContextMenu() {

MenuManager menuMgr = new MenuManager("#PopupMenu");

menuMgr.setRemoveAllWhenShown(true);

menuMgr.addMenuListener(new IMenuListener() {

public void menuAboutToShow(IMenuManager m) {

PropertiesEditor.this.fillContextMenu(m);

}

});

Tree tree = treeViewer.getTree();

Menu menu = menuMgr.createContextMenu(tree);

tree.setMenu(menu);

getSite().registerContextMenu(menuMgr,treeViewer);

}

8.5.1.2

Dynamically building the context menu

Every time a user clicks the right mouse button, the context menu’s content must be rebuilt from scratch because contributors can add commands based on the editor’s selection. In addition, the context menu must contain a separator with “edit” for our own commands and a second separator with the

IWorkbenchActionConstants.MB_ADDITIONS

constant, indicating where those contributed actions will appear in the context menu. The createContextMenu()

method (see the previous section) calls this new fillContextMenu(IMenuManager)

method: private void fillContextMenu(IMenuManager menuMgr) {

menuMgr.add(new Separator("edit"));

menuMgr.add(

new Separator(IWorkbenchActionConstants.MB_ADDITIONS));

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.5

Editor Commands 383

8.5.1.3

Creating commands

The

Properties

editor needs a command that will delete the selected tree elements from the editor. We could create a contribution item (see Section

7.3.2.1, Creating contributions, on page 314), but it is much easier to declare the command

and menuContribution

in the plug-in manifest. Start by declaring a command

(see Section 6.1.1, Defining a command, on page 216) with the following attributes:

• id—“com.qualityeclipse.properties.editor.delete”

• name—“Delete”

• description—“Delete the selected entries in the Properties Editor”

• categoryId—“com.qualityeclipse.favorites.commands.category”

• defaultHandler—“com.qualityeclipse.favorites.handlers.

DeletePropertiesHandler”

To create the handler referenced above, click on the

defaultHandler:

label to the left of this attribute’s text field to open the

New Java Class

wizard (for more, see Section 6.3, Handlers, on page 236).

public class DeletePropertiesHandler extends AbstractHandler

{

public Object execute(ExecutionEvent event)

throws ExecutionException {

ISelection selection = HandlerUtil.getCurrentSelection(event);

if (!(selection instanceof IStructuredSelection))

return null;

Iterator iter = ((IStructuredSelection) selection).iterator();

Shell shell = HandlerUtil.getActiveShellChecked(event);

shell.setRedraw(false);

try {

while (iter.hasNext())

((PropertyElement) iter.next()).removeFromParent();

}

finally {

shell.setRedraw(true);

}

return null;

}

}

Tip

: As shown in the preceding code, use the shell’s setRedraw(boolean) method to reduce flashing when making more than one modification to a control or its model.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

384 CHAPTER 8 • Editors

For the DeletePropertiesHandler described above to obtain its selection, the

Properties

editor’s tree viewer must be a selection provider (see Section

7.4, Linking the View, on page 336). To accomplish this, add the following statement to the end of the createPropertiesPage

method: getSite().setSelectionProvider(treeViewer); a

To display the new command in the

Properties

editor’s context menu, add menuContribution

(see Section 6.2.7, Defining an editor-specific menu or toolbar item, on page 229) with the following locationURI

: popup:com.qualityeclipse.properties.editor?after=edit

Right-click on the new menuContribution

and select

New

>

command

.

Set the properties of the new command as shown below, then add a visible-

When

expression to prevent the command from being visible when nothing is selected (see Section 6.2.10, visibleWhen expression, on page 231).

• commandId—“com.qualityeclipse.properties.editor.delete”

• icon—“icons/delete_edit.gif”

When this functionality is in place, the context menu, containing the

Delete

menu item plus items contributed by others, will appear (see Figure 8–8).

Figure 8–8

The Properties editor’s context menu.

8.5.2

Editor contributor

An editor contributor manages the installation and removal of global menus, menu items, and toolbar buttons for one or more editors. The need for an editor contributor is greatly reduced or eliminated by using commands instead.

Using commands is the recommended approach, but we include this section for completeness. Providing the same functionality using commands instead of an editor contributor is covered in the next section (see Section 8.5.3, Editor commands rather than editor contributor, on page 389).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.5

Editor Commands 385

An instance of org.eclipse.ui.IEditorActionBarContributor

is associated with one or more editors, adding or removing top level menus and toolbar elements. The plug-in manifest specifies which editor contributor, typically a subclass of org.eclipse.ui.part.EditorActionBarContributor

or org.eclipse.ui.part.MultiPageEditorActionBarContributor

, is associated with which editor type (see Section 8.1, Editor Declaration, on page 354). The platform then sends the following events to the contributor, indicating when an editor has become active or inactive, so that the contributor can install or remove menus and buttons as appropriate.

dispose()

—This method is automatically called when the contributor is no longer needed. It cleans up any platform resources, such as images, clipboard, and so on, which were created by this class. This follows the

if you create it, you destroy it

theme that runs throughout Eclipse.

init(IActionBars, IWorkbenchPage)

—This method is called when the contributor is first created.

setActiveEditor(IEditorPart)

—This method is called when an associated editor becomes active or inactive. The contributor should insert and remove menus and toolbar buttons as appropriate.

The

EditorActionBarContributor

class implements the

IEditor-

ActionBarContributor

interface, caches the action bar and workbench page, and provides two new accessor methods.

getActionBars()

—Returns the contributor’s action bars provided to the contributor when it was initialized.

getPage()

—Returns the contributor’s workbench page provided to the contributor when it was initialized.

The

MultiPageEditorActionBarContributor

class extends

Editor-

ActionBarContributor

, providing a new method to override instead of the setActiveEditor(IEditorPart)

method.

setActivePage(IEditorPart)

—Sets the active page of the multipage editor to the given editor. If there is no active page, or if the active page does not have a corresponding editor, the argument is null

.

8.5.2.1

Global actions

By borrowing from org.eclipse.ui.editors.text.TextEditorAction-

Contributor

and org.eclipse.ui.texteditor.BasicTextEditor-

ActionContributor

, you will create your own contributor for the

Properties

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

386 CHAPTER 8 • Editors

editor. This contributor hooks up global actions (e.g.,

cut, copy, paste

, etc. in the

Edit

menu) appropriate not only to the active editor but also to the active page within the editor.

package com.qualityeclipse.favorites.editors; import ...

public class PropertiesEditorContributor

extends EditorActionBarContributor

{

private static final String[] WORKBENCH_ACTION_IDS = {

ActionFactory.DELETE.getId(),

ActionFactory.UNDO.getId(),

ActionFactory.REDO.getId(),

ActionFactory.CUT.getId(),

ActionFactory.COPY.getId(),

ActionFactory.PASTE.getId(),

ActionFactory.SELECT_ALL.getId(),

ActionFactory.FIND.getId(),

IDEActionFactory.BOOKMARK.getId(),

};

private static final String[] TEXTEDITOR_ACTION_IDS = {

ActionFactory.DELETE.getId(),

ActionFactory.UNDO.getId(),

ActionFactory.REDO.getId(),

ActionFactory.CUT.getId(),

ActionFactory.COPY.getId(),

ActionFactory.PASTE.getId(),

ActionFactory.SELECT_ALL.getId(),

ActionFactory.FIND.getId(),

IDEActionFactory.BOOKMARK.getId(),

};

public void setActiveEditor(IEditorPart part) {

PropertiesEditor editor = (PropertiesEditor) part;

setActivePage(editor, editor.getActivePage());

}

public void setActivePage(

PropertiesEditor editor,

int pageIndex

) {

IActionBars actionBars = getActionBars();

if (actionBars != null) {

switch (pageIndex) {

case 0 :

hookGlobalTreeActions(editor, actionBars);

break;

case 1 :

hookGlobalTextActions(editor, actionBars);

break;

}

actionBars.updateActionBars();

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.5

Editor Commands 387

private void hookGlobalTreeActions(

PropertiesEditor editor,

IActionBars actionBars

) {

for (int i = 0; i < WORKBENCH_ACTION_IDS.length; i++)

actionBars.setGlobalActionHandler(

WORKBENCH_ACTION_IDS[i],

editor.getTreeAction(WORKBENCH_ACTION_IDS[i]));

}

private void hookGlobalTextActions(

PropertiesEditor editor,

IActionBars actionBars

) {

ITextEditor textEditor = editor.getSourceEditor();

for (int i = 0; i < WORKBENCH_ACTION_IDS.length; i++)

actionBars.setGlobalActionHandler(

WORKBENCH_ACTION_IDS[i],

textEditor.getAction(TEXTEDITOR_ACTION_IDS[i]));

}

}

Now modify the

Properties

editor to add accessor methods for the contributor.

public ITextEditor getSourceEditor() {

return textEditor;

} public IAction getTreeAction(String workbenchActionId) {

if (ActionFactory.DELETE.getId().equals(workbenchActionId))

return removeAction;

return null;

}

Append the following lines to the pageChange()

method to notify the contributor when the page has changed so that the contributor can update the menu items and toolbar buttons appropriately.

IEditorActionBarContributor contributor =

getEditorSite().getActionBarContributor(); if (contributor instanceof PropertiesEditorContributor)

((PropertiesEditorContributor) contributor)

.setActivePage(this, newPageIndex);

8.5.2.2

Top-level menu

Next, add the

Delete

action to a top-level menu for the purpose of showing how it is accomplished. In this case, instead of referencing the action directly as done with the context menu (see Section 8.5.1, Context menu, on page 381), you will use an instance of org.eclipse.ui.actions.

RetargetAction

, or more specifically, org.eclipse.ui.actions.

LabelRetargetAction

, which references the

remove

action indirectly via its

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

388 CHAPTER 8 • Editors

identifier. You’ll be using the

ActionFactory.DELETE.getId()

identifier, but could use any identifier so long as setGlobalActionHandler(String,

IAction)

is used to associate the identifier with the action. To accomplish all this, add the following to the

PropertiesEditorContributor

.

private LabelRetargetAction retargetRemoveAction =

new LabelRetargetAction(ActionFactory.DELETE.getId(), "Remove"); public void init(IActionBars bars, IWorkbenchPage page) {

super.init(bars, page);

page.addPartListener(retargetRemoveAction);

} public void contributeToMenu(IMenuManager menuManager) {

IMenuManager menu = new MenuManager("Property Editor");

menuManager.prependToGroup(

IWorkbenchActionConstants.MB_ADDITIONS,

menu);

menu.add(retargetRemoveAction);

} public void dispose() {

getPage().removePartListener(retargetRemoveAction);

super.dispose();

}

8.5.2.3

Toolbar buttons

You can use the same retargeted action (see previous section) to add a button to the workbench’s toolbar by including the following code in

Properties-

EditorContributor

.

public void contributeToToolBar(IToolBarManager manager) {

manager.add(new Separator());

manager.add(retargetRemoveAction);

}

8.5.2.4

Keyboard actions

By using the

remove

action again (see Section 8.5.1.3, Creating commands, on page 383), you can hook in the

Delete

key by modifying the initTreeEditors()

method introduced earlier (see Section 8.3.4, Editing versus selecting, on page 377) so that when a user presses it, the selected property key/value pairs in the tree will be removed.

private void initTreeEditors() {

... existing code ...

treeViewer.getTree().addKeyListener(new KeyListener() {

public void keyPressed(KeyEvent e) {

if (e.keyCode == SWT.ALT)

isAltPressed = true;

if (e.character == SWT.DEL)

removeAction.run();

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.5

Editor Commands

public void keyReleased(KeyEvent e) {

if (e.keyCode == SWT.ALT)

isAltPressed = false;

}

});

}

389

8.5.3

Editor commands rather than editor contributor

The commands API is the preferred over actions, and reduces or eliminates the need for an editor contributor. You can add a top level menu item (see Section

6.2.1, Defining a top level menu, on page 220) or top level tool bar (see Section 6.2.3, Defining a top level toolbar item, on page 221) and limit its visibility (see Section 6.2.4, Limiting top level menu and toolbar item visibility, on page 222) so that it appears only when your editor is active. In this section, we reimplement the same functionality as the prior section (see Section 8.5.2,

Editor contributor, on page 384), except using commands.

8.5.3.1

Global commands

At the moment, the global commands (e.g.,

cut, copy, paste

, etc. in the

Edit

menu) work appropriately in the “Source” page of our

Properties

editor, but not in the “Properties” page. To provide support for the

Edit

>

Delete

command, hook

DeletePropertiesHandler

(see Section 8.5.1.3, Creating commands, on page 383) to the global menu item using the following plug-in declaration (see Section 6.3, Handlers, on page 236 for more).

<handler

class="com.qualityeclipse.favorites.handlers.DeletePropertiesHandler"

commandId="org.eclipse.ui.edit.delete">

<activeWhen>

<with

variable="activeEditorId">

<equals

value="com.qualityeclipse.properties.editor">

</equals>

</with>

</activeWhen>

</handler>

8.5.3.2

Top-level menu

Next, add the

Delete

action to a top-level menu for the purpose of showing how it is accomplished. Start by declaring a new top-level menu labeled

“Properties Editor” (see Section 6.2.1, Defining a top level menu, on page 220). To that, add a Delete menu item (see Section 6.2.2, Adding to an existing top level menu, on page 221) and a visibleWhen

expression (see Section 6.2.10, visibleWhen expression, on page 231).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

390 CHAPTER 8 • Editors

<menuContribution

locationURI="menu:org.eclipse.ui.main.menu?after=additions">

<menu

id="com.qualityeclipse.favorites.menus.PropertiesEditor"

label="Properties Editor">

<command

commandId="com.qualityeclipse.properties.editor.delete"

icon="icons/delete_edit.gif">

</command>

<visibleWhen

checkEnabled="false">

<with

variable="activeEditorId">

<equals

value="com.qualityeclipse.properties.editor">

</equals>

</with>

</visibleWhen>

</menu>

</menuContribution>

This works well, except that the

Delete

menu item is enabled even when there is nothing selected in the Properties view. Adding an enabledWhen

expression to the handler would work, but the handler is currently the command’s default handler and an enabledWhen

expression cannot be attached to the command.

Move the handler to a separate declaration and attach an enableWhen

expression (see Section 6.3, Handlers, on page 236).

<handler

class="com.qualityeclipse.favorites.handlers.DeletePropertiesHandler"

commandId="com.qualityeclipse.properties.editor.delete">

<enabledWhen>

<with

variable="selection">

<count

value="+">

</count>

</with>

</enabledWhen>

</handler>

Once in place, this code causes a new top-level menu to appear in the workbench’s menu bar (see Figure 8–9).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

8.5

Editor Commands

5HWXUQWR7DEOHRI&RQWHQWV

391

Figure 8–9

Property Editor menu.

8.5.3.3

Toolbar buttons

You can add top-level toolbar buttons using an approach similar to the approach outlined in the previous section (see Section 6.2.3, Defining a top level toolbar item, on page 221). A visibleWhen

expression ensures that the toolbar contribution is only visible when the editor is active.

<menuContribution

locationURI="toolbar:org.eclipse.ui.main.toolbar?after=additions">

<toolbar

id="com.qualityeclipse.properties.editor.toolbar">

<command

commandId="com.qualityeclipse.properties.editor.delete"

icon="icons/delete_edit.gif">

</command>

<visibleWhen

checkEnabled="false">

<with

variable="activeEditorId">

<equals

value="com.qualityeclipse.properties.editor">

</equals>

</with>

</visibleWhen>

</toolbar>

</menuContribution>

8.5.3.4

Keyboard commands

An editor key binding context facilitates keyboard accelerators (see Section

6.4, Key Bindings, on page 238) that are active only when that particular editor is active. To showcase this technique, our goal is to associate

F9

with the

Delete

command so that pressing the

F9

key when the

Properties

editor has focus will delete the currently selected property file entries. Start by creating a new

Properties

editor key binding context.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

392 CHAPTER 8 • Editors

<extension point="org.eclipse.ui.contexts">

<context

id="com.qualityeclipse.properties.editor.context"

name="Properties Editor Context"

parentId="org.eclipse.ui.textEditorScope"/>

</extension>

This context should only be active when the

Properties

editor has focus. To accomplish this, add a new method called from the createPages

method that programmatically activates and deactivates the new key binding context as the

Properties

editor’s tree gains and looses focus, respectively.

private void initKeyBindingContext() {

final IContextService service = (IContextService)

getSite().getService(IContextService.class);

treeViewer.getControl().addFocusListener(new FocusListener() {

IContextActivation currentContext = null;

public void focusGained(FocusEvent e) {

if (currentContext == null)

currentContext = service.activateContext(

"

com.qualityeclipse.properties.editor.context

");

}

public void focusLost(FocusEvent e) {

if (currentContext != null)

service.deactivateContext(currentContext);

}

});

}

Finally, arbitrarily associate the F9 key with the Delete command.

<extension

point="org.eclipse.ui.bindings">

<key

commandId="com.qualityeclipse.properties.editor.delete"

contextId="com.qualityeclipse.properties.editor.context"

schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"

sequence="F9">

</key>

</extension>

8.5.4

Undo/Redo

Adding the capability for a user to undo and redo commands involves separating user edits into commands visible in the user interface and the underlying operations that can be executed, undone, and redone. Typically each command will instantiate a new operation every time the user triggers that

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.5

Editor Commands 393

command. The command gathers the current application state, such as the currently selected elements, and the operation caches that state so that it can be executed, undone, and redone independent of the original command. An instance of

IOperationHistory

manages the operations in the global undo/redo stack (see Figure 8–10). Each operation uses one or more associated undo/redo contexts to keep operations for one part separate from operations for another.

Figure 8–10

The Eclipse undo/redo infrastructure.

In this case, you need to split the

DeletePropertiesHandler

(see Section 8.5.1.3, Creating commands, on page 383), moving some functionality into a new

DeletePropertiesOperation

class. The

AbstractOperation superclass implements much of the required

IUndoableOperation

interface.

public class DeletePropertiesOperation extends AbstractOperation

{

private final PropertyElement[] elements;

public DeletePropertiesOperation(

PropertyElement[] elements

) {

super(getLabelFor(elements));

this.elements = elements;

}

The constructor calls the getLabelFor()

method to generate a humanreadable label for the operation based on the currently selected elements. This label appears wherever the undo and redo commands appear such as on the

Edit

menu.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

394 CHAPTER 8 • Editors

private static String getLabelFor(PropertyElement[] elements) {

if (elements.length == 1) {

PropertyElement first = elements[0];

if (first instanceof PropertyEntry) {

PropertyEntry propEntry = (PropertyEntry) first;

return "Remove property " + propEntry.getKey();

}

if (first instanceof PropertyCategory) {

PropertyCategory propCat = (PropertyCategory) first;

return "Remove category " + propCat.getName();

}

}

return "Remove properties";

}

The execute()

method prompts the user to confirm the operation and removes the specified properties. If the info

argument is not null

, then it can be queried for a UI context in which to prompt the user for information during execution. If the monitor argument is not null

, then it can be used to provide progress feedback to the user during execution. This method is only called the first time the operation is executed.

public IStatus execute(IProgressMonitor monitor, IAdaptable info)

throws ExecutionException

{

// If a UI context has been provided,

// then prompt the user to confirm the operation.

if (info != null) {

Shell shell = (Shell) info.getAdapter(Shell.class);

if (shell != null) {

if (!MessageDialog.openQuestion(

shell,

"Remove properties",

"Do you want to remove the currently selected properties?"

))

return Status.CANCEL_STATUS;

}

}

// Perform the operation.

return redo(monitor, info);

}

The execute()

method calls the redo()

method to perform the actual property removal. This method records information about the elements being removed in two additional fields so that this operation can be undone. The arguments passed to the redo()

method are identical to those supplied to the execute()

method described before.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.5

Editor Commands 395

private PropertyElement[] parents; private int[] indexes; public IStatus redo(IProgressMonitor monitor, IAdaptable info)

throws ExecutionException

{

// Perform the operation, providing feedback to the user

// through the progress monitor if one is provided.

parents = new PropertyElement[elements.length];

indexes = new int[elements.length];

if (monitor != null)

monitor.beginTask("Remove properties", elements.length);

Shell shell = (Shell) info.getAdapter(Shell.class);

shell.setRedraw(false);

try {

for (int i = elements.length; --i >= 0;) {

parents[i] = elements[i].getParent();

PropertyElement[] children = parents[i].getChildren();

for (int index = 0; index < children.length; index++) {

if (children[index] == elements[i]) {

indexes[i] = index;

break;

}

}

elements[i].removeFromParent();

if (monitor != null)

monitor.worked(1);

}

}

finally {

shell.setRedraw(true);

}

if (monitor != null)

monitor.done();

return Status.OK_STATUS;

}

The undo()

method reverses the current operation by reinserting the removed elements into the model.

public IStatus undo(IProgressMonitor monitor, IAdaptable info)

throws ExecutionException

{

Shell shell = (Shell) info.getAdapter(Shell.class);

shell.setRedraw(false);

try {

for (int i = 0; i < elements.length; i++) {

if (parents[i] instanceof PropertyCategory)

((PropertyCategory) parents[i]).addEntry(indexes[i],

(PropertyEntry) elements[i]);

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

396 CHAPTER 8 • Editors

else

((PropertyFile) parents[i]).addCategory(indexes[i],

(PropertyCategory) elements[i]);

}

}

finally {

shell.setRedraw(true);

}

return Status.OK_STATUS;

}

The preceding undo()

method inserts elements back into the model at exactly the same position from where they were removed. This necessitates some refactoring of the

PropertyCategory addEntry()

method (see Section

8.2.3, Editor model, on page 363 for more on the editor model).

public void addEntry(PropertyEntry entry) {

addEntry(entries.size(), entry);

} public void addEntry(int index, PropertyEntry entry) {

if (!entries.contains(entry)) {

entries.add(index, entry);

((PropertyFile) getParent()).entryAdded(

this, entry);

}

}

Here is a similar refactoring of the

PropertyFile addCategory()

method.

public void addCategory(PropertyCategory category) {

addCategory(categories.size(), category);

} public void addCategory(int index, PropertyCategory category) {

if (!categories.contains(category)) {

categories.add(index, category);

categoryAdded(category);

}

}

Rather than removing the selected properties, the

DeleteProperties-

Handler

must now build an array of properties to be removed and then pass that to a new instance of

DeletePropertiesOperation

. The operation is passed to the editor’s undo/redo manager for execution along with a UI context for prompting the user and a progress monitor for user feedback. If there is an exception during execution, you could use a

ExceptionsDetailsDialog

(see

Section 11.1.9, Details dialog, on page 454) rather than the following

MessageDialog

.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.5

Editor Commands 397

public Object execute(ExecutionEvent event)

throws ExecutionException {

ISelection selection = HandlerUtil.getCurrentSelection(event);

if (!(selection instanceof IStructuredSelection))

return null;

final IEditorPart editor = HandlerUtil.getActiveEditor(event);

if (!(editor instanceof PropertiesEditor))

return null;

return execute(

(PropertiesEditor) editor, (IStructuredSelection) selection);

} private Object execute(

final PropertiesEditor editor, IStructuredSelection selection

) {

// Build an array of properties to be removed.

Iterator<?> iter = selection.iterator();

int size = selection.size();

PropertyElement[] elements = new PropertyElement[size];

for (int i = 0; i < size; i++)

elements[i] = (PropertyElement) ((Object) iter.next());

// Build the operation to be performed.

DeletePropertiesOperation op

= new DeletePropertiesOperation(elements);

op.addContext(editor.getUndoContext());

// The progress monitor so the operation can inform the user.

IProgressMonitor monitor = editor.getEditorSite().getActionBars()

.getStatusLineManager().getProgressMonitor();

// An adapter for providing UI context to the operation.

IAdaptable info = new IAdaptable() {

public Object getAdapter(Class adapter) {

if (Shell.class.equals(adapter))

return editor.getSite().getShell();

return null;

}

};

// Execute the operation.

try {

editor.getOperationHistory().execute(op, monitor, info);

}

catch (ExecutionException e) {

MessageDialog.openError(editor.getSite().getShell(),

"Remove Properties Error",

"Exception while removing properties: " + e.getMessage());

}

return null;

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

398 CHAPTER 8 • Editors

The preceding execute

method calls some new methods in

PropertiesEditor

.

public IOperationHistory getOperationHistory() {

// The workbench provides its own undo/redo manager

//return PlatformUI.getWorkbench()

// .getOperationSupport().getOperationHistory();

// which, in this case, is the same as the default undo manager

return OperationHistoryFactory.getOperationHistory();

} public IUndoContext getUndoContext() {

// For workbench-wide operations, we should return

//return PlatformUI.getWorkbench()

// .getOperationSupport().getUndoContext();

// but our operations are all local, so return our own content

return undoContext;

}

This undoContext

must be initialized along with undo and redo commands in a new initUndoRedo()

method that is called from the createPages() method.

private UndoActionHandler undoAction; private RedoActionHandler redoAction; private IUndoContext undoContext; private void initUndoRedo() {

undoContext = new ObjectUndoContext(this);

undoAction = new UndoActionHandler(getSite(), undoContext);

redoAction = new RedoActionHandler(getSite(), undoContext);

}

These new undo and redo actions should appear in the context menu, so modify the fillContextMenu()

method.

private void fillContextMenu(IMenuManager menuMgr) {

menuMgr.add(undoAction);

menuMgr.add(redoAction);

menuMgr.add(new Separator());

... existing code ...

}

Then, modify the pageChange()

method to call 2 new methods so that the new undo and redo actions will be hooked to the global undo and redo actions that appear in the

Edit

menu.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.5

Editor Commands 399

protected void pageChange(int newPageIndex) {

switch (newPageIndex) {

case 0 :

if (isDirty())

updateTreeFromTextEditor();

setTreeUndoRedo();

break;

case 1 :

if (isPageModified)

updateTextEditorFromTree();

setTextEditorUndoRedo();

break;

}

isPageModified = false;

super.pageChange(newPageIndex);

} private void setTreeUndoRedo() {

final IActionBars actionBars = getEditorSite().getActionBars();

actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction);

actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction);

actionBars.updateActionBars();

} private void setTextEditorUndoRedo() {

final IActionBars actionBars = getEditorSite().getActionBars();

IAction undoAction2 = textEditor.getAction(ActionFactory.UNDO.getId());

actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(),undoAction2);

IAction redoAction2 = textEditor.getAction(ActionFactory.REDO.getId());

actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(),redoAction2);

actionBars.updateActionBars();

}

Finally, the undo/redo stack for the

Source

page is separate from the undo/redo stack for the

Properties

page, so add the following line to the end of the setTextEditorUndoRedo()

method to clear the undo/redo stack when the page changes.

getOperationHistory().dispose(undoContext, true, true, false);

A better solution similar to what has already been discussed, but not implemented here, would be to merge the two undo/redo stacks into a single unified undo/redo stack shared between both the

Properties

and

Source

pages.

If operations share a common undo context but also have some contexts that are not shared, then there exists the possibility that operations from one context will be undone in a linear fashion; however, some operations from another context may be skipped. To allieviate this problem, you can register an instance of

IOperationApprover

to ensure that an operation will not be undone without all prior operations being undone first.

This interface also provides a way to confirm undo and redo operations that affect contexts outside the active editor and not immediately apparent to the user. The Eclipse platform contains the following subclasses of

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

400 CHAPTER 8 • Editors

IOperationApprover

useful when managing undo/redo operations with overlapping contexts.

LinearUndoEnforcer

—An operation approver that enforces a strict linear undo. It does not allow the undo or redo of any operation that is not the latest available operation in all of its undo contexts.

LinearUndoViolationUserApprover

—An operation approver that prompts the user to see whether linear undo violations are permitted. A linear undo violation is detected when an operation being undone or redone shares an undo context with another operation appearing more recently in the history.

NonLocalUndoUserApprover

—An operation approver that prompts the user to see whether a nonlocal undo should proceed inside an editor. A non-local undo is detected when an operation being undone or redone affects elements other than those described by the editor itself.

The Eclipse SDK contains a basic undo/redo example as part of the eclipse-examples-3.4-win32.zip

download. It provides additional undo/redo code not covered here such as an

UndoHistoryView

and an implementation of

IOperationApprover

for approving the undo or redo of a particular operation within an operation history.

8.5.5

Clipboard actions

Clipboard-based actions for an editor are identical to their respective viewbased operations (see Section 7.3.7, Clipboard commands, on page 322).

8.6

Linking the Editor

The selection in the active editor can be linked to the views that surround it in a technique similar to linking view selections (see Section 7.4, Linking the

View, on page 336). In addition, the editor can provide content for the

Outline

view by implementing the getAdapter(Class)

method something like this

(see Section 7.4.2, Adaptable objects, on page 337 for more about adapters): private PropertiesOutlinePage outlinePage; public Object getAdapter(Class adapter) {

if (adapter.equals(IContentOutlinePage.class)) {

if (outlinePage == null)

outlinePage = new PropertiesOutlinePage();

return outlinePage;

}

return super.getAdapter(adapter);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.7

RFRS Considerations 401

The

PropertiesOutlinePage

class implements

IContentOutlinePage

, typically by extending

ContentOutlinePage

and implementing a handful of methods. These methods are similar to the methods discussed earlier in this chapter and in the previous chapter; thus, they are not covered in detail here.

8.7

RFRS Considerations

The “User Interface” section of the

RFRS Requirements

includes eleven items—five requirements and six best practices—dealing with editors. All of them are derived from the Eclipse UI Guidelines.

8.7.1

Using an editor to edit or browse

User Interface Guideline #6.1

is a

requirement

that states:

(RFRS 3.5.9)

Use an editor to edit or browse a file, document, or other input object.

This requirement tests that editors are used to edit files (or similar inputs) and views are used to aid navigation (e.g., Navigator) or handle simple modification tasks (e.g., Properties view). Editors must open on doubleor single-click (depending on the workbench’s single-click behavior preference) from the resource. While views may open simultaneously with the editor, it is improper to open only a view when the input follows an opensave-close lifecycle. Views must not retarget editor actions, contribute to the common toolbar, or otherwise substitute for proper editor behavior and appearance.

8.7.2

For this test, demonstrate the editors provided by your plug-in. Show the file, document, or other input type that they are used to edit or browse. Based on the examples presented in this chapter, show how the

Property File

editor is used to edit property files.

Editor lifecycle

User Interface Guideline #6.2

is a

requirement

that states:

(RFRS 3.5.10)

Modifications made in an editor must follow an open-save-close lifecycle model. When an editor first opens, the editor’s contents should be unmodified (clean). If the contents are modified, the editor should communicate this change to the platform. In response, an asterisk should appear in the editor’s tab. The modifications should be buffered within the edit model until such time as the user explicitly saves them. At that point, the modifications should be committed to the model storage.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

402 CHAPTER 8 • Editors

To pass this test, open your editor on a file and show that the editor is initially unmodified. Then make a change using the editor and show that an asterisk appears in the editor’s tab. Finally, save the editor and show that the changes have been committed to the file and that the editor has gone back to its unmodified state. See Section 8.4, Editor Lifecycle, on page 378 for more information about editor lifecycles.

8.7.3

Accessing global actions

User Interface Guideline #6.9

is a

requirement

that states:

(RFRS 3.5.11)

If an editor has support for

cut

,

copy

,

paste

, or any of the global actions, the same actions must be executable from the same actions in the window menu and toolbar. The window menu contains a number of global actions, such as

cut

,

copy

, and

paste

in the

Edit

menu. These actions target the active part, as indicated by a shaded title area. If these actions are supported within an editor, the editor should hook into these window actions so that selection in the window menu or toolbar produces the same result as selection of the same action in the editor. The editor should not ignore these actions and contribute duplicate actions to the window menu or toolbar. The following are the supported global actions: a. Undo b. Redo c. Cut d. Copy e. Paste f. Print g. Delete h. Find i. Select all j. Bookmark

Show that your editor supports any relevant global actions such as

cut

,

copy

, and

paste

. Trigger those actions from within your editor and then show that they may also be triggered from the workbench’s menu bar with the same effect. For the

Properties

editor, you would show that global actions, such as

delete

, can be accessed within both the

Properties

and

Source

pages of the editor. See Section 8.5.2.1, Global actions, on page 385 for more about hooking up global actions.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.7

RFRS Considerations 403

8.7.4

Closing when the object is deleted

User Interface Guideline #6.16

is a

requirement

that states:

(RFRS 3.5.12)

If the input to an editor is deleted and the editor contains no changes, the editor should be closed. When a resource is deleted from one of the navigators (e.g., Navigator view, J2EE view, Data view, or DBA Explorer view in SDP), the handling of any editor that is currently open on that resource depends on whether the editor has any unsaved changes. If the editor does not contain any changes since the resource was last saved then the editor should be immediately closed.

Show that your editor is closed automatically any time an input object to your editor (e.g., a specific resource) is deleted. For the

Properties

editor, you would create a new properties file, open it with the

Properties

editor, and then delete the file in the

Navigator

view. If you implement your editor using the editor framework described in this chapter, the framework should automatically enforce this guideline.

8.7.5

Synchronize external changes

User Interface Guideline #6.30

is a

requirement

that states:

(RFRS 3.5.14)

If modifications to a resource are made outside the workbench, users should be prompted to either override the changes made outside the workbench or back out of the

Save

operation when the save action is invoked in the editor.

Open your editor on a file and make a change. Next, modify the file outside Eclipse. Finally, switch back to Eclipse and attempt to save the file. Show that you are prompted to override the external changes or to cancel the save operation. If you implement your editor using the editor framework described in this chapter, the framework should automatically enforce this guideline.

8.7.6

Registering editor menus

User Interface Guideline #6.14

is a

best practice

that states:

(RFRS 5.3.5.2)

Register with the platform all context menus in the editor. In the platform, the menu and toolbar for an editor are automatically extended by the platform. By contrast, context menu extension is supported in collaboration between the editor and the platform. To achieve this collaboration, an editor must register each context menu it contains with the platform.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

404 CHAPTER 8 • Editors

To pass this test, show that your editor’s context menus have been registered with the platform. If they are properly registered, you should see the system contributing appropriate context menu items. See Section 8.5.1, Context menu, on page 381 for more about context menus.

8.7.7

Editor action filters

User Interface Guideline #6.15

is a

best practice

that states:

(RFRS 5.3.5.3)

Implement an action filter for each object type in the editor. An action filter makes it easier for one plug-in to add an action to objects in an editor defined by another plug-in.

For this test, show that menu action filtering is in effect for the objects edited by your editor. See Section 6.7.2, Action filtering and enablement, on page 260 for more about using action filters and Section 8.5.1.1, Creating the context menu, on page 382 for more about building context menus that can be extended by other plug-ins.

8.7.8

Unsaved editor modifications

User Interface Guideline #6.17

is a

best practice

that states:

(RFRS 5.3.5.4)

If the input to an editor is deleted and the editor contains changes, the editor should give the user a chance to save the changes to another location, and then close.

Start by opening your editor on a file and then making a change. Next, select the file in the

Navigator

view and delete it. Show that a warning message is displayed, informing the user that the editor contains unsaved changes. To pass the best practice component of this guideline, the user should be given the option to save the file to another location. If you implement your editor using the editor framework described in this chapter, the framework should automatically enforce this guideline.

8.7.9

Prefix dirty resources

User Interface Guideline #6.18

is a

best practice

that states:

(RFRS 5.3.5.5)

If a resource is dirty, prefix the resource name presented in the editor tab with an asterisk.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

8.8

Summary 405

This is essentially a subset of Guideline #14. Edit a file with your editor and show that the filename is prefixed with an asterisk. If you implement your editor using the editor framework described in this chapter, the framework should automatically enforce this guideline.

8.7.10

Editor outline view (RFRS 5.3.5.6)

User Interface Guideline #6.20

is a

best practice

that states:

If the data within an editor is too extensive to see on a single screen, and will yield a structured outline, the editor should provide an outline model to the

Outline

view. In Eclipse, there is a special relationship between each editor and the

Outline

view. When an editor is opened, the

Outline

view will connect to the editor and ask it for an outline model. If the editor answers with an outline model, that model will be displayed in the

Outline

view whenever the editor is active. The outline is used to navigate through the edit data, or interact with the edit data at a higher level of abstraction.

For this test, open your editor and show that it updates the contents of the

Outline

view and allows the data’s structure to be navigated. If a different instance of your editor is selected, show that the

Outline

view’s contents change appropriately. See Section 8.4, Editor Lifecycle, on page 378 for information about linking an editor to the

Outline

view.

8.7.11

Synchronize with outline view

User Interface Guideline #6.21

is a

best practice

that states:

(RFRS 5.3.5.7)

Notification about location between an editor and the

Outline

view should be two-way. Context menus should be available in the

Outline

view as appropriate.

Select an item in the

Outline

view and show that it selects the corresponding item in the editor. Next, select an item in the editor and show that it selects the corresponding item in the

Outline

view.

8.8

Summary

This chapter went into detail about how to create new editors for editing and browsing resources in the workbench. It showed how to set up a multipage editor, handle the editor lifecycle, and create various editor actions.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

406 CHAPTER 8 • Editors

References

Chapter source (see Section 2.9, Book Samples, on page 105).

Deva, Prashant, “Folding in Eclipse Text Editors,” March 11, 2005

(

www.eclipse.org/articles/Article-Folding-in-Eclipse-Text-Editors/ folding.html

).

Ho, Elwin, “Creating a Text-Based Editor for Eclipse,” HP, June 2003

(

devresource.hp.com/drc/technical_white_papers/eclipeditor/index.jsp

).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 9

Resource Change Tracking

The Eclipse system generates resource change events indicating, for example, the files and folders that have been added, modified, and removed during the course of an operation. Interested objects can subscribe to these events and take whatever action is necessary to keep themselves synchronized with

Eclipse.

To demonstrate resource change tracking, the

Favorites

view will be modified (see Chapter 7, Views) so that whenever a resource is deleted, you can remove the corresponding element from the

Favorites

view.

9.1

IResourceChangeListener

Eclipse uses the interface org.eclipse.core.resources.IResource-

ChangeListener

to notify registered listeners when a resource has changed.

The

FavoritesManager

(see Section 7.2.3, View model, on page 295) needs to keep its list of

Favorites

items synchronized with Eclipse. This is done by implementing the org.eclipse.core.resources.IResourceChange-

Listener

interface and registering for resource change events.

In addition, the

FavoritesActivator stop()

method must be modified to call the new

FavoritesManager shutdown()

method so that the manager is no longer notified of resource changes once the plug-in has been shut down.

Now, whenever a resource change occurs, Eclipse will call the resourceChanged()

method.

407

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

408 CHAPTER 9 • Resource Change Tracking

public class FavoritesManager

implements IResourceChangeListener

{

private FavoritesManager() {

ResourcesPlugin.getWorkspace().addResourceChangeListener (

this, IResourceChangeEvent.POST_CHANGE);

}

public static void shutdown() {

if (manager != null) {

ResourcesPlugin.getWorkspace()

.removeResourceChangeListener(manager);

manager.saveFavorites();

manager = null;

}

}

public void resourceChanged(IResourceChangeEvent e) {

// Process events here.

}

... existing code from Section 7.2.3, View model, on page 295 ...

}

9.1.1

IResourceChangeEvent

FavoritesManager

is only interested in changes that have already occurred and therefore uses the

IResourceChangeEvent.POST_CHANGE

constant when subscribing to change events. Several

IResourceChangeEvent

constants that can be used in combination to specify when an interested object should be notified of resource changes are provided by Eclipse. Below is the list of valid constants as they appear in the

IResourceChangeEvent

Javadoc.

PRE_BUILD

—Before-the-fact report of builder activity (see Section 14.1,

Builders, on page 535).

PRE_CLOSE

—Before-the-fact report of the impending closure of a single project as returned by getResource()

.

PRE_DELETE

—Before-the-fact report of the impending deletion of a single project as returned by getResource()

.

PRE_REFRESH

—before-the-fact report of refreshing of a project.

POST_BUILD

—After-the-fact report of builder activity (see Section 14.1,

Builders, on page 535).

POST_CHANGE

—After-the-fact report of creations, deletions, and modifications to one or more resources expressed as a hierarchical resource delta as returned by getDelta()

.

The

IResourceChangeEvent

interface also defines several methods that can be used to query its state.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

9.1

IResourceChangeListener 409

findMarkerDeltas(String, boolean)

—Returns all marker deltas of the specified type that are associated with resource deltas for this event.

Pass true

as the second parameter if you want to include subtypes of the specified type.

getBuildKind()

—Returns the kind of build that caused the event.

getDelta()

—Returns a resource delta, rooted at the workspace, describing the set of changes that happened to resources in the workspace. getResource()

—Returns the resource in question. getSource()

—Returns an object identifying the source of this event. getType()

—Returns the type of event being reported.

9.1.2

IResourceDelta

Each individual change is encoded as an instance of a resource delta that is represented by the

IResourceDelta

interface. Eclipse provides several different constants that can be used in combination to identify the resource deltas handled by the system. Below is the list of valid constants as they appear in the

IResourceDelta

Javadoc.

ADDDED

—Delta kind constant indicating that the resource has been added to its parent.

ADDED_PHANTOM

—Delta kind constant indicating that a phantom resource has been added at the location of the delta node.

ALL_WITH_PHANTOMS

—The bit mask that describes all possible delta kinds, including those involving phantoms.

CHANGED

—Delta kind constant indicating that the resource has been changed.

CONTENT

—Change constant indicating that the content of the resource has changed.

COPIED_FROM

—Change constant indicating that the resource was copied from another location.

DESCRIPTION

—Change constant indicating that a project’s description has changed.

ENCODING

—Change constant indicating that the encoding of the resource has changed.

LOCAL_CHANGED

—Change constant indicating that the underlying file or folder of the linked resource has been added or removed.

MARKERS

—Change constant indicating that the resource’s markers have changed.

MOVED_FROM

—Change constant indicating that the resource was moved from another location.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

410 CHAPTER 9 • Resource Change Tracking

MOVED_TO

—Change constant indicating that the resource was moved to another location.

NO_CHANGE

—Delta kind constant indicating that the resource has not been changed in any way.

OPEN

—Change constant indicating that the resource was opened or closed.

REMOVED

—Delta kind constant indicating that the resource has been removed from its parent.

REMOVED_PHANTOM

—Delta kind constant indicating that a phantom resource has been removed from the location of the delta node.

REPLACED

—Change constant indicating that the resource has been replaced by another at the same location (i.e., the resource has been deleted and then added).

SYNC

—Change constant indicating that the resource’s sync status has changed.

TYPE

—Change constant indicating that the type of the resource has changed.

The

IResourceDelta

class also defines numerous useful APIs as follows: accept(IResourceDeltaVisitor)

—Visits resource deltas that are

ADDED

,

CHANGED

, or

REMOVED

. If the visitor returns true

, the resource delta’s children are also visited.

accept(IResourceDeltaVisitor, boolean)

—Same as above but optionally includes phantom resources.

accept(IResourceDeltaVisitor, int)

—Same as above but optionally includes phantom resources and/or team private members.

findMember(IPath)

—Finds and returns the descendant delta identified by the given path in this delta, or null

if no such descendant exists.

getAffectedChildren()

—Returns resource deltas for all children of this resource that were

ADDED

,

CHANGED

, or

REMOVED

.

getAffectedChildren(int)

—Returns resource deltas for all children of this resource whose kind is included in the given mask. getFlags()

—Returns flags that describe in more detail how a resource has been affected. getFullPath()

—Returns the full, absolute path of this resource delta. getKind()

—Returns the kind of this resource delta. getMarkerDeltas()

—Returns the changes to markers on the corresponding resource. getMovedFromPath()

—Returns the full path (in the “before” state) from which this resource (in the “after” state) was moved.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

9.2

Processing Change Events 411

getMovedToPath()

—Returns the full path (in the “after” state) to which this resource (in the “before” state) was moved. getProjectRelativePath()

—Returns the project-relative path of this resource delta.

getResource()

—Returns a handle for the affected resource.

9.2

Processing Change Events

The

POST_CHANGE

resource change event is expressed not as a single change, but as a hierarchy describing one or more changes that have occurred. Events are batched in this manner for efficiency; reporting each change as it occurs to every interested object would dramatically slow down the system and reduce responsiveness to the user. To see this hierarchy of changes, add the following code to the

FavoritesManager

.

public void resourceChanged(IResourceChangeEvent event) {

System.out.println(

"FavoritesManager - resource change event");

try {

event.getDelta().accept(new IResourceDeltaVisitor() {

public boolean visit(IResourceDelta delta)

throws CoreException

{

StringBuffer buf = new StringBuffer(80);

switch (delta.getKind()) {

case IResourceDelta.ADDED:

buf.append("ADDED");

break;

case IResourceDelta.REMOVED:

buf.append("REMOVED");

break;

case IResourceDelta.CHANGED:

buf.append("CHANGED");

break;

default:

buf.append("[");

buf.append(delta.getKind());

buf.append("]");

break;

}

buf.append(" ");

buf.append(delta.getResource());

System.out.println(buf);

return true;

}

});

}

catch (CoreException ex) {

FavoritesLog.logError(ex);

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

412 CHAPTER 9 • Resource Change Tracking

The preceding code will generate a textual representation of the hierarchical structure describing the resource changes in the system. To see this code in action, launch the

Runtime Workbench

(see Section 2.6, Debugging the Product, on page 94) and open the

Favorites

view. In the

Runtime Workbench

, create a simple project and then add folders and files as shown here (see Figure

9–1).

Figure 9–1

Navigator view.

During the creation process, you will see output generated to the

Console

view describing the resource change events that were sent by Eclipse. The

FavoritesManager

is specifically interested in the deletion of resources, and when you delete these two files, you’ll see the following in the

Console

view:

FavoritesManager - resource change event

CHANGED R/

CHANGED P/Test

CHANGED F/Test/folder1

CHANGED F/Test/folder1/folder2

REMOVED L/Test/folder1/folder2/file1.txt

REMOVED L/Test/folder1/folder2/file2.txt

The next step is to modify the

FavoritesManager

methods to do something with this information. The modifications will enable the

Favorites-

Manager

to remove

Favorites

items that reference resources that have been removed from the system.

public void resourceChanged(IResourceChangeEvent event) {

Collection<IFavoriteItem> itemsToRemove

= new HashSet<IFavoriteItem>();

try {

event.getDelta().accept(new IResourceDeltaVisitor() {

public boolean visit(IResourceDelta delta)

throws CoreException

{

if (delta.getKind() == IResourceDelta.REMOVED) {

IFavoriteItem item =

existingFavoriteFor(delta.getResource());

if (item != null)

itemsToRemove.add(item);

}

return true;

}

});

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

9.2

Processing Change Events 413

catch (CoreException ex) {

FavoritesLog.logError(ex);

}

if (itemsToRemove.size() > 0)

removeFavorites(itemsToRemove.toArray(

new IFavoriteItem[itemsToRemove.size()]));

}

When the preceding code is in place, launch the

Runtime Workbench

to test this modification by creating a file in a project, adding that file as a

Favorites

item to the

Favorites

view, and then deleting the file from the project. The file is removed, but the

Favorites

item is not removed as it should be. Looking in the

.log

file (see Section 3.6.2, The Error Log view, on page 131) reveals the following exception: org.eclipse.swt.SWTException: Invalid thread access

This indicates that an SWT component, such as the table in the

Favorites

view, is being accessed from a thread other than the UI thread (see Section

4.2.5.1, Display, on page 148 for more about

Display.getDefault()

and the UI thread). To alleviate this problem, modify the

FavoritesView-

ContentProvider favoritesChanged()

method as shown below to ensure that the viewer is accessed on the UI thread.

public void favoritesChanged(final FavoritesManagerEvent event) {

// If this is the UI thread, then make the change.

if (Display.getCurrent() != null) {

updateViewer(event);

return;

}

// otherwise, redirect to execute on the UI thread.

Display.getDefault().asyncExec(new Runnable() {

public void run() {

updateViewer(event);

}

});

} private void updateViewer(final FavoritesManagerEvent event) {

// Use the setRedraw method to reduce flicker

// when adding or removing multiple items in a table.

viewer.getTable().setRedraw(false);

try {

viewer.remove(event.getItemsRemoved());

viewer.add(event.getItemsAdded());

}

finally {

viewer.getTable().setRedraw(true);

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

414 CHAPTER 9 • Resource Change Tracking

9.3

Batching Change Events

Anytime a UI plug-in modifies resources, it should wrap the resource modification code by subclassing org.eclipse.ui.actions.WorkspaceModify-

Operation

. The primary consequence of using this operation is that events that typically occur as a result of workspace changes (e.g., the firing of resource deltas, performance of autobuilds, etc.) are deferred until the outermost operation has successfully completed. In the

Favorites

view, if you want to implement a delete operation that deleted the underlying resources themselves rather than just the

Favorites

items that referenced the resources, then it might be implemented as shown below.

The run()

method, inherited from

WorkspaceModifyOperation

and called by an

Action

or

IActionDelegate

, first calls the execute()

method and then fires a single change event containing all the resources changed by the execute()

method.

package com.qualityeclipse.favorites.actions; import ...

public class DeleteResourcesOperation

extends WorkspaceModifyOperation

{

private final IResource[] resources;

public DeleteResourcesOperation(IResource[] resources) {

this.resources = resources;

}

protected void execute(IProgressMonitor monitor)

throws

CoreException,

InvocationTargetException,

InterruptedException

{

monitor.beginTask("Deleting resources...", resources.length);

for (int i = 0; i < resources.length; i++) {

if (monitor.isCanceled())

break;

resources[i].delete(

true, new SubProgressMonitor(monitor, 1));

}

monitor.done();

}

}

If you are modifying resources in a headless Eclipse environment or in a plug-in that does not rely on any UI plug-ins, the

WorkspaceModify-

Operation

class is not accessible. In this case, use the

IWorkspace.run() method to batch change events.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

9.4

Progress Monitor 415

9.4

protected void execute(IProgressMonitor monitor)

throws CoreException

{

ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {

public void run(IProgressMonitor monitor) throws CoreException

{

monitor.beginTask(

"Deleting resources...", resources.length);

for (int i = 0; i < resources.length; i++) {

resources[i].delete(

true, new SubProgressMonitor(monitor, 1));

}

monitor.done();

}

}, monitor);

}

Progress Monitor

For long-running operations, the progress monitor indicates what the operation is doing and an estimate of how much more there is left to be done. In the preceding code, a progress monitor was used to communicate with the user, indicating that resources were being deleted and how many resources needed to be deleted before the operation completed (see methods in previous sections and the redo()

method in Section 8.5.4, Undo/Redo, on page 392).

In addition, since

DeleteResourcesOperation

interacts with the user interface, isCanceled()

is called periodically to see if the user has canceled the operation. There is nothing more frustrating than looking at a long running operation with a cancel button only to find out that the cancel button has no effect.

9.4.1

IProgressMonitor

The org.eclipse.core.runtime.IProgressMonitor

interface provides methods for indicating when an operation has started, how much has been done, and when it is complete.

beginTask(String, int)

—Called once by the operation to indicate that the operation has started and approximately how much work must be done before it is complete. This method must be called exactly once per instance of a progress monitor.

done()

—Called by the operation to indicate that it is complete.

isCanceled()

—The operation should periodically poll this method to see whether the user has requested that the operation be canceled.

setCanceled(boolean)

—This method is typically called by UI code setting the canceled state to true

when the user clicks on the

Cancel

button during an operation.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

416 CHAPTER 9 • Resource Change Tracking

setTaskName(String)

—Sets the task name displayed to the user. Usually, there is no need to call this method because the task name is set by beginTask(String, int)

.

worked(int)

—Called by the operation to indicate that the specified number of units of work has been completed.

The

IProgressMonitorWithBlocking

interface extends

IProgress-

Monitor

for monitors that want to support feedback when an activity is blocked due to concurrent activity in another thread. If a running operation ever calls the setBlocked

method listed below, it must eventually call clearBlocked

before the operation completes.

clearBlocked()

—Called by an operation to indicate that the operation is no longer blocked.

setBlocked(IStatus)

—Called by an operation to indicate that this operation is blocked by some background activity.

9.4.2

Classes for displaying progress

Eclipse provides several classes that either implement the

IProgressMonitor interface or provide a progress monitor via the

IRunnableWithProgress interface. These classes are used under different circumstances to notify the user of the progress of long-running operations.

SubProgressMonitor

—A progress monitor passed by a parent operation to a suboperation so that the suboperation can notify the user of progress as a portion of the parent operation (see Section 9.3, Batching

Change Events, on page 414).

NullProgressMonitor

—A progress monitor that supports cancellation but does not provide any user feedback. Suitable for subclassing.

ProgressMonitorWrapper

—A progress monitor that wraps another progress monitor and forwards

IProgressMonitor

and

IProgress-

MonitorWithBlocking

methods to the wrapped progress monitor. Suitable for subclassing.

WorkspaceModifyOperation

—An operation that batches resource change events and provides a progress monitor as part of its execution

(see Section 9.3, Batching Change Events, on page 414).

ProgressMonitorPart

—An SWT composite consisting of a label displaying the task and subtask name, and a progress indicator to show progress.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

9.4

Progress Monitor 417

ProgressMonitorDialog

—Opens a dialog that displays progress to the user and provides a progress monitor used by the operation to relate that information.

TimeTriggeredProgressMonitorDialog

—Waits for a specified amount of time during operation execution

then

opens a dialog that displays progress to the user and provides a progress monitor used by the operation to relate that information. If the operation completes

before

the specified amount of time, then no dialog is opened. This is an internal workbench class but is listed here because the concept and its functionality is interesting. For more, see Section 9.4.4, IProgressService, on page 419 and Bugzilla entry 123797 at

bugs.eclipse.org/bugs/show_bug.cgi?id=123797

.

WizardDialog

—When opened, optionally provides progress information as part of the wizard. The wizard implements the

IRunnableContext

, and thus the operation can call run(boolean, boolean, IRunnable-

WithProgress)

and display progress in the wizard via the provided progress monitor (see Section 11.2.3, IWizardContainer, on page 468 and

Section 11.2.6, Wizard example, on page 473).

As an example, we implement a new

DeleteResourcesHandler

to open a

ProgressMonitorDialog

and then execute

DeleteResourcesOperation

to perform the actual operation. The new

DeleteResourcesHandler

class extends

AbstractHandler

and contains an execute

method like this: public Object execute(ExecutionEvent event)

throws ExecutionException

{

ISelection selection = HandlerUtil.getCurrentSelection(event);

if (!(selection instanceof IStructuredSelection))

return null;

IStructuredSelection structSel = (IStructuredSelection) selection;

// Build a collection of selected resources

final Collection<IResource> resources = new HashSet<IResource>();

for (Iterator<?> iter = structSel.iterator(); iter.hasNext();) {

Object element = iter.next();

if (element instanceof IAdaptable)

element = ((IAdaptable) element).getAdapter(IResource.class);

if ((element instanceof IResource))

resources.add((IResource) element);

}

// Execute the operation

try {

// Display progress either using the ProgressMonitorDialog ...

// Shell shell = HandlerUtil.getActiveShell(event);

// IRunnableContext context = new ProgressMonitorDialog(shell);

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

418 CHAPTER 9 • Resource Change Tracking

// ... or using the window's status bar ...

// IWorkbenchWindow context

// = HandlerUtil.getActiveWorkbenchWindow(event);

// ... or using the workbench progress service

IWorkbenchWindow window

= HandlerUtil.getActiveWorkbenchWindow(event);

IRunnableContext context

= window.getWorkbench().getProgressService();

9.4.3

context.run(true, false, new IRunnableWithProgress() {

public void run(IProgressMonitor monitor)

throws InvocationTargetException, InterruptedException

{

new DeleteResourcesOperation(resources.toArray(

new IResource[resources.size()])).run(monitor);

}

});

}

catch (Exception e) {

FavoritesLog.logError(e);

}

return null;

}

Workbench window status bar

The workbench window provides a progress display area along the bottom edge of the window. Use the

IWorkbenchWindow.run()

method to execute the operation and the progress monitor passed to

IRunnableWithProgress will be the progress monitor in the status bar. For example, the following snippet from a handler (see Section 6.3, Handlers, on page 236 for more on creating handlers) shows simulated progress in the status bar: public Object execute(ExecutionEvent event)

throws ExecutionException

{

IWorkbenchWindow window

= HandlerUtil.getActiveWorkbenchWindow(event);

try {

window.run(true, true, new IRunnableWithProgress() {

public void run(IProgressMonitor monitor)

throws InvocationTargetException, InterruptedException {

monitor.beginTask("simulate status bar progress:", 20);

for (int i = 20; i > 0; --i) {

monitor.subTask("seconds left = " + i);

Thread.sleep(1000);

monitor.worked(1);

}

monitor.done();

}

});

}

catch (InvocationTargetException e) {

FavoritesLog.logError(e);

}

catch (InterruptedException e) {

// User canceled the operation... just ignore.

}

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

9.4

Progress Monitor 419

If you have a view or editor, you can obtain the containing

IWorkbench-

Window

via

IWorkbenchPart

, which both

IViewPart

and

IEditorPart extend.

IWorkbenchWindow window = viewOrEditor

.getSite().getWorkbenchWindow();

You can also obtain the progress monitor in the status bar directly via the

IStatusLineManager

interface.

viewPart.getViewSite().getActionBars()

.getStatusLineManager().getProgressMonitor() or: editorPart.getEditorSite().getActionBars()

.getStatusLineManager().getProgressMonitor()

9.4.4

IProgressService

Yet another mechanism for displaying progress in the workbench is using the

IProgressService

interface. While the run()

method in

IWorkbench-

Window

displays progress in the status bar, the

IProgressService

interface displays progress using a subclass of

ProgressMonitorDialog

named

TimeTriggeredProgressMonitorDialog

. Although you could use a

ProgressMonitorDialog

,

IProgressService

only opens a progress dialog if the operation takes longer to execute than a specified amount of time

(currently 800 milliseconds).

window.getWorkbench().getProgressService().run(true, true,

new IRunnableWithProgress() {

public void run(IProgressMonitor monitor)

throws InvocationTargetException, InterruptedException

{

monitor.beginTask("Simulated long running task #1", 60);

for (int i = 60; i > 0; --i) {

monitor.subTask("seconds left = " + i);

if (monitor.isCanceled()) break;

Thread.sleep(1000);

monitor.worked(1);

}

monitor.done();

}

});

Typically, jobs are executed in the background (see Section 21.8, Background Tasks—Jobs API, on page 808), but the

IProgressService

provides the showInDialog()

method and the

UIJob

class for executing them in the foreground.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

420 CHAPTER 9 • Resource Change Tracking

9.5

Delayed Changed Events

Eclipse uses lazy initialization—only load a plug-in when it is needed. Lazy initialization presents a problem for plug-ins that need to track changes. How does a plug-in track changes when it is not loaded?

Eclipse solves this problem by queuing change events for a plug-in that is not loaded. When the plug-in is loaded, it receives a single resource change event containing the union of the changes that have occurred during the time it was not active. To receive this event, your plug-in must register to be a save participant when it is started up, as follows.

public static void addSaveParticipant() {

ISaveParticipant saveParticipant = new ISaveParticipant(){

public void saving(ISaveContext context)

throws CoreException

{

// Save any model state here.

context.needDelta();

}

public void doneSaving(ISaveContext context) {}

public void prepareToSave(ISaveContext context)

throws CoreException {}

public void rollback(ISaveContext context) {}

};

ISavedState savedState;

try {

savedState = ResourcesPlugin

.getWorkspace()

.addSaveParticipant (

FavoritesActivator.getDefault(),

saveParticipant);

}

catch (CoreException e) {

FavoritesLog.logError(e);

// Recover if necessary.

return;

}

if (savedState != null)

savedState.processResourceChangeEvents(

FavoritesManager.getManager());

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

9.6

Summary

Tip

: Even though Eclipse is based on lazy plug-in initialization, it does provide a mechanism for plug-ins to start when the workbench itself starts. To activate at startup, the plug-in must extend the org.eclipse. ui.startup

extension point and implement the org.eclipse.ui.

IStartup

interface. Once the plug-in is started, the workbench will call the plug-in’s earlyStartup()

method (see Section 3.4.2, Early plug-in startup, on page 121). A workbench preference option gives the user the ability to prevent a plug-in from starting early, so make sure that if your plug-in takes advantage of this extension point, it degrades gracefully in the event that it is not started early.

421

9.6

Summary

This chapter demonstrated how to process resource change events propagated by the system. Anytime a resource is added, modified, or removed, a corresponding change event is generated. Responding to these events provides a way for your plug-in to stay synchronized with the Eclipse environment.

References

Chapter source (see Section 2.9, Book Samples, on page 105).

Arthorne, John, “How You’ve Changed! Responding to Resource Changes in the Eclipse Workspace,” OTI, November 23, 2004 (

www.eclipse.org/articles/

Article-Resource-deltas/resource-deltas.html

).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 10

Perspectives

Perspectives are a way to group Eclipse views and commands for a particular task such as coding or debugging. Larger Eclipse enhancements that involve multiple plug-ins may provide their own perspectives. Smaller Eclipse enhancements that involve only one or two plug-ins and provide only one or two new Eclipse views typically enhance existing perspectives rather than provide entirely new perspectives.

This chapter will further extend the

Favorites

example by creating a new perspective for hosting the

Favorites

view and show how to add the

Favorites

view to existing perspectives.

10.1

Creating a Perspective

To create a new perspective, extend the org.eclipse.ui.perspectives

extension point and then define the layout of the perspective by creating a perspective factory class implementing the

IPerspectiveFactory

interface (see

Figure 10–1).

423

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

424

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 10 • Perspectives

Figure 10–1

Perspective declaration and behavior.

10.1.1

Perspective extension point

Start by opening the

Favorites

plug-in manifest editor, selecting the

Extensions

tab, and clicking the

Add

button. When the

New Extension

wizard opens,

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

10.1

Creating a Perspective 425

select

org.eclipse.ui.perspectives

from the list of all available extension points

(see Figure 10–2). Click the

Finish

button to add this extension to the plug-in manifest.

Figure 10–2

The New Extension wizard showing the org.eclipse.ui.perspectives

extension point selected.

Adding the extension immediately adds a new perspective named com.qualityeclipse.favorites.perspective1

to the the

Extensions

page of the plug-in manifest. Note that you can right-click on the org.eclipse.ui.perspectives

extension and select

New > perspective

to add additional perspectives at any time. Clicking on this new perspective displays its properties on the right side of the editor (see Figure 10–3). Modify them as follows:

id—

“com.qualityeclipse.favorites.FavoritesPerspective”

The unique identifier used to reference the perspective.

name—

“Favorites”

The text label associated with the perspective.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

426

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 10 • Perspectives

Figure 10–3

The extension element details for the Favorites perspective.

class—

“com.qualityeclipse.favorites.perspectives.FavoritesPerspective-

Factory”

The class describing the layout of the perspective. The class is instantiated using its no argument constructor, but can be parameterized using the

IExecutableExtension

interface (see Section 21.5, Types Specified in an Extension Point, on page 793).

icon—

“icons/sample.gif”

The icon associated with the perspective.

fixed

—(leave blank)

If true, then the layout of the perspective is fixed and views created by the perspective factory are not closeable, and cannot be moved.

If you switch to the

plugin.xml

page of the plug-in manifest editor, you will see the following new section of XML defining the new perspective:

<extension point="org.eclipse.ui.perspectives">

<perspective

class="com.qualityeclipse.favorites.perspectives.

FavoritesPerspectiveFactory"

icon="icons/sample.gif"

id="com.qualityeclipse.favorites.FavoritesPerspective"/>

name="Favorites"

</extension>

10.1.2

Perspective factories

When specifying the name of a perspective factory class, clicking on the

Browse...

button next to the

class

field will open a class selection editor, in which an existing class can be selected. Clicking on the

class:

label to the right of the

class

field will open a

New Java Class

wizard in which a new class (conforming to the

IPerspectiveFactory

interface) can be created

(see Figure 10–4).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

10.1

Creating a Perspective

5HWXUQWR7DEOHRI&RQWHQWV

427

Figure 10–4

The Java Class Selection wizard.

The

IPerspectiveFactory

interface defines a single method, createInitialLayout()

, which specifies the initial page layout and visible action sets for the perspective. The factory is only used to define the initial layout of the perspective and is then discarded. By default, the layout area contains space for the editors, but no views. The factory can add additional views, which are placed relative to the editor area or to another view.

Open the newly created

FavoritesPerspectiveFactory

class and modify it as follows so that the

Favorites

view will appear below the editor area and the standard

Outline

view will be shown to its left.

package com.qualityeclipse.favorites.perspectives; import org.eclipse.ui.*; public class FavoritesPerspectiveFactory

implements IPerspectiveFactory

{

private static final String FAVORITES_VIEW_ID =

"com.qualityeclipse.favorites.views.FavoritesView";

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

428 CHAPTER 10 • Perspectives

private static final String FAVORITES_ACTION_ID =

"com.qualityeclipse.favorites.workbenchActionSet";

public void createInitialLayout(IPageLayout layout) {

// Get the editor area.

String editorArea = layout.getEditorArea();

// Put the Outline view on the left.

layout.addView(

IPageLayout.ID_OUTLINE,

IPageLayout.LEFT,

0.25f,

editorArea);

// Put the Favorites view on the bottom with

// the Tasks view.

IFolderLayout bottom =

layout.createFolder(

"bottom",

IPageLayout.BOTTOM,

0.66f,

editorArea);

bottom.addView(FAVORITES_VIEW_ID);

bottom.addView(IPageLayout.ID_TASK_LIST);

bottom.addPlaceholder(IPageLayout.ID_PROBLEM_VIEW);

// Add the Favorites action set.

layout.addActionSet(FAVORITES_ACTION_ID);

}

}

Within the createInitialLayout()

method, the addView()

method is used to add the standard

Outline

view to the left of the editor area such that it takes up 25 percent of the horizontal area within the window. Using the createFolder()

method, a folder layout is created to occupy the bottom third of the layout below the editor area. The

Favorites

view and standard

Tasks

view are added to the folder layout so that each will appear stacked with a tab inside the folder.

Next, a placeholder for the standard

Problems

view is added to the folder.

If a user opened the

Problems

view, it would open in the location specified by the placeholder. Finally, the

Favorites

action set is made visible by default within the perspective.

To open the

Favorites

perspective select

Window > Open Perspective >

Other..

then select

Favorites

. When opened, the new

Favorites

perspective will look something like what’s shown in Figure 10–5.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

10.1

Creating a Perspective

5HWXUQWR7DEOHRI&RQWHQWV

429

Figure 10–5

The Favorites perspective.

10.1.3

IPageLayout

As seen in the previous section, the

IPageLayout

interface defines the protocol necessary to support just about any possible perspective layout. It defines many useful APIs, including the following: addActionSet(String)

—Adds an action set with the given ID to the page layout. addFastView(String)

—Adds the view with the given ID to the page layout as a fast view. addFastView(String, float)

—Adds the view with the given ID to the page layout as a fast view with the given width ratio. addNewWizardShortcut(String)

—Adds a creation wizard to the

File

New

menu. addPerspectiveShortcut(String)

—Adds a perspective shortcut to the

Perspective

menu.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

430 CHAPTER 10 • Perspectives

addPlaceholder(String, int, float, String)

—Adds a placeholder for a view with the given ID to the page layout. addShowInPart(String)

—Adds an item to the

Show In

prompter. addShowViewShortcut(String)

—Adds a view to the

Show View

menu. addStandaloneView(String, boolean, int, float, String)

Adds a stand-alone view with the given ID to this page layout. A standalone view cannot be docked together with other views. addView(String, int, float, String)

—Adds a view with the given ID to the page layout. createFolder(String, int, float, String)

—Creates and adds a folder with the given ID to the page layout. createPlaceholderFolder(String, int, float, String)

Creates and adds a placeholder for a new folder with the given ID to the page layout. getEditorArea()

—Returns the special ID for the editor area in the page layout. getViewLayout(String)

—Returns the layout for the view or placeholder with the given compound ID in this page layout. setEditorAreaVisible(boolean)

—Shows or hides the editor area for the page layout. setFixed(boolean)

—Sets whether this layout is fixed. In a fixed layout, layout parts cannot be moved or zoomed, and the initial set of views cannot be closed.

10.2

Enhancing an Existing Perspective

In addition to creating a new perspective, you can also extend an existing perspective by adding new views, placeholders, shortcuts, and action sets. To illustrate this, you can add several extensions to the standard

Resource

perspective.

To extend an existing perspective, open the

Favorites

plug-in manifest editor, select the

Extensions

tab, and click the

Add

button to open the

New

Extension

wizard. Select the org.eclipse.ui.perspectiveExtensions

extension point from the list of available extension points (see Figure 10–6).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

10.2

Enhancing an Existing Perspective

5HWXUQWR7DEOHRI&RQWHQWV

431

Figure 10–6

The New Extension wizard showing the org.eclipse.ui.perspectiveExtensions

extension point selected.

Adding the extension immediately adds a new perspective extension named com.qualityeclipse.favorites.perspectiveExtension1

to the

Extensions

page of the plug-in manifest. Note that you can right-click on the org.eclipse.ui.perspectiveExtensions

extension and select

New > perspectiveExtension

to add a perspective extension, if you need to. Click on this new perspective extension to display its properties and change the

targetID

field to “ org.eclipse.ui.resourcePerspective

” (see Figure 10–7).

This will change the name of the perspective extension as seen on the

Extensions

page.

Figure 10–7

The extension element details showing the perspective extension’s attributes.

Tip:

Use “

*

” as the targetID if you want the perspective extensions to appear in

all

perspectives.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

432 CHAPTER 10 • Perspectives

When the perspective extension has been created, a number of different extension types can be added, including views, placeholders, and action sets, as well as shortcuts for views, perspectives, and the new wizards.

10.2.1

Adding views and placeholders

A view can be either directly added to an existing perspective or a placeholder can be added so that when the user opens the view it appears in the correct place. As an example, add the

Favorites

view to the standard

Resource

perspective.

On the

Extensions

page, click on the newly created

org.eclipse.ui.resource-

Perspective

extension and select

New > view

. This immediately adds a perspective view extension named com.quality-eclipse.favorites.view1

to the plug-in manifest. Clicking on this new extension shows its properties, which should be modified as follows (see Figure 10–8).

id—

“com.qualityeclipse.favorites.views.FavoritesView”

The unique identifier of the

Favorites

view.

relationship—

“stack”

This specifies how the view should be oriented relative to the target view.

relative—

“org.eclipse.ui.views.TaskList”

The view relative to which the added view should be oriented.

visible—

“true”

The view should be initially visible.

Figure 10–8

The extension element details showing the perspective view extension’s attributes.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

10.2

Enhancing an Existing Perspective 433

The name of the perspective view extension as seen on the

Extensions

page will change to reflect the id

entered.

In addition to being stacked in a folder relative to another view, the added view could be placed at the left, right, above, or below the view specified in the

relative

field, or added as a fast view in the left toolbar. If the new view is added at the left, right, above, or below, the

ratio

of space that the new view takes from the old view can also be specified.

If the

visible

field is specified as true

, the new view will open when the perspective is opened. If it is set to false

, the view will not open automatically. Rather, a placeholder will be established that defines the initial location of the view, if it is ever opened by a user.

Other attributes such as closable

, movable

, and standalone

affect how the user can manipulate the view. To prevent the view from being closed or moved, set closable

or movable

to false

, respectively. Setting the standalone

property to true

prevents that view from being stacked with any other views.

When the

Resource

perspective is opened, the

Favorites

view will appear stacked relative to the

Tasks

view (see Figure 10–9).

Tip:

After declaring a new perspective extension, you must select

Window > Reset Perspective...

for that new perspective extension to appear in a perspective that is already open.

Figure 10–9

The Resource perspective showing the Favorites view.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

434 CHAPTER 10 • Perspectives

Switching to the

plugin.xml

page of the plug-in manifest editor, you will see the following new section of XML defining the new perspective extension.

<extension point="org.eclipse.ui.perspectiveExtensions">

<perspectiveExtension

targetID="org.eclipse.ui.resourcePerspective">

<view

id="com.qualityeclipse.favorites.views.FavoritesView"

minimized="false"

relationship="stack"

relative="org.eclipse.ui.views.TaskList"

visible="true">

</view>

</perspectiveExtension>

</extension>

10.2.2

Adding shortcuts

Shortcuts for quickly accessing related views, perspectives, and new wizards can also be added to a perspective. As an example, add shortcuts for accessing the

Favorites

view and perspective to the

Resources

perspective.

Start by adding a view shortcut for accessing the

Favorites

view from the

Resource

perspective. On the

Extensions

page, right-click on the org.eclipse.ui.resourcePerspective

extension and select

New > view-

Shortcut

. This adds a view shortcut extension named com.qualityeclipse.favorites.viewShortcut1

to the plug-in manifest. Click on it to show its properties and then change the

id

field to “ com.qualityeclipse.favorites.views.FavoritesView

” (see Figure 10–10). This will change the name of the view shortcut extension as seen on the

Extensions

page.

Figure 10–10

The extension element details showing the view shortcut extension’s attributes.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

10.2

Enhancing an Existing Perspective 435

When the

Resource

perspective is opened, this will add a shortcut to the

Favorites

view on the

Window > Show View

menu (see Figure 10–11).

Figure 10–11

The Show View menu showing the Favorites shortcut.

Next, add a perspective shortcut for accessing the

Favorites

perspective from the

Resource

perspective. On the

Extensions

page, right-click on the org.eclipse.ui.resourcePerspective

extension and select

New > perspectiveShortcut

. This adds a perspective shortcut extension named com.qualityeclipse.favorites.perspectiveShortcut1

to the plug-in manifest. Click on it to show its properties and then change the

id

field to

“ com.qualityeclipse.favorites.FavoritesPerspective

” (see Figure

10–12). This will change the name of the perspective shortcut extension as seen on the

Extensions

page.

Figure 10–12

The extension element details showing the perspective shortcut extension’s attributes.

When the

Resource

perspective is opened, this will add a shortcut to the

Favorites

view on the

Window > Open Perspective

menu (see Figure 10–13).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

436

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 10 • Perspectives

Figure 10–13

The Open Perspective menu showing the Favorites shortcut.

If you switch to the plug-in manifest editor’s

plugin.xml

page, you will see this added section of XML defining the new view and perspective shortcuts:

<extension point="org.eclipse.ui.perspectiveExtensions">

<perspectiveExtension

targetID="org.eclipse.ui.resourcePerspective">

...

<viewShortcut

id="com.qualityeclipse.favorites.views.FavoritesView">

</viewShortcut>

<perspectiveShortcut

id="com.qualityeclipse.favorites.FavoritesPerspective">

</perspectiveShortcut>

</perspectiveExtension>

</extension>

10.2.3

Adding action sets

Groups of commands (menu items and toolbar buttons) defined in action sets can also be added to a perspective (see Chapter 6, Actions, for more about adding actions). As an example, add the

Favorites

action set to the

Resources

perspective.

On the

Extensions

page, right-click on the org.eclipse.ui.resource-

Perspective

extension and select

New > actionSet

. This adds an action set extension, com.qualityeclipse.favorites.actionSet1

, to the plug-in manifest.

Click on this action set to reveal its properties and change the

id

field to

“ com.qualityeclipse.favorites.workbenchActionSet

” (see Figure

10–14). This will change the action set extension’s name as seen on the

Extensions

page.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

10.2

Enhancing an Existing Perspective

5HWXUQWR7DEOHRI&RQWHQWV

437

Figure 10–14

The extension element details showing the action set extension’s attributes.

If you switch to the

plugin.xml

page of the plug-in manifest editor, you will see the following added section of XML defining the new action set extension.

<extension point="org.eclipse.ui.perspectiveExtensions">

<perspectiveExtension

targetID="org.eclipse.ui.resourcePerspective">

...

<actionSet

id="com.qualityeclipse.favorites.workbenchActionSet">

</actionSet>

</perspectiveExtension>

</extension>

With the above enhancements in place, the new perspective and perspective extensions will now be visible on the

Extensions

page (see Figure 10–15).

Figure 10–15

The Extensions page showing the new perspective and perspective extensions.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

438

10.3

RFRS Considerations

CHAPTER 10 • Perspectives

The “User Interface” section of the

RFRS Requirements

includes three best practices dealing with perspectives. All of them are derived from the Eclipse

UI Guidelines.

10.3.1

Create for long-lived tasks (RFRS 5.3.5.10)

User Interface Guideline #8.1

is a

best practice

that states:

Create a new perspective type for long-lived tasks that involve the performance of smaller, non-modal tasks.

A new perspective type should be created when there is a group of related tasks that would benefit from a predefined configuration of actions and views, and these tasks are long-lived. A task-oriented approach is imperative.

To pass this test, create a list of the perspectives defined by your application and demonstrate their use. For every perspective, describe the views, shortcuts, new wizard items, and action sets that are included. In the case of the examples presented earlier in this chapter, show the

Favorites

perspective

(see Figure 10–5) and describe its use to the reviewers.

10.3.2

Extend existing perspectives (RFRS 5.3.5.11)

User Interface Guideline #8.2

is a

best practice

that states:

To expose a single view or two views, extend an existing perspective type.

If a plug-in contributes a small number of views, and these augment an existing task, it is better to add those views to an existing perspective. For instance, if you create a view that augments the task of Java code creation, don’t create a new perspective. Instead, add it to the existing Java perspective.

This strategy provides better integration with the existing platform.

For this test, simply create a list of any views that your application adds to other existing perspectives. In the case of the earlier examples, show the

Favorites

view added to the

Resource

perspective (see Figure 10–9).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

10.4

Summary 439

10.3.3

Add actions to the window menu

User Interface Guideline #8.6

is a

best practice

that states:

(RFRS 5.3.5.15)

Populate the window menu bar with actions and action sets that are appropriate to the task orientation of the perspective, and any larger workflow.

For this test, provide a list of any action sets that have been added to any perspectives. For the

Favorites

view, list things such as the

Favorites

action set that was added to the

Resource

perspective in Section 10.2.3, Adding action sets, on page 436.

10.4

Summary

Perspectives provide a way to group views and actions together to support a specific task. This chapter described how to create a new perspective, to define its default layout, and to add various extensions to it.

References

Chapter source (see Section 2.9, Book Samples, on page 105).

Springgay, Dave, “Using Perspectives in the Eclipse UI,” OTI, August 27,

2001 (

www.eclipse.org/articles/using-perspectives/PerspectiveArticle.html

).

Lorimer, R. J., “Eclipse: Create Your Own Perspective,” April 8, 2005

(

www.javalobby.org/java/forums/t18187.html

).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

CHAPTER 11

Dialogs and Wizards

Good UI guidelines suggest that developers construct modeless Eclipse editors and views, but there are times when a modal dialog or wizard is appropriate.

This chapter lays out Eclipse’s dialog and wizard framework, discusses when a dialog or wizard should be used instead of a view or editor, provides various examples, and discusses Eclipse’s various built-in dialog classes.

11.1

Dialogs

Whenever information is requested from or presented to the user in a modeless fashion, it allows the user to freely interact with all the resources in the workbench. Windows, pages, editors, and views are all examples of modeless

UI constructs that do not restrict the order in which the user interacts with them. Dialogs are typically modal, restricting the user to either entering the information requested or canceling the operation. The only time a modal UI construct should be used is when programming restrictions require a gathering or dissemination of information before any other processing can continue and, even then, for as short a time as possible.

In one case, the program could present two different versions of a file using a dialog. Unfortunately, this approach prevents the user from switching back and forth between the comparison and some other UI construct such as another editor or view. A better approach would be to present that same information in a comparison editor.

Creating a new project represents a different situation. In that case, the operation must gather all the necessary information sequentially before the

441

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

442 CHAPTER 11 • Dialogs and Wizards

operation can be performed. The user has requested the operation and typically does not need to interact with another aspect of the program until all the information is gathered and the operation is complete. In this case, a dialog or wizard is warranted.

11.1.1

SWT dialogs versus JFace dialogs

There are two distinct dialog hierarchies in Eclipse that should not be confused. SWT dialogs ( org.eclipse.swt.Dialog

) are Java representations of built-in platform dialogs such as a file dialog or font dialog; as such, they are not portable or extendable. JFace dialogs ( org.eclipse.jface.dialogs.

Dialog

) are platform-independent dialogs on which wizards are built. SWT dialogs are only briefly discussed, while JFace dialogs are covered in detail.

11.1.2

Common SWT dialogs

Eclipse includes several SWT dialog classes that provide platform-independent interfaces to underlying platform-specific dialogs:

ColorDialog

—Prompts the user to select a color from a predefined set of available colors.

DirectoryDialog

—Prompts the user to navigate the filesystem and select a directory. Valid styles include

SWT.OPEN

for selecting an existing directory and

SWT.SAVE for specifying a new directory.

FileDialog

—Prompts the user to navigate the filesystem and select or enter a filename. Valid styles include

SWT.OPEN

for selecting an existing file and

SWT.SAVE for specifying a new file.

FontDialog

—Prompts the user to select a font from all available fonts.

MessageBox

—Displays a message to the user. Valid icon styles are shown in Table 11–1.

Valid button styles include:

SWT.OK

SWT.OK | SWT.CANCEL

SWT.YES | SWT.NO

SWT.YES | SWT.NO | SWT.CANCEL

SWT.RETRY | SWT.CANCEL

SWT.ABORT | SWT.RETRY | SWT.IGNORE

PrintDialog

—Prompts the user to select a printer and various printrelated parameters prior to starting a print job.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs

Table 11–1

Icon Styles

Constant

SWT.ICON_ERROR

SWT.ICON_INFORMATION

SWT.ICON_QUESTION

SWT.ICON_WARNING

SWT.ICON_WORKING

Icon

443

With any of these SWT dialogs, one of the following modal styles can be specified:

SWT.MODELESS

—Modeless dialog behavior.

SWT.PRIMARY_MODAL

—Modal behavior with respect to the parent shell.

SWT.APPLICATION_MODAL

—Modal behavior with respect to the application.

SWT.SYSTEM_MODAL

—Modal behavior with respect to the entire system.

11.1.3

Common JFace dialogs

There are many JFace dialogs that can be either instantiated directly or reused via subclassing.

Abstract dialogs

AbstractElementListSelectionDialog

—An abstract dialog to select elements from a list of elements.

IconAndMessageDialog

—The abstract superclass of dialogs that have an icon and a message as the first two widgets.

SelectionDialog

—An abstract dialog for displaying and returning a selection.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

444 CHAPTER 11 • Dialogs and Wizards

SelectionStatusDialog

—An abstract base class for dialogs with a status bar and

OK/Cancel

buttons. The status message must be passed over as a

StatusInfo

object and can be an error, warning, or okay. The

OK

button is enabled or disabled depending on the status.

StatusDialog

—An abstract base class for dialogs with a status bar and

OK/Cancel

buttons.

TitleAreaDialog

—An abstract dialog having a title area for displaying a title and an image as well as a common area for displaying a description, a message, or an error message.

TrayDialog

—A specialized Dialog that can contain a tray on its side.

File dialogs

SaveAsDialog

—A standard “Save As” dialog that solicits a path from the user. The getResult()

method returns the path. Note that the folder at the specified path might not exist and might need to be created.

Information dialogs

ErrorDialog

—A dialog to display one or more errors to the user, as contained in an

IStatus

object. If an error contains additional detailed information, then a

Details

button is automatically supplied, which shows or hides an error details viewer when pressed by the user (see Section 11.1.9,

Details dialog, on page 454 for a similar dialog that meets RFRS criteria).

MessageDialog

—A dialog for showing messages to the user.

MessageDialogWithToggle

—A

MessageDialog

that also allows the user to adjust a toggle setting. If a preference store is provided and the user selects the toggle, then the user’s answer (yes/ok or no) will persist in the store (see Section 12.3.2, Accessing preferences, on page 503). If no store is provided, then this information can be queried after the dialog closes.

Resource dialogs

ContainerSelectionDialog

—A standard selection dialog that solicits a container resource from the user. The getResult()

method returns the selected container resource.

NewFolderDialog

—A dialog used to create a new folder. Optionally, the folder can be linked to a filesystem folder.

ProjectLocationMoveDialog

—A dialog used to select the location of a project for moving.

ProjectLocationSelectionDialog

—A dialog used to select the name and location of a project for copying.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 445

ResourceListSelectionDialog

—Shows a list of resources to the user with a text entry field for a string pattern used to filter the list of resources.

ResourceSelectionDialog

—A standard resource selection dialog that solicits a list of resources from the user. The getResult()

method returns the selected resources.

TypeFilteringDialog

—A selection dialog that allows the user to select a file editor.

Selection dialogs

CheckedTreeSelectionDialog

—A dialog to select elements out of a tree structure.

ContainerCheckedTreeViewer

—An enhanced

CheckedTree-

SelectionDialog

dialog with special checked/gray state on the container (non-leaf) nodes.

ElementListSelectionDialog

—A dialog to select elements out of a list of elements.

ElementTreeSelectionDialog

—A dialog to select elements out of a tree structure.

ListDialog

—A dialog that prompts for one element from a list of elements. Uses

IStructuredContentProvider

to provide the elements and

ILabelProvider

to provide their labels.

ListSelectionDialog

—A standard dialog that solicits a list of selections from the user. This class is configured with an arbitrary data model represented by content and label provider objects. The getResult() method returns the selected elements.

TwoPaneElementSelector

—A list selection dialog with two panes.

Duplicated entries will be folded together and are displayed in the lower pane (qualifier).

Miscellaneous dialogs

InputDialog

—A simple input dialog for soliciting an input string from the user.

MarkerResolutionSelectionDialog

—A dialog to allow the user to select from a list of marker resolutions.

ProgressMonitorDialog

—A modal dialog that displays progress during a long-running operation (see Section 9.4, Progress Monitor, on page 415).

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

446 CHAPTER 11 • Dialogs and Wizards

TaskPropertiesDialog

—Shows the properties of a new or existing task, or a problem.

WizardDialog

—A dialog displaying a wizard and implementing the

IWizardContainer

interface (see Section 11.2.3, IWizardContainer, on page 468).

11.1.4

Creating a JFace dialog

The default implementation of the

Dialog

class creates a dialog containing a content area for dialog-specific controls and a button bar below containing

OK

and

Cancel

buttons (see Figure 11–1).

Figure 11–1

Default dialog structure.

Typically, new dialogs are created by subclassing org.eclipse.jface.

dialogs.Dialog

and overriding a handful of methods to customize the dialog for a particular purpose.

buttonPressed(int)

—Called when a button created by the createButton

method is clicked by the user. The default implementation calls okPressed()

if the

OK

button is pressed and cancelPressed()

if the

Cancel

button is pressed.

cancelPressed()

—Called when the user presses the

Cancel

button.

The default implementation sets the return code to

Window.CANCEL

and closes the dialog.

close()

—Closes the dialog, disposes of its shell, and removes the dialog from its window manager (if it has one).

createButton(Composite, int, String, boolean)

—Creates and returns a new button in the button bar with the given identifier and label.

This method is typically called from the createButtonsForButtonBar method.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 447

createButtonBar(Composite)

—Lays out a button bar and calls the createButtonsForButtonBar

method to populate it. Subclasses can override createButtonBar or createButtonsForButtonBar as necessary.

createButtonsForButtonBar(Composite)

—Creates buttons in the button bar. The default implementation creates

OK

and

Cancel

buttons in the lower right corner. Subclasses can override this method to replace the default buttons, or extend this method to augment them using the createButton method.

createContents(Composite)

—Creates and returns this dialog’s contents. The default implementation calls createDialogArea

and createButtonBar

to create the dialog area and button bar, respectively.

Subclasses should override these methods rather than createContents

.

createDialogArea(Composite)

—Creates and returns the content area for the dialog above the button bar. Subclasses typically call the superclass method and then add controls to the returned composite.

okPressed()

—Called when the user presses the

OK

button. The default implementation sets the return code to

Window.OK

and closes the dialog.

open()

—Opens this dialog, creating it first if it has not yet been created.

This method waits until the user closes the dialog, and then returns the dialog’s return code. A dialog’s return codes are dialog-specific, although two standard return codes are predefined:

Window.OK and

Window.CANCEL

.

setShellStyle(int)

—Sets the shell style bits for creating the dialog.

This method has no effect after the shell is created. Valid style bits include:

SWT.MODELESS

SWT.PRIMARY_MODAL

SWT.APPLICATION_MODAL

SWT.SYSTEM_MODAL

SWT.SHELL_TRIM

SWT.DIALOG_TRIM

SWT.BORDER

SWT.CLOSE

SWT.MAX

SWT.MIN

SWT.RESIZE

SWT.TITLE

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

448 CHAPTER 11 • Dialogs and Wizards

setReturnCode(int)

—Sets the dialog’s return code that is returned by the open()

method.

Dialog also provides some related utility methods: applyDialogFont(Control)

—Applies the dialog font to the specified control and recursively to all child controls that currently have the default font.

getImage(String)

—Returns the standard dialog image with the given key (one of the

Dialog.DLG_IMG_*

constants). These images are managed by the dialog framework and must not be disposed by another party.

shortenText(String, Control)

—Shortens the specified text so that its width in pixels does not exceed the width of the given control, by inserting an ellipsis (“...”) as necessary.

11.1.5

Dialog units

If you are positioning controls in the dialog area based on absolute positioning

( null

layout) rather than using a layout manager, such as

GridLayout

or

FormLayout

, then problems may arise when a different font is used. If the dialog is sized for a font with one pixel size and the user has his or her system set for a font in a different pixel size, then the controls will be either too big or too small for the font used. To alleviate this problem, you should position and size the controls based on the font’s average character size or based on

dialog units

(see Figure 11–2).

Figure 11–2

Dialog units superimposed over the letter “T.”

Dialog units are based on the current font and are independent of the display device; thus, they can be used to position controls within a dialog, inde-

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 449

pendent of the font being used. They are defined as one-quarter of the average width of a character and one-eighth of the average height of a character. dialog unit X = average character width / 4 dialog unit Y = average character height / 8

Therefore, use the following to convert from dialog units to pixels.

pixelX = (dialog unit X * average character width) / 4 pixelY = (dialog unit Y * average character height) / 8

The Eclipse dialog framework provides several convenient methods for converting dialog units or character sizes into pixel sizes.

convertHeightInCharsToPixels(int)

—Returns the number of pixels corresponding to the height of the given number of characters.

convertHorizontalDLUsToPixels(int)

—Returns the number of pixels corresponding to the given number of horizontal dialog units.

convertVerticalDLUsToPixels(int)

—Returns the number of pixels corresponding to the given number of vertical dialog units.

convertWidthInCharsToPixels(int)

—Returns the number of pixels corresponding to the width of the given number of characters.

11.1.6

Initial dialog location and size

The default behavior for dialogs as implemented by the dialog framework is to initially position a dialog on top of its parent window specified in the dialog’s constructor. To provide a different initial location or size for a dialog, you would override the following methods as necessary.

getInitialLocation(Point)

—Returns the initial location to use for the dialog. The default implementation centers the dialog horizontally

(half the difference to the left and half to the right) and vertically (onethird above and two-thirds below) relative to the parent shell or display bounds if there is no parent shell. The parameter is the initial size of the dialog, as returned by the getInitialSize()

method.

getInitialSize()

—Returns the initial size to use for the dialog. The default implementation returns the preferred size of the dialog based on the dialog’s layout and controls using the computeSize method.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

450 CHAPTER 11 • Dialogs and Wizards

11.1.7

Resizable dialogs

By default, subclasses of

Dialog

are not resizable, but as of Eclipse 3.4 you may override the isResizable

method to make the dialog resizable: protected boolean isResizable() {

return true;

}

To preserve the size and location of the dialog across invocations, you must supply a location in which to store values. Once you implement isResizable

and getDialogBoundsSettings

, the user can resize the dialog and that size will be persisted across multiple instances: private static final String RESIZABLE_DIALOG_SETTINGS =

"MyResizableDialogSettings"; protected IDialogSettings getDialogBoundsSettings() {

IDialogSettings settings =

FavoritesActivator.getDefault().getDialogSettings();

IDialogSettings section =

settings.getSection(RESIZABLE_DIALOG_SETTINGS);

if (section == null)

section = settings.addNewSection(RESIZABLE_DIALOG_SETTINGS);

return section;

}

For more about

IDialogSettings

see Section 11.2.7, Dialog settings, on page 475.

11.1.8

Favorites view filter dialog

As an example, create a specialized filter dialog for the

Favorites

view that presents the user the option of filtering content based on name, type, or location (see Section 7.2.7, Viewer filters, on page 311 and Section 7.3.4, Pulldown menu, on page 319). The dialog restricts itself to presenting and gathering information from the user and providing accessor methods for the filter action. Start by creating a new

FavoritesFilterDialog

class: public class FavoritesFilterDialog extends Dialog

{

private String namePattern;

private String locationPattern;

private Collection<FavoriteItemType> selectedTypes;

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 451

public FavoritesFilterDialog(

Shell parentShell,

String namePattern,

String locationPattern,

FavoriteItemType[] selectedTypes

) {

super(parentShell);

this.namePattern = namePattern;

this.locationPattern = locationPattern;

this.selectedTypes = new HashSet<FavoriteItemType>();

for (int i = 0; i < selectedTypes.length; i++)

this.selectedTypes.add(selectedTypes[i]);

}

Next, override the createDialogArea()

method to create the various fields that appear in the upper area of the dialog.

private Text namePatternField; private Text locationPatternField; protected Control createDialogArea(Composite parent) {

Composite container = (Composite) super.createDialogArea(parent);

final GridLayout gridLayout = new GridLayout();

gridLayout.numColumns = 2;

container.setLayout(gridLayout);

final Label filterLabel = new Label(container, SWT.NONE);

filterLabel.setLayoutData(new GridData(GridData.BEGINNING,

GridData.CENTER, false, false, 2, 1));

filterLabel.setText("Enter a filter (* = any number of "

+ "characters, ? = any single character)"

+ "\nor an empty string for no filtering:");

final Label nameLabel = new Label(container, SWT.NONE);

nameLabel.setLayoutData(new GridData(GridData.END,

GridData.CENTER, false, false));

nameLabel.setText("Name:");

namePatternField = new Text(container, SWT.BORDER);

namePatternField.setLayoutData(new GridData(GridData.FILL,

GridData.CENTER, true, false));

final Label locationLabel = new Label(container, SWT.NONE);

final GridData gridData = new GridData(GridData.END,

GridData.CENTER, false, false);

gridData.horizontalIndent = 20;

locationLabel.setLayoutData(gridData);

locationLabel.setText("Location:");

locationPatternField = new Text(container, SWT.BORDER);

locationPatternField.setLayoutData(new GridData(GridData.FILL,

GridData.CENTER, true, false));

final Label typesLabel = new Label(container, SWT.NONE);

typesLabel.setLayoutData(new GridData(GridData.BEGINNING,

GridData.CENTER, false, false, 2, 1));

typesLabel.setText("Select the types of favorites to be shown:");

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

452 CHAPTER 11 • Dialogs and Wizards

final Composite typeCheckboxComposite = new Composite(container,

SWT.NONE);

final GridData gridData_1 = new GridData(GridData.FILL,

GridData.FILL, false, false, 2, 1);

gridData_1.horizontalIndent = 20;

typeCheckboxComposite.setLayoutData(gridData_1);

final GridLayout typeCheckboxLayout = new GridLayout();

typeCheckboxLayout.numColumns = 2;

typeCheckboxComposite.setLayout(typeCheckboxLayout);

return container;

}

Next create a new createTypeCheckboxes()

method, called at the end of the createDialogArea()

method, to create one checkbox for each type.

private Map typeFields; protected Control createDialogArea(Composite parent) {

... existing code ...

createTypeCheckboxes(typeCheckboxComposite);

return container;

} private void createTypeCheckboxes(Composite parent) {

typeFields = new HashMap<FavoriteItemType, Button>();

FavoriteItemType[] allTypes = FavoriteItemType.getTypes();

for (int i = 0; i < allTypes.length; i++) {

final FavoriteItemType eachType = allTypes[i];

if (eachType == FavoriteItemType.UNKNOWN)

continue;

final Button button = new Button(parent, SWT.CHECK);

button.setText(eachType.getName());

typeFields.put(eachType, button);

button.addSelectionListener(new SelectionAdapter() {

public void widgetSelected(SelectionEvent e) {

if (button.getSelection())

selectedTypes.add(eachType);

else

selectedTypes.remove(eachType);

}

});

}

}

Add the initContent()

method that is called at the end of the createDialogArea()

method to initialize the various fields in the dialog: protected Control createDialogArea(Composite parent) {

... existing code ...

createTypeCheckboxes(typeCheckboxComposite);

initContent();

return container;

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 453

private void initContent() {

namePatternField.setText(namePattern != null ? namePattern : "");

namePatternField.addModifyListener(new ModifyListener() {

public void modifyText(ModifyEvent e) {

namePattern = namePatternField.getText();

}

});

locationPatternField

.setText(locationPattern != null ? locationPattern : "");

locationPatternField.addModifyListener(new ModifyListener() {

public void modifyText(ModifyEvent e) {

locationPattern = locationPatternField.getText();

}

});

FavoriteItemType[] allTypes = FavoriteItemType.getTypes();

for (int i = 0; i < allTypes.length; i++) {

FavoriteItemType eachType = allTypes[i];

if (eachType == FavoriteItemType.UNKNOWN)

continue;

Button button = typeFields.get(eachType);

button.setSelection(selectedTypes.contains(eachType));

}

}

Override the configureShell()

method to set the dialog title: protected void configureShell(Shell newShell) {

super.configureShell(newShell);

newShell.setText("Favorites View Filter Options");

}

Finally, add accessor methods for clients to extract the settings specified by the user when the dialog was opened: public String getNamePattern() {

return namePattern;

} public String getLocationPattern() {

return locationPattern;

} public FavoriteItemType[] getSelectedTypes() {

return selectedTypes

.toArray(new FavoriteItemType[selectedTypes.size()]);

}

The filter action (see

FavoritesViewFilterAction

in Section 7.3.4,

Pull-down menu, on page 319) must be modified to fill the dialog with the current filter settings, open the dialog, and process the specified filter settings

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

454 CHAPTER 11 • Dialogs and Wizards

if the user closes the dialog using the

OK

button. If the dialog is closed using the

Cancel

button or any other way besides the

OK

button, the changes are discarded, per standard dialog operation guidelines. The type and location view filters referenced in the following code are left as an exercise for the reader.

public void run() {

FavoritesFilterDialog dialog =

new FavoritesFilterDialog(

shell,

nameFilter.getPattern(),

typeFilter.getTypes(),

locationFilter.getPattern());

if (dialog.open() != InputDialog.OK)

return;

nameFilter.setPattern(dialog.getNamePattern());

locationFilter.setPattern(dialog.getLocationPattern());

typeFilter.setTypes(dialog.getSelectedTypes());

}

Getting the preceding run()

method to compile involves adding a new

FavoritesViewLocationFilter

and

FavoritesViewTypeFilter

similar to the existing

FavoritesViewNameFilter

. When these changes are complete, the filter dialog presents the filter settings to the user when the

Filter…

menu item is selected (see Figure 11–3).

Figure 11–3

New Favorites View Filter Options dialog.

11.1.9

Details dialog

One of the RFRS criteria includes identifying the plug-in and plug-in creator when reporting problems to the user. In other words, whenever the application needs to report an error message or exception to the user, the plug-in’s unique identifier, version, and creator must be visible in the dialog. The org.eclipse.jface.dialogs.ErrorDialog

can display this information by setting the

ErrorSupportProvider

:

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 455

Policy.setErrorSupportProvider(new ErrorSupportProvider() {

public Control createSupportArea(Composite parent, IStatus status) {

... create controls to display the provider and exception info ...

}

}

Unfortunately this is a global setting and thus only applicable if you are building your own application based upon the Eclipse RCP framework. If you are providing functionality that enhances an existing application, then you must create your own

ExceptionDetailsDialog

(see Figure 11–4) that displays the exception and necessary product information in a details section that is shown or hidden using a

Details

button as required by RFRS standards.

Figure 11–4

Details dialog with details hidden.

When the

Details

button is pressed, the dialog resizes itself to show additional information (see Figure 11–5).

Figure 11–5

Details dialog with details showing.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

456 CHAPTER 11 • Dialogs and Wizards

The

ExceptionDetailsDialog behavior.

class implements this expanding details public class ExceptionDetailsDialog extends AbstractDetailsDialog {

private final Object details;

private final Bundle bundle;

public ExceptionDetailsDialog(Shell parentShell, String title,

Image image, String message, Object details, Bundle bundle)

{

this(new SameShellProvider(parentShell), title, image, message,

details, bundle);

}

public ExceptionDetailsDialog(IShellProvider parentShell,

String title, Image image, String message, Object details,

Bundle bundle)

{

super(parentShell, getTitle(title, details), getImage(image,

details), getMessage(message, details));

this.details = details;

this.bundle = bundle;

}

There are several utility methods that build content based on information provided in constructor arguments. The getTitle()

method returns the title based on the provided title and details object.

public static String getTitle(String title, Object details) {

if (title != null)

return title;

if (details instanceof Throwable) {

Throwable e = (Throwable) details;

while (e instanceof InvocationTargetException)

e = ((InvocationTargetException) e).getTargetException();

String name = e.getClass().getName();

return name.substring(name.lastIndexOf('.') + 1);

}

return "Exception";

}

The getImage()

method returns the image based on the provided image and details object.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 457

public static Image getImage(Image image, Object details) {

if (image != null)

return image;

Display display = Display.getCurrent();

if (details instanceof IStatus) {

switch (((IStatus) details).getSeverity()) {

case IStatus.ERROR :

return display.getSystemImage(SWT.ICON_ERROR);

case IStatus.WARNING :

return display.getSystemImage(SWT.ICON_WARNING);

case IStatus.INFO :

return display.getSystemImage(SWT.ICON_INFORMATION);

case IStatus.OK :

return null;

}

}

return display.getSystemImage(SWT.ICON_ERROR);

}

The getMessage()

method and helper methods build up a message based on the message and details objects provided.

public static String getMessage(String message, Object details) {

if (details instanceof Throwable) {

Throwable e = (Throwable) details;

while (e instanceof InvocationTargetException)

e = ((InvocationTargetException) e).getTargetException();

if (message == null)

return e.toString();

return MessageFormat.format(

message, new Object[] { e.toString() });

}

if (details instanceof IStatus) {

String statusMessage = ((IStatus) details).getMessage();

if (message == null)

return statusMessage;

return MessageFormat.format(

message, new Object[] { statusMessage });

}

if (message != null)

return message;

return "An Exception occurred.";

} public static void appendException(PrintWriter writer, Throwable ex)

{

if (ex instanceof CoreException) {

appendStatus(writer, ((CoreException) ex).getStatus(), 0);

writer.println();

}

appendStackTrace(writer, ex);

if (ex instanceof InvocationTargetException)

appendException(writer, ((InvocationTargetException) ex)

.getTargetException());

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

458 CHAPTER 11 • Dialogs and Wizards

public static void appendStatus(

PrintWriter writer, IStatus status, int nesting

) {

for (int i = 0; i < nesting; i++)

writer.print(" ");

writer.println(status.getMessage());

IStatus[] children = status.getChildren();

for (int i = 0; i < children.length; i++)

appendStatus(writer, children[i], nesting + 1);

} public static void appendStackTrace(

PrintWriter writer, Throwable ex

) {

ex.printStackTrace(writer);

}

When the

Details

button is clicked, the superclass determines whether the details area needs to be shown or hidden and, as necessary, calls the createDetailsArea()

method to create the content for the details area.

protected Control createDetailsArea(Composite parent) {

// Create the details area.

Composite panel = new Composite(parent, SWT.NONE);

panel.setLayoutData(new GridData(GridData.FILL_BOTH));

GridLayout layout = new GridLayout();

layout.marginHeight = 0;

layout.marginWidth = 0;

panel.setLayout(layout);

// Create the details content.

createProductInfoArea(panel);

createDetailsViewer(panel);

return panel;

} protected Composite createProductInfoArea(Composite parent) {

// If no bundle specified, then nothing to display here.

if (bundle == null)

return null;

Composite composite = new Composite(parent, SWT.NULL);

composite.setLayoutData(new GridData());

GridLayout layout = new GridLayout();

layout.numColumns = 2;

layout.marginWidth = convertHorizontalDLUsToPixels(

IDialogConstants.HORIZONTAL_MARGIN);

composite.setLayout(layout);

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 459

Dictionary<?, ?> bundleHeaders = bundle.getHeaders();

String pluginId = bundle.getSymbolicName();

String pluginVendor =

(String) bundleHeaders.get("Bundle-Vendor");

String pluginName = (String) bundleHeaders.get("Bundle-Name");

String pluginVersion =

(String) bundleHeaders.get("Bundle-Version");

new Label(composite, SWT.NONE).setText("Provider:");

new Label(composite, SWT.NONE).setText(pluginVendor);

new Label(composite, SWT.NONE).setText("Plug-in Name:");

new Label(composite, SWT.NONE).setText(pluginName);

new Label(composite, SWT.NONE).setText("Plug-in ID:");

new Label(composite, SWT.NONE).setText(pluginId);

new Label(composite, SWT.NONE).setText("Version:");

new Label(composite, SWT.NONE).setText(pluginVersion);

return composite;

} protected Control createDetailsViewer(Composite parent) {

if (details == null)

return null;

Text text = new Text(parent, SWT.MULTI | SWT.READ_ONLY

| SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);

text.setLayoutData(new GridData(GridData.FILL_BOTH));

// Create the content.

StringWriter writer = new StringWriter(1000);

if (details instanceof Throwable)

appendException(new PrintWriter(writer), (Throwable) details);

else if (details instanceof IStatus)

appendStatus(new PrintWriter(writer), (IStatus) details, 0);

text.setText(writer.toString());

return text;

}

The

ExceptionDetailsDialog

class is built on top of the more generic

AbstractDetailsDialog

class. This abstract dialog has a details section that can be shown or hidden by the user but subclasses are responsible for providing the content of the details section.

public abstract class AbstractDetailsDialog extends Dialog

{

private final String title;

private final String message;

private final Image image;

public AbstractDetailsDialog(Shell parentShell, String title,

Image image, String message)

{

this(new SameShellProvider(parentShell),title,image,message);

}

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

460 CHAPTER 11 • Dialogs and Wizards

public AbstractDetailsDialog(IShellProvider parentShell,

String title, Image image, String message)

{

super(parentShell);

this.title = title;

this.image = image;

this.message = message;

setShellStyle(SWT.DIALOG_TRIM | SWT.RESIZE

| SWT.APPLICATION_MODAL);

}

The configureShell()

method is responsible for setting the title: protected void configureShell(Shell shell) {

super.configureShell(shell);

if (title != null)

shell.setText(title);

}

The createDialogArea()

method creates and returns the contents of the upper part of this dialog (above the button bar). This includes an image, if specified, and a message.

protected Control createDialogArea(Composite parent) {

Composite composite = (Composite) super.createDialogArea(parent);

composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

if (image != null) {

((GridLayout) composite.getLayout()).numColumns = 2;

Label label = new Label(composite, 0);

image.setBackground(label.getBackground());

label.setImage(image);

label.setLayoutData(new GridData(

GridData.HORIZONTAL_ALIGN_CENTER

| GridData.VERTICAL_ALIGN_BEGINNING));

}

Label label = new Label(composite, SWT.WRAP);

if (message != null)

label.setText(message);

GridData data = new GridData(GridData.FILL_HORIZONTAL

| GridData.VERTICAL_ALIGN_CENTER);

data.widthHint = convertHorizontalDLUsToPixels(

IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH);

label.setLayoutData(data);

label.setFont(parent.getFont());

return composite;

}

Override the createButtonsForButtonBar()

method to create

OK

and

Details

buttons.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 461

private Button detailsButton; protected void createButtonsForButtonBar(Composite parent) {

createButton(parent, IDialogConstants.OK_ID,

IDialogConstants.OK_LABEL, false);

detailsButton = createButton(parent, IDialogConstants.DETAILS_ID,

IDialogConstants.SHOW_DETAILS_LABEL, false);

}

The buttonPressed()

method is called when either the

OK

or

Details

buttons is pressed. Override this method to alternately show or hide the details area if the

Details

button is pressed.

private Control detailsArea; private Point cachedWindowSize; protected void buttonPressed(int id) {

if (id == IDialogConstants.DETAILS_ID)

toggleDetailsArea();

else

super.buttonPressed(id);

} protected void toggleDetailsArea() {

Point oldWindowSize = getShell().getSize();

Point newWindowSize = cachedWindowSize;

cachedWindowSize = oldWindowSize;

// Show the details area.

if (detailsArea == null) {

detailsArea = createDetailsArea((Composite) getContents());

detailsButton.setText(IDialogConstants.HIDE_DETAILS_LABEL);

}

// Hide the details area.

else {

detailsArea.dispose();

detailsArea = null;

detailsButton.setText(IDialogConstants.SHOW_DETAILS_LABEL);

}

/*

* Must be sure to call

* getContents().computeSize(SWT.DEFAULT, SWT.DEFAULT)

* before calling

* getShell().setSize(newWindowSize)

* since controls have been added or removed.

*/

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

462 CHAPTER 11 • Dialogs and Wizards

// Compute the new window size.

Point oldSize = getContents().getSize();

Point newSize = getContents().computeSize(

SWT.DEFAULT, SWT.DEFAULT);

if (newWindowSize == null)

newWindowSize = new Point(oldWindowSize.x, oldWindowSize.y

+ (newSize.y - oldSize.y));

// Crop new window size to screen.

Point windowLoc = getShell().getLocation();

Rectangle screenArea =

getContents().getDisplay().getClientArea();

if (newWindowSize.y > screenArea.height

- (windowLoc.y - screenArea.y))

newWindowSize.y = screenArea.height

- (windowLoc.y - screenArea.y);

getShell().setSize(newWindowSize);

((Composite) getContents()).layout();

}

Finally, subclasses must implement createDetailsArea()

to provide content for the area of the dialog made visible when the

Details

button is clicked.

protected abstract Control createDetailsArea(Composite parent);

11.1.10

Opening a dialog—finding a parent shell

When constructing a new dialog, you need to know either the parent shell or an object that can provide a parent shell (an object that has a getShell() method or implements the

IShellProvider

interface). You can specify null for the parent shell, but this will prevent proper association of the dialog with its parent; if the dialog is modal as many dialogs are, then specifying the correct parent shell or shell provider will prevent the user from being able to activate the parent window before closing the dialog. So the question becomes:

How do you obtain the parent shell?

IHandler

(see example code in Section 6.3.1, Creating a new IHandler, on page 237)—If you have a handler, then you can obtain the active shell using a HandlerUtil method and the event argument.

public Object execute(ExecutionEvent event)

throws ExecutionException {

Shell parentShell = HandlerUtil.getActiveShell(event);

MyDialog dialog = new MyDialog(

parentShell

, ...);

... etc ...

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.1

Dialogs 463

IWorkbenchWindowActionDelegate

(see example code in Section 6.6.6,

Creating an action delegate, on page 249)—If you have an action delegate, then Eclipse provides the workbench window from which a shell can be obtained. Immediately after the action delegate is instantiated, Eclipse calls the init()

method with the workbench window as the argument. Cache this window and pass the window’s shell as an argument when constructing your dialog: private IWorkbenchWindow window; public void init(IWorkbenchWindow window) {

this.window = window;

} public void run(IAction action) {

Shell parentShell = window.getShell();

MyDialog dialog = new MyDialog(parentShell, ...);

... etc ...

}

IObjectActionDelegate

(see Section 6.7.3, IObjectActionDelegate, on page 266)—If you have an action in a context menu, Eclipse provides the target part from which a shell provider can be obtained. Before the run() method is called, Eclipse calls setActivePart()

with the target part. Cache this part and pass the site containing the part as an argument when constructing your dialog.

private IWorkbenchPart targetPart; public void setActivePart(IAction action, IWorkbenchPart targetPart)

{

this.targetPart = targetPart;

} public void run(IAction action) {

IWorkbenchPartSite site = targetPart.getSite();

MyDialog dialog = new MyDialog(site, ...);

... etc ...

}

IViewPart or

IEditorPart

(see Section 7.2, View Part, on page 293 or

Section 8.2, Editor Part, on page 358)—If you have a view or editor, then, similar to the preceding code, you can obtain a shell provider:

IShellProvider shellProvider = viewOrEditor.getSite();

PlatformUI

The platform UI provides the workbench window from which a shell can be obtained.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

464 CHAPTER 11 • Dialogs and Wizards

Shell parentShell =

PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();

Display

(see Section 4.2.5.1, Display, on page 148)—If all else fails, you can obtain the shell of the active window from

Display

.

Shell parentShell = Display.getDefault().getActiveShell();

11.2

Wizards

org.eclipse.jface.wizard.WizardDialog

is a specialized subclass of

Dialog

(see Figure 11–6) that is used when a modal operation requires a particular sequence for its information collection or when a single screen has too many fields. Wizards have a title area along the top; a content area in the middle showing the wizard pages; a progress bar as needed; and

Help

,

Next

,

Back

,

Finish

, and

Cancel

buttons (or some subset) along the bottom (see

Figure 11–7). The title area contains the wizard’s title, description, an optional image, and an error, warning, or informational message as required.

Figure 11–6

Wizard class hierarchy.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

11.2

Wizards

5HWXUQWR7DEOHRI&RQWHQWV

465

Figure 11–7

Default wizard dialog structure.

11.2.1

IWizard

Rather than subclass

WizardDialog

, you should subclass org.eclipse.

jface.wizard.Wizard

, which implements the org.eclipse.jface.

wizard.IWizard

interface for use with

WizardDialog

. The

WizardDialog uses the

IWizard

interface to obtain the pages to be displayed and to notify the wizard of user interaction. The concrete wizard class provides much of the

IWizard

behavior, allowing you to focus on a subset of the

IWizard

interface.

The wizard’s task is to create and initialize the pages it contains, handle any special customized flow and information between pages, and execute the operation when the

Finish

button is pressed.

addPages()

—Subclasses should override this method to add the appropriate pages by calling addPage()

.

canFinish()

—Returns whether this wizard could be finished without further user interaction. Typically, this is used by the wizard container to enable or disable the

Finish

button.

createPageControls(Composite)

—Creates this wizard’s controls in the given parent control.

dispose()

—Cleans up any native resources, such as images, clipboard, and so on, that were created by this class. This follows the

if you create it, you destroy it

theme that runs throughout Eclipse.

getContainer()

—Returns the wizard container in which this wizard is being displayed.

getDefaultPageImage()

—Returns the default page image for this wizard.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

466 CHAPTER 11 • Dialogs and Wizards

getDialogSettings()

—Returns the dialog settings for this wizard page.

getNextPage(IWizardPage)

—Returns the wizard page to be shown after the specified wizard page, or null if none. The default implementation shows pages in the order in which they were added to the wizard, so subclasses need only to override this method to implement a custom page flow.

getPreviousPage(IWizardPage)

—Returns the wizard page to be shown before the specified wizard page, or null

if none. The default implementation shows pages in the order in which they were added to the wizard, so subclasses need only to override this method to implement a custom page flow.

getStartingPage()

—Answers the first page to be displayed in the wizard. The default implementation answers the first wizard page added to the wizard, so subclasses need only to override this method if the starting page is not the first page added.

performCancel()

—Called by the wizard container if the wizard is canceled. Subclasses need only to override this method to provide any custom cancel processing. Return true if the wizard container can be closed, or false if it should remain open.

performFinish()

—Called by the wizard container when the

Finish

button is pressed. Subclasses should override this method to perform the wizard operation and return true to indicate that the wizard container should be closed, or false if it should remain open.

setDefaultPageImageDescriptor(ImageDescriptor)

—Sets the image displayed in the wizard’s title area if the current wizard page does not specify an image.

setHelpAvailable(boolean)

—Sets whether help is available and whether the

Help

button is visible.

setNeedsProgressMonitor(boolean)

—Sets whether this wizard needs a progress monitor. If true

, then space is reserved below the page area and above the buttons for a progress bar and progress message to be displayed (see Section 9.4, Progress Monitor, on page 415).

setTitleBarColor(RGB)

—Sets the color of the title area.

setWindowTitle(String)

—Sets the window title.

(FOLSVH3OXJLQV7KLUG(GLWLRQ

3DJH

5HWXUQWR7DEOHRI&RQWHQWV

11.2

Wizards 467

11.2.2

IWizardPage

Wizards use the org.eclipse.jface.wizard.IWizardPage

interface to communicate with the pages they contain. Typically, you will subclass the org.eclipse.jface.wizard.WizardPage

class, accessing and overriding the following methods, rather than implementing the

IWizardPage

interface.

The wizard page’s task is to present a page of information to the user, validate any information entered by the user on that page, and provide accessors for the wizard to gather the information entered. createControl(Composite)

—Creates the controls comprising this wizard page.

dispose()

—Cleans up any native resources, such as images, clipboard, and so on, that were created by this class. This follows the

if you create it, you destroy it

theme that runs throughout Eclipse.

getContainer()

—Returns the wizard container for this wizard page.

getDialogSettings()

—Returns the dialog settings for this wizard page.

getWizard()

—Returns the wizard that hosts this wizard page.

setDescription(String)

—Sets the descriptive text appearing in the wizard’s title area.

setErrorMessage(String)

—Sets or cl