/**
   * Causes resources used by the World Window to be freed. The World Window cannot be used once
   * this method is called. An OpenGL context for the window must be current.
   */
  public void shutdown() {
    WorldWind.getDataFileStore().removePropertyChangeListener(this);

    if (this.inputHandler != null) {
      this.inputHandler.dispose();
      this.inputHandler = new NoOpInputHandler();
    }

    // Clear the texture cache
    if (this.getGpuResourceCache() != null) this.getGpuResourceCache().clear();

    // Dispose all the layers //  TODO: Need per-window dispose for layers
    if (this.getModel() != null && this.getModel().getLayers() != null) {
      for (Layer layer : this.getModel().getLayers()) {
        try {
          layer.dispose();
        } catch (Exception e) {
          Logging.logger()
              .log(
                  java.util.logging.Level.SEVERE,
                  Logging.getMessage("WorldWindowGLCanvas.ExceptionWhileShuttingDownWorldWindow"),
                  e);
        }
      }
    }

    SceneController sc = this.getSceneController();
    if (sc != null) sc.dispose();
  }
  /**
   * Set the <code>MeasureTool</code> that this controller will be operating on.
   *
   * @param measureTool the <code>MeasureTool</code> that this controller will be operating on.
   */
  public void setMeasureTool(MeasureTool measureTool) {
    if (measureTool == null) {
      String msg = Logging.getMessage("nullValue.MeasureToolIsNull");
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    this.measureTool = measureTool;
  }
  /**
   * Specifies an effect used to decorate the text. Can be one of {@link AVKey#TEXT_EFFECT_SHADOW}
   * (default), or {@link AVKey#TEXT_EFFECT_NONE}.
   *
   * @param effect the effect to use for text rendering
   */
  public void setEffect(String effect) {
    if (effect == null) {
      String message = Logging.getMessage("nullValue.StringIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    this.effect = effect;
  }
  /**
   * Specifies the amount of space (in pixels) between the label's content and the edges of the
   * label's frame.
   *
   * @param insets the desired padding between the label's content and its frame, in pixels.
   * @throws IllegalArgumentException if <code>insets</code> is <code>null</code>.
   * @see #getInsets()
   */
  public void setInsets(Insets insets) {
    if (insets == null) {
      String message = Logging.getMessage("nullValue.InsetsIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    this.insets = insets;
  }
  /**
   * Specifies the opacity of the label's interior as a floating-point value in the range 0.0 to
   * 1.0. A value of 1.0 specifies a completely opaque interior, and 0.0 specifies a completely
   * transparent interior. Values in between specify a partially transparent interior.
   *
   * @param interiorOpacity the opacity of label's interior as a floating-point value from 0.0 to
   *     1.0.
   * @throws IllegalArgumentException if <code>opacity</code> is less than 0.0 or greater than 1.0.
   */
  public void setInteriorOpacity(double interiorOpacity) {
    if (opacity < 0 || opacity > 1) {
      String message = Logging.getMessage("generic.OpacityOutOfRange", opacity);
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    this.interiorOpacity = interiorOpacity;
  }
  /**
   * Specifies the material used to draw the label.
   *
   * @param material New material.
   */
  public void setMaterial(Material material) {
    if (material == null) {
      String message = Logging.getMessage("nullValue.MaterialIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    this.material = material;
  }
  /**
   * Specifies the line spacing applied to multi-line labels.
   *
   * @param lineSpacing New line spacing.
   */
  public void setLineSpacing(int lineSpacing) {
    if (lineSpacing < 0) {
      String message = Logging.getMessage("generic.ArgumentOutOfRange");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    this.lineSpacing = lineSpacing;
  }
  /**
   * Specifies the offset from the geographic position at which to draw the label. The default
   * offset aligns the label horizontal with the text alignment position, and centers the label
   * vertically. For example, if the text alignment is <code>AVKey.LEFT</code>., then the left edge
   * of the text will be aligned with the geographic position, and the label will be centered
   * vertically.
   *
   * <p>When the text is rotated a horizontal offset moves the text along the orientation line, and
   * a vertical offset moves the text perpendicular to the orientation line.
   *
   * @param offset The offset at which to draw the label.
   */
  public void setOffset(Offset offset) {
    if (offset == null) {
      String message = Logging.getMessage("nullValue.OffsetIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    this.offset = offset;
  }
  /**
   * Specifies the text alignment. Can be one of {@link AVKey#LEFT} (default), {@link AVKey#CENTER},
   * or {@link AVKey#RIGHT}.
   *
   * @param textAlign New text alignment.
   */
  public void setTextAlign(String textAlign) {
    if (textAlign == null) {
      String message = Logging.getMessage("nullValue.StringIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    this.textAlign = textAlign;
  }
  /**
   * Get the label bounding {@link java.awt.Rectangle} using OGL coordinates - bottom-left corner x
   * and y relative to the {@link gov.nasa.worldwind.WorldWindow} bottom-left corner. If the label
   * is rotated then the returned rectangle is the bounding rectangle of the rotated label.
   *
   * @param dc the current DrawContext.
   * @return the label bounding {@link java.awt.Rectangle} using OGL viewport coordinates.
   * @throws IllegalArgumentException if <code>dc</code> is null.
   */
  public Rectangle getBounds(DrawContext dc) {
    if (dc == null) {
      String message = Logging.getMessage("nullValue.DrawContextIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    this.computeGeometryIfNeeded(dc);
    return this.screenExtent;
  }
  /**
   * Specifies the font used to draw the label.
   *
   * @param font New font.
   */
  public void setFont(Font font) {
    if (font == null) {
      String message = Logging.getMessage("nullValue.FontIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    if (font != this.font) {
      this.font = font;
      this.bounds = null; // Need to recompute
    }
  }
  /** {@inheritDoc} */
  public void render(DrawContext dc) {
    // This render method is called three times during frame generation. It's first called as a
    // Renderable
    // during Renderable picking. It's called again during normal rendering. And it's called a third
    // time as an OrderedRenderable. The first two calls determine whether to add the label the
    // ordered renderable
    // list during pick and render. The third call just draws the ordered renderable.

    if (dc == null) {
      String msg = Logging.getMessage("nullValue.DrawContextIsNull");
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    if (dc.isOrderedRenderingMode()) this.drawOrderedRenderable(dc);
    else this.makeOrderedRenderable(dc);
  }
  /** {@inheritDoc} */
  public void pick(DrawContext dc, Point pickPoint) {
    // This method is called only when ordered renderables are being drawn.
    // Arg checked within call to render.

    if (dc == null) {
      String msg = Logging.getMessage("nullValue.DrawContextIsNull");
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    this.pickSupport.clearPickList();
    try {
      this.pickSupport.beginPicking(dc);
      this.render(dc);
    } finally {
      this.pickSupport.endPicking(dc);
      this.pickSupport.resolvePick(dc, pickPoint, this.pickLayer);
    }
  }
  protected void drawMultiLineText(TextRenderer textRenderer, int x, int y) {
    if (this.lines == null) {
      String msg = Logging.getMessage("nullValue.StringIsNull");
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    for (int i = 0; i < this.lines.length; i++) {
      String line = this.lines[i];
      Rectangle2D bounds = this.lineBounds[i];

      int xAligned = x;
      if (this.textAlign.equals(AVKey.CENTER)) xAligned = x - (int) (bounds.getWidth() / 2);
      else if (this.textAlign.equals(AVKey.RIGHT)) xAligned = x - (int) (bounds.getWidth());

      y -= this.lineHeight;
      textRenderer.draw3D(line, xAligned, y, 0, 1);
      y -= this.lineSpacing;
    }
  }