WMSlideshow.Mod 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209
  1. MODULE WMSlideshow;
  2. (****
  3. *
  4. * A simple slideshow/presentation tool with transition effect (see: WMTransitions.Mod).
  5. *
  6. * Represents a Model-View-Controller pattern
  7. * (some controlling parts are also in the view):
  8. * - Controller = SlideshowApp
  9. * - Model = SlideshowData
  10. * - View = SlideshowWindow & SlideshowNavigation
  11. *
  12. * Keyboard and mouse controls:
  13. * - Next: Spacebar/LeftMouseButton/PageDown/RightArrow
  14. * - Previous: PageUp/LeftArrow
  15. * - First: Home/UpArrow
  16. * - Last: End/DownArrow
  17. * - Exit: ESC
  18. * - (Re)Open navigation panel: "n"
  19. * - (Re)Open slide window: "w"
  20. * - Show/Dump internal file list: "l"
  21. *
  22. *
  23. * Usage description:
  24. * Drag & Drop new images on navigator window or use a predefinied XML file.
  25. *
  26. ****)
  27. IMPORT
  28. Codecs, Inputs, Modules, Streams, KernelLog, Files, Commands,
  29. Raster,
  30. Strings,
  31. WMDropTarget, (* Drag&Drop interface *)
  32. WMWindowManager, WMGraphics, WMRectangles,
  33. WMComponents, WMStandardComponents, WMDialogs,
  34. WMTransitions,
  35. XML, XMLObjects, XMLScanner, XMLParser;
  36. CONST
  37. DEBUG= FALSE;
  38. (****
  39. *
  40. * Just some type alias (typedefs)
  41. *
  42. ****)
  43. TYPE String = Strings.String;
  44. TYPE Image = WMGraphics.Image;
  45. TYPE TransitionMask = WMTransitions.TransitionMask;
  46. TYPE TransitionFade = WMTransitions.TransitionFade;
  47. TYPE ObjectArray = POINTER TO ARRAY OF ANY; (* Data Container for List Object *)
  48. (****
  49. *
  50. * A Slide contains:
  51. * - an image filename
  52. * - a transition effect to the next slide (optional)
  53. * - duration of the transition (optional) STILL IN FRAMES -> MS IS BETTER
  54. * - a short description (optional)
  55. *
  56. ****)
  57. TYPE Slide* = OBJECT
  58. VAR
  59. img, trans : String;
  60. dur : LONGINT;
  61. desc : String;
  62. PROCEDURE &New*(img : String; trans : String; dur : LONGINT; desc : String);
  63. BEGIN
  64. SELF.img := img; SELF.trans := trans; SELF.dur := dur; SELF.desc := desc;
  65. END New;
  66. END Slide;
  67. (****
  68. *
  69. * Generic Lockable Object List
  70. *
  71. * Author : TF (-> TFClasses.Mod), with a few modification by Reto Ghioldi
  72. * Purpose : Generic helper (similar to the well known vector class)
  73. * Note : Needed userdefinied initial size of the vector
  74. *
  75. ****)
  76. TYPE List* = OBJECT
  77. VAR
  78. list : ObjectArray;
  79. count : LONGINT;
  80. readLock : LONGINT;
  81. PROCEDURE &New*(size: LONGINT);
  82. BEGIN
  83. NEW(list, size); readLock := 0
  84. END New;
  85. (* ****
  86. *
  87. * Return the number of objects in the list. If count is used for indexing elements
  88. * (e.g. FOR - Loop) in a multi-process situation, the process calling the GetCount method
  89. * should call Lock before GetCount and Unlock after the last use of an index based on GetCount
  90. *
  91. *** *)
  92. PROCEDURE GetCount*():LONGINT;
  93. BEGIN
  94. RETURN count
  95. END GetCount;
  96. PROCEDURE Grow;
  97. VAR
  98. old: ObjectArray; i : LONGINT;
  99. BEGIN
  100. old := list; NEW(list, LEN(list)*2);
  101. FOR i := 0 TO count-1 DO list[i] := old[i] END;
  102. END Grow;
  103. (* ***
  104. *
  105. * Add an object to the list. Add may block if number of calls to Lock is bigger than the number of calls to Unlock
  106. *
  107. *** *)
  108. PROCEDURE Add*(x : ANY);
  109. BEGIN {EXCLUSIVE}
  110. AWAIT(readLock = 0);
  111. IF (count = LEN(list)) THEN Grow END; list[count] := x; INC(count);
  112. END Add;
  113. (* ***
  114. *
  115. * Atomic replace x by y
  116. *
  117. *** *)
  118. PROCEDURE Replace*(x, y : ANY);
  119. VAR
  120. i : LONGINT;
  121. BEGIN {EXCLUSIVE}
  122. AWAIT(readLock = 0);
  123. i := IndexOf(x); IF (i >= 0) THEN list[i] := y END;
  124. END Replace;
  125. (* ***
  126. *
  127. * Return the index of an object. In a multi-process situation, the process calling the IndexOf method
  128. * should call Lock before IndexOf and Unlock after the last use of an index based on IndexOf.
  129. *
  130. * If the object is not found, -1 is returned
  131. *
  132. *** *)
  133. PROCEDURE IndexOf *(x:ANY) : LONGINT;
  134. VAR
  135. i : LONGINT;
  136. BEGIN
  137. i := 0 ;
  138. WHILE (i < count) DO IF (list[i] = x) THEN RETURN i END; INC(i); END;
  139. RETURN -1;
  140. END IndexOf;
  141. (* ***
  142. *
  143. * Remove an object from the list. Remove may block if number of calls to Lock is bigger than the
  144. * number of calls to Unlock
  145. *
  146. *** *)
  147. PROCEDURE Remove*(x : ANY);
  148. VAR i : LONGINT;
  149. BEGIN {EXCLUSIVE}
  150. AWAIT(readLock = 0);
  151. i:=0;
  152. WHILE ( (i<count) & (list[i]#x) ) DO INC(i) END;
  153. IF (i<count) THEN
  154. WHILE (i<count-1) DO list[i]:=list[i+1]; INC(i); END;
  155. DEC(count); list[count]:=NIL
  156. END
  157. END Remove;
  158. PROCEDURE RemoveByIndex*(index : LONGINT);
  159. VAR i : LONGINT;
  160. BEGIN {EXCLUSIVE}
  161. AWAIT(readLock = 0);
  162. i := index;
  163. IF (i >= 0) & (i < count) THEN
  164. WHILE (i<count-1) DO list[i]:=list[i+1]; INC(i); END;
  165. DEC(count); list[count]:=NIL;
  166. END;
  167. END RemoveByIndex;
  168. (* ***
  169. *
  170. * Removes all objects from the list. Clear may block if number of calls to Lock is bigger than the
  171. * number of calls to Unlock
  172. *
  173. *** *)
  174. PROCEDURE Clear*;
  175. VAR i : LONGINT;
  176. BEGIN {EXCLUSIVE}
  177. AWAIT(readLock = 0);
  178. FOR i := 0 TO count - 1 DO list[i] := NIL; END;
  179. count := 0
  180. END Clear;
  181. (* ***
  182. *
  183. * Return an object based on an index. In a multi-process situation, GetItem is only safe in a locked
  184. * region Lock / Unlock
  185. *
  186. *** *)
  187. PROCEDURE GetItem*(i:LONGINT) : ANY;
  188. BEGIN
  189. ASSERT((i >= 0) & (i < count), 101);
  190. RETURN list[i];
  191. END GetItem;
  192. (* ***
  193. *
  194. * Lock prevents modifications to the list. All calls to Lock must be followed by a call to Unlock.
  195. * Lock can be nested.
  196. *
  197. *** *)
  198. PROCEDURE Lock*;
  199. BEGIN {EXCLUSIVE}
  200. INC(readLock);
  201. ASSERT(readLock > 0);
  202. END Lock;
  203. (* ***
  204. *
  205. * Unlock removes one modification lock. All calls to Unlock must be preceeded by a call to Lock.
  206. *
  207. *** *)
  208. PROCEDURE Unlock*;
  209. BEGIN {EXCLUSIVE}
  210. DEC(readLock);
  211. ASSERT(readLock >= 0);
  212. END Unlock;
  213. END List;
  214. (****
  215. *
  216. * The slideshow application
  217. *
  218. ****)
  219. TYPE SlideshowApp= OBJECT
  220. VAR
  221. data : SlideshowData;
  222. win : SlideshowWindow;
  223. nav : SlideshowNavigation;
  224. slideNr : LONGINT;
  225. fullscreen : BOOLEAN;
  226. (*****
  227. *
  228. * Constructor
  229. *
  230. *****)
  231. PROCEDURE &New*(CONST filename : ARRAY OF CHAR);
  232. BEGIN
  233. NEW(data);
  234. (* Load slides via drag & drop *)
  235. IF (filename # "") THEN
  236. data.LoadSlideshow(filename);
  237. END;
  238. IF app = NIL THEN app := SELF END; (* fld, adapt to new semantc of NEW *)
  239. (* Create a application window *)
  240. NEW(win, 320, 240, FALSE, data);
  241. fullscreen := FALSE;
  242. WMWindowManager.DefaultAddWindow(win);
  243. NEW(nav, data);
  244. WMWindowManager.DefaultAddWindow(nav);
  245. slideNr := 0;
  246. END New;
  247. (*****
  248. *
  249. * Handles the navigation inputs from the views
  250. *
  251. *****)
  252. PROCEDURE Next;
  253. BEGIN
  254. IF (data.CountSlides() = 0) THEN RETURN; END;
  255. IF ( slideNr < data.CountSlides() ) THEN
  256. win.Show(slideNr+1);
  257. INC(slideNr);
  258. nav.UpdatePreview();
  259. END;
  260. END Next;
  261. PROCEDURE Previous;
  262. BEGIN
  263. IF (data.CountSlides() = 0) THEN RETURN; END;
  264. slideNr := slideNr-1;
  265. IF (slideNr < 0) THEN slideNr := 0; RETURN; END;
  266. win.Update();
  267. nav.UpdatePreview();
  268. END Previous;
  269. PROCEDURE First;
  270. BEGIN
  271. IF (data.CountSlides() = 0) THEN RETURN; END;
  272. slideNr := 0;
  273. win.Update();
  274. nav.UpdatePreview();
  275. END First;
  276. PROCEDURE Last;
  277. BEGIN
  278. IF (data.CountSlides() = 0) THEN RETURN; END;
  279. slideNr := data.CountSlides()-1;
  280. IF (slideNr< 0) THEN slideNr := 0; END;
  281. win.Update();
  282. nav.UpdatePreview();
  283. END Last;
  284. (*****
  285. *
  286. * Handles the important keyboard events from the views
  287. *
  288. *****)
  289. PROCEDURE ToggleFullscreen;
  290. VAR
  291. view : WMWindowManager.ViewPort;
  292. manager : WMWindowManager.WindowManager;
  293. w, h : LONGINT;
  294. BEGIN
  295. IF (win = NIL) THEN RETURN; END;
  296. fullscreen := ~fullscreen;
  297. manager := WMWindowManager.GetDefaultManager();
  298. view := WMWindowManager.GetDefaultView();
  299. IF (fullscreen) THEN
  300. w := ENTIER(view.range.r - view.range.l);
  301. h := ENTIER(view.range.b - view.range.t);
  302. manager.SetWindowSize(win, w, h);
  303. manager.SetWindowPos(win, ENTIER(view.range.l), ENTIER(view.range.t));
  304. win.Resized(w, h);
  305. win.Invalidate( WMRectangles.MakeRect(0, 0, w, h) );
  306. ELSE
  307. w := win.img.width; h := win.img.height;
  308. manager.SetWindowSize(win, w, h);
  309. manager.SetWindowPos(win, ENTIER(view.range.l)+50, ENTIER(view.range.t)+50);
  310. win.Resized(w, h);
  311. win.Invalidate( WMRectangles.MakeRect(0, 0, w, h) );
  312. END;
  313. END ToggleFullscreen;
  314. (*****
  315. *
  316. * Outputs the internal filelist of the slideshow
  317. *
  318. *****)
  319. PROCEDURE ShowFileList;
  320. VAR
  321. dummy : ARRAY 2048 OF CHAR;
  322. nl : ARRAY 2 OF CHAR;
  323. slide : Slide;
  324. i : LONGINT;
  325. BEGIN
  326. nl[0] := 0DX; nl[1] := 0X;
  327. dummy[0] := 0X;
  328. FOR i := 0 TO data.CountSlides()-1 DO
  329. slide := data.GetSlide(i);
  330. Strings.Append(dummy, slide.img^);
  331. Strings.Append(dummy, nl);
  332. END;
  333. WMDialogs.Information("Slideshow file list", dummy); (* don't care for user click *)
  334. END ShowFileList;
  335. (*****
  336. *
  337. * Display a exit confirmation dialog
  338. *
  339. *****)
  340. PROCEDURE ExitDialog;
  341. BEGIN
  342. IF (WMDialogs.Confirmation("Exit Slideshow?", "You pressed ESC. Do you really want to exit the slideshow?") = WMDialogs.ResOk) THEN
  343. Cleanup();
  344. END;
  345. END ExitDialog;
  346. (*****
  347. *
  348. * Remove current slide
  349. *
  350. *****)
  351. PROCEDURE RemoveCurrentSlide;
  352. VAR
  353. isLast : BOOLEAN;
  354. BEGIN
  355. IF (DEBUG) THEN KernelLog.String("Remove slide nr."); KernelLog.Int(slideNr, 0); KernelLog.Ln; END;
  356. isLast := slideNr = data.CountSlides()-1;
  357. data.RemoveSlide(slideNr);
  358. IF (~isLast) THEN
  359. IF (data.CountSlides() > 0) THEN
  360. nav.UpdatePreview();
  361. win.Update();
  362. ELSE
  363. END;
  364. ELSE
  365. IF (DEBUG) THEN KernelLog.String("# of remaining slides is "); KernelLog.Int(data.CountSlides(), 0); KernelLog.Ln; END;
  366. IF (data.CountSlides() > 0) THEN
  367. DEC(slideNr);
  368. win.Update();
  369. ELSE
  370. (* there was just one slide left *)
  371. IF (DEBUG) THEN
  372. KernelLog.String("All slides deleted!"); KernelLog.Ln;
  373. END;
  374. slideNr := 0;
  375. win.Close();
  376. data.ClearSlides();
  377. NEW(win, 320, 240, FALSE, data);
  378. WMWindowManager.DefaultAddWindow(win);
  379. END;
  380. END;
  381. END RemoveCurrentSlide;
  382. (*****
  383. *
  384. * Destructor
  385. *
  386. *****)
  387. PROCEDURE Close;
  388. BEGIN
  389. (* close WM stuff *)
  390. win.Close();
  391. nav.Close();
  392. END Close;
  393. END SlideshowApp;
  394. (****
  395. *
  396. * The slideshow application
  397. *
  398. ****)
  399. TYPE SlideshowNavigation = OBJECT(WMComponents.FormWindow);
  400. VAR
  401. data : SlideshowData;
  402. imageP : WMStandardComponents.ImagePanel;
  403. prevLen : LONGINT;
  404. PROCEDURE &New*(data : SlideshowData);
  405. VAR
  406. panel, nav: WMStandardComponents.Panel;
  407. button : WMStandardComponents.Button;
  408. manager : WMWindowManager.WindowManager;
  409. windowStyle : WMWindowManager.WindowStyle;
  410. BEGIN
  411. SELF.data := data;
  412. prevLen := 180;
  413. Init(prevLen, prevLen+20, FALSE);
  414. manager := WMWindowManager.GetDefaultManager();
  415. windowStyle := manager.GetStyle();
  416. NEW(panel);
  417. panel.bounds.SetExtents(prevLen, prevLen+20);
  418. panel.fillColor.Set(0000000H);
  419. panel.takesFocus.Set(TRUE);
  420. NEW(imageP);
  421. imageP.bounds.SetExtents(prevLen, prevLen);
  422. imageP.alignment.Set(WMComponents.AlignTop);
  423. NEW(nav);
  424. nav.bounds.SetExtents(prevLen, 20);
  425. nav.fillColor.Set(LONGINT(0AAAAAAAAH));
  426. nav.takesFocus.Set(TRUE);
  427. nav.alignment.Set(WMComponents.AlignTop);
  428. NEW(button);
  429. button.caption.SetAOC("|<");
  430. button.alignment.Set(WMComponents.AlignLeft);
  431. button.onClick.Add(ButtonHandlerFirst);
  432. button.bounds.SetWidth(40); button.bounds.SetHeight(20);
  433. nav.AddContent(button);
  434. NEW(button);
  435. button.caption.SetAOC("Previous");
  436. button.alignment.Set(WMComponents.AlignLeft);
  437. button.onClick.Add(ButtonHandlerPrevious);
  438. button.bounds.SetWidth(50); button.bounds.SetHeight(20);
  439. nav.AddContent(button);
  440. NEW(button);
  441. button.caption.SetAOC("Next");
  442. button.alignment.Set(WMComponents.AlignLeft);
  443. button.onClick.Add(ButtonHandlerNext);
  444. button.bounds.SetWidth(50); button.bounds.SetHeight(20);
  445. nav.AddContent(button);
  446. NEW(button);
  447. button.caption.SetAOC(">|");
  448. button.alignment.Set(WMComponents.AlignLeft);
  449. button.onClick.Add(ButtonHandlerLast);
  450. button.bounds.SetWidth(40); button.bounds.SetHeight(20);
  451. nav.AddContent(button);
  452. panel.AddContent(nav);
  453. panel.AddContent(imageP);
  454. SetContent(panel);
  455. SetTitle( Strings.NewString("Slideshow Navigation") );
  456. IF (data.CountSlides() > 0) THEN
  457. UpdatePreview();
  458. END;
  459. END New;
  460. PROCEDURE UpdatePreview;
  461. VAR
  462. nextSlide : Slide;
  463. nextIndex : LONGINT;
  464. image : Image;
  465. fact : REAL;
  466. c : WMGraphics.BufferCanvas;
  467. w, h : LONGINT;
  468. BEGIN
  469. (* End? -> indiacted with a white preview panel *)
  470. IF (app.slideNr >= data.CountSlides()-1) THEN
  471. imageP.SetImage(SELF, NIL);
  472. ELSE
  473. (* load next image and scale slide to correct size *)
  474. nextIndex := app.slideNr+1;
  475. nextSlide := data.GetSlide(nextIndex);
  476. image := LoadImage(nextSlide.img^, Raster.BGR565);
  477. WHILE (image = NIL) & (nextIndex < data.CountSlides()) DO
  478. IF (DEBUG) THEN KernelLog.String("Error in UpdatePreview(): Remove invalid image "); KernelLog.String(nextSlide.img^); KernelLog.String("."); KernelLog.Ln; END;
  479. data.RemoveSlide(nextIndex);
  480. IF (nextIndex < data.CountSlides()) THEN
  481. nextSlide := data.GetSlide(nextIndex);
  482. image := LoadImage(nextSlide.img^, Raster.BGR565);
  483. END;
  484. END;
  485. IF (image = NIL) THEN
  486. imageP.SetImage(SELF, NIL);
  487. ELSE
  488. NEW(c, image);
  489. IF (image.width > prevLen) OR (image.height > prevLen) THEN
  490. IF (image.width >= image.height) THEN
  491. fact := image.width / prevLen;
  492. ELSE
  493. fact := image.height / prevLen;
  494. END;
  495. c.ScaleImage(image, WMRectangles.MakeRect(0, 0, image.width, image.height),
  496. WMRectangles.MakeRect(0, 0, ENTIER(image.width/fact), ENTIER(image.height/fact)), WMGraphics.ModeCopy, WMGraphics.ScaleBilinear);
  497. image.width := ENTIER(image.width/fact);
  498. image.height := ENTIER(image.height/fact);
  499. END;
  500. w := image.width; h := image.height + 20; (* Buttons = 20px *)
  501. imageP.SetImage(SELF, image);
  502. END;
  503. END;
  504. (* correct window width & height *)
  505. manager := WMWindowManager.GetDefaultManager();
  506. w := MAX(w, 180);
  507. manager.SetWindowSize(SELF, w, h);
  508. Resized(w, h);
  509. Invalidate( WMRectangles.MakeRect(0, 0, w, h) );
  510. END UpdatePreview;
  511. PROCEDURE ButtonHandlerNext(sender, data: ANY);
  512. BEGIN
  513. app.Next();
  514. END ButtonHandlerNext;
  515. PROCEDURE ButtonHandlerPrevious(sender, data: ANY);
  516. BEGIN
  517. app.Previous();
  518. END ButtonHandlerPrevious;
  519. PROCEDURE ButtonHandlerFirst(sender, data: ANY);
  520. BEGIN
  521. app.First();
  522. END ButtonHandlerFirst;
  523. PROCEDURE ButtonHandlerLast(sender, data: ANY);
  524. BEGIN
  525. app.Last();
  526. END ButtonHandlerLast;
  527. PROCEDURE KeyEvent*(ucs : LONGINT; flags : SET; keysym : LONGINT);
  528. BEGIN
  529. IF Inputs.Release IN flags THEN RETURN; END;
  530. IF ucs = ORD("f") THEN
  531. app.ToggleFullscreen();
  532. RETURN;
  533. ELSIF ucs = ORD("w") THEN
  534. app.win.Close();
  535. NEW(app.win, 320, 240, FALSE, data);
  536. WMWindowManager.DefaultAddWindow(app.win);
  537. RETURN;
  538. ELSIF ucs = ORD("l") THEN
  539. app.ShowFileList();
  540. RETURN;
  541. END;
  542. IF (keysym = 0FF51H) THEN (* Cursor Left *)
  543. app.Previous();
  544. ELSIF (keysym = 0FF53H) THEN (* Cursor Right *)
  545. app.Next();
  546. ELSIF (keysym = 0FF54H) THEN (* Cursor Down *)
  547. app.Last();
  548. ELSIF (keysym = 0FF52H) THEN (* Cursor Up *)
  549. app.First();
  550. ELSIF (keysym = 0FF56H) THEN (* Page Down *)
  551. app.Next();
  552. ELSIF (keysym = 0FF55H) THEN (* Page Up *)
  553. app.Previous();
  554. ELSIF (keysym = 0FF50H) THEN (* Cursor Home *)
  555. app.First();
  556. ELSIF (keysym = 0FF57H) THEN (* Cursor End *)
  557. app.Last();
  558. ELSIF (keysym = 00020H) THEN (* Spacebar *)
  559. app.Next();
  560. ELSIF (keysym = 0FF1BH) THEN (* ESC = 65307*)
  561. app.ExitDialog();
  562. ELSIF (keysym = 0FFFFH) THEN (* DEL = 65535*)
  563. app.RemoveCurrentSlide();
  564. ELSE
  565. IF (DEBUG) THEN KernelLog.String("unknown keysym= "); KernelLog.Int(keysym, 0); KernelLog.Ln; END;
  566. END;
  567. END KeyEvent;
  568. (** Dropped is called via the message handler to indicate an item has been dropped. *)
  569. PROCEDURE DragDropped*(x, y: LONGINT; dragInfo : WMWindowManager.DragInfo);
  570. VAR
  571. dropTarget : URLDropTarget;
  572. BEGIN
  573. KernelLog.Ln; (* fix to begin with new line later on *)
  574. NEW(dropTarget);
  575. dragInfo.data := dropTarget;
  576. ConfirmDrag(TRUE, dragInfo)
  577. END DragDropped;
  578. END SlideshowNavigation;
  579. (****
  580. *
  581. * When drag & dropping files/URLs into window (build slideshow on the fly, without transition!)
  582. *
  583. ****)
  584. TYPE URLDropTarget* = OBJECT(WMDropTarget.DropTarget);
  585. PROCEDURE GetInterface*(type : LONGINT) : WMDropTarget.DropInterface;
  586. VAR di : DropURL;
  587. BEGIN
  588. IF (type = WMDropTarget.TypeURL) THEN
  589. NEW(di);
  590. RETURN di;
  591. ELSE
  592. RETURN NIL;
  593. END
  594. END GetInterface;
  595. END URLDropTarget;
  596. TYPE DropURL* = OBJECT(WMDropTarget.DropURLs)
  597. PROCEDURE URL*(CONST url : ARRAY OF CHAR; VAR res : WORD);
  598. BEGIN
  599. (* handle dropped files -> build up SlideshowData on-the-fly (not via XML file) *)
  600. KernelLog.String("Dropped new URL: "); KernelLog.String(url); KernelLog.Ln;
  601. IF (app # NIL) THEN
  602. app.data.AddSlide(url);
  603. IF (app.data.CountSlides() = 1) THEN
  604. (* Load first slide *)
  605.  app.win.Update();
  606. ELSE
  607. app.nav.UpdatePreview();
  608. END;
  609. res := 0
  610. ELSE
  611. res := -1;
  612. END;
  613. END URL;
  614. END DropURL;
  615. (****
  616. *
  617. * The slideshow application
  618. *
  619. ****)
  620. TYPE SlideshowWindow = OBJECT(WMWindowManager.DoubleBufferWindow);
  621. VAR
  622. data: SlideshowData;
  623. PROCEDURE &New*( width, height : LONGINT; alpha : BOOLEAN; data : SlideshowData);
  624. BEGIN
  625. Init(width, height, alpha);
  626. SetTitle( Strings.NewString("Bluebottle Slideshow (ETHZ, 2005)") );
  627. SELF.data := data;
  628. IF (data.CountSlides() = 0) THEN RETURN; END;
  629. (* Load first slide *)
  630. Update();
  631. END New;
  632. PROCEDURE PointerDown*(x, y : LONGINT; keys : SET);
  633. BEGIN
  634. IF (0 IN keys) THEN
  635. (* Go to next Slide *)
  636. app.Next();
  637. END;
  638. END PointerDown;
  639. PROCEDURE KeyEvent*(ucs : LONGINT; flags : SET; keysym : LONGINT);
  640. BEGIN
  641. IF Inputs.Release IN flags THEN RETURN; END;
  642. IF ucs = ORD("f") THEN
  643. app.ToggleFullscreen();
  644. RETURN;
  645. ELSIF ucs = ORD("n") THEN
  646. app.nav.Close();
  647. NEW(app.nav, data);
  648. WMWindowManager.DefaultAddWindow(app.nav);
  649. RETURN;
  650. ELSIF ucs = ORD("l") THEN
  651. app.ShowFileList();
  652. RETURN;
  653. END;
  654. IF (keysym = 0FF51H) THEN (* Cursor Left *)
  655. app.Previous();
  656. ELSIF (keysym = 0FF53H) THEN (* Cursor Right *)
  657. app.Next();
  658. ELSIF (keysym = 0FF54H) THEN (* Cursor Down *)
  659. app.Last();
  660. ELSIF (keysym = 0FF52H) THEN (* Cursor Up *)
  661. app.First();
  662. ELSIF (keysym = 0FF56H) THEN (* Page Down *)
  663. app.Next();
  664. ELSIF (keysym = 0FF55H) THEN (* Page Up *)
  665. app.Previous();
  666. ELSIF (keysym = 0FF50H) THEN (* Cursor Home *)
  667. app.First();
  668. ELSIF (keysym = 0FF57H) THEN (* Cursor End *)
  669. app.Last();
  670. ELSIF (keysym = 00020H) THEN (* Spacebar *)
  671. app.Next();
  672. ELSIF (keysym = 0FF1BH) THEN (* ESC = 65307*)
  673. app.ExitDialog();
  674. ELSIF (keysym = 0FFFFH) THEN (* DEL = 65535*)
  675. app.RemoveCurrentSlide();
  676. ELSE
  677. IF (DEBUG) THEN KernelLog.String("unknown keysym= "); KernelLog.Int(keysym, 0); KernelLog.Ln; END;
  678. END;
  679. END KeyEvent;
  680. (*
  681. PROCEDURE Jump(slideNr : LONGINT);
  682. VAR s : Slide;
  683. w, h : LONGINT;
  684. BEGIN
  685. (* Load image *)
  686.  s := data.GetSlide(slideNr);
  687. img := LoadImage(s.img^, Raster.BGR565);
  688. manager := WMWindowManager.GetDefaultManager();
  689. w := img.width; h := img.height;
  690. manager.SetWindowSize(SELF, w, h);
  691. Resized(w, h);
  692. Invalidate( WMRectangles.MakeRect(0, 0, w, h) );
  693. END Jump;
  694. *)
  695. PROCEDURE Show(nextSlideNr : LONGINT );
  696. VAR
  697. current, next : Slide;
  698. src, dest : Image;
  699. maskFile : String;
  700. BEGIN
  701. (* At least two slides are needed *)
  702. IF (data.CountSlides() < 2) THEN RETURN; END;
  703. (* End? *)
  704. IF (nextSlideNr > data.CountSlides()-1) THEN RETURN; END;
  705. (* Advance to the next for transition rendering *)
  706. current := data.GetSlide(app.slideNr);
  707. next := data.GetSlide(nextSlideNr);
  708. src := LoadImage(current.img^, Raster.BGR565);
  709. dest := LoadImage(next.img^, Raster.BGR565);
  710. IF (dest = NIL) THEN
  711. IF (DEBUG) THEN KernelLog.String("Error: Invalid image - no decoder found for "); KernelLog.String(next.img^); KernelLog.Ln; END;
  712. data.RemoveSlide(nextSlideNr);
  713. Update();
  714. RETURN;
  715. END;
  716. IF (src = NIL) OR (dest = NIL) THEN HALT(99); END;
  717. (*
  718. 1) Mask
  719. 2) Fade
  720. 3) None
  721. *)
  722. IF (current.trans^ = "") THEN
  723. ShowNone(dest);
  724. ELSIF (Strings.Match("mask:*", current.trans^)) THEN
  725. maskFile := Strings.NewString(current.trans^);
  726. Strings.Delete(maskFile^, 0, 5);
  727. ShowMask(src, dest, maskFile^, current.dur);
  728. ELSIF (Strings.Match("fade", current.trans^)) THEN
  729. ShowFade(src, dest, current.dur);
  730. ELSE
  731. KernelLog.String("Invalid transition. Use 'mask:[URL]', 'fade' or '' (empty) in XML file!"); KernelLog.Ln;
  732. HALT(99);
  733. END;
  734. END Show;
  735. PROCEDURE ShowMask(current, next : Image; CONST mask: ARRAY OF CHAR; len : LONGINT);
  736. VAR
  737. tm : TransitionMask;
  738. i, step: LONGINT;
  739. w, h : LONGINT;
  740. BEGIN
  741. IF (DEBUG) THEN KernelLog.String("Mask transition: "); KernelLog.String(mask); KernelLog.Ln; END;
  742. w := current.width; h := current.height;
  743. i := 0;
  744. step := 256 DIV len;
  745. NEW(tm);
  746. tm.Init(w, h);
  747. tm.SetMask(WMGraphics.LoadImage(mask, TRUE));
  748. WHILE (i < 256) DO
  749. tm.CalcImage(next, current, img, i);
  750. Invalidate(WMRectangles.MakeRect(0, 0, w, h));
  751. i := i + step;
  752. END;
  753. IF (i # 255) THEN
  754. img := next;
  755. Invalidate(WMRectangles.MakeRect(0, 0, w, h));
  756. END;
  757. END ShowMask;
  758. PROCEDURE ShowFade(current, next : Image; len : LONGINT);
  759. VAR
  760. tf : TransitionFade;
  761. i,step : LONGINT;
  762. w, h : LONGINT;
  763. BEGIN
  764. IF (DEBUG) THEN KernelLog.String("Fade transition"); KernelLog.Ln; END;
  765. w := current.width; h := current.height;
  766. i := 0;
  767. step := 256 DIV len;
  768. NEW(tf);
  769. tf.Init(w, h);
  770. WHILE (i < 256) DO
  771. tf.CalcImage(current, next, img, i);
  772. Invalidate(WMRectangles.MakeRect(0, 0, w, h));
  773. i := i + step;
  774. END;
  775. IF (i #255) THEN
  776. img := next;
  777. Invalidate(WMRectangles.MakeRect(0, 0, w, h));
  778. END;
  779. END ShowFade;
  780. PROCEDURE ShowNone(next : Image);
  781. BEGIN
  782. img := next;
  783. Invalidate(WMRectangles.MakeRect(0, 0, next.width, next.height));
  784. END ShowNone;
  785. PROCEDURE Update;
  786. VAR s : Slide;
  787. w, h : LONGINT;
  788. manager : WMWindowManager.WindowManager;
  789. img: Image;
  790. BEGIN
  791. (* Load current slide *)
  792. IF (app.slideNr > data.CountSlides()-1) THEN RETURN; END;
  793.  s := data.GetSlide(app.slideNr);
  794. img := LoadImage(s.img^, Raster.BGR565);
  795. WHILE (img = NIL) DO
  796. IF (DEBUG) THEN KernelLog.String("Error: Invalid image - no decoder found for "); KernelLog.String(s.img^); KernelLog.Ln; END;
  797. data.RemoveSlide(app.slideNr);
  798. IF (app.slideNr < data.CountSlides()-1) THEN
  799.  s := data.GetSlide(app.slideNr);
  800. img := LoadImage(s.img^, Raster.BGR565);
  801. ELSIF ( (data.CountSlides() > 0) & (app.slideNr > 0) ) THEN
  802. DEC(app.slideNr);
  803.  s := data.GetSlide(app.slideNr);
  804. img := LoadImage(s.img^, Raster.BGR565);
  805. ELSE
  806. (* no more slides -> can't display one :-) *)
  807. IF (DEBUG) THEN KernelLog.String("Error: No more images in slideshow. Add new ones by dropping URLs in navigation window."); KernelLog.Ln; END;
  808. RETURN;
  809. END;
  810. END;
  811. SELF.img := img;
  812. manager := WMWindowManager.GetDefaultManager();
  813. w := img.width; h := img.height;
  814. manager.SetWindowSize(SELF, w, h);
  815. Resized(w, h);
  816. Invalidate( WMRectangles.MakeRect(0, 0, w, h) );
  817. IF (app.nav # NIL) THEN
  818. app.nav.UpdatePreview();
  819. END;
  820. END Update;
  821. END SlideshowWindow;
  822. TYPE SlideshowData= OBJECT
  823. VAR
  824. slides : List;
  825. hasErrors : BOOLEAN; (* XML Parsing *)
  826. PROCEDURE &New*;
  827. BEGIN
  828. NEW(slides, 50);
  829. IF (DEBUG) THEN KernelLog.String("All slides have been loaded!"); KernelLog.Ln; END;
  830. END New;
  831. PROCEDURE GetSlide(i : LONGINT) : Slide;
  832. VAR
  833. p : ANY; s : Slide;
  834. BEGIN
  835.  p := slides.GetItem(i);
  836. IF (p = NIL) THEN
  837. IF (DEBUG) THEN KernelLog.String("Slide nr. "); KernelLog.Int(i, 0); KernelLog.String(" doesn't exist!"); KernelLog.Ln; END;
  838. RETURN NIL;
  839. END;
  840. s := p(Slide); RETURN s;
  841. END GetSlide;
  842. PROCEDURE CountSlides() : LONGINT;
  843. BEGIN
  844. RETURN slides.GetCount();
  845. END CountSlides;
  846. PROCEDURE LoadSlideshow(CONST name : ARRAY OF CHAR);
  847. VAR
  848. f : Files.File;
  849. scanner : XMLScanner.Scanner;
  850. parser : XMLParser.Parser;
  851. reader : Files.Reader;
  852. doc : XML.Document;
  853. BEGIN {EXCLUSIVE}
  854. hasErrors := FALSE;
  855. f := Files.Old(name);
  856. IF (f = NIL) THEN
  857. IF (DEBUG) THEN KernelLog.String("Couldn't open "); KernelLog.String(name); KernelLog.String(". Slideshow NOT loaded."); KernelLog.Ln; END;
  858. HALT (99);
  859. END;
  860. (* Build up XML parser structure *)
  861. NEW(reader, f, 0);
  862. NEW(scanner, reader); scanner.reportError := ErrorReport;
  863. NEW(parser, scanner); parser.reportError := ErrorReport;
  864. (* Parse the XML file (without DTD/Schema checking) *)
  865. doc := parser.Parse();
  866. (* Check for parser errors *)
  867. IF (hasErrors) THEN
  868. IF (DEBUG) THEN KernelLog.String("Slideshow "); KernelLog.String(name); KernelLog.String("NOT ok."); KernelLog.Ln; END;
  869. HALT (99);
  870. END;
  871. IF (LoadSlides(doc)) THEN
  872. IF (DEBUG) THEN KernelLog.String("Slideshow "); KernelLog.String(name); KernelLog.String(" loaded."); KernelLog.Ln; END;
  873. ELSE
  874. IF (DEBUG) THEN KernelLog.String("Slideshow "); KernelLog.String(name); KernelLog.String(" NOT loaded."); KernelLog.Ln; END;
  875. HALT (99);
  876. END;
  877. END LoadSlideshow;
  878. PROCEDURE LoadSlides(doc: XML.Document) : BOOLEAN;
  879. VAR
  880. enum: XMLObjects.Enumerator;
  881. e, root: XML.Element;
  882. p: ANY;
  883. s, imgStr, transStr, durStr, descStr : String;
  884. dur : LONGINT;
  885. slide : Slide;
  886. BEGIN
  887. IF (doc = NIL) THEN
  888. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): doc = NIL"); END;
  889. RETURN FALSE;
  890. END;
  891. root := doc.GetRoot();
  892. IF (root = NIL) THEN
  893. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): root = NIL"); END;
  894. RETURN FALSE;
  895. END;
  896. enum := root.GetContents();
  897. WHILE ( enum.HasMoreElements() ) DO
  898. p := enum.GetNext();
  899. IF ~(p IS XML.Element) THEN
  900. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): p # XML.Element"); END;
  901. RETURN FALSE;
  902. END;
  903. (* Try to read 'Slide' element *)
  904. e := p(XML.Element);
  905. s := e.GetName();
  906. IF (s = NIL) OR (s^ # "Slide") THEN
  907. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): s = NIL OR s # 'Slide'"); END;
  908. RETURN FALSE;
  909. END;
  910. (*
  911. (* 0. try to read 'key' attribut -> not yet used!!! *)
  912. s := e.GetAttributeValue("key");
  913. IF (s = NIL) THEN
  914. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): s(key) = NIL"); END;
  915. RETURN FALSE;
  916. END;
  917. Strings.StrToInt(s^, i);
  918. IF (i<=0) & (i>WMTrans.duration) THEN KernelLog.String("Error: wrong index in XML"); RETURN FALSE; END;
  919. *)
  920. (* **
  921. *
  922. * WARNING: Values NOT yet zero terminated!!! Bug in XML Parser?!?
  923. * ==> create a new String with Strings.NewString()
  924. *
  925. ** *)
  926. (** 1. try to read 'imgage' attribut **)
  927. s := e.GetAttributeValue("image");
  928. IF (s = NIL) THEN
  929. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): s(image) = NIL"); END;
  930. RETURN FALSE;
  931. END;
  932. imgStr := Strings.NewString(s^);
  933. IF ( (imgStr = NIL) OR (imgStr^ = "") ) THEN
  934. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): filename = NIL OR empty"); END;
  935. RETURN FALSE;
  936. END;
  937. (** 2. try to read 'transition' attribut **)
  938. s := e.GetAttributeValue("transition");
  939. IF (s = NIL) THEN
  940. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): s(transition) = NIL"); END;
  941. RETURN FALSE;
  942. END;
  943. transStr := Strings.NewString(s^);
  944. IF (transStr = NIL) THEN
  945. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): transition = NIL"); END;
  946. RETURN FALSE;
  947. END;
  948. (** 3. try to read 'duration' attribut **)
  949. s := e.GetAttributeValue("duration");
  950. IF (s = NIL) THEN
  951. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): s(duration) = NIL"); END;
  952. RETURN FALSE;
  953. END;
  954. durStr := Strings.NewString(s^);
  955. Strings.StrToInt(durStr^, dur);
  956. (** 4. try to read 'description' attribut **)
  957. s := e.GetAttributeValue("description");
  958. IF (s = NIL) THEN
  959. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): s(description) = NIL"); END;
  960. RETURN FALSE;
  961. END;
  962. descStr := Strings.NewString(s^);
  963. IF (descStr = NIL) THEN
  964. IF (DEBUG) THEN KernelLog.String("Error in LoadSlides(): description = NIL"); END;
  965. RETURN FALSE;
  966. END;
  967. (** create slide entry and add it to list **)
  968. IF (DEBUG) THEN
  969. KernelLog.String("Loading Slide (image="); KernelLog.String(imgStr^); KernelLog.String(", transition="); KernelLog.String(transStr^); KernelLog.String(")."); KernelLog.Ln;
  970. END;
  971. NEW(slide, imgStr, transStr, dur, descStr);
  972. slides.Add(slide);
  973. END; (* while loop *)
  974. IF (slides.GetCount() = 0) THEN
  975. IF (DEBUG) THEN KernelLog.String("Slideshow "); KernelLog.String(" NOT loaded (empty file)."); KernelLog.Ln; END;
  976. RETURN FALSE;
  977. ELSE
  978. RETURN TRUE;
  979. END;
  980. END LoadSlides;
  981. (*****
  982. *
  983. * XML slideshow file reading stuff
  984. *
  985. *****)
  986. PROCEDURE ErrorReport(pos, line, row: LONGINT; CONST msg: ARRAY OF CHAR);
  987. BEGIN
  988. KernelLog.String("Parse error at pos "); KernelLog.Int(pos, 5); KernelLog.String(" in line "); KernelLog.Int(line, 5);
  989. KernelLog.String(" row "); KernelLog.Int(row, 5); KernelLog.String(" - "); KernelLog.String(msg); KernelLog.Ln;
  990. hasErrors := TRUE
  991. END ErrorReport;
  992. (*****
  993. *
  994. * Add a slide on the fly (uses a short fade transition)
  995. *
  996. *****)
  997. PROCEDURE AddSlide(CONST filename : ARRAY OF CHAR);
  998. VAR
  999. slide : Slide;
  1000. BEGIN
  1001. NEW(slide, Strings.NewString(filename), Strings.NewString("fade"), 15, Strings.NewString(filename));
  1002. slides.Add(slide);
  1003. END AddSlide;
  1004. (*****
  1005. *
  1006. * Remove a slide on the fly (if it has been detected as invalid image format)
  1007. *
  1008. *****)
  1009. PROCEDURE RemoveSlide(i : LONGINT);
  1010. BEGIN
  1011. slides.RemoveByIndex(i);
  1012. END RemoveSlide;
  1013. (*****
  1014. *
  1015. * Clears everything
  1016. *
  1017. *****)
  1018. PROCEDURE ClearSlides;
  1019. BEGIN
  1020. slides.Clear();
  1021. END ClearSlides;
  1022. END SlideshowData;
  1023. (****
  1024. *
  1025. * Global variables
  1026. *
  1027. ****)
  1028. VAR
  1029. app : SlideshowApp; (* using the singleton pattern *)
  1030. (****
  1031. *
  1032. * Global functions
  1033. *
  1034. ****)
  1035. PROCEDURE Open*(context : Commands.Context);
  1036. VAR dstring : ARRAY 256 OF CHAR;
  1037. BEGIN {EXCLUSIVE}
  1038. IF (app # NIL) THEN
  1039. app.Close();
  1040. END;
  1041. context.arg.SkipWhitespace; context.arg.String(dstring);
  1042. NEW(app, dstring);
  1043. END Open;
  1044. PROCEDURE Cleanup;
  1045. BEGIN
  1046. IF (app # NIL) THEN app.Close(); END
  1047. END Cleanup;
  1048. (****
  1049. *
  1050. * Load Image in given Format as WM class, Image is NOT SHAREABLE although it has a key!
  1051. *
  1052. * NOTE: With the "Raster.Image" you will have many type troubles with WM Framework
  1053. *
  1054. ****)
  1055. PROCEDURE LoadImage(CONST name : ARRAY OF CHAR; fmt : Raster.Format): Image;
  1056. VAR img : Image;
  1057. res: WORD; w, h, x : LONGINT;
  1058. decoder : Codecs.ImageDecoder;
  1059. in : Streams.Reader;
  1060. ext : ARRAY 16 OF CHAR;
  1061. BEGIN
  1062. IF (name = "") THEN RETURN NIL END;
  1063. GetExtension(name, ext);
  1064. Strings.UpperCase(ext);
  1065. decoder := Codecs.GetImageDecoder(ext);
  1066. IF (decoder = NIL) THEN
  1067. KernelLog.String("No decoder found for "); KernelLog.String(ext); KernelLog.Ln;
  1068. RETURN NIL;
  1069. END;
  1070. in := Codecs.OpenInputStream(name);
  1071. IF (in # NIL) THEN
  1072. decoder.Open(in, res);
  1073. IF (res = 0) THEN
  1074. decoder.GetImageInfo(w, h, x, x);
  1075. NEW(img);
  1076. Raster.Create(img, w, h, fmt);
  1077. decoder.Render(img);
  1078. NEW(img.key, LEN(name)); COPY(name, img.key^);
  1079. END;
  1080. END;
  1081. RETURN img;
  1082. END LoadImage;
  1083. (*****
  1084. *
  1085. * Procedure to split filename in the name and the extension
  1086. *
  1087. *****)
  1088. PROCEDURE GetExtension (CONST name: ARRAY OF CHAR; VAR ext: ARRAY OF CHAR);
  1089. VAR
  1090. i, j: LONGINT;
  1091. ch: CHAR;
  1092. BEGIN
  1093. i := 0; j := 0;
  1094. WHILE (name[i] # 0X) DO
  1095. IF (name[i] = ".") THEN j := i+1 END;
  1096. INC(i)
  1097. END;
  1098. i := 0;
  1099. REPEAT
  1100. ch := name[j]; ext[i] := ch; INC(i); INC(j)
  1101. UNTIL (ch = 0X) OR (i = LEN(ext));
  1102. ext[i-1] := 0X
  1103. END GetExtension;
  1104. BEGIN
  1105. Modules.InstallTermHandler(Cleanup)
  1106. END WMSlideshow.
  1107. (* Testing commands *)
  1108. System.Free WMSlideshow WMTransFade WMTransMask WMTrans ~
  1109. System.Free WMSlideshow~
  1110. PC.Compile RetoWMTrans.Mod RetoWMTransMask.Mod RetoWMTransFade.Mod RetoWMSlideshow.Mod~
  1111. WMSlideshow.Open ~
  1112. WMSlideshow.Open RetoWMSlideshow.XML~