view dw/fltkviewbase.cc @ 1791:8a5a07c67265

events
author corvid <corvid@lavabit.com>
date Thu, 06 Jan 2011 02:51:08 +0000
parents 4779a3d891a1
children c4dd094670d3
line wrap: on
line source
/*
 * Dillo Widget
 *
 * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */



#include "fltkviewport.hh"

#include <fltk/draw.h>
#include <fltk/damage.h>
#include <fltk/layout.h>
#include <fltk/events.h>
#include <fltk/Cursor.h>
#include <fltk/run.h>
#include <fltk/utf.h>

#include <stdio.h>
#include <wchar.h>
#include <wctype.h>
#include "../lout/msg.h"

using namespace fltk;
using namespace lout::object;
using namespace lout::container::typed;

namespace dw {
namespace fltk {

::fltk::Image *FltkViewBase::backBuffer;
bool FltkViewBase::backBufferInUse;

FltkViewBase::FltkViewBase (int x, int y, int w, int h, const char *label):
   Group (x, y, w, h, label)
{
   canvasWidth = 1;
   canvasHeight = 1;
   bgColor = WHITE;
   mouse_x = mouse_y = 0;
   exposeArea = NULL;
   if (backBuffer == NULL) {
      backBuffer = new Image ();
   }
}

FltkViewBase::~FltkViewBase ()
{
   cancelQueueDraw ();
}

void FltkViewBase::setBufferedDrawing (bool b) {
   if (b && backBuffer == NULL) {
      backBuffer = new Image ();
   } else if (!b && backBuffer != NULL) {
      delete backBuffer;
      backBuffer = NULL;
   }
}

void FltkViewBase::draw ()
{
   int d = damage ();

   if ((d & DAMAGE_VALUE) && !(d & DAMAGE_EXPOSE)) {
      lout::container::typed::Iterator <core::Rectangle> it;

      for (it = drawRegion.rectangles (); it.hasNext (); ) {
         draw (it.getNext (), DRAW_BUFFERED);
      }

      drawRegion.clear ();
      d &= ~DAMAGE_VALUE;
   }

   if (d & DAMAGE_CHILD) {
      drawChildWidgets ();
      d &= ~DAMAGE_CHILD;
   }

   if (d) {
      dw::core::Rectangle rect (
         translateViewXToCanvasX (0),
         translateViewYToCanvasY (0),
         w (),
         h ());

      if (d == DAMAGE_SCROLL) {
         // a clipping rectangle has already been set by fltk::scrollrect ()
         draw (&rect, DRAW_PLAIN);
      } else {
         draw (&rect, DRAW_CLIPPED);
         drawRegion.clear ();
      }
   }
}

void FltkViewBase::draw (const core::Rectangle *rect,
                         DrawType type)
{
   int offsetX = 0, offsetY = 0;

   /* fltk-clipping does not use widget coordinates */
   transform (offsetX, offsetY);

   ::fltk::Rectangle viewRect (
      translateCanvasXToViewX (rect->x) + offsetX,
      translateCanvasYToViewY (rect->y) + offsetY,
      rect->width, rect->height);

   ::fltk::intersect_with_clip (viewRect);

   viewRect.x (viewRect.x () - offsetX);
   viewRect.y (viewRect.y () - offsetY);

   if (! viewRect.empty ()) {
      dw::core::Rectangle r (
         translateViewXToCanvasX (viewRect.x ()),
         translateViewYToCanvasY (viewRect.y ()),
         viewRect.w (),
         viewRect.h ());

      exposeArea = &viewRect;

      if (type == DRAW_BUFFERED && backBuffer && !backBufferInUse) {
         backBufferInUse = true;
         {
            GSave gsave;

            backBuffer->setsize (viewRect.w (), viewRect.h ());
            backBuffer->make_current ();
            translate (-viewRect.x (), -viewRect.y ());

            setcolor (bgColor);
            fillrect (viewRect);
            theLayout->expose (this, &r);
         }

         backBuffer->draw (Rectangle (0, 0, viewRect.w (), viewRect.h ()),
            viewRect);

         backBufferInUse = false;
      } else if (type == DRAW_BUFFERED || type == DRAW_CLIPPED) {
         // if type == DRAW_BUFFERED but we do not have backBuffer available
         // we fall back to clipped drawing
         push_clip (viewRect);
         setcolor (bgColor);
         fillrect (viewRect);
         theLayout->expose (this, &r);
         pop_clip ();
      } else {
         setcolor (bgColor);
         fillrect (viewRect);
         theLayout->expose (this, &r);
      }

      exposeArea = NULL;
   }
}

void FltkViewBase::drawChildWidgets () {
   for (int i = children () - 1; i >= 0; i--) {
      Widget& w = *child(i);
      if (w.damage() & DAMAGE_CHILD_LABEL) {
         draw_outside_label(w);
         w.set_damage(w.damage() & ~DAMAGE_CHILD_LABEL);
      }
      update_child(w);
   }
}

core::ButtonState getDwButtonState ()
{
   int s1 = Fl::event_state ();
   int s2 = (core::ButtonState)0;

   if (s1 & SHIFT)   s2 |= core::SHIFT_MASK;
   if (s1 & CTRL)    s2 |= core::CONTROL_MASK;
   if (s1 & ALT)     s2 |= core::META_MASK;
   if (s1 & BUTTON1) s2 |= core::BUTTON1_MASK;
   if (s1 & BUTTON2) s2 |= core::BUTTON2_MASK;
   if (s1 & BUTTON3) s2 |= core::BUTTON3_MASK;

   return (core::ButtonState)s2;
}

int FltkViewBase::handle (int event)
{
   bool processed;

   /**
    * \todo Consider, whether this from the FLTK documentation has any
    *    impacts: "To receive fltk::RELEASE events you must return non-zero
    *    when passed a fltk::PUSH event. "
    */
   switch(event) {
   case FL_PUSH:
      processed =
         theLayout->buttonPress (this, event_clicks () + 1,
                                 translateViewXToCanvasX (event_x ()),
                                 translateViewYToCanvasY (event_y ()),
                                 getDwButtonState (), event_button ());
      _MSG("PUSH => %s\n", processed ? "true" : "false");
      if (processed) {
         /* pressed dw content; give focus to the view */
         ::fltk::focus(this);
      }
      return processed ? true : Group::handle (event);

   case FL_RELEASE:
      processed =
         theLayout->buttonRelease (this, event_clicks () + 1,
                                   translateViewXToCanvasX (event_x ()),
                                   translateViewYToCanvasY (event_y ()),
                                   getDwButtonState (), event_button ());
      _MSG("RELEASE => %s\n", processed ? "true" : "false");
      return processed ? true : Group::handle (event);

   case FL_MOVE:
      mouse_x = event_x();
      mouse_y = event_y();
      processed =
         theLayout->motionNotify (this,
                                  translateViewXToCanvasX (mouse_x),
                                  translateViewYToCanvasY (mouse_y),
                                  getDwButtonState ());
      _MSG("MOVE => %s\n", processed ? "true" : "false");
      return processed ? true : Group::handle (event);

   case FL_DRAG:
      processed =
         theLayout->motionNotify (this,
                                  translateViewXToCanvasX (event_x ()),
                                  translateViewYToCanvasY (event_y ()),
                                  getDwButtonState ());
      _MSG("DRAG => %s\n", processed ? "true" : "false");
      return processed ? true : Group::handle (event);

   case FL_ENTER:
      theLayout->enterNotify (this, translateViewXToCanvasX (event_x ()),
                              translateViewYToCanvasY (event_y ()),
                              getDwButtonState ());
      return Group::handle (event);

   case FL_LEAVE:
      theLayout->leaveNotify (this, getDwButtonState ());
      return Group::handle (event);

   default:
      return Group::handle (event);
   }
}

// ----------------------------------------------------------------------

void FltkViewBase::setLayout (core::Layout *layout)
{
   theLayout = layout;
}

void FltkViewBase::setCanvasSize (int width, int ascent, int descent)
{
   canvasWidth = width;
   canvasHeight = ascent + descent;
}

void FltkViewBase::setCursor (core::style::Cursor cursor)
{
   static Cursor *mapDwToFltk[] = {
      CURSOR_CROSS,
      CURSOR_DEFAULT,
      CURSOR_HAND,
      CURSOR_MOVE,
      CURSOR_WE,
      CURSOR_NESW,
      CURSOR_NWSE,
      CURSOR_NS,
      CURSOR_NWSE,
      CURSOR_NESW,
      CURSOR_NS,
      CURSOR_WE,
      CURSOR_INSERT,
      CURSOR_WAIT,
      CURSOR_HELP
   };

   /*
   static char *cursorName[] = {
      "CURSOR_CROSS",
      "CURSOR_DEFAULT",
      "CURSOR_HAND",
      "CURSOR_MOVE",
      "CURSOR_WE",
      "CURSOR_NESW",
      "CURSOR_NWSE",
      "CURSOR_NS",
      "CURSOR_NWSE",
      "CURSOR_NESW",
      "CURSOR_NS",
      "CURSOR_WE",
      "CURSOR_INSERT",
      "CURSOR_WAIT",
      "CURSOR_HELP"
   };

   MSG("Cursor changes to '%s'.\n", cursorName[cursor]);
   */

   /** \bug Does not work */
   this->cursor (mapDwToFltk[cursor]);
}

void FltkViewBase::setBgColor (core::style::Color *color)
{
   bgColor = color ?
      ((FltkColor*)color)->colors[dw::core::style::Color::SHADING_NORMAL] :
      WHITE;
}

void FltkViewBase::startDrawing (core::Rectangle *area)
{
}

void FltkViewBase::finishDrawing (core::Rectangle *area)
{
}

void FltkViewBase::queueDraw (core::Rectangle *area)
{
   drawRegion.addRectangle (area);
   /** DAMAGE_VALUE is just an arbitrary value other than DAMAGE_EXPOSE here */
   redraw (DAMAGE_VALUE);
}

void FltkViewBase::queueDrawTotal ()
{
   redraw (DAMAGE_EXPOSE);
}

void FltkViewBase::cancelQueueDraw ()
{
}

void FltkViewBase::drawPoint (core::style::Color *color,
                              core::style::Color::Shading shading,
                              int x, int y)
{
}

void FltkViewBase::drawLine (core::style::Color *color,
                             core::style::Color::Shading shading,
                             int x1, int y1, int x2, int y2)
{
   setcolor(((FltkColor*)color)->colors[shading]);
   drawline (translateCanvasXToViewX (x1), translateCanvasYToViewY (y1),
             translateCanvasXToViewX (x2), translateCanvasYToViewY (y2));
}

void FltkViewBase::drawTypedLine (core::style::Color *color,
                                  core::style::Color::Shading shading,
                                  core::style::LineType type, int width,
                                  int x1, int y1, int x2, int y2)
{
   char dashes[3], w, ng, d, gap, len;
   const int f = 2;

   w = (width == 1) ? 0 : width;
   if (type == core::style::LINE_DOTTED) {
      /* customized drawing for dotted lines */
      len = (x2 == x1) ? y2 - y1 + 1 : (y2 == y1) ? x2 - x1 + 1 : 0;
      ng = len / f*width;
      d = len % f*width;
      gap = ng ? d/ng + (w > 3 ? 2 : 0) : 0;
      dashes[0] = 1; dashes[1] = f*width-gap; dashes[2] = 0;
      line_style(::fltk::DASH + ::fltk::CAP_ROUND, w, dashes);

      /* These formulas also work, but ain't pretty ;)
       * line_style(::fltk::DOT + ::fltk::CAP_ROUND, w);
       * dashes[0] = 1; dashes[1] = 3*width-2; dashes[2] = 0;
       */
   } else if (type == core::style::LINE_DASHED) {
      line_style(::fltk::DASH + ::fltk::CAP_ROUND, w);
   }

   setcolor(((FltkColor*)color)->colors[shading]);
   drawLine (color, shading, x1, y1, x2, y2);

   if (type != core::style::LINE_NORMAL)
      line_style(::fltk::SOLID);
}

void FltkViewBase::drawRectangle (core::style::Color *color,
                                  core::style::Color::Shading shading,
                                  bool filled,
                                  int x, int y, int width, int height)
{
   setcolor(((FltkColor*)color)->colors[shading]);
   if (width < 0) {
      x += width;
      width = -width;
   }
   if (height < 0) {
      y += height;
      height = -height;
   }

   int x1 = translateCanvasXToViewX (x);
   int y1 = translateCanvasYToViewY (y);
   int x2 = translateCanvasXToViewX (x + width);
   int y2 = translateCanvasYToViewY (y + height);

   // We only support rectangles with line width 1px, so we clip with
   // a rectangle 1px wider and higher than what we actually expose.
   // This is only really necessary for non-filled rectangles.
   clipPoint (&x1, &y1, 1);
   clipPoint (&x2, &y2, 1);

   ::fltk::Rectangle rect (x1, y1, x2 - x1, y2 - y1);
   if (filled)
      fillrect (rect);
   else
      strokerect (rect);
}

void FltkViewBase::drawArc (core::style::Color *color,
                            core::style::Color::Shading shading, bool filled,
                            int centerX, int centerY, int width, int height,
                            int angle1, int angle2)
{
   setcolor(((FltkColor*)color)->colors[shading]);
   int x = translateCanvasXToViewX (centerX) - width / 2;
   int y = translateCanvasYToViewY (centerY) - height / 2;
   ::fltk::Rectangle rect (x, y, width, height);
   addchord(rect, angle1, angle2);
   closepath();
   if (filled)
      fillpath();
   else
      strokepath();
}

void FltkViewBase::drawPolygon (core::style::Color *color,
                                core::style::Color::Shading shading,
                                bool filled, int points[][2], int npoints)
{
   if (npoints > 0) {
      for (int i = 0; i < npoints; i++) {
         points[i][0] = translateCanvasXToViewX(points[i][0]);
         points[i][1] = translateCanvasYToViewY(points[i][1]);
      }
      setcolor(((FltkColor*)color)->colors[shading]);
      addvertices(npoints, points);
      closepath();
      if (filled)
         fillpath();
      else
         strokepath();
   }
}

core::View *FltkViewBase::getClippingView (int x, int y, int width, int height)
{
   push_clip (translateCanvasXToViewX (x), translateCanvasYToViewY (y),
              width, height);
   return this;
}

void FltkViewBase::mergeClippingView (core::View *clippingView)
{
   pop_clip ();
}

// ----------------------------------------------------------------------

FltkWidgetView::FltkWidgetView (int x, int y, int w, int h,
                                const char *label):
   FltkViewBase (x, y, w, h, label)
{
}

FltkWidgetView::~FltkWidgetView ()
{
}

void FltkWidgetView::layout () {
   /**
    * pass layout to child widgets. This is needed for complex fltk
    * widgets as TextEditor.
    * We can't use Group::layout() as that would rearrange the widgets.
    */
   for (int i = children () - 1; i >= 0; i--) {
      ::fltk::Widget *widget = child (i);

      if (widget->layout_damage ()) {
         widget->layout ();
      }
   }
}

void FltkWidgetView::drawText (core::style::Font *font,
                             core::style::Color *color,
                             core::style::Color::Shading shading,
                             int x, int y, const char *text, int len)
{
   FltkFont *ff = (FltkFont*)font;
   setfont(ff->font, ff->size);
   setcolor(((FltkColor*)color)->colors[shading]);

   if (!font->letterSpacing && !font->fontVariant) {
      drawtext(text, len,
               translateCanvasXToViewX (x), translateCanvasYToViewY (y));
   } else {
      /* Nonzero letter spacing adjustment, draw each glyph individually */
      int viewX = translateCanvasXToViewX (x),
          viewY = translateCanvasYToViewY (y);
      int curr = 0, next = 0, nb;
      char chbuf[4];
      wchar_t wc, wcu;

      if (font->fontVariant == 1) {
         int sc_fontsize = lout::misc::roundInt(ff->size * 0.78);
         for (curr = 0; next < len; curr = next) {
            next = theLayout->nextGlyph(text, curr);
            wc = utf8decode(text + curr, text + next, &nb);
            if ((wcu = towupper(wc)) == wc) {
               /* already uppercase, just draw the character */
               setfont(ff->font, ff->size);
               drawtext(text + curr, next - curr, viewX, viewY);
               viewX += font->letterSpacing;
               viewX += (int)getwidth(text + curr, next - curr);
            } else {
               /* make utf8 string for converted char */
               nb = utf8encode(wcu, chbuf);
               setfont(ff->font, sc_fontsize);
               drawtext(chbuf, nb, viewX, viewY);
               viewX += font->letterSpacing;
               viewX += (int)getwidth(chbuf, nb);
            }
         }
      } else {
         while (next < len) {
            next = theLayout->nextGlyph(text, curr);
            drawtext(text + curr, next - curr, viewX, viewY);
            viewX += font->letterSpacing +
                     (int)getwidth(text + curr,next - curr);
            curr = next;
         }
      }
   }
}

void FltkWidgetView::drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,
                              int x, int y, int width, int height)
{
   ((FltkImgbuf*)imgbuf)->draw (this,
                                translateCanvasXToViewX (xRoot),
                                translateCanvasYToViewY (yRoot),
                                x, y, width, height);
}

bool FltkWidgetView::usesFltkWidgets ()
{
   return true;
}

void FltkWidgetView::addFltkWidget (::fltk::Widget *widget,
                                  core::Allocation *allocation)
{
   allocateFltkWidget (widget, allocation);
   add (widget);
}

void FltkWidgetView::removeFltkWidget (::fltk::Widget *widget)
{
   remove (widget);
}

void FltkWidgetView::allocateFltkWidget (::fltk::Widget *widget,
                                       core::Allocation *allocation)
{
   widget->x (translateCanvasXToViewX (allocation->x));
   widget->y (translateCanvasYToViewY (allocation->y));
   widget->w (allocation->width);
   widget->h (allocation->ascent + allocation->descent);

   /* widgets created tiny and later resized need this flag to display */
   uchar damage = widget->layout_damage ();
   damage |= LAYOUT_XYWH;
   widget->layout_damage (damage);
}

void FltkWidgetView::drawFltkWidget (::fltk::Widget *widget,
                                   core::Rectangle *area)
{
   draw_child (*widget);
}

} // namespace fltk
} // namespace dw