text:
author: jlordiales
comments: true
share: true
date: 2015-07-12
layout: post
title: Orchestrating your containers with CoreOS, an introduction
categories:
- Devops
- Microservices
tags:
- Docker
- CoreOS
- Fleet
- etcd
---
Most docker tutorials that you'll find out there (the ones in this blog
included) will assume that you have a single host running all your containers or
a few hosts but where you are manually managing them. While this is nice and
simple to explain the basic concepts, it is probably not the way you want to run
your applications in production.
In most cases you will have a cluster of
servers all running different containers that need to talk to each other and
know how to function properly, even when some of those servers suddenly go
offline.

This is the area of orchestration and scheduling of containers, a topic that is
extremely hot these days. Particularly with big players in the industry working
in new projects to abstract away most of the complexities inherent to running
distributed containers. Amazon recently opened up their [EC2 container
service](http://aws.amazon.com/ecs/), Google has
[Kubernetes](http://kubernetes.io/) and [Mesosphere](https://mesosphere.com/) is
becoming pretty popular with
the underlying [Apache Mesos project](http://mesos.apache.org/).

One additional project that has been gaining a lot of attention in this area is
[CoreOS](https://coreos.com/). In this post I'm going to try to explore CoreOS
and give a basic overview of the problem that it tries to solve, how it works
and how to work effectively with it.

# Introduction
The idea behind CoreOS is the same as with any other cluster management system.
You stop thinking about your individual servers and how they work together.
Instead you think about your data center (a cluster of individual servers). In
other words, you no longer say "run this container in server 1 and this other
container in server 2" but "run these 2 containers in my data center" and let
the cluster manager take care of where and how to do that.

This also means that if one or more of your individual servers die the
cluster manager will take the containers that were running in those servers and
distribute them across the remaining healthy nodes.

Following this philosophy, CoreOS is an open source lightweight operating system
that comes together with a set of simple tools.
The main building block behind CoreOS is Docker. Since CoreOS doesn't come with
a package manager, everything you want to run on it has to run as a container.
It should be noted that while CoreOS fully supports Docker, they are also working
on their own container runtime called [rkt](https://github.com/coreos/rkt).

To start and manage all these containers CoreOS uses Fleet. Fleet is based on
[systemd](http://en.wikipedia.org/wiki/Systemd) and extends it in order to work
at the cluster level. In other words, while systemd works as a single machine
init system, fleet works as a cluster init system.

To coordinate all different nodes and let Fleet know where to run your
containers, CoreOS provides [etcd](https://coreos.com/etcd/), a distributed
key/value store with a strong consistency and partition tolerance model. Etcd
uses the [Raft consensus algorithm](https://raftconsensus.github.io/) to handle
the communication between the different nodes. This is the same algorithm used
by Consul by the way.

I will go into some detail about each of this tools and how they can work
together with Docker. But first, lets setup our CoreOS cluster running on
Vagrant.

# Bootstraping a CoreOS cluster
The CoreOS documentation has very comprehensive guides to run CoreOS on anything
from bare metal hardware, to cloud providers to virtualization platforms. You
can follow the step by step guide in
[here](https://coreos.com/docs/running-coreos/platforms/vagrant/) to run a basic
cluster locally on Vagrant.

The short version is that you can clone [this
repo](https://github.com/coreos/coreos-vagrant) and then do some minimal
configuration.
The relevant files for this part are the `config.rb` and
`user-data` ones.

The `Vagrantfile` is pretty generic and reads all the configuration it needs
from `config.rb`, so no need to change anything there. This latter file looks
something like the following:

{% highlight ruby %}
# Size of the CoreOS cluster created by Vagrant
$num_instances=6

# Official CoreOS channel from which updates should be downloaded
$update_channel='stable'

# Customize VMs
$vm_memory = 2048
$vm_cpus = 2

# Enable port forwarding from guest(s) to host machine, syntax is: { 80 => 8080 }, auto correction is enabled by default.
# 4001 is the default etcd port, we need this if we want to run fleetctl locally on the host
$forwarded_ports = {4001 => 4001}
{% endhighlight %}

The file is pretty self-explanatory. You see that we define the size of our
cluster (6 instances) and we give it some extra memory and cpus to run on.
Lastly we forward port 4001 which is the default port used by etcd. We'll see
why we want to do this in a bit.

Then we have the `user-data` file:

{% highlight yaml %}
#cloud-config
coreos:
  etcd2:
    #generate a new token for each unique cluster from https://discovery.etcd.io/new
    discovery: https://discovery.etcd.io/<token>
    # multi-region and multi-cloud deployments need to use $public_ipv4
    advertise-client-urls: http://$public_ipv4:2379
    initial-advertise-peer-urls: http://$private_ipv4:2380
    # listen on both the official ports and the legacy ports
    # legacy ports can be omitted if your application doesn't depend on them
    listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001
    listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001
  fleet:
    public-ip: $public_ipv4
  flannel:
    interface: $public_ipv4
  units:
    - name: etcd2.service
      command: start
    - name: fleet.service
      command: start
    - name: docker-tcp.socket
      command: start
      enable: true
      content: |
        [Unit]
        Description=Docker Socket for the API

        [Socket]
        ListenStream=2375
        Service=docker.service
        BindIPv6Only=both

        [Install]
        WantedBy=sockets.target
{% endhighlight %}

This is the only file you need to modify
before starting the cluster. Go to
[https://discovery.etcd.io/new](https://discovery.etcd.io/new) in your browser
and copy the URL that you get as a response there. Now go to `user-data` and
paste that URL where it says `discovery: https://discovery.etcd.io/<token>`.

You can now do a `vagrant up` and wait while your cluster gets created. When
it's done you should be able to run `vagrant status` and see the 6 nodes
running.

# Trying out etcd
Now that you have a CoreOS cluster up and running, we can start to play around
with the different tools that are shipped with it. Lets start with etcd, the
distributed key/value store.

You'll need 2 open terminals for this (or tabs, or splits or whatever you use).
We'll ssh into core-01 in one of them (with `vagrant ssh core-01`) and core-02
in the other (`vagrant ssh core-02`). Which nodes you ssh into is irrelevant, as
long as they are different.

CoreOS comes with a tool to read and write from etcd, called `etcdctl`. But etcd
also exposes an HTTP API that is really intuitive and easy to use. In fact,
etcdctl is just a facade in front of this API.
We'll see how to use both here.

Lets start by writing a value. From `core-01` do a `etcdctl set /key1 value1`.
This command adds a new key/value pair to etcd where the key is `key1` and the
value is `value1`. Now from the second node, you can read the value with
`etcdctl get /key1`. You should see `value1` as a response.

Note how etcd replicated the value that you wrote on the first node to the
second one almost instantaneously. In fact, it replicated the value to all nodes
in the cluster not just the two you are ssh'ed into. This is the power of a
distributed store.

If you wanted to use the HTTP API you could have accomplished the same thing
using `curl` instead of `etcdctl`. We can write a second key/value pair in this
way. From `core-01` you can do `curl -L -X PUT
http://127.0.0.1:4001/v2/keys/key2 -d value="value2"`. Now to read the value
from `core-02` you can do a `curl -L http://127.0.0.1:4001/v2/keys/key2`.
Admittedly, the `etcdctl` tool simplifies things a little bit but both options
are there to choose from.

Another really interesting thing that etcd provides are TTL (time to live)
values for each entry. This is quite useful when we use etcd for things like
service discovery, where we don't want to be reading stale values. To use it you
simply pass the `--ttl` parameter when you set a value.
To see this in action go back to `core-01` and do a `etcdctl set /key3 value3
--ttl 15`. This will add the new key with a TTL of 15 seconds. If you go to
`core-02` now and do a `etcdctl get /key3` you should see its value (provided it
took you less than 15 seconds to do that). Now wait for a while and run the same
get again. The key is gone!

Finally, if you want to list all the currently stored keys withing etcd you can
use the `etcdctl ls` command. This will print the keys available at the root
level. Alternatively, if you want to print keys at any level you can pass the
`--recursive` flag (as in `etcdctl ls --recursive`).

Etcd provides some other cool functionalities (like atomic test and set updates,
directories, event notifications) that are well documented if you do a `etcdctl
help`.

# Starting your first Fleet unit
As I mentioned in the introduction, CoreOS comes with a cluster manager called
Fleet. You'll use the `fleetctl` tool to interact with the cluster.
To see this in action ssh into one of the nodes and do a `fleetctl list-machines` like:

{% highlight bash %}
$ vagrant ssh core-01

core-01$ fleetctl list-machines

MACHINE         IP              METADATA
0b9fd6f8...     172.17.8.104    -
128a2e32...     172.17.8.103    -
2addf739...     172.17.8.101    -
3f608471...     172.17.8.106    -
73c0b7fc...     172.17.8.105    -
eabc97ed...     172.17.8.102    -
{% endhighlight %}

We can see that our 6 CoreOS nodes are automatically recognized by Fleet as
being part of the same cluster.

Like I said before, we know only care about our cluster and not our individual
nodes. This nodes are completely ephemeral and we should assume they can come
and go without previous notice. For this reason, it doesn't matter from which
node we run the previous `fleetctl` command. We could've ssh into "core-06" and
the result would've been exactly the same.

## Using fleetctl from your host
Being able to run `fleetctl` from within any node is great but even better is to
be able to run it from outside the cluster as well. For now our cluster is
running locally on Vagrant but the same setup could be running in AWS and we
would probably like to control it from our laptop without the need to
ssh into individual instances before.

Luckily, we can do this easily using the `--tunnel` flag. From your laptop run:

{% highlight bash %}
$ fleetctl --tunnel 127.0.0.1:2222 list-machines

MACHINE         IP              METADATA
0b9fd6f8...     172.17.8.104    -
128a2e32...     172.17.8.103    -
2addf739...     172.17.8.101    -
3f608471...     172.17.8.106    -
73c0b7fc...     172.17.8.105    -
eabc97ed...     172.17.8.102    -
{% endhighlight %}

This basically tunnels all communication with your cluster over SSH using the IP
and port specified. Port 2222 is the default port that Vagrant uses to SSH into
your VM (you can see this by running `vagrant ssh-config`).

If you get a message saying something like
_Failed initializing SSH client: ssh: handshake failed: ssh: unable to
authenticate, attempted methods [none publickey], no supported methods remain_,
make sure that your Vagrant insecure ssh key is added to your ssh-agent by
running `ssh-add ~/.vagrant.d/insecure_private_key`

To make `fleetctl` commands a bit less verbose we can actually put the tunnel
configuration into an environment variable:

{% highlight bash %}
$ export FLEETCTL_TUNNEL=127.0.0.1:2222
{% endhighlight %}

Then we'll be able to run Fleet just as we would if we were inside one of our
nodes:

{% highlight  bash %}
$ fleetctl list-machines

MACHINE         IP              METADATA
0b9fd6f8...     172.17.8.104    -
128a2e32...     172.17.8.103    -
2addf739...     172.17.8.101    -
3f608471...     172.17.8.106    -
73c0b7fc...     172.17.8.105    -
eabc97ed...     172.17.8.102    -
{% endhighlight %}

# Running Fleet units
Having our nodes up and running with Fleet is great but it is not doing anything
useful by itself. We want to start telling our cluster to run some services for
us. This is where Fleet Units come into play.

As I mentioned before Fleet can be seen as
[systemd](http://www.freedesktop.org/wiki/Software/systemd/) working at the
cluster level instead of at the individual machines level. As such, in order to
run anything with Fleet you need to submit regular systemd units files combined
with some Fleet specific properties.

A unit file defines what process you want to run and gives Fleet some hints to
help it determine how and where that process should be executed. To get started, lets
see what the unit file to run our [good old python
service](https://registry.hub.docker.com/u/jlordiales/python-micro-service/)
would look like:

{% highlight bash %}
$ cat python-test.service

[Unit]
Description=Python service
Requires=docker.service
After=docker.service

[Service]
TimeoutStartSec=0
Restart=on-failure

ExecStartPre=-/usr/bin/docker kill python-service
ExecStartPre=-/usr/bin/docker rm python-service
ExecStartPre=/usr/bin/docker pull jlordiales/python-micro-service

ExecStart=/usr/bin/docker run --name python-service -P jlordiales/python-micro-service

ExecStop=/usr/bin/docker stop python-service
{% endhighlight %}

Lets go through the unit file and see what each section is doing. The first line
simply sets a description for our unit, which is helpful when looking at all the
units that are currently running. The following 2 lines _Requires_ and _After_
specify ordering dependencies between units (the full documentation can be seen
[here](http://www.freedesktop.org/software/systemd/man/systemd.unit.html)).
Since we are running a docker container we need the _docker_ process to be
started first. This dependency also means that if the _docker_ unit is
stopped this _python-test.service_ unit will also be stopped.

We then have the _[Service]_
[section](http://www.freedesktop.org/software/systemd/man/systemd.service.html),
which effectively describes how our
service should run. We first tell systemd not to wait for a completion signal
from our service (with _TimeoutStartSec=0_). Next, we ask systemd to restart our
container whenever it exits unexpectedly (exit code different than 0). This is
extremely useful if we want to have a self-healing cluster and we'll see how
this works in a moment.

Finally, the _Exec*_ commands telling systemd how to run our container. The
_ExecStartPre_ commands are run before our container is started and are
basically there to setup the environment to ensure that our main process can run
smoothly. In our example, we make sure that no container with the same name is
running by doing a `docker kill` and `docker rm`. Note that this 2 lines are
prefixed with a `-` before the command to run. This is very important because by
default systemd will execute the commands in the order they are specified and
will stop as soon as one of them returns a non-zero exit code. By prefixing the
command with `-` systemd will ignore the exit code and continue executing the
next one. We need to do that for `docker kill` and `docker rm` because those
commands will fail if there is no container named _python-service_.

The 2 remaining lines are pretty self-explanatory. _ExecStart_ is the command
that will start the main process for our unit. In our case we run our container
as we usually would, specifying a name and the `-P` to expose its ports. One
important thing to notice here is that we don't pass the `-d` flag to docker (to
run in detached mode). If we do that the unit would run for a few seconds and
then exit, because the container would not be started as a child of the unit's
PID. Which basically means that from the unit's point of view there is nothing
to run.
The _ExecStop_ command in the last line will do a `docker stop` whenever we tell
systemd to stop our unit.

So now that we have our unit file, how do we run it? Well, first we need to load
the unit into our cluster because so far this is only a text file that we have
edited in our local environment (outside of any of the CoreOS hosts). We can do
this with `fleetctl submit python-test.service`. To see that the unit was
actually submitted we can do a `fleetctl list-unit-files`, which should give us
the list of all units that our cluster knows about. You can even look at the
contents of the unit with `fleetctl cat python-test.service`.

With the unit file submitted we can now do:

{% highlight bash %}
$ fleetctl start python-test.service

Unit python-test.service launched on 0b9fd6f8.../172.17.8.104
{% endhighlight %}

In this case, Fleet decided that the node 172.17.8.104 was good enough to run
our container. If we want to see all the currently running units we can do that
with:

{% highlight bash %}
$ fleetctl list-units

UNIT                    MACHINE                         ACTIVE  SUB
python-test.service     0b9fd6f8.../172.17.8.104        active  running
{% endhighlight %}

We can also check the status of any given unit:

{% highlight bash %}
$ fleetctl status python-test.service

● python-test.service - Python service
   Loaded: loaded (/run/fleet/units/python-test.service; linked-runtime; vendor preset: disabled)
   Active: active (running) since Sat 2015-07-04 10:46:22 ; 50s ago
  Process: 1726 ExecStartPre=/usr/bin/docker pull jlordiales/python-micro-service (code=exited, status=0/SUCCESS)
  Process: 1719 ExecStartPre=/usr/bin/docker rm python-service (code=exited, status=1/FAILURE)
  Process: 1655 ExecStartPre=/usr/bin/docker kill python-service (code=exited, status=1/FAILURE)
 Main PID: 1776 (docker)
   Memory: 8.3M
   CGroup: /system.slice/python-test.service
           └─1776 /usr/bin/docker run --name python-service -P jlordiales/python-micro-service

Jul 04 10:46:07 core-04 docker[1726]: 595ded12b855: Pulling fs layer
Jul 04 10:46:09 core-04 docker[1726]: 595ded12b855: Download complete
Jul 04 10:46:09 core-04 docker[1726]: 7e0b582bc16d: Pulling metadata
Jul 04 10:46:10 core-04 docker[1726]: 7e0b582bc16d: Pulling fs layer
Jul 04 10:46:22 core-04 docker[1726]: 7e0b582bc16d: Download complete
Jul 04 10:46:22 core-04 docker[1726]: 7e0b582bc16d: Download complete
Jul 04 10:46:22 core-04 docker[1726]: Status: Downloaded newer image for jlordiales/python-micro-service:latest
Jul 04 10:46:22 core-04 systemd[1]: Started Python service.
Jul 04 10:46:22 core-04 docker[1776]: * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Jul 04 10:46:22 core-04 docker[1776]: * Restarting with stat
{% endhighlight %}

There we can see that our process is active and running. We can also see the
exit code of the 3 _ExecStartPre_ instructions we discussed before. Finally, we
can see some of the output from each process.
To make sure that our container is running and responding where Fleet says it is
we can get the _Machine Id_ from the output of `fleetctl list-units` that we saw
before (0b9fd6f8 in our case) and ssh directly into it with:

{% highlight bash %}
$ fleetctl list-units

UNIT                    MACHINE                         ACTIVE  SUB
python-test.service     0b9fd6f8.../172.17.8.104        active  running

$ fleetctl ssh 0b9fd6f8

core-03$ docker ps

CONTAINER ID        IMAGE                                    COMMAND             CREATED             STATUS              PORTS                     NAMES
bd6681b7eef3        jlordiales/python-micro-service:latest   "python app.py"     19 minutes ago      Up 19 minutes       0.0.0.0:32768->5000/tcp   python-service

core-03$ curl localhost:32768

Hello World from bd6681b7eef3
{% endhighlight %}

# Self-healing nodes
When I was describing the unit file for the python unit, I briefly showed a
property called `Restart=on-failure`, which means that systemd will
automatically restart the process if it exits with an exit code different than
0. Lets see if this really works in our example. We'll ssh again into the node
running our container and kill it to see what happens:

{% highlight bash %}
$ fleetctl ssh 0b9fd6f8

core-03$ docker ps

CONTAINER ID        IMAGE                                    COMMAND             CREATED             STATUS              PORTS                     NAMES
bd6681b7eef3        jlordiales/python-micro-service:latest   "python app.py"     29 minutes ago      Up 29 minutes       0.0.0.0:32768->5000/tcp   python-service

core-03$ docker kill python-service

core-03$ docker ps

CONTAINER ID        IMAGE                                    COMMAND             CREATED             STATUS              PORTS                     NAMES
1da6c1b5cafc        jlordiales/python-micro-service:latest   "python app.py"     45 seconds ago      Up 44 seconds       0.0.0.0:32769->5000/tcp   python-service
{% endhighlight %}

Awesome! We killed the first running container and within seconds systemd
started a new one for us. If however we use `docker stop` instead of `docker
kill` (therefore stopping the container gracefully) systemd won't try to restart
it.

That is great if the process is killed for some reason but what happens if the
entire node disappears all of the sudden? I won't show it here but you can
easily simulate this by doing a `vagrant halt` on the VM where your unit was
placed. Fleet will detect that the node is dead and re-distribute all the units
that were running in that node across the rest of cluster.

# High availability services
One of the main benefits of using Fleet to mange our units is that it becomes
really easy to run a highly available service with multiple instances running in
different nodes. This, combined with the self-healing property we discussed in
the previous section gives you a lot of power to do pretty cool stuff.

This replication of any given service across your nodes is enabled by something
called Template unit files. This basically means that you can write a regular
unit file like the one we wrote before and use this as a template to instantiate
new units. The only difference is in the name of the unit file, that should now
follow the patter `<name>@.<suffix>`. For example, for our previous
`python-test.service` we should rename it to `python-test@.service`.

Lets rename our unit file and see how we can start as many instances of our
python container as we want. But first, remove the unit file we loaded before
with `fleetctl destroy python-test.service`. Now we can rename our unit and
submit it to our cluster in the same way as we did for the first one:

{% highlight bash %}
$ mv python-test.service python-test@.service
$ fleetctl submit python-test@.service
{% endhighlight %}

With our template loaded in the cluster we can now start instances of that
template using the name and some suffix after the `@`. For instance:

{% highlight bash %}
$ fleetctl start python-test@1 python-test@2 python-test@random

$ fleetctl list-units

UNIT                            MACHINE                         ACTIVE  SUB
python-test@1.service           0b9fd6f8.../172.17.8.104        active  running
python-test@2.service           128a2e32.../172.17.8.103        active  running
python-test@random.service      3f608471.../172.17.8.106        active  running
{% endhighlight %}

Here we can see that we started 3 instances of our python container. We can also
use our shell expansion functionality to start multiple instances:

{% highlight bash %}
$ fleetctl start python-test@{3..5}

$ fleetctl list-units

UNIT                            MACHINE                         ACTIVE  SUB
python-test@1.service           0b9fd6f8.../172.17.8.104        active  running
python-test@2.service           128a2e32.../172.17.8.103        active  running
python-test@3.service           73c0b7fc.../172.17.8.105        active  running
python-test@4.service           eabc97ed.../172.17.8.102        active  running
python-test@5.service           2addf739.../172.17.8.101        active  running
python-test@random.service      3f608471.../172.17.8.106        active  running
{% endhighlight %}

# Telling Fleet where to run your containers
By default Fleet makes no guarantees as to where in the cluster your units will
run. In the last example from the previous section we saw that we started 6
different instances of our python unit and it just so happens that Fleet decided
to run one on each node.

So what do we do if we have dependencies between our different units. Imagine
for instance that you have 2 different containers, one running your application
and one running a monitoring agent for that application.
In that case you want to keep those 2 running on the same node always.
Similarly, if you want to run multiple instances
of your service to scale horizontally you want those to run on different
nodes.

To do this, Fleet provides a set of [fleet-specific
options](https://coreos.com/docs/launching-containers/launching/fleet-unit-files/#unit-scheduling)
that allows you to control how the scheduling engine of Fleet will work.
We'll see how these work with some examples. But first we'll need another unit
file:

{% highlight bash %}
$ cat hello-world@.service

[Unit]
Description=MyApp
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill busybox1
ExecStartPre=-/usr/bin/docker rm busybox1
ExecStartPre=/usr/bin/docker pull busybox
ExecStart=/usr/bin/docker run --name busybox1 busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done"
ExecStop=/usr/bin/docker stop busybox1
{% endhighlight %}

This simply runs a container that will keep printing a Hello World message to
stdout.

## Running units together
Now imagine that we want to run this hello-world unit together with our
python-test one but we want to make sure that these 2 always run together in the
same node. We can use the `MachineOf` Fleet attribute to achieve this.

{% highlight bash %}
$ cat python-test@.service

[Unit]
Description=Python service
Requires=docker.service
After=docker.service

[Service]
TimeoutStartSec=0
Restart=on-failure

ExecStartPre=-/usr/bin/docker kill python-service
ExecStartPre=-/usr/bin/docker rm python-service
ExecStartPre=/usr/bin/docker pull jlordiales/python-micro-service

ExecStart=/usr/bin/docker run --name python-service -P jlordiales/python-micro-service

ExecStop=/usr/bin/docker stop python-service

[X-Fleet]
MachineOf=hello-world@%i.service
{% endhighlight %}

We added the `[X-Fleet]` section to our unit file specifying that our unit
should only be placed wherever there's also a `hello-world` unit running.
Lets see what happens when we submit these 2 units into our cluster:

{% highlight bash %}
$ fleetctl submit python-test@.service hello-world@.service
$ fleetctl start python-test@1 hello-world@1

$ fleetctl list-units

UNIT                    MACHINE                         ACTIVE  SUB
hello-world@1.service   0b9fd6f8.../172.17.8.104        active  running
python-test@1.service   0b9fd6f8.../172.17.8.104        active  running
{% endhighlight %}

As we expected, the 2 units were scheduled on the same node. The same thing
would happen if we start multiple instances of each unit at the same time:

{% highlight bash %}
$ fleetctl start python-test@{2..4} hello-world@{2..4}

$ fleetctl list-units

UNIT                    MACHINE                         ACTIVE  SUB
hello-world@1.service   0b9fd6f8.../172.17.8.104        active  running
hello-world@2.service   128a2e32.../172.17.8.103        active  running
hello-world@3.service   2addf739.../172.17.8.101        active  running
hello-world@4.service   3f608471.../172.17.8.106        active  running
python-test@1.service   0b9fd6f8.../172.17.8.104        active  running
python-test@2.service   128a2e32.../172.17.8.103        active  running
python-test@3.service   2addf739.../172.17.8.101        active  running
python-test@4.service   3f608471.../172.17.8.106        active  running
{% endhighlight %}

This dependency between units also means that if the unit we depend on
(hello-world in our example) is destroyed then all the units that were dependant
on that one (python-test in our example) will also be destroyed. We can see this
if we do:

{% highlight bash %}
$ fleetctl destroy hello-world@4

$ fleetctl list-units
UNIT                    MACHINE                         ACTIVE  SUB
hello-world@1.service   0b9fd6f8.../172.17.8.104        active  running
hello-world@2.service   128a2e32.../172.17.8.103        active  running
hello-world@3.service   2addf739.../172.17.8.101        active  running
python-test@1.service   0b9fd6f8.../172.17.8.104        active  running
python-test@2.service   128a2e32.../172.17.8.103        active  running
python-test@3.service   2addf739.../172.17.8.101        active  running
{% endhighlight %}

We removed `hello-world@4` and Fleet automatically removed `python-test@4` as
well.

## Running units away from each other
We saw how to run multiple units guaranteeing that they will be put always in
the same node. How about the opposite scenario, running 2 or more units making
sure that they are never put on the same node.
We can use Fleet's `Conflicts` option to achieve this. Let's change our
`python-test@.service` unit file to use this new option:

{% highlight bash %}
$ cat python-test@.service

[Unit]
Description=Python service
Requires=docker.service
After=docker.service

[Service]
TimeoutStartSec=0
Restart=on-failure

ExecStartPre=-/usr/bin/docker kill python-service
ExecStartPre=-/usr/bin/docker rm python-service
ExecStartPre=/usr/bin/docker pull jlordiales/python-micro-service

ExecStart=/usr/bin/docker run --name python-service -P jlordiales/python-micro-service

ExecStop=/usr/bin/docker stop python-service

[X-Fleet]
Conflicts=hello-world@%i.service
{% endhighlight %}

If we submit this new unit file and run multiple instances of our 2 services
we'll see that Fleet will place them on different nodes. If Fleet can not find a
distribution that satisfies the constraints specified in the unit files then it
will simply refuse to schedule them.

# Conclusion
Container orchestration and scheduling is an exciting and relatively new area
that is under heavy development by different players. CoreOS presents an easy
and lightweight approach using etcd, Fleet and Docker as its backbone.
In this post we saw how easy it is to create a local CoreOS cluster with Vagrant
and run highly available and self-healing services with the help of Fleet.

By combining a few simple configuration values, we can ensure that our services
are distributed across different regions and availability zones. This, combined
with the fact that we can run CoreOS on pretty much any cloud provider or
hardware, enables us to have very complex architectures with pretty much no
manual intervention.


text:
require_relative '../../spec_helper'

require_lib 'reek/smell_detectors/instance_variable_assumption'

RSpec.describe Reek::SmellDetectors::InstanceVariableAssumption do
  it 'reports the right values' do
    src = <<-RUBY
      class Alfa
        def bravo
          @charlie
        end
      end
    RUBY

    expect(src).to reek_of(:InstanceVariableAssumption,
                           lines:      [1],
                           context:    'Alfa',
                           message:    "assumes too much for instance variable '@charlie'",
                           source:     'string',
                           assumption: '@charlie')
  end

  it 'does count all occurrences' do
    src = <<-RUBY
      class Alfa
        def bravo
          @charlie
        end

        def delta
          @echo
        end
      end

    RUBY

    expect(src).
      to reek_of(:InstanceVariableAssumption, lines: [1], assumption: '@charlie').
      and reek_of(:InstanceVariableAssumption, lines: [1], assumption: '@echo')
  end

  it 'does not report an empty class' do
    src = <<-RUBY
      class Alfa
      end
    RUBY

    expect(src).not_to reek_of(:InstanceVariableAssumption)
  end

  it 'does not report when lazy initializing' do
    src = <<-RUBY
      class Alfa
        def bravo
          @charlie ||= 1
        end
      end
    RUBY

    expect(src).not_to reek_of(:InstanceVariableAssumption)
  end

  it 'reports variable even if others are initialized' do
    src = <<-RUBY
      class Alfa
        def initialize
          @bravo = 1
        end

        def charlie
          [@bravo, @delta]
        end
      end
    RUBY

    expect(src).to reek_of(:InstanceVariableAssumption, assumption: '@delta')
  end

  it 'reports inner class even if outer class initializes the variable' do
    src = <<-RUBY
      class Alfa
        def initialize
          @bravo = 1
        end

        class Charlie
          def delta
            @bravo
          end
        end
      end
    RUBY

    expect(src).to reek_of(:InstanceVariableAssumption, context: 'Alfa::Charlie')
  end
end

redpajama_set_name: RedPajamaGithub


text:
/** {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

.clear:after {
	content: "";
	display: block;
	clear: both;

}*/

/*mobile*/
@media only screen and (min-width: 240px) and (max-width: 400px) {

}
/*mobile end*/

/*tablet*/
@media only screen and (min-width: 401px) and (max-width: 990px) {

}
/*tablet end*/

/*large displays*/
@media only screen and (min-width: 990px) {

}
/*large displays end*/

div.master {
	border-radius: 20px;
}

.mart {
	display: block;
	margin-right: auto;
	margin-left: auto;
}

.headertext,
.capstone,
.workxp {
	color: #3b5998;
	text-align: center;
}

.workxpbot {
	margin-bottom: 20px;
	text-align: center;
}

.aboutskillset + p a {
	color: white !important;
}

.aboutskillset + p a:hover {
	color: #EF5350 !important;
}

.socialicons,
.resume {
	padding-top: 20px;
}

.github,
.linkedin {
	margin-right: 10px;
}

/*skillbar*/

.skillbar {
	position:relative;
	display:block;
	margin-bottom:15px;
	width:100%;
	background:#eee;
	height:20px;
	border-radius:3px;
	-moz-border-radius:3px;
	-webkit-border-radius:3px;
	-webkit-transition:0.4s linear;
	-moz-transition:0.4s linear;
	-ms-transition:0.4s linear;
	-o-transition:0.4s linear;
	transition:0.4s linear;
	-webkit-transition-property:width, background-color;
	-moz-transition-property:width, background-color;
	-ms-transition-property:width, background-color;
	-o-transition-property:width, background-color;
	transition-property:width, background-color;
}

.skillbar-title {
	position:absolute;
	top:0;
	left:0;
  width:0%;
	font-weight:bold;
	font-size:13px;
	color:#ffffff;
	background:#6adcfa;
	border-top-left-radius:3px;
	border-bottom-left-radius:3px;
}

.skillbar-title span {
	display:block;
	color: black;
	/*background:rgba(0, 0, 0, 0.5);*/
	padding:0 20px;
	height:20px;
	line-height:20px;
	-webkit-border-top-left-radius:3px;
	-webkit-border-bottom-left-radius:3px;
	-moz-border-radius-topleft:3px;
	-moz-border-radius-bottomleft:3px;
	border-top-left-radius:3px;
	border-bottom-left-radius:3px;
}

.skillbar-bar {
	height:20px;
	width:0px;
	background:#6adcfa;
	border-radius:3px;
	-moz-border-radius:3px;
	-webkit-border-radius:3px;
}

.skill-bar-percent {
	position:absolute;
	right:10px;
	top:0;
	font-size:11px;
	height:20px;
	line-height:20px;
	color:#ffffff;
	color:rgba(0, 0, 0, 0.4);
}

/*skillbar end*/


text:
import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService, Angular2SocialLoginModule } from "angular2-social-login";
import { FacebookService, LoginOptions, LoginResponse, InitParams } from 'ngx-facebook';

import { UserApi } from '../userApi/user-api';
import { FrameworkConfigService } from '../services/framework-config.service';
import { fadeInAnimation, slideInOutAnimation } from '../animations/index.animation';

@Component({
  selector: 'fw-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css',
              '../../assets/vendor/bootstrap/css/bootstrap-social.css'
              ],
  animations: [ fadeInAnimation, slideInOutAnimation ]

})
export class LoginComponent implements OnInit {

  formError: string;
  submitting = false;

  constructor(
    private frameworkConfigService: FrameworkConfigService,
    private userApi: UserApi,
    private router: Router,
    private socialAuth: AuthService,
    private fb: FacebookService
  ) {
      this.frameworkConfigService.showUserControls = false;
      this.frameworkConfigService.showNavbar = false;
  }

  ngOnInit() {
  }

  onLoginSubmit(loginForm: NgForm) {
    if (loginForm.valid) {
      console.log('submitting...', loginForm);
      this.submitting = true;
      this.formError = null;

      this.userApi.localLogin(loginForm.value.email, loginForm.value.password)
        .subscribe((data) => {

          console.log("Data BODY back from server: ", data);
          if (data.success){
            this.frameworkConfigService.showUserControls= true;
            this.frameworkConfigService.showNavbar=true;
            this.router.navigate(['/profile']);
          }else{
            this.submitting = false;
            this.formError = data.msg;
            setTimeout(()=>{ this.formError = ""}, 2000);
            this.router.navigate(['/login']);
          }
        },
        (err)=> {
          this.submitting = false;
          console.log('got error: ', err);
          this.formError = err;
        }
      );
    }
  }

  onFacebookAuth(){
   	let fbInitParams: InitParams = {
      appId: this.frameworkConfigService.providers.facebook.clientId,
      xfbml: this.frameworkConfigService.providers.facebook.xfbml,
      version: this.frameworkConfigService.providers.facebook.apiVersion
    };
		this.fb.init(fbInitParams);

		const loginOptions: LoginOptions = {
      enable_profile_selector: true,
      return_scopes: true,
      scope: 'public_profile,email'
    };

		this.fb.login(loginOptions)
      .then((response: LoginResponse) => {
				console.log(response);
				if (response.status === "connected"){
					this.fb.api('/me?fields=email,name,picture')
						.then((user: any) => {
							console.log("User profile from facebook api:", user);
              this.userApi.socialLogin( user.email,
                                        user.id,
                                        user.name,
                                        "facebook",
                                        user.picture.data.url )
                .subscribe((data) => {
                  console.log("Data back from server to framework: ", data);
                  if (data.success){
                    this.frameworkConfigService.showUserControls= true;
                    this.frameworkConfigService.showNavbar=true;
                    this.router.navigate(['/profile']);
                  }else{
                    this.submitting = false;
                    this.formError = data.msg;
                    setTimeout(()=>{ this.formError = ""}, 2000);
                    this.router.navigate(['/login']);
                  }
                },
                (err)=> {
                  this.submitting = false;
                  console.log('social login error: ', err);
                  this.formError = err;
                  setTimeout(()=>{ this.formError = ""}, 2000);
                })
            })
						.catch((error: any) => console.error("Error in getting Facebook profile: ", error));
				}else{
					console.log("NOT CONNECTED!");
				}
			})
      .catch((error: any) => console.error("Facebook login error: ", error));
	}

  onSocialLogin(provider){
    this.socialAuth.login(provider).subscribe(
      (profile) => {
          if (!profile){
            this.formError = "Error in fetching data from social network!";
            setTimeout(()=>{ this.formError = ""}, 2000);
          }else{
            this.submitting = true;
            console.log("your auth user profile consists of: ", profile);
            this.userApi.socialLogin( profile['email'],
                                      profile['uid'],
                                      profile['name'],
                                      profile['provider'],
                                      profile['image'] )
              .subscribe((data) => {
                console.log("Data back from server to framework: ", data);
                if (data.success){
                  this.frameworkConfigService.showUserControls= true;
                  this.frameworkConfigService.showNavbar=true;
                  this.router.navigate(['/profile']);
                }else{
                  this.submitting = false;
                  this.formError = data.msg;
                  setTimeout(()=>{ this.formError = ""}, 2000);
                  this.router.navigate(['/login']);
                }
              },
              (err)=> {
                this.submitting = false;
                console.log('got error: ', err);
                this.formError = err;
                setTimeout(()=>{ this.formError = ""}, 2000);
              }
      );
    }
  })
  }

}


text:
<!DOCTYPE html>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Home _ Grails</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="./Home_files/main_modularized.css">
<link rel="stylesheet" href="./Home_files/page-layout.css">
<link rel="stylesheet" href="./Home_files/message-error.css">
<link rel="stylesheet" href="./Home_files/scaffold-navigate.css">
<link rel="stylesheet" href="./Home_files/scaffold-create-edit.css">
<link rel="stylesheet" href="./Home_files/scaffold-list.css">
<link rel="stylesheet" href="./Home_files/scaffold-paginate.css">
<link rel="stylesheet" href="./Home_files/scaffold-action.css">
<link rel="stylesheet" href="./Home_files/scaffold.css">
<link rel="stylesheet" href="./Home_files/mobile.css">
<link rel="stylesheet" href="./Home_files/application.css">

        <meta name="layout" content="main">

Home

Controllers

            Skip to content…

Application Status

Environment: development

App profile: web

App version: 0.1

Grails version: 3.1.0.BUILD-SNAPSHOT

Groovy version: 2.4.5

JVM version: 1.8.0_66

Reloading active: true

Artefacts

Controllers: 4

Domains: 1

Services: 3

Tag Libraries: 14

Installed Plugins

restResponder - 3.1.0.BUILD-SNAPSHOT

dataBinding - 3.1.0.BUILD-SNAPSHOT

fields - 2.1.1

i18n - 3.1.0.BUILD-SNAPSHOT

eventBus - 3.1.0.BUILD-SNAPSHOT

core - 3.1.0.BUILD-SNAPSHOT

codecs - 3.1.0.BUILD-SNAPSHOT

controllers - 3.1.0.BUILD-SNAPSHOT

urlMappings - 3.1.0.BUILD-SNAPSHOT

assetPipeline - 3.0.14

dataSource - 3.1.0.BUILD-SNAPSHOT

controllersAsync - 3.1.0.BUILD-SNAPSHOT

mimeTypes - 3.1.0.BUILD-SNAPSHOT

interceptors - 3.1.0.BUILD-SNAPSHOT

domainClass - 3.1.0.BUILD-SNAPSHOT

filters - 3.1.0.BUILD-SNAPSHOT

converters - 3.1.0.BUILD-SNAPSHOT

hibernate - 5.0.0.RC3

groovyPages - 3.1.0.BUILD-SNAPSHOT

services - 3.1.0.BUILD-SNAPSHOT

scaffolding - 3.1.2

cache - 3.0.1

Welcome to Grails

Congratulations, you have successfully started your first Grails application! At the moment
                   this is the default page, feel free to modify it to either redirect to a controller or display whatever
                   content you may choose. Below is a list of controllers that are currently deployed in this application,
                   click on each to execute its default action:

Available Controllers:

profilerefresh.BarController

profilerefresh.ErrorController

profilerefresh.Foo2Controller

profilerefresh.FooController

Loading…

                    Powered by
                    Grails


text:
/**********************************************************************

  Audacity: A Digital Audio Editor

  Ruler.cpp

  Dominic Mazzoni

*******************************************************************//**

\class Ruler
\brief Used to display a Ruler.

  This is a generic class which can be used to display just about
  any kind of ruler.

  At a minimum, the user must specify the dimensions of the
  ruler, its orientation (horizontal or vertical), and the
  values displayed at the two ends of the ruler (min and max).
  By default, this class will display tick marks at reasonable
  round numbers and fractions, for example, 100, 50, 10, 5, 1,
  0.5, 0.1, etc.

  The class is designed to display a small handful of
  labeled Major ticks, and a few Minor ticks between each of
  these.  Minor ticks are labeled if there is enough space.
  Labels will never run into each other.

  In addition to Real numbers, the Ruler currently supports
  two other formats for its display:

  Integer - never shows tick marks for fractions of an integer

  Time - Assumes values represent seconds, and labels the tick
         marks in "HH:MM:SS" format, e.g. 4000 seconds becomes
         "1:06:40", for example.  Will display fractions of
         a second, and tick marks are all reasonable round
         numbers for time (i.e. 15 seconds, 30 seconds, etc.)
*//***************************************************************//**

\class RulerPanel
\brief RulerPanel class allows you to work with a Ruler like
  any other wxWindow.

*//***************************************************************//**

\class Ruler::Label
\brief An array of these created by the Ruler is used to determine
what and where text annotations to the numbers on the Ruler get drawn.

\todo Check whether Ruler is costing too much time in malloc/free of
array of Ruler::Label.

*//******************************************************************/

#include "../Audacity.h"

#include <math.h>

#include <wx/dcscreen.h>
#include <wx/dcmemory.h>
#include <wx/dcbuffer.h>
#include <wx/settings.h>

#include "../Internat.h"
#include "../Project.h"
#include "Ruler.h"
#include "../toolbars/ControlToolBar.h"
#include "../Theme.h"
#include "../AllThemeResources.h"
#include "../Experimental.h"
#include "../TimeTrack.h"

#define max(a,b)  ( (a<b)?b:a )

#define SELECT_TOLERANCE_PIXEL 10

#define PLAY_REGION_TRIANGLE_SIZE 6
#define PLAY_REGION_RECT_WIDTH 1
#define PLAY_REGION_RECT_HEIGHT 3
#define PLAY_REGION_GLOBAL_OFFSET_Y 7

//
// Ruler
//

Ruler::Ruler()
{
   mMin = 0.0;
   mMax = 100.0;
   mOrientation = wxHORIZONTAL;
   mSpacing = 6;
   mHasSetSpacing = false;
   mFormat = RealFormat;
   mFlip = false;
   mLog = false;
   mLabelEdges = false;
   mUnits = wxT("");

   mLeft = -1;
   mTop = -1;
   mRight = -1;
   mBottom = -1;
   mbTicksOnly = true;
   mbTicksAtExtremes = false;
   mTickColour = wxColour(153,153,153);
   mPen.SetColour(mTickColour);

   // Note: the font size is now adjusted automatically whenever
   // Invalidate is called on a horizontal Ruler, unless the user
   // calls SetFonts manually.  So the defaults here are not used
   // often.

   int fontSize = 10;
#ifdef __WXMSW__
   fontSize = 8;
#endif

   mMinorMinorFont = new wxFont(fontSize-1, wxSWISS, wxNORMAL, wxNORMAL);
   mMinorFont = new wxFont(fontSize, wxSWISS, wxNORMAL, wxNORMAL);
   mMajorFont = new wxFont(fontSize, wxSWISS, wxNORMAL, wxBOLD);
   mUserFonts = false;

   #ifdef __WXMAC__
   mMinorMinorFont->SetNoAntiAliasing(true);
   mMinorFont->SetNoAntiAliasing(true);
   mMajorFont->SetNoAntiAliasing(true);
   #endif

   mMajorLabels = 0;
   mMinorLabels = 0;
   mMinorMinorLabels = 0;
   mLengthOld = 0;
   mLength = 0;
   mBits = NULL;
   mUserBits = NULL;
   mUserBitLen = 0;

   mValid = false;

   mCustom = false;
   mbMinor = true;

   mGridLineLength = 0;
   mMajorGrid = false;
   mMinorGrid = false;
}

Ruler::~Ruler()
{
   Invalidate();  // frees up our arrays
   if( mUserBits )
      delete [] mUserBits;//JKC
   delete mMinorFont;
   delete mMajorFont;
   delete mMinorMinorFont;

   if (mMajorLabels)
      delete[] mMajorLabels;
   if (mMinorLabels)
      delete[] mMinorLabels;
   if (mMinorMinorLabels)
      delete[] mMinorMinorLabels;
}

void Ruler::SetFormat(RulerFormat format)
{
   // IntFormat, RealFormat, RealLogFormat, TimeFormat, or LinearDBFormat

   if (mFormat != format) {
      mFormat = format;

      Invalidate();
   }
}

void Ruler::SetLog(bool log)
{
   // Logarithmic

   if (mLog != log) {
      mLog = log;

      Invalidate();
   }
}

void Ruler::SetUnits(wxString units)
{
   // Specify the name of the units (like "dB") if you
   // want numbers like "1.6" formatted as "1.6 dB".

   if (mUnits != units) {
      mUnits = units;

      Invalidate();
   }
}

void Ruler::SetOrientation(int orient)
{
   // wxHORIZONTAL || wxVERTICAL

   if (mOrientation != orient) {
      mOrientation = orient;

      if (mOrientation == wxVERTICAL && !mHasSetSpacing)
         mSpacing = 2;

      Invalidate();
   }
}

void Ruler::SetRange(double min, double max)
{
   // For a horizontal ruler,
   // min is the value in the center of pixel "left",
   // max is the value in the center of pixel "right".

   if (mMin != min || mMax != max) {
      mMin = min;
      mMax = max;

      Invalidate();
   }
}

void Ruler::SetSpacing(int spacing)
{
   mHasSetSpacing = true;

   if (mSpacing != spacing) {
      mSpacing = spacing;

      Invalidate();
   }
}

void Ruler::SetLabelEdges(bool labelEdges)
{
   // If this is true, the edges of the ruler will always
   // receive a label.  If not, the nearest round number is
   // labeled (which may or may not be the edge).

   if (mLabelEdges != labelEdges) {
      mLabelEdges = labelEdges;

      Invalidate();
   }
}

void Ruler::SetFlip(bool flip)
{
   // If this is true, the orientation of the tick marks
   // is reversed from the default; eg. above the line
   // instead of below

   if (mFlip != flip) {
      mFlip = flip;

      Invalidate();
   }
}

void Ruler::SetMinor(bool value)
{
   mbMinor = value;
}

void Ruler::SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont)
{
   *mMinorMinorFont = minorMinorFont;
   *mMinorFont = minorFont;
   *mMajorFont = majorFont;

   #ifdef __WXMAC__
   mMinorMinorFont->SetNoAntiAliasing(true);
   mMinorFont->SetNoAntiAliasing(true);
   mMajorFont->SetNoAntiAliasing(true);
   #endif

   // Won't override these fonts
   mUserFonts = true;

   Invalidate();
}

void Ruler::OfflimitsPixels(int start, int end)
{
   int i;

   if (!mUserBits) {
      if (mOrientation == wxHORIZONTAL)
         mLength = mRight-mLeft;
      else
         mLength = mBottom-mTop;
      mUserBits = new int[mLength+1];
      for(i=0; i<=mLength; i++)
         mUserBits[i] = 0;
      mUserBitLen  = mLength+1;
   }

   if (end < start) {
      i = end;
      end = start;
      start = i;
   }

   if (start < 0)
      start = 0;
   if (end > mLength)
      end = mLength;

   for(i=start; i<=end; i++)
      mUserBits[i] = 1;
}

void Ruler::SetBounds(int left, int top, int right, int bottom)
{
   if (mLeft != left || mTop != top ||
       mRight != right || mBottom != bottom) {
      mLeft = left;
      mTop = top;
      mRight = right;
      mBottom = bottom;

      Invalidate();
   }
}

void Ruler::Invalidate()
{
   mValid = false;

   if (mOrientation == wxHORIZONTAL)
      mLength = mRight-mLeft;
   else
      mLength = mBottom-mTop;

   if (mBits) {
      delete [] mBits;
      mBits = NULL;
   }
   if (mUserBits && mLength+1 != mUserBitLen) {
      delete[] mUserBits;
      mUserBits = NULL;
      mUserBitLen = 0;
   }
}

void Ruler::FindLinearTickSizes(double UPP)
{
   // Given the dimensions of the ruler, the range of values it
   // has to display, and the format (i.e. Int, Real, Time),
   // figure out how many units are in one Minor tick, and
   // in one Major tick.
   //
   // The goal is to always put tick marks on nice round numbers
   // that are easy for humans to grok.  This is the most tricky
   // with time.

   double d;

   // As a heuristic, we want at least 16 pixels
   // between each minor tick
   double units = 16 * fabs(UPP);

   mDigits = 0;

   switch(mFormat) {
   case LinearDBFormat:
      if (units < 0.001) {
         mMinor = 0.001;
         mMajor = 0.005;
         return;
      }
      if (units < 0.01) {
         mMinor = 0.01;
         mMajor = 0.05;
         return;
      }
      if (units < 0.1) {
         mMinor = 0.1;
         mMajor = 0.5;
         return;
      }
      if (units < 1.0) {
         mMinor = 1.0;
         mMajor = 6.0;
         return;
      }
      if (units < 3.0) {
         mMinor = 3.0;
         mMajor = 12.0;
         return;
      }
      if (units < 6.0) {
         mMinor = 6.0;
         mMajor = 24.0;
         return;
      }
      if (units < 12.0) {
         mMinor = 12.0;
         mMajor = 48.0;
         return;
      }
      if (units < 24.0) {
         mMinor = 24.0;
         mMajor = 96.0;
         return;
      }
      d = 20.0;
      for(;;) {
         if (units < d) {
            mMinor = d;
            mMajor = d*5.0;
            return;
         }
         d *= 5.0;
         if (units < d) {
            mMinor = d;
            mMajor = d*5.0;
            return;
         }
         d *= 2.0;
      }
      break;

   case IntFormat:
      d = 1.0;
      for(;;) {
         if (units < d) {
            mMinor = d;
            mMajor = d*5.0;
            return;
         }
         d *= 5.0;
         if (units < d) {
            mMinor = d;
            mMajor = d*2.0;
            return;
         }
         d *= 2.0;
      }
      break;

   case TimeFormat:
      if (units > 0.5) {
         if (units < 1.0) { // 1 sec
            mMinor = 1.0;
            mMajor = 5.0;
            return;
         }
         if (units < 5.0) { // 5 sec
            mMinor = 5.0;
            mMajor = 15.0;
            return;
         }
         if (units < 10.0) {
            mMinor = 10.0;
            mMajor = 30.0;
            return;
         }
         if (units < 15.0) {
            mMinor = 15.0;
            mMajor = 60.0;
            return;
         }
         if (units < 30.0) {
            mMinor = 30.0;
            mMajor = 60.0;
            return;
         }
         if (units < 60.0) { // 1 min
            mMinor = 60.0;
            mMajor = 300.0;
            return;
         }
         if (units < 300.0) { // 5 min
            mMinor = 300.0;
            mMajor = 900.0;
            return;
         }
         if (units < 600.0) { // 10 min
            mMinor = 600.0;
            mMajor = 1800.0;
            return;
         }
         if (units < 900.0) { // 15 min
            mMinor = 900.0;
            mMajor = 3600.0;
            return;
         }
         if (units < 1800.0) { // 30 min
            mMinor = 1800.0;
            mMajor = 3600.0;
            return;
         }
         if (units < 3600.0) { // 1 hr
            mMinor = 3600.0;
            mMajor = 6*3600.0;
            return;
         }
         if (units < 6*3600.0) { // 6 hrs
            mMinor = 6*3600.0;
            mMajor = 24*3600.0;
            return;
         }
         if (units < 24*3600.0) { // 1 day
            mMinor = 24*3600.0;
            mMajor = 7*24*3600.0;
            return;
         }

         mMinor = 24.0 * 7.0 * 3600.0; // 1 week
         mMajor = 24.0 * 7.0 * 3600.0;
      }

      // Otherwise fall through to RealFormat
      // (fractions of a second should be dealt with
      // the same way as for RealFormat)

   case RealFormat:
      d = 0.000001;
      // mDigits is number of digits after the decimal point.
      mDigits = 6;
      for(;;) {
         if (units < d) {
            mMinor = d;
            mMajor = d*5.0;
            return;
         }
         d *= 5.0;
         if (units < d) {
            mMinor = d;
            mMajor = d*2.0;
            return;
         }
         d *= 2.0;
         mDigits--;
         // More than 10 digit numbers?  Something is badly wrong.
         // Probably units is coming in with too high a value.
         wxASSERT( mDigits >= -10 );
         if( mDigits < -10 )
            break;
      }
      mMinor = d;
      mMajor = d * 2.0;
      break;

   case RealLogFormat:
      d = 0.000001;
      // mDigits is number of digits after the decimal point.
      mDigits = 6;
      for(;;) {
         if (units < d) {
            mMinor = d;
            mMajor = d*5.0;
            return;
         }
         d *= 5.0;
         if (units < d) {
            mMinor = d;
            mMajor = d*2.0;
            return;
         }
         d *= 2.0;
         mDigits--;
         // More than 10 digit numbers?  Something is badly wrong.
         // Probably units is coming in with too high a value.
         wxASSERT( mDigits >= -10 );
         if( mDigits < -10 )
            break;
      }
      mDigits++;
      mMinor = d;
      mMajor = d * 2.0;
      break;
   }
}

wxString Ruler::LabelString(double d, bool major)
{
   // Given a value, turn it into a string according
   // to the current ruler format.  The number of digits of
   // accuracy depends on the resolution of the ruler,
   // i.e. how far zoomed in or out you are.

   wxString s;

   // Replace -0 with 0
   if (d < 0.0 && d+mMinor > 0.0)
      d = 0.0;

   switch(mFormat) {
   case IntFormat:
      s.Printf(wxT("%d"), (int)floor(d+0.5));
      break;
   case LinearDBFormat:
      if (mMinor >= 1.0)
         s.Printf(wxT("%d"), (int)floor(d+0.5));
      else {
         int precision = -log10(mMinor);
         s.Printf(wxT("%.*f"), precision, d);
      }
      break;
   case RealFormat:
      if (mMinor >= 1.0)
         s.Printf(wxT("%d"), (int)floor(d+0.5));
      else {
         s.Printf(wxString::Format(wxT("%%.%df"), mDigits), d);
      }
      break;
   case RealLogFormat:
      if (mMinor >= 1.0)
         s.Printf(wxT("%d"), (int)floor(d+0.5));
      else {
         s.Printf(wxString::Format(wxT("%%.%df"), mDigits), d);
      }
      break;
   case TimeFormat:
      if (major) {
         if (d < 0) {
            s = wxT("-");
            d = -d;
         }

         #if ALWAYS_HH_MM_SS
         int secs = (int)(d + 0.5);
         if (mMinor >= 1.0) {
            s.Printf(wxT("%d:%02d:%02d"), secs/3600, (secs/60)%60, secs%60);
         }
         else {
            wxString t1, t2, format;
            t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60);
            format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits);
            t2.Printf(format.c_str(), fmod(d, 60.0));
            s += t1 + t2;
         }
         break;
         #endif

         if (mMinor >= 3600.0) {
            int hrs = (int)(d / 3600.0 + 0.5);
            wxString h;
            h.Printf(wxT("%d:00:00"), hrs);
            s += h;
         }
         else if (mMinor >= 60.0) {
            int minutes = (int)(d / 60.0 + 0.5);
            wxString m;
            if (minutes >= 60)
               m.Printf(wxT("%d:%02d:00"), minutes/60, minutes%60);
            else
               m.Printf(wxT("%d:00"), minutes);
            s += m;
         }
         else if (mMinor >= 1.0) {
            int secs = (int)(d + 0.5);
            wxString t;
            if (secs >= 3600)
               t.Printf(wxT("%d:%02d:%02d"), secs/3600, (secs/60)%60, secs%60);
            else if (secs >= 60)
               t.Printf(wxT("%d:%02d"), secs/60, secs%60);
            else
               t.Printf(wxT("%d"), secs);
            s += t;
         }
         else {
            // The casting to float is working around an issue where 59 seconds
            // would show up as 60 when using g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3.
            int secs = (int)(float)(d);
            wxString t1, t2, format;

            if (secs >= 3600)
               t1.Printf(wxT("%d:%02d:"), secs/3600, (secs/60)%60);
            else if (secs >= 60)
               t1.Printf(wxT("%d:"), secs/60);

            if (secs >= 60)
               format.Printf(wxT("%%0%d.%dlf"), mDigits+3, mDigits);
            else
               format.Printf(wxT("%%%d.%dlf"), mDigits+3, mDigits);
            // The casting to float is working around an issue where 59 seconds
            // would show up as 60 when using g++ (Ubuntu 4.3.3-5ubuntu4) 4.3.3.
            t2.Printf(format.c_str(), fmod((float)d, (float)60.0));

            s += t1 + t2;
         }
      }
      else {
      }
   }

   if (mUnits != wxT(""))
      s = (s + mUnits);

   return s;
}

void Ruler::Tick(int pos, double d, bool major, bool minor)
{
   wxString l;
   wxCoord strW, strH, strD, strL;
   int strPos, strLen, strLeft, strTop;

   // FIXME: We don't draw a tick if of end of our label arrays
   // But we shouldn't have an array of labels.
   if( mNumMinorMinor >= mLength )
      return;
   if( mNumMinor >= mLength )
      return;
   if( mNumMajor >= mLength )
      return;

   Label *label;
   if (major)
      label = &mMajorLabels[mNumMajor++];
   else if (minor)
      label = &mMinorLabels[mNumMinor++];
   else
      label = &mMinorMinorLabels[mNumMinorMinor++];

   label->pos = pos;
   label->lx = mLeft - 1000; // don't display
   label->ly = mTop - 1000;  // don't display
   label->text = wxT("");

   mDC->SetFont(major? *mMajorFont: minor? *mMinorFont : *mMinorMinorFont);
   l = LabelString(d, major);
   mDC->GetTextExtent(l, &strW, &strH, &strD, &strL);

   if (mOrientation == wxHORIZONTAL) {
      strLen = strW;
      strPos = pos - strW/2;
      if (strPos < 0)
         strPos = 0;
      if (strPos + strW >= mLength)
         strPos = mLength - strW;
      strLeft = mLeft + strPos;
      if (mFlip) {
         strTop = mTop + 4;
         mMaxHeight = max(mMaxHeight, strH + 4);
      }
      else {
         strTop =-strH-mLead;
         mMaxHeight = max(mMaxHeight, strH + 6);
      }
   }
   else {
      strLen = strH;
      strPos = pos - strH/2;
      if (strPos < 0)
         strPos = 0;
      if (strPos + strH >= mLength)
         strPos = mLength - strH;
      strTop = mTop + strPos;
      if (mFlip) {
         strLeft = mLeft + 5;
         mMaxWidth = max(mMaxWidth, strW + 5);
      }
      else
         strLeft =-strW-6;
   }

   // FIXME: we shouldn't even get here if strPos < 0.
   // Ruler code currently does  not handle very small or
   // negative sized windows (i.e. don't draw) properly.
   if( strPos < 0 )
      return;

   // See if any of the pixels we need to draw this
   // label is already covered

   int i;
   for(i=0; i<strLen; i++)
      if (mBits[strPos+i])
         return;

   // If not, position the label and give it text

   label->lx = strLeft;
   label->ly = strTop;
   label->text = l;

   // And mark these pixels, plus some surrounding
   // ones (the spacing between labels), as covered
   int leftMargin = mSpacing;
   if (strPos < leftMargin)
      leftMargin = strPos;
   strPos -= leftMargin;
   strLen += leftMargin;

   int rightMargin = mSpacing;
   if (strPos + strLen > mLength - mSpacing)
      rightMargin = mLength - strPos - strLen;
   strLen += rightMargin;

   for(i=0; i<strLen; i++)
      mBits[strPos+i] = 1;

   wxRect r(strLeft, strTop, strW, strH);
   mRect.Union(r);

}

void Ruler::TickCustom(int labelIdx, bool major, bool minor)
{
   //This should only used in the mCustom case
   // Many code comes from 'Tick' method: this should
   // be optimized.

   int pos;
   wxString l;
   wxCoord strW, strH, strD, strL;
   int strPos, strLen, strLeft, strTop;

   // FIXME: We don't draw a tick if of end of our label arrays
   // But we shouldn't have an array of labels.
   if( mNumMinor >= mLength )
      return;
   if( mNumMajor >= mLength )
      return;

   Label *label;
   if (major)
      label = &mMajorLabels[labelIdx];
   else if (minor)
      label = &mMinorLabels[labelIdx];
   else
      label = &mMinorMinorLabels[labelIdx];

   pos = label->pos;         // already stored in label class
   l   = label->text;
   label->lx = mLeft - 1000; // don't display
   label->ly = mTop - 1000;  // don't display

   mDC->SetFont(major? *mMajorFont: minor? *mMinorFont : *mMinorMinorFont);

   mDC->GetTextExtent(l, &strW, &strH, &strD, &strL);

   if (mOrientation == wxHORIZONTAL) {
      strLen = strW;
      strPos = pos - strW/2;
      if (strPos < 0)
         strPos = 0;
      if (strPos + strW >= mLength)
         strPos = mLength - strW;
      strLeft = mLeft + strPos;
      if (mFlip) {
         strTop = mTop + 4;
         mMaxHeight = max(mMaxHeight, strH + 4);
      }
      else {

         strTop =-strH-mLead;
         strTop = mTop- mLead+4;// More space was needed...
         mMaxHeight = max(mMaxHeight, strH + 6);
      }
   }
   else {
      strLen = strH;
      strPos = pos - strH/2;
      if (strPos < 0)
         strPos = 0;
      if (strPos + strH >= mLength)
         strPos = mLength - strH;
      strTop = mTop + strPos;
      if (mFlip) {
         strLeft = mLeft + 5;
         mMaxWidth = max(mMaxWidth, strW + 5);
      }
      else {

         strLeft =-strW-6;
       }
   }

   // FIXME: we shouldn't even get here if strPos < 0.
   // Ruler code currently does  not handle very small or
   // negative sized windows (i.e. don't draw) properly.
   if( strPos < 0 )
      return;

   // See if any of the pixels we need to draw this
   // label is already covered

   int i;
   for(i=0; i<strLen; i++)
      if (mBits[strPos+i])
         return;

   // If not, position the label

   label->lx = strLeft;
   label->ly = strTop;

   // And mark these pixels, plus some surrounding
   // ones (the spacing between labels), as covered
   int leftMargin = mSpacing;
   if (strPos < leftMargin)
      leftMargin = strPos;
   strPos -= leftMargin;
   strLen += leftMargin;

   int rightMargin = mSpacing;
   if (strPos + strLen > mLength - mSpacing)
      rightMargin = mLength - strPos - strLen;
   strLen += rightMargin;

   for(i=0; i<strLen; i++)
      mBits[strPos+i] = 1;

   wxRect r(strLeft, strTop, strW, strH);
   mRect.Union(r);

}

void Ruler::Update()
{
  Update(NULL);
}

void Ruler::Update(TimeTrack* timetrack)// Envelope *speedEnv, long minSpeed, long maxSpeed )
{
   // This gets called when something has been changed
   // (i.e. we've been invalidated).  Recompute all
   // tick positions and font size.

   int i;
   int j;

   if (!mUserFonts) {
     int fontSize = 4;
     wxCoord strW, strH, strD, strL;
     wxString exampleText = wxT("0.9");   //ignored for height calcs on all platforms
     int desiredPixelHeight;

     if (mOrientation == wxHORIZONTAL)
       desiredPixelHeight = mBottom-mTop-5; // height less ticks and 1px gap
     else
         desiredPixelHeight = 12;   // why 12?  10 -> 12 seems to be max/min

     if (desiredPixelHeight < 10)//8)
       desiredPixelHeight = 10;//8;
     if (desiredPixelHeight > 12)
       desiredPixelHeight = 12;

      // Keep making the font bigger until it's too big, then subtract one.
      mDC->SetFont(wxFont(fontSize, wxSWISS, wxNORMAL, wxBOLD));
      mDC->GetTextExtent(exampleText, &strW, &strH, &strD, &strL);
      while( (strH-strD-strL) <= desiredPixelHeight && fontSize < 40) {
         fontSize++;
         mDC->SetFont(wxFont(fontSize, wxSWISS, wxNORMAL, wxBOLD));
         mDC->GetTextExtent(exampleText, &strW, &strH, &strD, & strL);
      }
      fontSize--;
      mDC->SetFont(wxFont(fontSize, wxSWISS, wxNORMAL, wxNORMAL));
      mDC->GetTextExtent(exampleText, &strW, &strH, &strD, &strL);
      mLead = strL;

     if (mMajorFont)
        delete mMajorFont;
     mMajorFont = new wxFont(fontSize, wxSWISS, wxNORMAL, wxBOLD);

     if (mMinorFont)
        delete mMinorFont;
     mMinorFont = new wxFont(fontSize, wxSWISS, wxNORMAL, wxNORMAL);

     if (mMinorMinorFont)
        delete mMinorMinorFont;
     mMinorMinorFont = new wxFont(fontSize-1, wxSWISS, wxNORMAL, wxNORMAL);
   }

   // If ruler is being resized, we could end up with it being too small.
   // Values of mLength of zero or below cause bad array allocations and
   // division by zero.  So...
   // IF too small THEN bail out and don't draw.
   if( mLength <= 0 )
      return;

   if (mOrientation == wxHORIZONTAL) {
      mMaxWidth = mLength;
      mMaxHeight = 0;
      mRect = wxRect(0,0, mLength,0);
   }
   else {
      mMaxWidth = 0;
      mMaxHeight = mLength;
      mRect = wxRect(0,0, 0,mLength);
   }

   // FIXME: Surely we do not need to allocate storage for the labels?
   // We can just recompute them as we need them?  Yes, but only if
   // mCustom is false!!!!

   if(!mCustom) {
      mNumMajor = 0;
      mNumMinor = 0;
      mNumMinorMinor = 0;
      if (mLength!=mLengthOld) {
         if (mMajorLabels)
            delete[] mMajorLabels;
         mMajorLabels = new Label[mLength+1];
         if (mMinorLabels)
            delete[] mMinorLabels;
         mMinorLabels = new Label[mLength+1];
         if (mMinorMinorLabels)
            delete[] mMinorMinorLabels;
         mMinorMinorLabels = new Label[mLength+1];
         mLengthOld = mLength;
      }
   }

   if (mBits)
      delete[] mBits;
   mBits = new int[mLength+1];
   if (mUserBits)
      for(i=0; i<=mLength; i++)
         mBits[i] = mUserBits[i];
   else
      for(i=0; i<=mLength; i++)
         mBits[i] = 0;

   // *************** Label calculation routine **************
   if(mCustom == true) {

      // SET PARAMETER IN MCUSTOM CASE
      // Works only with major labels

      int numLabel = mNumMajor;

      i = 0;
      while((i<numLabel) && (i<=mLength)) {

         TickCustom(i, true, false);
         i++;
      }

   } else if(mLog==false) {

      double UPP = (mMax-mMin)/mLength;  // Units per pixel
      FindLinearTickSizes(UPP);

      // Left and Right Edges
      if (mLabelEdges) {
         Tick(0, mMin, true, false);
         Tick(mLength, mMax, true, false);
      }

      // Zero (if it's in the middle somewhere)
      if (mMin * mMax < 0.0) {
         int mid = (int)(mLength*(mMin/(mMin-mMax)) + 0.5);
         Tick(mid, 0.0, true, false);
      }

      double sg = UPP > 0.0? 1.0: -1.0;

      // Major ticks
      double d, warpedD;
      d = mMin - UPP/2;
      if(timetrack)
         warpedD = timetrack->ComputeWarpedLength(0.0, d);
      else
         warpedD = d;
      // using ints for majorint doesn't work, as
      // majorint will overflow and be negative at high zoom.
      double majorInt = floor(sg * warpedD / mMajor);
      i = -1;
      while(i <= mLength) {
         i++;
         if(timetrack)
            warpedD += timetrack->ComputeWarpedLength(d, d + UPP);
         else
            warpedD += UPP;
         d += UPP;

         if (floor(sg * warpedD / mMajor) > majorInt) {
            majorInt = floor(sg * warpedD / mMajor);
            Tick(i, sg * majorInt * mMajor, true, false);
         }
      }

      // Minor ticks
      d = mMin - UPP/2;
      if(timetrack)
         warpedD = timetrack->ComputeWarpedLength(0.0, d);
      else
         warpedD = d;
      // using ints for majorint doesn't work, as
      // majorint will overflow and be negative at high zoom.
      // MB: I assume the same applies to minorInt
      double minorInt = floor(sg * warpedD / mMinor);
      i = -1;
      while(i <= mLength) {
         i++;
         if(timetrack)
            warpedD += timetrack->ComputeWarpedLength(d, d + UPP);
         else
            warpedD += UPP;
         d += UPP;

         if (floor(sg * warpedD / mMinor) > minorInt) {
            minorInt = floor(sg * warpedD / mMinor);
            Tick(i, sg * minorInt * mMinor, false, true);
         }
      }

      // Left and Right Edges
      if (mLabelEdges) {
         Tick(0, mMin, true, false);
         Tick(mLength, mMax, true, false);
      }

   }
   else {
      // log case
      mDigits=2;	//TODO: implement dynamic digit computation
      double loLog = log10(mMin);
      double hiLog = log10(mMax);
      double scale = mLength/(hiLog - loLog);
      int loDecade = (int) floor(loLog);

      int pos;
      double val;
      double startDecade = pow(10., (double)loDecade);

      // Major ticks are the decades
      double decade = startDecade;
      double delta=hiLog-loLog, steps=fabs(delta);
      double step = delta>=0 ? 10 : 0.1;
      double rMin=wxMin(mMin, mMax), rMax=wxMax(mMin, mMax);
      for(i=0; i<=steps; i++)
      {  // if(i!=0)
         {  val = decade;
            if(val > rMin && val < rMax) {
               pos = (int)(((log10(val) - loLog)*scale)+0.5);
               Tick(pos, val, true, false);
            }
         }
         decade *= step;
      }

      // Minor ticks are multiples of decades
      decade = startDecade;
      float start, end, mstep;
      if (delta > 0)
      {  start=2; end=10; mstep=1;
      }else
      {  start=9; end=1; mstep=-1;
      }
      steps++;
      for(i=0; i<=steps; i++) {
         for(j=start; j!=end; j+=mstep) {
            val = decade * j;
            if(val >= rMin && val < rMax) {
               pos = (int)(((log10(val) - loLog)*scale)+0.5);
               Tick(pos, val, false, true);
            }
         }
         decade *= step;
      }

      // MinorMinor ticks are multiples of decades
      decade = startDecade;
      if (delta > 0)
      {  start= 10; end=100; mstep= 1;
      }else
      {  start=100; end= 10; mstep=-1;
      }
      steps++;
      for(i=0; i<=steps; i++) {
         for(int f=start; f!=int(end); f+=mstep) {
            if (int(f/10)!=f/10.0f) {
               val = decade * f/10;
               if(val >= rMin && val < rMax) {
                  pos = (int)(((log10(val) - loLog)*scale)+0.5);
               Tick(pos, val, false, false);
               }
            }
         }
         decade *= step;
      }
   }

   int displacementx=0, displacementy=0;
   if (!mFlip) {
      if (mOrientation==wxHORIZONTAL) {
         int d=mTop+mRect.GetHeight()+5;
         mRect.Offset(0,d);
         mRect.Inflate(0,5);
         displacementx=0;
         displacementy=d;
      }
      else {
         int d=mLeft-mRect.GetLeft()+5;
         mRect.Offset(d,0);
         mRect.Inflate(5,0);
         displacementx=d;
         displacementy=0;
      }
   }
   else {
      if (mOrientation==wxHORIZONTAL) {
         mRect.Inflate(0,5);
         displacementx=0;
         displacementy=0;
      }
   }
   for(i=0; i<mNumMajor; i++) {
      mMajorLabels[i].lx+= displacementx;
      mMajorLabels[i].ly+= displacementy;
   }
   for(i=0; i<mNumMinor; i++) {
      mMinorLabels[i].lx+= displacementx;
      mMinorLabels[i].ly+= displacementy;
   }
   for(i=0; i<mNumMinorMinor; i++) {
      mMinorMinorLabels[i].lx+= displacementx;
      mMinorMinorLabels[i].ly+= displacementy;
   }
   mMaxWidth = mRect.GetWidth ();
   mMaxHeight= mRect.GetHeight();
   mValid = true;
}

void Ruler::Draw(wxDC& dc)
{
   Draw( dc, NULL);
}

void Ruler::Draw(wxDC& dc, TimeTrack* timetrack)
{
   mDC = &dc;
   if( mLength <=0 )
      return;

   if (!mValid)
      Update(timetrack);

#ifdef EXPERIMENTAL_THEMING
   mDC->SetPen(mPen);
   mDC->SetTextForeground(mTickColour);
#else
   mDC->SetPen(*wxBLACK_PEN);
   mDC->SetTextForeground(*wxBLACK);
#endif

   // Draws a long line the length of the ruler.
   if( !mbTicksOnly )
   {
      if (mOrientation == wxHORIZONTAL) {
         if (mFlip)
            mDC->DrawLine(mLeft, mTop, mRight+1, mTop);
         else
            mDC->DrawLine(mLeft, mBottom, mRight+1, mBottom);
      }
      else {
         if (mFlip)
            mDC->DrawLine(mLeft, mTop, mLeft, mBottom+1);
         else
         {
            // These calculations tappear to be wrong, and to never have been used (so not tested) prior to MixerBoard.
            //    mDC->DrawLine(mRect.x-mRect.width, mTop, mRect.x-mRect.width, mBottom+1);
            const int nLineX = mRight - 1;
            mDC->DrawLine(nLineX, mTop, nLineX, mBottom+1);
         }
      }
   }

   int i;

   mDC->SetFont(*mMajorFont);

   // We may want to not show the ticks at the extremes,
   // though still showing the labels.
   // This gives a better look when the ruler is on a bevelled
   // button, since otherwise the tick is drawn on the bevel.
   int iMaxPos = (mOrientation==wxHORIZONTAL)? mRight : mBottom-5;

   for(i=0; i<mNumMajor; i++) {
      int pos = mMajorLabels[i].pos;

      if( mbTicksAtExtremes || ((pos!=0)&&(pos!=iMaxPos)))
      {
         if (mOrientation == wxHORIZONTAL) {
            if (mFlip)
               mDC->DrawLine(mLeft + pos, mTop,
                             mLeft + pos, mTop + 4);
            else
               mDC->DrawLine(mLeft + pos, mBottom - 4,
                             mLeft + pos, mBottom);
         }
         else {
            if (mFlip)
               mDC->DrawLine(mLeft, mTop + pos,
                             mLeft + 4, mTop + pos);
            else
               mDC->DrawLine(mRight - 4, mTop + pos,
                             mRight, mTop + pos);
         }
      }

      if (mMajorLabels[i].text != wxT(""))
         mDC->DrawText(mMajorLabels[i].text,
                       mMajorLabels[i].lx,
                       mMajorLabels[i].ly);
   }

   if(mbMinor == true) {
      mDC->SetFont(*mMinorFont);
      for(i=0; i<mNumMinor; i++) {
         int pos = mMinorLabels[i].pos;
         if( mbTicksAtExtremes || ((pos!=0)&&(pos!=iMaxPos)))
         {
            if (mOrientation == wxHORIZONTAL)
            {
               if (mFlip)
                  mDC->DrawLine(mLeft + pos, mTop,
                                mLeft + pos, mTop + 2);
               else
                  mDC->DrawLine(mLeft + pos, mBottom - 2,
                                mLeft + pos, mBottom);
            }
            else
            {
               if (mFlip)
                  mDC->DrawLine(mLeft, mTop + pos,
                                mLeft + 2, mTop + pos);
               else
                  mDC->DrawLine(mRight - 2, mTop + pos,
                                mRight, mTop + pos);
            }
         }
         if (mMinorLabels[i].text != wxT(""))
            mDC->DrawText(mMinorLabels[i].text,
                          mMinorLabels[i].lx,
                          mMinorLabels[i].ly);
      }
   }

   mDC->SetFont(*mMinorMinorFont);

   for(i=0; i<mNumMinorMinor; i++) {
      if (mMinorMinorLabels[i].text != wxT(""))
      {
         int pos = mMinorMinorLabels[i].pos;

         if( mbTicksAtExtremes || ((pos!=0)&&(pos!=iMaxPos)))
         {
            if (mOrientation == wxHORIZONTAL)
            {
               if (mFlip)
                  mDC->DrawLine(mLeft + pos, mTop,
                                mLeft + pos, mTop + 2);
               else
                  mDC->DrawLine(mLeft + pos, mBottom - 2,
                                mLeft + pos, mBottom);
            }
            else
            {
               if (mFlip)
                  mDC->DrawLine(mLeft, mTop + pos,
                                mLeft + 2, mTop + pos);
               else
                  mDC->DrawLine(mRight - 2, mTop + pos,
                                mRight, mTop + pos);
            }
         }
         mDC->DrawText(mMinorMinorLabels[i].text,
                       mMinorMinorLabels[i].lx,
                       mMinorMinorLabels[i].ly);
      }
   }
}

// ********** Draw grid ***************************
void Ruler::DrawGrid(wxDC& dc, int length, bool minor, bool major, int xOffset, int yOffset)
{
   mGridLineLength = length;
   mMajorGrid = major;
   mMinorGrid = minor;
   mDC = &dc;

   Update();

   int gridPos;
   wxPen gridPen;

   if(mbMinor && (mMinorGrid && (mGridLineLength != 0 ))) {
      gridPen.SetColour(178, 178, 178); // very light grey
      mDC->SetPen(gridPen);
      for(int i=0; i<mNumMinor; i++) {
         gridPos = mMinorLabels[i].pos;
         if(mOrientation == wxHORIZONTAL) {
            if((gridPos != 0) && (gridPos != mGridLineLength))
               mDC->DrawLine(gridPos+xOffset, yOffset, gridPos+xOffset, mGridLineLength+yOffset);
         }
         else {
            if((gridPos != 0) && (gridPos != mGridLineLength))
               mDC->DrawLine(xOffset, gridPos+yOffset, mGridLineLength+xOffset, gridPos+yOffset);
         }
      }
   }

   if(mMajorGrid && (mGridLineLength != 0 )) {
      gridPen.SetColour(127, 127, 127); // light grey
      mDC->SetPen(gridPen);
      for(int i=0; i<mNumMajor; i++) {
         gridPos = mMajorLabels[i].pos;
         if(mOrientation == wxHORIZONTAL) {
            if((gridPos != 0) && (gridPos != mGridLineLength))
               mDC->DrawLine(gridPos+xOffset, yOffset, gridPos+xOffset, mGridLineLength+yOffset);
         }
         else {
            if((gridPos != 0) && (gridPos != mGridLineLength))
               mDC->DrawLine(xOffset, gridPos+yOffset, mGridLineLength+xOffset, gridPos+yOffset);
         }
      }

      int zeroPosition = GetZeroPosition();
      if(zeroPosition > 0) {
         // Draw 'zero' grid line in black
         mDC->SetPen(*wxBLACK_PEN);
         if(mOrientation == wxHORIZONTAL) {
            if(zeroPosition != mGridLineLength)
               mDC->DrawLine(zeroPosition+xOffset, yOffset, zeroPosition+xOffset, mGridLineLength+yOffset);
         }
         else {
            if(zeroPosition != mGridLineLength)
               mDC->DrawLine(xOffset, zeroPosition+yOffset, mGridLineLength+xOffset, zeroPosition+yOffset);
         }
      }
   }
}

int Ruler::FindZero(Label * label, const int len)
{
   int i = 0;
   double d = 1.0;   // arbitrary
   wxString s;

   do {
      s = label[i].text;
      if(!s.IsEmpty())
         s.ToDouble(&d);
      else
         d = 1.0; // arbitrary, looking for some text here
      i++;
   } while( (i < len) && (d != 0.0) );

   if(d == 0.0)
      return (label[i - 1].pos) ;
   else
      return -1;
}

int Ruler::GetZeroPosition()
{
   int zero;
   if((zero = FindZero(mMajorLabels, mNumMajor)) < 0)
      zero = FindZero(mMinorLabels, mNumMinor);
   return zero;
}

void Ruler::GetMaxSize(wxCoord *width, wxCoord *height)
{

   if (!mValid) {
      wxMemoryDC tmpDC;
      wxBitmap tmpBM(1, 1);
      tmpDC.SelectObject(tmpBM);
      mDC = &tmpDC;
      Update( NULL);
   }

   if (width)
      *width = mRect.GetWidth(); //mMaxWidth;

   if (height)
      *height = mRect.GetHeight(); //mMaxHeight;
}

void Ruler::SetCustomMode(bool value) { mCustom = value; }

void Ruler::SetCustomMajorLabels(wxArrayString *label, int numLabel, int start, int step)
{
   int i;

   mNumMajor = numLabel;
   mMajorLabels = new Label[numLabel];

   for(i=0; i<numLabel; i++) {
      mMajorLabels[i].text = label->Item(i);
      mMajorLabels[i].pos  = start + i*step;
   }
   //Remember: delete majorlabels....
}

void Ruler::SetCustomMinorLabels(wxArrayString *label, int numLabel, int start, int step)
{
   int i;

   mNumMinor = numLabel;
   mMinorLabels = new Label[numLabel];

   for(i=0; i<numLabel; i++) {
      mMinorLabels[i].text = label->Item(i);
      mMinorLabels[i].pos  = start + i*step;
   }
   //Remember: delete majorlabels....
}

//
// RulerPanel
//

BEGIN_EVENT_TABLE(RulerPanel, wxPanel)
   EVT_ERASE_BACKGROUND(RulerPanel::OnErase)
   EVT_PAINT(RulerPanel::OnPaint)
   EVT_SIZE(RulerPanel::OnSize)
END_EVENT_TABLE()

IMPLEMENT_CLASS(RulerPanel, wxPanel)

RulerPanel::RulerPanel(wxWindow* parent, wxWindowID id,
                       const wxPoint& pos /*= wxDefaultPosition*/,
                       const wxSize& size /*= wxDefaultSize*/):
   wxPanel(parent, id, pos, size)
{
}

RulerPanel::~RulerPanel()
{
}

void RulerPanel::OnErase(wxEraseEvent & WXUNUSED(evt))
{
   // Ignore it to prevent flashing
}

void RulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
{
   wxPaintDC dc(this);

#if defined(__WXGTK__)
   dc.SetBackground(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE)));
#endif

   dc.Clear();
   ruler.Draw(dc);
}

void RulerPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
{
   Refresh(false);
}

// LL:  We're overloading DoSetSize so that we can update the ruler bounds immediately
//      instead of waiting for a wxEVT_SIZE to come through.  This is needed by (at least)
//      FreqWindow since it needs to have an updated ruler before RulerPanel gets the
//      size event.
void RulerPanel::DoSetSize(int x, int y,
                           int width, int height,
                           int sizeFlags)
{
   wxPanel::DoSetSize(x, y, width, height, sizeFlags);

   int w, h;
   GetClientSize(&w, &h);

   ruler.SetBounds(0, 0, w-1, h-1);
}

/**********************************************************************

  Implementation of AdornedRulerPanel.
  Either we find a way to make this more generic, Or it will move
  out of the widgets subdirectory into its own source file.

**********************************************************************/

#include "../ViewInfo.h"
#include "../AColor.h"

BEGIN_EVENT_TABLE(AdornedRulerPanel, wxPanel)
   EVT_ERASE_BACKGROUND(AdornedRulerPanel::OnErase)
   EVT_PAINT(AdornedRulerPanel::OnPaint)
   EVT_SIZE(AdornedRulerPanel::OnSize)
   EVT_MOUSE_EVENTS(AdornedRulerPanel::OnMouseEvents)
   EVT_MOUSE_CAPTURE_LOST(AdornedRulerPanel::OnCaptureLost)
END_EVENT_TABLE()

AdornedRulerPanel::AdornedRulerPanel(wxWindow* parent,
                                     wxWindowID id,
                                     const wxPoint& pos,
                                     const wxSize& size,
                                     ViewInfo *viewinfo):
   wxPanel( parent, id, pos, size )
{
   SetLabel( _("Vertical Ruler") );
   SetName( _("Vertical Ruler") );

   mLeftOffset = 0;
   mCurPos = -1;
   mIndPos = -1;
   mIndType = -1;
   mPlayRegionStart = -1;
   mPlayRegionEnd = -1;
   mMouseEventState = mesNone;

   mBuffer = new wxBitmap( 1, 1 );
   mViewInfo = viewinfo;

   mOuter = GetClientRect();

   mInner = mOuter;
   mInner.x += 1;          // +1 for left bevel
   mInner.y += 1;          // +1 for top bevel
   mInner.width -= 2;      // -2 for left and right bevels
   mInner.height -= 3;     // -3 for top and bottom bevels and bottom line

   ruler.SetBounds( mInner.GetLeft(),
                    mInner.GetTop(),
                    mInner.GetRight(),
                    mInner.GetBottom() );
   ruler.SetLabelEdges( false );
   ruler.SetFormat( Ruler::TimeFormat );
}

AdornedRulerPanel::~AdornedRulerPanel()
{
   delete mBuffer;
}

void AdornedRulerPanel::OnErase(wxEraseEvent & WXUNUSED(evt))
{
   // Ignore it to prevent flashing
}

void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
{
#if defined(__WXMAC__)
   wxPaintDC dc(this);
#else
   wxBufferedPaintDC dc(this);
#endif

   DoDrawBorder(&dc);

   if (mViewInfo->sel0 < mViewInfo->sel1)
   {
      DoDrawSelection(&dc);
   }

   if (mIndType >= 0)
   {
      DoDrawIndicator(&dc);
   }

   DoDrawMarks(&dc, true);

   if (mViewInfo->sel0 == mViewInfo->sel1)
   {
      DoDrawCursor(&dc);
   }

   DoDrawPlayRegion(&dc);
}

void AdornedRulerPanel::OnSize(wxSizeEvent & WXUNUSED(evt))
{
   mOuter = GetClientRect();

   mInner = mOuter;
   mInner.x += 1;          // +1 for left bevel
   mInner.y += 1;          // +1 for top bevel
   mInner.width -= 2;      // -2 for left and right bevels
   mInner.height -= 3;     // -3 for top and bottom bevels and bottom line

   ruler.SetBounds( mInner.GetLeft(),
                    mInner.GetTop(),
                    mInner.GetRight(),
                    mInner.GetBottom() );

   if( mBuffer )
   {
      delete mBuffer;
   }

   mBuffer = new wxBitmap( mOuter.GetWidth(), mOuter.GetHeight() );

   Refresh( false );
}

double AdornedRulerPanel::Pos2Time(int p)
{
   return (p-mLeftOffset) / mViewInfo->zoom + mViewInfo->h;
}

int AdornedRulerPanel::Time2Pos(double t)
{
   return mLeftOffset + (int)((t-mViewInfo->h) * mViewInfo->zoom + 0.5);
}

bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
{
   if (markerTime < 0)
      return false;

   int pixelPos = Time2Pos(markerTime);
   int boundLeft = pixelPos - SELECT_TOLERANCE_PIXEL;
   int boundRight = pixelPos + SELECT_TOLERANCE_PIXEL;

   return mousePosX >= boundLeft && mousePosX < boundRight;
}

void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
{
   bool isWithinStart = IsWithinMarker(evt.GetX(), mPlayRegionStart);
   bool isWithinEnd = IsWithinMarker(evt.GetX(), mPlayRegionEnd);

   mLastMouseX = evt.GetX();

   if (isWithinStart || isWithinEnd)
      SetCursor(wxCursor(wxCURSOR_SIZEWE));
   else
      SetCursor(wxCursor(wxCURSOR_HAND));

   double mouseTime = Pos2Time(evt.GetX());
   if (mouseTime < 0.0) {
      mouseTime = 0.0;
   }

   if (evt.LeftDown())
   {
      mButtonDownMousePos = evt.GetX();

      if (isWithinStart && isWithinEnd)
      {
         // Both could be selected, check which marker is nearer
         if (fabs(mouseTime-mPlayRegionStart) < fabs(mouseTime-mPlayRegionEnd))
            mMouseEventState = mesDraggingPlayRegionStart;
         else
            mMouseEventState = mesDraggingPlayRegionEnd;
      } else if (isWithinStart)
         mMouseEventState = mesDraggingPlayRegionStart;
      else if (isWithinEnd)
         mMouseEventState = mesDraggingPlayRegionEnd;
      else {
         // First, we enter "click" mode to avoid selecting a small range
         // accidentially
         mMouseEventState = mesSelectingPlayRegionClick;
         mPlayRegionStart = mouseTime;
         mPlayRegionEnd = mouseTime;
         Refresh();
      }

      CaptureMouse();
   }

   switch (mMouseEventState)
   {
   case mesNone:
      // do nothing
      break;
   case mesDraggingPlayRegionStart:
      mPlayRegionStart = mouseTime;
      Refresh();
      break;
   case mesDraggingPlayRegionEnd:
      mPlayRegionEnd = mouseTime;
      Refresh();
      break;
   case mesSelectingPlayRegionClick:
      if (abs(evt.GetX() - mButtonDownMousePos) > SELECT_TOLERANCE_PIXEL)
      {
         // User moved mouse at least SELECT_TOLERANCE_PIXEL, so change
         // from "click" mode to "range" mode to allow selecting a range
         mMouseEventState = mesSelectingPlayRegionRange;
         mPlayRegionEnd = mouseTime;
         Refresh();
      }
      break;
   case mesSelectingPlayRegionRange:
      mPlayRegionEnd = mouseTime;
      Refresh();
      break;
   }

   if (evt.LeftUp())
   {
      if (HasCapture())
         ReleaseMouse();

      if (mPlayRegionStart < 0 && mPlayRegionEnd >= 0)
      {
         // This makes no sense, remove play region
         mPlayRegionStart = -1;
         mPlayRegionEnd = -1;
      }

      if (mPlayRegionEnd < mPlayRegionStart)
      {
         // Swap values to make sure mPlayRegionStart > mPlayRegionEnd
         double tmp = mPlayRegionStart;
         mPlayRegionStart = mPlayRegionEnd;
         mPlayRegionEnd = tmp;
      }

      bool startPlaying = mPlayRegionStart >= 0 &&
         (mMouseEventState == mesSelectingPlayRegionClick ||
          mMouseEventState == mesSelectingPlayRegionRange);

      mMouseEventState = mesNone;

      if (startPlaying)
      {
         ControlToolBar* ctb = mProject->GetControlToolBar();
         ctb->StopPlaying();
         ctb->PlayDefault();
      }
   }
}

void AdornedRulerPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(evt))
{
   wxMouseEvent e(wxEVT_LEFT_UP);
   e.m_x = mLastMouseX;
   OnMouseEvents(e);
}

void AdornedRulerPanel::DoDrawPlayRegion(wxDC * dc)
{
   double start, end;
   GetPlayRegion(&start, &end);

   if (start >= 0)
   {
      int x1 = Time2Pos(start) + 1;
      int x2 = Time2Pos(end);
      int y = mInner.height/2;

      bool isLocked = mProject->IsPlayRegionLocked();
      AColor::PlayRegionColor(dc, isLocked);

      wxPoint tri[3];
      wxRect r;

      tri[0].x = x1;
      tri[0].y = y + PLAY_REGION_GLOBAL_OFFSET_Y;
      tri[1].x = x1 + PLAY_REGION_TRIANGLE_SIZE;
      tri[1].y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
      tri[2].x = x1 + PLAY_REGION_TRIANGLE_SIZE;
      tri[2].y = y + PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
      dc->DrawPolygon(3, tri);

      r.x = x1;
      r.y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
      r.width = PLAY_REGION_RECT_WIDTH;
      r.height = PLAY_REGION_TRIANGLE_SIZE*2 + 1;
      dc->DrawRectangle(r);

      if (end != start)
      {
         tri[0].x = x2;
         tri[0].y = y + PLAY_REGION_GLOBAL_OFFSET_Y;
         tri[1].x = x2 - PLAY_REGION_TRIANGLE_SIZE;
         tri[1].y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
         tri[2].x = x2 - PLAY_REGION_TRIANGLE_SIZE;
         tri[2].y = y + PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
         dc->DrawPolygon(3, tri);

         r.x = x2 - PLAY_REGION_RECT_WIDTH + 1;
         r.y = y - PLAY_REGION_TRIANGLE_SIZE + PLAY_REGION_GLOBAL_OFFSET_Y;
         r.width = PLAY_REGION_RECT_WIDTH;
         r.height = PLAY_REGION_TRIANGLE_SIZE*2 + 1;
         dc->DrawRectangle(r);

         r.x = x1 + PLAY_REGION_TRIANGLE_SIZE;
         r.y = y - PLAY_REGION_RECT_HEIGHT/2 + PLAY_REGION_GLOBAL_OFFSET_Y;
         r.width = x2-x1 - PLAY_REGION_TRIANGLE_SIZE*2;
         r.height = PLAY_REGION_RECT_HEIGHT;
         dc->DrawRectangle(r);
      }
   }
}

void AdornedRulerPanel::DoDrawBorder(wxDC * dc)
{
   // Draw AdornedRulerPanel border
   AColor::MediumTrackInfo( dc, false );
   dc->DrawRectangle( mInner );

   wxRect r = mOuter;
   r.width -= 1;                 // -1 for bevel
   r.height -= 2;                // -2 for bevel and for bottom line
   AColor::BevelTrackInfo( *dc, true, r );

   dc->SetPen( *wxBLACK_PEN );
   dc->DrawLine( mOuter.x,
                 mOuter.y + mOuter.height - 1,
                 mOuter.x + mOuter.width,
                 mOuter.y + mOuter.height - 1 );
}

void AdornedRulerPanel::DoDrawMarks(wxDC * dc, bool /*text */ )
{
   double min = mViewInfo->h - mLeftOffset / mViewInfo->zoom;
   double max = min + mInner.width / mViewInfo->zoom;

   ruler.SetTickColour( theTheme.Colour( clrTrackPanelText ) );
   ruler.SetRange( min, max );
   ruler.Draw( *dc );
}

void AdornedRulerPanel::DrawSelection()
{
   Refresh(false);
}

void AdornedRulerPanel::DoDrawSelection(wxDC * dc)
{
   // Draw selection
   double zoom = mViewInfo->zoom;
   double sel0 = mViewInfo->sel0 - mViewInfo->h + mLeftOffset / zoom;
   double sel1 = mViewInfo->sel1 - mViewInfo->h + mLeftOffset / zoom;

   if( sel0 < 0.0 )
      sel0 = 0.0;

   if( sel1 > ( mInner.width / zoom ) )
      sel1 = mInner.width / zoom;

   int p0 = int ( sel0 * zoom + 1.5 );
   int p1 = int ( sel1 * zoom + 2.5 );

   dc->SetBrush( wxBrush( theTheme.Colour( clrRulerBackground )) );
   dc->SetPen(   wxPen(   theTheme.Colour( clrRulerBackground )) );

   wxRect r;
   r.x = p0;
   r.y = 1;
   r.width = p1 - p0 - 1;
   r.height = mInner.height;
   dc->DrawRectangle( r );
}

void AdornedRulerPanel::DrawCursor(double pos)
{
   mCurPos = pos;

   Refresh(false);
}

void AdornedRulerPanel::DoDrawCursor(wxDC * dc)
{
   int x = mLeftOffset + int ( ( mCurPos - mViewInfo->h ) * mViewInfo->zoom );

   // Draw cursor in ruler
   dc->DrawLine( x, 1, x, mInner.height );
}

//
//This draws the little triangular indicator on the
//AdornedRulerPanel.
//
void AdornedRulerPanel::ClearIndicator()
{
   mIndType = -1;

   Refresh(false);
}

void AdornedRulerPanel::DrawIndicator( double pos, bool rec )
{
   mIndPos = pos;

   if( mIndPos < 0 )
   {
      ClearIndicator();
      return;
   }

   mIndType = ( rec ? 0 : 1 );

   Refresh(false);
}

void AdornedRulerPanel::DoDrawIndicator(wxDC * dc)
{
   if( mIndType < 0 )
   {
      return;
   }

   int indsize = 6;
   int x = mLeftOffset + int ( ( mIndPos - mViewInfo->h ) * mViewInfo->zoom );

   wxPoint tri[ 3 ];
   tri[ 0 ].x = x - indsize;
   tri[ 0 ].y = 1;
   tri[ 1 ].x = x + indsize;
   tri[ 1 ].y = 1;
   tri[ 2 ].x = x;
   tri[ 2 ].y = ( indsize * 3 ) / 2 + 1;

   AColor::IndicatorColor( dc, ( mIndType ? true : false ) );
   dc->DrawPolygon( 3, tri );
}

void AdornedRulerPanel::SetPlayRegion(double playRegionStart,
                                      double playRegionEnd)
{
   // This is called by AudacityProject to make the play region follow
   // the current selection. But while the user is selecting a play region
   // with the mouse directly in the ruler, changes from outside are blocked.
   if (mMouseEventState != mesNone)
      return;

   mPlayRegionStart = playRegionStart;
   mPlayRegionEnd = playRegionEnd;
   Refresh();
}

void AdornedRulerPanel::ClearPlayRegion()
{
   mPlayRegionStart = -1;
   mPlayRegionEnd = -1;
   Refresh();
}

void AdornedRulerPanel::GetPlayRegion(double* playRegionStart,
                                      double* playRegionEnd)
{
   if (mPlayRegionStart >= 0 && mPlayRegionEnd >= 0 &&
       mPlayRegionEnd < mPlayRegionStart)
   {
      // swap values to make sure end > start
      *playRegionStart = mPlayRegionEnd;
      *playRegionEnd = mPlayRegionStart;
   } else
   {
      *playRegionStart = mPlayRegionStart;
      *playRegionEnd = mPlayRegionEnd;
   }
}

void AdornedRulerPanel::GetMaxSize(wxCoord *width, wxCoord *height)
{
   ruler.GetMaxSize(width, height);
}


text:
AWS::Core::Configuration.module_eval do

  add_service 'EMR', 'emr', 'elasticmapreduce'

end