platform-ui-home/rcp-proposal/rcp_tutorial/tutorial1.html
Parent Directory
|
Revision Log
Revision 1.1 - (view) (download) (as text)
| 1 : | jeem | 1.1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
| 2 : | <html> | ||
| 3 : | |||
| 4 : | <head> | ||
| 5 : | <title>Rich Client Platform</title> | ||
| 6 : | <link href="default_style.css" rel=stylesheet> | ||
| 7 : | </head> | ||
| 8 : | |||
| 9 : | <body> | ||
| 10 : | |||
| 11 : | <div align="right"> | ||
| 12 : | <font face="Times New Roman, Times, serif" size="2">Copyright © 2003-2004 Ed | ||
| 13 : | Burnette.</font> | ||
| 14 : | <table border="0" cellspacing="0" cellpadding="2" width="100%"> | ||
| 15 : | <tbody> | ||
| 16 : | <tr> | ||
| 17 : | <td align="left" valign="top" colspan="2" bgcolor="#0080c0"><b><font face="Arial,Helvetica"><font color="#ffffff">Eclipse | ||
| 18 : | Article</font></font></b></td> | ||
| 19 : | </tr> | ||
| 20 : | </tbody> | ||
| 21 : | </table> | ||
| 22 : | </div> | ||
| 23 : | <div align="left"> | ||
| 24 : | <h1 title="RCP Tutorial"><img src="images/Idea.jpg" align="middle" width="120" height="86"></h1> | ||
| 25 : | </div> | ||
| 26 : | <h1 align="center">Rich Client Tutorial Part 1 - Draft</h1> | ||
| 27 : | <p class="summary">The Rich Client Platform (RCP) is an exciting new way to | ||
| 28 : | build Java applications that can compete with native applications on any | ||
| 29 : | platform. This tutorial is designed to get you started building RCP applications | ||
| 30 : | quickly.</p> | ||
| 31 : | <p><b>By Ed Burnette, SAS Institute Inc.</b><br> | ||
| 32 : | <font size="-1">January 2, 2004 - Updated for 3.0M6, removed plug-in class</font></p> | ||
| 33 : | <p><i>Note that Eclipse 3 is undergoing constant change so the steps you need to | ||
| 34 : | take, the APIs, and the results may vary slightly (or not so slightly) from this | ||
| 35 : | description.</i></p> | ||
| 36 : | <hr width="100%"> | ||
| 37 : | <h2>Introduction</h2> | ||
| 38 : | <p>Try this experiment: Show Eclipse to some friends or co-workers who haven't | ||
| 39 : | seen it before and ask them to guess what language it is written in. Chances | ||
| 40 : | are, they'll guess VB, C++, or C#, because those languages are used most often | ||
| 41 : | for high quality client side applications. Then watch the look on their faces | ||
| 42 : | when you tell them it was created in Java, especially if they are Java | ||
| 43 : | programmers.</p> | ||
| 44 : | <p>Because of its unique open source license, you can use the technologies that | ||
| 45 : | went into Eclipse to create your own commercial quality programs With previous | ||
| 46 : | version of Eclipse, this was possible but difficult, especially when you wanted | ||
| 47 : | to heavily customize the menus, layouts, and other user interface elements. That | ||
| 48 : | was because the "IDE-ness" of Eclipse was hard-wired into it. Version | ||
| 49 : | 3 introduces the Rich Client Platform, which is basically a refactoring of the | ||
| 50 : | fundamental parts of Eclipse's UI, allowing it to be used for non-IDE | ||
| 51 : | applications.</p> | ||
| 52 : | <p>If you want to cut to the chase and look at the code you can <a href="RcpTutorial.zip">download | ||
| 53 : | the Eclipse project here</a>. Otherwise, let's take a look at how to construct | ||
| 54 : | an RCP application.</p> | ||
| 55 : | <h2><a name="section_1"></a> Getting started</h2> | ||
| 56 : | <p>RCP applications are based on the familiar Eclipse plug-in architecture, (if | ||
| 57 : | it's not familiar to you, see the references section). Therefore, you'll need to | ||
| 58 : | create a plug-in to be your main program. Select New > Project > Plug-in | ||
| 59 : | Development > Plug-in Project to bring up the Plug-in Project wizard. On the | ||
| 60 : | subsequent pages, enter a Project name such as <b>org.eclipsepowered.rcptutorial1</b>, | ||
| 61 : | and a Plug-in Id (this should match the project name). On the Code Generators | ||
| 62 : | page, select the Default Plug-in Structure, then click Next and Finish to | ||
| 63 : | generate the template.</p> | ||
| 64 : | <h2>The plug-in class</h2> | ||
| 65 : | <p>The generated plug-in class that you may be familiar with in previous | ||
| 66 : | releases is no longer required in Eclipse 3.0. | ||
| 67 : | You can still have one to hold global data if you like, but for this | ||
| 68 : | example you can just remove it altogether to save a little space. | ||
| 69 : | Right click on the plug-in java file (in our example this is called | ||
| 70 : | RcpTutorial1Plugin.java) and delete it. | ||
| 71 : | </p> | ||
| 72 : | <h2>The main program</h2> | ||
| 73 : | <p>The main program implements IPlatformRunnable, which just has a method called | ||
| 74 : | run(). Listing 1 shows a simple implementation that shows you the minimum you have to | ||
| 75 : | do.</p> | ||
| 76 : | <p><b>Listing 1. RcpApplication class. | ||
| 77 : | </b></p> | ||
| 78 : | <pre> | ||
| 79 : | package org.eclipsepowered.rcptutorial1; | ||
| 80 : | import org.eclipse.core.boot.IPlatformRunnable; | ||
| 81 : | import org.eclipse.swt.widgets.Display; | ||
| 82 : | import org.eclipse.ui.PlatformUI; | ||
| 83 : | import org.eclipse.ui.application.WorkbenchAdvisor; | ||
| 84 : | |||
| 85 : | public class RcpApplication implements IPlatformRunnable { | ||
| 86 : | public Object run(Object args) { | ||
| 87 : | WorkbenchAdvisor workbenchAdvisor = new RcpWorkbenchAdvisor(); | ||
| 88 : | Display display = PlatformUI.createDisplay(); | ||
| 89 : | int returnCode = PlatformUI.createAndRunWorkbench(display, | ||
| 90 : | workbenchAdvisor); | ||
| 91 : | if (returnCode == PlatformUI.RETURN_RESTART) { | ||
| 92 : | return IPlatformRunnable.EXIT_RESTART; | ||
| 93 : | } else { | ||
| 94 : | return IPlatformRunnable.EXIT_OK; | ||
| 95 : | } | ||
| 96 : | } | ||
| 97 : | } | ||
| 98 : | </pre> | ||
| 99 : | <h2>Creating a default perspective</h2> | ||
| 100 : | <p> | ||
| 101 : | Next, you must define at least one perspective and make it the default. | ||
| 102 : | Perspectives are created by implementing IPerspectiveFactory (see listing 2). | ||
| 103 : | The important part | ||
| 104 : | of this interface is the createInitialLayout() method where you position and | ||
| 105 : | open any views and/or editors you'd like the user to start with. | ||
| 106 : | In this example | ||
| 107 : | we're not going to create any views so it will be a pretty boring perspective. | ||
| 108 : | </p> | ||
| 109 : | |||
| 110 : | <p><b>Listing 2. RcpPerspective class. | ||
| 111 : | </b></p> | ||
| 112 : | <pre> | ||
| 113 : | package org.eclipsepowered.rcptutorial1; | ||
| 114 : | |||
| 115 : | import org.eclipse.ui.IPageLayout; | ||
| 116 : | import org.eclipse.ui.IPerspectiveFactory; | ||
| 117 : | |||
| 118 : | public class RcpPerspective implements IPerspectiveFactory { | ||
| 119 : | |||
| 120 : | public RcpPerspective() { | ||
| 121 : | } | ||
| 122 : | |||
| 123 : | public void createInitialLayout(IPageLayout layout) { | ||
| 124 : | } | ||
| 125 : | } | ||
| 126 : | |||
| 127 : | </pre> | ||
| 128 : | <h2>Workbench Advisor</h2> | ||
| 129 : | <p> | ||
| 130 : | The Workbench Advisor class helps customize the workbench to add and subtract | ||
| 131 : | toolbars, perspectives, and so forth. | ||
| 132 : | This will covered in more detail in part 2 of the tutorial. | ||
| 133 : | For now, the absolute minimum you have to do here | ||
| 134 : | is define which perspective is the default one. | ||
| 135 : | See listing 3 for the code. | ||
| 136 : | </p> | ||
| 137 : | |||
| 138 : | <p><b>Listing 3. RcpWorkbenchAdvisor class. | ||
| 139 : | </b></p> | ||
| 140 : | <pre> | ||
| 141 : | package org.eclipsepowered.rcptutorial1; | ||
| 142 : | import org.eclipse.ui.application.WorkbenchAdvisor; | ||
| 143 : | |||
| 144 : | public class RcpWorkbenchAdvisor extends WorkbenchAdvisor { | ||
| 145 : | |||
| 146 : | public String getInitialWindowPerspectiveId() { | ||
| 147 : | return "org.eclipsepowered.rcptutorial1.RcpPerspective"; | ||
| 148 : | } | ||
| 149 : | } | ||
| 150 : | </pre> | ||
| 151 : | <h2>Plug-in manifest</h2> | ||
| 152 : | <p>As usual, the plug-in manifest, plugin.xml, ties everything together. | ||
| 153 : | <i>Note: Future builds of 3.0 will likely change this further.</i> | ||
| 154 : | The class name in | ||
| 155 : | the plugin tag refers to the plug-in class we deleted earlier, | ||
| 156 : | so you should remove the reference too. | ||
| 157 : | The main program | ||
| 158 : | class name is defined with the org.eclipse.core.runtime.applications extension | ||
| 159 : | and the perspective with org.eclipse.ui.perspectives. | ||
| 160 : | Note you have to manually edit the <code>runtime</code> section to take out | ||
| 161 : | the reference to org.eclipse.core.resources and add | ||
| 162 : | org.eclipse.core.runtime.compatibility | ||
| 163 : | in current builds. | ||
| 164 : | When you're done you should have something that looks like listing 4. | ||
| 165 : | </p> | ||
| 166 : | |||
| 167 : | <p><b>Listing 4. Plugin.xml. | ||
| 168 : | </b></p> | ||
| 169 : | <pre> | ||
| 170 : | <?xml version="1.0" encoding="UTF-8"?> | ||
| 171 : | <?eclipse version="3.0"?> | ||
| 172 : | <plugin | ||
| 173 : | id="org.eclipsepowered.rcptutorial1" | ||
| 174 : | name="%pluginName" | ||
| 175 : | version="0.0.0" | ||
| 176 : | provider-name="%providerName"> | ||
| 177 : | |||
| 178 : | <runtime> | ||
| 179 : | <library name="rcptutorial1.jar"> | ||
| 180 : | <export name="*"/> | ||
| 181 : | </library> | ||
| 182 : | </runtime> | ||
| 183 : | <requires> | ||
| 184 : | <import plugin="org.eclipse.core.runtime.compatibility"/> | ||
| 185 : | <import plugin="org.eclipse.ui"/> | ||
| 186 : | </requires> | ||
| 187 : | |||
| 188 : | |||
| 189 : | <extension | ||
| 190 : | id="RcpApplication" | ||
| 191 : | point="org.eclipse.core.runtime.applications"> | ||
| 192 : | <application> | ||
| 193 : | <run | ||
| 194 : | class="org.eclipsepowered.rcptutorial1.RcpApplication"> | ||
| 195 : | </run> | ||
| 196 : | </application> | ||
| 197 : | </extension> | ||
| 198 : | <extension | ||
| 199 : | point="org.eclipse.ui.perspectives"> | ||
| 200 : | <perspective | ||
| 201 : | name="%perspectiveName" | ||
| 202 : | class="org.eclipsepowered.rcptutorial1.RcpPerspective" | ||
| 203 : | id="org.eclipsepowered.rcptutorial1.RcpPerspective"> | ||
| 204 : | </perspective> | ||
| 205 : | </extension> | ||
| 206 : | </plugin> | ||
| 207 : | </pre> | ||
| 208 : | <h2>Miscellaneous</h2> | ||
| 209 : | <p>The build.properties file (see listing 5) | ||
| 210 : | will be needed when exporting the application for | ||
| 211 : | others to use.</p> | ||
| 212 : | |||
| 213 : | <p><b>Listing 5. Build.properties. | ||
| 214 : | </b></p> | ||
| 215 : | <pre> | ||
| 216 : | bin.includes = plugin.xml,\ | ||
| 217 : | *.jar,\ | ||
| 218 : | rcptutorial1.jar,\ | ||
| 219 : | plugin.properties | ||
| 220 : | source.rcptutorial1.jar = src/ | ||
| 221 : | </pre> | ||
| 222 : | |||
| 223 : | <p> | ||
| 224 : | Finally, the plugin.properties file (listing 6) contains natural language strings at the | ||
| 225 : | plug-in registry level (i.e., things the run-time has to know about your plug-in | ||
| 226 : | before even loading it). | ||
| 227 : | You could hard-code these into your plug-in manifest | ||
| 228 : | but it's best to get into the i18n habit from the start. | ||
| 229 : | </p> | ||
| 230 : | |||
| 231 : | <p><b>Listing 6. Plugin.properties | ||
| 232 : | </b></p> | ||
| 233 : | <pre> | ||
| 234 : | pluginName = RcpTutorial1 Plug-in | ||
| 235 : | providerName = eclipsepowered.org | ||
| 236 : | perspectiveName = RcpTutorial1 | ||
| 237 : | </pre> | ||
| 238 : | |||
| 239 : | <h2>Taking it for a spin</h2> | ||
| 240 : | <p>Normally, when testing a plug-in you would select the project and then select | ||
| 241 : | Run > Debug > Debug As > Run-time Workbench. Go ahead and try that now. | ||
| 242 : | It will give you a full blown Eclipse IDE Workbench window because we haven't | ||
| 243 : | overridden the application name - it is still defaulting to the IDE application. | ||
| 244 : | To fix this, edit the launch configuration you just created (select Run > | ||
| 245 : | Debug > Debug..., and then select New_Configuration or create a new one). In | ||
| 246 : | the Arguments tab, add this to the end of the Program Arguments:</p> | ||
| 247 : | <pre> | ||
| 248 : | -application org.eclipsepowered.rcptutorial1.RcpApplication | ||
| 249 : | </pre> | ||
| 250 : | <p>This name comes from the id specified on the | ||
| 251 : | org.eclipse.core.runtime.applications extension point.</p> | ||
| 252 : | <p>Now switch over to the Plug-ins and Fragments tab. Select the option to | ||
| 253 : | Choose plug-ins and fragments, and select the following plug-ins. This is | ||
| 254 : | currently the absolute minimum that will work for a GUI RCP program. | ||
| 255 : | <i>Note: Future builds of 3.0 will likely change this further.</i> | ||
| 256 : | </p> | ||
| 257 : | <ul> | ||
| 258 : | <li>org.eclipse.core.runtime | ||
| 259 : | <li>org.eclipse.core.runtime.compatibility | ||
| 260 : | <li>org.eclipse.help | ||
| 261 : | <li>org.eclipse.jface | ||
| 262 : | <li>org.eclipse.osgi | ||
| 263 : | <li>org.eclipse.osgi.services | ||
| 264 : | <li>org.eclipse.osgi.util | ||
| 265 : | <li>org.eclipse.swt | ||
| 266 : | <li>org.eclipse.swt.win32 | ||
| 267 : | <li>org.eclipse.ui | ||
| 268 : | <li>org.eclipse.ui.workbench | ||
| 269 : | <li>org.eclipse.update.configurator | ||
| 270 : | <li>org.eclipsepowered.rcptutorial1 | ||
| 271 : | </ul> | ||
| 272 : | <p>Now press Debug. You should see a bare-bones Workbench start up (see figure 1).</p> | ||
| 273 : | |||
| 274 : | <p><b>Figure 1. World's simplest RCP application. | ||
| 275 : | </b></p> | ||
| 276 : | <img src="images/spin.jpg" width="406" height="269"> | ||
| 277 : | |||
| 278 : | <p>If you don't see this, you should be able to find an error message in the | ||
| 279 : | run-time workbench's .log file (runtime-workspace/.metadata/.log). Simply bring | ||
| 280 : | it up in a text editor and look for the most recent errors. You may find it | ||
| 281 : | useful to delete the .log file before running the program so you won't get | ||
| 282 : | confused by older messages.</p> | ||
| 283 : | |||
| 284 : | <h2>Running it outside of Eclipse</h2> | ||
| 285 : | <p>The whole point of all this is to be able to run stand-alone applications | ||
| 286 : | without the user having to know anything about the Java and Eclipse code being | ||
| 287 : | used under the covers. For a real application you will probably want to provide | ||
| 288 : | a self-contained executable generated by an install program like InstallShield. | ||
| 289 : | That's really beyond the scope of this article though, so we'll do something | ||
| 290 : | simpler.</p> | ||
| 291 : | <p>We need to create a simplified version of the Eclipse install directory | ||
| 292 : | because the Eclipse plug-in loader expects things to be in a certain layout. | ||
| 293 : | Here are the steps to get started:</p> | ||
| 294 : | <ol> | ||
| 295 : | <li>Right click on the plug-in project and select Export. | ||
| 296 : | <li>Select Deployable plug-ins and fragments and then press Next. | ||
| 297 : | <li>Select the plug-in(s) that should be included. For this | ||
| 298 : | example that would be org.eclipsepowered.rcptutorial1. | ||
| 299 : | <li>Select the option to Export as a directory structure, and for the | ||
| 300 : | directory enter a name ending with RcpTutorial1 (for example, C:\RcpTutorial1 on | ||
| 301 : | Windows). | ||
| 302 : | <li>If you want to provide source code for your application, | ||
| 303 : | select the Include Source Code option. | ||
| 304 : | <li>Press Finish to export the plug-in. | ||
| 305 : | Eclipse will perform a full build in the background | ||
| 306 : | and populate the directory for you. | ||
| 307 : | </ol> | ||
| 308 : | <p>To complete the RcpTutorial1 directory, copy the startup.jar file from the Eclipse | ||
| 309 : | install directory into the top level, and copy the other org.eclipse plug-ins | ||
| 310 : | from the list of required plug-ins above into the plugins directory. When you're | ||
| 311 : | done you should have a structure that looks like this:</p> | ||
| 312 : | <pre> | ||
| 313 : | RcpTutorial1 | ||
| 314 : | | startup.jar | ||
| 315 : | +--- plugins | ||
| 316 : | +--- org.eclipse.core.runtime.compatibility_3.0.0 | ||
| 317 : | +--- org.eclipse.core.runtime_3.0.0 | ||
| 318 : | +--- org.eclipse.help_3.0.0 | ||
| 319 : | +--- org.eclipse.jface_3.0.0 | ||
| 320 : | +--- org.eclipse.osgi.services_3.0.0 | ||
| 321 : | +--- org.eclipse.osgi.util_3.0.0 | ||
| 322 : | +--- org.eclipse.osgi_3.0.0 | ||
| 323 : | +--- org.eclipse.swt.win32_3.0.0 | ||
| 324 : | +--- org.eclipse.swt_3.0.0 | ||
| 325 : | +--- org.eclipse.ui.workbench_3.0.0 | ||
| 326 : | +--- org.eclipse.ui_3.0.0 | ||
| 327 : | +--- org.eclipse.update.configurator_3.0.0 | ||
| 328 : | +--- org.eclipsepowered.rcptutorial1 | ||
| 329 : | </pre> | ||
| 330 : | <p>That's all you need to run an RCP application, but it would be difficult for | ||
| 331 : | anyone to use it without some kind of launching program. Eclipse uses | ||
| 332 : | eclipse.exe, but we'll just use a batch file. Create a Windows command file | ||
| 333 : | called rcptutorial1.cmd and place it in the top level RcpTutorial1 directory. A Unix shell | ||
| 334 : | script version would be similar. | ||
| 335 : | <pre> | ||
| 336 : | @echo off | ||
| 337 : | setlocal | ||
| 338 : | cd %~dp0 | ||
| 339 : | rem Check workspace\.metadata\.log for errors | ||
| 340 : | start javaw -cp startup.jar org.eclipse.core.launcher.Main | ||
| 341 : | -application org.eclipsepowered.rcptutorial1.RcpApplication %* | ||
| 342 : | endlocal | ||
| 343 : | |||
| 344 : | </pre> | ||
| 345 : | <p>The start command should be all on one line. As before, the -application | ||
| 346 : | option refers back to the id specified on the | ||
| 347 : | org.eclipse.core.runtime.applications extension point.</p> | ||
| 348 : | <p>You can get as fancy as you want in this script file. Here's a variant that I | ||
| 349 : | like to use when debugging because it will display any error messages from the | ||
| 350 : | Eclipse loader in a separate window:</p> | ||
| 351 : | <pre> | ||
| 352 : | echo on | ||
| 353 : | setlocal | ||
| 354 : | cd %~dp0 | ||
| 355 : | rem Display workspace\.metadata\.log if there are errors | ||
| 356 : | del workspace\.metadata\.log | ||
| 357 : | java -cp startup.jar org.eclipse.core.launcher.Main | ||
| 358 : | -application org.eclipsepowered.rcptutorial1.RcpApplication %* | ||
| 359 : | || type workspace\.metadata\.log && pause | ||
| 360 : | endlocal | ||
| 361 : | </pre> | ||
| 362 : | <p>The java command should be all on one line.</p> | ||
| 363 : | <p>To eliminate the startup window on Windows, you can use a shortcut instead of | ||
| 364 : | a script file. Right click inside the RcpTutorial1 folder, select New > Shortcut | ||
| 365 : | and enter this as the location of the item (on one line):</p> | ||
| 366 : | <pre> | ||
| 367 : | %windir%\system32\javaw.exe -cp startup.jar org.eclipse.core.launcher.Main | ||
| 368 : | -application org.eclipsepowered.rcptutorial1.RcpApplication | ||
| 369 : | </pre> | ||
| 370 : | <p>Enter a descriptive name on the next page and then press Finish. Then try | ||
| 371 : | double-clicking on your new shortcut to try it out. You may want to edit the | ||
| 372 : | shortcut (right click on it and select Properties) to change the default working | ||
| 373 : | directory.</p> | ||
| 374 : | <h2>Conclusion</h2> | ||
| 375 : | <p>In part 1 of this tutorial, we looked at what is necessary to create a | ||
| 376 : | bare-bones Rich Client application. The next part will delve into customizations | ||
| 377 : | using the WorkbenchAdvisor class. | ||
| 378 : | All the sample code may be <a href="RcpTutorial.zip">downloaded | ||
| 379 : | here</a>. | ||
| 380 : | </p> | ||
| 381 : | |||
| 382 : | <h2>References</h2> | ||
| 383 : | <p><a href="../rich_client_platform_facilities.html">Rich Client Platform | ||
| 384 : | Facilities</a><br> | ||
| 385 : | <a href="http://bugs.eclipse.org/bugs/show_bug.cgi?id=36967">Bug 36967 - [Plan | ||
| 386 : | Item] Enable Eclipse to be used as a rich client platform</a><br> | ||
| 387 : | <a href="../../examples/rcp/browserExample.zip">Nick Edgar's Browser example | ||
| 388 : | (zip file)</a><br> | ||
| 389 : | <a href="http://www.eclipse.org/articles/Article-PDE-does-plugins/PDE-intro.html">PDE | ||
| 390 : | Does Plug-ins</a><br> | ||
| 391 : | <a href="http://www.eclipse.org/articles/Article-Internationalization/how2I18n.html">How | ||
| 392 : | to Internationalize your Eclipse Plug-in</a><br> | ||
| 393 : | <a href="http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html">Notes | ||
| 394 : | on the Eclipse Plug-in Architecture</a><br> | ||
| 395 : | </p> | ||
| 396 : | <p><small>IBM is trademark of International Business Machines Corporation in the | ||
| 397 : | United States, other countries, or both.</small></p> | ||
| 398 : | <p><small>Java and all Java-based trademarks and logos are trademarks or | ||
| 399 : | registered trademarks of Sun Microsystems, Inc. in the United States, other | ||
| 400 : | countries, or both.</small></p> | ||
| 401 : | <p><small>Microsoft and Windows are trademarks of Microsoft Corporation in the | ||
| 402 : | United States, other countries, or both.</small></p> | ||
| 403 : | <p><small>Other company, product, and service names may be trademarks or service | ||
| 404 : | marks of others.</small></p> | ||
| 405 : | |||
| 406 : | </body> | ||
| 407 : | |||
| 408 : | </html> |
| help@eclipse.org | ViewVC Help |
| Powered by ViewVC 1.0.3 |
