View Javadoc

1   /*
2     Copyright 2004-2005 robsite.org
3         
4      Licensed under the Apache License, Version 2.0 (the "License"); 
5      you may not use this file except in compliance with the License.
6      You may obtain a copy of the License at
7         
8          http://www.apache.org/licenses/LICENSE-2.0
9         
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15   */
16   
17  package org.robsite.extension.rss;
18  
19  import java.io.InputStream;
20  import java.util.ArrayList;
21  
22  import java.util.Map;
23  import javax.swing.Timer;
24  import javax.swing.event.ChangeEvent;
25  import javax.swing.event.ChangeListener;
26  import oracle.ide.Ide;
27  import oracle.ide.IdeAction;
28  import oracle.ide.IdeEvent;
29  import oracle.ide.IdeListener;
30  import oracle.ide.MenuConstants;
31  import oracle.ide.addin.Addin;
32  import oracle.ide.addin.Controller;
33  import oracle.ide.docking.DockStation;
34  import oracle.ide.panels.Navigable;
35  
36  import oracle.javatools.Environment;
37  
38  import org.robsite.extension.rss.model.Channels;
39  
40  /***
41   * The RSS News Reader extension.
42   * 
43   * @author brian_duff@sourceforge.net
44   * @version $Revision: 1.1.1.1 $
45   */
46  public class NewsFeedExtension implements Addin 
47  {
48    public static final String VIEW_ID = "RSSNewsWindow";
49    static final String PREFS_KEY = "newsFeedPrefs";
50  
51    private ArrayList _changeListeners = new ArrayList();
52    private static NewsFeedExtension s_instance;
53    private NewsDockableWindow _dockableWindow;
54    
55    private ResHelper _res = new ResHelper( "Extension" );
56    
57    /***
58     * The runnable that actually checks news.
59     */
60    private NewsCheckRunnable _checkRunnable;
61    
62    /***
63     * The timer that notifies the news check runnable to wake up and check
64     * news periodically.
65     */
66    private Timer _timer;
67    
68    
69  
70    /***
71     * Get the singleton news feed extension. 
72     * 
73     * @return get the only instance of this extension class. May return null
74     *    if the extension has not been initialized.
75     */
76    public static NewsFeedExtension get()
77    {
78      return s_instance;
79    }
80    
81    /***
82     * Get the news dockable window.
83     * 
84     * @return the news dockable window. This may be null if the window has
85     *    never been shown or is currently not visible.
86     */
87    public synchronized NewsDockableWindow getNewsDockable()
88    {
89      return _dockableWindow;
90    }
91    
92    /***
93     * Set the news dockable window. This is used only by the news dockable
94     * window itself to notify the extension when the window is visible.
95     * 
96     * @param newsDockable the news dockable.
97     */
98    synchronized void setNewsDockable( NewsDockableWindow newsDockable )
99    {
100     _dockableWindow = newsDockable;
101     // Always force an update of news when the dockable is shown.
102     if ( newsDockable != null && _checkRunnable != null )
103     {
104       _checkRunnable.forceUpdate();
105     }
106   }
107   
108   /***
109    * Get the preferences object.
110    * 
111    * @return preferences.
112    */
113   public NewsFeedPreferences getPreferences()
114   {
115     NewsFeedPreferences prefs = (NewsFeedPreferences) 
116       Ide.getSettings().getData( PREFS_KEY );  
117       
118     if ( prefs == null )
119     {
120       prefs = new NewsFeedPreferences();
121       prefs.setCheckDelaySeconds( 600 ); // Ten minutes.
122       prefs.setHTTPTimeout( 3000 );
123       prefs.setCheckAutomatically( true );
124       prefs.setSyncChannels( true );
125       prefs.setCheckOnStartup( true );
126       prefs.setDirectoryURL( "http://otn.oracle.com/products/jdev/htdocs/partners/addins/exchange/rss/newsdirectory.xml" );
127       
128       Ide.getSettings().putData( PREFS_KEY, prefs );
129     }
130     
131     return prefs;
132   }
133   
134   public Channels getChannels()
135   {
136     return getPreferences().getChannels();
137   }
138   
139   /***
140    * Start (or restart) the timer which periodically updates.
141    */
142   private void restartTimer()
143   {
144     if ( _timer != null )
145     {
146       _timer.stop();
147       _timer = null;
148     }
149     
150     if ( getPreferences().isCheckAutomatically() )
151     {
152       _timer = new Timer(  
153         getPreferences().getCheckDelaySeconds() * 1000,
154         _checkRunnable
155       );
156       _timer.setRepeats( true );
157       _timer.start();
158     }
159   }
160   
161 ///////////////////////////////////////////////////////////////////////////////
162 // Addin implementation
163 ///////////////////////////////////////////////////////////////////////////////
164 
165   public void initialize()
166   {
167     s_instance = this;
168     
169     // Install version number into Help->About
170     Ide.getVersionInfo().addComponent(
171       NewsFeedExtension.class.getPackage().getSpecificationTitle(),
172       NewsFeedExtension.class.getPackage().getImplementationVersion()
173     );    
174     
175     // Register the dockable factory that controls creation of the RSS News
176     // window.
177     DockStation.getDockStation().registerDockableFactory(
178       VIEW_ID, new NewsDockableFactory()
179     );
180     
181     getPreferences(); // Force prefs to be defaulted if they don't exist
182     
183     // Register UI for the preferences dialog.
184     Ide.getSettings().registerUI( 
185       new Navigable( _res.getRes( "PreferencesName" ), 
186       NewsFeedPreferencesPanel.class ) );
187     
188     // The controller is responsible for determining whether items are 
189     // enabled or disabled. You can also use a controller to perform operations
190     // but in this extension, we choose not to do this. Instead each operation
191     // has a class following the command pattern that carries out the operation
192     NewsFeedController controller = new NewsFeedController();
193     
194     createAction( controller, Commands.VIEW_NEWS_DOCKABLE, 
195       "org.robsite.extension.rss.ViewNewsDockableCommand", 
196       _res.getRes( "CheckRSSNews" ),
197       _res.getRes( "CheckRSSNewsMnemonic" ).charAt( 0 )
198     );
199     
200     createAction( controller, Commands.VIEW_NEWS_DOCKABLE_VIEW,
201       "org.robsite.extension.rss.ViewNewsDockableCommand",
202       _res.getRes( "RSSNews" ),
203       _res.getRes( "RSSNewsMnemonic" ).charAt( 0 )
204     );
205 
206     createAction( controller, Commands.MARK_AS_READ, 
207       "org.robsite.extension.rss.MarkAsReadCommand",
208       _res.getRes( "MarkAsRead" ),
209       _res.getRes( "MarkAsReadMnemonic" ).charAt( 0 )
210     );
211     
212     createAction( controller, Commands.OPEN_IN_BROWSER, 
213       "org.robsite.extension.rss.OpenInBrowserCommand",
214       _res.getRes( "OpenInBrowser" ),
215       _res.getRes( "OpenInBrowserMnemonic" ).charAt( 0 )
216     );    
217 
218     // Install an item into the Help menu in the same section as the
219     // existing "Check for Updates" item.
220     Ide.getMenubar().add( 
221       Ide.getMenubar().createMenuItem( 
222         Commands.getAction( Commands.VIEW_NEWS_DOCKABLE ) 
223       ),
224       Environment.getJMenu( Ide.getMainWindow().MENU_HELP ),
225       MenuConstants.SECTION_HELP_UPDATES
226     );
227     
228     // Install an item into the View menu in the section set aside for 
229     // extensions.
230     Ide.getMenubar().add(
231       Ide.getMenubar().createMenuItem( 
232         Commands.getAction( Commands.VIEW_NEWS_DOCKABLE_VIEW ) 
233       ),
234       Environment.getJMenu( Ide.getMainWindow().MENU_VIEW ),
235       MenuConstants.SECTION_VIEW_ADDINS
236     );    
237     
238     // This is the standard mechanism for delaying some initialization until
239     // after all other addins have initialized. It's a good idea to do this
240     // if you are starting a thread, since it prevents your thread from 
241     // slowing down the initialization of other addins (and hence the
242     // overall startup time of the IDE). In debug builds of JDeveloper, 
243     // diagnostics will yell loudly at you if you try to create a thread
244     // in your initialize() method.
245     Ide.addIdeListener( new IdeListener() {
246       public void mainWindowClosing( IdeEvent ideEvent ) {}
247       public void mainWindowOpened( IdeEvent ideEvent ) 
248       {
249         try
250         {
251           _checkRunnable =  new NewsCheckRunnable( NewsFeedExtension.this );
252           Thread t = new Thread( _checkRunnable, "RSS News Check Thread" );
253           t.start();
254           
255           // If the preference to check automatically on startup is set,
256           // force a check right now.
257           if ( getPreferences().isCheckOnStartup() )
258           {
259             _checkRunnable.forceUpdate();
260           }
261           
262           restartTimer();
263           
264           // Finally, attach a listener to preferences so that the timer 
265           // is reset whenever they change.
266           getPreferences().addChangeListener( new ChangeListener() {
267             public void stateChanged( ChangeEvent ce )
268             {
269               restartTimer();
270               // Always force an update after changing preferences. This ensures
271               // that any new channels get populated now (rather than during
272               // the next timer tick, which could be 10 minutes away)
273               _checkRunnable.forceUpdate();
274             }
275           });
276         }
277         finally
278         {
279           // We don't care about any more events
280           Ide.removeIdeListener( this );
281         }
282       }
283       public void addinsLoaded( IdeEvent ideEvent ) {}
284     });
285 
286   }
287   
288   public void shutdown()
289   {
290     s_instance = null;
291   }
292 
293   public float version()
294   {
295     // Since JDev 9.0.5.1, this value is now being displayed in the Extensions
296     // panel off the Help dialog.
297     return 9.0517f;
298   }
299 
300   public float ideVersion()
301   {
302     // This value is not used at all by modern versions of JDeveloper.
303     return 0;
304   }
305 
306   public boolean canShutdown()
307   {
308     return true;
309   }  
310   
311 /// Utilities
312 
313   
314   /***
315    * Utility method to create and register an IDE action.
316    * 
317    * @param controller the controller to associate with the action
318    * @param id the command name for the action.
319    * @param commandClass the command class for the action.
320    * @param label the label for the action.
321    * @param mnemonic the mnemonic for the action.
322    */
323   private void createAction( 
324     Controller controller, String id, String commandClass, String label, 
325     int mnemonic )
326   {
327     // Be sure to always use IdeAction.get() instead of IdeAction.create().
328     // The main difference is that get() actually remembers your action
329     // in the IDE, enabling certain functionality like accelerators to work
330     // properly. If you just use IdeAction.create(), your actions are created
331     // transiently and not remembered by the IDE.
332     IdeAction.get( 
333       Commands.id( id ),
334       commandClass,
335       label,
336       null,
337       new Integer( mnemonic ),
338       null,
339       null,
340       true
341     ).addController( controller );
342   }
343 }