TweetFollow Us on Twitter

Dylan Application
Volume Number:10
Issue Number:9
Column Tag:Apple Technology First Look

Writing An Application In Dylan

Looking ahead to a richer programming experience

By Larisa Matejic, Brown University

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

Conceived and developed in Cambridge, Massachusetts, Dylan holds the promise of a new breed of development environment - dynamic, interactive, object oriented, and aimed at the professional application developer. As of this writing, the Dylan team continues to prepare Dylan for release. We’re pleased to be able to bring you this first look at developing an application in Dylan. Our author has worked with Dylan over the past year or so as an intern with Apple’s Cambridge group, and walks us through using Dylan to construct a QuickTime movie player. While we’ll bring you more articles on things like language specifics and the highly-interactive nature of the development environment later, this article should help you get a feel for putting together an application with Dylan - Ed stb

Dylan is one of the most versatile languages I have encountered. Apple Dylan has a number of features that make it easy and natural to use. Apple Dylan includes a development environment which greatly simplifies programming and debugging. Dylan’s expressive, yet simple syntax lends itself to efficient application development.

In this article, we’ll look at the process of writing a movie player application to explore Dylan’s unique features. We’ll focus on different properties of the Dylan language, as well as Apple Dylan’s cross-language calling mechanism.

The movie player application we’ll build mimics the QuickTime Movie Player application provided in the QuickTime developer’s kit.

QuickTime API

To use QuickTime, we’ll need to import QuickTime function and type definitions into Dylan. In fact, we’ll need to do the same for any Toolbox function or type we use in writing our application.

Dylan makes it easy to interface with C code - simply specify the .h file name and list the names of the functions and types you want to use. Dylan takes care of the rest. Here’s an example of how to import Toolbox function headers and types:


/* 1 */
define interface
  #include "Quickdraw.h",
     import:  {"Picture", "PicHandle",
               "DrawPicture", "KillPicture",
               "CGrafPort", "UnionRect", "GDHandle",
               "GWorldPtr"
              }
end interface;

We can call any of the C functions we import in the scope of our application as if they were written in Dylan.

Dylan Framework API

Dylan Framework provides useful class definitions for writing applications; it consists mainly of Dylan derivatives of Toolbox functionality. Among other things, it handles events, pick correlation (a fancy buzzword), view, and window operations. It originated with MacApp, but is cleaner and written entirely in Dylan.

The next thing is to get QuickTime and the Framework together. For that, we’ll define classes to deal with QuickTime movies. First, a bit about how classes work in Dylan.

Class Definitions

As in other object-oriented languages, each instance of a Dylan class stores information about its state in slots. A slot, equivalent to a data-member or field in other object-oriented languages, is a place to keep data, and each instance gets its own storage (i.e. set of slots). In addition to having its own slots, a class inherits all the slots of its superclasses.

Slots in Dylan are accessed by invoking methods using a simple syntax. For instance, to get the value stored in slot v of some object point, we write: point.v.

We’ll define a class called <movie-view>, which will provide a view onto the QuickTime movie. Note: it’s a convention in Dylan to surround class names by angle brackets.

Our class <movie-view> inherits from the Framework’s <view>. Among other things, it handles mouse events in the view. In other words, <movie-view> is a subclass of <view>, and we’ll add the data and methods we need to work with a QuickTime movie. We include slots particular to <movie-view> in the class definition; it inherits other slots from <view>.


/* 2 */
define class <movie-view> (<view>)
 slot movie :: <Movie>,
 init-value: as(<Movie>, 0),
 init-keyword: movie:;  
 slot show-mc?,
 init-value: #t,
 init-keyword: show-mc?:;
 slot enable-editing?,
 init-value: #t,
 init-keyword: enable-editing?:;
 slot is-playing?,
 init-value: #f;
 slot loop?,
 init-value: #f;
 slot mc :: <MovieController>,
 init-value: as(<MovieController>, 0);
end class;

From the class definition above, we see that the slot movie is of type <Movie>, imported from QuickTime through the define interface. Its initial value is a null pointer, or, more accurately, it is the integer 0 coerced into type <Movie> by the as operator. To specify which movie to associate with a particular <movie-view>, we’ll use the init-keyword at instantiation time. In this case, the init-keyword is movie:.

The next slot in <movie-view> is a boolean called show-mc?, whose value indicates whether to show QuickTime’s movie controller in the <movie-view>. Similarly, the boolean slot enable-editing? indicates whether the movie is read-only.

The next two slots can’t be initialized by use of a keyword by the creator of an instance of <movie-view>. That’s because we chose to not include an init-keyword in their slot definitions. Rather, their values are determined in the initialize routine of the instance of <movie-view>.

The slot is-playing? is used to keep track of when the movie is playing when the movie controller is not present to do that for us.

The slot mc is of the type <MovieController>, a type also imported from QuickTime. This is where we store a pointer to the movie controller object. The movie controller provides an interface to the movie, making it possible to play, stop and edit the movie.

With the QuickTime Movie Player, a movie is associated with a window that contains it, that window’s controls, and the file the movie is stored in. We need to define a class that will provide that window and the file. The Dylan Framework class <document> provides a mechanism for exactly that. We’ll define a subclass of <document> called <movie-document> in the following way:


/* 3 */
define class <movie-document> (<document>)
 slot movie :: <Movie>, 
 init-value: as(<Movie>, 0);
 slot movie-view :: <movie-view>;
end class;

In addition to inheriting all the slots of <document>, <movie-document> has a slot for storing an instance of <movie-view> and a slot for storing a pointer to the movie. The slot movie stores a copy of the pointer to the QuickTime movie already stored in <movie-view>. Having the slot movie present in <movie-document> is a matter of convenience and can be omitted if desired.

Now that we have defined our main classes, we can address how to define methods for these classes to provide the functionality want in this application.

Our app’s File and Edit menus will be standard. We’ll also have a Movie menu with items like Set Movie Poster, Show Movie Poster and Loop. We’ll focus our discussion on implementing the functionality of the items in the File menu, mainly Opening and Closing a movie. The code at the end of the article shows how we implemented the functionality of the rest of the menu items.

We aren’t going to show how to create and add menu items to a menu here. What we will discuss is the functionality needed for the menu callbacks, i.e. the functions that are called as a result of a menu item being selected.

Methods in Dylan

Dylan methods are grouped by name into collections called generic functions. Dylan supports polymorphism through generic functions and method dispatch. When you call a generic function, Dylan determines which method to invoke based on the type of arguments passed to it. This process is called method dispatch.

A method is said to be specialized when it is defined to operate on instances of a specific class. That is, a method is specialized on the type of its parameters. In addition to providing type information to the compiler, specializers make code easier for humans to understand. During method dispatch, a generic function compares all specializers with the arguments it receives to determine which method to invoke. The method whose parameters most closely match the argument types gets invoked.

You don’t need to modify class definitions to modify an object’s behavior. Method definitions in Dylan are separate from the classes they use for specialization. This makes for more flexible code organization. Given that you may not even be able to get to the code where a class is defined, the most effective approach then seems to be adding a method specialized on a specific class. For instance, Dylan lets you add a method on the class <integer> without changing the class itself or affecting other applications.

Next we’ll discuss some of the functions of our movie player app, and Dylan’s modularity will become more apparent. We’ll also show how simple it is to use pre-defined classes and augment them to get the behavior you want.

Opening a Movie File

When the user selects Open from the File menu and chooses a QuickTime movie, we’ll make an instance of <movie-document>, and store the file pointer in the main-file slot.

Since <movie-document> is a subclass of <document>, we can specialize the methods for <document> to incorporate our movie specific functionality.

The initialize method for <document> calls the method open-files, which opens a QuickTime movie file:


/* 4 */
define method open-files (document :: <movie-document>)
 open-movie-file(document.main-file);
end method;

define method open-movie-file (file :: <file>)
  with-stack-structure
 (spec(<FSSpec>),
   begin
        set-fs-spec(file, spec);
         let (err, ref-num) = 
            OpenMovieFile(spec, file.data-permission);
         fail-os-error(err);
         file.data-ref-num := ref-num;
       end);
   end method;

To get the movie data out of the file, we specialize read-from-file, a method of <document>, that is eventually called by initialize.


/* 5 */
define method read-from-file (document :: <movie-document>,
                              file :: <file>) 
  let (err, new-movie,resId) = 
       NewMovieFromFile( file.data-ref-num, "",  0);
  ignore(resId);
  fail-os-error(err);
  document.movie := new-movie;
  close-files(document); 
end method;

Next, in instantiating <document>, initialize calls the method make-windows to open the document in a window on screen.


/* 6 */
define method make-windows (document :: <movie-document>)
 let movie-bounds =
       with-stack-structure(box(<qd-rect>),
                            begin
                              GetMovieBox(document.movie,box);
                              get-rect(box);
                            end);
   let movie-size = point(movie-bounds.width, movie-bounds.height);
  
   document.movie-view := make(<movie-view>, 
                              location: $zero-point, 
                              size: movie-size);
  
   let window = make(<window>,
                        location: point(100, 100),
                        size: movie-size,
                        next-handler: document,
                        target-view: document.movie-view,
                        has-go-away: #t);
  
   add-sub-view(window.root-view, document.movie-view);
   open(window);
  
   set-movie-to-view(document.movie-view, document.movie);  
   make-document-title(document);
end method;

In make-windows we get the movie’s size by calling the QuickTime function GetMovieBox. The Dylan wrapper surrounding the function call itself sets up a temporary local instance of <qd-rect> (a Dylan derivative of a QuickDraw rectangle), fills it with the values returned by GetMovieBox, and removes it from the stack. Then we create an instance of <movie-view> of the same size, followed by an instance of <window> with the same dimensions, and we designate our instance of <movie-view> to be the target view of the window. Then we add our <movie-view> instance to the window’s list of subviews. After opening the window, we call the method set-movie-to-view.


/* 7 */
define method set-movie-to-view (view :: <movie-view>,
                                 new-movie :: <Movie>)
  unless (new-movie = view.movie)
    unless (nil?(view.movie))
      unless(nil?(view.mc))
        DisposeMovieController(view.mc);
      end unless;
      DisposeMovie(view.movie);   
    end unless;
    
    view.movie := new-movie;
    
    with-stack-structure
 (view-rect(<qd-rect>),
        begin
        set-rect(view-rect, view.local-bounds);
        let movie-window = get-window(view);
            let movie-port= as(<CGrafPort>, 
 movie-window.window-ptr);
            SetMovieGWorld(view.movie, movie-port,
                           as(<GDHandle>,0));
            SetMovieBox(view.movie, view-rect);
            if (view.show-mc?)
            view.mc := as(<MovieController>,
                             NewMovieController(view.movie,
 view-rect, $mcTopLeftMovie));
               unless (nil?(view.mc))
                 MCSetControllerBoundsRect(view.mc, view-rect);
                 MCSetControllerPort(view.mc, movie-port);
                 draw(view, view.local-bounds);
                 *controller-list* := add!(*controller-list*,
                                           view.mc);
                 if (view.enable-editing?)
                   MCEnableEditing(view.mc, #t);
                 end if;
               end unless;
            end if;
 end);
  end unless;
end method;

In set-movie-to-view, we use the QuickTime function SetMovieGWorld using the GWorld of the movie’s window (we instantiated it in make-windows). We also call SetMovieBox to let QuickTime know how big a window the movie needs to be displayed in.

The second half of the set-movie-to-view method instantiates a movie controller if the show-mc? slot of view (our instance of <movie-view>) is true. Then we call MCSetControllerBoundsRect with the size of our movie. We next set the GrafPort of the controller to match that of the movie and the window. The draw method draws the current frame (in this case, the first frame) of the movie and the controller on the screen. The variable *controller-list* is global and stores pointers to all movie controllers that are visible at a given time.

In Dylan, it is a convention to begin and end global variable names with asterisks.

In order to include controller events in our event loop, we need to keep a global list of controller pointers. Finally, if editing is enabled, we invoke the MCEnableEditing function.

Closing a Movie File

close-files is a generic function defined on <document>, which we specialize in the following way:


/* 8 */
define method close-files (document :: <movie-document>)
 close-movie-file(document.main-file);
end method;
 
define method close-movie-file (file :: <file>)
 unless (~(file.data-ref-num))
 CloseMovieFile(file.data-ref-num);
 file.data-ref-num := 0;
 end unless;
end method;

Playing a movie

There are two ways for the user to invoke movie playback. If the movie controller is visible, then it handles all the playback functionality. Otherwise, a double-click in the movie region on screen starts playback, a single click pauses it. This is one of the reasons why we subclassed <view> when we defined <movie-view>. <view> is equipped with methods to handle mouse events, which can easily be specialized to add the behavior we want (see the code listing for these).

Now we’ll discuss how to get the movie controller to take care of the playback functionality. We already mentioned that we keep a global list, *controller-list*, of all visible movie controllers that we update as each controller is created or disposed. A movie can have at most one controller, but we can have a number of movies open at once, so we can have more than a single item in *controller-list*.

Next, we create an event filter so a movie controller can be aware of user events. It will filter out events relevant to the movie controller and act upon them.


/* 9 */
define method controller-event-hook (event-rec :: <EventRecord>)
 block(exit)
 for (mc in *controller-list*)
 if (MCIsPlayerEvent(mc, event-rec) ~= 0)
 exit(#t);
 end if;
 end for;
 #f;
 end block;
end method;

The method defined here is called from the event loop each time an event occurs and before it is handled. What our event-hook does is call MCIsPlayer on each movie controller in the list. It informs the event handler that the event was handled if a controller responds with something other than 0. Otherwise the event record is passed back to the event loop to be handled somewhere else. MCIsPlayerEvent is a QuickTime function which examines the event record and handles the event if it determines that the event should be handled by the movie controller. MCIsPlayerEvent returns a 0 if it didn’t handle the event.

Summary

Dylan has a number of features which make it a great language and environment:

• straightforward syntax

• easy access to functions coded in other languages

• design modularity through specialization, inheritance, etc

• a good framework to build on

Although this look at building Dylan applications leaves a lot of territory unexplored, reading the following code may help build your understanding further. There’ll be much more to come in the months ahead.

The Dylan environment lends itself to interactive browsing of code, so it’s unusual to see Dylan code flattened like this, but we’ve got to do something to get it into the magazine.


/* 10 */
// Source container: toolbox-interface.dylan
// Interface to C files
// Files.h
define interface
  #include "Files.h",
      import: {"fsWrPerm"};
end interface;
// Quickdraw.h
define interface
  #include "Quickdraw.h",
      import:  { "CGrafPort",  "GDHandle"},
     
     type: {"CQDProcsPtr" => <machine-pointer>};
  
  end interface;
// Movies.h
define interface
  // import everything from Movies.h, and just a few necessary types from 
other files.
  #include "TextEdit.h",
     define: {"SystemSevenOrLater" => 1},
     import: {"ScrpSTElement"};
  
  #include "Components.h",
     define: {"SystemSevenOrLater" => 1},
     import: {"Component", "ComponentInstance"};
  
  #include "ImageCompression.h",
     import: {"MatrixRecord"};
  
  #include "Movies.h",
define: {"SystemSevenOrLater" => 1};
                                   
  function "PreflightMovie", output-argument: outFlags;
  function "GetMovieGWorld", output-argument: port, 
 output-argument: gdh;
  function "GetMoviePreviewTime", output-argument: previewTime,
 output-argument: previewDuration;
  function "GetMovieSelection", output-argument: selectionTime,
 output-argument: selectionDuration;
  function "GetMovieActiveSegment", output-argument: startTime, 
 output-argument: duration;
  function "GetTrackDimensions", output-argument: width, 
 output-argument: height;
  function "GetMediaHandlerDescription", output-argument: mediaType, 

 output-argument: creatorManufacturer;
  
  function "SampleNumToMediaTime", output-argument: sampleTime, 
 output-argument: sampleDuration;
  function "MediaTimeToSampleNum", output-argument: sampleNum, 
 output-argument: sampleTime,
     output-argument: sampleDuration;
  function "AddMediaSample", output-argument: sampleTime;
  function "AddMediaSampleReference", output-argument: sampleTime;
  function "GetMediaSample", output-argument: size, 
 output-argument: sampleTime, 
     output-argument: durationPerSample,
 output-argument: sampleDescriptionIndex, 
     output-argument: numberOfSamples, output-argument: sampleFlags;
  function "GetMediaSampleReference", 
     output-argument: dataOffset, output-argument: size, 
 output-argument: sampleTime, 
     output-argument: durationPerSample, 
 output-argument: sampleDescriptionIndex, 
     output-argument: numberOfSamples, output-argument: sampleFlags;
  function "GetMediaShadowSync", output-argument: syncSampleNum;
  function "ConvertFileToMovieFile", output-argument: resID;
  function "ConvertMovieToFile", output-argument: resID;
  function "GetTrackDimensions", output-argument: height;
  function "GetMediaHandlerDescription", output-argument: mediaType, 

 output-argument: creatorManufacturer;
  function "GetMediaDataHandlerDescription", 
 output-argument: dhType, output-argument: creatorManufacturer;
  function "SampleNumToMediaTime", output-argument: sampleTime, 
 output-argument: sampleDuration;
  function "MediaTimeToSampleNum", output-argument: *sampleNum, 
 output-argument: sampleTime, output-argument: sampleDuration;
  function "AddMediaSample", output-argument: sampleTime;
  function "AddMediaSampleReference", output-argument: sampleTime;
  function "GetMediaSample", output-argument: size, 
 output-argument: sampleTime, 
     output-argument: durationPerSample, 
 output-argument: sampleDescriptionIndex, 
 output-argument: numberOfSamples,
     output-argument: sampleFlags;
  function "GetMediaSampleReference", output-argument: dataOffset, 
 output-argument: size, 
     output-argument: sampleTime, 
 output-argument: durationPerSample, 
 output-argument: sampleDescriptionIndex,
     output-argument: numberOfSamples, output-argument: sampleFlags;
  function "GetMediaShadowSync", output-argument: syncSampleNum;
  function "ConvertFileToMovieFile", output-argument: resID;
  function "ConvertMovieToFile", output-argument: resID;
  function "GetMediaNextInterestingTime", 
 output-argument: interestingTime, 
 output-argument: interestingDuration;
  function "GetTrackNextInterestingTime", 
 output-argument: interestingTime, 
 output-argument: interestingDuration;
  function "GetMovieNextInterestingTime", 
 output-argument: whichMediaTypes, 
 output-argument: interestingTime,
     output-argument: interestingDuration;
  function "CreateMovieFile", output-argument: resRefNum, 
 output-argument: newmovie;
  function "OpenMovieFile", output-argument: resRefNum;
  function "NewMovieFromFile", output-argument: theMovie, 
 output-argument: resId, output-argument: dataRefWasChanged;
  function "NewMovieFromHandle", output-argument: theMovie, 
 output-argument: dataRefWasChanged;
  function "NewMovieFromDataFork", output-argument: theMovie, 
 output-argument: dataRefWasChanged;
  function "AddMovieResource", output-argument: resId;
  function "GetMovieDefaultDataRef", output-argument: dataRef, 
 output-argument: dataRefType;
  function "FlattenMovie", output-argument: resId;
  function "GetVideoMediaGraphicsMode", 
 output-argument: graphicsMode;
  function "GetSoundMediaBalance", output-argument: balance;
  function "AddTextSample", output-argument: sampleTime;
  function "AddTESample", output-argument: sampleTime;
  function "AddHiliteSample", output-argument: sampleTime;
  function "FindNextText", output-argument: foundTime, 
 output-argument: foundDuration, output-argument: offset;
  function "GetMovieCoverProcs", output-argument: uncoverProc, 
 output-argument: coverProc, output-argument: refcon;
  function "GetMovieStatus", output-argument: firstProblemTrack;
  function "GetMediaDataRef", output-argument: dataRef, 
 output-argument: dataRefType, 
 output-argument: dataRefAttributes;
  function "AddMediaDataRef", output-argument: index;
  function "GetMediaDataRefCount", output-argument: count;
  function "MCGetCurrentTime", output-argument: scale;
  function "MCGetControllerInfo", output-argument: someFlags;
  function "MCGetClip", output-argument: theClip, 
 output-argument: movieClip;
  function "MCDrawBadge", output-argument: badgeRgn;
end interface;
Events.h
define interface
  #include "Events.h",
     import: {"GetMouse",  "TickCount"};
end interface;

Menus.h


/* 11 */
define interface
  #include "Menus.h",
     import: {"MenuHandle"};
end interface;

// source container:  movie-utils.dylan
define variable *controller-list* = #();
define variable *edit-menu* = #();

// this is the event hook called from the event loop to filter out movie 
controller events
define constant controller-event-hook =
     method (event-rec :: <EventRecord>)
       block(exit)
         for (mc in *controller-list*)
           if(MCIsPlayerEvent(mc, event-rec) ~= 0)
             exit(#t);
           end if;
         end for;
         #f;
       end block; 
end method;

// this must be called to initialize QuickTime
define constant init-quicktime =
     method ()
       fail-os-error(EnterMovies());
       *controller-list* := #();
       *event-hooks* := add(*event-hooks*, controller-event-hook); 
     end method;

// recommended to call this upon exiting QuickTime
define constant exit-quicktime =
     method()
       ExitMovies();
     end method;

// opens a movie file
define method open-movie-file (file :: <file>)
  with-stack-structure(spec(<FSSpec>),
                  begin
                    set-fs-spec(file, spec);
                    let (err, ref-num) = 
                      OpenMovieFile(spec, file.data-permission);
                    fail-os-error(err);
                    file.data-ref-num := ref-num;
                  end);
   end method;

// closes a movie file
define method close-movie-file (file :: <file>)
  unless (~(file.data-ref-num))
    CloseMovieFile(file.data-ref-num);
    file.data-ref-num := 0;
  end unless;
end method;

// source container: movie-view.dylan
define class <movie-view> (<view>)
  slot movie :: <Movie>,
     init-value: as(<Movie>, 0),
     init-keyword: movie:;
  slot show-mc?,
     init-value: #t,
     init-keyword: show-mc?:;
  slot enable-editing?,
     init-value: #t,
     init-keyword: enable-editing?:;
  slot is-playing?,
     init-value: #f;
  slot loop?,
     init-value: #f;
  slot mc :: <MovieController>,
     init-value: as(<MovieController>, 0);
end class;

// called as each instance of <movie-view> is instantiated
define method initialize (view :: <movie-view>, #key)
  next-method();
  
  unless (nil?(view.movie))
    set-movie-to-view(view, view.movie);
  end unless;
  
end method;

// when a movie is closed, we need to clean up the relevant structures
define method dispose (view :: <movie-view>)
  
  stop-playing(view);
  
  unless (nil?(view.movie))
    unless(nil?(view.mc))
      *controller-list* := remove!(*controller-list*,view.mc);
      DisposeMovieController(as(<ComponentInstance>,view.mc));
      view.mc := as(<MovieController>, 0);
    end unless;
    DisposeMovie(view.movie);
    view.movie := as(<Movie>, 0);
  end unless;
  
  next-method();
end method;

// this informs the movie about the size of the window it will play inside
// of on screen and intializes the GWorld of the movie to be the current 
one
define method size-setter(view :: <movie-view>, new-size :: <point>)
  next-method();
  
  unless (nil?(view.movie))
    with-stack-structure(r(<qd-rect>),
               begin
                 focus(view);                                        
         // Set the movie to play in our view
                 set-rect(r, view.local-bounds);
                 SetMovieBox(view.movie, r);
                 SetMovieGWorld(view.movie,as(<CGrafPort>, 0),
                             as(<GDHandle>,0));
               end);
  end unless;
end method;

define method start-playing (view :: <movie-view>)
  unless (nil?(view.movie))
    if (view.is-playing?)
      stop-playing(view);
    end if;
    
    GoToBeginningOfMovie(view.movie);
    
    //  Start the movie playing
      StartMovie(view.movie);
    
    //  Mark that we are playing
      view.is-playing? := #t;
    end unless;
  end method;

define method stop-playing (view :: <movie-view>)
  unless (nil?(view.movie) | ~ view.is-playing?)
      StopMovie(view.movie);
      view.is-playing? := #f;
  end unless;
end method;

// this is called in response to an update event (the engine that calls 
it is 
// embedded within the Framework
define method draw (view :: <movie-view>,
                    draw-rect :: <rect>)
  ignore(draw-rect);
 
  unless (nil?(view.movie))
    SetMovieActive(view.movie, #t);  
    UpdateMovie(view.movie);
    MoviesTask(view.movie, $doTheRightThing);
  end unless;
  
  unless (~view.show-mc? | nil?(view.mc))
    MCDraw(view.mc, get-window(view).window-ptr); 
  end unless;

end method;

//sets up a view onto the movie in the window that is to contain the 
movie
define method set-movie-to-view (view :: <movie-view>,
                                 new-movie :: <Movie>)
  
  unless (new-movie = view.movie)
    unless (nil?(view.movie))
      unless(nil?(view.mc))
        DisposeMovieController(view.mc);
      end unless;
      DisposeMovie(view.movie);   
    end unless;
    
    view.movie := new-movie;
    
    with-stack-structure(view-rect(<qd-rect>),
         begin
           set-rect(view-rect, view.local-bounds);
              
           let movie-window = get-window(view);
           let movie-port = as(<CGrafPort>, 
                               movie-window.window-ptr);
                           
           SetMovieGWorld(view.movie, movie-port,
                          as(<GDHandle>,0));
           SetMovieBox(view.movie, view-rect);
                          
           if (view.show-mc?)
             view.mc := as(<MovieController>,
                   NewMovieController(view.movie, 
                                      view-rect,
                                      $mcTopLeftMovie));
             unless (nil?(view.mc))
               MCSetControllerBoundsRect(view.mc, view-rect);
               MCSetControllerPort(view.mc, movie-port);
               draw(view, view.local-bounds);
               *controller-list* := add!(*controller-list*,
                                         view.mc);
               if (view.enable-editing?)
                 MCEnableEditing(view.mc, #t);
               end if;
             end unless;
           end if;
         end);
  end unless;
end method;

// called while waiting for the events to occur (during idle time)
define method do-idle (view :: <movie-view>)
  
  if (view.mc & view.show-mc)
    // hack to have null events sent to the event loop during idle time
    //  next version of the Framework will take care of this 
    with-stack-structure(event-record(<EventRecord>),
              begin
                event-record.EventRecord$what := 0;
                GetMouse(event-record.EventRecord$where);
                event-record.EventRecord$when := TickCount();
                event-record.EventRecord$message := 0;
                event-record.EventRecord$modifiers := #x80;
                MCIsPlayerEvent(view.mc, event-record);
              end);
  else
    
    if (view.is-playing?)
      MoviesTask(view.movie, $doTheRightThing);
      if (IsMovieDone(view.movie))
        stop-playing(view);
      end if;
      
    end if;
  end if;
end method;

// The  following methods are menu callbacks that suport editing 
// functionality of the movie controller

define method do-event (view :: <movie-view>,
                 event :: <menu-event>,
                 id == #"clear")
  
  ignore(event, id);
  unless(nil?(view.mc))
    MCClear(view.mc);
  end unless;
end method;

define method do-event (view :: <movie-view>,
                 event :: <menu-event>,
                 id == #"undo")
  
  ignore(event, id);
  unless(nil?(view.mc))
    MCUndo(view.mc);
  end unless;
end method;

define method do-event (view :: <movie-view>,
                 event :: <menu-event>,
                 id == #"copy")
  
  ignore(event, id);
  unless(nil?(view.mc))
    let selection = MCCopy(view.mc);
    unless(nil?(selection))
      PutMovieOnScrap(selection, $movieScrapOnlyPutMovie);
      DisposeMovie(selection);
         end unless;
  end unless;
end method;


define method do-event (view :: <movie-view>,
                 event :: <menu-event>,
                 id == #"cut")
  
  ignore(event, id);
  unless(nil?(view.mc))
    let selection = MCCut(view.mc);
    unless(nil?(selection))
      PutMovieOnScrap(selection, $movieScrapOnlyPutMovie);
      DisposeMovie(selection);
    end unless;
  end unless;
end method;

define method do-event (view :: <movie-view>,
                 event :: <menu-event>,
                 id == #"paste")
  ignore(event, id);
  unless(nil?(view.mc) | ~IsScrapMovie(as(<Track>,0)))
    MCPaste(view.mc, as(<Movie>,0));
  end unless;
  end method;

// this method highlights the menus correctly
define method do-setup-menus (view :: <movie-view>)
  next-method();
  
  unless(nil?(view.mc) | ~view.enable-editing? 
 | ~view.show-mc | nil?(view.movie)) 
    enable-item(#"copy");
    MCSetUpEditMenu(view.mc, $doTheRightThing,        
                  as(<MenuHandle>,*edit-menu*.toolbox-menu));
  end unless;       
end method;

define method do-event (view :: <movie-view>, 
                        event :: <generic-mouse-down-event>, 
                        id :: <keyword>)
  // when controller not present: double-click to play, single-click 
to stop
  ignore(id);
  if (~view.show-mc | nil?(view.mc))
    if (double-click? (event))
      if (IsMovieDone(view.movie))
        GoToBeginningOfMovie(view.movie);
      end if;
      start-playing(view);
    else
      stop-playing(view);
      next-method();
    end if;
  else
    next-method();
  end if;
end method; 

// source container: movie-document.dylan
define class <movie-document> (<document>)
  slot movie :: <Movie>, 
 init-value: as(<Movie>, 0);
  slot movie-view :: <movie-view>;
end class;

define method main-file-type (document :: <movie-document>)
  ignore(document);
  $MovieFileType;
end method;

//opens up a window containing a view onto the movie
define method make-windows (document :: <movie-document>)
  let movie-bounds =
       with-stack-structure(box(<qd-rect>),
                          begin
                            GetMovieBox(document.movie, box);
                            get-rect(box);
                          end);
  let movie-size = point(movie-bounds.width, 
                         movie-bounds.height);
  
  document.movie-view := make(<movie-view>, 
                              location: $zero-point, 
                              size: movie-size);
  let window = make(<window>,
                    location: point(100, 100),
                    size: movie-size,
                    next-handler: document,
                    target-view: document.movie-view,
                    has-go-away: #t);
  
  add-sub-view(window.root-view, document.movie-view);
  open(window);
  set-movie-to-view(document.movie-view, document.movie);  
  make-document-title(document);
end method;

define method open-files (document :: <movie-document>)
  open-movie-file(document.main-file);
end method;

define method close-files (document :: <movie-document>)
  close-movie-file(document.main-file);
end method;

// gets the movie data in correctly from a file
define method read-from-file (document :: <movie-document>,
                              file :: <file>) 
  let (err, new-movie,resId) 
 = NewMovieFromFile( file.data-ref-num, "",  0);
  ignore(resId);
  fail-os-error(err);
  document.movie := new-movie;
  close-files(document); 
  
end method;

// highlights the menu items in the Movie menu
define method do-setup-menus (document :: <movie-document>)
  next-method();

  enable-item(#"show-poster");
  enable-item(#"set-poster");
end method;

// menu callback for Show Poster menu item - goes to the poster frame
define method do-event (document :: <movie-document>,
                        event :: <menu-event>,
                        id == #"show-poster")
  ignore(event, id);
  
  unless (nil?(document.movie))  
    let movie = document.movie;
    let poster-time = GetMoviePosterTime(movie);
    let movie-time = GetMovieTime(movie, as(<TimeRecord>, 0));
    
    unless (poster-time = movie-time)
      StopMovie(movie);
      SetMovieTimeValue(movie, poster-time);
    end unless;
    
  end unless;
end method;

// menu callback for Set Poster menu item - sets movie poster to be the 
current frame
define method do-event (document :: <movie-document>,
                        event :: <menu-event>,
                        id == #"set-poster")
  ignore(event, id);
  
  unless (nil?(document.movie))  
    let movie = document.movie;
    let new-poster-time = GetMovieTime(movie, as(<TimeRecord>, 0));
    
    SetMoviePosterTime(movie, new-poster-time);
  end unless;
  
end method;

// Source container: movie-player.dylan
define class <movie-player-behavior> (<behavior>)
end class;

define method behavior-setup-menus (behavior :: <movie-player-behavior>, 

                              next :: <list>,
                              main-handler :: <main-handler>)
  ignore(behavior, next, main-handler);
  next-method();
end method;

// callback for Quit menu item
define method behavior-event (behavior :: <movie-player-behavior>, 
                              next :: <list>, 
                              main-handler :: <main-handler>,
                              event :: <menu-event>, 
                              id == #"quit")
  ignore(behavior, next, main-handler, event, id);
  exit-quicktime();
  next-method();
end method;    

define method init-movie ()

// initializes the Framework and QuickTime
  init-framework();
  init-quicktime();
  
 // Set up the menus.
  make(<menu>,
       title: "File",
       install: #t,
       items: list(make(<menu-item>,
                        title: "New",
                        identifier: #"new",
                        command-key: 'N'),
                   make(<menu-item>,
                        title: "Open ",
                        identifier: #"open",
                        command-key: 'O'),
                   make(<separator-item>),
                   make(<menu-item>,
                        title: "Save",
                        identifier: #"save",
                        command-key: 'S'),
                   make(<menu-item>,
                        title: "Save As ",
                        identifier: #"save-as"),
                   make(<separator-item>),
                   make(<menu-item>,
                        title: "Close",
                        identifier: #"close",
                        command-key: 'W'),
                   make(<separator-item>),
                   make(<menu-item>,
                        title: "Quit",
                        identifier: #"quit",
                        command-key: 'Q')));
  *edit-menu* := make-edit-menu(install: #t);

  make(<menu>,
       title: "Movie",
       install: #t,
       items: list( 
                   make(<menu-item>,
                        title: "Go To Poster Frame",
                        identifier: #"show-poster"),
                   make(<menu-item>,
                        title: "Set Poster Frame",
                        identifier: #"set-poster")
                        ));

  add-behavior(*main-handler*, make(<movie-player-behavior>));
  add-document-type(*main-handler*, $MovieFileType, <movie-document>);
  
  #t;
end method;

// this method is called to actually run the application 
define method run-movie ()
  init-movie();
  run();
end method;







  
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

coconutBattery 3.9.14 - Displays info ab...
With coconutBattery you're always aware of your current battery health. It shows you live information about your battery such as how often it was charged and how is the current maximum capacity in... Read more
Keynote 13.2 - Apple's presentation...
Easily create gorgeous presentations with the all-new Keynote, featuring powerful yet easy-to-use tools and dazzling effects that will make you a very hard act to follow. The Theme Chooser lets you... Read more
Apple Pages 13.2 - Apple's word pro...
Apple Pages is a powerful word processor that gives you everything you need to create documents that look beautiful. And read beautifully. It lets you work seamlessly between Mac and iOS devices, and... Read more
Numbers 13.2 - Apple's spreadsheet...
With Apple Numbers, sophisticated spreadsheets are just the start. The whole sheet is your canvas. Just add dramatic interactive charts, tables, and images that paint a revealing picture of your data... Read more
Ableton Live 11.3.11 - Record music usin...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Affinity Photo 2.2.0 - Digital editing f...
Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more
SpamSieve 3.0 - Robust spam filter for m...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
WhatsApp 2.2338.12 - Desktop client for...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
Fantastical 3.8.2 - Create calendar even...
Fantastical is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event details... Read more
iShowU Instant 1.4.14 - Full-featured sc...
iShowU Instant gives you real-time screen recording like you've never seen before! It is the fastest, most feature-filled real-time screen capture tool from shinywhitebox yet. All of the features you... Read more

Latest Forum Discussions

See All

The iPhone 15 Episode – The TouchArcade...
After a 3 week hiatus The TouchArcade Show returns with another action-packed episode! Well, maybe not so much “action-packed" as it is “packed with talk about the iPhone 15 Pro". Eli, being in a time zone 3 hours ahead of me, as well as being smart... | Read more »
TouchArcade Game of the Week: ‘DERE Veng...
Developer Appsir Games have been putting out genre-defying titles on mobile (and other platforms) for a number of years now, and this week marks the release of their magnum opus DERE Vengeance which has been many years in the making. In fact, if the... | Read more »
SwitchArcade Round-Up: Reviews Featuring...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for September 22nd, 2023. I’ve had a good night’s sleep, and though my body aches down to the last bit of sinew and meat, I’m at least thinking straight again. We’ve got a lot to look at... | Read more »
TGS 2023: Level-5 Celebrates 25 Years Wi...
Back when I first started covering the Tokyo Game Show for TouchArcade, prolific RPG producer Level-5 could always be counted on for a fairly big booth with a blend of mobile and console games on offer. At recent shows, the company’s presence has... | Read more »
TGS 2023: ‘Final Fantasy’ & ‘Dragon...
Square Enix usually has one of the bigger, more attention-grabbing booths at the Tokyo Game Show, and this year was no different in that sense. The line-ups to play pretty much anything there were among the lengthiest of the show, and there were... | Read more »
Valve Says To Not Expect a Faster Steam...
With the big 20% off discount for the Steam Deck available to celebrate Steam’s 20th anniversary, Valve had a good presence at TGS 2023 with interviews and more. | Read more »
‘Honkai Impact 3rd Part 2’ Revealed at T...
At TGS 2023, HoYoverse had a big presence with new trailers for the usual suspects, but I didn’t expect a big announcement for Honkai Impact 3rd (Free). | Read more »
‘Junkworld’ Is Out Now As This Week’s Ne...
Epic post-apocalyptic tower-defense experience Junkworld () from Ironhide Games is out now on Apple Arcade worldwide. We’ve been covering it for a while now, and even through its soft launches before, but it has returned as an Apple Arcade... | Read more »
Motorsport legends NASCAR announce an up...
NASCAR often gets a bad reputation outside of America, but there is a certain charm to it with its close side-by-side action and its focus on pure speed, but it never managed to really massively break out internationally. Now, there's a chance... | Read more »
Skullgirls Mobile Version 6.0 Update Rel...
I’ve been covering Marie’s upcoming release from Hidden Variable in Skullgirls Mobile (Free) for a while now across the announcement, gameplay | Read more »

Price Scanner via MacPrices.net

New low price: 13″ M2 MacBook Pro for $1049,...
Amazon has the Space Gray 13″ MacBook Pro with an Apple M2 CPU and 256GB of storage in stock and on sale today for $250 off MSRP. Their price is the lowest we’ve seen for this configuration from any... Read more
Apple AirPods 2 with USB-C now in stock and o...
Amazon has Apple’s 2023 AirPods Pro with USB-C now in stock and on sale for $199.99 including free shipping. Their price is $50 off MSRP, and it’s currently the lowest price available for new AirPods... Read more
New low prices: Apple’s 15″ M2 MacBook Airs w...
Amazon has 15″ MacBook Airs with M2 CPUs and 512GB of storage in stock and on sale for $1249 shipped. That’s $250 off Apple’s MSRP, and it’s the lowest price available for these M2-powered MacBook... Read more
New low price: Clearance 16″ Apple MacBook Pr...
B&H Photo has clearance 16″ M1 Max MacBook Pros, 10-core CPU/32-core GPU/1TB SSD/Space Gray or Silver, in stock today for $2399 including free 1-2 day delivery to most US addresses. Their price... Read more
Switch to Red Pocket Mobile and get a new iPh...
Red Pocket Mobile has new Apple iPhone 15 and 15 Pro models on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide service using all the major... Read more
Apple continues to offer a $350 discount on 2...
Apple has Studio Display models available in their Certified Refurbished store for up to $350 off MSRP. Each display comes with Apple’s one-year warranty, with new glass and a case, and ships free.... Read more
Apple’s 16-inch MacBook Pros with M2 Pro CPUs...
Amazon is offering a $250 discount on new Apple 16-inch M2 Pro MacBook Pros for a limited time. Their prices are currently the lowest available for these models from any Apple retailer: – 16″ MacBook... Read more
Closeout Sale: Apple Watch Ultra with Green A...
Adorama haș the Apple Watch Ultra with a Green Alpine Loop on clearance sale for $699 including free shipping. Their price is $100 off original MSRP, and it’s the lowest price we’ve seen for an Apple... Read more
Use this promo code at Verizon to take $150 o...
Verizon is offering a $150 discount on cellular-capable Apple Watch Series 9 and Ultra 2 models for a limited time. Use code WATCH150 at checkout to take advantage of this offer. The fine print: “Up... Read more
New low price: Apple’s 10th generation iPads...
B&H Photo has the 10th generation 64GB WiFi iPad (Blue and Silver colors) in stock and on sale for $379 for a limited time. B&H’s price is $70 off Apple’s MSRP, and it’s the lowest price... Read more

Jobs Board

Optometrist- *Apple* Valley, CA- Target Opt...
Optometrist- Apple Valley, CA- Target Optical Date: Sep 23, 2023 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 796045 At Target Read more
Senior *Apple* iOS CNO Developer (Onsite) -...
…Offense and Defense Experts (CODEX) is in need of smart, motivated and self-driven Apple iOS CNO Developers to join our team to solve real-time cyber challenges. Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
Child Care Teacher - Glenda Drive/ *Apple* V...
Child Care Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Share on Facebook Apply Read more
Machine Operator 4 - *Apple* 2nd Shift - Bon...
Machine Operator 4 - Apple 2nd ShiftApply now " Apply now + Start apply with LinkedIn + Apply Now Start + Please wait Date:Sep 22, 2023 Location: Swedesboro, NJ, US, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.