# $Id: Range.py,v 1.9 2006/06/02 10:21:53 marcusva Exp $
#
# Copyright (c) 2004-2006, Marcus von Appen
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  * Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""An abstract widget suitable for scale ranges and numerical
adjustments.
"""

from BaseWidget import BaseWidget
from Constants import *

class Range (BaseWidget):
    """Range (minimum, maximum, step=1.0) -> Range

    An abstract widget class for scale ranges and numerical adjustments.

    The Range widget class is an abstract class suitable for widgets,
    which need numerical adjustment support, scale ranges and similar
    things. It supplies various attributes and methods to enable a
    widget for numerical input and range limit checks.

    A minimum range value (or lower limit) can be set using the
    'minimum' attribute or set_minimum() method. It must not be greater
    than the set maximum value of the Range and any integer or float
    value are valid input for it.

    range.minimum = 1.7
    range.set_minimum (5)

    In contrast of this, the 'maximum' attribute or set_maximum() method
    set the upper limit (or maximum range value) of the Range. As well
    as the 'minimum' attribute any integer or float are a valid value
    and must not be smaller than the minimum value of the Range.

    range.maximum = 123.45
    range.set_maximum (100)

    To in- or decrease the Range value easily (in loops for example),
    the 'step' attribute or set_step() method can be used, which set the
    step value for the increase() and decrease() method.

    range.step = 0.5
    range.set_step (10)

    while x > 100:
        range.increment ()
        x -= 1

    while y > 100:
        range.decrement ()
        y -= 1

    The current set value of the Range widget can be set or retrieved
    with the 'value' attribute or set_value() method.

    range.value = 10.0
    range.set_value (100)

    Note: When you set the 'minimum' or 'maximum' attribute of the Range
    widget, the 'value' attribute will be automatically reset to the
    minimum or maximum value, if it is not within the range of the
    widget.

    Default action (invoked by activate()):
    None
    
    Mnemonic action (invoked by activate_mnemonic()):
    None

    Signals:
    SIG_VALCHANGED - Invoked, when the value of the Range changed.
    
    Attributes:
    minimum - The minimum value of the Range.
    maximum - The maximum value of the Range.
    step    - Step range to use for in- or decreasing the Range.
    value   - The current value of the Range.
    """
    def __init__ (self, minimum, maximum, step=1.0):
        BaseWidget.__init__ (self)

        self._signals[SIG_VALCHANGED] = []
        
        # Ranges and step value.
        self._minimum = minimum  # Set the min and max values temporary
        self._maximum = maximum  # and check them later.
        self._step = 0.0
        self._value = 0.0

        if minimum >= maximum:
            raise ValueError ("minimum must be smaller than maximum")
        self.set_step (step)

    def set_minimum (self, minimum):
        """R.set_minimum (...) -> None

        Sets the minimum value of the Range.

        Raises a TypeError, if the passed argument is not a float or
        integer.
        Raises a ValueError, if the passed argument is greater than
        the Range its maximum value.
        """
        if type (minimum) not in (float, int):
            raise TypeError ("minimum must be a float or integer")
        if minimum > self.maximum:
            raise ValueError ("minimum must be smaller than %f" % self.maximum)
        self._minimum = minimum
        if minimum > self.value:
            # Adjust the current value on demand.
            self.value = minimum
    
    def set_maximum (self, maximum):
        """R.set_maximum (...) -> None

        Sets the maximum value of the Range.

        Raises a TypeError, if the passed argument is not a float or
        integer.
        Raises a ValueError, if the passed argument is smaller than
        the Range its minimum value.
        """
        if type (maximum) not in (float, int):
            raise TypeError ("maximum must be a float or integer")
        if maximum < self.minimum:
            raise ValueError ("maximum must be greater than %f" % self.minimum)
        self._maximum = maximum
        if maximum < self.value:
            # Adjust the current value on demand.
            self.value = maximum

    def set_step (self, step=1.0):
        """R.set_step (...) -> None

        Sets the step range for in- or decreasing the Range value.

        Raises a TypeError, if the passed argument is not a float or
        integer.
        """
        if type (step) not in (float, int):
            raise TypeError ("step must be a float or integer")
        self._step = step

    def set_value (self, value):
        """R.set_value (...) -> None

        Sets the current value of the Range.

        Sets the current value of the Range and raises a SIG_VALCHANGED
        event, if the new value differs from the old one.

        Raises a TypeError, if the passed argument is not a float or
        integer.
        Raises a ValueError, if the passed argument is not within the
        range of the Range its maximum and minimum value.
        """
        if type (value) not in (float, int):
            raise TypeError ("value must be a float or integer")
        if (self.minimum > value) or (self.maximum < value):
            raise ValueError ("value must be in the range from %f to %f"
                              % (self.minimum, self.maximum))
        if value != self._value:
            self._value = value
            self.run_signal_handlers (SIG_VALCHANGED)
            self.dirty = True

    def increase (self):
        """R.increase () -> None

        Increases the current Range value by one step.
        """
        val = self.value
        if val < self.maximum:
            val += self.step
            if val > self.maximum:
                val = self.maximum
        self.value = val

    def decrease (self):
        """R.decrease () -> None

        Decreases the current Range value by one step.
        """
        val = self.value
        if val > self.minimum:
            val -= self.step
            if val < self.minimum:
                val = self.minimum
        self.value = val

    minimum = property (lambda self: self._minimum,
                        lambda self, var: self.set_minimum (var),
                        doc = "The minimum value of the Range.")
    maximum = property (lambda self: self._maximum,
                        lambda self, var: self.set_maximum (var),
                        doc = "The maximum value of the Range.")
    value = property (lambda self: self._value,
                      lambda self, var: self.set_value (var),
                      doc = "The current value of the Range.")
    step = property (lambda self: self._step,
                     lambda self, var: self.set_step (var),
                     doc = "The step range of the Range.")
