[Date Prev][Date Next][Thread Prev][Thread Next] [Search] [Date Index] [Thread Index]

[MacPerl] Clock 2.0



#!perl -w

# Analog Clock v2.0
# by Kevin Reid <kpreid@ibm.net>
#
# Displays an analog clock in a window.
# Control-click for options.
#
# This program may be freely distributed, and you may
# use code from it, as long as my name stays on it.

# Programming information:
# 
# The array @hands stores information about the clock hands.
# 0: hour 1: minute 2: second
# Values:
#   len (x, y): length of hand in pixels
#   ang: current angle of hand in radians
#   tip (x, y): current position of end of hand
#   begin & end: temp values for storing rectangle enclosing hand

# Revision history
# 
# 2.0
#   * Added live resizing.
#   * Added Close command.
#   * Removed 'Title Bar' option.
#   * Moved text time.
#   * Added 'Run Script...' option for executing other scripts while the clock is running.
# 1.1
#   Cleaned up several areas.
#   Fixed hour hand.
#   Added circle WDEF.
# 1.0
#   First release.

package KMacAnalogClock;

use Mac::Events;
use Mac::Windows;
use Mac::QuickDraw;
use Mac::Menus;
use Time::Local;

@divs = (.4, .75, .90); # length of hour, minute, second hands
$showtext  = 1;
$resize = 0;
$tstr = scalar localtime;

for (0..2) {
  $hands[$_]{div}  = $divs[$_]; 
  $hands[$_]{ang}  = 0;
  $hands[$_]{bbox} = new Rect (0,0,0,0);
}

$pi = 3.14159;    # Originally, I used the pi from Math::Trig, but that apparently
$pi2 = $pi * 2;   #   returned a complex number, which caused some problems.

$wrect = new Rect (100, 100, 300, 300); # Initial window position & size

$lgray = new RGBColor ((55000) x 3);
$dgray = new RGBColor ((12000) x 3);
$tgray = new RGBColor ((32000) x 3);
$black = new RGBColor ((    0) x 3);
$white = new RGBColor ((65535) x 3);
# Colors are pre-calculated for speed.

sub SetMouse {
  use Mac::LowMem;
  my ($pt) = @_;
  LMSetMouseTemp($pt);
  LMSetRawMouseLocation($pt);
  LMSetCursorNew(1);
}

sub InitWin { # Create clock window and set hooks
  my ($bbox) = @_;

  $win->dispose if $win; # Destroy previous window if present
  $win = new MacColorWindow (
    $bbox,
    'Clock',
    1,
    \&CircDef_1,
    1,
  );

  $win->sethook(layout => sub { # Handle resizing of window

    my $wRect = $win->window->contRgn->rgnBBox;
      # get bounds of window (in global coordinates)

    SetPort($win->window);
    my $sPt = GlobalToLocal(new Point ($wRect->right, $wRect->bottom));
      # $sPt now holds position of bottom-right corner

    $midh = $sPt->h * .5;      # $midh, $midv is center-point of clock face
    $midv = $sPt->v * .5;

    my $mdv = $midv - ($midv < 20 ? 2 : 5);
    my $mdh = $midh - ($midh < 20 ? 2 : 5);
    $md = ($mdh > $mdv ? $mdv : $mdh);

    $circrect = new Rect ($midh - $md + 1,
                          $midv - $md + 1,
                          $midh + $md + 1,
                          $midv + $md + 1);
    $circrect_e = new Rect ($midh - $md,
                            $midv - $md,
                            $midh + $md,
                            $midv + $md);

    # Set up hands
    foreach (@hands) {
      $_->{len}[0] = $md * $_->{div}; 
      $_->{len}[1] = $md * $_->{div};
    }

    TextSize(20);
    while (StringWidth($tstr) > $sPt->h - 20) {
      TextSize($win->window->txSize - 1);
      last if $win->window->txSize <= 6;
    }
    my ($ascent, $descend) = GetFontInfo();
    $tpos_v = $midv + $ascent + 2; # V pos of text display
    $tpos_vb = $midv + $ascent + $descend + 2; # bottom of text rectangle

    calc_hands(); # Recalculate tip values
    InvalRect new Rect (0, 0, $sPt->h, $sPt->v); # Force update
  });

  $win->layout; # initial setup of clock face
  RGBBackColor($lgray); # set background color

  $win->sethook(idle => \&idler);
  $win->sethook(redraw => sub { # redraw procedure

    # draw text time
    if ($showtext and $win->window->txSize > 6) {
      RGBForeColor($white);
      MoveTo($tpos_hl+1, $tpos_v+1);
      DrawString $tstr;

      RGBForeColor($tgray);
      MoveTo($tpos_hl, $tpos_v);
      DrawString $tstr;
    }

    # draw embossed circle
    RGBForeColor($white); FrameOval($circrect);
    RGBForeColor($dgray); FrameOval($circrect_e);

    # draw hands
    RGBForeColor($black);
    foreach (@hands) {
      MoveTo($midh, $midv);
      LineTo(@{$_->{tip}});
    }

  });

  $win->sethook(click => sub { # click handler
    if ($resize) {$resize = 0; ShowCursor()}
    if ($Mac::Events::CurrentEvent->modifiers & controlKey) {
      # if control key is down, then display menu
      my ($mw, $pt) = @_;
      $m->insert; # set up menu
      my $mp = LocalToGlobal(GetMouse); # find mouse position

      SetItemMark $m->{menu}, 1, $showtext ? "\cR" : ''; # set up check marks

      PopUpMenuSelect $m->{menu}, $mp->v, $mp->h, 1; # display menu
      $m->delete; # remove menu
    } else {
      # if control key is up, drag window
      DragWindow($win->window, $Mac::Events::CurrentEvent->where);
    }
  });
}

# create clock window
InitWin($wrect);

sub calc_hands { # calculate hand & text positions
  foreach (@hands) {
    $_->{tip}[0] = sin($_->{ang})*$_->{len}[0]+$midh;  # calculate end-point of hand from angle
    $_->{tip}[1] = -cos($_->{ang})*$_->{len}[1]+$midv;

    my @box = map {($$_[0] < $$_[1]) ? $_ : [$$_[1], $$_[0]]} ([$_->{tip}[0], $midh], [$_->{tip}[1], $midv]);
    $_->{bbox} = new Rect ($box[0][0], $box[1][0], $box[0][1] + 1, $box[1][1] + 1);
      # These messy lines calculate a rectangle that
      # encloses the hand, so that window updates
      # can redraw only that which needs to be redrawn.
  }
  $tpos_hl = $midh - StringWidth($tstr) / 2;
  $tpos_hr = $midh + StringWidth($tstr) / 2;
}

sub CircDef_1 {
  my ($variant, $win, $msg, $param) = @_;
  if ($msg == wCalcRgns) {
    my $r = OffsetRect($win->portRect,
      -$win->portBits->bounds->left,
      -$win->portBits->bounds->top);
    my $circ = NewRgn();
    OpenRgn;
    FrameOval $r;
    CloseRgn $circ;
    CopyRgn $circ, $win->contRgn;
    CopyRgn $circ, $win->strucRgn;
  } elsif ($msg == wHit) {
    if (PtInRgn($param, $win->contRgn)) {
      return wInContent;
    }
  }
  return 0;
}

# create options menu
$m = new MacHierMenu 2000, '', (
  ['Text Time' => sub {
    InvalRect new Rect ($tpos_hl, $midv, $tpos_hr, $tpos_vb)
      if $showtext;
    $showtext = !$showtext;
  }],
  ['Resize' => sub {
    SetMouse LocalToGlobal new Point $md, $md;
    $resize = GetMouse;
    HideCursor();
    $wsiz = 0;
 }],
  [],
  ['Close' => sub {exit}],
  ['Run Script...' => sub {
    local $^W;
    require 'StandardFile.pl';
    my $file = StandardFile::GetFile('TEXT') or return;
    {package main; do $file}
    exit;
  }],
);

$t = $tt = 0;
# In order to avoid flickering, the clock is updated
# only once per second. The $t variable stores the time
# when the clock was last updated, and when the current
# time differs from the stored time, the clock is updated.

sub idler {
  if ($t != ($tt = time) and $t = $tt) { # see above
    SetPort($win->window);
    foreach (@hands) {
      InvalRect $_->{bbox};
      # mark hand areas for updating; since
      # the hands will no longer be there,
      # they must be erased.
    }

    $tstr = scalar localtime; # get text time
    $secs_today = time % 86400; # calc seconds since beginning of day
    $hands[0]{ang} = $secs_today % 43200 / 43200 * $pi2; # calc hour hand position
    $hands[1]{ang} = $secs_today % 3600 / 3600 * $pi2; # calc minute hand position
    $hands[2]{ang} = $secs_today % 60 / 60 * $pi2;     # calc second hand position

    calc_hands(); # calculate hand positions
    foreach (@hands) {
      InvalRect $_->{bbox};
      # mark NEW hand areas for updating, too.
    }
    InvalRect new Rect ($tpos_hl, $midv, $tpos_hr, $tpos_vb)
      if $showtext; # mark text time area for updating
  }
  if ($resize) {
    SetPort $win->window;
    my $pt = GetMouse;
    $osiz = $wsiz;
    $wsiz = ($pt->h + $pt->v);
    $wsiz = 30 if $wsiz < 30;
    $wsiz = 600 if $wsiz > 600;
    if ($osiz != $wsiz) {
      SizeWindow $win->window, $wsiz, $wsiz;
      $win->layout;
      $win->update;
    }
    SetMouse LocalToGlobal new Point (($wsiz/2+.5)x2);
  }
}

WaitNextEvent while 1;

END {$win->dispose; ShowCursor if $resize}

__END__

===== Want to unsubscribe from this list?
===== Send mail with body "unsubscribe" to macperl-request@macperl.org