261 103 5MB
Polish Pages 312 [311] Year 2014
Tytuł oryginału: Eclipse 4 Plug-in Development by Example: Beginner's Guide Tłumaczenie: Rafał Jońca ISBN: 978-83-246-8757-2 Copyright © Packt Publishing 2013. First published in the English language under the title „Eclipse 4 Plug-in Development by Example: Beginner's Guide”. Polish edition copyright © 2014 by Helion S.A. All rights reserved. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/eclip4_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland.
Poleć książkę na Facebook.com Kup w wersji papierowej Oceń książkę
Księgarnia internetowa Lubię to! » Nasza społeczność
Spis treści Przedmowa
15
Rozdział 1. Tworzenie pierwszej wtyczki
21
Przygotowanie środowiska Kroki do wykonania — konfiguracja środowiska Eclipse SDK Tworzenie pierwszej wtyczki Kroki do wykonania — tworzenie wtyczki Quiz — przestrzenie nazw i wtyczki Eclipse Uruchomienie wtyczki Kroki do wykonania — uruchomienie Eclipse z poziomu Eclipse Quiz — uruchamianie Eclipse Sprawdź się — modyfikacja wtyczki Debugowanie wtyczki Kroki do wykonania — debugowanie wtyczki Kroki do wykonania — aktualizacja kodu w debuggerze Debugowanie z filtrami kroków Kroki do wykonania — ustawienie filtru kroków Korzystanie z różnych rodzajów punktów wstrzymania Kroki do wykonania — wstrzymanie przy wejściu do metody lub wyjściu z niej Warunkowe punkty wstrzymania Kroki do wykonania — ustawienie warunkowego punktu wstrzymania Wstrzymanie działania po wystąpieniu wyjątku Kroki do wykonania — wyłapywanie wyjątków Kroki do wykonania — obserwacja zmiennych i wyrażeń Quiz — debugowanie Sprawdź się — korzystanie z punktów wstrzymania Podsumowanie
Rozdział 2. Tworzenie widoków w SWT Tworzenie widoków i widgetów Kroki do wykonania — tworzenie widoku Kroki do wykonania — rysowanie własnego widoku Kroki do wykonania — rysowanie wskazówki sekund
21 22 25 25 28 28 28 31 31 31 31 34 35 35 37 37 38 39 40 40 43 45 45 46
47 48 48 50 53
Spis treści
Kroki do wykonania — animacja wskazówki sekund Kroki do wykonania — uruchomienie w wątku interfejsu użytkownika Kroki do wykonania — tworzenie widgetu wielokrotnego użytku Kroki do wykonania — korzystanie z układu graficznego widoku Quiz — działanie widoków Sprawdź się — wskazówki minut i godzin Zarządzanie zasobami Kroki do wykonania — więcej kolorów Kroki do wykonania — znajdowanie wycieku Kroki do wykonania — zatykanie wycieku Quiz — działanie zasobów Sprawdź się — rozbudowa widgetu zegara Interakcja z użytkownikiem Kroki do wykonania — uzyskiwanie aktywności Kroki do wykonania — reakcja na działania użytkownika Quiz — działanie widgetów Sprawdź się — aktualizacja widgetu zegara Korzystanie z innych widgetów SWT Kroki do wykonania — dodanie elementów do zasobnika Kroki do wykonania — reakcja na akcje użytkownika Kroki do wykonania — obiekty modalne i inne efekty Kroki do wykonania — grupy i zakładki Quiz — korzystanie z SWT Sprawdź się — rozbudowa widoku stref czasowych Podsumowanie
Rozdział 3. Tworzenie widoków w JFace Dlaczego JFace? Tworzenie widoków TreeViewer Kroki do wykonania — tworzenie obiektu TreeViewer Kroki do wykonania — JFace i obrazy Kroki do wykonania — style w dostawcy etykiet Quiz — podstawy JFace Sprawdź się — dodanie obrazów dla regionów Sortowanie i filtracja Kroki do wykonania — sortowanie elementów w widoku Kroki do wykonania — filtrowanie elementów w widoku Quiz — sortowanie i filtracja Sprawdź się — rozwijanie gałęzi i filtracja Interakcje i właściwości Kroki do wykonania — dodanie procedury obsługi podwójnego kliknięcia Kroki do wykonania — wyświetlanie właściwości Quiz — działanie właściwości Dane tabelaryczne Kroki do wykonania — przeglądanie stref czasowych w tabeli Kroki do wykonania — synchronizacja wyboru Quiz — działanie tabel Podsumowanie
4
54 55 56 58 61 61 61 62 63 65 67 67 67 67 69 70 70 71 71 73 74 76 82 82 82
83 83 84 84 88 91 93 93 93 94 95 97 97 98 98 101 105 105 105 109 111 112
Spis treści
Rozdział 4. Interakcja z użytkownikiem Tworzenie akcji, poleceń i procedur obsługi Kroki do wykonania — dodanie menu kontekstowego Kroki do wykonania — tworzenie poleceń i procedur obsługi Kroki do wykonania — powiązanie poleceń ze skrótami Kroki do wykonania — zmiana kontekstu Kroki do wykonania — włączanie i wyłączanie elementów menu Kroki do wykonania — wielokrotne użycie wyrażeń Kroki do wykonania — dodanie poleceń do menu kontekstowego Sprawdź się — wykorzystanie menu i pasków narzędziowych Quiz — działanie menu Zadania i paski postępu Kroki do wykonania — uruchamianie operacji działających w tle Sprawdź się — użycie zadania UIJob Kroki do wykonania — raportowanie postępu prac Kroki do wykonania — sprawdzanie anulowania zadania Kroki do wykonania — podzadania i ich monitorowanie Kroki do wykonania — użycie monitorów i podmonitorów typu null Kroki do wykonania — ustawienie właściwości klasy Job Sprawdź się — wyświetlanie zadania w pasku systemowym Quiz — korzystanie z zadań Zgłaszanie błędów Kroki do wykonania — wyświetlanie błędów Quiz — zgłaszanie błędów Podsumowanie
Rozdział 5. Przechowywanie preferencji i ustawień Przechowywanie preferencji Kroki do wykonania — trwałość wartości Kroki do wykonania — utworzenie strony preferencji Kroki do wykonania — tworzenie komunikatów ostrzeżeń i błędów Kroki do wykonania — wybór elementu z listy Kroki do wykonania — dodanie siatki Kroki do wykonania — lokalizacja strony preferencji Kroki do wykonania — użycie innych edytorów pól Kroki do wykonania — dodanie słów kluczowych Kroki do wykonania — użycie IEclipsePreferences Sprawdź się — tłumaczenie na inne języki Użycie IMemento i DialogSettings Kroki do wykonania — dodanie IMemento do widoku stref czasowych Kroki do wykonania — użycie DialogSettings Quiz — działanie preferencji Podsumowanie
113 113 114 115 117 119 121 123 124 126 127 127 127 129 129 131 131 133 135 138 138 138 138 141 142
143 143 144 145 146 147 149 150 151 153 154 155 155 156 157 159 159
5
Spis treści
Rozdział 6. Korzystanie z zasobów Korzystanie z przestrzeni roboczych i zasobów Kroki do wykonania — tworzenie edytora Kroki do wykonania — tworzenie parsera Kroki do wykonania — tworzenie systemu budującego Kroki do wykonania — iteracja przez zasoby Kroki do wykonania — tworzenie zasobów Kroki do wykonania — implementacja budowania inkrementacyjnego Kroki do wykonania — obsługa usunięcia Sprawdź się — rozbudowa mechanizmu budowania Użycie charakterów projektu Kroki do wykonania — tworzenie charakteru projektu Sprawdź się — ukrywanie charakteru Użycie znaczników Kroki do wykonania — znacznik błędu, gdy plik jest pusty Kroki do wykonania — rejestracja rodzaju znacznika Sprawdź się — prawidłowe działanie, gdy plik jest naprawdę pusty Quiz — obsługa zasobów, procesu budowania i znaczników Podsumowanie
Rozdział 7. Model Eclipse 4 Korzystanie z modelu Eclipse 4 Kroki do wykonania — instalacja narzędzi Eclipse 4 Kroki do wykonania — tworzenie aplikacji Eclipse 4 Kroki do wykonania — tworzenie części Kroki do wykonania — obstylowanie interfejsu użytkownika za pomocą CSS Sprawdź się — użycie menedżera tematów Usługi i konteksty Kroki do wykonania — dodanie logowania do dziennika zdarzeń Kroki do wykonania — pobranie okna Kroki do wykonania — uzyskanie zaznaczenia Kroki do wykonania — korzystanie ze zdarzeń Kroki do wykonania — obliczanie wartości na żądanie Kroki do wykonania — użycie preferencji Kroki do wykonania — interakcja z interfejsem użytkownika Korzystanie z poleceń, procedur obsługi i elementów menu Kroki do wykonania — powiązanie menu z poleceniem i procedurą obsługi Kroki do wykonania — przekazywanie parametrów polecenia Kroki do wykonania — utworzenie bezpośredniego menu i skrótów klawiszowych Kroki do wykonania — utworzenie menu kontekstowego i menu widoku Tworzenie własnych klas do wstrzykiwania Kroki do wykonania — tworzenie prostej usługi Kroki do wykonania — wstrzykiwanie podtypów Sprawdź się — użycie mostka narzędziowego Quiz — działanie Eclipse 4 Podsumowanie
6
161 161 162 164 165 168 170 172 172 174 175 175 178 178 179 180 181 182 182
183 183 184 186 190 194 199 199 199 201 202 204 207 209 211 213 213 215 218 220 222 222 223 224 224 225
Spis treści
Rozdział 8. Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów Grupowanie wtyczek jako funkcjonalności Kroki do wykonania — tworzenie funkcjonalności Kroki do wykonania — eksport funkcjonalności Kroki do wykonania — instalacja funkcjonalności Kroki do wykonania — kategoryzacja witryny aktualizacji Kroki do wykonania — zależność od innych funkcjonalności Kroki do wykonania — tworzenie oznaczeń funkcjonalności Sprawdź się — zdalna publikacja zawartości Budowanie aplikacji i produktów Kroki do wykonania — wykonanie aplikacji bez interfejsu użytkownika Kroki do wykonania — tworzenie produktu Sprawdź się — tworzenie produktu bazującego na funkcjonalności Quiz — sposób działania funkcjonalności, aplikacji i produktów Podsumowanie
Rozdział 9. Automatyczne testy wtyczek Użycie frameworku JUnit do testów zautomatyzowanych Kroki do wykonania — wykonanie prostego przypadku testowego JUnit Kroki do wykonania — wykonanie testu wtyczki Wykorzystanie SWTBot do testów interfejsu graficznego Kroki do wykonania — tworzenie testów SWTBot Kroki do wykonania — korzystanie z menu Sprawdź się — korzystanie z zasobów Korzystanie z SWTBot Kroki do wykonania — ukrywanie ekranu powitalnego Kroki do wykonania — unikanie błędów wykonania z SWTBot Korzystanie z widoków Kroki do wykonania — wyświetlenie widoków Kroki do wykonania — przesłuchiwanie widoków Interakcja z interfejsem użytkownika Kroki do wykonania — pobranie wartości z interfejsu użytkownika Kroki do wykonania — oczekiwanie na warunek Sprawdź się — sterowanie kreatorem nowej klasy Quiz — działanie SWTBot Podsumowanie
Rozdział 10. Automatyczne budowanie przy użyciu Tycho Wykorzystanie Maven i Tycho do budowania wtyczek Eclipse Kroki do wykonania — instalacja Maven Kroki do wykonania — budowanie za pomocą Tycho Sprawdź się — korzystanie z platform docelowych Budowanie funkcjonalności i witryn aktualizacji za pomocą Tycho Kroki do wykonania — tworzenie projektu nadrzędnego Kroki do wykonania — budowanie funkcjonalności Kroki do wykonania — budowanie witryny aktualizacji
227 228 228 230 232 234 237 239 241 241 242 245 249 249 249
251 251 252 253 254 254 256 258 258 258 259 260 260 261 262 262 263 265 265 265
267 267 268 270 272 273 273 275 276
7
Spis treści
Kroki do wykonania — budowanie produktu Sprawdź się — zależność od komponentów Maven Testy i publikacja Kroki do wykonania — uruchomienie testów automatycznych Kroki do wykonania — zmiana numeru wersji Sprawdź się — włączenie budowania dla pozostałych wtyczek Podpisywanie witryn aktualizacji Kroki do wykonania — tworzenie certyfikatu podpisanego przez samego siebie Kroki do wykonania — podpisywanie wtyczek Kroki do wykonania — serwer z witryną aktualizacji Quiz — automatyczne budowanie i witryny aktualizacji Podsumowanie
Dodatek A Odpowiedzi do quizów Rozdział 1. Tworzenie pierwszej wtyczki Rozdział 2. Tworzenie widoków w SWT Rozdział 3. Tworzenie widoków w JFace Rozdział 4. Interakcja z użytkownikiem Rozdział 5. Przechowywanie preferencji i ustawień Rozdział 6. Korzystanie z zasobów Rozdział 7. Model Eclipse 4 Rozdział 8. Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów Rozdział 9. Automatyczne testy wtyczek Rozdział 10. Automatyczne budowanie przy użyciu Tycho
Skorowidz
8
278 282 283 283 286 288 288 288 290 292 293 293
295 295 296 298 299 300 301 301 303 303 304
305
O autorze Dr Alex Blewitt tworzy aplikacje Javy od wydania wersji 1.0 w roku 1996. Korzysta z platformy Eclipse od momentu jej pierwszego wydania jako elementu zestawu WebSphere Studio firmy IBM. Co więcej, niektóre z rozszerzeń przeniósł z VisualAge for Java do WebSphere Studio (Eclipse) jako część pracy doktorskiej na temat automatycznej weryfikacji wzorców projektowych. Włączył się w społeczność open source jako tester wraz z wydaniem Eclipse 2.1 dla systemu Mac OS X. Później dalej wspierał społeczność jako redaktor EclipseZone i został finalistą Eclipse Ambassador w 2007 roku. W ostatnim czasie Alex udzielał się na łamach InfoQ, poruszając tematy związane z Javą, a szczególnie z Eclipse i OSGi. W 2011 roku był prelegentem OSGi Community Event; wygłosił mowę przewodnią na temat przeszłości, teraźniejszości i przyszłości OSGi. Omówienie najnowszych wydań platformy Eclipse i jej projektów, a także wywiady wideo z niektórymi głównymi uczestnikami projektu Eclipse można znaleźć na InfoQ. Za swój trud w 2012 roku został nominowany do nagrody Eclipse Top Contributor i ją otrzymał. Obecnie Alex pracuje w banku inwestycyjnym w Londynie. We współpracy z Bandlem Limited wydał również kilka aplikacji w Apple AppStore. Jeśli nie zajmuje się technologią i pogoda jest dobra, lubi wzbić się w powietrze z pobliskiego lotniska Cranfield. Regularnie udziela się na własnym blogu http://alblue.bandlem.com. Umieszcza też regularnie tweety na Twitterze i App.net jako @alblue.
Eclipse 4. Programowanie wtyczek na przykładach
10
Podziękowania Chciałbym podziękować mojej żonie Amy za wsparcie w trakcie prac nad książką (za tolerowanie siedzenia do nocy i weekendów spędzonych na pisaniu), a także za więcej niż dekadę wspólnego życia. Dziękuję również moim rodzicom, Derekowi i Ann, za zaszczepienie niezależności i wiary we własne możliwości, co pozwoliło mi znaleźć się w wielu różnych miejscach. Mam nadzieję, że podobny poziom pewności siebie i wiary uda mi się zaszczepić moim dzieciom — Samowi i Holly. Szczególne podziękowania należą się Ann Ford, która przesłała szczegółowe komentarze na temat każdego rozdziału i zawartych w nim ćwiczeń. Bez jej gorliwości i spostrzegawczości książka zawierałaby znacznie więcej błędów. Wszystkie błędy, które mogą się jeszcze pojawić, są z mojej winy. Dziękuję również recenzentom wczesnych wersji pozostałych rozdziałów: Thomasowi Fletcherowi i Jeffowi Maury emu za komentarze i sugestie. Na nieco bardziej zaawansowanym etapie prac na książką miałem dużo szczęścia i otrzymałem wnikliwe informacje zwrotne od Paula Webstera i Larsa Vogela, którzy są mocno zaangażowani w prace nad platformą Eclipse 4. Ich komentarz dotyczący rozdziału o Eclipse 4 znacząco poprawił jego zawartość. Na końcu chciałbym podziękować OD, DJ i JC za ich wsparcie i sprawienie, że książka ta stała się możliwa do wydania.
Eclipse 4. Programowanie wtyczek na przykładach
12
O recenzentach Ann Ford jest doświadczoną programistką dodatków do Eclipse, która odpowiada za znaczącą część projektu Eclipse Technology Accessibility Tools Framework jako główna opiekunka tegoż projektu. Ponad trzydzieści lat pracowała w IBM, gdzie zajmowała się narzędziami i komponentami OS/2, DB2 i IBM JDK. Ma duże doświadczenie związane z użytecznością, dostępnością i tłumaczeniami. Obecnie specjalizuje się w projektowaniu i programowaniu graficznych interfejsów użytkownika dla aplikacji typu desktop i narzędzi korzystających z Java Swing, Eclipse SWT i JFace. Stara się zwracać uwagę na przyszłe wykorzystanie ich w aplikacjach mobilnych. Thomas Fletcher od ponad dziesięciu lat zajmuje się aplikacjami osadzanymi i czasu rzeczywistego. Często udziela się jako prelegent na konferencjach branżowych. Jest technicznym ekspertem i liderem w takich dziedzinach jak projektowanie i architektura systemów osadzanych, analiza wydajności czasu rzeczywistego, zarządzanie zasilaniem i wysoką dostępnością. Przed pracą w Crank Software Thomas kierował zespołem programistów narzędziowych w QNX Software Systems. Był głównym architektem multimediów i liderem zespołu Core OS. Często wspomagał zespoły sprzedaży i marketingu jako osoba, która potrafi łączyć technologię z potrzebami klientów. Thomas jest aktywnym członkiem społeczności Eclipse i jedną z osób tworzących rewizje w projekcie C/C++ Development Tools (CDT). Reprezentował QNX na spotkaniach grup Eclipse Architecture i Multicore Association. Thomas ma tytuł magistra informatyki zdobyty na Uniwersytecie Carleton. Skupia się na instrumentacji i analizie wydajności systemów osadzanych. Uzyskał również tytuł licencjata z elektrotechniki na Uniwersytecie Victorii.
Eclipse 4. Programowanie wtyczek na przykładach
Jeff MAURY obecnie pracuje jako lider techniczny zespołu Javy w firmie SYSPERTEC, francuskiego ISV oferującego narzędzia integracyjne systemów typu mainframe. Przed SYSPERTEC w 1996 roku był współzałożycielem innego ISV o nazwie SCORT, który był prekursorem koncepcji serwerów aplikacji i oferował narzędzia integracyjne dla J2EE. Karierę rozpoczął w 1988 roku w MARBEN, francuskiej firmie integracyjnej specjalizującej się w protokołach telekomunikacyjnych. W MARBEN zaczynał jako programista aplikacji, a odchodził jako lider techniczny zespołu X.400 i strateg działu internetowego. Chciałby zadedykować swoją pracę Jeanowi-Pierre owi ANSARTowi, swojemu mentorowi, a także żonie Julii (dziękując za cierpliwość) i trzem synom — Robinsonowi, Paulowi i Ugowi.
14
Przedmowa Książka stanowi ogólny opis sposobu tworzenia dodatkowych rozszerzeń, czyli — inaczej mówiąc — wtyczek (plug-in) dla platformy Eclipse. By wykonać wszystkie przykłady prezentowane w tej książce, nie jest wymagane żadne dodatkowe doświadczenie, poza znajomością języka Java. Po przeczytaniu książki wykonanie od podstaw własnej wtyczki Eclipse, a także testów automatycznych dla niej, nie powinno nastręczać żadnych problemów.
Zawartość książki W rozdziale 1., „Tworzenie pierwszej wtyczki”, omówiony został sposób pobrania platformy Eclipse, przygotowania jej do tworzenia wtyczek, wykonania prostej wtyczki, a następnie jej uruchomienia i sprawdzenia. W rozdziale 2., „Tworzenie widoków w SWT”, objaśniono sposób tworzenia widoków z SWT, a także wykorzystanie niezależnych komponentów SWT, takich jak ikony przybornika systemowego oraz zarządzanie zasobami. W rozdziale 3., „Tworzenie widoków w JFace”, podano opis tworzenia widoków z JFace przy użyciu TableViewers i TreeViewers, a także objaśniono integrację z widokiem właściwości i interakcjami użytkownika. Rozdział 4., „Interakcja z użytkownikiem”, stanowi opis użycia poleceń, procedur obsługi i menu do interakcji z użytkownikiem. Omówiono w nim również API Jobs i Progress. W rozdziale 5., „Przechowywanie preferencji i ustawień”, zawarto informacje na temat trwałego zapamiętania preferencji, a także wyświetlania ich za pomocą stron właściwości.
Eclipse 4. Programowanie wtyczek na przykładach
W rozdziale 6., „Korzystanie z zasobów”, opisano sposób, w jaki należy wczytywać i tworzyć obiekty Resource, a także wyjaśniono sposoby użycia obiektów budujących i techniki przetwarzania automatycznego. W rozdziale 7., „Model Eclipse 4”, omówiono kluczowe różnice między modelami Eclipse 3.x i Eclipse 4.x, a także wyjaśniono przenoszenie istniejących treści do nowego modelu. W rozdziale 8., „Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów”, wyjaśnione zostało, jak można wykorzystać utworzone do tej pory wtyczki i połączyć je w funkcjonalności, opublikować je na witrynach aktualizacji, a także użyć mechanizmów aplikacji i produktów do utworzenia niezależnych bytów. W rozdziale 9., „Automatyczne testy wtyczek”, objaśniono sposób pisania testów automatycznych sprawdzających wtyczki Eclipse zarówno z komponentami interfejsu użytkownika, jak i bez tych komponentów. Rozdział 10., „Automatyczne budowanie przy użyciu Tycho”, to omówienie automatycznego budowania wtyczek, funkcjonalności, witryn aktualizacji, aplikacji i produktów Eclipse przy użyciu Maven Tycho.
Co jest potrzebne przy czytaniu książki? Aby wykonać przykłady prezentowane w książce, potrzebny będzie komputer z aktualnym systemem operacyjnym (może to być system Windows, Mac OS X lub Linux). Konieczna jest instalacja Javy; JDK 1.7 to obecnie wydana wersja, choć przedstawione w książce instrukcje powinny obowiązywać również dla nowszych wydań. Książka została sprawdzona przy użyciu Eclipse SDK (Classic i Standard) dla wydań Juno (4.2) i Kepler (4.3). Nowsze wersje Eclipse również mogą działać prawidłowo. Warto uważać z instalacją Eclipse dla programistów RCP i RAP, ponieważ może spowodować, że aplikacje prezentowane w rozdziałach 7. i 8. mogą nie działać prawidłowo. W rozdziale 1. omówiono wszystkie kroki niezbędne do przygotowania środowiska, w tym pobranie i instalację zarówno Eclipse, jak i Javy.
Do kogo kierowana jest książka? Książka kierowana jest przede wszystkim do programistów Javy zainteresowanych nauką tworzenia wtyczek, modułów, produktów i aplikacji na platformie Eclipse. Zaczyna się od opisu instalacji i użycia Eclipse oraz budowania i testowania wtyczek. Następnie opisane zostały różne rodzaje interfejsów użytkownika. Kończy się opisem tworzenia witryn aktualizacji i automatycznego testowania wtyczek.
16
Przedmowa
Książka może również okazać się pomocna osobom, które mają już pewne doświadczenie w tworzeniu wtyczek Eclipse i chciałyby się dowiedzieć, jak skonfigurować automatyczne budowanie przy użyciu Maven Tycho, które stało się standardem do budowania modułów Eclipse. Programiści Eclipse znający model dostępny w Eclipse 3.x, a zainteresowani poznaniem modelu Eclipse 4.x, znajdą tu opis i podsumowanie wszystkich zmian wprowadzonych w nowym modelu. Wystarczy, że zajrzą do rozdziału 7.
E4: Książka obejmuje zarówno modele Eclipse 3.x, jak i 4.x. Platforma Eclipse 4 zawiera mechanizmy zgodności wstecznej z API Eclipse 3.x. Gdy API starszej wersji będzie różne od API wersji 4.x, pojawi się ikona E4 informująca o wskazywaniu różnic. Pełne wyjaśnienie różnic zostanie omówione dopiero w rozdziale 7., więc uwagi E4 można pominąć przy pierwszym czytaniu książki.
Programując wtyczkę dla Eclipse IDE, warto rozważyć korzystanie z API Eclipse 3.x, ponieważ taka wtyczka będzie działała zarówno w starszych, jak i nowszych wersjach platformy. Podczas programowania dla aplikacji Eclipse RCP bez konieczności obsługiwania starszych wersji warto rozważyć bazowanie tylko na nowych API. Przyszłe wersje platformy Eclipse (4.4 Luna lub nowsze) być może umożliwią użycie części API Eclipse 4 w IDE.
Konwencje stosowane w książce W książce bardzo często będzie pojawiało się kilka nagłówków. Aby podać klarowne instrukcje dotyczące realizacji konkretnego zadania, użyty zostanie następujący nagłówek.
Kroki do wykonania — nagłówek 1. Krok 1. 2. Krok 2. 3. Krok 3. Czasem instrukcje wymagają dodatkowych wyjaśnień, by można je było w pełni rozumieć. W takiej sytuacji pojawi się następujący nagłówek.
Co się stało? Tekst znajdujący się po nagłówku wyjaśnia wykonane właśnie zadanie lub kroki. Książka zawiera również kilka innych pomocy, między innymi poniższe.
17
Eclipse 4. Programowanie wtyczek na przykładach
Quiz — nagłówek To krótkie pytania z możliwością zaznaczenia więcej niż jednej odpowiedzi, pomagające sprawdzić właściwe przyswojenie istotnych informacji.
Sprawdź się — nagłówek Te praktyczne wyzwania to zbiór pomysłów pozwalających poeksperymentować ze zdobytą wiedzą. W książce zastosowano kilka różnych stylów tekstu, by wyróżnić pewne informacje. Oto kilka z tych stylów wraz z wyjaśnieniem ich znaczenia. Kod w tekście wyróżniany jest czcionką o stałej szerokości, a foldery kursywą, na przykład: „Zauważ, że do usunięcia folderu Drush użyliśmy polecenia rm z systemów Unix zamiast polecenia del z systemu DOS”. Blok kodu ma postać taką, jak poniżej. # * Dostrojenie konfiguracji. # key_buffer = 16M key_buffer_size = 32M max_allowed_packet = 16M thread_stack = 512K thread_cache_size = 8 max_connections = 300
Jeśli konieczne będzie zwrócenie szczególnej uwagi na konkretny fragment kodu, zostanie on pogrubiony. # * Dostrojenie konfiguracji. # key_buffer = 16M key_buffer_size = 32M max_allowed_packet = 16M thread_stack = 512K thread_cache_size = 8 max_connections = 300
Wszystkie polecenia wprowadzane w wierszu poleceń mają postać taką, jak poniżej. cd /ProgramData/Propeople rm -r Drush git clone --branch master http://git.drupal.org/project/drush.git
18
Przedmowa
Nowe terminy lub istotne słowa będą pogrubione. Słowa, które pojawiają się na ekranie, stanowią część menu lub okna dialogowego, zostaną zapisane kursywą, na przykład: „Na ekranie Select Destination Location kliknij przycisk Next, by zaakceptować domyślną lokalizację docelową”. Ostrzeżenia lub istotne uwagi oraz wskazówki i sztuczki są zapisywane w ten sposób.
Pobranie przykładów dla książki Przykłady dotyczące książki są dostępne do pobrania pod adresem ftp://ftp.helion.pl/przyklady/ eclip4.zip.
Errata Choć ze wszystkich sił staramy się, by materiał zawarty w książce był prawidłowy, mogą pojawić się błędy. Jeśli znajdziesz w książce błąd — dotyczy to zarówno treści, jak i przykładów — będziemy bardzo wdzięczni za jego zgłoszenie. Zgłaszając błąd, pomożesz innym czytelnikom uniknąć frustracji, a my usuniemy błędy w następnym wydaniu. Erratę możesz zgłosić przy użyciu formularza dostępnego na stronie http://helion.pl/user/erraty/eclip4. Po sprawdzeniu zgłoszenia i jego akceptacji poprawka pojawi się na liście errat dotyczących książki na stronie internetowej wydawnictwa.
Piractwo Nieprzestrzeganie praw autorskich w internecie to stały problem. Wydawnictwo traktuje ochronę praw autorskich bardzo poważnie. Jeśli natkniesz się na nielegalne kopie wydawanych przez nas książek w dowolnej formie, zgłoś nam adres strony lub jej nazwę, byśmy mogli przedsięwziąć odpowiednie kroki zapobiegawcze. Zauważone naruszenie praw autorskich możesz zgłosić, używając formularza dostępnego pod adresem http://helion.pl/piracy.phtml. Dziękujemy za pomoc w ochronie autorów książek i pomoc w dostarczaniu treści wysokiej jakości.
19
Eclipse 4. Programowanie wtyczek na przykładach
20
1 Tworzenie pierwszej wtyczki Eclipse to wybitnie modułowa aplikacja składająca się z setek wtyczek, którą można rozbudowywać za pomocą dodatkowych rozszerzeń. Dodatkowe wtyczki tworzy się i testuje w środowisku PDE (Plug-in Development Environment).
W tym rozdziale: skonfigurujemy środowisko Eclipse pod kątem tworzenia wtyczek, utworzymy wtyczkę, wykorzystując nowy kreator wtyczek, uruchomimy nową instancję Eclipse z włączoną wtyczką, przetestujemy wtyczkę.
Przygotowanie środowiska Tworzenie dodatkowych wtyczek wymaga środowiska programistycznego Eclipse. Przykłady zawarte w książce były sprawdzane w wydaniach Juno (Eclipse 4.2) i Kepler (Eclipse 4.3). Użyj najnowszej dostępnej wersji. Wtyczki Eclipse najczęściej pisze się w języku Java. Choć nic nie stoi na przeszkodzie, by stosować dowolny inny język bazujący na maszynie wirtualnej Javy (taki jak na przykład Groovy lub Scala), wszystkie przykłady dotyczyć będą języka Java.
Eclipse 4. Programowanie wtyczek na przykładach
Na stronie pobierania Eclipse dostępne są różne wersje pakietów. Każdy z nich zawiera inne kombinacje modułów (wtyczek) dodatkowych. Książkę sprawdzono w następujących kombinacjach: Eclipse SDK pobrane z http://download.eclipse.org/eclipse/downloads/, Eclipse Classic i Eclipse Standard pobrane z http://www.eclipse.org/downloads/.
Wszystkie wymienione pakiety zawierają funkcjonalność Plug-in Development Environment (PDE), a także kod źródłowy, dokumentację i inne użyteczne funkcje. Nie należy korzystać z pakietów RCP i RAP, ponieważ mogą sprawić problemy w rozdziale 7. Można również zainstalować funkcjonalność Eclipse PDE w istniejącej instancji Eclipse. W tym celu przejdź do menu Help i wybierz polecenie Install New Software. Wskaż kategorię General Purpose Tools z witryny aktualizacji. Funkcjonalność Plug-in Development Environment zawiera wszystkie elementy niezbędne do tworzenia nowych dodatków.
Kroki do wykonania — konfiguracja środowiska Eclipse SDK Eclipse to aplikacja bazująca na języku Java, więc wymaga instalacji środowiska Javy. Eclipse pobiera się w postaci skompresowanego archiwum, co nie wymaga osobnego kroku instalacyjnego. 1. Udaj się pod adres http://java.com i skorzystaj z zawartych tam instrukcji, by pobrać i zainstalować Javę. Java dystrybuowana jest w dwóch wersjach, 32- i 64-bitowej. Jeśli używany system operacyjny jest 32-bitowy, zainstaluj 32-bitowe JDK. W przeciwnym razie zainstaluj JDK w wersji 64-bitowej. 2. Uruchomienie w wierszu poleceń komendy java -version powinno zwrócić tekst podobny do poniższego. java version "1.7.0_10" Java(TM) SE Runtime Environment (build 1.7.0_10-b18) Java HotSpot(TM) 64-Bit Server VM (build 23.6-b04, mixed mode)
3. Przejdź pod adres http://www.eclipse.org/downloads/ i pobierz wydanie Eclipse Classic lub Eclipse Standard. 4. Pobierz wersję odpowiadającą wersji JDK. Po uruchomieniu komendy java -version powinieneś zobaczyć,
jeśli to wersja 32-bitowa, tekst: Java HotSpot(TM) Client VM
jeśli to wersja 64-bitowa, tekst: Java HotSpot(TM) 64-Bit Server VM
W systemie Linux Eclipse wymaga zainstalowania biblioteki GTK2. W większości dystrybucji systemu Linux zastosowano menedżera okien bazującego na GNOME, który zawiera biblioteki GTK 2.x.
22
Rozdział 1. • Tworzenie pierwszej wtyczki
5. W celu instalacji Eclipse pobierz i wydobądź zawartość archiwum w dowolnej, wygodnej lokalizacji. Ponieważ Eclipse jest dostarczane jako archiwum, nie wymaga uprawnień administratora. Nie należy uruchamiać środowiska z napędu sieciowego, bo może to powodować problemy z wydajnością. Warto mieć na uwadze, że Eclipse musi zapisywać dane w folderze, w którym jest zainstalowane, więc najlepiej zadbać o to od razu. Nie zaleca się instalacji w folderze /Applications lub C:\Program Files, kiedy jesteś zalogowany jako administrator systemu. 6. Uruchom Eclipse, klikając dwukrotnie ikonę lub też uruchamiając plik eclipse.exe (Windows), eclipse (Linux) lub Eclipse.app (OS X). 7. W trakcie uruchamiania pojawi się ekran początkowy.
8. Wybierz folder przestrzeni roboczej, w której będą przechowywane projekty, i kliknij przycisk OK.
9. Zamknij ekran powitalny, klikając znak na prawo od tekstu Welcome z zakładki. Ekran powitalny można otworzyć ponownie po wybraniu z menu polecenia Help/Welcome.
23
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Eclipse do działania wymaga maszyny wirtualnej Javy, więc pierwszym krokiem związanym z instalacją Eclipse jest upewnienie się, że posiadasz aktualną wersję Javy. Domyślnie Eclipse szuka instalacji Javy w ścieżce lub standardowych folderach. Możliwe jest wskazanie innej wersji Javy przez użycie w wierszu poleceń argumentu -vm. Jeśli ekran początkowy nie pojawi się, wersja Eclipse najprawdopodobniej nie jest zgodna z zainstalowanym JDK (na przykład 64-bitowe JDK i 32-bitowe Eclipse). Typowym błędem, który pojawia się w takiej sytuacji w trakcie uruchamiania, jest Unable to find companion launcher lub tajemniczy błąd informujący o braku biblioteki SWT. W wersji dla systemu Windows istnieje dodatkowy plik eclipsec.exe, który zapewnia wyświetlanie w konsoli dodatkowych komunikatów. Dzięki temu, gdy Eclipse nie chce się uruchomić, znacznie łatwiej znaleźć przyczynę. W pozostałych systemach operacyjnych wystarcza standardowy plik eclipse. Zarówno w eclipse.exe, jak i w eclipse obsługiwany jest argument -consolelog, który wyświetla bardziej szczegółowe informacje na temat problemów z uruchomieniem Eclipse. Przestrzeń robocza Eclipse to folder pełniący dwojaką rolę: stanowi domyślną lokalizację dla plików projektu i przechowuje folder .metadata, zawierający ustawienia Eclipse, preferencje użytkownika i inne informacje pomocnicze. Dziennik zdarzeń Eclipse znajduje się w pliku .metadata/.log.
24
Rozdział 1. • Tworzenie pierwszej wtyczki
Okno wskazania folderu roboczego zawiera opcję umożliwiającą ustawienie danego obszaru jako domyślnego. Po uruchomieniu Eclipse aktualny ekran roboczy zmienia się poleceniem File/Switch Workspace. Konkretny ekran roboczy można również wskazać w wierszu poleceń w trakcie uruchamiania Eclipse — wystarczy użyć argumentu -data. Ekran powitalny przydaje się początkującym użytkownikom. W pozostałych przypadkach warto go od razu zamknąć (zamiast minimalizować).
Tworzenie pierwszej wtyczki W tym zadaniu do utworzenia pierwszej wtyczki posłużymy się kreatorem.
Kroki do wykonania — tworzenie wtyczki W środowisku PDE (Plug-in Development Environment) każda wtyczka ma swój własny, indywidualny projekt. Projekt wtyczki rozpoczyna się, uruchamiając kreator New Project. Istnieje również możliwość konwersji istniejącego projektu na projekt wtyczki przez dodanie natury PDE i wymaganych plików (użyj polecenia Configure/Convert to plug-in project). 1. Aby utworzyć wtyczkę typu „Witaj, świecie”, użyj polecenia File/New/Project z menu.
25
Eclipse 4. Programowanie wtyczek na przykładach
2. Lista typów projektów może różnić się od przedstawionej na rysunku, ale powinna zawierać Plug-in Project z Eclipse Classic. Jeśli menu File/New nie zawiera odpowiedniego wpisu, użyj najpierw polecenia Window/Open Perspective/Other/ Plug-in Development. Po tej operacji w menu File/New powinny pojawić się nowe opcje. 3. Wybierz Plug-in Project i kliknij przycisk Next. Wypełnij zawartość okna dialogowego w następujący sposób.
W polu Project name wpisz com.packtpub.e4.hello.ui.
Włącz opcję Use default location.
Włącz opcję Create a Java project.
Z listy Eclipse Version wybierz 3.5 or greater.
4. Kliknij przycisk Next. Wypełnij właściwości wtyczki w następujący sposób.
W polu ID wpisz com.packtpub.e4.hello.ui.
W polu Version wpisz 1.0.0.qualifier.
W polu Name wpisz Witaj.
W polu Vendor wpisz Packtpub.
Listę Execution Environment pozostaw na wartości domyślne (na przykład JavaSE-1.6 lub JavaSE-1.7).
Włącz opcję Generate an Activator.
W polu Activator wpisz com.packtpub.e4.hello.ui.Activator.
Włącz opcję This plug-in will make contributions to the UI.
Pozostaw pole wyboru Rich client application na wartości No.
5. Kliknij przycisk Next, a pojawi się lista szablonów do wyboru.
Włącz opcję Create a plug-in using one of the templates.
Zaznacz szablon Hello World Command.
6. Kliknij przycisk Next, aby przeprowadzić zmiany w szablonie. Określ pakiet Javy, który domyślnie jest nazwą projektu. Określ nazwę klasy obsługi, która zawiera kod wywoływany dla konkretnej akcji.
Określ tekst komunikatu, który się pojawi.
7. Kliknij przycisk Finish, by wygenerować projekt. 8. Jeśli pojawi się okno dialogowe z prośbą, wybierz przycisk Yes, by wyświetlić perspektywę tworzenia wtyczki.
Co się stało? Utworzenie projektu wtyczki to pierwszy krok do zbudowania wtyczki dla Eclipse. Do utworzenia nowego projektu posłużył kreator New Plug-in Project oraz jeden z przykładowych szablonów.
26
Rozdział 1. • Tworzenie pierwszej wtyczki
We wtyczkach najczęściej stosowane są nazwy zgodne z odwróconym formatem domenowym, więc we wszystkich przykładach zaprezentowanych w książce użyty został przedrostek com.packtpub.e4. Pozwala to na wyróżnienie wśród innych wtyczek. Przykładowo Eclipse SDK zawiera ponad 440 różnych wtyczek, a te zrealizowane przez programistów Eclipse zaczynają się od org.eclipse. Stosuje się również dodatkową konwencję: jeśli wtyczka tworzy dodatkowe elementy interfejsu użytkownika (lub ich wymaga), w nazwie pojawia się fragment .ui.. Pozwala to wyróżnić taką wtyczkę od pozostałych, które nie stosują żadnego widocznego interfejsu. Z ponad 440 wtyczek Eclipse SDK jedynie 120 ma związek z interfejsem użytkownika; pozostałe go nie wymagają.
Projekt zawiera kilka plików, które zostały wygenerowane automatycznie na podstawie danych wprowadzonych w kreatorze. Oto kluczowe pliki. META-INF/MANIFEST.MF — manifest OSGi określający zależności wtyczki, jej
wersję i nazwę. Podwójne kliknięcie spowoduje otwarcie edytora, który wyświetli dane wprowadzone w kreatorze. Plik można też otworzyć w standardowym edytorze tekstu. W pliku manifestu stosuje się typowe dla Javy konwencje. Przykładowo kontynuacje są reprezentowane przez znaki nowego wiersza, po których następuje pojedynczy znak spacji, a plik musi się kończyć znakiem nowego wiersza. Warto wspomnieć, że maksymalna długość wiersza to 72 znaki, choć często jest to ignorowane. plugin.xml — plik plugin.xml deklaruje, w jaki sposób wtyczka rozszerza platformę
Eclipse. Nie wszystkie wtyczki potrzebują pliku plugin.xml; wtyczki bez interfejsu użytkownika najczęściej go nie wymagają. Sposoby rozszerzeń zostaną dokładniej omówione dalej w tej książce, ale domyślny projekt tworzy rozszerzenia związane z poleceniami, procedurami obsługi, dowiązaniami i menu. (Wybranie starszego szablonu „Witaj, świecie” występującego w wersji 3.7 lub starszej spowoduje użycie jedynie rozszerzenia actionSets). Etykiety tekstowe dla poleceń, akcji i menu są przedstawiane w pliku plugin.xml w sposób deklaratywny, a nie programowy; dzięki temu Eclipse może wyświetlić element menu przed załadowaniem lub wykonaniem jakiegokolwiek kodu. To jeden z powodów, dla którego Eclipse uruchamia się tak szybko. Jeśli nie trzeba wczytywać lub uruchamiać klas, można skupić się tylko na tym, co jest potrzebne w danym momencie, a klasę wczytać i uruchomić dopiero po wykonaniu akcji przez użytkownika. Akcje typu Swing zapewniają etykiety i podpowiedzi w sposób programowy, co skutkuje wolniejszą inicjalizacją interfejsu. build.properties — tego pliku używa się w PDE w trakcie prac nad kodem i w czasie
jego budowania. Najczęściej można go zignorować, ale gdy dodawane są zasoby niezbędne dla wtyczki (na przykład obrazy, pliki właściwości, treść HTML), trzeba umieścić w pliku odpowiedni zapis, bo inaczej zasób nie zostanie znaleziony.
27
Eclipse 4. Programowanie wtyczek na przykładach
Najłatwiej uczynić to z poziomu zakładki Build po otwarciu pliku build.properties, co pozwoli wyświetlić zawartość projektu w widoku przypominającym drzewo. Plik ten pochodzi z nieco już archaicznych czasów, gdy do budowania projektu używano narzędzia Ant. W bardziej nowoczesnych systemach budowania, takich jak Maven Tycho (omawiamy go w rozdziale 10.), jest w zasadzie bezużyteczny.
Quiz — przestrzenie nazw i wtyczki Eclipse P1. Czym jest przestrzeń nazw Eclipse? P2. Jaka jest konwencja nazewnictwa projektów wtyczek Eclipse? P3. Jakie są nazwy trzech kluczowych plików wtyczki Eclipse?
Uruchomienie wtyczki Do przetestowania wtyczki Eclipse stosuje się nową instancję Eclipse zawierającą skompilowaną i zainstalowaną wtyczkę, której dotyczy test.
Kroki do wykonania — uruchomienie Eclipse z poziomu Eclipse Eclipse może uruchomić nową instancję Eclipse po kliknięciu przycisku Run lub użyciu polecenia Run z menu. 1. Zaznacz projekt wtyczki w przestrzeni roboczej. 2. Kliknij przycisk Run ( ), by uruchomić projekt. Przy pierwszej próbie uruchomienia pojawi się okno dialogowe. Kolejne uruchomienia będą korzystały z wcześniej wybranej opcji.
28
Rozdział 1. • Tworzenie pierwszej wtyczki
3. Wybierz element Eclipse Application i kliknij przycisk OK. Uruchomiona zostanie nowa instancja Eclipse. 4. Zamknij ekran powitalny w nowej instancji (jeśli się pojawił). 5. Kliknij ikonę Hello World z paska narzędziowego lub wybierz z menu polecenie Sample Menu/Sample Command, a pojawi się okno dialogowe utworzone za pomocą kreatora.
6. Zamknij testową instancję Eclipse, zamykając okno, stosując kombinację klawiszy (Cmd+Q w OS X lub Alt+F4 w Windows) lub używając menu.
Co się stało? Kliknięcie przycisk Run ( ) z paska narzędziowego (lub wybranie Run/Run As/Eclipse Application z menu) powoduje utworzenie konfiguracji uruchomieniowej, która zawiera wszystkie wtyczki otwarte w przestrzeni roboczej. Druga kopia Eclipse (z własną, tymczasową przestrzenią roboczą) umożliwia przetestowanie wtyczki i weryfikację, czy działa zgodnie z oczekiwaniami. Operacja uruchomienia jest inteligentna, ponieważ uruchamiana aplikacja bazuje na tym, co zostało wybrane w przestrzeni roboczej. Jeśli zaznaczono wtyczkę, zaoferuje uruchomienie nowej instancji Eclipse. Jeśli projekt zawiera klasę z metodą main, uruchomi aplikację niezależną. Jeśli aplikacja zawiera testy, zaproponuje uruchomienie systemu testów. Operacja uruchomienia (Run) bywa czasem mało intuicyjna; kliknięcie jej po raz drugi, ale w innym kontekście, może spowodować wywołanie innej operacji uruchomienia, niż można by się spodziewać. Listę dostępnych konfiguracji uruchomieniowych łatwo uzyskać, przechodząc do menu Run lub otwierając listę rozwijaną obok ikony Run. Menu Run/Run Configurations zawiera wszystkie dostępne typy, włącznie z wszystkimi wcześniejszymi uruchomieniami (patrz rysunek na następnej stronie). Domyślnie między uruchomieniami używa się tej samej przestrzeni roboczej uruchamiania. Konfiguracja uruchomieniowa Eclipse zawiera wiele opcji, które można zmienić. Na poprzednim zrzucie ekranu część Workspace Data z zakładki Main zawiera informację o miejscu przechowywania przestrzeni roboczej uruchamiania. Dostępna jest również opcja czyszczenia przestrzeni roboczej (z potwierdzeniem lub bez niego) między uruchomieniami.
29
Eclipse 4. Programowanie wtyczek na przykładach
Konfigurację uruchomieniową można usunąć, klikając czerwoną ikonę Delete ( ) na górze po lewej. Tworzenie nowych konfiguracji odbywa się po kliknięciu ikony New ( ). Każda konfguracja uruchomieniowa ma określony typ, taki jak: Eclipse Application, Java Applet, Java Application, JUnit, JUnit Plug-in Test, OSGi Framework.
Konfigurację uruchomieniową można traktować jak wcześniej przygotowany skrypt, który uruchamia różne rodzaje programów. Dodatkowe zakładki służą do dostosowania uruchomienia, na przykład wskazania zmiennych środowiskowych, właściwości systemowych lub argumentów wiersza poleceń. Typ konfiguracji określa, jakie dokładnie parametry są niezbędne i w jaki dokładnie sposób nastąpi uruchomienie. Uruchomienie programu przy użyciu ikony Run powoduje, że późniejsze zmiany w kodzie źródłowym aplikacji nie będą uwzględniane w trakcie działania programu. Jak się jednak przekonasz w następnym podrozdziale, uruchomienie za pomocą ikony Debug zmienia ten stan rzeczy.
30
Rozdział 1. • Tworzenie pierwszej wtyczki
Jeśli testowa wersja Eclipse nie działa prawidłowo i nie udaje się jej zamknąć, można użyć widoku Console (wyświetlanego poleceniem Window/View/Show View/Other/General/Console z menu) i ikony Stop ( ), by zatrzymać instancję testową.
Quiz — uruchamianie Eclipse P1. Wymień oba sposoby zatrzymania uruchomionej instancji testowej Eclipse. P2. Czym są konfiguracje uruchomieniowe? P3. W jaki sposób tworzy się i usuwa konfiguracje uruchomieniowe?
Sprawdź się — modyfikacja wtyczki Gdy udało się uruchomić wtyczkę, wykonaj następujące ćwiczenia. Zmień tekst etykiety i tytuł okna dialogowego na inny. Wywołaj akcję przy użyciu skrótu klawiszowego (definiowanego w pliku
plugin.xml). Zmień tekst podpowiedzi akcji na inny. Zmień ikonę akcji na inną (jeśli użyjesz innej nazwy pliku, pamiętaj o aktualizacji
plików plugin.xml i build.properties).
Debugowanie wtyczki Bardzo rzadko wszystko działa za pierwszym razem, więc najczęściej korzysta się z podejścia iteracyjnego i dodaje nowe funkcjonalności stopniowo. Czasem trzeba się również dowiedzieć, co dzieje się w samym kodzie, szczególnie jeśli chcemy poprawić błąd, który bardzo trudno namierzyć (na przykład wyjątek NullPointerException). Na szczęście, Eclipse doskonale obsługuje testowanie i poszukiwanie błędów, co pozwala bardzo szybko znaleźć i naprawić błąd zarówno w niezależnej aplikacji Javy, jak i we wtyczce Eclipse.
Kroki do wykonania — debugowanie wtyczki Debugowanie wtyczki Eclipse jest bardzo podobne do zwykłego uruchomienia wtyczki Eclipse, ale dodatkowo można określić punkty wstrzymania, aktualizować stan programu i zmiennych, a nawet przeprowadzać drobne zmiany w kodzie źródłowym. Zamiast debugować wtyczki pojedynczo, całą konfigurację uruchomieniową włącza się w trybie debuggera. W ten sposób w tym samym czasie można sprawdzać wszystkie wtyczki.
31
Eclipse 4. Programowanie wtyczek na przykładach
Choć tryb uruchomienia jest nieco szybszy, dodatkowa elastyczność i łatwość zmian w trybie debuggera sprawiają, że jest on znacznie bardziej atrakcyjny jako domyślny tryb uruchomieniowy. Uruchom testową wersję Eclipse, wybierając z menu polecenie Debug/Debug As/Eclipse Application lub klikając ikonę Debug ( ) na pasku narzędziowym. 1. Kliknij ikonę Hello World ( ) w testowej wersji Eclipse, by wyświetlić okno dialogowe, tak jak wcześniej, a następnie zamknij je, klikając przycisk OK. 2. W głównym Eclipse otwórz klasę SampleHandler i przejdź do metody execute(). 3. Dodaj punkt wstrzymania: dwukrotnie kliknij pionowy pasek po lewej stronie okna edytora. Ewentualnie naciśnij kombinację klawiszy Ctrl+Shift+B (lub Cmd+Shift+B w systemie OS X). Na pasku pojawi się niebieska kropka ( ) reprezentująca punkt wstrzymania.
4. Ponownie kliknij ikonę Hello World ( ) w testowej wersji Eclipse, by wyświetlić okno dialogowe. Debugger zatrzyma wątek w punkcie wstrzymania i wyświetli główną instancję Eclipse (patrz rysunek na następnej stronie). Perspektywa debuggera otworzy się, gdy tylko zostanie osiągnięty punkt wstrzymania. Program zostanie zapauzowany. Pauza powoduje, że testowa wersja Eclipse nie będzie reagowała na działania użytkownika. Kliknięcia nie zakończą się żadną akcją i cały czas będzie wyświetlany kursor zajętości.
32
Rozdział 1. • Tworzenie pierwszej wtyczki
5. Po prawej stronie u góry pojawi się lista zmiennych aktywnych w zatrzymanym wierszu kodu. W tym przypadku są to po prostu niejawne zmienne (na przykład this), zmienne lokalne (na razie nie ma żadnych) oraz parametry (w tym przypadku event). 6. Kliknij ikonę Step Over ( ) lub naciśnij klawisz F6, by zmienna window znalazła się na liście dostępnych zmiennych.
7. Aby wznowić wykonywanie kodu, kliknij ikonę Resume (
) lub naciśnij klawisz F8. 33
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Wbudowany w Eclipse debugger posłużył do uruchomienia kopii Eclipse w trybie debuggera. Po wywołaniu akcji prowadzącej do punktu wstrzymania pojawiła się perspektywa debuggera umożliwiająca sprawdzenie zawartości zmiennych lokalnych. W oknie debuggera znajdziesz kilka opcji poruszania się po kodzie. Step Over (
) — umożliwia przechodzenie po kodzie wiersz po wierszu bez wchodzenia do kodu wywoływanych metod.
Step Into (
) — wchodzi do kodu wywoływanych metod, by można było przekonać się, jak działają.
Dostępne jest również polecenie Run/Step into Selection z menu, które nie posiada własnej ikony na pasku narzędziowym. Można je uruchomić kombinacją klawiszy Ctrl+F5 (Alt+F5 w systemie OS X). Służy do wejścia do konkretnego wyrażenia. Step Return (
) — skacze na koniec metody.
Drop to Frame (
) — wraca do ramki stosu w wątku, by ponownie wykonać
operację.
Kroki do wykonania — aktualizacja kodu w debuggerze Gdy instancja Eclipse zostanie włączona w trybie uruchomieniowym, zmiany wprowadzane w kodzie źródłowym nie są odzwierciedlane w działającej instancji. Tryb debuggera umożliwia automatyczne nanoszenie zmian w kodzie na działającą instancję testową Eclipse. 1. Uruchom testową wersję Eclipse w trybie debuggera, klikając ikonę Debug (
).
2. Kliknij ikonę Hello World ( ) w testowej wersji Eclipse, by wyświetlić okno dialogowe, tak jak wcześniej, a następnie zamknij je, klikając przycisk OK. Prawdopodobnie niezbędne będzie wcześniejsze wznowienie ponownego działania instancji po punkcie wstrzymania. 3. W głównym Eclipse otwórz klasę SampleHandler i przejdź do metody execute(). 4. Zmień treść okna dialogowego na Witaj ponownie, świecie Eclipse i zapisz plik. Jeśli opcja Project/Build Automatically jest włączona, nastąpi automatyczna kompilacja. 5. Ponownie kliknij ikonę Hello World w testowej instancji. Powinien pojawić się nowy komunikat.
Co się stało? Domyślnie Eclipse ma włączoną opcję Project/Build Automatically. Zmiana plików Javy powoduje ich automatyczną, ponowną kompilację wraz ze wszystkimi niezbędnymi zależnościami.
34
Rozdział 1. • Tworzenie pierwszej wtyczki
Włączenie programu Javy w trybie uruchomieniowym sprawia, że klasy są wczytywane na żądanie, ale po wczytaniu pozostają w maszynie wirtualnej aż do wyłączenia programu. Jeśli nawet klasa zmieniła się, maszyna wirtualna nie zauważy aktualizacji, więc w działającej aplikacji nie będą widoczne żadne modyfikacje. Jeśli jednak program został włączony w trybie debuggera, zmiana klasy spowoduje uaktualnienie działającej instancji maszyny wirtualnej (jeśli taka podmiana jest możliwa). To, co można zmienić bez ponownego uruchomienia, zależy od mechanizmu JVMTI maszyny wirtualnej i zwrócenia wartości true przez metodę canUnrestrictedlyRedefineClasses(). Najczęściej przyjmuje się, że można zmieniać zawartość istniejących metod lub dodawać nowe metody i pola, ale zmiany w interfejsach i klasach bazowych wymagają ponownego uruchomienia (więcej informacji na ten temat znajdziesz na stronie http://en.wikipedia.org/wiki/Java_Virtual_ Machine_Tools_Interface). Maszyna wirtualna Hotspot (kiedyś firmy Sun) nie potrafi zastępować klas, jeśli dodawane są metody lub zmieniane interfejsy. Inne maszyny wirtualne posiadają dodatkowe możliwości i większą elastyczność w podmianie kodu. Gdy z czasem zostaną połączone maszyny wirtualne JRockit i Hotspot, lista dostępnych operacji zastąpienia wydłuży się. W innych sytuacjach zawsze istnieje JRebel. Maszyny wirtualne innych firm, na przykład firmy IBM, mogą radzić sobie z szerszym zakresem zmian.
Trzeba jednak zdawać sobie sprawę z tego, iż pewne zmiany nigdy nie będą uaktualniane automatycznie. Dotyczy to na przykład nowych rozszerzeń dodawanych do pliku plugin.xml. Aby je zobaczyć, można zatrzymać i uruchomić wtyczkę poleceniem OSGi z konsoli lub też ponownie uruchomić instancję Eclipse.
Debugowanie z filtrami kroków Kiedy w debuggerze skorzystamy z kroków Step Into, kod bardzo często będzie przechodził do szczegółów wewnętrznych Javy, na przykład implementacji klas kolekcji lub klas wewnętrznych JVM. Najczęściej przeglądanie takiego kodu nie ma dużego sensu. Na szczęście, Eclipse pozwala ignorować takie fragmenty lub klasy.
Kroki do wykonania — ustawienie filtru kroków Filtrowanie kroków umożliwia ignorowanie nieinteresujących pakietów i klas w trakcie krokowego przechodzenia przez kod. 1. Uruchom testową instancję Eclipse w trybie debuggera. 2. Upewnij się, że punkt wstrzymania ustawiony jest na początku metody execute() klasy SampleHandler.
35
Eclipse 4. Programowanie wtyczek na przykładach
3. Kliknij ikonę Hello World. Debugger otworzy się w miejscu pierwszego wiersza kodu. 4. Kliknij ikonę Step Into ( ) pięć lub sześć razy. W tym momencie kod przejdzie do następnej metody w wyrażeniu; najpierw do metod w HandlerUtil, a następnie do ExecutionEvent. 5. Kliknij ikonę Resume (
), by kontynuować.
6. Otwórz okno Preferences i przejdź do Java/Debug/Step Filtering. 7. Włącz opcję Use Step Filters. 8. Kliknij przycisk Add Packages, wpisz org.eclipse.ui i kliknij przycisk OK.
9. Ponownie kliknij ikonę Hello World. 10. Kliknij ikonę Step Into ( ), tak jak wcześniej. W tym momencie kod przejdzie od razu do metody getApplicationContext() klasy ExecutionEvent. 11. Kliknij ikonę Resume (
), by kontynuować.
12. Aby debugowanie stało się bardziej wydajne (poprzez pomijanie metod dostępowych), wróć do ustawień Step Filtering i włącz opcję Filter Simple Getters. 13. Ponownie kliknij ikonę Hello World. 14. Kliknij ikonę Step Into (
), tak jak wcześniej.
15. Zamiast przejść do metody getApplicationContext(), wykonywanie kodu przeskoczy od razu do metody getVariable() klasy ExpressionContext.
36
Rozdział 1. • Tworzenie pierwszej wtyczki
Co się stało? Ustawienia Step Filtering umożliwiają pominięcie mało interesujących pakietów (oczywiście, tylko z perspektywy debuggera). Najczęściej wewnętrzne klasy JVM (na przykład zaczynające się od sun lub sunw) nie są pomocne w trakcie debugowania i można je z powodzeniem pominąć. Warto również pominąć kod dotyczący wczytywania klas na żądanie. W większości sytuacji warto zastanowić się nad włączeniem wszystkich domyślnych pakietów w ustawieniach Step Filtering, ponieważ bardzo rzadko trzeba sprawdzać biblioteki JVM (interfejsy publiczne lub wewnętrzne). Oznacza to, że w trakcie krokowego wykonywania kodu debugger po napotkaniu typowej metody, takiej jak List.toString(), nie będzie przechodził przez jej wewnętrzną implementację. Warto również odfiltrować proste metody pobierające i ustawiające (czyli takie, które tylko pobierają konkretną wartość z innego miejsca lub ją ustawiają). Jeśli metoda jest bardziej skomplikowana (podobnie jak wskazana wcześniej metoda getVariable()), spowoduje zatrzymanie debuggera. Można dodatkowo włączyć pomijanie konstruktorów i inicjalizatorów statycznych.
Korzystanie z różnych rodzajów punktów wstrzymania Choć punkt wstrzymania można umieścić w dowolnym miejscu metody, istnieje specjalny rodzaj punktu wstrzymania, który zadziała w momencie wejścia do metody, wyjścia z niej lub w obu tych sytuacjach. Punkty wstrzymania można również ustawić tak, by działały tylko w określonych sytuacjach lub po spełnieniu wskazanego warunku.
Kroki do wykonania — wstrzymanie przy wejściu do metody lub wyjściu z niej Punkty wstrzymania dla metod ułatwiają śledzenie, gdy wykonywanie kodu wchodzi do metody lub wychodzi z niej. 1. Otwórz klasę SampleHandler i przejdź do metody execute(). 2. Kliknij dwukrotnie pionowy pasek w miejscu sygnatury metody lub wybierz Toggle Method Breakpoint dla metody w widokach Outline, Package Explorer lub Members. 3. Punkt wstrzymania powinien wskazywać wiersz public Object execute(...) throws ExecutionException {.
37
Eclipse 4. Programowanie wtyczek na przykładach
4. Otwórz właściwości punktu wstrzymania, klikając go prawym przyciskiem myszy lub korzystając z widoku Breakpoints w perspektywie debuggera. Włącz wyzwalanie punktu wstrzymania w momencie wejścia do metody i wyjścia z niej. 5. Kliknij ikonę Hello World. 6. Gdy debugger zatrzyma się w momencie wejścia do metody, kliknij ikonę Resume. 7. Gdy debugger zatrzyma się w momencie wyjścia z metody, kliknij ikonę Resume.
Co się stało? Punkt wstrzymania wyzwala się w momencie wejścia do metody i w momencie osiągnięcia polecenia return. Warto pamiętać, że wyjście z metody zatrzyma program tylko w sytuacji, gdy jest to standardowe wyjście z metody. Zgłoszenie wyjątku powodujące opuszczenie metody nie jest traktowane jako zwykłe opuszczenie metody, więc w takiej sytuacji punkt wstrzymania w tym trybie nie zadziała. Nie istnieją, poza samym typem punktu wstrzymania, żadne znaczące różnice między utworzeniem punktu w momencie wejścia do metody a wskazaniem pierwszego wiersza metody jako punktu wstrzymania. W obu przypadkach można sprawdzić przekazane parametry i przeprowadzić dodatkowe testy przed uruchomieniem jakiegokolwiek kodu w samej metodzie. Z drugiej strony, punkt wstrzymania zakończenia metody zostanie wyzwolony tylko w momencie, w którym polecenie return będzie chciało opuścić metodę. Oznacza to, że wyrażenie z polecenia return będzie w całości wyliczone, zanim kod zostanie wstrzymany. Warto porównać to ze standardowym punktem wstrzymania, który zatrzyma się przed wyliczeniem wyrażenia zawartego w instrukcji return. Warto wiedzieć, że ten sam efekt uzyska się przy użyciu ikony Step Return ( ). Kod będzie działał aż do momentu dojścia do instrukcji return. Użycie punktu wstrzymania wyjścia z metody jest znacznie szybsze, jeśli chcemy się dowiedzieć, kiedy kod opuszcza metodę, bo nie trzeba zatrzymywać się w odpowiednim wierszu i używać ikony Step Return.
Warunkowe punkty wstrzymania Punkty wstrzymania to przydatna funkcjonalność, bo mogą być wywoływane przy każdym wejściu do konkretnego wiersza kodu. Czasem jednak interesuje nas tylko określona sytuacja, na przykład ustawienie konkretnych wartości lub też zła inicjalizacja wartości. Z pomocą wtedy przychodzą warunkowe punkty wstrzymania.
38
Rozdział 1. • Tworzenie pierwszej wtyczki
Kroki do wykonania — ustawienie warunkowego punktu wstrzymania Standardowo punkty wstrzymania działają przy każdym wejściu do konkretnego wiersza. Można jednak tak skonfigurować punkt, by wstrzymanie nastąpiło tylko po spełnieniu określonych warunków. 1. Przejdź do metody execute() klasy SampleHandler. 2. Usuń wszystkie istniejące punkty wstrzymania, klikając je dwukrotnie lub wybierając polecenie Delete all breakpoints z widoku Breakpoints. 3. Dodaj punkt wstrzymania do pierwszego wiersza kodu metody execute(). 4. Kliknij punkt wstrzymania prawym przyciskiem myszy i z menu kontekstowego wybierz polecenie Breakpoint Properties. (Właściwości można również wyświetlić, wykonując operację Ctrl+podwójne kliknięcie lub Cmd+podwójne kliknięcie w systemie OS X na samej ikonie punktu wstrzymania).
5. W polu Hit Count wpisz wartość 3 i kliknij przycisk OK. 6. Kliknij trzykrotnie ikonę Hello World. Po trzecim kliknięciu debugger otworzy się w wyznaczonym wierszu kodu.
39
Eclipse 4. Programowanie wtyczek na przykładach
7. Ponownie otwórz właściwości punktu wstrzymania, wyłącz opcję Hit Count i włącz opcje Enabled i Conditional. W polu poniżej wpisz warunek wyzwolenia: ((org.eclipse.swt.widgets.Event)event.trigger).stateMask == 65536
8. Kliknij ikonę Hello World; zauważysz, że punkt wstrzymania nie został wyzwolony. 9. Przytrzymaj klawisz Alt i kliknij ikonę Hello World, a debugger otworzy się (65536 to wartość SWT.MOD3, czyli klawisz Alt).
Co się stało? Domyślnie punkt wstrzymania będzie wyzwalany niezależnie od sytuacji. Można go tymczasowo wyłączyć, by nie wpływał na przebieg wykonywania programu. Wyłączone punkty wstrzymania bardzo łatwo ponownie włączyć z poziomu kodu lub w widoku Breakpoints. Często zdarza się, że kod ma ustawionych wiele punktów wstrzymania, ale nie wszystkie są włączone w tym samym czasie. Można także tymczasowo wyłączyć wszystkie punkty wstrzymania, używając opcji Skip All Breakpoints modyfikowanej z poziomu menu Run (jeśli aktywna jest perspektywa debugowania) lub za pomocą ikony z widoku Breakpoints. Włączenie opcji spowoduje pomijanie wszystkich punktów wstrzymania. Warunkowe punkty wstrzymania muszą korzystać z wyrażeń zwracających wartość boolowską. Nie może to być instrukcja lub zbiór instrukcji. W pewnych sytuacjach to duże ograniczenie; wtedy warto dodać klasę pomocniczą z metodą statyczną wykonującą bardziej złożony test (oczywiście, do metody trzeba przekazać wszystkie niezbędne dane).
Wstrzymanie działania po wystąpieniu wyjątku Czasem w trakcie testowania programu pojawia się wyjątek. Najczęściej nie mamy o nim pojęcia aż do momentu zgłoszenia wyjątku, który pojawi się użytkownikowi jako okno dialogowe lub komunikat w konsoli.
Kroki do wykonania — wyłapywanie wyjątków Oczywiście, bardzo łatwo umieścić punkt wstrzymania w bloku catch, ale będzie to miejsce, w którym błąd został wyłapany, a nie miejsce jego rzeczywistego wystąpienia. Rzeczywiste miejsce zgłoszenia to często całkowicie inna wtyczka i nie zawsze łatwo wskazać rzeczywiste źródło problemu, bo to zależy od ilości informacji umieszczonych w obiekcie wyjątku (szczególnie po zmianie typu wyjątku).
40
Rozdział 1. • Tworzenie pierwszej wtyczki
1. Wprowadź w metodzie execute() klasy SampleHandler błąd, dodając przed wywołaniem metody MessageDialog.openInformation() poniższy wiersz. window = null; Pobranie przykładowego kodu Pamiętaj, że przykładowy kod do książki jest dostępny do pobrania pod adresem ftp://ftp.helion.pl/ przyklady/eclip4.zip.
2. Kliknij ikonę Hello World. 3. W testowej wersji Eclipse wydaje się, że nic się nie stało, ale widok Console instancji głównej wyświetli poniższy tekst. Caused by: java.lang.NullPointerException at com.packtpub.e4.hello.ui.handlers.SampleHandler.execute(SampleHandler.java:30) at org.eclipse.ui.internal.handlers.HandlerProxy.execute(HandlerProxy.java:293) at org.eclipse.ui.internal.handlers.E4HandlerProxy.execute(E4HandlerProxy.java:76)
4. Utwórz punkt wstrzymania dla wyjątków Javy ( ) w widoku Breakpoints perspektywy debugowania. Pojawi się okno dialogowe określenia wyjątku.
5. W polu wyszukiwania wpisz NullPointerException, zaznacz wyjątek i kliknij OK. 6. Kliknij ikonę Hello World i debugger zatrzyma się w wierszu powodującym zgłoszenie wyjątku, a nie w miejscu jego przechwycenia.
41
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Punkt wstrzymania wyjątku Javy działa w miejscu zgłoszenia wyjątku, a nie w miejscu jego przechwycenia. Okno dialogowe prosi o wskazanie jednej klasy wyjątku do wyłapywania. Domyślnie okno wyświetla listę wszystkich klas zawierających w nazwie tekst *Exception*. Filtrowanie może odbywać się według dowolnej nazwy, włącznie ze skrótami, takimi jak FNFE dla FileNotFoundException. Nic nie stoi na przeszkodzie, by stosować znaki wieloznaczne i szukać klas z wyrażeniami Nu*Ex lub *Unknown*. Domyślnie punkt wstrzymania dla wyjątków dotyczy instancji określonej klasy. To przydatne rozwiązanie dla wyjątków typu NullPointerException, ale niezbyt dobre dla wyjątków o rozbudowanej hierarchii klas, na przykład IOException. W oknie właściwości punktu wstrzymania i na dole widoku Breakpoints istnieje opcja włączająca dopasowanie również do wszystkich podklas wybranego wyjątku. Okno punktów wstrzymania wyjątków zawiera dwie dodatkowe opcje dotyczące zatrzymywania się w sytuacji, gdy wyjątek jest wyłapywany, i w sytuacji, gdy nie został wyłapany. Domyślnie obie opcje są włączone; wyłączenie obu powoduje w praktyce pomijanie tego wyjątku jako punktu wstrzymania. Opcja Suspend on caught exceptions dotyczy sytuacji, w której istnieje blok try-catch, a Suspend on uncaught exceptions sytuacji, gdy brak takiego bloku powoduje przekazanie wyjątku wyżej w hierarchii wywołań.
42
Rozdział 1. • Tworzenie pierwszej wtyczki
Kroki do wykonania — obserwacja zmiennych i wyrażeń Najwyższy czas przyjrzeć się widokowi Variables. 1. Utwórz punkt wstrzymania na początku metody execute(). 2. Ponownie kliknij ikonę Hello World. 3. Zaznacz wywołanie openInformation() i wywołaj polecenie Run/Step Into Selection. 4. Zaznacz title w widoku Variables. 5. Zmodyfikuj tekst Witaj na tekst Żegnaj w dolnej części widoku Variables.
6. Zapisz wartość przy użyciu skrótu klawiszowego Ctrl+S (lub Cmd+S w systemie OS X). 7. Kliknij ikonę Resume i nowy tytuł pojawi się w oknie dialogowym. 8. Ponownie kliknij ikonę Hello World. 9. Po zatrzymaniu debuggera w metodzie execute() zaznacz event w widoku Variables. 10. Kliknij prawym przyciskiem myszy wartość i wybierz polecenie Inspect (kombinacja klawiszy Ctrl+Shift+I lub Cmd+Shift+I w systemie OS X). Wartość pojawi się w widoku Expressions.
43
Eclipse 4. Programowanie wtyczek na przykładach
11. Kliknij opcję Add new expression na dole widoku Expressions. 12. Dodaj wpis new java.util.Date(), a w części po prawej stronie wyświetli się aktualny czas. 13. Kliknij prawym przyciskiem myszy wpis java.util.Date() i wybierz polecenie Re-evaluate Watch Expression. Po prawej stronie pojawi się nowa wartość. 14. Przejdź przez kod krok po kroku, by przekonać się, że wyrażenie jest wyliczane w każdym kroku. 15. Wyłącz wyrażenie, klikając je prawym przyciskiem myszy i wybierając polecenie Disable. 16. Przejdź przez kod krok po kroku, by przekonać się, że wyrażenie nie jest już wyliczane.
Co się stało? Debugger Eclipse zawiera wiele użytecznych funkcjonalności. Możliwość sprawdzenia (i zmiany) stanu programu jest jedną z ważniejszych. Obserwacja wyrażeń w połączeniu z warunkowymi punktami wstrzymania ułatwia odnalezienie sytuacji, w których dane są nieprawidłowe, lub też służy do poznania wartości dostępnych w konkretnej sytuacji. Wyrażenia mogą być wyliczanie na podstawie obiektów z widoku Variables. Do wyboru metod używa się uzupełniania kodu. Wynik wyświetla się w oknie Display.
44
Rozdział 1. • Tworzenie pierwszej wtyczki
Quiz — debugowanie P1. W jaki sposób uruchomić wtyczkę Eclipse w trybie debugowania? P2. W jaki sposób uniknąć wyświetlania niektórych pakietów w trakcie debugowania? P3. Jakie są różne rodzaje punktów wstrzymania, które można ustawić? P4. W jaki sposób przetestować pętlę, w której błąd występuje dopiero po 256 iteracjach? P5. W jaki sposób ustawić punkt wstrzymania aktywujący się tylko w sytuacji, gdy argument ma wartość null? P6. Jakie jest zadanie inspekcji obiektu? P7. W jaki sposób można wyliczać wartość wyrażenia?
Sprawdź się — korzystanie z punktów wstrzymania Użycie warunkowego punktu wstrzymania do zatrzymania metody jest proste, jeśli dane nie są skomplikowane, ale czasem potrzeba więcej niż jednego wyrażenia. By zaimplementować dodatkową funkcjonalność, punkt wstrzymania można oddelegować do metody breakpoint() z klasy Utility. Oto kroki, dzięki którym łatwiej uzyskać właściwą konfigurację. 1. Utwórz klasę Utility z metodą statyczną breakpoint(), która zwróci wartość true, gdy punkt wstrzymania ma się uaktywnić i false w sytuacji przeciwnej. public class Utility { public static boolean breakpoint() { System.out.println("Punkt wstrzymania"); return false; } }
2. Utwórz w metodzie execute()warunkowy punkt wstrzymania, który wywołuje Utility.breakpoint(). 3. Kliknij ikonę Hello World i zauważ wyświetlenie komunikatu w widoku Console głównego Eclipse. Punkt wstrzymania nie uaktywnił się. 4. Zmodyfikuj metodę breakpoint(), by zwracała wartość true zamiast false. Ponownie kliknij ikonę. Debugger zatrzyma wykonywanie kodu. 5. Zmodyfikuj metodę breakpoint(), by jako argument przyjmowała komunikat oraz dodatkową wartość logiczną wskazującą, czy punkt wstrzymania powinien się uaktywnić. 6. Ustaw warunkowy punkt wstrzymania z poniższym kodem. Utility.breakpoint(((org.eclipse.swt.widgets.Event)event.trigger). stateMask != 0, "Punkt wstrzymania")
45
Eclipse 4. Programowanie wtyczek na przykładach
7. Zmodyfikuj metodę breakpoint(), by przyjmowała zmienną liczbę argumentów jako tablicę obiektów Object, a następnie użyj metody String.format() do wykonania końcowej wersji komunikatu. Utility.breakpoint( ((org.eclipse.swt.widgets.Event)event.trigger).stateMas k != 0, "Punkt wstrzymania %s %h", event, new java.util.Date().getTime()))
Podsumowanie W tym rozdziale pojawił się opis konfiguracji i rozpoczęcia tworzenia wtyczek Eclipse. Zaczęliśmy od pobrania odpowiedniego pakietu Eclipse (z bardzo szerokiej listy). Następnie wygenerowaliśmy wtyczkę za pomocą kreatora i przetestowaliśmy ją, co pozwoli bez problemów zrealizować pozostałe przykłady zawarte w książce. Omówiliśmy następujące tematy. Eclipse SDK (nazywane też Eclipse Classic) zawiera funkcjonalności wymagane
do tworzenia wtyczek. Kreator tworzenia wtyczek służy do utworzenia początkowego projektu z wykorzystaniem opcjonalnego szablonu. Testowanie wtyczek odbywa się przez uruchomienie drugiej kopii Eclipse z zainstalowaną wtyczką. Uruchomienie testowej wersji Eclipse w trybie debugowania umożliwia aktualizację
kodu i zatrzymanie wykonywania przy użyciu punktów wstrzymania definiowanych w edytorze. Po zapoznaniu się z podstawami tworzenia wtyczek Eclipse możemy rozpocząć prace nad wtyczkami dodającymi nowe elementy do IDE — zaczniemy od wtyczek SWT i widoków, które są tematem następnego rozdziału.
46
2 Tworzenie widoków w SWT SWT to zbiór widgetów używany przez Eclipse, który zapewnia w sposób przenośny wydajny dostęp do rozwiązań wbudowanych w system operacyjny. W odróżnieniu od biblioteki Swing, gdzie stosowane są operacje rysujące z poziomu Javy, SWT przekierowuje właściwy rendering do systemu operacyjnego.
W tym rozdziale: utworzymy widok Eclipse z widgetami SWT, zbudujemy własny widget SWT, wykorzystamy zasoby i dowiemy się, jak wykrywać i naprawiać wycieki zasobów, obsłużymy operacje zmiany aktywności, pogrupujemy komponenty i będziemy zmieniać ich rozmiar w sposób
automatyczny, utworzymy elementy przybornika systemowego, wyświetlimy okna inne niż prostokątne, dodamy możliwość przewijania okna i nawigację opartą na zakładkach.
Eclipse 4. Programowanie wtyczek na przykładach
Tworzenie widoków i widgetów W tym podrozdziale wprowadzimy widoki i widgety, które posłużą do realizacji ćwiczeń związanych z zegarem.
Kroki do wykonania — tworzenie widoku Interfejs Eclipse składa się z wielu widoków, które są prostokątnymi obszarami wyświetlającymi konkretne treści. Przykładem mogą być widoki Outline, Console lub Package Explorer. W Eclipse 3.x widoki tworzy się przez dodanie punktu rozszerzenia do istniejącej wtyczki lub korzysta się z szablonu. Wykonamy wtyczkę clock.ui, która będzie zawierała widgety i widoki dotyczące zegarów. 1. Otwórz kreator wtyczek, wywołując polecenie File/New/Other/Plug-in Project. Wpisz w pierwszym ekranie następujące dane. W polu Project name wpisz com.packtpub.e4.clock.ui. Włącz opcję Use default location. Włącz opcję Create a Java project. Z listy Eclipse Version wybierz 3.5 or greater. 2. Kliknij przycisk Next. Wypełnij właściwości wtyczki w następujący sposób. W polu ID wpisz com.packtpub.e4.clock.ui.
W polu Version wpisz 1.0.0.qualifier.
W polu Name wpisz Zegar.
W polu Vendor wpisz Packtpub.
Włącz opcję Generate an Activator. W polu Activator wpisz com.packtpub.e4.clock.ui.Activator.
Włącz opcję This plug-in will make contributions to the UI. Pozostaw pole wyboru Rich client application na wartości No. 3. Kliknij przycisk Next, a pojawi się lista szablonów do wyboru. Włącz opcję Create a plug-in using one of the templates. Zaznacz szablon Plug-in with a view. 4. Kliknij przycisk Next, aby przeprowadzić zmiany w szablonie. W polu Java package name wpisz com.packtpub.e4.clock.ui.views.
48
W polu View class name wpisz ClockView.
W polu View name wpisz Widok zegara.
W polu View category ID wpisz com.packtpub.e4.clock.ui.
W polu View category name wpisz Śledzenie czasu.
Wybierz pole wyboru Table viewer.
Rozdział 2. • Tworzenie widoków w SWT
5. Wyłącz opcje zaczynające się od Add, bo nie są potrzebne. 6. Kliknij przycisk Finish, by wygenerować projekt. 7. Uruchom testową wersję Eclipse przy użyciu ikony Run. 8. Wykonaj polecenie Window/Show View/Other/Śledzenie czasu/Widok zegara, by wyświetlić widok zegara zawierający przykładową listę z elementami One, Two i Three.
Co się stało? Nieczęsto zdarza się tworzyć projekty wtyczek dla konkretnego widoku lub konkretnej akcji. Najczęściej wtyczki zapewniają szerszy zakres funkcjonalności. Jednak zegary nie mają zbyt dużo wspólnego z wcześniejszym rozszerzeniem Hello World, więc utworzyliśmy nową wtyczkę. Kreator wtyczek utworzył prawie pusty projekt z dwoma istotnymi plikami.
Plik manifest.mf W pliku manifestu znajdują się odniesienia do zależności, takich jak inne wtyczki i interfejsy. Zawiera on następujący tekst. Bundle-SymbolicName: com.packtpub.e4.clock.ui; singleton:=true Bundle-Version: 1.0.0.qualifier Bundle-Activator: com.packtpub.e4.clock.ui.Activator Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime
Wtyczki dodające elementy interfejsu użytkownika trzeba: uzależnić od org.eclipse.ui, dodać do nich ;singleton:=true po nazwie symbolicznej paczki.
Zależność od paczki org.eclipse.ui zapewnia dostęp do SWT (Standard Widget Toolkit) i innych kluczowych elementów frameworka Eclipse. Fragment ;singleton:=true to dyrektywa OSGi, która oznacza, że w każdym momencie może być zainstalowana co najwyżej jedna wersja wtyczki. W przypadku wtyczek dodających zależności związane z interfejsem użytkownika istnieje wymóg, by były singletonami. (Ograniczenie to jest jednym z powodów, dla których instalacja nowej wtyczki wymaga ponownego uruchomienia IDE Eclipse).
49
Eclipse 4. Programowanie wtyczek na przykładach
Plik manifestu określa ścieżkę klas projektu. Należy w nim umieścić wszystkie dodatkowe zależności wtyczki.
Plik plugin.xml Plik plugin.xml definiuje listę rozszerzeń oferowanych przez wtyczkę. Punkty rozszerzeń to sposób, w jaki Eclipse reklamuje rozszerzenia wtyczki, podobnie jak hub USB zapewnia jeden rodzaj wtyczki do podłączania różnego rodzaju urządzeń. Punkty rozszerzeń Eclipse są opisane w systemie pomocy. Każdy z nich stosuje identyfikator point oraz opcjonalne elementy podrzędne zależne od rodzaju punktu. W tym przypadku rozszerzenie korzysta z punktu org.eclipse.ui.views, który oczekuje zastosowania elementów category i view. Oto pełna treść fragmentu pliku.
Klasa rozszerza klasę abstrakcyjną ViewPart używaną dla wszystkich widoków w Eclipse 3.x.
E4: Model Eclipse 4 definiuje widoki w inny sposób, który zostanie szczegółowo przedstawiony
w rozdziale 7. SDK dla Eclipse 4.x zawiera warstwę zgodności z wersją 3.x, więc prezentowany przykład zadziała w nim prawidłowo.
Komponent przeglądania to domyślny widok tabeli, więc zastąpimy go w następnym przykładzie.
Kroki do wykonania — rysowanie własnego widoku Klasa Canvas z SWT może posłużyć do zapewnienia własnego sposobu renderowania widoku. Ponieważ interesuje nas zegar, zaczniemy od metody drawArc(), by utworzyć okrąg. 1. Usuń zawartość klasy ClockView, pozostawiając jedynie puste implementacje metod setFocus() i createPartControl(). 2. Uruchom testową wersję Eclipse, by przekonać się, że widok zegara jest pusty.
50
Rozdział 2. • Tworzenie widoków w SWT
3. W metodzie createPartControl() wykonaj następujące operacje. 1. Utwórz nowy obiekt Canvas, który jest widgetem, po którym można rysować. 2. Dodaj do Canvas obiekt PaintListener. 3. Pobierz gc z PaintEvent i wywołaj metodę drawArc(), by narysować okrąg. Kod po tej operacji powinien wyglądać, tak jak poniżej. import import import import
org.eclipse.swt.*; org.eclipse.swt.events.*; org.eclipse.swt.widgets.*; org.eclipse.ui.part.ViewPart;
public class ClockView extends ViewPart { public void createPartControl(Composite parent) { final Canvas clock = new Canvas(parent,SWT.NONE); clock.addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { e.gc.drawArc(e.x,e.y,e.width-1,e.height-1,0,360); } }); } public void setFocus() { } }
4. Uruchom testową wersję Eclipse i wyświetl Widok zegara. 5. Modyfikuj rozmiar widoku, a rozmiar zegara będzie się automatycznie dostosowywał.
Co się stało? W SWT widgetem używanym do swobodnego rysowania jest klasa Canvas. Widok powstaje w trakcie wywołania metody createPartControl(), która zostaje uruchomiona jeden raz w momencie pojawienia się widoku. Późniejsza minimalizacja lub maksymalizacja widoku nie wywołuje metody po raz kolejny. Jeśli jednak widok zostanie zamknięty i później otwarty ponownie, Eclipse utworzy nową instancję ClockView i ją zainicjalizuje.
51
Eclipse 4. Programowanie wtyczek na przykładach
W odróżnieniu od innych frameworków interfejsu użytkownika języka Java, widget nie jest dodawany lub usuwany do zawierającego go elementu nadrzędnego — element nadrzędny jest wskazany na etapie konstrukcji. Z tego powodu zamiast tworzyć go z pustym konstruktorem, a dopiero potem dodawać, element nadrzędny od razu trafia do konstruktora widgetu. Przekazana zostaje także zmienna style. Widgety wykorzystują ją w różny sposób; na przykład widget Button używa jej do wskazania, czy należy przycisk wyświetlić jako przycisk standardowy, opcję, przełącznik lub strzałkę. By zapewnić jednolity interfejs, SWT we wszystkich widgetach używa zmiennej style typu int, czyli istnieją 32 bity możliwych opcji. W klasie SWT poszczególne style są zdefiniowane jako stałe; na przykład styl przycisku jako opcja to stała SWT.CHECKBOX. Opcje można ze sobą łączyć, używając operacji na bitach, więc płaski przycisk wciskany powstanie po użyciu poniższego kodu. new Button(parent,SWT.PUSH|SWT.FLAT) Wartość SWT.NONE służy do zastosowania opcji domyślnych.
W kodzie został dodany do widoku pusty obiekt Canvas, ale jak na nim cokolwiek narysować? SWT nie udostępnia metod rysowania w żadnym ze swych widgetów. Zamiast tego wywołuje obiekt PaintListener, jeśli trzeba ponownie narysować kanwę. Wszystko w imię wydajności Zastanawiasz się zapewne, dlaczego istnieją te wszystkie małe różnice między sposobem obsługi widgetów przez SWT i przez AWT lub Swing. Po pierwsze — w imię szybkości i chęci przekazania jak największej liczby zadań mechanizmom wbudowanym w system operacyjny (o ile to możliwe). Miało to szczególnie duże znaczenie w początkach Javy (Eclipse 1.0 został wydany, gdy najbardziej zaawansowaną wersją Javy była wersja 1.3), ponieważ dawniej zarówno technologia JIT, jak i procesory nie były tak szybkie. Po drugie, głównym celem SWT jest przeniesienie jak największej liczby zadań na komponenty własne systemu operacyjnego (podobnie jak w AWT), by to system wykonywał najbardziej obciążające prace. Dzięki temu zmniejsza się czas spędzony przy renderowania grafiki w maszynie wirtualnej, bo z tym zadaniem system operacyjny poradzi sobie zdecydowanie lepiej. Obiekt nasłuchiwania rysowania to jedna z tych sytuacji, w których rysowania unika się aż do momentu, gdy jest to naprawdę niezbędne.
Metoda paintControl() zostaje wywołana z argumentem PaintEvent, który zawiera referencje do wszystkich danych niezbędnych do narysowania komponentu. By zmniejszyć liczbę wywołań, metody pola są dostępne publicznie (nie uznaje się tego za dobry styl, ale w tej sytuacji działa zdecydowanie bardziej wydajnie). Metoda zawiera również referencje do kontekstu graficznego (gc) umożliwiającego wywołania funkcji rysujących. Zdarzenie zawiera dodatkowo informację o regionie, w którym zgłoszono potrzebę ponownego rysowania. Pola x i y zawierają informację o położeniu lewego górnego narożnika, a pola width i height — wymiary obszaru rysowania.
52
Rozdział 2. • Tworzenie widoków w SWT
W tym przypadku ustawiamy odpowiedni kolor rysowania i wywołujemy metodę drawArc() w dopuszczalnym zakresie rysowania. Warto pamiętać, że metoda łuku używa wartości podawanych w stopniach (od 0 do 360), a nie radianów lub innych jednostek.
Kroki do wykonania — rysowanie wskazówki sekund Na razie zegar to tylko okrąg bez wskazówek i cyfr. Ponieważ łuki są rysowane w kierunku przeciwnym do ruchu wskazówek zegara od 0 (prawa strona, czyli godzina 3) przez 90 (godzina 12), 180 (godzina 9), 270 (godzina 6) aż do 360 (ponownie godzina 3), położenie wskazówki sekund można wyliczyć za pomocą wzoru (15-s)*6%360. 1. Przejdź do metody paintControl() z PaintListener wewnątrz klasy ClockView. 2. Dodaj zmienną seconds inicjalizowaną wartością new Date().getSeconds(). 3. Pobierz stałą SWT.COLOR_BLUE i zapamiętaj ją w zmiennej lokalnej blue. 4. Ustaw kolor tła kontekstu grafiki na blue. 5. Narysuj łuk, używając wcześniejszego wzoru. Kod powinien wyglądać, tak jak poniżej. public void paintControl(PaintEvent e) { int seconds = new Date().getSeconds(); int arc = (15-seconds) * 6 % 360; Color blue = e.display.getSystemColor(SWT.COLOR_BLUE); e.gc.setBackground(blue); e.gc.fillArc(e.x,e.y,e.width-1,e.height-1,arc-1,2); }
6. Uruchom testową wersję Eclipse i wyświetl Widok zegara. Wskazówka sekund pojawi się, ale nie będzie animowana. 7. Zmień rozmiar widoku, by przekonać się, że wskazówka pojawi się w nowym miejscu.
Co się stało? Kod wylicza położenie łuku, by prawidłowo narysować wskazówkę sekund. Ponieważ łuki rysuje się przeciwnie do ruchu wskazówek zegara, sekundy muszą być ujemne. Przesunięcie o 15 wynika z faktu, iż 0 oznacza na zegarze 15 sekund (godzina 3). Następnie mnożymy wszystko przez 6 (60 sekund = 360 stopni), a końcowy wynik dzielimy modulo 360, by mieć pewność, że znajduje się w zakresie do 360 stopni. (Wartość może być ujemna; łuk będzie w tej sytuacji rysowany prawidłowo). Gdy metoda drawArc() używa koloru głównego, metoda fillArc() korzysta z koloru tła. Obiekt gc przechowuje informację o dwóch kolorach, głównym i tła. Standardowo obiekt Color z SWT należałoby wywołać z metodą dispose() po użyciu, ale by uprościć przykład, użyliśmy metody getSystemColor() klasy Display, której wyniku nie trzeba niszczyć.
53
Eclipse 4. Programowanie wtyczek na przykładach
Łuk powstaje od wyliczonej pozycji wskazówki i jest przeciągnięty przez dwa kolejne stopnie. By go wyśrodkować, zaczynamy od pos-1 i kończymy na pos+1. Gdy widok wymaga przerysowania, wywołana zostaje metoda redraw() z Canvas, więc wskazówka ponownie znajdzie się we właściwej pozycji. By jednak uzyskać wrażenie prawdziwego zegara, wszystko musi odbywać się automatycznie. Aktualizację warto realizować tylko wtedy, gdy widok jest aktywny.
Kroki do wykonania — animacja wskazówki sekund Wskazówkę odrysowuje metoda redraw() z Canvas. Musimy ją jednak sami okresowo wyzwalać. Jeżeli odrysowanie będzie odbywało się co sekundę, zasymulujemy prawdziwą wskazówkę sekundnika. Eclipse zawiera mechanizm zwany zadaniami, który byłby odpowiedni dla tego rodzaju prac, ale omówimy je dopiero w rozdziale 4. W tym miejscu użyjemy prostej klasy Thread, by wymuszać ponowne odrysowanie. 1. Otwórz klasę ClockView. 2. Dodaj na dole metody createPartControl() następujący kod. new Thread("TikTak") { public void run() { while (!clock.isDisposed()) { clock.redraw(); try { Thread.sleep(1000); } catch (InterruptedException e) { return; } } } }.start();
3. Uruchom testową wersję Eclipse i otwórz Widok zegara. 4. Otwórz główną instancję i sprawdź widok Console pod kątem błędów.
Co się stało? Tuż po utworzeniu widoku Widok zegara powstaje również wątek Thread. Jest on uruchamiany, by wymuszać przerysowanie raz na sekundę. Niestety, co sekundę w widoku Console głównego Eclipse pojawia się poniższy wyjątek. Exception in thread "TikTak" org.eclipse.swt.SWTException: Invalid thread access at org.eclipse.swt.SWT.error(SWT.java:4361) at org.eclipse.swt.SWT.error(SWT.java:4276)
54
Rozdział 2. • Tworzenie widoków w SWT
at at at at at
org.eclipse.swt.SWT.error(SWT.java:4247) org.eclipse.swt.widgets.Widget.error(Widget.java:775) org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:570) org.eclipse.swt.widgets.Control.redraw(Control.java:2748) com.packtpub.e4.clock.ui.views.ClockView$2.run(ClockView.java:41)
Takiego wyjątku można się było spodziewać po tak napisanym kodzie, ale warto przyjrzeć się szczegółom działania SWT, by dowiedzieć się, dlaczego. Wiele systemów zarządzania interfejsem użytkownika stosuje osobny wątek, który odpowiada za koordynację aktualizacji interfejsu użytkownika przez kod programu. Jeśli wątek ten wykonuje jakąś dłuższą operację, program wydaje się nie reagować na akcje użytkownika. Systemy zarządzania oknami zawierają wbudowany proces, który w przypadku braku szybkiej odpowiedzi z wątku interfejsu wyświetla użytkownikowi kursor klepsydry. SWT również stosuje wątek uaktualniania interfejsu użytkownika i stara się, by wszystkie operacje związane z aktualizacją komponentów SWT odbywały się właśnie w tym wątku. Odrysowanie odbywa się w wątku SWT, podobnie jak wywołania metod, takich jak createPartControl(). Technicznie SWT jest zdolne do obsługi wielu wątków interfejsu użytkownika.
W przykładzie z aktualizacją zegara aktualizacje są zgłaszane przez inny wątek (w tym przypadku wątek TikTak), co skutkuje pojawieniem się wspomnianego wyjątku. W jaki sposób zapewnić wykonanie aktualizacji we właściwym wątku?
Kroki do wykonania — uruchomienie w wątku interfejsu użytkownika Aby wykonać kod w wątku interfejsu użytkownika, trzeba przekazać obiekt Runnable do jednej z dwóch metod klasy Display, takich jak syncExec() i asyncExec(). Metoda syncExec() uruchomi kod w sposób synchroniczny (kod wywołujący zatrzyma się aż do momentu wykonania zadania). Metoda asyncExec() wykona kod w sposób asynchroniczny (kod wywołujący będzie działał dalej, a zadanie wykona się w tle). Klasa Display z SWT to pośrednik odpowiadający monitorowi (system wykonawczy może stosować więcej niż jeden obiekt Display, każdy o innej rozdzielczości). Aby uzyskać dostęp do instancji, trzeba użyć metod Display.getCurrent() lub Display.getDefault(). Czasem jednak znacznie łatwiej pobrać obiekt Display z powiązanego widoku lub widgetu. W tym przypadku wykorzystamy klasę Display powiązaną z obiektem Canvas. 1. Przejdź do wątku TikTak wewnątrz metody createPartControl() klasy ClockView. 2. Wewnątrz metody run() zastąp wywołanie clock.redraw() poniższym kodem.
55
Eclipse 4. Programowanie wtyczek na przykładach
clock.getDisplay().asyncExec(new Runnable() { public void run() { if (clock != null && !clock.isDisposed()) clock.redraw(); } });
3. Uruchom testową wersję Eclipse i wyświetl Widok zegara. Wskazówka sekundnika powinna aktualizować się prawidłowo.
Co się stało? Tym razem zdarzenie wykona się bez przeszkód. Wątek TikTak działa w tle i wybudza co sekundę, by wysłać obiekt Runnable do wątku interfejsu użytkownika, który działa asynchronicznie. Przedstawiony kod mógłby również korzystać z metody syncExec() i różnica nie byłaby zauważalna. Warto jednak stosować wersję asyncExec(), chyba że istnieją konkretne przesłanki do użycia operacji blokującej. Wątek znajduje się w pętli while i jest chroniony wywołaniem clock.isDisposed(). Każdy widget SWT nie znajduje się w stanie usunięcia tuż po utworzeniu. Jego usunięcie powoduje wywołanie metody dispose(). Po usunięciu widgetu wszystkie przypisane mu zasoby systemu operacyjnego są zwalniane i próba wykonania jakiejkolwiek operacji zwróci wyjątek. W tym przypadku obiekt Canvas zostanie usunięty po zamknięciu widoku (widok automatycznie usuwa wszystkie zawarte w nim komponenty). W ten sposób po zamknięciu widoku wątek Thread wyłączy się automatycznie. (Wątek można także przerwać, przerywając mu czas uśpienia). Zauważ, że sprawdzenie, czy widget nie jest równy null i czy nie został usunięty, znajduje się również w kodzie obiektu Runnable. Choć widget nie jest usunięty, gdy dodajemy go do kolejki zdarzeń, może zostać usunięty nieco później.
Kroki do wykonania — tworzenie widgetu wielokrotnego użytku Klasa ClockView wyświetla animowany zegar, jednak utworzenie niezależnego widgetu umożliwia użycie zegara również w innych miejscach. 1. Utwórz nową klasę w pakiecie com.packtpub.e4.clock.ui. Nadaj jej nazwę ClockWidget i upewnij się, że rozszerza klasę Canvas. 2. Utwórz konstruktor przyjmujący parametry parent (typu Composite) oraz style (typu int), które przekazuje do klasy nadrzędnej. public ClockWidget(Composite parent, int style) { super(parent, style); }
3. Przenieś implementację metody paintControl() z ClockView do ClockWidget. Usuń odniesienia do PaintListener z klasy ClockView.
56
Rozdział 2. • Tworzenie widoków w SWT
4. W konstruktorze ClockWidget zarejestruj anonimową klasę PaintListener, która deleguje wywołanie do metody paintControl(). addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { ClockWidget.this.paintControl(e); } });
5. Przenieś wątek TikTak z ClockView do konstruktora ClockWidget; w ten sposób ClockWidget będzie mógł działać niezależnie. Zmień wszystkie odniesienia clock na ClockWidget.this. new Thread("TikTak") { public void run() { while (!ClockWidget.this.isDisposed()) { ClockWidget.this.getDisplay().asyncExec(new Runnable() { public void run() { if (!ClockWidget.this.isDisposed()) ClockWidget.this.redraw(); } }); try { Thread.sleep(1000); } catch (InterruptedException e) { return; } } } }.start();
6. Dodaj metodę computeSize(), dzięki której zegar zawsze będzie okręgiem, bo zostanie użyta minimalna szerokość (width) lub wysokość (height). Ponieważ możliwe jest również przekazanie wartości SWT.DEFAULT równej -1, musimy jawnie obsłużyć ten przypadek. public Point computeSize(int w,int h,boolean changed) { int size; if(w == SWT.DEFAULT) { size = h; } else if (h == SWT.DEFAULT) { size = w; } else { size = Math.min(w,h); } if(size == SWT.DEFAULT) size = 50; return new Point(size,size); }
7. Na końcu zmień wiersz w metodzie createPartControl() z ClockView, który tworzył obiekt Canvas, by zbudował obiekt ClockWidget. final ClockWidget clock = new ClockWidget(parent,SWT.NONE);
8. Uruchom testową wersję Eclipse, a zegar powinien wyglądać podobnie jak wcześniej.
57
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Logikę związaną z rysowaniem przenieśliśmy do osobnego widgetu, a obiekt PaintListener powiązaliśmy z metodą z ClockWidget, by mógł sam się renderować. W ten sposób zegar stał się niezależnym widgetem, którego można użyć w dowolnej aplikacji Eclipse lub SWT. W rzeczywistej aplikacji w każdym z zegarów nie stosowano by osobnego wątku. Najprawdopodobniej albo istniałby jeden wątek obsługujący wszystkie instancje zegarów, albo zostałoby użyte powracające zadanie Jobs z frameworka Eclipse. Więcej informacji na ten temat znajdziesz w rozdziale 4. Technika wykorzystująca klasę anonimową, by dowiązać konkretny typ procedury obsługi do instancji klasy, to wzorzec często spotykany w SWT. Konwencja polega na użyciu tej samej nazwy metody, co w klasie wykorzystywanej przez procedurę; dzięki temu unika się dwuznaczności. (Pamiętaj o ustawieniu procedury obsługi na początku, bo później możesz o niej zapomnieć i dziwić się, dlaczego nie jest wywoływana). Również z tego powodu używamy konstrukcji ClockWidget.this zamiast bezpośredniego wywołania this.paintControl() lub paintControl(), bo doprowadziłoby to do pętli nieskończonej. Możliwe również, że ClockWidget mógłby zaimplementować PaintListener bezpośrednio. W takiej sytuacji w konstruktorze wystarczyłoby wywołanie addPaintListener(this). Nowoczesne systemy JIT w obu sytuacjach zapewnią podobną rzeczywistą ścieżkę wykonania, więc wszystko zależy od stylu programisty i decyzji, czy ClockWidget powinna implementować interfejs PaintListener. Ostatni element to wyliczanie rozmiaru bazujące na wskazówkach. Jest wywoływane przez menedżera układu graficznego do określenia właściwego rozmiaru widgetu. W widgetach o stałym rozmiarze (na przykład w obrazach lub tekstach) przekazywana wartość zależy od układu graficznego. W tym przypadku zwracamy wartości odpowiadające kwadratowi przy zachowaniu wskazówek szerokości i wysokości. Jeśli są bardzo małe, przyjmowana jest wartość 50. Musimy sobie poradzić również z wartością SWT.DEFAULT równą -1.
Kroki do wykonania — korzystanie z układu graficznego widoku Gdy mamy dostęp do klasy ClockWidget, nic nie stoi na przeszkodzie, by do ClockView dodać kilka zegarów. 1. Zmodyfikuj metodę createPartControl() klasy ClockView, by utworzyć trzy instancje ClockWidget. final ClockWidget clock1 = new ClockWidget(parent, SWT.NONE); final ClockWidget clock2 = new ClockWidget(parent, SWT.NONE); final ClockWidget clock3 = new ClockWidget(parent, SWT.NONE);
58
Rozdział 2. • Tworzenie widoków w SWT
2. Uruchom testową wersję Eclipse, by wyświetlić Widok zegara. Pojawią się trzy zegary odmierzające sekundy.
3. W konstruktorze ClockView utwórz nowy obiekt RowLayout z wartością SWT.HORIZONTAL, a następnie ustaw go jako szatę graficzną dla parent (Composite). public void createPartControl(Composite parent) { RowLayout layout = new RowLayout(SWT.HORIZONTAL); parent.setLayout(layout);
4. Ponownie uruchom kod, a zegary znajdą się w poziomym wierszu.
5. Zmień rozmiar widoku, by zegary znalazły się w różnych wierszach.
Klasa RowLayout zawiera kilka pól sterujących sposobem układania widgetów: center — czy komponenty mają być wyśrodkowane, fill — czy należy wypełnić całą dostępną przestrzeń komponentu nadrzędnego, justyfy — czy ułożyć elementy tak, by odległości między nimi były identyczne, pack — czy komponenty powinny uzyskać preferowany przez siebie rozmiar lub być rozszerzane,
by zająć całe miejsce, wrap — czy komponenty powinny przechodzić do następnego wiersza.
Inne opcje to między innymi sterowanie odstępem między elementami (spacing) czy marginesami na krawędziach (marginHeight i marginWidth lub też wyrażone osobno jako marginTop, marginBottom, marginLeft i marginRight).
59
Eclipse 4. Programowanie wtyczek na przykładach
6. Każdy widget SWT posiada opcjonalny obiekt danych układu graficznego, który zależy od układu graficznego stosowanego przez element nadrzędny. W metodzie createPartControl() dodawany jest obiekt RowData do pierwszego i ostatniego zegara. clock1.setLayoutData(new RowData(20,20)); clock3.setLayoutData(new RowData(100,100));
7. Otwórz Widok zegara, a poszczególne zegary będą coraz większe.
Co się stało? Obiekt Composite może przechowywać wiele widgetów, a decyzja o ich wzajemnym rozmieszczeniu należy do odpowiedniego obiektu LayoutManager. Standardowymi menedżerami układu graficznego są: FillLayout, RowLayout, GridLayout, FormLayout i CellLayout (technicznie CellLayout nie należy do SWT, ale do frameworka Eclipse). Domyślnie widoki korzystają z FillLayout, choć ręcznie utworzony obiekt Composite nie ma powiązanego ze sobą układu graficznego. Zarówno FillLayout, jak i RowLayout tworzą pionowe i poziome zbiory widgetów o kontrolowanym rozmiarze. Układ FillLayout jest stosowany domyślnie i ustala rozmiar widgetów tak, by zajęły całą dostępną przestrzeń. Układ RowLayout ustawia wielkość komponentów na ich rozmiar domyślny wyliczany przez computeSize(SWT.DEFAULT, SWT.DEFAULT). Menedżery układu graficznego mają różne właściwości, na przykład SWT.HORIZONTAL i SWT.VERTICAL, które określają, co się stanie, jeśli wiersz będzie pełny. Dokumentacja każdego układu zawiera szczegółowe informacje na temat jego zachowania. Obiekty danych układu graficznego umożliwiają użycie różnych wartości dla poszczególnych obiektów znajdujących się w obiekcie Composite. W przedstawionym przykładzie użyliśmy obiektów RowData. Klasa FillData związana z układem FillLayout nie posiada żadnych publicznych pól, więc nie jest zbyt użyteczna. Niektóre menedżery, na przykład GridLayout, zapewniają bardzo rozbudowany zakres modyfikacji w klasach GridData. Pamiętaj, że zmiana klasy LayoutManager oznacza również konieczność zmiany obiektu danych.
60
Rozdział 2. • Tworzenie widoków w SWT
Quiz — działanie widoków P1. Jaka jest klasa nadrzędna wszystkich tworzonych widoków? P2. W jaki sposób rejestruje się widoki we frameworku Eclipse? P3. Jakie dwa argumenty trafiają do wszystkich widgetów SWT i jakie jest ich znaczenie? P4. Co oznacza usunięcie widgetu? P5. W jaki sposób użyć obiektu Canvas do narysowania okręgu? P6. Jaką procedurę obsługi trzeba zarejestrować, by wykonywać operacje rysowania? P7. Co się stanie, gdy spróbuje się uaktualnić obiekt SWT poza wątkiem interfejsu graficznego? P8. W jaki sposób uaktualnić komponent SWT z poziomu innego wątku? P9. Do czego służy wartość SWT.DEFAULT? P10. W jaki sposób wskazać konkretny rozmiar widgetu w układzie RowLayout?
Sprawdź się — wskazówki minut i godzin Skoro widok o nazwie Widok zegara animuje prawidłowo wskazówkę sekund, wykonaj w podobny sposób wskazówki godzin i minut. Minuty można wyliczyć podobnie jak sekundy; godziny pomnóż przez 5, by uzyskać podobną ścieżkę. Linie dotyczące poszczególnych wartości 5 minut na tarczy zegara narysuj za pomocą funkcji drawLine(). Do wyliczenia punktu początkowego i końcowego linii niezbędne będą proste operacje matematyczne. Na końcu narysuj w odpowiednich miejscach liczby odpowiadające godzinom. Metoda drawText() służy do umieszczenia tekstu w wybranym miejscu. Na środku zegara wyświetl aktualną datę lub aktualny czas.
Zarządzanie zasobami Jednym z wyzwań związanych z SWT jest konieczność zwalniania zasobów, gdy nie są już potrzebne. W odróżnieniu od AWT i Swing, które wszystkie operacje obsługują automatycznie w trakcie czyszczenia pamięci (GC), SWT wymaga ręcznego zarządzania zasobami. Dlaczego SWT wymaga ręcznego zarządzania pamięcią? Bardzo często pojawia się pytanie, dlaczego SWT wymaga ręcznego zarządzania, gdy Java od wielu lat stosuje automatyczne zarządzanie pamięcią i zasobami? Po części wynika to z faktu, iż SWT wymyślono, zanim pojawił się akceptowany mechanizm czyszczenia pamięci, ale również z chęci jak najszybszego zwracania zasobów systemu, gdy nie są już potrzebne.
61
Eclipse 4. Programowanie wtyczek na przykładach
Z perspektywy wydajnościowej dodanie metody finalize() do obiektu powoduje, że automatyczne oczyszczanie pamięci będzie działało z trudem; duża szybkość dzisiejszych mechanizmów oczyszczania wynika z faktu, iż nie muszą takich metod wywoływać, bo nie są one zdefiniowane. Szkodziłoby to również SWT, ponieważ obiekt musi operację usunięcia zgłosić do wątku interfejsu użytkownika, co opóźnia proces oczyszczania.
Nie wszystkie obiekty trzeba usuwać; w zasadzie istnieje abstrakcyjna klasa Resource będąca przodkiem wszystkich zasobów wymagających usunięcia. To właśnie ona implementuje metodę dispose() i obsługuje wywołanie isDisposed(). Po usunięciu zasobu próby użycia jego metod spowodują zgłoszenie wyjątku z komunikatem Widget is disposed lub Graphic is disposed. Aby jeszcze bardziej utrudnić całe zagadnienie, kod wywołujący nie powinien niszczyć niektórych instancji Resource. Najczęściej nie powinny być niszczone instancje znajdujące się w metodach dostępowych innych klas. Przykładowo instancja Color zwracana przez metodę getSystemColor() klasy Display należy do klasy Display, więc nie należy jej niszczyć. Zasoby, które utworzył konkretny kod, powinny być jawnie niszczone przez ten kod.
Kroki do wykonania — więcej kolorów Aby dodać opcję, za pomocą której ClockWidget będzie mógł stosować różne kolory, trzeba pobrać odpowiednią instancję zamiast zaszytej na stałe referencji na BLUE. Ponieważ obiekty Color to pochodne Resource, muszą zostać prawidłowo usunięte w momencie usuwania widgetu. Aby uniknąć bezpośredniego przekazywania instancji Color, konstruktor będzie obsługiwał wartości RGB (trzy wartości typu int) i użyje obiektu Color do przechowania uzyskanych wartości. Czas życia instancji Color powiążemy z czasem życia obiektu ClockWidget. 1. Dodaj instancję private final Color o nazwie color do obiektu ClockWidget. private final Color color;
2. Zmodyfikuj konstruktor ClockWidget, by przyjął instancję RGB i użył jej do utworzenia obiektu Color. W tej chwili obiekt color będzie wyciekał. Zostanie to poprawione w przyszłości. public ClockWidget(Composite parent, int style, RGB rgb) { super(parent, style); // FIXME color wycieka! this.color = new Color(parent.getDisplay(),rgb); ...
3. Zmodyfikuj metodę paintControl(), by korzystała z wybranego koloru. protected void paintControl(PaintEvent e) { ... e.gc.setBackground(color); e.gc.fillArc(e.x, e.y, e.width-1, e.height-1, arc-1, 2);
4. Na końcu zmień kod instancji ClockView, by tworzyła trzy zegary o różnych kolorach. 62
Rozdział 2. • Tworzenie widoków w SWT
public void createPartControl(Composite parent) { ... final ClockWidget clock1 = new ClockWidget(parent, SWT.NONE, new RGB(255,0,0)); final ClockWidget clock2 = new ClockWidget(parent, SWT.NONE, new RGB(0,255,0)); final ClockWidget clock3 = new ClockWidget(parent, SWT.NONE, new RGB(0,0,255));
5. Uruchom aplikację, by zobaczyć nowe kolory.
Co się stało? Obiekt Color powstał na podstawie wartości czerwieni, zieleni i niebieskiego przekazanych do konstruktora ClockWidget. Ponieważ RGB to jedynie obiekt z wartościami, nie trzeba go jawnie niszczyć. Po utworzeniu obiekt Color trafia do pola instancji. W momencie rysowania wskazówki mają odpowiedni kolor. Jedyny problem związany z tym rozwiązaniem wynika z wycieku instancji Color. Gdy widok będzie niszczony, powiązany z nim obiekt Color trafi do mechanizmu oczyszczania pamięci, ale użyty przez niego zasób systemowy nie zostanie zwolniony.
Kroki do wykonania — znajdowanie wycieku Warto wiedzieć, ile zasobów zostało zaalokowanych, by stwierdzić, czy występuje wyciek, czy też nie. Na szczęście, SWT zapewnia odpowiedni mechanizm wykorzystujący klasy Display i DeviceData. Najczęściej używa się osobnej wtyczki, ale w tym przykładzie zmodyfikujemy klasę ClockView. 1. Na początku metody createPartControl() klasy ClockView dodaj wywołanie, które pobierze liczbę zaalokowanych obiektów przy użyciu obiektu DeviceData klasy Display. public void createPartControl(Composite parent) { Object[] oo=parent.getDisplay().getDeviceData().objects;
63
Eclipse 4. Programowanie wtyczek na przykładach
2. Przejdź przez zaalokowane obiekty, zliczając instancje Color. int c = 0; for (int i = 0; i < oo.length; i++) if (oo[i] instanceof Color) c++;
3. Wyświetl liczbę znalezionych instancji na standardowym strumieniu błędów. System.err.println("Istnieje " + c + " instancji Color");
4. Uruchom testową instancję Eclipse w trybie debugowania i wyświetl Widok zegara. Widok Console głównego Eclipse powinien wyświetlić następujące wpisy. Istnieje 0 instancji Color Istnieje 0 instancji Color Istnieje 0 instancji Color Ze względu na chęć uzyskania największej wydajności SWT nie zawsze śledzi wszystkie alokowane zasoby. Można jednak takie śledzenie włączyć w trakcie uruchamiania poprzez plik options zawierający pary klucz=wartość. Włączenie śledzenia wymaga użycia znacznika -debug. Łatwo włącza się odpowiednią konfigurację w zakładce Tracing konfiguracji uruchomieniowej.
5. Zamknij testową wersję Eclipse, jeśli jest uruchomiona. 6. Przejdź do konfiguracji uruchomieniowej poleceniem Run/Debug Configurations z menu. 7. Zaznacz Eclipse Application (jeśli nie jest zaznaczone) i przejdź do zakładki Tracing. Włącz opcję Enable tracing i zaznacz wtyczkę org.eclipse.ui. Włącz opcje debug (na górze listy po prawej) oraz trace/graphics.
64
Rozdział 2. • Tworzenie widoków w SWT
8. Uruchom konfigurację, klikając przycisk Debug. Następnie kilkakrotnie otwórz i zamknij Widok zegara. Istnieje Istnieje Istnieje Istnieje
73 76 79 82
instancji instancji instancji instancji
Color Color Color Color
Co się stało? Wyraźnie widać, że po każdym otwarciu widoku wyciekają trzy instancje Color. Nie powinno dziwić, że to akurat wartość 3, ponieważ każda z trzech tworzonych instancji ClockWidget używa własnej wersji obiektu Color. To oznacza, że wyciek zasobu występuje w klasach ClockWidget lub ClockView. Gdy SWT działa w trybie śledzenia, przechowuje listę wszystkich zaalokowanych zasobów w globalnej liście dostępnej w obiekcie DeviceData. Jeśli zasób zostanie zniszczony, SWT usunie go z listy. Umożliwia to monitorowanie stanu zasobów i łatwe znajdowanie wycieków — wystarczy kilkakrotnie powtórzyć akcję i zobaczyć zwiększenie licznika zasobów. Lista przechowuje również inne rodzaje obiektów (na przykład Font i Image), więc warto przefiltrować dane według typu zasobu. Warto pamiętać o tym, że system wykonawczy Eclipse również używa zasobów, więc i one znajdują się na liście. Umiejętność włączenia śledzenia i programowego wykrycia alokowanych obiektów ułatwia znalezienie wycieków, a także późniejsze potwierdzenie ich usunięcia.
Kroki do wykonania — zatykanie wycieku Skoro wiemy o wycieku, trzeba go naprawić. Rozwiązaniem jest wywołanie metody dispose() obiektu Color, gdy nie będzie już potrzebny, czyli w momencie usuwania widoku. Szybkie sprawdzenie ClockWidget sugeruje, że wystarczy zastosować własną wersję metody dispose() (to nie jest właściwe rozwiązanie; wyjaśnienie, dlaczego, znajduje się dalej w tym rozdziale). 1. Utwórz w klasie ClockWidget metodę dispose() zawierającą następujący kod. @Override public void dispose() { if(color != null && !color.isDisposed()) color.dispose(); super.dispose(); }
65
Eclipse 4. Programowanie wtyczek na przykładach
2. Uruchom aplikację Eclipse w trybie debugowania (z włączonym śledzeniem) i ponownie kilkakrotnie zamknij i utwórz widok. Wynik będzie podobny do poniższego. Istnieje Istnieje Istnieje Istnieje
87 91 94 98
instancji instancji instancji instancji
Color Color Color Color
3. Usuń metodę dispose (ponieważ nie działa zgodnie z oczekiwaniami) i zmodyfikuj konstruktor ClockWidget, dodając anonimową klasę DisposeListener, która niszczy obiekt Color. public ClockWidget(Composite parent, int style, RGB rgb) { super(parent, style); this.color = new Color(parent.getDisplay(),rgb); addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { if(color != null && !color.isDisposed()) color.dispose(); } }); }
4. Uruchom testową wersję Eclipse, by zobaczyć, co się stanie przy ponownym otwieraniu i zamykaniu widoku. Istnieje Istnieje Istnieje Istnieje
87 88 88 88
instancji instancji instancji instancji
Color Color Color Color
Wyciek udało się załatać.
Co się stało? Gdy udało się znaleźć przyczynę wycieku, trzeba w odpowiednim momencie wywołać metodę dispose() obiektu Color. Choć wydawało się, że problem uda się rozwiązać przez przesłonięcie metody dispose() z ClockWidget, w rzeczywistości rozwiązanie to nie zadziałało. Metoda dispose() jest wywoływana jedynie na głównym poziomie, czyli w klasie Shell (lub ViewPart) i jeśli nie istnieją obiekty nasłuchujące, metoda usuwająca nie zostanie wywołana dla komponentów podrzędnych. Ponieważ ten sposób działania nie jest zbyt intuicyjny, warto było go sprawdzić, by dobrze zapamiętać, że należy go unikać. Wykrywanie i naprawa wycieku zasobów to żmudny proces. Istnieje wtyczka napisana przez zespół SWT, która wykonuje migawki zasobów i sprawdza, czy nie nastąpił ich wyciek. Sposób działania przypomina technikę zastosowaną w przykładzie. Wtyczkę można znaleźć na witrynie aktualizacji narzędzi SWT (użyj adresu http://www.eclipse.org/swt/tools.php i poszukaj wtyczki Sleak). Nie wymaga ona modyfikacji kodu (jak w przedstawionym przykładzie), by monitorować wszystkie alokowane zasoby.
66
Rozdział 2. • Tworzenie widoków w SWT
Wykonując testy, warto pamiętać, że pierwszy lub drugi przebieg może dawać inne wyniki, ponieważ najczęściej w międzyczasie będą alokowane również inne zasoby. Uzyskaj przynajmniej kilka wyników przed analizą danych i nie zapominaj, że inne wtyczki (szczególne działające w tle) również alokują zasoby. Korzystając z widgetów SWT, warto stosować pewną dobrą praktykę, a mianowicie zawsze sprawdzać, czy zasób został już zwolniony, czy też nie. Dokumentacja metody dispose() twierdzi, że nie jest to konieczne, bo zasoby, które zostały już zwolnione, powodują, że metoda jest operacją pustą.
Quiz — działanie zasobów P1. Z czego wynikają wycieki zasobów? P2. Jakie są różne rodzaje zasobów (obiektów Resource)? P3. W jaki sposób włączyć śledzenie zasobów SWT? P4. Gdy śledzenie zostanie włączone, w jaki sposób sprawdzić konkretne obiekty? P5. Jaki jest nieodpowiedni i właściwy sposób zwalniania zasobów?
Sprawdź się — rozbudowa widgetu zegara Ponieważ widget ClockWidget działa prawidłowo, spróbuj wykonać następujące zadania. Napisz widok typu Sleak, który okresowo zlicza zaalokowane obiekty według typu. Zmodyfikuj tekst zegara przez pobranie obiektu Font i jego zwolnienie. Wykonaj ogólny obiekt nasłuchiwania, który przyjmuje instancję obiektu Resource. Dodaj metodę setColor() umożliwiającą zmianę koloru.
Interakcja z użytkownikiem Podstawowym zadaniem interfejsu użytkownika jest interakcja z tym użytkownikiem. Posiadanie widoku, który wyświetla informacje, jest pomocne, ale najczęściej trzeba poprosić użytkownika o dane lub reagować na jego akcje.
Kroki do wykonania — uzyskiwanie aktywności Aby zmienić strefę czasową widgetów zegara, dodamy do widoku listę rozwijaną (typ Combo) oraz przycisk (typ Button). Obiekt Combo otrzyma tablicę obiektów String reprezentujących identyfikatory stref czasowych.
67
Eclipse 4. Programowanie wtyczek na przykładach
1. Utwórz pole timezones w klasie ClockView. private Combo timezones;
2. Na końcu metody createPartControl() dodaj kod tworzący listę rozwijaną. public void createPartControl(Composite parent) { ... String[] ids = TimeZone.getAvailableIDs(); timezones = new Combo(parent, SWT.DROP_DOWN); timezones.setVisibleItemCount(5); for (int i = 0; i < ids.length; i++) { timezones.add(ids[i]); } }
3. Uruchom testową wersję Eclipse i wyświetl Widok zegara, by zobaczyć listę stref czasowych.
4. Przyjęło się, by ustawiać aktywność konkretnego widgetu w momencie otwierania widoku. Dodaj odpowiedni kod metody setFocus() w klasie ClockView. public void setFocus() { timezones.setFocus(); }
5. Uruchom ponownie testową wersję Eclipse i otwórz widok, by przekonać się, że lista rozwijana będzie domyślnie aktywna.
Co się stało? Każdy obiekt Control z SWT zawiera metodę setFocus(), która powoduje przełączenie aktywności aplikacji do tego konkretnego widgetu. Gdy widok staje się aktywny (gdy zostanie otwarty lub użytkownik przełączy się do niego z innego widoku), Eclipse wywołuje metodę setFocus().
E4: Zgodnie z rozdziałem 7. w Eclipse 4 metoda setFocus() może nosić dowolną nazwę — wystarczy użyć adnotacji @Focus. Warto jednak dla własnej wygody nadać jej nazwę setFocus().
68
Rozdział 2. • Tworzenie widoków w SWT
Kroki do wykonania — reakcja na działania użytkownika Aby pokazać efekt zmiany strefy czasowej, musimy do zegara dodać wskazówkę godzinową. Gdy zostanie wybrana strefa czasowa, wskazówka godzinowa zmieni położenie. 1. Dodaj w klasie ClockWidget pole offset i jego metodę ustawiającą. private int offset; public void setOffset(int offset) { this.offset = offset; }
2. Metody ustawiające i pobierające można wygenerować automatycznie. Po dodaniu pola użyj polecenia Source/Generate Getters and Setters, by wygenerować wszystkie brakujące metody; żeby wygenerować pojedynczą parę metod, wpisz set w treści klasy i naciśnij kombinację klawiszy Ctrl+Spacja (Cmd+Spacja w systemie OS X). 3. Dodaj wskazówkę godzinową w metodzie paintControl(). e.gc.setBackground(e.display.getSystemColor(SWT.COLOR_BLACK)); int hours = new Date().getHours() + offset; arc = (3 - hours) * 30 % 360; e.gc.fillArc(e.x, e.y, e.width-1, e.height-1, arc - 5, 10);
4. Aby uaktualnić zegar po zmianie strefy czasowej, zarejestruj obiekt SelectionListener w obiekcie Combo w metodzie createPartControl() klasy ClockView. timezones.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { String z = timezones.getText(); TimeZone tz = z == null ? null : TimeZone.getTimeZone(z); TimeZone dt = TimeZone.getDefault(); int offset = tz == null ? 0 : (tz.getOffset(System.currentTimeMillis()) dt.getOffset(System.currentTimeMillis())) / 3600000; clock3.setOffset(offset); clock3.redraw(); } public void widgetDefaultSelected(SelectionEvent e) { clock3.setOffset(0); clock3.redraw(); } });
5. Uruchom testową wersję Eclipse i zmień strefę czasową. Ostatni zegar powinien się zaktualizować.
69
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Metoda addSelectionListener() klasy Combo służy do zarejestrowania obiektu, który będzie powiadamiany o zmianach na liście rozwijanej. Po otrzymaniu powiadomienia kod używa tekstu z listy rozwijanej do wyszukania w klasie TimeZone przesunięcia czasowego odpowiadającego wybranej strefie czasowej. Od wyniku odejmuje się przesunięcie aktualnej strefy czasowej. Ponieważ czas jest podawany w milisekundach, wynik jest dzielony przez 1000, a następnie przez 60 razy 60, by uzyskać wartość w godzinach. (Dodatkowo uzyskana wartość jest liczbą całkowitą). Jeśli użytkownik wybrał wartość domyślną (a tak naprawdę brak wartości) lub strefa czasowa nie została znaleziona, przesunięcie otrzymuje wartość 0. Wskazówka godzinowa nie do końca działa prawidłowo; w większości zegarków jest krótsza od wskazówki sekundnika i nie przeskakuje między pełnymi godzinami, ale porusza się płynnie. Odpowiednie modyfikacje warto wykonać jako ćwiczenie dodatkowe. Aby wyświetlić wprowadzone zmiany, kod nakazuje zegarowi ponowne przerysowanie się. Zadanie to można by wykonać z poziomu metody setOffset() klasy ClockView, ale wtedy konieczne byłoby sprawdzenie, czy operacja odbywa się w wątku SWT, lub trzeba by zlecić to zadanie wątkowi SWT asynchronicznie. Dla wygody clock3.redraw() zostaje wywołane tuż po ustawieniu przesunięcia, bo z pewnością jest to wątek SWT.
Quiz — działanie widgetów P1. W jaki sposób oznaczyć widget jako domyślny w danym widoku? P2. W jaki sposób uaktualnić widget po jego modyfikacji? P3. Jakie metody nasłuchujące można zarejestrować w widgecie Combo? P4. Jakie jest zadanie metody widgetDefaultSelected()?
Sprawdź się — aktualizacja widgetu zegara Skoro widget ClockWidget obsługuje strefy czasowe, wykonaj następujące zadania. Uaktualnij wskazówkę godzinową tak, by poruszała się płynnie między godzinami. Wyświetl strefę czasową na tarczy widgetu ClockWidget. Zmodyfikuj klasę ClockWidget w taki sposób, by przyjmowała strefę czasową
zamiast przesunięcia. Wyświetlaj informację, czy przedstawiany czas to czas letni, czy zimowy.
70
Rozdział 2. • Tworzenie widoków w SWT
Korzystanie z innych widgetów SWT SWT zawiera wiele różnych widgetów innych niż Canvas. W tej części rozdziału przedstawimy niektóre z nich. JFace będzie omawiane w następnym rozdziale i choć zapewnia wygodny model MVC (Model-View-Controller) do obsługi interfejsów graficznych, warto wiedzieć co nieco o podstawowych klasach SWT, z których korzysta.
Kroki do wykonania — dodanie elementów do zasobnika Większość systemów operacyjnych stosuje koncepcję zasobnika systemowego, czyli zbioru ikon widocznych w głównym oknie i zapewniających szybki dostęp do komponentów. W systemie OS X są to ikony na górnym pasku menu, a w systemie Windows to ikony obok zegara w prawym dolnym narożniku. W systemach typu Linux zasobniki pojawiają się w różnych miejscach, a czasem nawet nie występują. Ponieważ istnieje tylko jeden obiekt Tray, element musi zostać dodany dokładnie raz. Klasa Activator zapewnia, że element TrayItem będzie tworzony przy starcie i usuwany przy wyłączaniu. 1. Otwórz klasę Activator i dodaj dwa pola prywatne. private TrayItem trayItem; private Image image;
2. Dodaj do metody start() poniższy kod. final Display display = Display.getDefault(); display.asyncExec(new Runnable() { public void run() { image = new Image(display, Activator.class.getResourceAsStream ("/icons/sample.gif")); Tray tray = display.getSystemTray(); if (tray != null && image != null) { trayItem = new TrayItem(tray, SWT.NONE); trayItem.setToolTipText("Witaj, świecie"); trayItem.setVisible(true); trayItem.setText("Witaj, świecie"); trayItem.setImage(new Image(trayItem.getDisplay(), Activator.class.getResourceAsStream("/icons/sample.gif"))); } } });
3. Uruchom testową wersję Eclipse i wyświetl Widok zegara. Mała ikona sample.gif pojawi się w obszarze zasobnika (na górze po prawej w systemie OS X; na dole po prawej w systemie Windows). 4. Aby przetestować zatrzymywanie i ponownie uruchamianie paczki, otwórz widok Console w testowej instancji Eclipse. Kliknij listę rozwijaną na górze po prawej stronie widoku i utwórz konsolę Host OSGi Console.
71
Eclipse 4. Programowanie wtyczek na przykładach
WARNING: This console is connected to the current running instance of Eclipse!osgi> "Framework is launched."
5. Wpisz ss clock w wierszu poleceń, by wyświetlić identyfikator paczki używany do jej zatrzymania lub uruchomienia. osgi> ss clock id State Bundle 4 RESOLVED com.packtpub.e4.clock.ui_1.0.0.qualifier
6. Zatrzymaj i uruchom paczkę, wpisując start i stop w konsoli z utrzymanym identyfikatorem paczki. osgi> osgi> osgi> osgi>
stop 4 start 4 stop 4 start 4
7. Zauważ, że nowy element TrayItem pojawia się w zasobniku (Tray) po każdym uruchomieniu. Metodę stop() klasy Activator trzeba wzbogacić o kod czyszczący. public void stop(BundleContext context) throws Exception { if (trayItem != null && !trayItem.isDisposed()) { Display.getDefault().asyncExec(new Runnable() { public void run() { if (trayItem != null && !trayItem.isDisposed()) trayItem.dispose(); } }); } if (image != null && !image.isDisposed()) { Display.getDefault().asyncExec(new Runnable() { public void run() { if (image != null && !image.isDisposed()) image.dispose(); } }); } }
8. Ponownie uruchom aplikację, a następnie zatrzymaj i uruchom paczkę (prawdopodobnie identyfikator będzie taki sam, ale warto to sprawdzić). Ikona będzie się pojawiała po włączeniu paczki i zniknie po jej wyłączeniu.
Co się stało? Obiekt TrayItem z SWT trafia do zasobnika systemowego w momencie uruchomienia paczki i jest z niego usuwany po zatrzymaniu paczki. Wyświetlana ikona należy do przykładowego projektu. Używając innej, warto pamiętać o aktualizacji pliku build.properties.
72
Rozdział 2. • Tworzenie widoków w SWT
Ponieważ zasobnik wyświetla ikony, gdy jej zabraknie, w zasobniku nic się nie pojawi. Testy podpowiedzi nie są wymagane. Warto pamiętać, że nie każdy system musi posiadać zasobnik, więc warto obsłużyć sytuację, w której metoda display.getSystemTray() zwraca wartość null. Paczka jest uruchamiania automatycznie w momencie wczytania. Za wczytanie odpowiada wywołanie polecenia z menu. Innymi słowy, otwarcie widoku wczytuje klasę i dodaje ikonę. Uruchomienie i zatrzymanie można też zrealizować programowo lub poprzez konsolę OSGi, co umożliwia sprawdzenie prawidłowego funkcjonowania metod stop() i start() klasy Activator.
Kroki do wykonania — reakcja na akcje użytkownika Gdy użytkownik kliknie ikonę, nic się nie dzieje. Wynika to z faktu, iż obiekt TrayIcon nie posiada zarejestrowanej metody nasłuchującej. Istnieją dwie osobne procedury nasłuchiwania, które można zarejestrować: SelectionListener wywoływana po kliknięciu ikony oraz MenuDetectListener reagująca na menu kontekstowe. Pierwsza posłuży do wyświetlenia zegara w osobnym oknie, które w SWT reprezentuje klasa Shell. 1. Otwórz klasę Activator. 2. Dodaj pole przechowujące obiekt Shell. private Shell shell;
3. Przejdź do metody run() wewnątrz klasy anonimowej Runnable w metodzie start() klasy Activator. 4. Po utworzeniu obiektu TrayItem wywołaj metodę addSelectionListener(), której procedura obsługi utworzy nowy obiekt Shell zawierający ClockView. trayItem.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { if (shell == null) { shell = new Shell(trayItem.getDisplay()); shell.setLayout(new FillLayout()); new ClockWidget(shell, SWT.NONE, new RGB(255, 0, 255)); shell.pack(); } shell.open(); } });
5. Uruchom testową wersję Eclipse, otwórz Widok zegara i kliknij ikonę w przyborniku systemowym. Zegar pojawi się w nowym oknie.
73
Eclipse 4. Programowanie wtyczek na przykładach
6. Musisz jeszcze zapewnić usunięcie obiektu Shell przy zatrzymywaniu paczki, więc dodaj poniższy kod na końcu metody stop() klasy Activator. if (shell != null && !shell.isDisposed()) { Display.getDefault().asyncExec(new Runnable() { public void run() { if (shell != null && !shell.isDisposed()) shell.dispose(); } }); }
7. Uruchom testową wersję Eclipse, kliknij ikonę z przybornika i użyj konsoli OSGi, by zatrzymać i ponownie uruchomić paczkę. W momencie zatrzymania paczki okno powinno zniknąć.
Co się stało? Po zainstalowaniu obiektu TrayItem w obiekcie Tray można zarejestrować procedury obsługi nasłuchujące działań użytkownika. Kliknięcie ikony powoduje wywołanie obiektu powiązanego z SelectionListener, w którym mamy szansę wyświetlić nowe okno (obiekt Shell w SWT). Przy tworzeniu obiektu Shell wykorzystujemy obiekt Display powiązany z TrayItem. Choć można by użyć metod Display.getDefault() lub Display.getCurrent(), żadna z nich nie byłaby odpowiednia. Ponieważ programiści często stosują wiele monitorów lub też wirtualny wyświetlacz obejmuje swym zasięgiem wiele pulpitów, warto upewnić się, że obiekt Shell będzie powiązany z tym samym wyświetlaczem, co obiekt Tray. Bez obiektu LayoutManager zegar się nie pojawi. Obiekt FillLayout zapewnia, iż zegar będzie tak samo duży jak okno (zmiana rozmiaru okna spowoduje zmianę rozmiaru zegara). Po utworzeniu okna wywołujemy metodę pack(), która ustawia rozmiar okna na zalecany rozmiar znajdujących się w nim elementów; w tym przypadku na domyślny rozmiar ClockView. Metoda open() na samym końcu kodu otwiera okno.
Kroki do wykonania — obiekty modalne i inne efekty W oknach można stosować wiele różnych bitów sterujących sposobem ich działania lub wyglądem. Przykładowo nic nie stoi na przeszkodzie, by zegar był częściowo przezroczysty lub znajdował się nad innymi oknami. Obiekt Shell z SWT zawiera wiele interesujących opcji. 1. Zmodyfikuj tworzenie klasy Shell wewnątrz metody widgetSelected() w klasie wewnętrznej klasy Activator, ustawiając bity SWT.NO_TRIM (brak przycisków zamknięcia, minimalizacji i maksymalizacji okna) oraz SWT.ON_TOP (okno zawsze znajduje się nad innymi oknami). shell = new Shell(trayItem.getDisplay(),SWT.NO_TRIM|SWT.ON_TOP);
74
Rozdział 2. • Tworzenie widoków w SWT
2. Ustaw okno na półprzezroczyste. shell.setAlpha(128);
3. Uruchom testową wersję Eclipse i kliknij ikonę z przybornika systemowego, by zobaczyć, jakie okno zostanie zbudowane. 4. Aby utworzyć okno modalne (które wyłączy możliwość interakcji z głównym oknem), zmień używane przełączniki na SWT.APPLICATION_MODAL. shell = new Shell(trayItem.getDisplay(),SWT.APPLICATION_MODAL);
5. Aby aplikacja była wyświetlana na pełnym ekranie, w zależności od systemu operacyjnego lub platformy, wywołaj metodę setFullscreen() lub setMaximized(). shell.setFullScreen(true); shell.setMaximized(true);
6. Pamiętaj, że brak przycisków zamykania okna oznacza, że trzeba podobnie działające przyciski zapewnić samodzielnie. 7. Uruchom testową wersję Eclipse, by przekonać się, jaki wpływ mają nowe przełączniki. 8. Z powrotem zmień przełączniki obiektu Shell na SWT.NO_TRIM i SWT.ON_TOP. 9. Aby utworzyć „pływające” okno o kształcie zegara, dodaj metodę circle() do klasy Activator. Kod metody pochodzi z pliku Snippet134.java dostępnego na stronie http://www.eclipse.org/swt/snippets/. private static int[] circle(int r, int offsetX, int offsetY) { int[] polygon = new int[8 * r + 4]; //x^2 + y^2 = r^2 for (int i = 0; i < 2 * r + 1; i++) { int x = i - r; int y = (int)Math.sqrt(r*r - x*x); polygon[2*i] = offsetX + x; polygon[2*i+1] = offsetY + y; polygon[8*r - 2*i - 2] = offsetX + x; polygon[8*r - 2*i - 1] = offsetY - y; } return polygon; }
10. Na końcu zmień kształt okna na kołowy, stosując obiekt Region w obiekcie Shell. W ten sposób uzyskasz efekt, jakby to sam zegar unosił się nad pulpitem. Dodaj poniższy kod w metodzie widgetSelected() po utworzeniu obiektu Shell. final Region region = new Region(); region.add(circle(25, 25, 25)); shell.setRegion(region);
11. Zegar po uruchomieniu będzie podobny do tego na poniższym zrzucie ekranu.
75
Eclipse 4. Programowanie wtyczek na przykładach
12. Dodaj procedurę obsługi zdarzenia niszczenia, by usunąć obiekt Region. shell.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { if (region != null && !region.isDisposed()) region.dispose(); } });
Co się stało? Zmiana znaczników używanych przy tworzeniu obiektu Shell zmienia sposób wyświetlania okna i interakcji z użytkownikiem. Dodatkowe metody obiektu umożliwiają przejście w stan pełnego ekranu lub minimalizację (albo maksymalizację) okna, co czasem bywa pomocne. Niektóry systemy okien rozróżniają stan maksymalizacji od pełnego ekranu, inne nie. Znacznik SWT.NO_TRIM służy do wyświetlenia okna bez standardowej ramki i przycisków. Można to połączyć z ustawianiem regionów metodą setRegion(), by utworzyć okno o kształcie innym niż prostokąt. Najczęściej okna bez ramki są tak zwanymi oknami pływającymi i pozostają ponad innymi oknami aplikacji nawet wtedy, kiedy nie są aktywne. By to osiągnąć, wystarczy użyć znacznika SWT.ON_TOP i zmienić przezroczystość okna za pomocą metody setAlpha(). Przekazywana wartość musi znajdować się w przedziale od 0 (pełna przezroczystość) do 255 (całkowity brak przezroczystości). Region definiuje się jako zbiór połączonych ze sobą punktów lub też ustawia się go piksel po pikselu. Warto pamiętać, że obiekt Region to zasób, więc trzeba go właściwie usunąć (najczęściej w momencie usuwania obiektu Shell). Operacja usunięcia była już stosowana wcześniej i polega na dodaniu procedury obsługi metodą addDisposeListener() obiektu Shell.
Kroki do wykonania — grupy i zakładki Nowy widok TimeZoneView wyświetli listę zegarów ze strefami czasowymi z różnych regionów świata. Tym razem jednak, zamiast korzystać z kreatora, dodamy rozszerzenie samodzielnie.
E4: Sposób definiowania widoków w Eclipse 4 omówiony został w rozdziale 7. W tym rozdziale opisano tworzenie okien w Eclipse 3.x. Eclipse 4.x zawiera warstwę zgodności z wersją 3.x.
1. Kliknij projekt prawym przyciskiem myszy i przejdź do Plug-in Tools/Open Manifest lub znajdź plik plugin.xml i kliknij go dwukrotnie. 2. Przejdź do zakładki Extensions edytora. Lista po lewej zawiera wpis org.eclipse.ui.view. Rozwiń go, a pojawią się wpisy Śledzenie czasu (category) i Widok zegara (view) dodane przez kreator wtyczek.
76
Rozdział 2. • Tworzenie widoków w SWT
3. Prawym przyciskiem myszy kliknij org.eclipse.ui.view i z menu wybierz New/view. Na liście pojawi się tymczasowa nazwa name (view). Po prawej stronie znajdą się jej właściwości, które należy zmienić. Zmiany są następujące. W polu ID wpisz com.packtpub.e4.clock.ui.views.TimeZoneView.
W polu Name wpisz Widok strefy czasowej.
W polu Class wpisz com.packtpub.e4.clock.ui.views.TimeZoneView.
W polu Category wpisz com.packtpub.e4.clock.ui.
W polu Icon wpisz icons/sample.gif.
4. Zapisz plik. Konfigurator umieścił w pliku plugin.xml następujący wpis.
5. Utwórz klasę TimeZoneView. Najłatwiej wykonać to zadanie, klikając etykietę class* obok nazwy klasy w ustawieniach widoku o nazwie Widok strefy czasowej z zakładki Extensions pliku plugin.xml. Alternatywne rozwiązanie polega na użyciu kreatora New Class, wywoływanego poleceniem menu File/New/Class, i utworzeniu klasy TimeZoneView jako podklasy ViewPart umieszczonej w pakiecie com.packtpub.e4.clock.ui.views. 6. Utwórz klasę o nazwie TimeZoneComparator, która implementuje interfejs Comparator i znajduje się w nowym pakiecie com.packtpub.e4.clock.ui.internal. Klasy pomocnicze warto umieszczać w osobnym pakiecie, który nie będzie widoczny dla pozostałej części kodu. Metoda compare() powinna delegować realizację właściwego porównania do metody compareTo() klasy TimeZone. public class TimeZoneComparator implements Comparator { public int compare(Object o1, Object o2) { if(o1 instanceof TimeZone && o2 instanceof TimeZone) { return ((TimeZone) o1).getID().compareTo(((TimeZone) o2).getID()); } else { throw new IllegalArgumentException(); } } }
7. Do klasy TimeZoneComparator dodaj nową metodę typu public static o nazwie getTimeZones(), która zwróci obiekt Map zawierający zbiory (Set) obiektów TimeZones. Obiekt Map będzie indeksowany pierwszą połową identyfikatora klasy TimeZone. Identyfikator składa się z dwóch części, na przykład Europe/Milton_Keynes lub America/New_York. W ten sposób wszystkie strefy czasowe dotyczące Europy znajdą się w jednej grupie, a Ameryki w drugiej grupie.
77
Eclipse 4. Programowanie wtyczek na przykładach
public static Map getTimeZones(){ String[] ids = TimeZone.getAvailableIDs(); Map timeZones = new TreeMap(); for (int i = 0; i < ids.length; i++) { String[] parts = ids[i].split("/"); if (parts.length == 2) { String region = parts[0]; Set zones = timeZones.get(region); if (zones == null) { zones = new TreeSet(new TimeZoneComparator()); timeZones.put(region, zones); } TimeZone timeZone = TimeZone.getTimeZone(ids[i]); zones.add(timeZone); } } return timeZones; }
8. W metodzie createPartControl() klasy TimeZoneView utwórz obiekt CTabFolder i umieść w nim po jednym obiekcie CTabItem dla każdej grupy stref czasowych. public void createPartControl(Composite parent) { Map timeZones = TimeZoneComparator.getTimeZones(); CTabFolder tabs = new CTabFolder(parent, SWT.BOTTOM); Iterator regionIterator = timeZones. entrySet().iterator(); while(regionIterator.hasNext()) { Entry region = regionIterator.next(); CTabItem item = new CTabItem(tabs, SWT.NONE); item.setText(region.getKey()); } tabs.setSelection(0); }
9. Uruchom przykład i wyświetl Widok strefy czasowej. Na dole powinna znajdować się długa lista zakładek.
78
Rozdział 2. • Tworzenie widoków w SWT
10. Wewnątrz pętli while dodaj instancję Composite przechowującą wiele klas ClockWidget dla każdej instancji TimeZone. item.setText(region.getKey()); // z wcześniejszej części Composite clocks = new Composite(tabs, SWT.NONE); clocks.setLayout(new RowLayout()); item.setControl(clocks);
11. Przejdź przez listę stref czasowych, dodając dla każdej z nich obiekt ClockWidget. RGB rgb = new RGB(128, 128, 128); TimeZone td = TimeZone.getDefault(); Iterator timezoneIterator = region.getValue().iterator(); while (timezoneIterator.hasNext()) { TimeZone tz = timezoneIterator.next(); ClockWidget clock = new ClockWidget(clocks, SWT.NONE, rgb); clock.setOffset((tz.getOffset(System.currentTimeMillis()) td.getOffset(System.currentTimeMillis())) / 3600000); }
12. Uruchom testową wersję Eclipse i otwórz Widok strefy czasowej, by zobaczyć wszystkie zegary.
13. By można było stwierdzić, który zegar dotyczy której strefy czasowej, każdy z nich umieścimy w obiekcie Group z nadaną etykietą, więc hierarchia zmieni się z CTabItem→Composite→ClockWidget na CTabItem→Composite→Group→ClockWidget. Zastąp tworzenie obiektu ClockWidget w przedstawiony poniżej sposób. ClockWidget clock = new ClockWidget(clocks, SWT.NONE, rgb); Group group = new Group(clocks,SWT.SHADOW_ETCHED_IN); group.setText(tz.getID().split("/")[1]); ClockWidget clock = new ClockWidget(group, SWT.NONE, rgb);
14. Uruchom testową wersję Eclipse, by przekonać się, że pojawią się puste elementy.
79
Eclipse 4. Programowanie wtyczek na przykładach
15. Ponieważ domyślny menedżer układu graficznego dla ogólnych klas Composite jest ustawiony na wartość null, obiekty Group nie stosują żadnego menedżera, więc zegary nie mają odpowiedniego rozmiaru. Problem łatwo naprawić — wystarczy jawnie dodać menedżera układu graficznego elementów. group.setLayout(new FillLayout());
16. Ponownie uruchom Eclipse, by zobaczyć zegary.
17. Zegary na dole okna są nieproporcjonalne, a samego widoku nie można przewijać, choć wyraźnie widać, że stref czasowych jest więcej. By dodać do widgetu przewijanie, użyjemy klasy ScrolledComposite. Klasa zapewnia automatycznie dodawane paski przewijania, co pozwala zastosować znacznie większy obszar widoku, który użytkownik może przewijać. Hierarchia widoku zmieni się z CtabItem Composite Group ClockWidget na CTabItem ScrolledComposite Composite Group ClockWidget po przedstawionej poniżej zmianie w kodzie. Composite clocks = new Composite(tabs, SWT.NONE); item.setControl(clocks); ScrolledComposite scrolled = new ScrolledComposite(tabs,SWT.H_SCROLL | SWT.V_SCROLL); Composite clocks = new Composite(scrolled, SWT.NONE); item.setControl(scrolled); scrolled.setContent(clocks);
18. Uruchom testową wersję Eclipse. Niestety, oczom ukaże się widok przedstawiony poniżej.
19. Zaistniała sytuacja wynika z faktu, iż ScrolledComposite nie ma ustalonego minimalnego rozmiaru. Można go jednak wyliczyć na podstawie kontenera clocks. Dodaj poniższy kod na końcu pętli while po utworzeniu obiektu ScrolledComposite.
80
Rozdział 2. • Tworzenie widoków w SWT
Point size = clocks.computeSize(SWT.DEFAULT,SWT.DEFAULT); scrolled.setMinSize(size); scrolled.setExpandHorizontal(true); scrolled.setExpandVertical(true);
20. Uruchom kod, a zegary pojawią się ponownie.
21. Obiekt ScrolledComposite stosuje inne tło. By je zmienić, dodaj poniższy wiersz po utworzeniu obiektu Composite. clocks.setBackground(Display.getDefault().getSystemColor (SWT.COLOR_LIST_BACKGROUND));
22. To już koniec pracy nad widokiem o nazwie Widok strefy czasowej.
Co się stało? Połączenie różnych rodzajów Composite umożliwiło wykonanie widoku z wieloma zakładkami, w którym wykorzystano instancje CTabFolder i CTabItem. Wewnątrz każdego CTabItem kontener ScrolledComposite zawiera obiekt Composite z wieloma instancjami Group, a każda z nich zawiera jeden obiekt ClockWidget. Użycie ScrolledComposite zapewnia pasek przewijania w widoku, a Group umożliwia dodanie powyżej zegara nazwy strefy czasowej. Niektóre z użytych w ćwiczeniu komponentów należą do pakietu org.eclipse.swt.custom, a nie do pakietu org.eclipse.swt.widgets. Niektóre z nich zaczynają się literą C, by odróżnić je od standardowych widgetów. Klasy CTabFolder i CTabItem to wersje implementujące zakładki bezpośrednio w SWT. Klasy TabFolder i TabItem korzystają z funkcjonalności zapewnianych przez system operacyjny.
81
Eclipse 4. Programowanie wtyczek na przykładach
Quiz — korzystanie z SWT P1. W jaki sposób dodać ikonę do przybornika systemowego? P2. Co powoduje użycie stylu SWT.NO_TRIM w obiekcie Shell? P3. W jaki sposób sprawić, by okno obiektu Shell stało się przezroczyste? P4. W jaki sposób utworzyć w obiekcie Shell okno, które nie będzie prostokątem? P5. Jaki element Composite umożliwia dodanie etykiety do grupy powiązanych ze sobą elementów? P6. Z którego menedżera układu graficznego korzysta domyślnie instancja Group? P7. W jaki sposób dodać pasek przewijania do istniejącego widgetu?
Sprawdź się — rozbudowa widoku stref czasowych Zegary wyświetlają czas w różnych strefach czasowych, ale to nie koniec możliwej rozbudowy. Przełącz się do zakładki zawierającej domyślną strefę czasową użytkownika. Posortuj zegary względem przesunięcia czasowego, a nie nazwy regionu. Utwórz zakładkę ulubionych stref czasowych, którą można wypełnić na zasadzie
„przeciągnij i upuść”. Popraw szybkość aktualizacji, zapewniając jeden obiekt Thread współdzielony przez wszystkie zegary. Popraw rozmiar komponentu ScrollableComposite, by był wyświetlany więcej niż jeden wiersz zegarów.
Podsumowanie W tym rozdziale omówiliśmy tworzenie widoków przy użyciu widgetów SWT. Prześledziliśmy zarówno użycie wbudowanych widgetów, jak i tworzenie własnych, a także łączenie widgetów w grupy za pomocą klas Composite i Layout. Opisaliśmy sposób zarządzania zasobami w SWT, włącznie z procedurą wykrywania i naprawiania wycieku zasobów. W następnym rozdziale przyjrzymy się abstrakcji wyższego poziomu — JFace.
82
3 Tworzenie widoków w JFace W poprzednim rozdziale przyjrzeliśmy się podstawowym elementom SWT, które stanowią pomost między elementami systemu operacyjnego a Javą. W tym rozdziale poznamy JFace, który korzysta z SWT w celu zapewnienia architektury MVC, a także dostarczenia wielu typowych widgetów używanych przez Eclipse.
W tym rozdziale: utworzymy widok do przedstawiania hierarchicznych danych, użyjemy zasobów obrazu, czcionki lub koloru, wygenerujemy stylizowany tekst, posortujemy i przefiltrujemy wpisy w widokach, dodamy akcje dla podwójnych kliknięć, zaznaczymy i obsłużymy właściwości, utworzymy widok dla danych tabelarycznych.
Dlaczego JFace? Choć SWT zapewnia podstawową implementację prostych widgetów (na przykład drzew, przycisków i etykiet), wszystko działa na bardzo podstawowym poziomie, bo wykorzystywane są teksty i indeksy zaznaczeń. Aby łatwiej wyświetlać strukturyzowane dane, JFace udostępnia kilka zaawansowanych widoków, które stanowią połączenie widgetów SWT i menedżerów zdarzeń, co zapewnia wygodną obsługę interfejsu użytkownika dla strukturyzowanych treści.
Eclipse 4. Programowanie wtyczek na przykładach
Istnieje wiele rodzajów zaawansowanych widoków nazywanych viewer (wszystkie dziedziczą po klasie Viewer), ale najczęściej stosowanymi są te, które należą do ContentViewer, na przykład TreeViewer i TableViewer. Istnieją również wersje bazujące na tekście (TextViewer ma podklasy dla SourceViewer), a także widoki operacyjne (ConsoleViewer dla widoku Console lub DetailedProgressViewer dla widoku Progress). W tym rozdziale wykonamy widoki bazujące na klasach TreeViewer i TableViewer. Ponieważ JFace bazuje na SWT, wiedza na temat szczegółów działania SWT jest niezbędna do prawidłowego użytkowania JFace.
Tworzenie widoków TreeViewer Wiele widgetów w Eclipse bazuje na widoku przypominającym drzewo — jest to zarówno nawigator plików, jak i okno wyświetlania zawartości klas. Framework JFace udostępnia klasę TreeViewer realizującą wszystkie niezbędne funkcjonalności. Użyjemy jej do wykonania widoku TimeZoneTreeView.
Kroki do wykonania — tworzenie obiektu TreeViewer Podobnie jak miało to miejsce w poprzednim rozdziale, nowy widok TimeZoneTreeView utworzymy przy użyciu edytora plugin.xml. Widok wyświetli strefy czasowe ułożone hierarchicznie względem regionów. 1. Kliknij prawym przyciskiem myszy projekt com.packtpub.e4.clock.ui i wybierz polecenie Plug-in Tools/Open Manifest, by otworzyć plik plugin.xml, jeśli jeszcze nie jest otwarty. 2. Otwórz zakładkę Extensions i znajdź element org.eclipse.ui.views. Kliknij go prawym przyciskiem myszy i z menu wybierz polecenie New/View. Wypełnij pola w sposób opisany poniżej. W polu ID wpisz com.packtpub.e4.clock.ui.views.TimeZoneTreeView.
W polu Name wpisz Widok drzewa stref czasowych.
W polu Class wpisz com.packtpub.e4.clock.ui.views.TimeZoneTreeView.
W polu Category wpisz com.packtpub.e4.clock.ui.
W polu Icon wpisz icons/sample.gif.
3. Zapisz plik. Konfigurator umieścił w pliku plugin.xml następujący wpis.
84
Rozdział 3. • Tworzenie widoków w JFace
4. Podobnie jak wcześniej, utwórz klasę TimeZoneTreeView, która rozszerza klasę ViewPart. 5. W metodzie createPartControl() utwórz instancję TreeViewer z opcjami V_SCROLL, H_SCROLL i MULTI. Zapamiętaj obiekt w polu klasy. Zaimplementuj metodę setFocus(), by ustawiała widok drzewa jako aktywny element. public class TimeZoneTreeView extends ViewPart { private TreeViewer treeViewer; public void createPartControl(Composite parent) { treeViewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL ); } public void setFocus() { treeViewer.getControl().setFocus(); } }
E4: Choć Eclipse 4 zostanie szczegółowo omówione w rozdziale 7., warto wspomnieć, że w Eclipse 4 nad metodą createPartControl() niezbędna jest adnotacja @Inject (by zapewnić przekazanie obiektu Composite), a nad metodą setFocus() — adnotacja @Focus.
6. Uruchom testową wersję Eclipse i przejdź do widoku, wybierając polecenie Window/ShowView/Other/Śledzenie czasu/Widok drzewa stref czasowych.
7. W odróżnieniu od Swing, gdzie oczekuje się otrzymywania danych w klasie bazującej na konkretnym interfejsie, JFace nie wymaga żadnej konkretnej klasy. W zamian oczekuje obiektu wartości do wyświetlenia (wejście), interfejsu, który czyta dane (dostawca treści), i interfejsu do wyświetlania danych (dostawca etykiet). 8. Utwórz nową klasę o nazwie TimeZoneLabelProvider dziedzicząca po LabelProvider (z pakietu org.eclipse.jface.viewers). Będzie zawierała metodę o nazwie getText(), która otrzymuje obiekt i zamienia go na reprezentację tekstową. Zamiast wywoływać toString(), zwróć odpowiednią wartość związaną z Map.Entry lub TimeZone. public class TimeZoneLabelProvider extends LabelProvider { public String getText(Object element) { if (element instanceof Map) { return "Strefy czasowe";
85
Eclipse 4. Programowanie wtyczek na przykładach
} else if return } else if return } else { return }
(element instanceof Map.Entry) { ((Map.Entry) element).getKey().toString(); (element instanceof TimeZone) { ((TimeZone) element).getID().split("/")[1]; "Nieznany typ: " + element.getClass();
} }
Ponieważ obiekt TreeViewer może mieć wiele korzeni, test instanceof Map służy do sprawdzenia, czy to wierzchołek drzewa, który powinien mieć nazwę Strefy czasowe. 9. Warto zapewnić wartość domyślną — nawet jeśli jest to pusty tekst — bo otrzymanie nieznanego typu można łatwo wyśledzić i naprawić. 10. Utwórz nową klasę TimeZoneContentProvider implementującą interfejs ITreeContentProvider. Interfejs wymaga implementacji trzech metod z sześciu (pozostałe mogą pozostać puste). Oto one. hasChildren() — zwraca true, jeśli węzeł ma potomków.
getChildren() — zwraca potomków konkretnego węzła.
getElements() — zapewnia główne korzenie.
11. Metoda hasChildren() zwróci wartość true, jeśli zostanie do niej przekazany obiekt typu Map lub Collection, który nie będzie pusty. Przekazanie Map.Entry spowoduje wywołanie rekurencyjne. Dla drzew bazujących na zagnieżdżonych Map lub Collection metoda hasChildren() będzie wyglądała identycznie. public boolean hasChildren(Object element) { if (element instanceof Map) { return !((Map) element).isEmpty(); } else if (element instanceof Map.Entry) { return hasChildren(((Map.Entry)element).getValue()); } else if (element instanceof Collection) { return !((Collection) element).isEmpty(); } else { return false; } }
12. Implementacja getChildren() rekurencyjnie wchodzi do obiektów typu Map, Collection lub Map.Entry, stosując przy tym opisany wcześniej wzorzec. Ponieważ metoda wymaga zwrócenia typu Object[], kod używa funkcjonalności wbudowanej w klasę Map, by zamienić zawartość entrySet() na tablicę. public Object[] getChildren(Object parentElement) { if (parentElement instanceof Map) { return ((Map) parentElement).entrySet().toArray();
86
Rozdział 3. • Tworzenie widoków w JFace
} else if return } else if return } else { return }
(parentElement instanceof Map.Entry) { getChildren(((Map.Entry)parentElement).getValue()); (parentElement instanceof Collection) { ((Collection) parentElement).toArray(); new Object[0];
}
13. Kluczem przy implementacji ITreeContentProvider jest zapewnienie stałej synchronizacji kodu metod getChildren() i hasChildren(). Jednym ze sposobów zapewnienia takiej sytuacji jest użycie w metodzie hasChildren() metody getChildren() i sprawdzanie, czy tablica jest pusta, ale wydajność takiej operacji nie będzie najlepsza, jeśli getChildren() to operacja bardzo złożona obliczeniowo. 14. Ponieważ TreeViewer może mieć wiele korzeni, istnieje metoda pobierająca tablicę korzeni z elementu wejściowego. Błąd we frameworku JFace uniemożliwia argumentowi getElements() posiadanie własnej wartości. Z tego powodu przyjęło się, że najlepiej przekazać tablicę (zawierającą tylko jeden element) i następnie ją zwrócić. Metoda przedstawiona poniżej będzie najprawdopodobniej wyglądała tak samo dla każdej klasy TreeContentProvider, którą kiedykolwiek napiszesz. public Object[] getElements(Object inputElement) { if (inputElement instanceof Object[]) { return (Object[]) inputElement; } else { return new Object[0]; } }
15. Po zakończeniu tworzenia odpowiednich klas dostawców danych przejdź do metody createPartControl() klasy TimeZoneTreeView, by połączyć dostawców z obiektem widoku i ustalić obiekt będący źródłem danych. treeViewer.setLabelProvider(new TimeZoneLabelProvider()); treeViewer.setContentProvider(new TimeZoneContentProvider()); treeViewer.setInput(new Object[]{TimeZoneComparator.getTimeZones()});
16. Uruchom testową wersję Eclipse i otwórz widok poleceniem Window/ShowView/ Other/Śledzenie czasu/Widok drzewa stref czasowych, by zobaczyć efekt końcowy.
87
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Dane do TreeViewer przekazaliśmy przy użyciu metody setInput(). Metoda prawie zawsze otrzymuje tablicę obiektów, nawet jeśli jest to tylko jeden element. Aby zapewnić rozpakowanie struktury danych, interfejs ITreeContentProvider udostępnia dwie kluczowe metody — hasChildren() i getChildren(). Umożliwiają one przechodzenie przez strukturę danych na żądanie, gdy użytkownik zwija lub rozwija gałęzie drzewa. Powodem istnienia dwóch metod jest fakt, iż obliczenia w metodzie getChildren() mogą być bardzo kosztowne. Metoda hasChildren() służy do sprawdzenia, czy należy wyświetlić ikonę rozwinięcia węzła. Wywołanie metody getChildren() jest opóźnione aż do momentu faktycznego otwarcia węzła. W strukturach danych, które to zapewniają, warto również zaimplementować metodę getParent(); umożliwia ona dostęp do obiektu. Jeśli jest zaimplementowana, wywołanie viewer.reveal(Object) powoduje rozwinięcie węzłów w hierarchii, by odsłonić wskazany obiekt.
Do wyświetlenia etykiet na drzewie służy klasa LabelProvider. Dostarcza ona etykietę (i opcjonalny obrazek) dla każdego elementu. Dla każdego typu obiektu można użyć innej ikony. Z rozwiązania tego skorzystano w widoku Package z perspektywy Java, który wyświetla ikonę klasy dla klas, ikonę pakietu dla pakietów i tak dalej. Klasa LabelProvider może wyświetlać komunikaty na różne sposoby. Nic nie stoi na przeszkodzie, by dodać do etykiety informację o przesunięciu czasowym (różnicę między konkretną strefą czasową i czasem GMT).
Kroki do wykonania — JFace i obrazy Klasa TimeZoneLabelProvider może zwrócić obiekt Image będący standardowym widgetem SWT. Choć obraz (obiekt Image) można wczytać w sposób podobny jak w poprzednim rozdziale, JFace oferuje rejestry zasobów służące do zarządzania zestawami zasobów aplikacji. Rejestry obsługują klasy ImageRegistry, FontRegistry i ColorRegistry. Rejestr zasobów ma za zadanie przechowywać listę obiektów Resource i zwalniać je we właściwy sposób, ale tylko wtedy, gdy nie są już potrzebne. JFace posiada rejestry globalne, ale istnieją również rejestry bardziej szczegółowe używane przez IDE na przykład do przechowywania list ikon folderów i plików. W rejestrze tego typu korzysta się z deskryptorów do określania konkretnych zasobów, więc po przekazaniu deskryptora otrzymuje się odpowiadającą mu instancję zasobu. Zwróconym zasobem zarządza rejestr, więc kod, który go otrzyma, nie powinien go zwalniać. 1. W TimeZoneLabelProvider dodaj metodę getImage(), w której używa się rejestru obrazów ImageRegistry, by pobrać ikonę folderu. Oto kod metody.
88
Rozdział 3. • Tworzenie widoków w JFace
public Image getImage(Object element) { if(element instanceof Map.Entry) { return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); } else { return super.getImage(element); } }
2. Uruchom testową wersję Eclipse i otwórz Widok drzewa stref czasowych. Obok tekstu z nazwą regionu pojawi się ikona folderu. Instancji Image nie trzeba niszczyć samodzielnie, ponieważ należy do wtyczki PlatformUI (zasób obrazu zostanie zwolniony w momencie wyłączania PlatformUI).
E4: W Eclipse 4 instancję ISharedImages można otrzymać poprzez wstrzyknięcie. @Inject private ISharedImages images; images.getImage(ISharedImages.IMG_OBJ_FOLDER);
3. By otrzymać inny obraz, użyj globalnych instancji ImageRegistry lub JFaceRegistry lub utwórz własną kopię. Zastosowanie globalnej wersji oznacza, że obraz Image nigdy nie zostanie zniszczony, ponieważ JFaceRegistry istnieje przez cały czas życia instancji Eclipse. Zamiast tego utwórz obiekty LocalResourceManager i ImageRegistry powiązane z cyklem życia kontrolki. Gdy kontrolka nadrzędna będzie usuwana, automatycznie usunięte zostaną również obrazy. Umieść w metodzie CreatePartControl klasy TimeZoneTreeView poniższy kod. public void createPartControl(Composite parent) { ResourceManager rm = JFaceResources.getResources(); LocalResourceManager lrm = new LocalResourceManager(rm,parent);
4. Używając obiektu LocalResourceManger, utwórz instancję ImageRegistry i za pomocą metody createFromURL() dodaj obiekt ImageDescriptor na podstawie adresu URL. ImageRegistry ir = new ImageRegistry(lrm); URL sample = getClass().getResource("/icons/sample.gif"); ir.put("sample", ImageDescriptor.createFromURL(sample));
89
Eclipse 4. Programowanie wtyczek na przykładach
5. Po wypełnieniu obiektu ImageRegistry trzeba go powiązać z obiektem LabelProvider, by ten mógł użyć właściwego obrazu, jeśli będzie trzeba. Przekaż rejestr obrazów do konstruktora klasy TimeZoneLabelProvider. treeViewer.setLabelProvider(new TimeZoneLabelProvider()); treeViewer.setLabelProvider(new TimeZoneLabelProvider(ir));
6. Zaimplementuj w TimeZoneLabelProvider konstruktor, który zapamięta obiekt ImageRegistry. Następnie użyj go do pobrania obrazu w wywołaniu getImage(). private final ImageRegistry ir; public TimeZoneLabelProvider(ImageRegistry ir) { this.ir = ir; } public Image getImage(Object element) { if(element instanceof Map.Entry) { return ir.get("sample"); } else if(element instanceof TimeZone) { return ir.get("sample"); } else { return super.getImage(element); } }
7. Ponownie uruchom testową wersję Eclipse. W drzewie pojawi się przykładowa ikona wtyczki.
Co się stało? Początkowo użyliśmy standardowych obrazów znajdujących się w obiekcie PlatformUI. Predefiniowane deskryptory pochodziły z interfejsu ISharedImages. Nazwy deskryptorów zaczynają się od IMG; zastosowano w nich następujący wzorzec: etool lub dtool — włączone lub wyłączone ikony paska narzędziowego, elcl lub dlcl — włączone lub wyłączone ikony lokalnego paska narzędziowego, dec — dekorator, obj i objs — obiekty (pliki, foldery i tym podobne).
Inne wtyczki zawierają własne zestawy obrazów, na przykład interfejs JDT dodaje ikony związane z pakietami, klasami, metodami i polami.
90
Rozdział 3. • Tworzenie widoków w JFace
W celu użycia własnych obrazów utworzyliśmy obiekt ImageRegistry obsługiwany przez obiekt LocalResourceManager. Jeśli do konstruktora trafi obiekt Control, klasa rejestruje w nim obiekt DisposeListener. W ten sposób, gdy kontrola będzie niszczona, podobnie stanie się z powiązanymi z nią obrazami. Dzięki temu cały kod jest bardziej przejrzysty, gdyż ImageRegistry można bez większych problemów przekazać do klasy TimeZoneContentProvider. Obiekt ImageRegistry inicjalizujemy zestawem obiektów ImageDescriptor — w tym przypadku plikiem icons/sample.gif pochodzącym z kreatora wtyczek. Ten sam klucz służy do inicjalizacji i dostępu do obrazu. Niektóre projekty Eclipse trzymają się konwencji z interfejsem ISharedImages z zestawem stałych.
Kroki do wykonania — style w dostawcy etykiet Interfejs IStyledLabelProvider służy do zmiany domyślnego stylu w widoku drzewa. Użyto go w widoku treści klas, który wyświetla typ zwracany przez metody, lub też w dekoratorze zespołów, który wyświetla informację o zmianach. 1. Dodaj interfejs IStyledLabelProvider do TimeZoneLabelProvider i utwórz metodę getStyledText(). Jeśli zaznaczonym elementem jest Map.Entry zawierający TimeZone, w nawiasach wskaż przesunięcie czasowe. public class TimeZoneLabelProvider extends LabelProvider implements IStyledLabelProvider { public StyledString getStyledText(Object element) { String text = getText(element); StyledString ss = new StyledString(text); if (element instanceof TimeZone) { int offset = -((TimeZone) element).getOffset(0); ss.append(" (" + offset / 3600000 + "h)", StyledString.DECORATIONS_ STYLER); } return ss; } }
2. By użyć dostawcy etykiet ze zmienionym stylem, trzeba go otoczyć klasą DelegatingStyledCellLabelProvider. Zmodyfikuj konstruktor wywoływany w metodzie createPartControl() metody TimeZoneTreeView. treeViewer.setLabelProvider( new DelegatingStyledCellLabelProvider( new TimeZoneLabelProvider(ir)));
3. Uruchom testową wersję Eclipse i otwórz widok, by zobaczyć przesunięcia czasowe zapisane innym kolorem.
91
Eclipse 4. Programowanie wtyczek na przykładach
4. By zmienić czcionkę używaną w widoku, klasa TimeZoneLabelProvider musi implementować interfejs IFontProvider. Klasa FontRegistry z JFace umożliwia pobranie domyślnej czcionki z włączoną kursywą. Dodaj parametr FontRegistry do konstruktora TimeZoneLabelProvider i zaimplementuj metodę getFont(). public class TimeZoneLabelProvider extends LabelProviderimplements IStyledLabelProvider, IFontProvider { private final FontRegistry fr; public TimeZoneLabelProvider(ImageRegistry ir, FontRegistry fr){ this.ir = ir; this.fr = fr; } public Font getFont(Object element) { Font italic = fr.getItalic(JFaceResources.DEFAULT_FONT); return italic; } }
5. Zmodyfikuj klasę TimeZoneTreeView, by utworzyć i przekazać globalny obiekt FontRegistry pobrany z klasy JFaceResources. FontRegistry fr = JFaceResources.getFontRegistry(); treeViewer.setLabelProvider( new DelegatingStyledCellLabelProvider( new TimeZoneLabelProvider(ir))); treeViewer.setLabelProvider( new DelegatingStyledCellLabelProvider( new TimeZoneLabelProvider(ir, fr)));
6. Ponownie uruchom testową wersję Eclipse, by przekonać się, że strefy czasowe są teraz pisane kursywą.
Co się stało? Implementując interfejs IStyledLabelProvider i otaczając go klasą DelegatingStyledCellLabelProvider, można zmieniać styl poszczególnych elementów drzewa, włącznie ze zmianami czcionki i koloru. Klasa StyledText umożliwia wyświetlanie tekstu różnymi stylami. Choć w przykładzie pojawiła się klasa DecorationsStyler, dodatkowe style można także zdefiniować przy użyciu wywołania StyledString.createColorRegistryStyler("czcionka", "tło"), gdzie oba teksty to klucze w globalnym obiekcie ColorRegistry z JFace.
92
Rozdział 3. • Tworzenie widoków w JFace
Choć kolory można zmieniać dla poszczególnych znaków, czcionka (obiekt Font) jest jedna dla całego tekstu. Wynika to z faktu, iż wyliczenia rozmiaru etykiety zakładają, że cały tekst jest pisany identyczną czcionką. Jako dobrą praktykę uważa się używanie przez dostawców treści i etykiet menedżerów zasobów przekazywanych do konstruktorów. Dzięki temu kod łatwo sprawdzić za pomocą testów automatycznych i pozorowanych zasobów. Niezależnie od tego, czy stosuje się model programistyczny Eclipse 3.x, czy Eclipse 4.x, oddzielenie użycia zasobów od miejsca ich tworzenia to klucz do wygodnego testowania.
Quiz — podstawy JFace P1. Jakie metody zawiera LabelProvider? P2. Jaka jest różnica między metodami hasChildren() i getChildren() z ContentProvider? P3. Do czego służy klasa ImageRegistry? P4. W jaki sposób zmienić styl elementów widoku drzewa?
Sprawdź się — dodanie obrazów dla regionów Po poznaniu podstaw postaraj się rozszerzyć przykład o kilka elementów. Popraw klasę TimeZoneLabelProvider, by podawała przesunięcie w godzinach
i minutach względem GMT. Uaktualnij wtyczkę, dodając ikony flag i tworząc wpisy w rejestrze obrazów
(nazwa strefy czasowej może być kluczem, co ułatwi całą obsługę). Wyświetl nazwę regionu kursywą, ale same nazwy stref czasowych pogrubioną
czcionką.
Sortowanie i filtracja Jedną z cech JFace jest to, że za sortowanie danych może odpowiadać widok, co odciąża strukturę danych od odpowiedzialności za właściwe przetwarzanie materiałów. W ten sposób bardzo łatwo utworzyć widoki z filtracją, w których użytkownik szuka określonej frazy lub też sortuje wyniki zgodnie ze swym zapotrzebowaniem. Filtry są powszechnie używane w IDE Eclipse. Przykładem są chociażby opcje Hide libraries from external lub Hide closed projects znajdujące się w opcjach wielu widoków.
93
Eclipse 4. Programowanie wtyczek na przykładach
Kroki do wykonania — sortowanie elementów w widoku Widok drzewa wyświetla obecnie dane w sposób posortowany, ale za sortowanie nie odpowiada widok. Ponieważ dane znajdują się w obiekcie TreeMap, wykonuje on automatyczne sortowanie elementów na podstawie wartości zwracanych przez metodę toString(). By użyć innego sposobu sortowania (na przykład bazującego na przesunięciu czasu), można albo zmodyfikować obiekt TreeMap, dodając nowy komparator i sortując dane przy ich tworzeniu, albo sortować na poziomie widoku drzewa. Pierwszy wybór jest dobry tylko w sytuacji, gdy z danych korzysta jeden widok lub gdy dane pochodzą z dużego, zewnętrznego magazynu danych, który przeprowadzi sortowanie zdecydowanie bardziej efektywnie (takiego jak na przykład relacyjna baza danych). W mniejszych zbiorach danych sortowaniem może zająć się widok. 1. Widoki strukturyzowane JFace umożliwiają sortowanie przy użyciu klasy ViewerComparator. Utwórz nową klasę — TimeZoneViewerComparator — w pakiecie com.packtpub.e4.clock.ui.internal i zaimplementuj metodę compare(). public class TimeZoneViewerComparator extends ViewerComparator { public int compare(Viewer viewer, Object o1, Object o2) { int compare; if (o1 instanceof TimeZone && o2 instanceof TimeZone) { long time= System.currentTimeMillis(); compare=((TimeZone)o2).getOffset(time) - ((TimeZone)o1).getOffset(time); } else { compare = o1.toString().compareTo(o2.toString()); } return compare; } }
2. Podepnij nową klasę porównywania do widoku. treeViewer.setComparator(new TimeZoneViewerComparator());
3. Uruchom testową wersję Eclipse i otwórz Widok drzewa stref czasowych. Strefy czasowe powinny być posortowane najpierw po przesunięciu czasu, a następnie alfabetycznie.
4. Aby dodać sortowanie specyficzne dla widoku, zmodyfikuj metodę compare() z TimeZoneViewerComparator, by otrzymać klucz REVERSE z danych widoku. Użyj go do odwrócenia sortowania wyników.
94
Rozdział 3. • Tworzenie widoków w JFace
return compare; boolean reverse = Boolean.parseBoolean(String.valueOf(viewer.getData("REVERSE"))); return reverse ? -compare : compare;
5. Aby zobaczyć efekt działania nowego sortowania, ustaw klucz REVERSE tuż przed wywołaniem setComparator() na końcu metody createPartControl() z TimeZoneTreeView. treeViewer.setData("REVERSE",Boolean.TRUE); treeViewer.setComparator(new TimeZoneViewerComparator());
6. Ponownie uruchom testową wersję Eclipse, by przekonać się, że wyniki posortowane są odwrotnie niż poprzednio.
Co się stało? Dodając obiekt ViewerComparator do obiektu Viewer, możemy określić sposób sortowania danych w konkretnym widoku. Oczywiście, najczęściej będzie to powiązane z wyborem odpowiedniej opcji w widoku — może to być opcja odwracająca sortowanie lub też zmieniająca sortowanie między nazwą i przesunięciem czasu. Implementując obiekt komparatora, warto upewnić się, że metoda będzie obsługiwała różne typy obiektów (włączając te, których się nie oczekuje). Dane w widoku mogą się zmieniać lub być inne w trakcie działania aplikacji. Korzystaj z instanceof, by upewnić się, że typ jest właściwy. Aby zapamiętać właściwości specyficzne dla widoku, użyj metod setData() i getData() z widoku. Dzięki temu można użyć ogólnego komparatora w wielu różnych widokach przy jednoczesnym respektowaniu ustawień filtracji i sortowania dla konkretnego widoku. Przedstawiony przykład zawiera dane sortowania ustawione na stałe, co wymaga ponownego uruchomienia Eclipse, by zobaczyć efekt zmian. Po zmianie właściwości widoku, które wpływają na sortowanie lub filtrację, wywołaj metodę refresh() widoku, by zaktualizować wyświetlane dane zgodnie z nowymi ustawieniami.
Kroki do wykonania — filtrowanie elementów w widoku Inną, często wykorzystywaną w widokach funkcją jest filtracja. Służy ona do ręcznego wyszukiwania konkretnego elementu lub też poszukiwania konkretnych elementów widoku. Bardzo często filtrację wiąże się z menu widoku, czyli menu rozwijanym po kliknięciu trójkąta w prawym górnym rogu widoku. Najczęściej stosuje się nazwę Filters (filtry). Klasa ViewerFilter zawiera metodę dotyczącą filtracji nazywaną select(). (Istnieją metody filter(), ale służą one do filtracji całej tablicy; metoda select() używana jest do określenia, czy należy wyświetlić konkretny element, czy też go pominąć).
95
Eclipse 4. Programowanie wtyczek na przykładach
1. Utwórz klasę TimeZoneViewerFilter w pakiecie com.packtpub.e4.clock.ui.internal, która dziedziczy po klasie ViewerFilter. Konstruktor powinien przyjmować wzorzec typu String. Metoda select() musi zwracać true, jeśli element jest typu TimeZone i zawiera w swej nazwie wzorzec. public class TimeZoneViewerFilter extends ViewerFilter { private String pattern; public TimeZoneViewerFilter(String pattern) { this.pattern = pattern; } public boolean select(Viewer v, Object parent, Object element) { if(element instanceof TimeZone) { TimeZone zone = (TimeZone)element; return zone.getDisplayName().contains(pattern); } else { return true; } } }
2. Filtr ustawia się na poziomie widoku. Ponieważ widoki mogą mieć kilka filtrów, przekazuje się je do widoku jako tablicę. W tym przypadku wzorzec filtru ustawiamy w konstruktorze, ale w rzeczywistości zostałby pobrany od użytkownika. Zmodyfikuj klasę TimeZoneTreeView na końcu metody createPartControl(). treeViewer.setFilters(new ViewerFilter[] { new TimeZoneViewerFilter("GMT")});
3. Uruchom testową wersję Eclipse i otwórz widok. Wyświetlane są tylko strefy czasowe z regionu Etc.
4. Aby usunąć ikony rozwijania węzłów przy pozostałych elementach, można włączyć w widoku drzewa automatyczne wykonywanie testów rozwinięć węzłów. treeViewer.setExpandPreCheckFilters(true);
5. Ponownie uruchom testową wersję Eclipse i otwórz Widok drzewa stref czasowych. Zauważ, że puste grupy nadal są wyświetlane, ale nie ma już obok nich ikon rozwijania, bo po filtracji nie mają już elementów potomnych.
96
Rozdział 3. • Tworzenie widoków w JFace
Co się stało? Klasa TimeZoneViewerFilter powstała jako podklasa klasy ViewerFilter i jest przekazywana do klasy TreeViewer. W trakcie wyświetlania i filtracji danych filtr jest wywoływany dla każdego elementu drzewa (włącznie z korzeniem). Domyślnie, gdy metoda hasChildren() zwróci wartość true, pojawi się ikona rozwinięcia gałęzi. Po kliknięciu algorytm przejdzie przez wszystkie potomki i wykona dla nich operację filtracji. Jeżeli okaże się, że po filtracji nie pozostał ani jeden element, algorytm usunie ikonę rozwinięcia. Włączenie opcji setExpandPreCheckFilters(true) dla widoku spowoduje, że widok już na samym początku sprawdzi, czy po filtracji w gałęzi pozostanie choć jeden potomek. Opcja nie ma żadnych negatywnych konsekwencji, jeśli w ogóle nie ustawiono w niej filtrów. Jeśli filtry są ustawione, a danych w zbiorze jest dużo, wykonanie operacji sprawdzenia może zająć sporo czasu. Aby domyślnie wyświetlić wszystkie elementy drzewa lub też zwinąć je do pojedynczego elementu, użyj metod expandAll() i collapseAll(). Najczęściej metody te wywołuje się z poziomu ikon typu [+] i [-] umieszczonych w lokalnym pasku narzędziowym widoku (patrz widoki Synchronize i Package Explorer). Jeśli dane mają strukturę drzewiastą, która domyślnie powinna wyświetlić tylko część poziomów, warto zastosować metody expandToLevel() i collapseToLevel() przyjmujące wartość całkowitą i obiekt (użyj getRoot() dla korzenia, jeśli obiekt nie jest jawnie określony). Spowodują one rozwinięcie lub zwinięcie wszystkich elementów do zadanego poziomu. Metoda expandAll() to skrót wywołujący metodę expandToLevel(getRoot(), ALL_LEVELS). W odpowiedzi na zdarzenie wyboru, które zawiera ukryty obiekt, warto wykonać wcześniej operację reveal(), by konkretny element stał się widoczny. Pamiętaj, że reveal() działa tylko wtedy, gdy metoda getParent() jest poprawnie zaimplementowana, co w prezentowanym przykładzie nie ma miejsca.
Quiz — sortowanie i filtracja P1. Jak posortować elementy drzewa w sposób inny niż domyślny? P2. Jaka metoda służy do filtracji elementów? P3. W jaki sposób połączyć kilka filtrów?
Sprawdź się — rozwijanie gałęzi i filtracja Po poznaniu zasad dotyczących sortowania i filtracji rozbuduj przykład o kilka nowych elementów. Dodaj drugi filtr, który usuwa wszystkie strefy czasowe z ujemnym przesunięciem czasu. Po otwarciu widoku wykonaj operację expandAll().
97
Eclipse 4. Programowanie wtyczek na przykładach
Zastosuj sortowanie, w którym regiony są ułożone w odwrotnej kolejności
alfabetycznej, ale strefy czasowe — w porządku alfabetycznym. Dodaj okno dialogowe pozwalające na zmianę wzorca filtru. Dodatkowo użyj pustego ciągu znaków jako wartości stosowanej do usunięcia filtracji.
Interakcje i właściwości Możliwość wyświetlenia danych to jedna rzecz, ale w większości widoków najważniejsza jest interaktywność. Niezależnie od tego, czy dotyczy to funkcjonalności sortowania i filtracji z wcześniejszej części rozdziału, czy wyboru konkretnych elementów, widoki muszą być elementami interaktywnymi, by można ich użyć nie tylko do przeglądania danych, ale również do ich edycji.
Kroki do wykonania — dodanie procedury obsługi podwójnego kliknięcia Widok drzewa najczęściej służy do wyświetlania treści w sposób hierarchiczny. Niestety, drzewo nie jest odpowiednią strukturą, by wyświetlić wszystkie szczegóły obiektu. Po podwójnym kliknięciu konkretnego elementu warto wyświetlić jego szczegóły. 1. Na końcu metody createPartControl() klasy TimeZoneTreeView zarejestruj anonimową klasę wewnętrzną implementującą interfejs IDoubleClickListener i dodaj ją metodą addDoubleClickListener() do obiektu treeViewer. Podobnie jak w rozdziale 1., otwórz okno dialogowe, by sprawdzić, czy wszystko zadziałało prawidłowo. treeViewer.addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { Viewer viewer = event.getViewer(); Shell shell = viewer.getControl().getShell(); MessageDialog.openInformation(shell, "Podwójne kliknięcie", "Wykryto podwójne kliknięcie"); } });
2. Uruchom testową wersję Eclipse i otwórz widok. Podwójnie kliknij drzewo, a pojawi się komunikat Wykryto podwójne kliknięcie. Okno jest typu modalnego, co zapobiega wybraniu innych elementów interfejsu aż do momentu zamknięcia okna. 3. By znaleźć wybrane obiekty, Eclipse udostępnia interfejs ISelection (który zapewnia jedynie metodę isEmpty()) oraz interfejs IStructuredSelection (zapewnia iterator i inne metody dostępowe). Istnieje również kilka wyspecjalizowanych podtypów, na przykład ITreeSelection, który potrafi prześledzić ścieżkę prowadzącą do aktualnie wybranego elementu drzewa. W metodzie dotyczącej podwójnego kliknięcia znajdującej się w metodzie createPartControl() klasy TimeZoneTreeView zastąp fragment MessageDialog poniższym kodem.
98
Rozdział 3. • Tworzenie widoków w JFace
MessageDialog.openInformation(shell, Podwójne kliknięcie", "Wykryto podwójne kliknięcie"); ISelection sel = viewer.getSelection(); Object selectedValue; if (!(sel instanceof IStructuredSelection) || sel.isEmpty()) { selectedValue = null; } else { selectedValue = ((IStructuredSelection)sel).getFirstElement(); } if (selectedValue instanceof TimeZone) { TimeZone timeZone = (TimeZone)selectedValue; MessageDialog.openInformation(shell, timeZone.getID(), timeZone.toString()); }
4. Uruchom Eclipse i otwórz widok. Dwukrotnie kliknij element drzewa, a pojawi się okno informacyjne z tekstem zawierającym nazwę strefy czasowej. 5. Aby wyświetlić więcej informacji na temat obiektu TimeZone, utwórz podklasę klasy MessageDialog o nazwie w TimeZoneDialog i umieść ją w pakiecie com.packtpub.e4.clock.ui.internal. Implementacja ma następującą postać. public class TimeZoneDialog extends MessageDialog { private TimeZone timeZone; public TimeZoneDialog(Shell parentShell, TimeZone timeZone) { super(parentShell, timeZone.getID(), null, "Strefa czasowa " + timeZone. getID(), INFORMATION, new String[] { IDialogConstants.OK_LABEL }, 0); this.timeZone = timeZone; } }
6. Treść okna zapewnia metoda CustomArea() używana do budowania zawartości widoku. Dodaj do klasy TimeZoneDialog metodę createCustomArea(). protected Control createCustomArea(Composite parent) { ClockWidget clock = new ClockWidget(parent,SWT.NONE, new RGB(128,255,0)); clock.setOffset((TimeZone.getDefault().getOffset(System.currentTimeMillis()) - timeZone.getOffset(System.currentTimeMillis()))/3600000); return parent; }
7. Na końcu zmodyfikuj wywołanie MessageDialog.open() z klasy TimeZoneTreeView, by korzystało z nowej implementacji. if (selectedValue instanceof TimeZone) { TimeZone timeZone = (TimeZone) selectedValue; MessageDialog.openInformation(shell, timeZone.getID(), timeZone. toString()); new TimeZoneDialog(shell, timeZone).open(); }
99
Eclipse 4. Programowanie wtyczek na przykładach
8. Uruchom testową wersję Eclipse i dwukrotnie kliknij strefę czasową, by zobaczyć okno dialogowe.
Co się stało? Dodaliśmy procedurę obsługi podwójnego kliknięcia i zarejestrowaliśmy ją za pomocą metody addDoubleClickListener(). Początkowo wyświetlaliśmy standardowe okno informacyjne, ale później utworzyliśmy własną podklasę MessageDialog, która korzysta z klasy ClockWidget. By otrzymać odpowiednią strefę czasową (obiekt TimeZone), pobraliśmy aktualnie zaznaczony obiekt z TreeViewer. Za zaznaczanie odpowiada interfejs ISelection. Metoda getSelection() powinna zawsze zwrócić wartość inną niż null, ale czasem uzyskana wartość spowoduje zwrócenie true po użyciu jej w metodzie isEmpty(). Istnieją jednak dwa interesujące interfejsy pochodne — IStructuredSelection i ITreeSelection. Interfejs ITreeSelection jest podtypem IStructuredSelection i dodaje metody specyficzne dla drzew. Umożliwia otrzymanie informacji o aktualnie zaznaczonych elementach i ich elementach nadrzędnych (w strukturze drzewa). Interfejs IStructuredSelection jest chyba najczęściej stosowanym interfejsem, gdy chodzi o systemy wyboru. Jeśli wybór nie jest pusty, praktycznie zawsze jest instancją implementującą IStructuredSelection. Z tego powodu bardzo często można zobaczyć poniższy fragment kodu. ISelection sel = viewer.getSelection(); Object selectedValue; if (!(sel instanceof IStructuredSelection) || sel.isEmpty()) { selectedValue = null; } else { selectedValue = ((IStructuredSelection)sel).getFirstElement(); }
Fragment pobiera zaznaczenie z widoku. Jeśli wynik nie jest instancją IStructuredSelection lub jest pusty, przypisuje zmiennej selectedValue wartość null. W pozostałych sytuacjach rzutuje otrzymany obiekt na interfejs IStructuredSelection i wywołuje metodę getFirstElement(), by pobrać pojedynczą wartość zaznaczenia.
100
Rozdział 3. • Tworzenie widoków w JFace
Zaznaczeniu mogło ulec więcej elementów, co oznacza, że metoda getFirstElement() zwraca jedynie pierwszy z nich. Klasa implementująca IStructuredSelection musi zapewnić iterator umożliwiający pobranie wszystkich zaznaczonych obiektów.
E4: W Eclipse 4 zaznaczony obiekt można wstrzyknąć do metody za pomocą adnotacji. @Inject @Optional void setTZ(@Named(IServiceConstants.ACTIVE_SELECTION) TimeZone timeZone) { }
Kroki do wykonania — wyświetlanie właściwości IDE Eclipse, zamiast wymuszać tworzenie coraz to nowych okien dialogowych dla każdego obiektu, udostępnia ogólny widok właściwości (znajdujący się we wtyczce org.eclipse.ui.views), który służy do wyświetlania informacji o aktualnie zaznaczonym obiekcie. Właściwości są odkrywane w sposób uogólniony, a dostęp do nich zapewnia interfejs IPropertySource. Dzięki temu obiekt może wprowadzić abstrakcję w kwestii wyliczania wartości pól pokazywanych w oknie właściwości. Najprostszym sposobem utworzenia źródła właściwości jest zapewnienie, by obiekt sam zaimplementował interfejs IPropertySource. Oczywiście, jest to możliwe tylko w sytuacji, gdy kod źródłowy można zmienić, ale w wielu sytuacjach (na przykład w przypadku obiektu TimeZone lub Map.Entry zawierającego klucz typu String i obiekt TimeZone) kod źródłowy nie jest dostępny. 1. Otwórz plik MANIFEST/META-INF.MF i dodaj org.eclipse.ui.views jako zależność w zakładce Dependencies lub jako paczkę w Require-Bundle. W przeciwnym razie IPropertySource nie będzie odnajdywane. 2. Utwórz w pakiecie com.packtpub.e4.clock.ui.internal klasę TimeZonePropertySource implementującą interfejs IPropertySource. W konstruktorze przyjmij pojedynczą instancję TimeZone. public class TimeZonePropertySource implements IPropertySource { private TimeZone timeZone; public TimeZonePropertySource(TimeZone timeZone) { this.timeZone = timeZone; } }
3. Jedynymi metodami, które trzeba zaimplementować, są getPropertyValue() i getPropertyDescriptors(). (Pozostałe metody, takie jak getEditableValue() i isPropertySet(), można zignorować, bo używa się ich tylko w operacjach edycji. Powinny pozostać puste lub zwracać null albo false. Metody getPropertyValue() i isPropertySet() wywołuje się z identyfikatorem. Pozostałe metody zwracają tablice obiektów PropertyDescriptors łączące identyfikator i nazwę właściwości do wyświetlenia w interfejsie graficznym. Dodaj poniższy kod do klasy TimeZonePropertySource.
101
Eclipse 4. Programowanie wtyczek na przykładach
private static final Object ID = new Object(); private static final Object DAYLIGHT = new Object(); private static final Object NAME = new Object(); public IPropertyDescriptor[] getPropertyDescriptors() { return new IPropertyDescriptor[] { new PropertyDescriptor(ID, "Strefa czasowa"), new PropertyDescriptor(DAYLIGHT, "Czas letni"), new PropertyDescriptor(NAME, "Nazwa") }; } public Object getPropertyValue(Object id) { if (ID.equals(id)) { return timeZone.getID(); } else if(DAYLIGHT.equals(id)) { return timeZone.inDaylightTime(new Date()); } else if (NAME.equals(id)) { return timeZone.getDisplayName(); } else { return null; } }
4. Powiązanie źródła właściwości z oknem właściwości wymaga użycia adaptera. Można go wskazać za pomocą interfejsu IAdaptable, który umożliwia klasie wirtualną implementację interfejsu. Ponieważ TimeZone nie może zaimplementować IAdaptable w sposób bezpośredni, potrzebujemy IAdapterFactory. 5. Utwórz w pakiecie com.packtpub.e4.clock.ui.internal klasę TimeZoneAdapterFactory implementującą interfejs IAdapterFactory. public class TimeZoneAdapterFactory implements IAdapterFactory { public Class[] getAdapterList() { return new Class[] { IPropertySource.class }; } public Object getAdapter(Object o, Class type) { if(type == IPropertySource.class && o instanceof TimeZone) { return new TimeZonePropertySource((TimeZone)o); } else { return null; } } }
6. Aby zarejestrować fabrykę adapterów w Eclipse, dodaj odpowiedni wpis w pliku plugin.xml.
102
Rozdział 3. • Tworzenie widoków w JFace
7. Uruchom testową wersję Eclipse, wybierz strefę czasową w widoku drzewa i otwórz okno właściwości poleceniem Window/Show View/Other/General/Properties z menu. Nie pojawią się żadne informacje. By mieć pewność, że adapter jest podpięty prawidłowo, dodaj na końcu metody createPartControl() z TimeZoneTreeView następujący wpis. System.out.println("Adapterem jest " + Platform.getAdapterManager(). getAdapter(TimeZone.getDefault(),IPropertySource.class));
8. Uruchom testową wersję Eclipse, otwórz Widok drzewa stref czasowych i sprawdź widok Console głównego Eclipse. Konsola powinna zawierać wpis podobny do poniższego. Adapterem jest com.packtpub.e4.clock.ui.internal.TimeZonePropertySource @7f8a6fb0
9. Czego więc brakuje? Okazuje się, że okno Properties nie otrzymuje informacji o zmianie zaznaczenia. By rozwiązać problem, dodaj w metodzie createPartControl() z TimeZoneTreeView następujący wpis. System.out.println("Adapterem jest " + Platform.getAdapterManager(). getAdapter(TimeZone.getDefault(),IPropertySource.class)); getSite().setSelectionProvider(treeViewer);
10. Teraz zmiany zaznaczenia będą przekazywane do IDE, by inne widoki mogły zaktualizować swoje dane. Po wybraniu strefy czasowej okno Properties będzie aktualizowało się automatycznie. Uruchom testową wersję Eclipse, otwórz Widok drzewa stref czasowych, wybierz strefę czasową i otwórz widok Properties.
E4: W celu powiązania widoku z dostawcą zaznaczania należy użyć kodu podobnego do poniższego. @Inject ESelectionService selectionService; ISelectionChangedListener selectionListener; @PostConstruct public void postConstruct() { selectionListener = new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent e) { if (selectionService != null) selectionService.setSelection(e.getSelection()); } }; treeViewer.addSelectionChangedListener(selectionListener);
103
Eclipse 4. Programowanie wtyczek na przykładach
} @PreDestroy public void preDestroy() { if(selectionListener != null) treeViewer.removeSelectionChangedListener(selectionListener); selectionListener = null; }
Co się stało? Aby zaktualizować stan zaznaczenia IDE, musieliśmy powiązać dostawcę zaznaczenia widoku z tym, który dotyczy IDE (metoda getSite()). Gdy zmieni się zaznaczenie elementu w widoku, widok wyśle komunikat do wszystkich nasłuchujących obiektów, by te mogły odpowiednio zaktualizować swoje dane.
E4: Procedurę obsługi zaznaczenia trzeba zarejestrować (i wyrejestrować) ręcznie, by zapewnić
właściwe powiązanie między widokiem i usługą zaznaczenia. Zamiast ISelectionService używa się ESelectionService. Interfejs jest nieco inny, ponieważ ISelectionService jest powiązany z klasą IWorkbenchPart, a ESelectionService nie posiada podobnego powiązania.
W celu zapewnienia informacji dla widoku Properties utworzyliśmy dla TimeZone klasę bazującą na interfejsie IPropertySource i powiązaliśmy ją z IAdapterManager obiektu Platform poprzez deklarację w pliku plugin.xml. Powiązania znacznie wygodniej tworzyć w sposób deklaratywny w pliku plugin.xml, bo nie trzeba stosować metod aktywacyjnych start() i stop(). Wynika to z faktu, iż metoda startowa z Activator nie może zostać wywołana aż do momentu wczytania pierwszej klasy z paczki; w przypadku adaptera rejestracja deklaratywna zapewnia odpowiednią informację niezależnie od kolejności wczytywania. Fabryka adapterów zapewnia metodę getAdapter(), która odpowiada za otoczenie lub konwersję przekazanego obiektu na obiekt pożądanego typu. Jeśli obiekt jest już instancją docelowego typu, zostanie po prostu zwrócony — w przeciwnym razie metoda zwraca POJO, pośrednika lub otoczkę implementującą pożądany interfejs. Często zdarza się, że posiadamy klasę (na przykład TimeZonePropertySupport), której jedynym zadaniem jest implementacja pożądanego interfejsu. Klasa tego typu stanowi otoczkę dla obiektu (TimeZone) w celu zapewnienia wymaganej funkcjonalności. Interfejs IPropertySupport zapewnia podstawowe metody do pobierania właściwości obiektu. Do identyfikacji właściwości używa identyfikatorów. Identyfikator może być obiektem dowolnego typu. W prezentowanym przykładzie były to instancje new Object. Choć można użyć obiektów typu String (co można zobaczyć w wielu przykładach), nie jest to podejście zalecane, ponieważ wartość obiektu String nie ma znaczenia, ale zajmuje miejsce w przestrzeni PermGen pamięci maszyny wirtualnej. Co więcej, obiekt Object umożliwia porównywanie instancji za
104
Rozdział 3. • Tworzenie widoków w JFace
pomocą operacji == bez narażania się na ostrzeżenia automatycznych testów stylu lub pytania przy ocenie kodu. (Inne przykłady stosują metodę equals(), by zachęcić do jej użycia, gdy nie są stosowane obiekty Object, ale dobry JIT i tak wykona optymalizację, szczególnie wtedy, gdy kod wysyła wiadomość do instancji typu static final).
Quiz — działanie właściwości P1. Jak instancje TableViewer mogą reagować na kliknięcie? P2. Dlaczego tworzy się podklasy klasy Dialog? P3. Czym są deskryptory właściwości? P4. Jak wyświetlić właściwości w widoku Properties?
Dane tabelaryczne Widok drzewa pojawia się w Eclipse bardzo często, ale czasem trzeba wyświetlić dodatkowe informacje związane z pojedynczym elementem. JFace zapewnia klasę TableViewer podobną do TreeViewer, ale zamiast pojedynczych etykiet można wyświetlać wiele kolumn z danymi. Istnieje również klasa TableTreeViewer, która łączy funkcjonalność obu klas.
Kroki do wykonania — przeglądanie stref czasowych w tabeli Aby wyświetlać strefy czasowe w postaci tabelarycznej, utworzymy nowy widok o nazwie Widok tabeli stref czasowych. 1. Kliknij prawym przyciskiem myszy projekt com.packtpub.e4.clock.ui i wybierz polecenie Plug-in Tools/Open Manifest. Otwórz zakładkę Extensions, kliknij prawym przyciskiem myszy org.eclipse.ui.views i wybierz New/View. Wypełnij pola w następujący sposób.
W polu ID wpisz com.packtpub.e4.clock.ui.views.TimeZoneTableView.
W polu Name wpisz Widok tabeli stref czasowych.
W polu Class wpisz com.packtpub.e4.clock.ui.views.TimeZoneTableView.
W polu Category wpisz com.packtpub.e4.clock.ui.
W polu Icon wpisz icons/sample.gif.
2. Plik plugin.xml powinien po tej operacji zawierać następujący fragment.
3. Utwórz nową klasę TimeZoneTableView rozszerzającą ViewPart na podstawie skrótu z edytora lub skorzystaj z nowego kreatora klas. Po utworzeniu widoku dodaj pusty obiekt TableViewer i użyj klasy ArrayContentProvider z listą dostępnych stref czasowych. public class TimeZoneTableView extends ViewPart { private TableViewer tableViewer; public void createPartControl(Composite parent) { tableViewer=new TableViewer(parent,SWT.H_SCROLL|SWT.V_SCROLL); tableViewer.getTable().setHeaderVisible(true); tableViewer.setContentProvider(ArrayContentProvider.getInstance()); tableViewer.setInput(TimeZone.getAvailableIDs()); } public void setFocus() { tableViewer.getControl().setFocus(); } }
E4: Tworząc część aplikacji dla Eclipse 4, trzeba pamiętać o dodaniu adnotacji @Inject dla konstruktora i adnotacji @Focus dla metody setFocus().
4. Uruchom testową wersję Eclipse, a w widoku o nazwie Widok listy stref czasowych pojawi się jednowymiarowa lista wszystkich stref czasowych.
5. Skonwertuj tablicę obiektów String na tablicę obiektów TimeZone i ustaw ją jako dane wejściowe. tableViewer.setInput(TimeZone.getAvailableIDs()); String[] ids = TimeZone.getAvailableIDs(); TimeZone[] timeZones = new TimeZone[ids.length]; for(int i=0;i pt -v org.eclipse.ui.contexts Extension point: org.eclipse.ui.contexts [from org.eclipse.ui] Extension(s): -------------------
120
Rozdział 4. • Interakcja z użytkownikiem
null [from org.eclipse.ant.ui]
name = Editing Ant Buildfiles description = Editing Ant Buildfiles Context parentId = org.eclipse.ui.textEditorScope id = org.eclipse.ant.ui.AntEditorScope
null [from org.eclipse.compare]
name = Comparing in an Editor description = Comparing in an Editor parentId = org.eclipse.ui.contexts.window id = org.eclipse.compare.compareEditorScope
Kroki do wykonania — włączanie i wyłączanie elementów menu W poprzednim punkcie przedstawiliśmy ukrywanie dowiązania klawiszy w zależności od rodzaju edytora. Nie blokuje to jednak możliwości wywołania polecenia z menu i nie ukrywa samego polecenia. Menu również można ukryć. Wystarczy w tym celu zdefiniować w poleceniu blok visibleWhen. Framework wyrażeń zapewnia wiele zmiennych, włącznie ze zmienną activeContext zawierającą listę aktywnych kontekstów. Jest to lista, ponieważ w tym samym czasie może być aktywnych wiele kontekstów (na przykład [dialogAndWindows,windows,textEditor,javaEditor]). By znaleźć wpis (operacja contains), używamy operatora iterate z wyrażeniem equals. 1. Otwórz plik plugin.xml. Znajdź polecenie Witaj i dodaj wyrażenie visibleWhen.
121
Eclipse 4. Programowanie wtyczek na przykładach
2. Uruchom testową wersję Eclipse; zobaczysz, że menu jest ukryte aż do momentu otwarcia edytora kodu Javy. Jeśli opisana sytuacja nie będzie miała miejsca, zamknij aplikację Eclipse i wykonaj czyszczenie przestrzeni roboczej. Po czyszczeniu ponownie utwórz projekt, klasę Javy i pusty plik tekstowy, by sprawdzić poprawność kodu.
Co się stało? W elementach menu może być zastosowane zabezpieczenie visibleWhen, które określa moment widoczności elementu menu. Zwrócenie wartości false oznacza ukrycie elementu menu. W składni wyrażeń użyto zagnieżdżonych elementów XML. Przykładowo blok ma wartość true, jeśli wszystkie elementy podrzędne mają wartość true. Blok ma wartość true, gdy przynajmniej jeden z elementów podrzędnych ma wartość true. Zmienne można testować przy użyciu bloków (określa zmienną ze stosu) i w celu porównania wartości. W zmiennych będących listami element służy do przejścia przez elementy. Argument operator="or" lub operator="and" ułatwia automatyczne wyliczenie włączenia lub wyłączenia. By dowiedzieć się, czy lista zawiera element, stosuje się kombinację operatorów i . W testach można korzystać z wielu zmiennych. Są one wymienione w pomocy Eclipse, w rozdziale „Workbench Core Expressions”. Oto one. activeContexts — lista identyfikatorów aktywnych kontekstów. activeShell — aktywna powłoka (okno dialogowe lub zwykłe). activeWorkbenchWindow — aktywne okno. activeEditor — aktualnie aktywny edytor (lub poprzednio aktywny edytor). activePart — aktywna część (edytor lub widok). selection — aktualne zaznaczenie. org.eclipse.core.runtime.Platform — obiekt Platform.
Obiekt Platform służy głównie do przeprowadzania dynamicznych testów. Oto przykład.
Wiedza, czy paczka została zainstalowana, bywa przydatna — warto włączyć funkcjonalność tylko wtedy, gdy paczka jest włączona (lub w terminologii OSGi jest w stanie ACTIVE). Z tego powodu zamiast isBundleInstalled lepiej zastosować test bundleState=ACTIVE.
122
Rozdział 4. • Interakcja z użytkownikiem
Kroki do wykonania — wielokrotne użycie wyrażeń Choć można kopiować i wklejać wyrażenia między miejscami ich użycia, znacznie lepszym rozwiązaniem okazuje się wielokrotne użycie wyniku tego samego wyrażenia. 1. Zadeklaruj wyrażenie, używając odpowiedniego punktu rozszerzenia, ale wcześniej otwórz plik plugin.xml projektu zegarów.
Jeśli korzystasz z kreatora wyrażeń, zostaniesz poproszony o dodanie zależności od paczki org.eclipse.core.expressions. Prezentowany przykład może działać również bez niej. 2. Aby skorzystać z definicji, zmień wyrażenie na referencję.
3. Skoro już zdefiniowałeś referencję, warto zmienić również procedurę obsługi, by oba elementy (menu i procedura obsługi) aktywowały się jednocześnie. Dodaj następujący wpis do procedury obsługi Hello z pliku plugin.xml.
123
Eclipse 4. Programowanie wtyczek na przykładach
4. Uruchom testową wersję Eclipse. Działanie systemu nie uległo zmianie, ale logika włączania znajduje się teraz w jednym miejscu.
Co się stało? Punkt rozszerzenia org.eclipse.core.expressions zdefiniował wirtualny warunek wyliczany po zmianach w kontekście, by element menu i procedura obsługi mogły włączać się i być widoczne jednocześnie. Referencję stosuje warunek enabledWhen z procedury obsługi i warunek visibleWhen z menu. Ponieważ referencje można wykorzystywać w dowolnym miejscu, wyrażenia mogą być definiowane w kategoriach innych wyrażeń. Jeśli tylko wyrażenie nie stosuje rekurencji, można je tworzyć w dowolny sposób.
Kroki do wykonania — dodanie poleceń do menu kontekstowego Warto mieć możliwość dodania elementów do menu kontekstowego, by te mogły być stosowane w różnych miejscach. Na szczęście, nie jest to trudne. Wystarczy użyć elementu menuContribution i kilku testów włączenia. W ten sposób element Action wprowadzony na początku rozdziału uda się zastąpić bardziej ogólną parą polecenia i procedury obsługi. Istnieje wycofany punkt rozszerzania — który nadal działa w Eclipse 4.3 — nazywany objectContribution. Służy on do dodania menu kontekstowego do obiektu. Wycofano go już jakiś czas temu, ale starsze ćwiczenia odnosiły się do niego bardzo często. 1. Otwórz klasę TimeZoneTableView i dodaj metodę hookContextMenu(). private void hookContextMenu(Viewer viewer) { MenuManager manager = new MenuManager("#PopupMenu"); Menu menu = manager.createContextMenu(viewer.getControl()); viewer.getControl().setMenu(menu); getSite().registerContextMenu(manager, viewer); }
2. Tę samą metodę hookContextMenu() dodaj do klasy TimeZoneTreeView. 3. W klasie TimeZoneTreeView na końcu metody createPartControl() wywołaj metodę hookContextMenu(treeViewer). 4. Na końcu metody createPartControl() z klasy TimeZoneTableView zastąp wywołanie akcji wywołaniem metody hookContextMenu(). hookContextMenu(tableViewer); MenuManager manager = new MenuManager("#PopupMenu"); Menu menu = manager.createContextMenu(tableViewer.getControl()); tableViewer.getControl().setMenu(menu); Action deprecated = new Action() {
124
Rozdział 4. • Interakcja z użytkownikiem
public void run() { MessageDialog.openInformation(null, "Witaj", "świecie"); } }; deprecated.setText("Witaj"); manager.add(deprecated);
5. Uruchomienie testowej wersji Eclipse w tym momencie nie zaowocuje wyświetleniem menu, ponieważ nic jeszcze do niego nie dodano. 6. Utwórz polecenie i procedurę obsługi Pokaż czas.
7. Utwórz w pakiecie com.packtpub.e4.clock.ui.handlers klasę ShowTheTime rozszerzającą klasę org.eclipse.core.commands.AbstractHandler. Klasa pokaże czas w konkretnej strefie czasowej. public class ShowTheTime extends AbstractHandler { public Object execute(ExecutionEvent event) { ISelection sel = HandlerUtil.getActiveWorkbenchWindow(event). getSelectionService().getSelection(); if (sel instanceof IStructuredSelection && !sel.isEmpty()) { Object value = ((IStructuredSelection)sel).getFirstElement(); if (value instanceof TimeZone) { SimpleDateFormat sdf = new SimpleDateFormat(); sdf.setTimeZone((TimeZone) value); MessageDialog.openInformation(null, "Czas to", sdf.format(new Date())); } } return null; } }
8. Na końcu dodaj menu do specjalnego locationURI: popup:org.eclipse.ui.popup.any.
125
Eclipse 4. Programowanie wtyczek na przykładach
9. Uruchom testową wersję Eclipse i otwórz Widok drzewa stref czasowych lub Widok tabeli stref czasowych. Kliknij prawym przyciskiem myszy element TimeZone (jeden z liści drzewa lub wiersz tabeli), a pojawi się wpis Pokaż czas. Kliknij polecenie, by zobaczyć okno dialogowe z czasem.
Co się stało? Widoki z poprzedniego rozdziału i wiedza na temat konstrukcji poleceń z tego rozdziału pozwalają w sposób uniwersalny dodawać polecenia do wybranych typów obiektów. Ten sposób rejestracji poleceń ma wiele do zaoferowania, bo dowolna strefa czasowa, która zostanie udostępniona jako wybór w przyszłości, będzie miała automatycznie dodany element menu Pokaż czas. Polecenia definiują ogólnie operację, a procedury obsługi łączą polecenia z implementacjami. Menu kontekstowe zależne od rodzaju obiektów zapewnia punkt rozszerzenia dostępny pod adresem locationURI popup:org.eclipse.ui.popup.any. W ten sposób menu można dodać do dowolnego menu kontekstowego używającego MenuManager po wskazaniu jako celu TimeZone. Klasa MenuManager odpowiada za nasłuchiwanie gestów myszy związanych z wyświetleniem menu i przekazanie do menu właściwych danych. W przykładzie polecenie stawało się aktywne, gdy obiekt był instancją klasy TimeZone lub też mógł zostać do takiej instancji skonwertowany. Oznacza to, że inny rodzaj obiektu (na przykład wizytówka) może posiadać adapter konwertujący do obiektu TimeZone, a tym samym dopuszczający do wyświetlenia polecenia pokazania czasu.
Sprawdź się — wykorzystanie menu i pasków narzędziowych Sposób dodawania menu widoku przypomina dodawanie menu kontekstowego — wartość locationURI wskazuje na identyfikator widoku, a nie na element menu. Dodaj menu Pokaż czas do widoku stref czasowych jako menu widoku. Innym sposobem dodania menu jest dodanie go do paska narzędziowego, czyli jako ikonę głównego okna Eclipse. Dodaj ikonę Pokaż czas do głównego paska narzędziowego IDE Eclipse. By umożliwić testowanie widoków, dodaj element menu, który pozwala na wyświetlenie widoków TimeZone po wywołaniu polecenia PlatformUI.getActiveWorkbenchWindow().getActive Page().showView(id).
126
Rozdział 4. • Interakcja z użytkownikiem
Quiz — działanie menu P1. Jaka jest różnica między Action i Command. Której z klas należy używać w nowym kodzie? P2. W jaki sposób powiązać obiekt Command z menu? P3. Czym jest klawisz M1? P4. W jaki sposób powiązać kombinację klawiszy z poleceniem? P5. Jakie ma zadanie locationURI z menu? P6. Jak tworzy się menu kontekstowe?
Zadania i paski postępu Interfejs użytkownika jest jednowątkowy, zatem polecenie zajmujące naprawdę sporo czasu zablokuje interfejs użytkownika i ten nie będzie niczego odrysowywał ani przyjmował nowych zgłoszeń. Oznacza to, że długo działające operacje trzeba wykonywać w dodatkowym wątku, by zapobiegać zawieszeniu się interfejsu użytkownika. Choć standardowa biblioteka Javy zawiera klasę java.util.Timer, API zadań z Eclipse zapewnia wygodny mechanizm zarówno do uruchamiania zadań, jak i raportowania ich postępu. Co więcej, umożliwia grupowanie zadań, a nawet ich wstrzymywanie i łączenie.
Kroki do wykonania — uruchamianie operacji działających w tle Jeśli wykonanie polecenia zajmuje sporo czasu, interfejs użytkownika przez cały ten czas pozostanie zablokowany. Wynika to z faktu, iż interfejs użytkownika to tylko jeden wątek, a uruchomienie polecenia następuje z poziomu interfejsu użytkownika, więc jest częścią tego wątku. Warto więc, by operacje działające potencjalnie długo znajdowały się w osobnych wątkach. Oczywiście można utworzyć nowy obiekt Thread (jak w pierwszych przykładach z zegarem) lub skorzystać z innych technik, takich jak obiekt Timer. System Eclipse oferuje własny mechanizm wykonywania zadań (klasa Job), w tym wersję działającą w kontekście interfejsu użytkownika (UIJob). 1. Otwórz klasę HelloHandler i przejdź do metody execute(). Zastąp jej treść następującym fragmentem kodu. public Object execute(ExecutionEvent event) { Job job = new Job("Chcę powiedzieć witaj") { protected IStatus run(IProgressMonitor monitor) { try { Thread.sleep(5000);
127
Eclipse 4. Programowanie wtyczek na przykładach
} catch (InterruptedException e) { } MessageDialog.openInformation(null, "Witaj", "świecie"); return Status.OK_STATUS; } }; job.schedule(); return null; }
2. Uruchom instancję Eclipse i wybierz z menu polecenie Help/Witaj (wyświetlenie polecenia wymaga wcześniejszego otwarcia pliku z klasą Javy). Otwórz widok Progress, w którym pojawi się zadanie Chcę powiedzieć witaj. Niestety, po chwili pojawi się następujące okno dialogowe.
3. Problem pojawił się, ponieważ zadanie (Job) działa w wątku niezwiązanym z wątkiem interfejsu użytkownika, więc próba użycia MessageDialog kończy się zgłoszeniem wyjątku. W celu naprawy problemu trzeba utworzyć drugi obiekt Job lub Runnable, który zadziała w wątku interfejsu użytkownika. Zastąp wywołanie MessageDialog następującym fragmentem. MessageDialog.openInformation(null, "Witaj", "świecie"); Display.getDefault().asyncExec(new Runnable() { public void run() { MessageDialog.openInformation(null, "Witaj", "świecie"); } });
Przykład korzysta z asyncExec() do przeprowadzenia wyświetlenia w wątku interfejsu użytkownika (podobnie jak czyni to metoda SwingUtilities.invokeLater() z Swing). 4. Uruchom testową wersję Eclipse, wybierz polecenie Witaj i po pięciu sekundach pojawi się okno z komunikatem.
128
Rozdział 4. • Interakcja z użytkownikiem
Co się stało? Każda akcja związana z interfejsem użytkownika musi działać w przeznaczonym do tego wątku, jeśli więc akcja wykonuje się długo, interfejs będzie sprawiał wrażenie, że słabo reaguje. Najlepiej zatem wszystkie dłuższe prace wykonywać poza wątkiem interfejsu użytkownika, ale wszystkie modyfikacje w interfejsie po wykonaniu zadania trzeba ponownie zrobić w wątku interfejsu użytkownika. W przykładzie wykorzystano zarówno klasę Job (mechanizm kolejkowania nazwanych procesów, które można monitorować w widoku Progress), jak i metodę asyncExec() do wyświetlenia końcowego okna dialogowego. W obu przykładach konieczne było użycie klas wewnętrznych, które dodatkowo zaciemniają właściwy kod. W przyszłych wersjach języka Java obsługujących wyrażenia lambda ten dodatkowy narzut uda się znacząco ograniczyć.
E4: Ponieważ
Eclipse 4 może stosować różne renderery, klasa Display nie jest dobrym celem do wysyłania zadań UIJob. Zamiast tego użyj instancji UISynchornize do uzyskania dostępu do metod asyncExec() lub syncExec().
Sprawdź się — użycie zadania UIJob Zamiast zgłaszać aktualizację interfejsu użytkownika metodą Display.asyncExec(), użyj klasy UIJob. Działa dokładnie tak samo jak klasa Job, ale trzeba przesłonić metodę runInUIThread() zamiast metody run(). Rozwiązanie to przydaje się, gdy jest więcej interakcji z użytkownikiem, na przykład pokazuje się prośba o wprowadzenie dodatkowych danych.
Kroki do wykonania — raportowanie postępu prac Korzystając z klasy Job, należy okresowo informować użytkownika o aktualnym stanie prac. Domyślnie klasa nie zapewnia żadnych informacji o postępach prac, więc wyświetla standardowy komunikat busy. Wykonywane zadanie otrzymuje obiekt IProgressMonitor, którego może użyć do powiadamiania użytkownika o postępie prac (lub uzyskania informacji o wstrzymaniu operacji). Monitor postępów ma kilka zadań, a każde z nich określa łączną liczbę jednostek do wykonania. W zadaniach, dla których nie zna się dokładnie ilości prac do wykonania, można użyć wartości UNKNOWN, która spowoduje wyświetlenie standardowego komunikatu busy. 1. Otwórz klasę HelloHandler i przejdź do metody execute(). W metodzie run() wewnętrznego zadania Job dodaj na początku wywołanie beginTask(), a następnie co sekundę wywołuj w pętli oczekiwania metodę worked(). Oto cały fragment kodu.
129
Eclipse 4. Programowanie wtyczek na przykładach
protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask("Przygotowuję się", 5000); for(int i=0;i
3. Dodaj folder OSGI-INF w pliku build.properties, by upewnić się, że zostanie dodany w trakcie budowania paczki. bin.includes = OSGI-INF/,\ META-INF/,\
4. Aby system wczytał i utworzył komponent, dodaj w pliku META-INF/MANIFEST.MF poniższy nagłówek. Service-Component: OSGI-INF/*.xml
5. Na końcu wstrzyknij wartość do aplikacji. W części Hello dodaj następujący kod. @Inject @Named("math.random") private Object random; @PostConstruct public void create(Composite parent) { label = new Label(parent, SWT.NONE); label.setText(window.getLabel() + " " + random); }
6. Uruchom aplikację, a w zakładce Witaj pojawi się tytuł okna wraz z losową wartością. Każde uruchomienie aplikacji spowoduje wyświetlenie innej wartości.
Co się stało? Interfejs IEclipseContext umożliwia pobranie wyliczonych wartości, a także wartości wstawianych do systemu wykonawczego. Obliczeniami zajmuje się funkcja rejestrowana za pomocą interfejsu IContextFunction, choć obecnie jedynym sposobem rejestracji pozostaje model deklaratywny przedstawiony w przykładzie.
208
Rozdział 7. • Model Eclipse 4
Klasa implementacji powinna rozszerzać klasę ContextFunction, ponieważ interfejs IContextFunction jest oznaczony jako @NoImplement. Umożliwia to dodanie dodatkowych metod. W Eclipse 4.3 w interfejsie pojawiła się nowa metoda compute(IEclipseContext,String), która została również dodana do klasy bazowej ContextFunction. API deklaratywnego definiowania w OSGi umożliwia utworzenie usługi i udostępnienie jej wszystkim potrzebującym klientom. Aby zarejestrować usługę w DS, trzeba wykonać dokument usługi (w przykładzie jest to random.xml) i odnieść się do niego w nagłówku Service-Component z pliku MANIFEST.MF. Po zainstalowaniu wtyczki komponent zauważa nagłówek, odczytuje dokument i tworzy klasę. Klasa staje się dostępna w kontekście Eclipse 4 i może być wstrzykiwana. Pamiętaj, że wartość wyliczona przez funkcję zostaje zapamiętana po pierwszym wykonaniu. Jeśli nawet kod zmieni się w taki sposób, by wstrzykiwać wartość jako parametr metody, zawsze będzie pojawiała się pierwsza wartość. Choć IEclipseContext zawiera metodę usuwającą wartość, nie powoduje ona usunięcia z wszystkich kontekstów. Dla danych wyliczanych za każdym razem zaleca się użycie usługi OSGi.
Kroki do wykonania — użycie preferencji Oprócz wstrzykiwania konkretnych elementów, z kontekstu można również pobrać preferencje z mechanizmu preferencji z Eclipse. Preferencje są przechowywane w hierarchicznej strukturze węzłów. Każdy węzeł posiada identyfikator (najczęściej nazwę wtyczki) oraz pewną liczbę par klucz-wartość. Adnotacja @Preference umożliwia łatwy dostęp do preferencji. 1. Dodaj pole String greeting do części Hello. Aby pobrać preferencję, dodaj adnotacje @Inject i @Preferece w sposób przedstawiony poniżej. @Inject @Preference(nodePath="com.packtpub.e4.application", value="greeting") private String greeting;
2. Zmodyfikuj metodę create(), by korzystała z wartości greeting jako początkowej wartości etykiety w zakładce Witaj. @PostConstruct public void create(Composite parent) { label = new Label(parent, SWT.NONE); label.setText(greeting+" "+window.getLabel()+" "+random);
3. Uruchom aplikację, a w etykiecie, w części dotyczącej greeting pojawi się wartość null. 4. Aby ustawić wartość preferencji, trzeba do części Hello wstrzyknąć obiekt IEclipsePreferences. Dodaj nowe pole o nazwie prefs służące do interakcji z mechanizmem preferencji. @Inject @Preference(nodePath="com.packtpub.e4.application") private IEclipsePreferences prefs;
209
Eclipse 4. Programowanie wtyczek na przykładach
Jeśli nodePath preferencji nie zostanie wskazany, system w trakcie wykonywania kodu zgłosi wyjątek.
5. Zmodyfikuj utworzoną wcześniej metodę receiveEvent() i ustaw wartość koloru jako wartość greeting. @Inject @Optional public void receiveEvent( @UIEventTopic("rainbow/colour") String data) { label.setText(data); prefs.put("greeting", "Lubię " + data); prefs.sync(); }
6. Uruchom aplikację, przejdź do zakładki Tęcza i wybierz kolor. Przełącz się na zakładkę Witaj i upewnij, że zdarzenie zostało odebrane. Teraz zamknij aplikację i uruchom ją ponownie. Zapisana preferencja greeting powinna pojawić się w etykiecie (o ile przestrzeń robocza nie była czyszczona między uruchomieniami). 7. Zmiany w wartości preferencji są dynamicznie wstrzykiwane do części, ale kod nie otrzymuje żadnej notyfikacji, gdy wstrzyknięte pole uległo zmianie. By uzyskać informację o ustawieniu nowej wartości, preferencję można wstrzykiwać jako parametr oznaczony @Preference do metody nazwanej @Optional. @Inject @Optional void setText(@Preference(nodePath="com.packtpub.e4.application", value="greeting") String text) { if (text != null && label != null && !label.isDisposed()) { // Należy działać w wątku interfejsu użytkownika! label.setText(text); } }
8. Jeśli preferencja zostanie ustawiona, tekst etykiety również się uaktualni. Wywołanie preferencji nie zawsze musi odbyć się z poziomu wątku interfejsu użytkownika, więc należy je odpowiednio delegować. Może się zdarzyć, że metoda zostanie wywołana w trakcie uruchamiania lub wyłączania aplikacji, więc warto chronić się przed wartościami null lub zniszczoną etykietą.
Co się stało? Pobieranie preferencji za pomocą wstrzykiwania znacząco upraszcza uzyskiwanie wartości i ich ustawianie. Użycie pojedynczej wartości preferencji to najprostszy sposób na pobranie poszczególnych wartości. Jeśli preferencja ma ulec zmianie, niezbędna okazuje się referencja do obiektu IEclipsePreferences.
210
Rozdział 7. • Model Eclipse 4
Jeśli interfejs użytkownika musi reagować na zmiany w preferencjach, warto wartość preferencji wstrzykiwać do metody ustawiającej. Gdy preferencja zmieni zawartość, metoda zostanie wywołana i będzie można uaktualnić interfejs użytkownika. Pamiętaj, że ustawianie wartości preferencji może być wywołane z poziomu dowolnego wątku. Jeśli zmiana wymaga aktualizacji interfejsu użytkownika, upewnij się, że będzie miała miejsce z poziomu wątku tegoż interfejsu. Sposób realizacji tego zadania przedstawiono w kolejnym ćwiczeniu.
Kroki do wykonania — interakcja z interfejsem użytkownika Czasem konieczne jest pisanie kodu działającego w wątku interfejsu użytkownika, ale w sytuacji, gdy zmiana jest realizowana wewnątrz procedury obsługi, nie wiadomo, czy stanowi część wątku interfejsu użytkownika, czy też nie. W Eclipse 3.x istnieją metody Display.getDefault().syncExec(), która uruchamia kod Runnable wewnątrz wątku interfejsu użytkownika, i .asyncExec() dla wątków spoza interfejsu użytkownika. W Eclipse 4 wprowadzona została klasa UISynchronize, która wprowadza abstrakcyjny mechanizm wykonywania kodu w wątku interfejsu użytkownika. (Przypomina interfejs dla Display, ale Display go nie implementuje i nie jest to interfejs). Zapewnia metody syncExec() i asyncExec() używane do harmonogramowania zadań Runnable. Jeśli długo wykonywana operacja musi zaktualizować interfejs użytkownika, zastosowanie UISynchronize ułatwia realizację zadania we właściwym wątku. 1. Utwórz pole typu Button w części Hello i dołącz procedurę nasłuchiwania, która po naciśnięciu przycisku wywoła setEnabled(false) na samej sobie. W tym samym czasie dodaj harmonogramowane zadanie Job, które po jednej sekundzie wywoła setEnabled(true). private Button button; @PostConstruct public void create(Composite parent) { button = new Button(parent, SWT.PUSH); button.setText("Nie klikaj"); button.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { button.setEnabled(false); new Job("Zwolnienie przycisku") { @Override protected IStatus run(IProgressMonitor monitor) { button.setEnabled(true); return Status.OK_STATUS; } }.schedule(1000); } @Override
211
Eclipse 4. Programowanie wtyczek na przykładach
public void widgetDefaultSelected(SelectionEvent e) { } }); ... }
2. Gdy aplikacja działa i użytkownik kliknie przycisk, zostanie on od razu zablokowany (wyszarzony). Sekundę później w dzienniku zdarzeń pojawi się wpis z komunikatem „Invalid thread access”. !MESSAGE An internal error occurred during: "Button Pusher". !STACK 0 org.eclipse.swt.SWTException: Invalid thread access at org.eclipse.swt.SWT.error(SWT.java:4361)
3. Błąd pojawił się, ponieważ setEnabled() trzeba wywołać z poziomu wątku interfejsu użytkownika. Choć można to zrobić, korzystając z Display.getDefault().syncExec(), w Eclipse 4 istnieje rozwiązanie bazujące na adnotacjach. Wstrzyknij instancję UISynchronize do części Hello. @Inject private UISynchronize ui;
4. Zmodyfikuj implementację Job w metodzie create() w sposób przedstawiony poniżej. protected IStatus run(IProgressMonitor monitor) { ui.asyncExec(new Runnable() { @Override public void run() { button.setEnabled(true); } }); return Status.OK_STATUS; }
5. Uruchom aplikację i naciśnij przycisk. Zostanie on wyłączony, a po jednej sekundzie włączony ponownie.
Co się stało? Użycie UISynchronize umożliwia bezpieczną interakcję z interfejsem użytkownika. Innym sposobem realizacji zadania byłoby użycie UIJob. Jedną z zalet stosowania klasy UISynchronize jest to, że nie musi być powiązana z SWT. Eclipse 4 umożliwia korzystanie z innych rendererów części, co pozwala na zastosowanie wersji HTML, Swing lub JavaFX, takich jak e(fx)clipse. Tworząc wtyczki, które mają działać zarówno w Eclipse 4, jak i w Eclipse 3.x, nadal korzystaj z Display.getDefault() lub Display.getCurrent(), by harmonogramować aktualizacje interfejsu użytkownika, ponieważ we wcześniejszych wydaniach nie ma dostępu do UISynchronize.
212
Rozdział 7. • Model Eclipse 4
Korzystanie z poleceń, procedur obsługi i elementów menu Polecenia i procedury obsługi działają w Eclipse 4 podobnie jak w Eclipse 3. Polecenie reprezentuje ogólną operację, a procedura obsługi to kod, który ją implementuje. W Eclipse 4 implementacja procedury obsługi korzysta z zalet adnotacji, co pozwala uniknąć tworzenia podklas.
Kroki do wykonania — powiązanie menu z poleceniem i procedurą obsługi Podobnie jak w Eclipse 3.x, polecenie zawiera identyfikator i powiązaną klasę Handler. Można je powiązać z menu. W odróżnieniu od Eclipse 3.x, informacji nie określa się w pliku plugin.xml, ale w pliku Application.e4xmi. 1. Otwórz plik Application.e4xmi z projektu com.packtpub.e4.application. 2. Przejdź do węzła Application/Commands w drzewie po lewej i z menu kontekstowego wybierz polecenie Add child/Command. W formularzu po prawej wpisz następujące dane. W polu ID wpisz com.packtpub.e4.application.command.hello.
W polu Name wpisz helloCommand.
W polu Description wpisz Powiedz witaj.
3. W pakiecie com.packtpub.e4.application.handlers utwórz klasę o nazwie HelloHandler. Nie musi rozszerzać żadnej innej klasy czy implementować konkretnej metody. Utwórz metodę o nazwie hello(), która nie przyjmuje żadnych argumentów i wyświetla komunikat za pomocą System.out. Metodę opatrz adnotacją @Execute. 213
Eclipse 4. Programowanie wtyczek na przykładach
package com.packtpub.e4.application.handlers; import org.eclipse.e4.core.di.annotations.Execute; public class HelloHandler { @Execute public void hello() { System.out.println("Witaj, świecie"); } }
4. Procedurę obsługi trzeba zdefiniować w pliku Application.e4xmi. Przejdź w drzewie do węzła Application/Handlers i z menu kontekstowego wybierz polecenie Add child/Handler. Wypełnij formularz po prawej w sposób podany poniżej.
W polu ID wpisz com.packtpub.e4.application.handler.hello. W polu Command ustaw wartość helloCommand – com.packtpub.e4.application. command.hello. W polu Class URI wpisz wartość bundleclass://com.packtpub.e4.application/ com.packtpub.e4.application.handlers.HelloHandler.
5. Można wreszcie powiązać procedurę obsługi z menu. Przejdź do węzła Application/Windows/Trimmed Window/Main Menu/File i z menu kontekstowego wybierz polecenie Add child/HandledMenuItem. To samo zadanie można wykonać, klikając Menu - File i wybierając Add child/HandledMenuItem. Uzupełnij formularz po prawej zgodnie z poniższym zapisem.
W polu ID wpisz com.packtpub.e4.application.handledmenuitem.hello.
W polu Label wpisz Witaj.
W polu Tooltip wpisz Mówi Witaj, świecie.
W polu Command wybierz helloCommand – com.packtpub.e4.application. command.hello.
214
Rozdział 7. • Model Eclipse 4
6. Zapisz plik Application.e4xmi i uruchom aplikację. Przejdź do menu File i kliknij element menu Witaj, by w widoku Console głównego Eclipse pojawił się wpis Witaj, świecie.
Co się stało? Klasa HelloHandler zapewnia prosty komunikat w konsoli. Punkt wejścia został oznaczony adnotacją @Execute. Procedura obsługi może również, za pomocą wartości boolean zwracanej z metody oznaczonej adnotacją @CanExecute, zgłaszać, czy może być wykonywana. Brak adnotacji oznacza przyjęcie wartości true. Polecenie to ogólny identyfikator, który można połączyć z jedną lub wielu procedurami obsługi i jednym lub wieloma elementami interfejsu użytkownika, na przykład MenuItem, Button, lub nawet zarządzić jego wykonanie z poziomu kodu. Element helloCommand jest domyślnie powiązany z HelloHandler. Na końcu polecenie zostało połączone z elementem menu File/Witaj, by umożliwić jego wykonanie.
Kroki do wykonania — przekazywanie parametrów polecenia Wyświetlenie komunikatu w widoku Console oznacza, że polecenie działa prawidłowo, ale co zrobić, jeśli polecenie musi pobrać lokalny stan? Na szczęście, adnotacje @Named i @Inject umożliwiają wstrzyknięcie obiektów do metody w momencie jej wywołania.
215
Eclipse 4. Programowanie wtyczek na przykładach
1. Zmodyfikuj metodę hello(), by zamiast wyświetlać komunikat w System.out, otwierała nowe okno dialogowe w aktywnej powłoce. public void hello(@Named(IServiceConstants.ACTIVE_SHELL) Shell s){ MessageDialog.openInformation(s, "Witaj, świecie", "Witamy w Eclipse 4"); }
2. Inne argumenty można otrzymać z kontekstu zarządzanego przez interfejs IEclipseContext. Przykładowo nic nie stoi na przeszkodzie, by wstrzyknąć wartość zwróconą przez funkcję math.random zdefiniowaną w jednym z wcześniejszych ćwiczeń. public void hello(@Named(IServiceConstants.ACTIVE_SHELL) Shell s, @Named("math.random") double value) {
3. Jeśli ta sama procedura obsługi służy do realizacji różnych poleceń (na przykład Paste i Paste Special), rozróżnienie poleceń łatwo zrealizować, przekazując z poleceniem jakąś ustaloną wartość. Zmodyfikuj helloCommand przez dodanie parametru. Otwórz plik Application.e4xmi, przejdź do węzła Application/ Commands/helloCommand, wyświetl menu kontekstowe i wybierz polecenie Add child/Command Parameter. W formularzu po prawej umieść następujące wartości. W polu ID wpisz com.packtpub.e4.application.commandparameter.hello.value.
W polu Name wpisz hello.value.
Upewnij się, że opcja Optional jest włączona.
4. Aby przekazać wartość do polecenia, przejdź do pliku Application.e4xmi, a następnie rozwiń węzeł Application/Windows/Main Menu/Menu (File)/ HandledMenuItem (Witaj)/Parameters. Otwórz menu kontekstowe i wykonaj polecenie Add child/Parameter. Uzupełnij formularz następującymi danymi.
W polu ID wpisz com.packtpub.e4.application.parameter.hello.value. W polu Name wpisz com.packtpub.e4.application.commandparameter. hello.value.
216
W polu Value wpisz Parametr dla Witaj, świecie.
Rozdział 7. • Model Eclipse 4
5. Na końcu zmodyfikuj procedurę obsługi polecenia, by otrzymała zdefiniowaną wcześniej wartość. public void hello(@Named(IServiceConstants.ACTIVE_SHELL) Shell s, @Optional @Named("com.packtpub.e4.application.commandparameter.hello.value")String hello, @Named("math.random") double value) { MessageDialog.openInformation(s, "Witaj, świecie", hello+value); }
6. Uruchom aplikację i przejdź do menu File/Witaj. Parametr zostanie przekazany do wywołanej procedury obsługi.
Co się stało? Do metody w momencie jej wywoływania można wstrzyknąć dowolną wartość, o ile tylko jest ona dostępna w kontekście w chwili wywoływania procedury obsługi. Można ją uzyskać ze standardowych stałych (na przykład znajdujących się w IServiceConstants) lub też z własnych wartości wstrzykniętych w trakcie wykonywania programu. Innymi dostępnymi wartościami są: ACTIVE_WINDOW — aktualnie wyświetlane okno, ACTIVE_PART — obecnie wybrana część, ACTIVE_SELECTION — aktualne zaznaczenie.
Jeśli wartości są jednymi z ustawianych wartości, nic nie stoi na przeszkodzie, by zdefiniować je w menu lub poleceniu. Można je również ustawić z poziomu kodu, używając wywołania typu IEclipseContext.set().
217
Eclipse 4. Programowanie wtyczek na przykładach
Kroki do wykonania — utworzenie bezpośredniego menu i skrótów klawiszowych Choć procedury obsługi polecenia to bardzo ogólny sposób ponownego wykorzystania treści, można wprowadzić krótszą ścieżkę do implementacji menu — wystarczy użyć Direct MenuItem. Różnica między nim a Handled MenuItem polega na tym, że pierwszy zawiera referencję do klasy @Executable. 1. Aby dodać nowy bezpośredni element menu, otwórz plik Application.e4xmi i przejdź do Application/Windows/Trimmed Window/Main Menu/Menu (File). Kliknij menu prawym przyciskiem myszy i wybierz polecenie Add child/Direct MenuItem. Wypełnij formularz, włącznie z łączem Class URI do utworzonego wcześniej HelloHandler.
W polu Id wpisz com.packtpub.e4.application.directmenuitem.hello.
W polu Label wpisz Bezpośrednie Witaj.
W polu Class URI wpisz bundleclass://com.packtpub.e4.application/ com.packtpub.e4.application.handlers.HelloHandler.
2. Uruchom aplikację i wybierz polecenie File/Bezpośrednie Witaj, by zobaczyć ten sam komunikat, co wcześniej. 3. Z poleceniami aplikacji można powiązać skróty klawiszowe, które będą uaktywniane w określonych kontekstach. Domyślnymi kontekstami są: In Dialogs and Windows, In Dialogs i In Windows. Inne konteksty można utworzyć samodzielnie. Aby ustawić skrót klawiszowy, otwórz plik Application.e4xmi i przejdź do Application/ BindingTables/BindingTable – In Dialog and Windows. Kliknij prawym przyciskiem myszy ostatni z wymienionych węzłów i wybierz polecenie Add child/KeyBinding. Wypełnij formularz po prawej stronie.
218
Rozdział 7. • Model Eclipse 4
Pole ID pozostaw puste.
W polu Sequence wpisz M1+L.
W polu Command ustaw wartość helloCommand com.packtpub.e4.application.command.hello.
4. Uruchom aplikację i naciśnij klawisze M1+L (Cmd+L w systemie OS X lub Alt+L w systemie Windows i Linux). Wykonana zostanie procedura obsługi Hello.
Co się stało? Element Direct MenuItem umożliwia bezpośrednie powiązanie elementu menu z metodą wykonującą właściwą pracę, co pozwala uniknąć definiowania polecenia i procedury obsługi. W przypadku operacji dotyczących całej aplikacji, na przykład jej zamknięcia, użycie Direct MenuItem jest uzasadnione. Jeśli jednak polecenie musi być obsługiwane w innym kontekście, zastosowanie osobnego polecenia i procedury obsługi zapewnia znacznie większą elastyczność i łatwość ponownego użycia kodu. W odróżnieniu od Handled MenuItem, Direct MenuItem nie może zawierać powiązanych z nim parametrów poleceń. Co więcej, Direct MenuItem nie dopuszcza przypisania skrótów klawiszowych. By powiązać skrót klawiszowy z poleceniem, trzeba wybrać odpowiedni kontekst. Najczęściej jest to In Dialogs and Windows, ale można również wybrać inne konteksty (na przykład In Dialogs lub In Windows). Wszystkie konteksty znajdują się w węźle Binding Table pliku Application.e4xmi.
219
Eclipse 4. Programowanie wtyczek na przykładach
Sekwencja może być pojedynczym znakiem lub ich zbiorem. Metaznaki (M1, M2, M3, M4 i tak dalej) są zdefiniowane w punkcie rozszerzenia org.eclipse.ui.bindings i oznaczają: M1 to klawisz Cmd w OS X i Ctrl w systemie Windows, M2 to klawisz Shift na wszystkich platformach, M3 to klawisz Alt na wszystkich platformach, M4 to klawisz Ctrl w systemie OS X.
W momencie użycia skrótu klawiszowego wykonane zostanie polecenie wskazane na liście. Ponieważ to polecenie, można dla niego zdefiniować dodatkowe parametry, podobnie jak miało to miejsce w przypadku Handled MenuItem.
Kroki do wykonania — utworzenie menu kontekstowego i menu widoku Menu kontekstowe i menu widoku definiuje się w sposób deklaratywny w pliku Application.e4xmi. Ponieważ są specyficzne dla części, ich definicja znajduje się wewnątrz deklaracji części. 1. Otwórz plik Application.e4xmi. 2. Przejdź do węzła Application/Windows/Trimmed Window/Controls/Perspective Stack/Perspective/Controls/PartSashContainer/Part Stack/Part (Witaj)/Menus. 3. Kliknij prawym przyciskiem myszy węzeł Menus i z menu kontekstowego wybierz Add child/Popup Menu. Kliknij prawym przyciskiem węzeł Popup Menu i z menu kontekstowego wybierz Add child/HandledMenuItem. To taki sam element jak w innych menu. Wypełnij formularz po prawej zgodnie z poniższym opisem. W polu Label wpisz Witaj.
220
W polu Command ustaw wartość helloCommand - com.packtpub.e4. application.command.hello.
Rozdział 7. • Model Eclipse 4
4. Ponownie kliknij węzeł Menus i z menu kontekstowego wybierz Add child/View Menu. Kliknij prawym przyciskiem węzeł View Menu i z menu kontekstowego wybierz Add child/HandledMenuItem. Użyj tego samego polecenia i etykiety jak w menu kontekstowym. 5. Uruchom aplikację. Na górze po prawej będzie znajdowała się trójkątna ikona menu rozwijanego z menu widoku. Menu kontekstowego nie uda się wywołać, ponieważ trzeba powiązać komponent SWT z menu przy użyciu identyfikatora. 6. Przejdź do węzła Popup Menu i ustaw element ID na wartość com.packtpub.e4.application.popupmenu.hello.
7. Dodaj org.eclipse.e4.ui.workbench.swt jako zależność w zakładce Dependencies edytora pliku plugin.xml. 8. Dodaj w metodzie create() klasy Hello wiersz, który rejestruje menu kontekstowe o określonym identyfikatorze. W tym celu trzeba przekazać nowy parametr EMenuService menu, którego metoda registerContextMenu() zostanie wywołana. public void create(Composite parent, EMenuService menu) { menu.registerContextMenu(parent, "com.packtpub.e4.application. popupmenu.hello");
9. Uruchom aplikację i prawym przyciskiem myszy kliknij etykietę Witaj lub inny fragment części Witaj. Pojawi się menu kontekstowe z poleceniem Witaj.
Co się stało? Menu kontekstowe można powiązać z częścią, ale nie pojawi się automatycznie. Trzeba je zarejestrować w widgecie SWT. Menu kontekstowe może dotyczyć całej części lub tylko jednego z jej elementów.
221
Eclipse 4. Programowanie wtyczek na przykładach
Interfejs EMenuService dotyczy menu w Eclipse 4. Zostaje wstrzyknięty w trakcie tworzenia widgetu i zapewnia detekcję zdarzeń myszy i klawiatury związanych z wywołaniem menu kontekstowego. Dodanie menu View Menu wygląda dokładnie tak samo jak dodanie Popup Menu, ale nie wymaga stosowania dodatkowego kodu.
Tworzenie własnych klas do wstrzykiwania Framework wstrzykiwania z Eclipse 4 umożliwia stosowanie przy wstrzykiwaniu własnych klas i usług. Poza rejestracją usług OSGi, można także zdefiniować i utworzyć na żądanie obiekty POJO. Reguły dotyczące automatycznego tworzenia egzemplarza danego typu są następujące: musi to być klasa nieabstrakcyjna, musi posiadać nieprywatny konstruktor, musi być oznaczona adnotacją @Creatable.
Kroki do wykonania — tworzenie prostej usługi Obiekty POJO można tworzyć i udostępniać w kontekście Eclipse 4, co oznacza, że mogą być wstrzykiwane do innych klas lub tworzone na żądanie. Dzięki temu aplikację łatwiej wykonać w sposób elastyczny, bez tworzenia ścisłych związków między usługami. 1. Utwórz w pakiecie com.packtpub.e4.application klasę o nazwie StringService oznaczoną adnotacją @Creatable i zawierającą metodę process(), która przyjmuje obiekt String i zamienia wszystkie jego znaki na wielkie litery. import org.eclipse.e4.core.di.annotations.Creatable; @Creatable public class StringService { public String process(String string) { return string.toUpperCase(); } }
2. Dodaj wstrzykiwaną instancję StringService do klasy Rainbow. @Inject private StringService stringService;
3. Użyj wstrzykniętej usługi do przetworzenia nazwy koloru przed przesłaniem go do brokera zdarzeń.
222
Rozdział 7. • Model Eclipse 4
public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection sel = (IStructuredSelection)event.getSelection(); Object colour = sel.getFirstElement(); broker.post("rainbow/colour",stringService.process(colour.toString())); }
4. Uruchom aplikację. Przejdź do części Tęcza i wybierz kolor. Przełącz się do części Witaj i sprawdź, czy nazwa koloru jest pisana wielkimi literami.
Co się stało? Oznaczając POJO adnotacją @Creatable, informujemy system wstrzykiwania zależności w Eclipse 4, by po napotkaniu wstrzyknięcia wskazanego typu otworzył po prostu instancję wybranej klasy. Mechanizm wstrzykiwania wywoła konstruktor domyślny i wstrzyknie wynik do pola. Warto pamiętać, że wynikowa instancja nie zostanie umieszczona w kontekście. Oznacza to, że jeśli będą potrzebne kolejne instancje (jako inne pole w tej samej lub innej części), system wstrzykiwania utworzy nowy obiekt dla każdego wstrzyknięcia. Użycie POJO w ten sposób warto ograniczyć do usług bezstanowych. Jeśli usługa potrzebuje stanu współdzielonego przez wiele elementów wywołujących, zarejestruj usługę OSGi lub użyj singletonu wstrzykiwanego do kontekstu.
Kroki do wykonania — wstrzykiwanie podtypów Choć tworzenie POJO to efektywny sposób budowania prostych klas, w wielu sytuacjach odwoływanie się do definicji konkretnej klasy w definicjach innych klas bywa sporym ograniczeniem. Z tego powodu lepiej wskazać jako typ usługi klasę abstrakcyjną lub interfejs. 1. Utwórz nowy interfejs w pakiecie com.packtpub.e4.application. Nadaj mu nazwę IStringService. Zdefiniuj metodę process() jako metodę abstrakcyjną. public interface IStringService { public abstract String process(String string); }
2. Zmodyfikuj referencję w klasie Rainbow, by stosowała interfejs IStringService zamiast klasy StringService. Dodaj również interfejs IStringService w klasie StringService. @Inject private IStringService stringService;
3. Uruchom aplikację i przełącz się na zakładkę Tęcza. W widoku Console pojawi się błąd wstrzykiwania zależności. org.eclipse.e4.core.di.InjectionException: Unable to process "Rainbow.stringService": no actual value was found for the argument "IStringService".
223
Eclipse 4. Programowanie wtyczek na przykładach
4. Choć system wykonawczy wie, że StringService jest instancją @Creatable, nie szuka domyślnie podtypów interfejsu. Aby wstrzyknąć podtyp, zmodyfikuj klasę Activator, dodając poniższy kod. public class Activator implements BundleActivator { public void start(BundleContext bundleContext) throws Exception { Activator.context = bundleContext; InjectorFactory.getDefault().addBinding(IStringService.class). implementedBy(StringService.class); } }
5. Uruchom aplikację i przekonaj się, że część zostanie utworzona poprawnie.
Co się stało? Użycie interfejsu jako typu usługi to zalecana praktyka, ponieważ dodatkowo oddziela użycie usługi od jej implementacji. By jednak system wstrzykiwania zależności działał prawidłowo dla typów abstrakcyjnych (dla interfejsów i klas abstrakcyjnych, ale i dla konkretnych klas bez adnotacji @Creatable), trzeba napisać kod informujący wstrzykiwanie o istniejących zależnościach. Dowiązanie utworzone powyżej informuje InjectorFactory o potrzebie utworzenia instancji StringService, gdy programista prosi o IStringService.
Sprawdź się — użycie mostka narzędziowego Choć części Eclipse 3.x mogą działać w IDE Eclipse 4, to aby skorzystać z modelu Eclipse 4, kod musi być zaimplementowany jako POJO, bo wtedy można go zarejestrować w modelu. Aby dopasować POJO z Eclipse 4 do IDE Eclipse 3.x, trzeba użyć mostka Eclipse 4. Zainstaluj Eclipse E4 Tools Bridge for 3.x z witryny aktualizacji Eclipse 4, by zapewnić odpowiednie widoki. Następnie utwórz klasę o nazwie HelloView, która rozszerza DIViewPart i przekazuje instancję Hello.class do konstruktora superklasy. Zarejestruj HelloView w plugin.xml w taki sam sposób, jak inne widoki Eclipse 3.x. Od tego momentu część jest widoczna jako niezależny element w Eclipse 4 i jako widok w Eclipse 3.x.
Quiz — działanie Eclipse 4 P1. Czym jest model aplikacji i do czego służy? P2. Jaka jest różnica między częścią i widokiem? P3. Czy w Eclipse 4 nadal korzysta się z punktów rozszerzenia? P4. W jaki sposób obstylować części Eclipse 4? P5. Czym jest kontekst w Eclipse 4? 224
Rozdział 7. • Model Eclipse 4
P6. Jakiego rodzaju adnotacje stosuje się w Eclipse 4; jakie jest ich znaczenie? P7. Jaki jest sposób dostępu do preferencji w Eclipse 4? P8. W jaki sposób otrzymywać i wysyłać komunikaty przy użyciu mechanizmu zdarzeń? P9. W jaki sposób w Eclipse 4 otrzymać informację o zaznaczeniu?
Podsumowanie Eclipse 4 to nowy sposób budowania aplikacji Eclipse, który wprowadza wiele ulepszeń; dzięki nim tworzenie części (widoków i edytorów), a także pobieranie usług i zapewnienie komunikacji między nimi, jest znacznie prostsze. Jeśli buduje się bazujące na Eclipse aplikacje RCP, nie ma powodu, dla którego nie warto skorzystać z frameworka Eclipse 4 i wielu wprowadzonych w nim usprawnień. Jeśli tworzy się wtyczki, które muszą działać zarówno w Eclipse 3.x, jak i Eclipse 4, przed dokonaniem zmiany warto zastanowić się nad zgodnością wstecz. Jednym ze sposobów obsługi obu systemów jest wtyczka zgodności (właśnie z niej korzysta Eclipse 4, jeśli pobiera się SDK lub jeden z pakietów EPP), która umożliwia dalsze stosowanie API Eclipse 3.x. Oznacza to jednak, że w kodzie nie można używać elementów wprowadzonych w Eclipse 4.x. Inne podejście polega na napisaniu wtyczki bazującej na Eclipse 4, a następnie otoczenie jej warstwą zgodności ze starszą wersją. Taką warstwę zgodności zapewnia funkcjonalność Eclipse E4 Tools Bridge for 3.x dostępna z poziomu witryny aktualizacji Eclipse 4. Dostarczone klasy DIViewPart, DISaveableViewPart i DIEditorPart zapewniają adaptery dla punktów rozszerzeń z Eclipse 3.x. W następnym rozdziale przyjrzymy się mechanizmom tworzenia funkcjonalności i witryn aktualizacji, dzięki którym napisane do tej pory wtyczki uda się pobrać i zainstalować w innych aplikacjach Eclipse.
225
Eclipse 4. Programowanie wtyczek na przykładach
226
8 Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów Eclipse to coś znacznie więcej niż tylko aplikacja. Architektura bazująca na wtyczkach umożliwia łatwą instalację dodatkowych funkcji. Wtyczki łączy się w grupy nazywane funkcjonalnościami. Oba rodzaje elementów, czyli wtyczki i funkcjonalności, pobiera się z witryn aktualizacji. W ten sposób można zarówno dodawać nowe elementy do już istniejących aplikacji, jak i budować od podstaw nowe aplikacje i produkty.
W tym rozdziale: utworzymy funkcjonalność łączącą kilka wtyczek, wygenerujemy witrynę aktualizacji zawierającą funkcjonalności i wtyczki, przeprowadzimy kategoryzację witryny aktualizacji, zbudujemy aplikację, utworzymy produkt i wyeksportujemy go.
Eclipse 4. Programowanie wtyczek na przykładach
Grupowanie wtyczek jako funkcjonalności Choć właściwe funkcje w Eclipse zapewniają wtyczki, najczęściej nie instaluje się ich pojedynczo. Dawniej platforma Eclipse korzystała tylko z funkcjonalności, czyli wtyczek połączonych w grupy. Choć w systemie aktualizacji P2 (stosowanym w Eclipse od wersji 3.5) można instalować niezależne wtyczki, w zasadzie wszystkie istotne funkcje instaluje się poprzez system funkcjonalności.
Kroki do wykonania — tworzenie funkcjonalności Projekt funkcjonalności służy w Eclipse do tworzenia, testowania i eksportu funkcjonalności. Funkcjonalność grupuje wiele wtyczek w jedną, spójną całość. Przykładowo funkcjonalność JDT składa się z dwudziestu sześciu różnych wtyczek. Funkcjonalności służą również do konstrukcji witryn aktualizacji opisywanych dalej w tym rozdziale. 1. Utwórz projekt funkcjonalności, wybierając z menu polecenie File/New/Project…, a następnie zaznaczając na liście element Feature Project. 2. Nadaj projektowi nazwę com.packtpub.e4.feature. Będzie to domyślna nazwa stosowana jako Feature ID. Podobnie jak we wtyczkach, stosuje się odwrócone nazwy domenowe, choć nazwę kończy się tekstem feature, by odróżnić funkcjonalności od wtyczek, które reprezentują. Domyślną wersją jest 1.0.0.qualifier. Nazwa funkcjonalności (Feature Name) to nazwa, która pojawi się użytkownikowi w momencie instalacji. Domyślnie nazwa odzwierciedla tekst użyty w ostatnim segmencie nazwy projektu.
228
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
3. Kliknij przycisk Next, a pojawi się lista wyboru wtyczek. Zaznacz wtyczkę com.packtpub.e4.clock.ui.
4. Kliknij przycisk Finish, by utworzyć projekt funkcjonalności. 5. Kliknij dwukrotnie plik feature.xml, by otworzyć go w edytorze. Przejdź do zakładki Plug-ins i upewnij się, że wtyczka zegara znajduje się na liście. 6. W zakładce Information dodaj dodatkowe informacje, takie jak opis funkcjonalności, umowy licencyjne i informacje o prawach autorskich.
Co się stało? Utworzyliśmy projekt funkcjonalności com.packtpub.e4.feature zawierający plik feature.xml. Informacje wskazane w oknie dialogowym znajdują się w pliku, który można w dowolnej chwili zmienić.
229
Eclipse 4. Programowanie wtyczek na przykładach
Identyfikator funkcjonalności musi być unikatowy, ponieważ to właśnie on posłuży Eclipse i P2 do instalacji. Wersja funkcjonalności stosuje ten sam format, co wtyczki, czyli major.minor. micro.qualifier. Oto znaczenie poszczególnych elementów. Zmiana numeru major oznacza wprowadzenie zmian niezgodnych wstecz. Zmiana numeru minor oznacza nową funkcjonalność z zapewnieniem zgodności wstecz. Zmiana numeru micro oznacza brak nowych funkcjonalności (poprawiono jedynie zauważone błędy). Kwalifikator (qualifier) może być dowolną wartością tekstową. Specjalne słowo kluczowe qualifier powoduje, że Eclipse zastąpi ten fragment numerem kompilacji, który domyślnie składa się z daty i znacznika czasowego. Wymieniona w pliku wtyczka została wybrana z listy. Domyślnie numer wersji to 0.0.0, ale po jej opublikowaniu zostanie wybrana najwyższa dostępna wersja. Dojdzie też do zmiany tekstu z numerem wersji we wtyczce. W pliku feature.xml mogą pojawić się również dodatkowe elementy, takie jak license , description i copyright. Są opcjonalne, ale jeśli istnieją, zostaną wyświetlone w oknie dialogowym w momencie instalacji funkcjonalności.
Kroki do wykonania — eksport funkcjonalności Po utworzeniu funkcjonalności i dodaniu przynajmniej jednej wtyczki można rozpocząć eksport z poziomu IDE. Wyeksportowaną funkcjonalność można zainstalować w innej kopii Eclipse, co wkrótce przećwiczymy. Warto pamiętać, że eksport funkcjonalności powoduje również zbudowanie i wyeksportowanie wszystkich powiązanych wtyczek. 1. Aby wyeksportować wtyczkę, wywołaj polecenie File/Export/Deployable features. Pojawi się okno dialogowe z wyborem wszystkich funkcjonalności dostępnych w IDE. 2. Zaznacz com.packtpub.e4.feature i wskaż odpowiedni folder (patrz rysunek na następnej stronie). 3. Kliknij przycisk Finish, a funkcjonalność i wtyczki zostaną wyeksportowane. 4. Otwórz folder docelowy w eksploratorze plików, by zobaczyć następujące pliki: artifacts.jar, content.jar, features/com.packtpub.e4.feature_1.0.0.201402081015.jar, plugins/com.packtpub.e4.clock.ui_1.0.0.201402081015.jar.
Co się stało? Polecenie File/Export/Deployable features zrealizowało wiele zadań. Przede wszystkim skompilowało wtyczki do osobnych plików JAR. Następnie umieściło zawartość projektu funkcjonalności w pliku ZIP i na końcu przeniosło oba elementy do folderów plugins i features.
230
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
W momencie eksportu funkcjonalności dochodzi do budowania wtyczek, więc problemy z eksportem najczęściej wynikają z błędów kompilacji wtyczki. Aby znaleźć problem z kompilacją wtyczki, sprawdź plik build.properties. Steruje on budowaniem bazującym na narzędziu ant. Czasem PDE znajdzie w tym pliku błędy lub ostrzeżenia, zdarza się to szczególnie wtedy, kiedy kompilowane foldery zostały przeniesione po utworzeniu projektu. Plik build.properties wygląda podobnie do poniższego fragmentu. source.. = src/ output.. = bin/ bin.includes = plugin.xml,\ META-INF/,\ .,\ icons/
Jeśli pojawią się problemy, sprawdź, czy ścieżki odpowiadają folderom wtyczek. Wpis source.. to w zasadzie odniesienie do aktualnego folderu. Jeśli ma powstać wiele plików JAR, pojawią się wpisy source.plikjar i source.innyjar. Dyrektywa source pojawia się, jeśli wtyczki mają eksportowane źródła. Klasy i inne skompilowane zasoby pochodzą z właściwości output. Jeśli zmieni się nazwa folderu wyjściowego (na przykład na target lub classes), upewnij się, że właściwość output z pliku build.properties została zaktualizowana. Jeśli istnieją zasoby spoza Javy, które trzeba wyeksportować, muszą znaleźć się w tym pliku. Gdy zostanie wskazany folder (na przykład icons/), w strukturze pliku JAR będzie on odtworzony w dokładnie ten sam sposób. Nie trzeba jawnie wskazywać poszczególnych elementów umieszczonych w folderze icons/. 231
Eclipse 4. Programowanie wtyczek na przykładach
Oracle Java 1.7 w systemie OS X Próba eksportu funkcjonalności w systemie OS X przy użyciu Oracle Java 1.7 może spowodować zgłoszenie błędu /Library/Java/JavaVirtualMachines/.../Contents/Home/Classes does not exist. Aby błąd naprawić, przejdź do folderu Home wymienionego w błędzie i wykonaj polecenie sudo ln -s jre/libClasses.
Kroki do wykonania — instalacja funkcjonalności Funkcjonalność po wyeksportowaniu można zainstalować w Eclipse. W tym celu można wykorzystać aktualną kopię Eclipse lub też użyć nowej wersji z inną przestrzenią roboczą. (W systemie OS X dwukrotne kliknięcie Eclipse.app ponownie wyświetli uruchomione Eclipse, więc trzeba przejść w konsoli do folderu z aplikacji i wywołać polecenie eclipse). 1. Aby zainstalować funkcjonalność, użyj polecenia Help/Install New Software…. 2. W oknie dialogowym, które się pojawi, wpisz adres URL folderu w polu Work with. Jeśli funkcjonalność została wyeksportowana do folderu /tmp/exported, wpisz w polu Work with wartość file:///tmp/exported. W systemie Windows należy zastosować zwykłe ukośniki, więc jeśli eksport odbył się do folderu c:\temp\exported, użyj wartości file:///c:/temp/exported. 3. Po wpisaniu adresu URL naciśnij klawisz Enter. Pojawi się komunikat no categorized items. Wyłącz opcję Group items by category na dole okna i zobaczysz funkcjonalność Feature.
232
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
4. Zaznacz element Feature i przejdź przez kolejne ekrany aż do momentu uaktywnienia przycisku Finish. Spowoduje to instalację funkcjonalności i wszystkich jej zależności. 5. Uruchom ponownie Eclipse, by zakończyć instalację. 6. Upewnij się, że widoki zegarów są dostępne po użyciu polecenia Window/Show View/Other/Śledzenie czasu.
Co się stało? Funkcjonalność wyeksportowana w poprzednim zadaniu została zaimportowana z tej samej lokalizacji. Dopóki funkcjonalność nie znajdzie się w kategorii, nie pojawi się na liście elementów do instalacji. Wyłączenie opcji Group items by category powoduje, że pojawią się wszystkie funkcjonalności, nie tylko te, które zostały umieszczone w kategoriach. Nazwa Feature wynika z domyślnej nazwy zastosowanej w pliku feature.xml; można ją dowolnie zmienić, jeśli trzeba. Jeżeli instancja Eclipse nie wyświetla żadnych zmian (po modyfikacji funkcjonalności lub wtyczki), przejdź do preferencji i znajdź element Install/Update/Available Software Sites. Kliknij wyeksportowane repozytorium. Włączony zostanie przycisk Reload znajdujący się po prawej stronie. Kliknij go, by wymusić załadowanie nowej wersji repozytorium.
233
Eclipse 4. Programowanie wtyczek na przykładach
Po ponownym załadowaniu repozytorium przejdź do Help/Install New Software, a pojawi się opcja aktualizacji. Jeśli jej brakuje, sprawdź, czy wersja funkcjonalności kończy się tekstem .qualifier. Jeśli numer wersji nie zwiększa się, Eclipse nie może stwierdzić, że wtyczka lub funkcjonalność zmieniła się, i nie zainstaluje jej ponownie. Upewnij się, że wyeksportowana wersja kończy się nowszą datą. W przeciwnym razie usuń folder, wyeksportuj wtyczkę i ponownie przeładuj repozytorium.
Kroki do wykonania — kategoryzacja witryny aktualizacji Mechanizm Group items by category umożliwia wyświetlenie na liście niewielkiej liczby funkcjonalności umieszczonych w kategoriach. Eclipse jest aplikacją wysoce modułową i nawet standardowe instalacje zawierają ponad czterysta funkcjonalności i ponad sześćset wtyczek. Jednowymiarowa lista wszystkich funkcjonalności byłaby bardzo długa i nie zapewniałaby najlepszych wrażeń użytkownikowi (sama aplikacja Mylyn umożliwia instalację ponad stu pięćdziesięciu funkcjonalności w zależności od wybranych opcji). Kategoryzacja działa na podstawie pliku category.xml (znanego również jako site.xml), który definiuje kategorię i kolekcję funkcjonalności (w Eclipse 4.3 i wyższych również wtyczki). Po włączeniu opcji Group items by category wyświetlane są tylko grupy i funkcjonalności zdefiniowane w pliku category.xml (cała reszta jest ukryta). Tworzenie pliku odbywa się w osobnym projekcie Update Site Project. 1. Utwórz nowy projekt Update Site Project o nazwie com.packtpub.e4.update. Projekt zawiera plik site.xml (zmiana nazwy z site.xml na category.xml spowoduje błąd, więc jej nie zmieniaj). 2. Kliknij dwukrotnie plik site.xml, by otworzyć edytor.
234
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
3. Kliknij przycisk New Category i wpisz w formularzu następujące dane.
W polu ID wpisz com.packtpub.e4.category.
W polu Name wpisz Przykładowa kategoria E4.
W polu Description wpisz Zawiera funkcjonalności z książki o Eclipse 4 autorstwa Aleksa Blewitta..
4. Upewnij się, że element com.packtpub.e4.category jest zaznaczony, i kliknij przycisk Add Feature. 5. W oknie, które się pojawi, znajdź funkcjonalność com.packtpub.e4.feature i zaznacz ją, by dodać do kategorii.
6. Kliknij przycisk Build All, a witryna aktualizacji zmaterializuje się w folderze projektu.
235
Eclipse 4. Programowanie wtyczek na przykładach
7. Na końcu upewnij się, że kategoria została utworzona prawidłowo, przechodząc do Help| Install New Software i umieszczając ścieżkę do przestrzeni roboczej. 8. Upewnij się, że opcja Group items by category jest włączona. Powinna pojawić się kategoria zawierająca przygotowaną funkcjonalność.
Co się stało? Plik site.xml nie jest wymagany przez nowoczesne systemy wykonawcze Eclipse, ale artifacts.jar i content.jar zawierają pliki XML wymagane przez P2 do przeprowadzenia instalacji. Znajduje się w nich lista wszystkich funkcjonalności i wtyczek, a także informacja o ograniczeniach związanych z instalacją (na przykład inne paczki). P2 generuje kategoryzację na podstawie plików site.xml lub category.xml. Pliki są w zasadzie takie same, ale witryna aktualizacji ma ładniejszy interfejs użytkownika związany z edycją i generowaniem treści.
236
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
Zawiera funkcjonalności z książki o Eclipse 4 autorstwa Aleksa Blewitta.
Plik zawiera listę funkcjonalności (a w nich znajdują się wtyczki) i służy do wygenerowania witryny aktualizacji, która zawiera foldery features i plugins oraz kilka plików w głównym folderze. Jeśli brakuje plików artifacts.jar lub content.jar (i nie ma pliku site.xml), Eclipse nie jest w stanie zainstalować elementów z repozytorium. Możliwość wczytania treści z repozytorium zawierającego tylko plik site.xml została usunięta w Eclipse 4.3 i nowszych wersjach.
W trakcie budowania witryny aktualizacji element .qualifier zostanie zastąpiony wygenerowanym numerem budowania, który zależy od aktualnej daty i czasu. Wartość tę można zmienić na dowolną inną, jeśli trzeba. Pliki artifacts.jar i content.jar są plikami ZIP zawierającymi pojedynczy plik xml. Plik ten jest umieszczony w plikach JAR tylko ze względu na kompresję; nic nie stoi na przeszkodzie, by plik ten serwować bezpośrednio (choć mniej wydajnie) jako artifacts.xml lub content.xml. Witrynę aktualizacji można umieścić na serwerze, by ułatwić instalację na innych kopiach Eclipse. Środowisko to obsługuje domyślnie protokoły HTTP i FTP, ale można je rozszerzyć również o obsługę innych protokołów. Witryna aktualizacji powinna uwidaczniać jedynie główne funkcjonalności. Oczywiście, może zawierać inne funkcjonalności lub wtyczki, ale tylko te najwyższego poziomu pojawiają się w oknie dialogowym i na liście zainstalowanych elementów dostępnej po wywołaniu polecenia Help/About/Installation Details.
Kroki do wykonania — zależność od innych funkcjonalności Jeśli funkcjonalność wymaga elementów zapewnianych przez inną funkcjonalność, można tę zależność wskazać w pliku feature.xml. Przykładowo instalacja funkcjonalności Eclipse 4 może zależeć od niektórych komponentów zapewnianych przez JGit, co oznacza, że jeśli JGit będzie
237
Eclipse 4. Programowanie wtyczek na przykładach
zainstalowane, wszystkie zależności zostaną spełnione. Aby dodać JGit jako zależność w funkcjonalności Eclipse 4, wykonaj poniższe kroki. 1. Otwórz plik feature.xml i przejdź do zakładki Dependencies.
2. Kliknij przycisk Add Feature i zaznacz org.eclipse.jgit na liście. Formularz po prawej zostanie wypełniony wersją wskazaną przez wtyczkę. Warto jednak użyć wersji nieco niższej, co zapewni lepszą zgodność, bo wskazana wersja będzie zapewne dostępna na większej liczbie systemów. Powstały plik feature.xml ma następującą postać.
3. Ponownie uruchom pełne budowanie poleceniem Build All. Folder features zawiera jedynie com.packtpub.e4.feature, a folder plugins — jedynie wtyczkę com.packtpub.e4.clock.ui. 4. Ponownie zainstaluj funkcjonalność (lub użyj Help/Check for Updates, jeśli folder został już wcześniej dodany). Tym razem poza instalacją funkcjonalności Eclipse 4 pojawi się również prośba o instalację wtyczki JGit, która zostanie pobrana ze standardowych witryn aktualizacji.
238
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
Co się stało? Dodając zależność od innej funkcjonalności, wskazuje się wyraźnie, że dodatkowa funkcjonalność musi istnieć w Eclipse przed instalacją właściwej wtyczki. Jeśli opcja Consult all update sites jest włączona, a funkcjonalność nie jest zainstalowana i nie ma jej w aktualnej witrynie aktualizacji, Eclipse poszuka jej w pozostałych znanych witrynach aktualizacji. Zauważ, że funkcjonalność JGit nie znajduje się w eksportowanej witrynie. To pożądane działanie, bo nie ma sensu duplikować funkcjonalności dostępnych w innych miejscach. Jeśli jednak chcemy jawnie dołączyć zależność, trzeba ją usunąć z zakładki Dependencies i umieścić w zakładce Included features. W ten sposób zależność requires zmieni się w pliku feature.xml na zależność includes.
Kroki do wykonania — tworzenie oznaczeń funkcjonalności Funkcjonalności zazwyczaj nie pojawiają się w oknie dialogowym About z Eclipse, ponieważ nie ma tam zbyt wiele miejsca. W oknie pojawią się tylko funkcjonalności najwyższego poziomu zawierające informacje o marce (lub inne oznaczenia tego typu). 1. Wykonaj polecenie Help/About Eclipse (w systemie OS X polecenie Eclipse/About Eclipse), a zobaczysz zbiór ikon reprezentujących zainstalowane funkcjonalności główne. Funkcjonalności te zawierają wtyczkę marki z plikiem about.ini, w którym znajdują się dodatkowe informacje.
2. Najpierw trzeba utworzyć powiązanie między funkcjonalnością i wtyczką z informacją o marce. Wykorzystajmy w tym celu ponownie wtyczkę com.packtpub.e4.clock.ui. Otwórz plik feature.xml, przejdź do zakładki Overview i w polu Branding plug-in wpisz com.packtpub.e4.clock.ui.
239
Eclipse 4. Programowanie wtyczek na przykładach
3. We wtyczce com.packtpub.e4.clock.ui utwórz plik about.ini o następującej zawartości. featureImage=icons/sample.gif aboutText=\ Wtyczka zegara\n\ \n\ Przykład użycia wtyczek do zapewnienia modułowości aplikacji\n
4. Zbuduj witrynę aktualizacji i zainstaluj wtyczkę. Po ponownym uruchomieniu wykonaj polecenie Help/About Eclipse. Niestety, tekst informacyjny nie pojawił się. Wynika to z faktu, iż choć plik about.ini stanowi część wtyczki, nie został umieszczony w pliku JAR. Zmodyfikuj plik build.properties z wtyczki com.packtpub.e4.clock.ui, by jawnie zawierał plik about.ini. bin.includes = plugin.xml,\ META-INF/,\ .,\ icons/,\ about.ini,\ ...
5. Wyeksportuj witrynę aktualizacji. Wygenerowany plik JAR zawiera plik about.ini. 6. Ponownie wczytaj aktualizację poleceniem Window/Preferences/Install/Update/Available Update Sites, zaznacz funkcjonalność i kliknij przycisk Reload. 7. Wykonaj polecenie Help/Check for Updates, by zaktualizować Eclipse. Uruchom je ponownie. 8. Wyświetl okno About, by zobaczyć ikonę funkcjonalności.
240
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
Co się stało? Funkcjonalność można powiązać z wtyczką zawierającą informację o marce (naprawdę to tylko ikona i dodatkowy tekst). Cała informacja znajduje się w pliku about.ini umieszczonym we wskazanej wtyczce. Opcjonalnie dodaje się ikonę 32×32 px. Ponieważ ikona wygenerowana przez kreator ma wymiary 16×16 px, jest mniejsza niż wszystkie inne ikony wyświetlone w oknie. Ikona funkcjonalności powinna mieć wymiary 32×32 px. Zapewnienie ikony o odpowiednim rozmiarze warto potraktować jako ćwiczenie dodatkowe.
Sprawdź się — zdalna publikacja zawartości Ponieważ witrynę aktualizacji można udostępnić na serwerze WWW, umieść wygenerowane pliki na serwerze i przeprowadź instalację. Ewentualnie użyj serwera Apache lub podobnego wbudowanego w system operacyjny, by udostępnić treść przy użyciu lokalnego serwera WWW.
Budowanie aplikacji i produktów Aplikacja Eclipse to zbiór funkcjonalności, a każda z funkcjonalności to zbiór wtyczek. Aplikację, w której znajdują się wszystkie wymienione elementy, nazywa się produktem. Produkt stosuje ogólną markę, określa nazwę aplikacji, koordynuje działanie na różnych platformach i zapewnia istnienie wszystkich elementów niezbędnych do działania w konkretnym systemie operacyjnym. W poprzednim rozdziale wykonaliśmy produkt bazujący na Eclipse 4, ale produkty działają w ten sam sposób zarówno w Eclipse 3.x, jak i 4.x.
241
Eclipse 4. Programowanie wtyczek na przykładach
Kroki do wykonania — wykonanie aplikacji bez interfejsu użytkownika Produkt przekazuje właściwą obsługę aplikacji, którą w zasadzie można traktować jako specyficzną wersję klasy Runnable. To główny punkt wejścia w aplikację, który odpowiada za utworzenie i usunięcie treści aplikacji. 1. Utwórz nową wtyczkę o nazwie com.packtpub.e4.headless.app. Upewnij się, że opcja This plug-in will make contributions to the UI jest wyłączona, a Would you like to create a 3.x rich client application ma ustawioną wartość No.
2. Kliknij przycisk Finish, by utworzyć projekt. 3. Otwórz projekt, wykonaj polecenie Plug-in Tools/Open Manifest z menu kontekstowego i przejdź do zakładki Extensions. To właśnie w tym miejscu Eclipse przechowuje listę rozszerzeń systemu. Tu też definiuje się aplikację. 4. Kliknij przycisk Add i w polu na górze wpisz applications. Upewnij się, że opcja Show only extension points from the required plug-ins jest wyłączona. Zaznacz punkt rozszerzenia org.eclipse.core.runtime.applications (patrz pierwszy rysunek na następnej stronie). 5. Po kliknięciu przycisku Finish pojawi się okno dialogowe z zapytaniem, czy dodać zależność od org.eclipse.equinox.app. Kliknij przycisk Yes (patrz drugi rysunek na następnej stronie).
242
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
6. Edytor przełączy się na widok drzewa. Kliknij element (application) prawym przyciskiem myszy i wybierz polecenie New/run, by utworzyć nową referencję do aplikacji.
243
Eclipse 4. Programowanie wtyczek na przykładach
7. Użyj com.packtpub.e4.headless.app.Application jako nazwy klasy i kliknij podkreślone łącze class*, by wyświetlić kreator klasy z wypełnioną nazwą klasy i interfejsem IApplication.
8. Zaimplementuj klasę w następujący sposób. public class Application implements IApplication { public Object start(IApplicationContext c) throws Exception { System.out.println("Aplikacja bez interfejsu"); return null; } public void stop() { } }
9. Uruchom aplikację, przechodząc do zakładki Extensions manifestu i klikając przycisk odtwarzania na górze po prawej, lub przy użyciu łącza Launch an Eclipse application. W widoku Console pojawi się wpis Aplikacja bez interfejsu.
Co się stało? Utworzenie aplikacji wymaga punktu rozszerzenia i klasy implementującej interfejs IApplication. Używając kreatora, utworzyliśmy aplikację i zaimplementowaliśmy metodę start() wyświetlającą prosty komunikat. Kliknięcie przycisku odtwarzania spowoduje utworzenie nowej konfiguracji uruchomieniowej wskazującej na aplikację.
244
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
Referencje są generowane automatycznie na podstawie danych z pliku plugin.xml. Gdy Eclipse uruchamia element wykonawczy, przygotowuje środowisko wykonawcze i przekazuje sterowanie do instancji aplikacji. Po zakończeniu wykonywania metody start() aplikacja wyłączy się.
Kroki do wykonania — tworzenie produktu Produkt Eclipse to definicja marki i referencja do aplikacji. Produkt ma wpływ na to, jakie funkcjonalności i wtyczki będą dostępne i które z nich będą uruchamiane (i w jakiej kolejności). W rozdziale 7. utworzyliśmy produkt uruchamiający aplikację Eclipse 4 (zapewnianą przez klasę org.eclipse.e4.ui.workbench.swt.E4Application), ale tym razem powiążemy produkt z aplikacją bez interfejsu użytkownika, by pokazać sam sposób łączenia. 1. Użyj polecenia File/New/Other/Plug-in Development/Product Configuration, by wyświetlić kreator produktu. 2. Zaznacz projekt com.packtpub.e4.headless.app i umieść tekst headless w polu File name. 3. Pozostaw włączoną opcję Create a configuration file with the basic settings. 4. Kliknij przycisk Finish, by utworzyć produkt. Otwórz plik headless.product w edytorze. 5. Wypełnij formularz następującymi danymi.
W polu ID wpisz com.packtpub.e4.headless.application.product.
W polu Version wpisz 1.0.0.
W polu Name wpisz Produkt bez interfejsu.
245
Eclipse 4. Programowanie wtyczek na przykładach
6. Kliknij przycisk New na prawo od część definiującej produkt. Pojawi się okno tworzenia produktu. Wypełnij je w następujący sposób.
W polu Defining plug-in wpisz com.packtpub.e4.headless.app.
W polu Product ID wpisz product.
Na liście Application wybierz com.packtpub.e4.headless.application.id.
Końcówką może być również id1, id2 lub inna podobna wartość. Pochodzi ona z pliku plugin.xml.
7. Kliknij przycisk Run w górnym prawym narożniku, by uruchomić produkt.
246
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
8. Zostanie zgłoszony błąd java.lang.ClassNotFoundException: org.eclipse.core. runtime.adaptor.EclipseStarter, ponieważ system uruchomieniowy nie potrafi znaleźć wymaganych wtyczek. Przełącz się na zakładkę Dependencies i dodaj elementy: com.packtpub.e4.headless.app,
org.eclipse.core.runtime.
9. Kliknij przycisk Add Required Plug-ins, by automatycznie dodać pozostałe zależności.
10. Uruchom produkt, a w konsoli pojawi się ten sam komunikat, co wcześniej.
247
Eclipse 4. Programowanie wtyczek na przykładach
11. Wyeksportuj produkt za pomocą polecenia File/Export/Plug-in Development/Eclipse Product lub przy użyciu przycisku Export na górze edytora produktu do lokalnego folderu. 12. Z poziomu folderu, w którym znajduje się nowy produkt, uruchom plik eclipse, by zobaczyć komunikat. W systemie Windows uruchom polecenie eclipsec.exe. W systemie OS X uruchom polecenie Eclipse.app/Contents/MacOS/eclipse.
Co się stało? Użycie i uruchomienie produktu nie wydaje się różnić znacząco od uruchomienia aplikacji, ale kluczowa różnica między nimi polega na tym, że aplikacja jest jedynie punktem początkowym i można ją zainstalować w istniejącej aplikacji, a produkt to niezależny system działający całkowicie osobno. Produkt definiuje wygląd ikony startowej, określa wszystkie eksportowane paczki i sposób uruchomienia. Następnie przekazuje sterowanie aplikacji, która uruchamia właściwy kod. W edytorze modyfikowaliśmy plik produktu w sposób graficzny. Jest to plik XML o treści podobnej do poniższej.
-XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts
248
Rozdział 8. • Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów
Sprawdź się — tworzenie produktu bazującego na funkcjonalności Utworzony produkt bazował na dokładnej liście wtyczek wymaganych do poprawnego działania. Wiele aplikacji Eclipse bazuje na funkcjonalnościach, więc produkty można definiować również na ich podstawie. Przenieś zależności dotyczące wtyczek z produktu do funkcjonalności i niech produkt zależy tylko od wskazanej funkcjonalności. Dzięki tej operacji funkcjonalność można aktualizować niezależnie od definicji produktu.
Quiz — sposób działania funkcjonalności, aplikacji i produktów P1. Jakie słowo kluczowe w numerze wersji zostaje zastąpione znacznikiem czasowym? P2. Jakie pliki generuje budowanie witryny aktualizacji? P3. Jak nazywa się plik, który umożliwia kategoryzację witryny aktualizacji? P4. Jaka jest różnica między funkcjonalnościami typu requires i includes? P5. Jaka jest różnica między aplikacją i produktem? P6. Jaki jest punkt wejścia do aplikacji?
Podsumowanie W tym rozdziale opisaliśmy, w jaki sposób tworzyć funkcjonalności i witryny aktualizacji, dzięki którym wtyczki można wyeksportować i zainstalować w innej kopii Eclipse. Zawartość witryny aktualizacji można opublikować na serwerze WWW i zarejestrować w sklepie Eclipse, by uzyskać ogólnoświatową widoczność. Omówiliśmy również tworzenie aplikacji i produktów służących do eksportu aplikacji najwyższego poziomu. W następnym rozdziale nauczymy się tworzenia zautomatyzowanych testów dla wtyczek Eclipse.
249
Eclipse 4. Programowanie wtyczek na przykładach
250
9 Automatyczne testy wtyczek JUnit to framework testów stosowany w większości aplikacji Eclipse. Można go użyć zarówno do testów zwykłego kodu Javy, jak i do testów wtyczek. Jeśli trzeba sprawdzić działanie interfejsu, SWTBot zapewnia fasadę dla aplikacji Eclipse i umożliwia sterowanie menu, oknami dialogowymi i widokami.
W tym rozdziale: utworzymy test JUnit dotyczący zwykłego kodu Javy, zbudujemy test JUnit dotyczący wtyczki, napiszemy testy interfejsu użytkownika przy użyciu SWTBot, przesłuchamy widoki i popracujemy z oknami dialogowymi, poczekamy na zajście warunku przed kontynuacją testu.
Użycie frameworku JUnit do testów zautomatyzowanych Jeden z oryginalnych frameworków zautomatyzowanych testów jednostkowych — JUnit — działa w Eclipse od ponad dekady. Jakość Eclipse wynika po części również z ogromnego zestawu zautomatyzowanych testów, które sprawdzają i część graficzną, i zwykły kod komponentów. W JUnit tworzy się tak zwane przypadki testowe z jednym lub większą liczbą testów, które najczęściej odpowiadają klasie i metodom. Przyjęło się kończyć nazwy klas fragmentem Test, ale nie jest to wymóg. Przypadki testowe łączy się w zestawy testów, choć w zasadzie projekt samoistnie staje się zestawem testów.
Eclipse 4. Programowanie wtyczek na przykładach
Kroki do wykonania — wykonanie prostego przypadku testowego JUnit W tym punkcie przyjrzymy się pisaniu i uruchamianiu w Eclipse prostego testu JUnit. 1. Utwórz nowy projekt Javy o nazwie com.packtpub.e4.junit.example. 2. W utworzonym pakiecie dodaj nową klasę o nazwie MathUtil. 3. Utwórz metodę typu public static o nazwie isOdd(), która przyjmuje wartość typu int i zwraca wartość typu boolean równą true, jeśli liczba jest nieparzysta (test value % 2 == 1). 4. Napisz nową klasę o nazwie MathUtilTest w pakiecie com.packtpub.e4.junit.example. 5. Zbuduj metodę o nazwie testOdd() z adnotacją @Test. Właśnie w ten sposób JUnit 4 rozpoznaje, że metoda jest przypadkiem testowym. 6. Kliknij podpowiedź naprawy z tekstem Add JUnit 4 library to the build path lub też dodaj ręcznie do ścieżek budowania plik plugins/org.junit_4.*.jar. 7. Zaimplementuj metodę testOdd() w następujący sposób. assertTrue(MathUtil.isOdd(3)); assertFalse(MathUtil.isOdd(4));
8. Dodaj fragment static import do org.junit.Assert.*, by naprawić błędy kompilacji. 9. Kliknij projekt prawym przyciskiem myszy i wykonaj polecenie Run As/JUnit Test. W widoku testu powinien pojawić się kolor zielony.
10. Sprawdź, że test faktycznie działa: zmodyfikuj metodę isOdd() w taki sposób, by zawsze zwracała wartość false. Ponownie uruchom test i zobaczysz kolor czerwony. 252
Rozdział 9. • Automatyczne testy wtyczek
Co się stało? Przykładowy projekt to ilustracja sposobu tworzenia i wykonywania testów JUnit w Eclipse. Przykład działa zarówno w projektach OSGi, jak i pozostałych, o ile tylko dostępna jest odpowiednia wersja biblioteki JUnit. Pamiętaj o oznaczaniu metod testowych adnotacją @Test, bo w przeciwnym razie nie zostaną wykonane. Czasem warto specjalnie napisać metodę z testem, którego nie uda się przejść poprawnie, by zweryfikować, czy sam system testów działa prawidłowo. Nie ma nic bardziej bezużytecznego niż zielony pasek testów, gdy tak naprawdę żaden test nie został wykonany, a właściwy kod zawiera błędy. Testy można uruchomić ponownie w widoku JUnit. Zielony przycisk odtwarzania ułatwia ponowienie wszystkich testów, a przycisk odtwarzania z czerwonym krzyżykiem umożliwia wykonanie tylko tych testów, w których znaleziono błędy (w przedstawionym zrzucie ekranu przycisk jest zablokowany).
Kroki do wykonania — wykonanie testu wtyczki Choć w projektach Javy i w projektach wtyczek wykorzystano języki Java i JUnit, wtyczki najczęściej potrzebują dostępu do dodatkowych (zapewnianych przez platformę uruchomieniową) elementów, które muszą być uruchomione w środowisku Eclipse lub OSGi. 1. Utwórz nowy projekt wtyczki o nazwie com.packtpub.e4.junit.plugin. 2. W pakiecie com.packtpub.e4.junit.plugin utwórz klasę testu JUnit o nazwie PlatformTest. 3. Utwórz metodę o nazwie testPlatform() i sprawdź, czy platforma (Platform) jest uruchomiona. @Test public void test() { assertTrue(Platform.isRunning()); }
4. Kliknij podpowiedź szybkiej naprawy, by dodać paczkę org.junit.
Ewentualnie otwórz manifest projektu, klikając projekt prawym przyciskiem myszy i wybierając polecenie Plug-in Tools/Open Manifest. Przejdź do zakładki Dependencies i kliknij przycisk Add, a następnie wybierz org.junit z okna dialogowego. Upewnij się, że org.eclipse.core.runtime również figuruje jako zależność.
5. Uruchom test, klikając projekt prawym przyciskiem myszy i wybierając polecenie Run As/JUnit Test. Zauważ, że pojawi się informacja o błędzie w teście (błąd asercji). 6. Uruchom test jako wtyczkę, klikając projekt prawym przyciskiem myszy i wybierając polecenie Run As/JUnit Plug-in Test. Tym razem nie będzie błędów.
253
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Choć sam kod testu jest bardzo podobny, sposób jego uruchomienia jest inny. W pierwszym podejściu zastosowaliśmy standardowy kod uruchamiania testów JUnit, który działa w standardowej maszynie wirtualnej Javy. Ponieważ nie ma dostępu do środowiska uruchomieniowego Eclipse, test nie zadziała. Testy wtyczek uruchamia się inaczej — powstaje nowa instancja Eclipse, wtyczka jest eksportowana i instalowana w nowej instancji, uruchamiane są różne usługi OSGi i dopiero na końcu dochodzi do uruchomienia właściwych testów. Oznacza to, że uruchamianie testów wtyczek w ten sposób znacząco zwiększa opóźnienie rozpoczęcia testów, bo wcześniej musi się uruchomić cała platforma. Z tego powodu niektóre prostsze testy uruchamia się jako standardowe testy Javy, a jedynie testy integracyjne wykonuje się w kontekście pełnego środowiska uruchomieniowego. Fragmenty kodu, które zależą od OSGi i usług platformy, muszą być uruchamiane jako testy wtyczki.
Wykorzystanie SWTBot do testów interfejsu graficznego SWTBot to system zautomatyzowanych testów, który umożliwia sprawdzanie zarówno interfejsu użytkownika Eclipse, jak i innych aplikacji SWT. Choć tworzenie testów i weryfikowanie modeli aplikacji jest niezwykle istotne, czasem warto również sprawdzić integrację z interfejsem użytkownika.
Kroki do wykonania — tworzenie testów SWTBot Pierwszy krok to instalacja SWTBot z witryny aktualizacji Eclipse. Przykłady były testowane w wersji 2.1.0 pobranej z witryny http://download.eclipse.org/technology/swtbot/releases/latest/. Pamiętaj, że Eclipse Kepler (4.3) wymaga SWTBot 2.1.1 lub nowszego. 1. Wykonaj polecenie Help/Install New Software i wpisz witrynę aktualizacji SWTBot. 2. Zaznacz wszystko poza funkcjonalnością GEF (patrz rysunek na następnej stronie). 3. Kliknij przycisk Next, by zainstalować SWTBot. 4. Ponownie uruchom Eclipse, gdy zostaniesz o to poproszony. 5. Dodaj do pliku manifestu wtyczki w projekcie com.packtpub.e4.junit.plugin następujące zależności: org.eclipse.swtbot.junit4_x,
254
org.eclipse.swtbot.forms.finder,
org.eclipse.swtbot.eclipse.finder,
org.eclipse.ui.
Rozdział 9. • Automatyczne testy wtyczek
6. Utwórz w pakiecie com.packtpub.e4.junit.plugin klasę o nazwie UITest. 7. Dodaj adnotację klasy @RunWith(SWTBotJunit4ClassRunner.class). 8. Utwórz metodę o nazwie testUI() z adnotacją @Test. 9. Wewnątrz metody testUI() utwórz instancję klasy SWTWorkbenchBot. 10. Przejdź w pętli przez dane zwrócone przez metodę shell() obiektu bot i sprawdź, czy widoczne okno ma tytuł Java – Eclipse SDK (to tytuł okna Eclipse, czasem może być inny). Oto pełny kod. package com.packtpub.e4.junit.plugin; import static org.junit.Assert.assertEquals; import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner; import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(SWTBotJunit4ClassRunner.class) public class UITest { @Test public void testUI() { SWTWorkbenchBot bot = new SWTWorkbenchBot(); SWTBotShell[] shells = bot.shells(); for (int i = 0; i < shells.length; i++) { if (shells[i].isVisible()) {
255
Eclipse 4. Programowanie wtyczek na przykładach
assertEquals("Java - Eclipse SDK", shells[i].getText()); } } } }
11. Uruchom test, klikając prawym przyciskiem myszy projekt i wybierając polecenie Run As/SWTBot Test. 12. Upewnij się, że testy JUnit zostały wykonane bez błędów.
Co się stało? SWTBot to mechanizm testowania interfejsu użytkownika, który umożliwia sterowanie interfejsem w sposób programowy. W teście utworzyliśmy obiekt SWTWorkbenchBot, by wejść w interakcję z IDE Eclipse. (W zwykłych aplikacjach SWT stosuje się klasę SWTBot). Przechodzimy przez wszystkie dostępne powłoki obiektu bot po otwarciu IDE. Choć na liście pojawi się więcej niż jedna powłoka, tylko jedna jest widoczna. Tytuł powłoki pobiera metoda getText(). Zwróci tekst Java – Eclipse SDK, jeśli IDE zostanie otwarte domyślnie w perspektywie Java — wartość ta zależy od perspektywy i zainstalowanych pakietów Eclipse. Jeśli test zgłosi błąd, zastąp porównywaną wartość właściwym tekstem pojawiającym się w tytule okna. Aplikacja jest podobna do systemu uruchamiania produktu Eclipse; odpowiedni dobór wtyczek, właściwości uruchomieniowych i produktów następuje w menu Run lub Debug. Podobnie jak w zwykłych testach JUnit, w trybie Debug można ustawić punkty wstrzymania.
Kroki do wykonania — korzystanie z menu Warto pamiętać, że SWTBot działa domyślnie poza wątkiem interfejsu użytkownika, by uniknąć blokady w przypadku użycia modalnych okien dialogowych lub innych akcji. Jeśli testy muszą wejść w interakcję z konkretnymi widgetami SWT, trzeba wykonać kod z poziomu wątku interfejsu użytkownika. Aby ułatwić całe zadanie, framework SWTBot udostępnia kilka metod pomocniczych zapewniających fasadę dla IDE, dającą możliwość między innymi kliknięcia przycisku lub wyświetlenia menu. 1. Utwórz w klasie UITest nową metodę testową o nazwie createProject() i oznacz ją adnotacją @Test. 2. Utwórz nową instancję SWTWorkbenchBot. 3. Użyj metody menu(), by przejść do File/Project… i wykonać operację click(). 4. Użyj metody shell(), by wybrać dopiero co otwartą powłokę o tytule New Project. Aktywuj powłokę, by do niej trafiały zdarzenia.
256
Rozdział 9. • Automatyczne testy wtyczek
5. Użyj metody tree(), by znaleźć w powłoce drzewo, rozwinąć węzeł General i ostatecznie wybrać element Project. 6. Wywołaj przycisk Next > metodą click(). Pamiętaj o znaku spacji między Next i >. 7. Kliknij etykietę o nazwie Project name: i ustaw tekst w polu na wartość Testowy projekt SWTBot. 8. Kliknij przycisk Finish. 9. Kod wygląda następująco. @Test public void createProject() { SWTWorkbenchBot bot = new SWTWorkbenchBot(); bot.menu("File").menu("Project...").click(); SWTBotShell shell = bot.shell("New Project"); shell.activate(); bot.tree().expandNode("General").select("Project"); bot.button("Next >").click(); bot.textWithLabel("Project name:"). setText("Testowy projekt SWTBot"); bot.button("Finish").click(); }
10. Uruchom test jako SWTBot Test i sprawdź, czy metoda createProject() wykonała się bez przeszkód.
Co się stało? Po utworzeniu bota dla IDE przejdź do menu File/Project…. Ponieważ powoduje to otwarcie nowego okna, trzeba uzyskać obiekt reprezentujący nową powłokę. W tym celu utworzyliśmy nowy obiekt SWTBotShell, który jest elementem sterującym wyświetlaną powłoką. Tytuł służy do odnalezienia odpowiedniej powłoki. Jeśli poszukiwana powłoka nie jest dostępna, SWTBot szuka odpowiedniej (domyślnie co 500 milisekund) aż do momentu jej znalezienia lub upłynięcia domyślnego czasu oczekiwania (5 sekund), co powoduje zgłoszenie wyjątku WidgetNotFoundException. Metoda activate() czeka na uaktywnienie okna dialogowego. Do poruszania się po oknie dialogowym służą metody, takie jak tree() i textWithLabel(), które znajdują konkretne elementy w interfejsie użytkownika lub zwracają wyjątki, gdy nie zostaną znalezione. Jeśli istnieje tylko jeden element konkretnego typu, wystarczające mogą okazać się proste metody, takie jak tree(). W przeciwnym razie trzeba użyć metod xxxWithLabel() lub xxxWithId(), by znaleźć konkretny element. Aby ustawić identyfikator obiektu, tak by mógł zostać odnaleziony przy użyciu metody withId(), użyj wywołania widget.setData(SWTBotPreferences.DEFAULT_KEY,"theWidgetId").
257
Eclipse 4. Programowanie wtyczek na przykładach
Uzyskane z metod dostępowych obiekty nie są widgetami SWT — to otoczki, podobnie jak SWTWorkspaceBot jest otoczką przestrzeni roboczej. Choć kod wydaje się wywoływać setText() na czymś, co przypomina etykietę, w rzeczywistości działa w wątku spoza interfejsu użytkownika. Metoda powoduje przekazanie do wątku interfejsu użytkownika odpowiedniego kodu do wykonania, który ma zadanie ustawić nowy tekst. Wszystkim tym zajmuje się SWTBot. Oczywiście, nietrudno się domyślić, że stosując do poszukiwania elementów ich etykiety, bardzo mocno uzależniamy się od języka. Test nie zadziała, jeśli aplikacja zostanie uruchomiona w innym języku. Co więcej, test w istotny sposób zależy od struktury aplikacji — jeśli nastąpią znaczące zmiany w interfejsie użytkownika, testy trzeba będzie zmodyfikować, a nawet napisać od podstaw.
Sprawdź się — korzystanie z zasobów Testy automatyczne sprawdzają różne ścieżki w kodzie, ale czasem trzeba sprawdzić nie tylko, czy interfejs użytkownika zareagował prawidłowo, ale czy zadziałały wszystkie efekty uboczne. By sprawdzić, czy projekt faktycznie został utworzony, użyj ResourcesPlugin (z paczki org.eclipse.core.resources), by pobrać przestrzeń roboczą, której korzeń pozwoli pobrać obiekt IProject. Użyj metody exists() projektu, by dowiedzieć się, czy projekt został utworzony prawidłowo. Zmodyfikuj metodę createProject() w taki sposób, by sprawdzała, że projekt nie istnieje na początku metody, a istnieje na jej końcu. Pamiętaj, że metoda getProject() z IWorkspaceRoot zwróci wartość różną od null niezależnie od tego, czy projekt istnieje, czy też go nie ma.
Korzystanie z SWTBot Istnieją pewne techniki pomagające przy pisaniu testów w SWTBot, na przykład odpowiednia organizacja kodu testów lub ukrywanie ekranu powitalnego na początku (bo może zakłócić wykonanie testu).
Kroki do wykonania — ukrywanie ekranu powitalnego Eclipse w trakcie uruchamiania zazwyczaj wyświetla ekran powitalny. Ponieważ bardzo często przeszkadza to w wykonywaniu zautomatyzowanych testów, najlepiej zamknąć go już na samym początku. 1. W bloku try metody createProject() pobierz widok zawierający w tytule tekst Welcome. 2. Wywołaj metodę close(). 258
Rozdział 9. • Automatyczne testy wtyczek
3. Kod będzie wyglądał następująco. SWTWorkbenchBot bot = new SWTWorkbenchBot(); try { bot.viewByTitle("Welcome").close(); } catch (WidgetNotFoundException e) { // zignoruj }
4. Uruchom test — ekran powitalny zostanie zamknięty przed wykonaniem właściwych testów.
Co się stało? IDE tuż po uruchomieniu wyświetla ekran powitalny — to widok zawierający w tytule tekst Welcome. Korzystając z metody dostępowej viewByTitle(), uzyskujemy dostęp do otoczki widoku utworzonej przez SWTBot. Jeśli widok nie istnieje, zostanie zgłoszony wyjątek WidgetNotFoundException, który przechwytujemy i pomijamy, ponieważ brak ekranu powitalnego nie jest błędem. Po znalezieniu widoku wywołujemy jego metodę close(), by go zamknąć.
Kroki do wykonania — unikanie błędów wykonania z SWTBot Gdy dodamy więcej metod testowych, system wykonawczy może zacząć zgłaszać dziwne błędy. Wynika to z faktu, iż kolejność wykonywania testów może mieć znaczenie i testy wykonane wcześniej mogą zmienić stan IDE. Można temu zaradzić, przenosząc kod konfiguracyjny do wspólnych metod ustawiania i niszczenia. 1. Utwórz metodę beforeClass() typu static. 2. Dodaj adnotację @BeforeClass z pakietu org.junit. 3. Przenieś tworzenie obiektu SWTWorkbenchBot do metody statycznej i zapisz ją w polu typu static. 4. Oto kod. private static SWTWorkbenchBot bot; @BeforeClass public static void beforeClass() { bot = new SWTWorkbenchBot(); try { bot.viewByTitle("Welcome").close(); } catch (WidgetNotFoundException e) { // zignoruj } }
5. Uruchom testy ponownie, by upewnić się, że nie ma błędów.
259
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Adnotacja JUnit @BeforeClass umożliwia wykonanie pojedynczej metody typu static przed wszystkimi testami znajdującymi się w klasie. Metoda tworzy pojedynczą instancję SWTWorkbenchBot wykorzystywaną później przez wszystkie inne testy w klasie. To również dobre miejsce do zamknięcia ekranu powitalnego, by wszystkie właściwe testy mogły założyć, iż jest zamknięty. Nie wywołuj bot.resetWorkbench(), bo kolejne testy nie będą działały prawidłowo.
Korzystanie z widoków Nie tylko menu i okna dialogowe można sprawdzać przy użyciu SWTBot — dotyczy to również widoków.
Kroki do wykonania — wyświetlenie widoków By wyświetlić inne widoki, stosuje się w testach interfejsu użytkownika ten sam mechanizm, którego użyłby użytkownik — przechodzi się do okna dialogowego wyświetlanego poleceniem Window/Show View/Other. 1. Utwórz nową metodę testTimeZoneView() i oznacz ją adnotacją @Test. 2. Z poziomu testu otwórz okno dialogowe Other i przejdź do Window/Show View. 3. Pobierz powłokę o tytule Show View i aktywuj ją. 4. Rozwiń węzeł Śledzenie czasu i zaznacz węzeł Widok strefy czasowej (węzeł został utworzony w rozdziale 2.). 5. Kliknij przycisk OK, by wyświetlić widok. 6. Użyj metody bot.viewByTitle(), by pobrać referencję do widoku. 7. Upewnij się, że widok nie ma wartości null. 8. Kod powinien przypominać poniższy. @Test public void testTimeZoneView() { bot.menu("Window").menu("Show View").menu("Other...").click(); SWTBotShell shell = bot.shell("Show View"); shell.activate(); bot.tree().expandNode("Śledzenie czasu").select("Widok strefy czasowej"); bot.button("OK").click(); SWTBotView timeZoneView = bot.viewByTitle("Widok strefy czasowej"); assertNotNull(timeZoneView); }
9. Uruchom testy i upewnij się, że żaden nie zgłosił błędu.
260
Rozdział 9. • Automatyczne testy wtyczek
Co się stało? Wykorzystując wbudowany w Eclipse mechanizm przełączania widoków, przeszliśmy do węzła Widok strefy czasowej z Window/Show View/Other/Śledzenie czasu, by wyświetlić widok. Metoda viewByTitle() służy do pobrania widoku po jego tytule, ale trzeba się upewnić, czy nie zwróci wartości null. Ponieważ otwarcie widoku w sposób programowy to powszechna operacja, warto wykonać metodę pomocniczą, która otworzy na żądanie dowolny widok.
Kroki do wykonania — przesłuchiwanie widoków Po pobraniu referencji do widoku możemy przystąpić do korzystania z zawartych w widoku elementów. Dla standardowych kontrolek, takich jak Button lub Text, SWTBot zapewnia standardowe metody. W przypadku pozostałych komponentów trzeba posłużyć się hierarchią widgetów w sposób bezpośredni. 1. W metodzie testTimeZoneView() pobierz Widget z SWTBotView. 2. Utwórz obiekt Matcher bazujący na widgetsOfType(CTabItem.class). 3. Użyj bot.widgets(), by poszukać listy instancji CTabItem w widgecie widoku. 4. Upewnij się, że zwrócono osiemnaście elementów. Sprawdź w działającej aplikacji, czy na pewno istnieje tyle zakładek. Czasem liczba dostępnych stref czasowych zależy od systemu operacyjnego komputera. 5. Kod powinien wyglądać następująco. SWTBotView timeZoneView = bot.viewByTitle("Widok strefy czasowej"); assertNotNull(timeZoneView); Widget widget = timeZoneView.getWidget(); org.hamcrest.Matcher matcher = WidgetMatcherFactory.widgetOfType(CTabItem.class); final java.util.List
4.0.0
Kod można skopiować z pliku pom.xml wygenerowanego w poprzednim punkcie, ponieważ wszystkie pliki pom.xml mają ten sam początek. 3. Nadaj projektowi unikatowe wartości groupId, artifactId i version, umieszczając je tuż za znacznikiem modelVersion. com.packtpub.e4 com.packtpub.e4.clock.ui 1.0.0-SNAPSHOT
Element version musi odpowiadać wersji zawartej w pliku plugin.xml, ale element .qualifier należy zastąpić fragmentem -SNAPSHOT. Element artifactId musi zawierać pełną nazwę wtyczki (element Bundle-SymbolicName w pliku MANIFEST.MF). 4. Zdefiniuj typ pakietu jako eclipse-plugin. eclipse-plugin
5. Jeśli spróbujesz w tym momencie uruchomić polecenie mvn package, pojawi się błąd Unknown packaging: eclipse-plugin. By go naprawić, dodaj Tycho jako wtyczkę budowania.
org.eclipse.tycho tycho-maven-plugin 0.18.0 true
270
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
6. Ponownie uruchom budowanie. Tym razem pojawi się błąd związany z brakiem poszukiwanych elementów. [ERROR] Internal error: java.lang.RuntimeException: "No solution found because the problem is unsatisfiable.": ["Unable to satisfy dependency from com.packtpub.e4.clock.ui 1.0.0.qualifier to bundle org.eclipse.ui 0.0.0."] -> [Help 1]
7. Dodaj repozytorium juno (lub kepler albo luna).
juno p2 http://download.eclipse.org/releases/juno
8. Wykonaj polecenie mvn clean package, a wtyczka zostanie zbudowana.
Co się stało? We wszystkich projektach Maven stosuje się plik pom.xml, który steruje procesem budowania; wtyczki Eclipse nie są żadnym wyjątkiem. Nagłówek pliku pom.xml nie zmienia się, więc najczęściej zamiast wpisywać go ręcznie, po prostu kopiuje się go z innego pliku pom.xml (lub generuje za pomocą narzędzi). Każdy plik Maven musi posiadać unikatowe wartości groupId, artifactId i version (jako całość). W projektach eclipse-plugins nazwa wpisana w artifactId musi odpowiadać wartości Bundle-SymbolicName z pliku MANIFEST.MF. W przeciwnym razie pojawi się poniższy błąd. [ERROR] Failed to execute goal org.eclipse.tycho:tycho-packaging-plugin:0.18.0:validate-id (default-validate-id) on project com.packtpub.e4.clock.uix: The Maven artifactId (currently: "com.packtpub.e4.clock.uix") must be the same as the bundle symbolic name (currently: "com.packtpub.e4.clock.ui") -> [Help 1]
271
Eclipse 4. Programowanie wtyczek na przykładach
To samo dotyczy numeru wersji z pliku pom.xml, który musi odpowiadać numerowi wersji z pliku MANIFEST.MF. Gdy wartości nie będą się zgadzały, narzędzie zgłosi poniższy błąd. [ERROR] Failed to execute goal org.eclipse.tycho:tycho-packaging-plugin:0.18.0:validate-version (default-validate-version) on project com.packtpub.e4.clock.ui: Unqualified OSGi version 1.0.0.qualifier must match unqualified Maven version 1.0.1-SNAPSHOT for SNAPSHOT builds -> [Help 1]
Zbioru Tycho można używać do budowania wtyczek Eclipse, bo stosuje się w nim typ pakietu eclipse-plugin. Aby jednak narzędzie Maven znało typ eclipse-plugin, Tycho musi zostać zdefiniowany jako wtyczka Maven dla budowania. Co ważne, musi zostać wskazany jako rozszerzenie, bo inaczej nie może wpływać na typ pakietu.
org.eclipse.tycho tycho-maven-plugin 0.18.0 true
Choć można wersję Tycho zapisać w ten sposób, najczęściej zastępuje się ją właściwością. Zamianę tę wykonamy, definiując dalej w tym rozdziale projekt nadrzędny. Na końcu dodaliśmy do pliku pom.xml repozytorium Eclipse, by możliwe było rozwiązanie wszystkich dodatkowych wtyczek i funkcjonalności. Musimy zdefiniować je jako repozytorium typu p2, by odróżnić je od repozytorium default przechowującego artefakty Maven. Ogólnie nie zaleca się umieszczania repozytoriów w plikach pom.xml. Warto przenieść je do osobnego pliku settings.xml. Umożliwia to budowanie tego samego projektu dla różnych wersji Eclipse bez wykonywania zmian w kodzie lub stosowania kilku bardzo podobnych plików. Plik ustawień można do Maven przekazać poleceniem mvn -s /ścieżka/do/settings.xml. W ten sposób zależności od wtyczek można łatwo zmieniać, nie edytując niepotrzebnie pliku pom.xml.
Sprawdź się — korzystanie z platform docelowych Choć można zbudować aplikację bazującą na Eclipse poprzez wskazanie repozytorium, nie zapewnia to pełnej powtarzalności budowania. Przykładowo możliwe było budowanie na podstawie repozytorium Kepler w lipcu 2013 roku, gdy obowiązywała wersja 4.3.0. Budowanie można było ponowić w grudniu 2013 roku, ale w wersji 4.3.1. Wynik operacji budowania w obu przypadkach będzie różny, nawet jeśli między wersjami nie wprowadzono żadnych zmian w kodzie źródłowym.
272
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
Platformę docelową definiuje się w Eclipse, przechodząc do strony Target Platform za pomocą polecenia Window/Preferences/Plug-in Development. Utwórz nową definicję celu zawierającą bazowy RCP, a następnie kliknij przycisk Share, by zapisać plik .target. Typu pakietu eclipse-target-definition można użyć do zdefiniowania GAV dla pliku .target, a po połączeniu z wtyczką target-platform-configuration można posłużyć się nim do budowania z wykorzystaniem tylko i wyłącznie wskazanych komponentów. Zajrzyj do dokumentacji Tycho lub przykładowego kodu dla książki.
Budowanie funkcjonalności i witryn aktualizacji za pomocą Tycho Proces budowania funkcjonalności i witryn aktualizacji działa bardzo podobnie do procesu budowania wtyczek, ale stosuje się w nim inne typy pakietów. Choć bardzo często zdarza się, że funkcjonalności i wtyczki buduje się w tym samym procesie budowania Maven, najczęściej jednak trzeba zreorganizować projekt. Najczęściej tworzy się projekt nadrzędny i kilka projektów podrzędnych.
Kroki do wykonania — tworzenie projektu nadrzędnego Zdarza się, że projekty nadrzędne i podrzędne znajdują się poza przestrzenią roboczą. Z powodów historycznych w Eclipse nie stosuje się zagnieżdżania projektów w przestrzeni roboczej. Najczęściej projekt nadrzędny zawiera całą konfigurację dotyczącą Tycho, co znacząco upraszcza konfigurację projektów podrzędnych. 1. Utwórz projekt General, wykonując polecenie File/New/Project/General/Project. 2. Wyłącz opcję Use default location. 3. Umieść pliki w miejscu poza przestrzenią roboczą Eclipse. 4. Nadaj projektowi nazwę com.packtpub.e4.parent. 5. Kliknij przycisk Finish. 6. Utwórz nowy plik pom.xml w korzeniu projektu. 7. Skopiuj zawartość pliku pom.xml wtyczki do projektu nadrzędnego, ale zmień wartości artifactId na com.packtpub.e4.parent, a packaging na pom. 8. Utwórz element properties w pliku pom.xml. Wewnątrz utwórz dwa elementy potomne: tycho-version (o zawartości 0.18.0) i eclipse (o wartości http://download.eclipse.org/releases/juno). 9. Zmodyfikuj w istniejącej wtyczce referencję do 0.18.0 i zastąp ją wartością ${tycho-version}. 10. Zmodyfikuj referencję do http://download.eclipse.org/releases/juno w istniejącym pliku wtyczki i zastąp ją wartością ${eclipse}.
273
Eclipse 4. Programowanie wtyczek na przykładach
11. Przenieś wtyczkę com.packtpub.e4.clock.ui do projektu nadrzędnego. 12. Dodaj element modules w pliku pom.xml, a wewnątrz elementu element module z wartością com.packtpub.e4.clock.ui. 13. Oto postać nadrzędnego pliku pom.xml.
4.0.0 com.packtpub.e4 com.packtpub.e4.parent 1.0.0-SNAPSHOT pom
0.18.0 http://download.eclipse.org/releases/juno
com.packtpub.e4.clock.ui
org.eclipse.tycho tycho-maven-plugin ${tycho-version} true
juno p2 ${eclipse}
14. Zmodyfikuj plik com.packtpub.e4.clock.ui/pom.xml i dodaj element parent z groupId, artifactId i version z pliku nadrzędnego. Można również usunąć z pliku potomnego pom.xml elementy version i groupId — w takiej sytuacji użyte zostaną wartości z przodka.
com.packtpub.e4 com.packtpub.e4.parent 1.0.0-SNAPSHOT
274
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
15. Usuń elementy plugins i repositories z pliku pom.xml znajdującego się w folderze com.packtpub.e4.clock.ui. 16. Przejdź do projektu nadrzędnego i wykonaj polecenie mvn clean package. Spowoduje to zbudowanie wszystkich projektów znajdujących się na liście. [INFO] [INFO] [INFO] [INFO] [INFO] [INFO]
Reactor Summary: com.packtpub.e4.parent . .......... SUCCESS [0.049s] com.packtpub.e4.clock.ui . ........ SUCCESS [1.866s] BUILD SUCCESS
Co się stało? Każda wtyczka to osobny projekt Eclipse, a co za tym idzie, osobny projekt Maven. Aby zbudować kilka projektów, trzeba zastosować nadrzędny plik pom.xml stanowiący niejako spoiwo dla projektów podrzędnych. W trakcie procesu budowania Maven wylicza kolejność, w której należy wykonać poszczególne projekty, a następnie wywołuje generowanie podprojektów. Dodatkową zaletą nadrzędnego pliku pom.xml jest możliwość określenia standardowych wtyczek budowania, a także ogólnych danych konfiguracyjnych. W tym przypadku projekt nadrzędny określa powiązanie z Tycho i jego konkretną wersją. Upraszcza to tworzenie pozostałych wtyczek i funkcjonalności.
Kroki do wykonania — budowanie funkcjonalności W zasadzie funkcjonalności buduje się w taki sam sposób jak wtyczki, ale typem pakietu jest eclipse-feature. 1. Przenieś folder projekt com.packtpub.e4.feature poniżej folderu projektu com.packtpub.e4.parent. 2. Dodaj wiersz com.packtpub.e4.feature do pliku pom.xml projektu nadrzędnego. 3. Skopiuj plik pom.xml z wtyczki clock do projektu feature. 4. Zmień fragment dotyczący typu pakietu na eclipse-feature. 5. Zmień wartość artifactId na com.packtpub.e4.feature. 6. Wynikowy plik pom.xml powinien mieć następującą postać.
275
Eclipse 4. Programowanie wtyczek na przykładach
4.0.0
com.packtpub.e4 com.packtpub.e4.parent 1.0.0-SNAPSHOT
com.packtpub.e4 com.packtpub.e4.feature
eclipse-repository
7. Zmień nazwę pliku site.xml na category.xml. (To nieco dziwaczna zmiana wymagana przez p2, mimo iż pliki są identyczne). 8. Upewnij się, że plik category.xml nie zawiera atrybutu url i element version ma wartość 0.0.0. W przeciwnym razie pojawi się komunikat Unable to satisfy dependencies.
9. Wykonaj polecenie mvn package z poziomu projektu nadrzędnego, a na liście pojawi się witryna aktualizacji. [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO]
Reactor Summary: com.packtpub.e4.parent .................. com.packtpub.e4.clock.ui ................ com.packtpub.e4.feature ................. com.packtpub.e4.update .................. BUILD SUCCESS
SUCCESS SUCCESS SUCCESS SUCCESS
[0.048s] [1.416s] [0.074s] [2.727s]
10. Zainstaluj witrynę aktualizacji w Eclipse, wywołując polecenie Help/Install New Software, wpisując w polu Work with wartość file:///ścieżka/do/com.packtpub.e4.parent/ com.packtpub.e4.update/target/repository i instalując funkcjonalność.
277
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Tworzenie witryny aktualizacji nie różni się znacząco od budowania wtyczki lub funkcjonalności. Istnieje plik pom.xml, który definiuje nazwę witryny aktualizacji i wykrywa istnienie pliku category.xml, by wygenerować właściwą witrynę aktualizacji. Plik category.xml pod względem funkcjonalności odpowiada wykonanemu wcześniej plikowi site.xml. Zmianę nazwy kilka wydań wcześniej wykonano w repozytorium p2. W projekcie witryny aktualizacji nadal używa się starej nazwy, więc dla Tycho należy zmienić nazwę z site.xml na category.xml. (Istnieje typ pakietu eclipse-update-site wykorzystujący plik site.xml, ale nie jest już zalecany i wkrótce może zostać usunięty). Podobnie jak to miało miejsce w przypadku projektu witryny aktualizacji w Eclipse, także i tutaj po zbudowaniu poszczególnych elementów numery wersji funkcjonalności i wtyczki zostają w pliku category.xml zastąpione właściwymi wartościami.
Kroki do wykonania — budowanie produktu Produkt (aplikację Eclipse z dodatkowymi informacjami o marce lub aplikację uruchamianą poleceniem eclipse -application w wierszu poleceń) również można zbudować za pomocą Tycho i typu pakietu eclipse-repository. W tym celu trzeba wykonać projekt aplikacji przy użyciu Tycho, udostępnić go jako funkcjonalność i utworzyć nowy projekt dla produktu. 1. Przenieś folder projektu com.packtpub.e4.app do folderu com.packtpub.e4.parent. 2. W nadrzędnym pliku pom.xml umieść wiersz com.packtpub.e4.app. 3. Skopiuj plik pom.xml z wtyczki clock do projektu app. 4. Zmodyfikuj artifactId, wpisując wartość com.packtpub.e4.app. 5. Wynikowy plik pom.xml ma następującą postać.
4.0.0
com.packtpub.e4 com.packtpub.e4.parent 1.0.0-SNAPSHOT
com.packtpub.e4 com.packtpub.e4.app
linux gtk x86_64
16. Ponownie rozpocznij proces budowania, by uzyskać produkty dla wielu systemów. Powstaną 64-bitowe wersje dla systemów Windows, Linux i OS X. By zbudować wersje 32-bitowe, zduplikuj poszczególne bloki i użyj wartości x86. 17. Aby zmaterializować produkty, a nie tylko zapewnić dla nich repozytorium p2, dodaj cel materialize-products do pliku com.packtpub.e4.app/pom.xml.
org.eclipse.tycho tycho-p2-director-plugin ${tycho-version}
zip tar.gz tar.gz
280
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
materialize-products
materialize-products
archive-products
archive-products
18. Uruchom budowanie z projektu nadrzędnego poleceniem mvn clean package, a powstanie zarówno struktura com.packtpub.e4.app/target/products/os/ws/arch, jak i pliki archiwów (pliki ZIP). 19. Uruchamiając wygenerowany produkt, można napotkać poniższy błąd (może być on widoczny dopiero po użyciu polecenia eclipse -consoleLog). java.lang.IllegalStateException: Unable to acquire application service. Ensure that the org.eclipse.core.runtime bundle is resolved and started (see config.ini).
20. Przedstawiony błąd dotyczy produktu. Ogólnie problem polega na tym, że produkt Eclipse musi posiadać kilka wtyczek uruchamianych przy starcie, by wykonać polecenie eclipse -application. Dodaj poniższy fragment do pliku com.packtpub.e4.app.product.
21. Wykonaj polecenie mvn clean package i spróbuj ponownie uruchomić produkt. Tym razem nie powinny pojawić się żadne błędy.
281
Eclipse 4. Programowanie wtyczek na przykładach
Co się stało? Aplikację Eclipse udostępnia się jako repozytorium p2 lub jako archiwa do pobrania. Repozytorium p2 umożliwia aktualizację produktu za pomocą standardowych mechanizmów aktualizacji. Właśnie w ten sposób działają aktualizacje w wersjach od 4.2.0 przez 4.2.1 do 4.2.2. Archiwa służą do zapewnienia bezpośrednich łączy do pobrania i dla standardowych pakietów można je znaleźć pod adresem http://download.eclipse.org. W aplikacjach korzystających z RCP znacznie łatwiej bazować na funkcjonalności RCP, która zapewnia niezbędne, specyficzne dla platformy fragmenty dla SWT i systemu plików. Choć budowanie produktu opartego na wtyczkach jest możliwe, w zasadzie wszystkie aplikacje RCP i SDK bazują na funkcjonalnościach RCP lub funkcjonalnościach IDE. By Eclipse udało się uruchomić bez przeszkód, przy starcie aplikacji trzeba wczytać pewne wtyczki. Za ich listę odpowiada plik config.ini odczytywany przez simpleconfigurator, który to musi zostać uruchomiony na samym początku. Dodatkowa platforma Eclipse 4 wymaga zainstalowania i uruchomienia Declarative Services (DS), a także wczytania paczki uruchomieniowej. Dzieląc projekt na funkcjonalności, a następnie korzystając z tej samej funkcjonalności zarówno w SWTBot, jak i w definicji produktu, uzyskujemy w zasadzie jedno miejsce, w którym warto dodawać nowe treści. Zmiany wprowadzone w funkcjonalności są od razu widoczne w produkcie, testach automatycznych i w witrynie aktualizacji.
Sprawdź się — zależność od komponentów Maven Czasem trzeba uzależnić się od komponentów budowanych za pomocą standardowych zadań Maven. Choć nie można mieszać standardowych mechanizmów budowania i Tycho, warto rozważyć sytuację, w której Tycho znajduje komponenty Maven jako części docelowej platformy. Ponieważ standardowe zależności Maven znajdują się w znaczniku , można zdefiniować dodatkowe zależności, które da się konsumować lub wykorzystywać z poziomu Tycho. Normalnie Tycho nie korzysta z tych informacji, ale pozwala na ich znalezienie i udostępnienie jako paczek OSGi w trakcie budowania. By to uzyskać, wystarczy zmodyfikować konfigurację target-platform-configuration i dodać wiersz consider do elementu . Pamiętaj, że do listy zależności dodawane są jedynie paczki OSGi. Pozostałe elementy będą ignorowane.
282
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
Testy i publikacja Ostatni krok budowania Eclipse polega na zapewnieniu, by tuż przed wykonaniem właściwej publikacji zostały uruchomione wszystkie testy automatyczne i zmienione wszystkie numery wersji.
Kroki do wykonania — uruchomienie testów automatycznych Choć testy kodu wtyczki (znajdujące się w folderze src/test/java) zostaną uruchomione automatycznie jako część procesu budowania Maven, bardzo często właściwe testy można wykonać tylko w działającej aplikacji Eclipse. W poprzednim rozdziale omówiono tworzenie zautomatyzowanych testów interfejsu użytkownika. Warto uruchomić je jako część procesu zautomatyzowanego budowania. 1. Przenieś folder com.packtpub.e4.junit.plugin do wnętrza folderu com.packtpub.e4.parent. 2. Dodaj wiersz com.packtpub.e4.junit.plugin w pliku pom.xml projektu nadrzędnego. 3. Dodaj repozytorium SWTBot do pliku pom.xml.
http://download.eclipse.org/technology/swtbot/releases/latest
...
swtbot p2 ${swtbot}
Dla Eclipse 4.3 (Kepler) SWTBot musi być w wersji 2.1.1 lub wyższej.
4. Skopiuj plik pom.xml z wtyczki clock do projektu junit.plugin. 5. Zmodyfikuj typ pakietu na eclipse-test-plugin. 6. Zmień zawartość artefactId na com.packtpub.e4.junit.plugin. 7. Aby uruchomić testy w Tycho, dodaj poniższą wtyczkę budowania.
283
Eclipse 4. Programowanie wtyczek na przykładach
src
org.eclipse.tycho tycho-surefire-plugin ${tycho-version}
true false org.eclipse.sdk.ide org.eclipse.ui.ide.workbench
8. Polecenie mvn integration-test powinno uruchomić testy (jeśli nie używasz systemu OS X). W przypadku systemu OS X do konfiguracji JVM należy dodać jeden wiersz.
true false -XstartOnFirstThread ...
Choć testy uruchamiają się, pojawiają się błędy, ponieważ środowisko SWTBot zapewnia jedynie minimalne zależności niezbędne do uruchomienia wtyczki JUnit. W tym przypadku nie dołącza nawet wykonanej wcześniej wtyczki zegara. By to naprawić, trzeba do pliku pom.xml dodać zależność, by system wykonawczy mógł utworzyć właściwą przestrzeń roboczą zawierającą wtyczkę zegara oraz Eclipse SDK. Wynika to z faktu, iż testy korzystają z polecenia Open View z przestrzeni roboczej oraz JDT do utworzenia projektu Javy.
true false
p2-installable-unit com.packtpub.e4 com.packtpub.e4.clock.ui
p2-installable-unit org.eclipse.sdk.feature.group
284
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
...
9. Uruchom polecenie mvn integration-test, by testy zostały uruchomione i wykonane bez zgłaszania żadnych błędów.
Co się stało? Dodatek tycho-surefire-plugin umożliwia uruchamianie aplikacji SWTBot. Testy zostają wykonane, a zwracana wartość informuje proces budowania Maven, czy wszystkie udało się wykonać z sukcesem, czy może pojawiły się jakieś błędy. Choć mogłoby się wydawać, że wskazanie produktu lub aplikacji spowoduje użycie niezbędnych zależności, w rzeczywistości tak nie jest. Uruchomiony test SWTBot wydaje się nie zwracać żadnej uwagi na zależności związane z aplikacją lub produktem. Oznacza to, że trzeba dodać w pliku pom.xml wszystkie zależności, by SWTBot korzystał z właściwego środowiska uruchomieniowego. Napisane wcześniej testy SWTBot posiadały również niejawną zależność od SDK, bo zakładały pojawienie się określonego tytułu okna i wystąpienie elementu Show View w menu. To przykład luźno powiązanych zależności — nie są powiązane z kodem, ale do prawidłowego działania testu niezbędne jest dodanie do środowiska wybranych wtyczek i funkcjonalności. Gdy tworzy się aplikacje lub funkcjonalności, można je wykorzystać do dodania wymaganych zależności — nie trzeba wtedy wskazywać konkretnych wtyczek. W przykładzie wtyczka clock.ui została dodana jawnie, podobnie jak org.eclipse.sdk.feature. Pamiętaj, że konwencja nazewnictwa dla instalowanych jednostek p2 wymaga dodania do nazwy końcówki .feature.group. Oznacza to, że zależność od wtyczki clock.ui trzeba zastąpić wpisem com.packtpub.e4.feature.feature.group. Użycie funkcjonalności jako zależności ułatwia pielęgnację kodu, ponieważ projekt testu może zależeć tylko od jednej funkcjonalności, a funkcjonalność od niezbędnych wtyczek. Jeśli trzeba dodać zależność, wystarczy umieścić ją w funkcjonalności, co spowoduje, iż zostanie użyta zarówno w witrynie aktualizacji, jak i w projekcie testu. Można w sposób dynamiczny sprawdzić, czy budowanie odbywa się na komputerze z systemem OS X i włączyć argument -XstartOnFirstThread, jeśli trzeba. Efekt dynamiczności rozwiązania uzyskuje się przez ustawianie właściwości za pomocą profili wybieranych automatycznie w zależności od systemu operacyjnego.
OSX
mac
285
Eclipse 4. Programowanie wtyczek na przykładach
-Xmx1024m -XstartOnFirstThread
NotOSX
!mac
-Xmx1024m
Profil OSX zostanie w sposób automatyczny włączony w przypadku korzystania z systemu Mac OS X, a profil NonOSX zostanie włączony we wszystkich innych sytuacjach (po użyciu znaku negacji ! przed nazwą rodziny systemów).
Kroki do wykonania — zmiana numeru wersji Gdy pojawia się nowa wersja projektu, trzeba zaktualizować numery wersji wtyczek i funkcjonalności. Można to zadanie wykonać samodzielnie, zmieniając numery wersji w plikach pom.xml i MANIFEST.MF, albo użyć odpowiedniego narzędzia. 1. Z poziomu folderu nadrzędnego wykonaj następujące polecenie (cały kod musi znajdować się w jednym wierszu). mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion= 1.2.3-SNAPSHOT
2. Wynikowy tekst powinien zawierać słowo SUCCESS przy projekcie nadrzędnym i SKIPPED dla pozostałych projektów. [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO]
Reactor Summary: com.packtpub.e4.parent .................. com.packtpub.e4.clock.ui ................ com.packtpub.e4.junit.plugin ............ com.packtpub.e4.feature ................. com.packtpub.e4.update ..................
SUCCESS [5.569s] SKIPPED SKIPPED SKIPPED SKIPPED
3. Uruchom proces budowania, by sprawdzić, czy wersje zostały uaktualnione. [INFO] Building com.packtpub.e4.parent 1.2.3-SNAPSHOT [INFO] Building com.packtpub.e4.clock.ui 1.2.3-SNAPSHOT [INFO] Building com.packtpub.e4.junit.plugin 1.2.3-SNAPSHOT [INFO] Building com.packtpub.e4.feature 1.2.3-SNAPSHOT
286
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
[INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO] [INFO]
Building com.packtpub.e4.update 1.2.3-SNAPSHOT Reactor Summary: com.packtpub.e4.parent . ......... com.packtpub.e4.clock.ui . ....... com.packtpub.e4.junit.plugin . ... com.packtpub.e4.feature . ........ com.packtpub.e4.update . .........
SUCCESS SUCCESS SUCCESS SUCCESS SUCCESS
[0.001s] [0.561s] [0.176s] [0.071s] [2.764s]
4. Po zakończeniu wszystkich prac programistycznych zbuduj projekt jako wersję wydaniową. mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion= 1.2.3.RELEASE
Co się stało? Wtyczka set-versions z Tycho przypomina podobną wtyczkę version:set stosowaną w Maven. Wtyczka dla Tycho modyfikuje zarówno plik META-INF/MANIFEST.MF (wymagany przez Eclipse), jak i plik pom.xml (wymagany przez Maven). Wersje deweloperskie w Maven kończą się fragmentem -SNAPSHOT, który oznacza, że cały czas się zmieniają. Maven stosuje w ich przypadku specjalny system pobierania „najnowszej” wersji. W Eclipse podobną rolę pełni .qualifier umieszczany na końcu numerów wersji wtyczek i funkcjonalności. W prostszych projektach, gdzie mamy tylko jedną wtyczkę i jedną funkcjonalność, warto obie wersje utrzymywać na tym samym poziomie. Czasem, gdy istnieją w tej samej funkcjonalności dwie wysoce ze sobą powiązane wtyczki (na przykład JDT i JDT UI), warto zapewnić pełną synchronizację ich wersji. Gdy projekt jest większy i jeden proces budowania wykorzystuje wiele modułów, stosowanie różnych numerów wersji dla poszczególnych wtyczek nie jest niczym dziwnym. Numery wersji w OSGi, a co za tym idzie, również w Eclipse, stosują wersjonowanie semantyczne (patrz strona http://semver.org). Numer wersji składa się z trzech głównych komponentów: major, minor i micro, oraz z opcjonalnego komponentu qualifier. Jeśli komponenty główne nie występują, otrzymują wartość 0, natomiast qualifier otrzymuje wartość równą pustemu tekstowi. Element qualifier zawiera najczęściej znacznik czasowy lub identyfikator rewizji (na przykład kod tworzony przez git describe w nowoczesnych systemach kontroli wersji). Gdy wersje major, minor i micro są sortowane na podstawie wartości liczbowych, qualifier jest sortowane leksykograficznie. Niestety, numeracja wersji OSGi i numeracja wersji Maven różnią się w ocenie tego, co jest wartością „największą”. W Maven pusty kwalifikator jest wyżej (innymi słowy, 1.2.3.build < 1.2.3), natomiast w OSGi jest odwrotnie (1.2.3.build > 1.2.3). Z tego powodu organizacje, takie jak SpringSource, wprowadziły coś, co stało się de facto standardem, czyli kwalifikator RELEASE do oznaczania wersji wydaniowych (1.2.3.build < 1.2.3.RELEASE). Używane są również
287
Eclipse 4. Programowanie wtyczek na przykładach
oznaczenia M1, M2, M3 i tak dalej dla kamieni milowych i RC1, RC2 i tak dalej dla wydań kandydujących, bo wszystkie te wartości są mniejsze niż RELEASE. Z tego powodu w wielu projektach pojawia się następująca kolejność nazw stosowanych jako kwalifikator: -SNAPSHOT, M1, M2, RC1, RC2 i RELEASE.
Sprawdź się — włączenie budowania dla pozostałych wtyczek Zastosuj ten sam system budowania i pliki pom.xml dla pozostałych wtyczek wykonanych w tej książce, by uzyskać automatyczne budowanie wszystkich przykładów. Dotyczy to aplikacji bez interfejsu (produkt można połączyć z poprzednim) oraz edytora Minimark. Samodzielne testy JUnit trzeba zbudować jako jar, a nie eclipse-plugin. Przykłady znajdują się na witrynie narzędzia Maven i w przykładach do książki.
Podpisywanie witryn aktualizacji Gdy instaluje się treści z repozytorium, Eclipse informuje, czy wtyczki są podpisane cyfrowo, czy też nie. Podpis cyfrowy daje pewność, że zawartości wtyczki nikt nie modyfikował, oraz pozwala sprawdzić tożsamość podpisującego.
Kroki do wykonania — tworzenie certyfikatu podpisanego przez samego siebie Aby podpisać zawartość, potrzeba klucza publicznego i prywatnego. Klucz prywatny służy do podpisania treści, a klucz publiczny ma za zadanie sprawdzić, czy treść nie została zmieniona. Parę kluczy można utworzyć za pomocą narzędzia wiersza poleceń o nazwie keytool udostępnianego wraz z Javą. 1. Uruchom narzędzie keytool, by zobaczyć listę opcji i sprawdzić, czy narzędzie znajduje się w ścieżce. 2. Utwórz nową parę kluczy, wykonując poniższe polecenie (należy je w całości zapisać w jednym wierszu). keytool -genkey -alias packtpub -keypass SayK3ys -keystore /sciezka/do/magazynu/kluczy -storepass BarC0der -dname "cn=packtpub,ou=pub,o=packt"
288
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
3. Upewnij się, że parę kluczy wygenerowano poprawnie. keytool -list -keystore /path/to/keystore -storepass BarC0der
4. Utwórz w celach testowych plik JAR, kompresując zawartość folderu. jar cf test.jar .
5. Podpisz plik JAR, by sprawdzić, czy cały system działa. Wykonaj poniższe polecenie (należy je w całości zapisać w jednym wierszu). jarsigner -keypass SayK3ys -storepass BarC0der -keystore /sciezka/do/magazynu/kluczy test.jar packtpub
6. Sprawdź poprawność podpisu, wykonując następujące polecenie. jarsigner -verify test.jar
Co się stało? Narzędzie keytool zarządza certyfikatami i kluczami dla programów Javy, które mają obsługiwać podpisywanie zawartości. Każdy wpis w magazynie kluczy posiada alias (co ułatwia szybkie odniesienie się do konkretnego klucza) oraz dwa hasła (dotyczące klucza i dotyczące całego magazynu kluczy). Magazyn kluczy powstaje w wybranej lokalizacji i jest chroniony hasłem BarC0der. Aby skorzystać z jakiegokolwiek klucza z magazynu, trzeba wcześniej za pomocą hasła odblokować cały magazyn. Aby użyć klucza prywatnego, trzeba podać hasło do niego, którym w tym przypadku jest SayK3ys. Najczęściej hasło do klucza różni się od hasła do magazynu. Jeśli magazyn zawiera
wiele kluczy, warto stosować inne hasło do każdego z nich. Rozróżnialna nazwa (dname) to identyfikator LDAP dla „właściciela” klucza. Stosuje się ciąg par nazwa=wartość oddzielanych znakiem przecinka. Jako minimum potrzebna jest wspólna nazwa (cn) i pewien rodzaj identyfikatora organizacji. W tym przypadku jednostką organizacyjną (ou) jest pub, a organizacją (o) packpub. Innym bardzo popularnym sposobem reprezentacji własności są komponenty domenowe (dc), więc alternatywą mógłby być zapis w postaci cn=pakcktpub, dc=packtpub, dc=com, w którym domena packpub.com została podzielona na dwa elementy dc. Pamiętaj, że kolejność elementów ma znaczenie. Narzędzie jarsigner służy do podpisywania plików JAR, więc musi mieć dostęp do magazynu i obu haseł. Można również wskazać alias, który wskaże konkretny klucz. Jeśli nie zostanie wskazany, narzędzie poszuka dowolnego pasującego klucza (zakłada się, że każdy klucz stosuje inne hasło). Narzędzie jarsigner służy również do sprawdzenia, czy podpis jest prawidłowy, czy też nie. Weryfikację wykonuje się przy użyciu argumentu -verify.
289
Eclipse 4. Programowanie wtyczek na przykładach
Kroki do wykonania — podpisywanie wtyczek Integracja podpisywania z Tycho wymaga jedynie dodania nowego rozszerzenia do skryptu budowania. Dodatkowo trzeba zastosować właściwości Javy, by zapewnić odpowiednie przekazanie argumentów niezbędnych do prawidłowego funkcjonowania narzędzia jarsigner. 1. Dodaj wtyczkę do pliku pom.xml projektu nadrzędnego.
org.apache.maven.plugins maven-jarsigner-plugin 1.2
sign
sign
2. Wykonaj polecenie mvn package, a pojawi się następujący błąd. [ERROR] Failed to execute goal org.apache.maven.plugins:maven-jarsigner-plugin:1.2:sign (sign) on project com.packtpub.e4.parent: The parameters 'alias' for goal org.apache.maven.plugins:maven-jarsigner-plugin:1.2:sign are missing or invalid -> [Help 1]
3. Przekaż argumenty wymagane przez narzędzie jarsigner jako właściwości systemu Java z przedrostkiem jarsigner (polecenie należy w całości zapisać w jednym wierszu). mvn package -Djarsigner.alias=packtpub -Djarsigner.keypass=SayK3ys -Djarsigner.storepass=BarC0der -Djarsigner.keystore=/sciezka/do/magazynu/kluczy
4. Jeśli wszystko zadziała prawidłowo, powinien pojawić się następujący wynik. [INFO] [INFO] [INFO] [INFO] [INFO] [INFO]
--- maven-jarsigner-plugin:1.2:sign (sign) @ com.packtpub.e4.clock.ui --1 archive(s) processed --- maven-jarsigner-plugin:1.2:sign (sign) @ com.packtpub.e4.feature --1 archive(s) processed --- maven-jarsigner-plugin:1.2:sign (sign) @ com.packtpub.e4.update --1 archive(s) processed
5. Aby podpisywanie wykonać warunkowo, można użyć profilu. Przenieś wtyczkę podpisywania z elementu build do osobnego elementu profiles w pliku pom.xml.
290
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
sign
org.apache.maven.plugins maven-jarsigner-plugin ...
6. Uruchom budowanie poleceniem mvn package, by sprawdzić, czy zadziała bez podpisywania. 7. Uruchom budowanie z włączonym podpisywaniem, wywołując polecenie mvn package -Psign, czyli z profilem sign. Powinna pojawić się prośba o alias. 8. Aby automatycznie włączyć profil sign, gdy zostanie użyta właściwość jarsigner.alias, dodaj do profilu następujący fragment kodu.
sign
jarsigner.alias
...
9. Uruchom budowanie jako mvn package -Djarstore.alias=packtpub ..., by przekonać się, że podpisywanie zadziała bez jawnego użycia argumentu -Psign.
Co się stało? Po dodaniu rozszerzenia maven-jarsigner-plugin do procesu budowania narzędzie Maven zaczęło podpisywać wszystkie zbudowane pliki JAR (włącznie z plikami content.jar i artifacts.jar, których nie trzeba podpisywać). To standardowy sposób tworzenia podpisanych treści za pomocą Maven i nie ma nic wspólnego z Tycho i Eclipse. Parametry dla jarsigner określa się jako właściwości systemowe. Znacznik -D w Maven, podobnie jak ma to miejsce w Javie, służy do określania właściwości systemowych w wierszu poleceń. Rozszerzenie maven-jarsigner-plugin odczytuje właściwości z przedrostkiem jarsigner, więc alias przekazuje się jako jarsigner.alias, a magazyn kluczy jako jarsigner.store.
291
Eclipse 4. Programowanie wtyczek na przykładach
Pamiętaj, że lokalizację pliku magazynu danych trzeba podać jako pełną ścieżkę, ponieważ rozszerzenie będzie uruchamiane z różnych folderów (folderów docelowych poszczególnych projektów). Próba zastosowania ścieżki względnej z pewnością nie powiedzie się.
Kroki do wykonania — serwer z witryną aktualizacji Skoro udało się witrynę aktualizacji napisać, przetestować i automatycznie zbudować, nadszedł czas na umieszczenie zawartości witryny aktualizacji (elementów znajdujących się w folderze com.packtpub.e4.update/target/repository) na serwerze WWW lub serwerze FTP i wypróbowanie instalacji. Jeśli na komputerze jest dostępny Python 2.7 lub nowszy, wystarczy użyć następujących kroków. 1. Zmień aktualny folder na com.packtpub.e4.update/target/repository. 2. Uruchom serwer SimpleHTTPServer. python -m SimpleHTTPServer 8080 Serving HTTP on 0.0.0.0 port 8080 ...
3. Sprawdź, czy witryna aktualizacji jest dostępna, wpisując adres http://localhost:8080/ jako lokalizację witryny aktualizacji. Jeśli Python nie jest zainstalowany, można skorzystać z serwera WWW wbudowanego w system operacyjny lub użyć dowolnego innego serwera WWW. W systemie OS X dostępny jest system Web Sharing, który udostępnia pliki umieszczone w folderze ~/Sites. W systemach Linux najczęściej skonfigurowany jest serwer Apache, który udostępnia pliki umieszczone w folderze ~/public_html. W systemie Microsoft Windows można użyć serwera IIS, którego domyślna lokalizacja to c:\intepub\wwwroot. Szczegółów szukaj w dokumentacji systemu.
Co się stało? Witryna aktualizacji to prosty serwer WWW, który ma za zadanie udostępnić pliki content.jar i artifacts.jar oraz foldery plugins i features. Jeśli jest zainstalowany Python 2.7 lub nowszy, wystarczy użyć modułu SimpleHTTPServer, by udostępnić zawartość folderu update/target/repository pod adresem http://localhost:8080/. Umieszczenie zawartości repozytorium na zewnętrznym serwerze WWW można potraktować jako ćwiczenie dodatkowe. W przypadku posiadania instalacji Python 3 lub nowszej użyj polecenia python3 -m http.server. Po umieszczeniu witryny aktualizacji w publicznie dostępnym miejscu można zarejestrować ją w Eclipse Marketplace (http://marketplace.eclipse.org), by witrynę mogły łatwo znaleźć wszystkie zainteresowane nią osoby.
292
Rozdział 10. • Automatyczne budowanie przy użyciu Tycho
Quiz — automatyczne budowanie i witryny aktualizacji P1. Czym jest GroupId, ArtifactId i Version (w skrócie GAV)? P2. Jakie są cztery rodzaje pakietów wymagane do budowania wtyczek, funkcjonalności, produktów i witryn aktualizacji? P3. W jaki sposób można w Maven aktualizować numery wersji wtyczek i funkcjonalności? P4. Dlaczego i w jaki sposób podpisuje się pliki JAR? P5. W jaki sposób uruchomić serwer WWW przy użyciu języka Python? P6. Gdzie zwyczajowo rejestruje się funkcjonalności Eclipse, by mogły je znaleźć inne osoby?
Podsumowanie W tym rozdziale kończymy tworzenie wtyczek i funkcjonalności Eclipse. Ponieważ ostatni krok polega na budowaniu i udostępnieniu wszystkich elementów innym osobom, skupiliśmy się na automatyzacji budowania za pomocą Tycho i udostępnieniu wykonanych witryn aktualizacji innym użytkownikom.
293
Eclipse 4. Programowanie wtyczek na przykładach
294
A Odpowiedzi do quizów
Rozdział 1. Tworzenie pierwszej wtyczki Quiz — przestrzenie nazw i wtyczki Eclipse P1
Przestrzeń nazw Eclipse to miejsce, w którym przechowywane są wszystkie projekty.
P2
Konwencja nazewnictwa wtyczek Eclipse polega na użyciu odwrotnych nazw domenowych, czyli com.packtpub. Dodatkowo projekty interfejsu użytkownika zawierają w nazwie element UI.
P3
Trzema kluczowymi plikami wtyczki Eclipse są: META-INF/MANIFEST.MF, plugin.xml i build.properties.
Quiz — uruchamianie Eclipse P1
Wyjdź z aplikacji poleceniem File/Exit. Użyj przycisku Stop w widokach Debug lub Console.
P2
Konfiguracje uruchomieniowe przypominają wcześniej przygotowane skrypty, które mogą uruchomić aplikację, ustawić folder roboczy i uruchomić klasę.
P3
Konfiguracje uruchomieniowe modyfikuje się za pomocą okna dialogowego otwieranego poleceniem
Run/Run Configurations… lub Debug/Debug Configurations….
Eclipse 4. Programowanie wtyczek na przykładach
Quiz — debugowanie P1
Przejdź do polecenia Debug/Debug configurations lub Debug/Debug As….
P2
Ustaw filtry kroków w menu preferencji, by uniknąć przechodzenia do niektórych pakietów.
P3
Punkty wstrzymania mogą działać warunkowo, dotyczyć wejścia lub wyjścia z metody, mogą być włączone i wyłączone oraz mogą być uaktywniane po określonej liczbie iteracji.
P4
Ustaw punkt wstrzymania i włącz go po 256 przejściach przez punkt wstrzymania.
P5
Ustaw warunkowy punkt wstrzymania i użyj argument==null jako warunku.
P6
Inspekcja obiektu oznacza otwarcie go w widoku, by możliwe było dokładne rozwinięcie wszystkich wartości.
P7
Wyrażenie analizuje dane okna i umożliwia ustawienie dowolnego wyrażenia.
Rozdział 2. Tworzenie widoków w SWT Quiz — działanie widoków P1
W modelu Eclipse 3.x widoki muszą być podklasami klasy ViewPart. W modelu Eclipse 3.x części nie muszą mieć żadnego konkretnego przodka.
P2
W modelu Eclipse 3.x widoki rejestruje się w pliku plugin.xml za pomocą punktu rozszerzenia org.eclipse.ui.views.
P3
Dwoma argumentami stosowanymi w większości obiektów SWT są element nadrzędny Composite i pole flags będące liczbą całkowitą.
P4
Usunięcie widgetu oznacza zwolnienie przydzielonych zasobów systemowych i przekazanie ich z powrotem systemowi. Wszystkie następne akcje spowodują zgłoszenie wyjątku SWTException z komunikatem Widget is disposed.
P5
Obiekt Canvas udostępnia wiele operacji rysowania. Aby narysować okrąg, użyj metody drawArc() i wskaż pełny łuk.
P6
Aby otrzymywać zdarzenia rysowania, trzeba utworzyć obiekt PaintListener i powiązać go z kontrolką za pomocą metody addPaintListener.
P7
Próba aktualizacji interfejsu użytkownika spoza odpowiedniego wątku spowoduje zgłoszenie wyjątku SWTException z komunikatem Invalid thread access.
P8
Aby wykonać aktualizację widgetu spoza wątku interfejsu użytkownika, użyj metod asyncExec() lub syncExec() klasy Display (3.x) lub UISynchronize (4.x), by otoczyć kod i wykonać go w wątku użytkownika.
P9
Wartość SWT.DEFAULT służy do oznaczenia domyślnych opcji w parametrze flags przekazywanym do konstruktora widgetu.
P10
Utwórz obiekt RowData o określonym rozmiarze i powiąż go z każdym obiektem Widget.
296
Dodatek A • Odpowiedzi do quizów
Quiz — działanie zasobów P1
Wyciek zasobów ma miejsce, gdy zasób SWT zostaje pobrany z systemu operacyjnego, ale nie jest zwracany metodą dispose() przed usunięciem obiektu przez mechanizm czyszczenia pamięci.
P2
Dostępne typy zasobów to: Color, Cursor, Font, GC, Image, Path, Pattern, Region, TextLayout i Transform.
P3
Uruchom Eclipse w trybie śledzenia z elementami org.eclipse.ui/debug i org.eclipse.ui/ trace/graphics oraz z opcją -debug.
P4
Użyj danych wyświetlania do pobrania tablicy obiektów i przejdź przez poszczególne obiekty, szukając jednego z nich.
P5
Właściwy sposób to rejestracja nasłuchiwania zdarzenia usunięcia w widoku, a niewłaściwy to przesłonięcie metody dispose().
Quiz — działanie widgetów P1
Użyj metody setFocus(), by uaktywnić konkretny widget.
P2
Wywołanie metody redraw() wymusi ponowne przerysowanie widgetu.
P3
Obiekt Combo może mieć powiązany ze sobą obiekt SelectionListener.
P4
Metoda widgetDefaultSelected() zostaje wywołana po wybraniu wartości domyślnej, najczęściej pustej wartości.
Quiz — korzystanie z SWT P1
Użyj widgetów Tray i TrayItem.
P2
Styl SWT.NO_TRIM wymusza, by nie rysowano krawędzi okna oraz przycisków zamknięcia, minimalizacji i maksymalizacji.
P3
Użyj polecenia setAlpha() do zmiany przezroczystości widgetu, włącznie z obiektem Shell.
P4
Użyj metody setRegion() ze ścieżką opisującą kształt.
P5
Grupa umożliwia powiązanie elementów w standardowym elemencie.
P6
W większości obiektów Composite używa się domyślnie wartości null jako LayoutManager. Jedynie Shell i Dialog posiadają inną wartość.
P7
Użyj ScrolledComposite.
297
Eclipse 4. Programowanie wtyczek na przykładach
Rozdział 3. Tworzenie widoków w JFace Quiz — podstawy JFace P1
Zawiera metodę getImage() do wyświetlenia Image wpisu oraz getText() do wyświetlenia treści wpisu.
P2
Metoda hasChildren() służy do określenia, czy element powinien zostać wyświetlony z ikoną rozwinięcia, a getChildren() do wyliczenia listy potomków.
P3
Klasa ImageRegistry służy do współdzielenia obrazów między wtyczkami lub różnymi widokami wtyczki i dodatkowo troszczy się o właściwe zwalnianie zasobów po zamknięciu widoku.
P4
Wpisy można obstylować za pomocą IStyledLabelProvider.
Quiz — sortowanie i filtracja P1
Wskazanie ViewerComparator umożliwia sortowanie elementów w sposób inny niż domyślny.
P2
Metoda select() służy do filtrowania elementów. Jej nazwa pochodzi z terminologii języka Smalltalk.
P3
Można ze sobą łączyć wiele filtrów, przekazując ich tablicę lub tworząc filtr, który wewnętrznie używa kilku innych filtrów.
Quiz — działanie właściwości P1
Dodaj DoubleClickListener do widoku.
P2
Podklasy klasy Dialog służą do tworzenia okien dialogowych z własną zawartością.
P3
Deskryptory właściwości służą do reprezentacji kluczy dla właściwości konkretnego obiektu.
P4
Właściwości są wyświetlane w widoku Properties, jeśli obiekt jest typu adaptacyjnego (czy to bezpośrednio poprzez interfejs IAdaptable, czy pośrednio za pomocą IAdapterManager), czyli zwraca instancję źródła właściwości, a ta zwraca deskryptory właściwości.
Quiz — działanie tabel P1
Aby wyświetlić nagłówki, pobierz Table z Viewer i użyj obiektu do wywołania metody setHeaderVisible(true).
P2
Obiektów TableViewerColumn używa się do ustawiania właściwości poszczególnych kolumn i do zapewnienia dostawców etykiet.
P3
Synchronizację uzyskuje się poprzez rejestrację witryny jako dostawcy zaznaczenia (w ten sposób zdarzenia wyboru trafiają do IDE), nasłuchiwania nadchodzących zdarzeń wyboru i odpowiedniego na nie reagowania. Trzeba uważać, by nie dopuścić do wywołań rekurencyjnych.
298
Dodatek A • Odpowiedzi do quizów
Rozdział 4. Interakcja z użytkownikiem Quiz — działanie menu P1
Obiekty Action to stary sposób powiązania wykonywalnego kodu z elementem menu. Polecenie to abstrakcyjna reprezentacja efektu, który nie musi posiadać komponentu interfejsu użytkownika. Obiektów Action nie należy używać w nowym kodzie, bo zostały oznaczone do wycofania już jakiś czas temu i wkrótce mogą zostać całkowicie usunięte.
P2
Polecenie można powiązać z procedurą obsługi, by zapewnić element menu. Procedury obsługi to mechanizmy pośrednie, dzięki którym ten sam element menu (na przykład kopiowanie) może działać inaczej w zależności od kontekstu. Można z menu powiązać domyślny identyfikator polecenia, by uniknąć elementu pośredniczącego.
P3
Kod M1 odnosi się do klawisza Cmd w systemie OS X lub do klawisza Ctrl na innych platformach. Definiując standardowe polecenia, na przykład kopiowanie (M1+C), uzyskuje się mechanizm działający na wszystkich platformach (Cmd+C w OS X lub Ctrl+C w innych).
P4
Skróty klawiaturowe łączy się z poleceniami za pomocą dowiązań, które określają klawisze niezbędne do naciśnięcia w celu wywołania konkretnego polecenia lub procedury obsługi.
P5
Element locationURI z menu określa miejsce umieszczenia w interfejsie użytkownika. Określa się go albo w sposób relatywny względem istniejącego elementu menu, albo jako ogólny wpis additions. Można nawet tworzyć swoje odniesienia dotyczące własnego kodu.
P6
Menu kontekstowe tworzy się za pomocą specjalnej wartości locationURI równej popup:org.eclipse.ui.popup.any. Pamiętaj, że objectContribution nie jest tak elastyczne i może zostać wkrótce usunięte z platformy.
Quiz — korzystanie z zadań P1
Metoda syncExec() blokuje dalsze wykonywanie kodu i czeka na wykonanie zleconego zadania. Metoda asyncExec() umożliwia kontynuację wykonywania kodu i wykona zadanie w sposób asynchroniczny.
P2
Klasa Display wykonuje zadania w wątku interfejsu użytkownika SWT, ale instancja UISynchronize umożliwia wykonywanie zadań również w wątkach interfejsu użytkownika innych niż SWT. W aplikacjach Eclipse 4 zaleca się stosowanie klasy UISynchronize.
P3
Obiekt UIJob zawsze wykona się w wątku interfejsu użytkownika, więc bezpośredni dostęp do widgetów nie spowoduje zgłoszenia błędu wątku. Należy jednak zminimalizować ilość czasu spędzanego w wątku interfejsu użytkownika, by nie blokować IDE Eclipse. Gdy zadanie nie musi korzystać z elementów interfejsu użytkownika, warto wykonywać je poza wątkiem UI.
P4
Singleton Status.OK_STATUS służy do ogólnego oznaczenia sukcesu. Choć można utworzyć obiekt Status z kodem OK, zrobienie tego jedynie zwiększy presję na system czyszczenia pamięci, bo wynikowy obiekt Status zostanie niemal natychmiast zapomniany.
299
Eclipse 4. Programowanie wtyczek na przykładach
P5
Obiekt CommandService pobiera się za pomocą wywołania PlatformUI.getWorkbench().getService(), które przyjmuje klasę i zwraca jej instancję. Tę samą technikę wykorzystuje się do pobrania instancji UISynchronize.
P6
Ikonę można wyświetlić, ustawiając dla zadania właściwość o nazwie IProgressConstants2.ICON_NAME.
P7
Obiekt SubMonitor najłatwiej wykorzystać na początku metody, by mieć pewność, że przekazany monitor zostanie prawidłowo rozdzielony na poszczególne składowe. W zasadzie nie powinno się stosować obiektów SubProgressMonitor.
P8
Anulowanie powinno się sprawdzać możliwie często, ponieważ jak tylko użytkownik zdecyduje się przerwać zadanie, należy je zakończyć.
Quiz — zgładzanie błędów P1
Informacyjne okno dialogowe wyświetla się, wywołując metodę MessageDialog.openInformation() (lub .openWarning() albo .openError()). Istnieje również wersja MessageDialog.openInformation(), która zwraca wynik wyświetlenia użytkownikowi zapytania typu tak lub nie.
P2
Klasa StatusManager jest klasą pochodzącą z Eclipse 3.x. Jest ściśle powiązania z interfejsem użytkownika. Klasa StatusReporter zapewnia podobne działanie, ale nie jest związana z interfejsem użytkownika.
P3
Raportowanie statusu z zasady jest operacją asynchroniczną. Istnieje jednak opcja BLOCK powodująca, że będzie to operacja synchroniczna.
P4
Aby połączyć wyniki wielu spraw w jeden raport, użyj obiektu MultiStatus.
Rozdział 5. Przechowywanie preferencji i ustawień Quiz — działanie preferencji P1
Stylem domyślnym jest FLAT, ale można go zmienić na styl GRID, który zapewnia lepsze wyświetlanie stron preferencji.
P2
Istnieje wiele podklas klasy FieldEditor. Są to edytory Boolean, Color, Combo, Font, List, RadioGroup, Scale, String, Integer, Directory i File.
P3
Aby zapewnić wyszukiwanie na stronie preferencji, jako punkt rozszerzeń trzeba zarejestrować słowa kluczowe.
P4
Nie używaj IMemento. Skorzystaj z dowolnych innych rozwiązań, na przykład IEclipsePreferences lub DialogSettings.
P5
Klasa MessageDialogWithToggle zapewnia obsługę tekstu typu „Nie wyświetlaj tego komunikatu ponownie”.
300
Dodatek A • Odpowiedzi do quizów
Rozdział 6. Korzystanie z zasobów Quiz — obsługa zasobów, procesu budowania i znaczników P1
Jeśli edytor zgłasza błąd dotyczący brakującego dostawcy dokumentów, zainstaluj instancję TextFileDocumentProvider za pomocą metody setDocumentProvider() edytora.
P2
Interfejs IResourceProxy służy mechanizmowi budowania jako otoczka dla IResource, która nie wymaga konstrukcji obrazu IResource.
P3
Interfejs IPath to ogólny komponent dotyczący plików używany do przemieszczania się po plikach i folderach projektów.
P4
Natura to odmiana projektu, która włącza określone zachowania. Instaluje się ją jako aktualizację deskryptora projektu.
P5
Znaczniki tworzy najczęściej proces budowania, ale w zasadzie może je utworzyć dowolna wtyczka wykorzystująca zasoby. Zasoby posiadają specjalną funkcję umożliwiającą utworzenie znacznika określonego typu.
Rozdział 7. Model Eclipse 4 Quiz — działanie Eclipse 4 P1
Model aplikacji znajduje się w pliku e4xmi i zapewnia mechanizm reprezentacji stanu całego interfejsu użytkownika aplikacji. Jest utrwalany przy zapisie i ponownie wczytywany przy uruchamianiu, więc pozycje części i ich widoczność zostają zapamiętane na stałe. Model jest również dostępny w trakcie działania systemu poprzez różne klasy M, takie jak MApplication i MPart. Model można w trakcie pracy systemu odpytywać i modyfikować.
P2
Części to bardziej ogólna forma widoków i edytorów. W odróżnieniu od Eclipse 3.x, nie wszystko musi pasować do kategorii widoku lub edytora; wszystko jest częścią zawierającą komponenty interfejsu użytkownika i wszystko może być konstruowane w dowolny sposób.
P3
Choć punkty rozszerzenia nie są stosowane dla spraw, takich jak polecenia, skróty klawiaturowe lub widoki, nadal służą do definiowania innych rozszerzeń Eclipse, na przykład mechanizmów budowania, typów znaczników i analizatorów języków programowania. Jedyną sprawą, którą model Eclipse 4 usuwa z punktów rozszerzenia, jest obsługa interfejsu użytkownika. Z drugiej strony, zapewnienie zgodności wstecz w IDE Eclipse 4 powoduje, że punkty rozszerzeń dotyczące interfejsu nadal są renderowane. Można oczekiwać, że API Eclipse 3.x dotyczące wtyczek będzie dostępne jeszcze przez kilka kolejnych wydań IDE Eclipse.
301
Eclipse 4. Programowanie wtyczek na przykładach
P4
Części Eclipse 4 można obstylować za pomocą CSS. Renderer stosuje style przez cały czas, nawet w przypadku zmiany CSS. Umożliwia to systemowi zarządzania wyglądem stosowanie w Eclipse 4 różnych kombinacji kolorów, co w Eclipse 3 nie było możliwe.
P5
Konteksty Eclipse 4 to w zasadzie zestawy obiektów HashMap zawierających wartości (obiekty) i powiązane z nimi klucze. Części mogą dynamicznie pobierać treść z kontekstu, który zawiera wszystkie wstrzyknięte usługi, a także dynamicznie zmieniające się elementy, takie jak aktualne zaznaczenie. Kontekst jest stosowany niejawnie dla każdej części i dziedziczy po kontekście nadrzędnym oraz systemie wykonawczym OSGi.
P6
Oto kilka adnotacji stosowanych przez Eclipse 4.
@Inject — używana do zapewnienia w Eclipse ogólnej instrukcji typu „tu wstaw wartość”.
@Optional — wskazuje, że wartością może być null.
@Named — pobiera z kontekstu wartość elementu o wskazanej nazwie.
@PostConstruct — wywoływana tuż po utworzeniu obiektu.
@PreDestroy — wywoływana tuż przed zniszczeniem obiektu.
@Preference — pobiera wartość konkretnej preferencji lub magazynu preferencji.
@EventTopic i @UIEventTopic — otrzymywanie zdarzeń za pomocą usługi zdarzeń lub wątku
@Persist i @PersistState — zapis i przeglądanie danych.
@Execute i @CanExecute — określa metodę do wykonania oraz warunek boolowski wskazujący,
@Creatable — wskazuje, czy obiekt można utworzyć.
@GroupUpdate — wskazuje, czy aktualizację można opóźnić.
interfejsu użytkownika.
czy metodę można uruchomić.
P7
Preferencje są dostępne za pomocą adnotacji @Preference, która wstrzykuje wartość do pola. Jeśli niezbędne są aktualizacje, należy adnotacji użyć jako parametru metody. W ten sposób metoda będzie wywoływana po każdej zmianie wartości preferencji.
P8
Komunikaty wysyła się za pomocą EventBroker dostępnego w kontekście wstrzykiwania. Obiekt zawiera metody sendEvent() i postEvent(). Po stronie odbierającej używa się adnotacji @UIEventTopic lub @EventTopic, by w łatwy sposób pobrać wartość. Podobnie jak w przypadku preferencji, parametr metody umożliwia dowiedzenie się o zajściu zdarzenia (wskazana metoda zostanie po prostu wywołana).
P9
Zaznaczenie można pobrać za pomocą wartości z kontekstu oraz wstrzykiwania metody lub też wstrzykiwania wartości przy użyciu @Named(IServiceConstants.ACTIVE_SELECTION).
302
Dodatek A • Odpowiedzi do quizów
Rozdział 8. Tworzenie funkcjonalności, witryn aktualizacji, aplikacji i produktów Quiz — sposób działania funkcjonalności, aplikacji i produktów P1
Słowo kluczowe qualifier zostaje zastąpione znacznikiem czasowym w momencie budowania wtyczki lub funkcjonalności.
P2
Plikami są artifacts.jar i content.jar, a także po jednym pliku na funkcjonalność i wtyczkę.
P3
Można użyć starszego pliku site.xml lub nowszego pliku category.xml, który w zasadzie jest równoważny.
P4
Jeśli jedna funkcjonalność wymaga innej, musi znajdować się w instancji Eclipse, by instalacja się udała. Jeśli funkcjonalność zawiera inną funkcjonalność, kopia dołączonej funkcjonalność znajdzie się w witrynie aktualizacji.
P5
Aplikacja jest niezależnym programem, który można uruchomić po instalacji w dowolnej instancji Eclipse. Produkt wpływa na instancję Eclipse jako całość, zastępując mechanizm uruchamiania, ikony i domyślnie uruchamianą aplikację.
P6
Aplikacja to klasa implementująca interfejs IApplication oraz zawierająca metodę start(). Odniesienie do niej znajduje się w pliku plugin.xml. Można ją wywołać na podstawie id, używając w wierszu poleceń argumentu -application.
Rozdział 9. Automatyczne testy wtyczek Quiz — działanie SWTBot P1
Wymaganą wersją Runner dla JUnit jest SWTBotJunit4ClassRunner, którą ustawia się za pomocą adnotacji @RunWith(SWTBotJunit4ClassRunner.class).
P2
Widoki otwiera się, wymuszając w systemie przejście do menu Window/Show View/Other i wywołanie odpowiedniego elementu.
P3
Aby pobrać tekst z dialogu, użyj textWithLabel(), by znaleźć pole znajdujące się obok wskazanej etykiety, a następnie ustaw lub pobierz tekst pola.
P4
Klasa Matcher służy do zakodowania konkretnego warunku, na przykład widoku lub okna o określonym tytule. Można ją przekazać do SWTBot, by wykonał test w wątku interfejsu użytkownika i zwrócił odpowiednią wartość.
303
Eclipse 4. Programowanie wtyczek na przykładach
P5
Aby pobrać wartości z interfejsu użytkownika, użyj StringResult (lub innego równoważnego typu) i przekaż go do metody syncExec() z UIThreadRunnable, a uruchomi kod, zwróci wartość i przekaże ją do wywołującego wątku.
P6
Użyj metod waitUntil() lub waitWhile() bota, które blokują wykonywanie testu aż do spełnienia określonego warunku.
Rozdział 10. Automatyczne budowanie przy użyciu Tycho Quiz — automatyczne budowanie i witryny aktualizacji P1
Elementy GroupId, ArtifactId i Version (nazywane w skrócie GAV) to coś na kształt współrzędnych używanych przez Maven do identyfikacji zależności i wtyczek. Grupa (GroupId) łączy kilka artefaktów, a artefakt (ArtifactId) to nazwa konkretnego komponentu. W OSGi i Eclipse grupą jest najczęściej kilka pierwszych segmentów nazwy paczki, a artefaktem jest sama nazwa paczki. Wersja (Version) stosuje tę samą składnię, co wersja paczki, ale zamiast .qualifier używa się -SNAPSHOT.
P2
Czterema typami są: pom (używany dla projektu nadrzędnego), eclipse-plugin (dla wtyczek), eclipse-feature (dla funkcjonalności) i eclipse-repository (dla witryn aktualizacji i produktów).
P3
Numery wersji aktualizuje się za pomocą polecenia mvn org.eclipse.tycho:tychoversionsplugin:set-version -DnewVersion=numer.wersji. Choć istnieje mvn version:set, nie ustawi on wersji wtyczek.
P4
Pliki JAR podpisuje się, by mieć pewność, że pliku JAR nie modyfikowano po otworzeniu. Eclipse analizuje pliki JAR przy uruchamianiu, by mieć pewność, że nie zostały zmienione. Jeśli zostały zmienione lub podpis jest nieważny, wyświetla ostrzeżenie. Do podpisywania plików JAR i sprawdzania poprawności podpisu służy narzędzie Javy jarsigner. Do tworzenia i modyfikacji kluczy służy narzędzie keytool.
P5
Prosty serwer WWW można uruchomić poleceniem python -m SimpleHTTPServer. W przypadku Python 3 jest to polecenie python3 -m http.server.
P6
Funkcjonalności Eclipse publikuje się najczęściej w systemie Eclipse Marketplace dostępnym na stronie http://marketplace.eclipse.org. Dotyczy to zarówno bezpłatnych, jak i płatnych wtyczek.
304
Skorowidz A adnotacja @PostConstruct, 157 @PreDestroy, 157 @Test, 253 JUnit @BeforeClass, 260 akcje, 113–116 aktualizacja kodu w debuggerze, 34–35 aplikacja Eclipse, 241 a produkt Eclipse, 248 archiwa, 282 arkusz stylów, 194 automatyczne testy wtyczek, 251–265
B budowanie funkcjonalności za pomocą Tycho, 275–276 inkrementacyjne, 172 pełne, 172 produktu za pomocą Tycho, 278–282 witryny aktualizacji za pomocą Tycho, 276–278 wtyczki za pomocą Tycho, 270–273
C charakter projektu, 175–178 części, 190, 193
D dane tabelaryczne w JFace, 105–111 debugowanie wtyczki, 31–35 z filtrami kroków, 35 dodanie elementów do zasobnika w SWT, 71–73 logowania do dziennika zdarzeń w Eclipse 4, 199–201 menu kontekstowego, 114–115 poleceń do menu kontekstowego, 124–126 procedury obsługi podwójnego kliknięcia w JFace, 98–101 Drop to Frame, 34
E Eclipse 4, informacje ogólne, 183–184 Eclipse Marketplace, 292 Eclipse, informacje ogólne, 21–22 edytor EMF, 186 element Direct MenuItem, 219 locationURI, 117
F FillLayout, 60 filtracja w JFace, 93–97 filtrowanie elementów w widoku w JFace, 95–97 kroków, 31–35
Skorowidz
funkcjonalności, 228–241 a Tycho, 275–276 eksport, 230–232 instalacja w Eclipse, 232–234 tworzenie, 228–230 tworzenie oznaczeń, 239–241 zależności, 237–239
G GridLayout, 60 grupowanie wtyczek, 228–241 grupy i zakładki w SWT, 76–81
I identyfikator funkcjonalności, 230 polecenia, 116 IMemento dodanie IMemento do widoku stref czasowych, 156 implementacja budowania inkrementacyjnego, 172 instalacja Maven, 268–269 narzędzi Eclipse 4, 184–186 instancja FieldEditor, 146 IPreferenceStore, 144 interakcja z interfejsem użytkownika w Eclipse 4, 211–212 z użytkownikiem, 113–142 w SWT, 67–70 interaktywność w JFace, 98–105 interfejs EMenuService, 222 ESelectionService, 104, 111 IContextFunction, 207 IEclipseContext, 208 IEclipsePreferences, 154 IMemento, 155, 157 IPreferenceStore, 154 IProjectNature, 175 IPropertySupport, 104 ISelection, 98 IStructuredSelection, 100 IStyledLabelProvider, 91–92
306
ITreeSelection, 100 MContext, 202 iteracja przez zasoby, 168–170
J jarsigner, 289 JFace a obrazy, 88–91 JFace, informacje ogólne, 83–84 JUnit, 251–254
K kategoryzacja witryny aktualizacji, 234–237 keytool, 289 klasa Canvas, 51 ColorRegistry, 88 ComboFieldEditor, 147 ContributionManager, 114 DialogSettings, 155, 157–158 Display, 55 FontRegistry, 88 ImageRegistry, 88 Job, 127, 129 ustawianie właściwości, 135–137 LabelProvider, 88 MenuManager, 114 MessageDialogWithToggle, 158 MinimarkNature, 178 MultiStatus, 141 Path, 171 Resource, 62 RowLayout, 59 StatusManager, 140 StatusReporter, 141 SubMonitor, 134 TableTreeViewer, 105 TableViewer, 105 UISynchronize, 211–212 ViewerComparator, 94 ViewerFilter, 95 klawisz M1, 118 klawisze, 118 klucz prywatny, 288–289 publiczny, 288 QualifiedName, 137
Skorowidz
konfiguracja środowiska Eclipse SDK, 22–25 uruchomieniowa, 29–31 kontekst, 119–120 w Eclipse 4, 204 kreator tworzenia wtyczek, 25–28
M M1, 220 M2, 220 M3, 220 M4, 220 magazyn kluczy, 289 maszyna wirtualna Hotspot, 35 Matcher, 261 Maven, 267–269 menedżer tematów w Eclipse 4, 199 układu graficznego, 60 menu, 114–126 metaznaki, 220 metoda addSelectionListener(), 70 asyncExec(), 55 build(), 166 collapseAll(), 97 collapseToLevel(), 97 computeSize(), 57 convert(), 134 createPartControl(), 51 CustomArea(), 99 dispose(), 62, 66 drawArc(), 50 exists(), 171 expandAll(), 97 expandToLevel(), 97 filter(), 95 finalize(), 62 getAdapter(), 104 getChildren(), 88 getData(), 95 getParent(), 88 getPreferenceStore(), 144 hasChildren(), 88 isCancelled(), 131 openError(), 138, 141 paintControl(), 52 redraw(), 54 reveal(), 97
select(), 95 selectionChanged(), 110 setData(), 95 setFocus(), 68 setInput(), 88 setWorkRemaining(), 135 syncExec(), 55 viewByTitle(), 261 metody nasłuchujące, 73 modele w Eclipse 4, 201 monitor postępu prac, 130 monitory i podmonitory typu null, 133–135
N nazewnictwo projektów wtyczek Eclipse, 26–27 numeracja wersji Maven, 287
O obiekt Action, 115 Color, 63 Composite, 60 Event, 206 IPath, 170 Platform, 122 Status, 141 TrayIcon, 73 obiekty modalne w SWT, 74–76 obliczanie wartości na żądanie w Eclipse 4, 207–209 obserwacja wyrażeń, 43–44 obsługa usunięcia pliku, 172–174 widgetów, 52 obstylowanie interfejsu użytkownika za pomocą CSS w Eclipse 4, 194–198 okna pływające, 76 opcja setExpandPreCheckFilters(true), 97 operacje działające w tle, 127–129
P PDE, 22 plik Application.e4xmi, 220 artifacts.jar, 236–237 build.properties, 27
307
Skorowidz
plik
content.jar, 236–237 feature.xml, 237 manifestu, 49–50 META-INF/MANIFEST.MF, 27 plugin.properties, 155 plugin.xml, 27, 50 pom.xml, 271–272 pliki wtyczki Eclipse, 27–28 Plug-in Development Environment (PDE), 22 pobranie okna w Eclipse 4, 201–202 podklasa AbstractUIPlugin, 144–145 podpisywanie witryn aktualizacji, 288–292 wtyczek, 290–292 podzadania, 131–133 POJO, 222–224 polecenia, 115–26 w Eclipse 4, 215 powiązanie menu z poleceniem i procedurą obsługi w Eclipse 4, 213–215 poleceń ze skrótami klawiaturowymi, 117–118 preferencja użytkownika, 143 dodanie siatki, 149 dodanie słów kluczowych, 153 lokalizacja strony preferencji, 150–151 tworzenie komunikatów ostrzeżeń i błędów, 146–147 utworzenie strony preferencji, 145–146 użycie innych edytorów pól, 151–152 wybór elementu z listy, 147–149 zapisywanie i wczytywanie, 144–145 preferencje w Eclipse 4, 209–211 procedury obsługi, 115–126 produkt a Tycho, 278 produkt Eclipse, 241, 245, 248 przekazywanie parametrów polecenia w Eclipse 4, 215–217 przestrzeń nazw Eclipse, 26–27 przestrzeń robocza, 161 przypadki testowe, 251 pseudoselektor, 196 publikowanie witryny aktualizacji na serwerze, 292 punkty rozszerzeń, 50 punkty wstrzymania dla metod, 37–38 warunkowe, 38–40 wstrzymanie działania po wystąpieniu wyjątku, 40–44
308
R raportowanie postępu prac, 129–130 reakcja na akcje użytkownika w SWT, 73–74 rejestr zasobów, 88 rejestracja rodzaju znacznika, 180–181 repozytorium p2, 282 RowLayout, 60 Run, 29–31 Run/Step into Selection, 34 rysowanie własnego widoku w SWT, 50–60
S selektor stylów, 194–196 słowa kluczowe, 153 sortowanie w JFace, 93–97 sprawdzanie anulowania zadania, 131 Step Filtering, 37 Step Into, 34 Step Over, 34 Step Return, 34, 38 styl FLAT, 149 style w dostawcy etykiet w JFace, 91–93 Suspend on caught exceptions, 42 Suspend on uncaught exceptions, 42 SWT a obsługa wątków, 55 SWT, informacje ogólne, 47, 52 SWT.NO_TRIM, 76 SWT.ON_TOP, 76 SWTBot, 254–260 interakcja z interfejsem użytkownika, 262–265 korzystanie z widoków, 260–262 synchronizacja wyboru widoku w JFace, 109 systemy budujące, 165 szpieg CSS, 185
Ś śledzenie w SWT, 64–65
T testy automatyczne, 283–286 interfejsu graficznego, 254–260 wtyczek, 251–265 tłumaczenie na inne języki, 155
Skorowidz
tworzenie akcji, 113–115 aplikacji bez interfejsu użytkownika, 242–245 bezpośredniego menu i skrótów klawiszowych w Eclipse 4, 218–220 charakteru projektu, 175–178 części w Eclipse 4, 190–193 edytora, 162–164 funkcjonalności, 228–230 menu kontekstowego i menu widoku w Eclipse 4, 220–222 obiektu TreeViewer w JFace, 84–88 parsera, 164–165 poleceń i procedur obsługi, 115–117 produktu Eclipse, 245–249 projektu nadrzędnego, 273–275 przykładowej aplikacji Eclipse 4, 186–190 systemu budującego, 165–168 usługi w Eclipse 4, 222–223 widgetu wielokrotnego użytku w SWT, 56–58 widoków TreeViewer w JFace, 84–93 widoku w SWT, 48–50 własnych klas do wstrzykiwania w Eclipse 4, 222–224 wtyczki za pomocą kreatora, 25–28 zasobów, 170–171 Tycho, 268
U UIJob, 127 układ graficzny widoku w SWT, 58–60 ukrywanie ekranu powitalnego, 258–259 uruchomienie w wątku interfejsu użytkownika w SWT, 55– 56 wtyczki, 28–31 usługa OSGi, 199 OSGi EventAdmin, 204, 206 uzyskanie zaznaczenia w Eclipse 4, 202–204
W wersjonowanie semantyczne, 287 widgety w SWT, 47–82 widok Variables, 43–44 widoki w SWT, 47–82 wielokrotne użycie wyrażeń, 123–124 witryna aktualizacji, 292 a Tycho, 276–278 kategoryzacja, 234–237 podpisywanie, 288–292 właściwości stylów, 197–198 włączanie i wyłączanie elementów menu, 121–122 wstrzykiwanie podtypów w Eclipse 4, 223–224 wtyczka zgodności, 225 wycieki zasobów, 63–67 wyłapywanie wyjątków, 40–42 wyrażenie visibleWhen, 121–122 wyświetlanie właściwości w JFace, 101–105 wywołanie isDisposed(), 62
Z zadania, 54, 127–138 zakres kontekstu, 119 zarządzanie zasobami w SWT, 61–67 zasobnik systemowy, 71 zasoby, 161–182 zatykanie wycieku, 65–67 zdarzenia w Eclipse 4, 204–207 wyboru, 111 zestawy testów, 251 zgłaszanie błędów, 138–141 zmiana kontekstu, 119–121 numeru wersji, 286–288 zmienna style, 52 zmienne w Workbench Core Expressions, 122 znaczniki, 178–181 znajdowanie wycieku, 63–65
V Variables, 43–44
309
Skorowidz
310