diff options
author | Rodrigo Arias Mallo <rodarima@gmail.com> | 2024-12-10 22:30:12 +0100 |
---|---|---|
committer | Rodrigo Arias Mallo <rodarima@gmail.com> | 2024-12-10 22:30:12 +0100 |
commit | 429d5f88b94ff28416cbfc6420b6389fa284df97 (patch) | |
tree | fb6fdaf7731de1ef396f98b748c56f3149801c84 /dw/fltkviewbase.cc |
Import RTFL 0.1.1v0.1.1
Diffstat (limited to 'dw/fltkviewbase.cc')
-rw-r--r-- | dw/fltkviewbase.cc | 744 |
1 files changed, 744 insertions, 0 deletions
diff --git a/dw/fltkviewbase.cc b/dw/fltkviewbase.cc new file mode 100644 index 0000000..3b1c0cc --- /dev/null +++ b/dw/fltkviewbase.cc @@ -0,0 +1,744 @@ +/* + * RTFL (originally part of dillo) + * + * 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; with the following exception: + * + * The copyright holders of RTFL give you permission to link this file + * statically or dynamically against all versions of the graphviz + * library, which are published by AT&T Corp. under one of the following + * licenses: + * + * - Common Public License version 1.0 as published by International + * Business Machines Corporation (IBM), or + * - Eclipse Public License version 1.0 as published by the Eclipse + * Foundation. + * + * 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 <FL/Fl.H> +#include <FL/fl_draw.H> + +#include <stdio.h> +#include "../lout/msg.h" + +extern Fl_Widget* fl_oldfocus; + +using namespace lout::object; +using namespace lout::container::typed; + +namespace dw { +namespace fltk { + +FltkViewBase::BackBuffer::BackBuffer () +{ + w = 0; + h = 0; + created = false; +} + +FltkViewBase::BackBuffer::~BackBuffer () +{ + if (created) + fl_delete_offscreen (offscreen); +} + +void FltkViewBase::BackBuffer::setSize (int w, int h) +{ + if (!created || w > this->w || h > this->h) { + this->w = w; + this->h = h; + if (created) + fl_delete_offscreen (offscreen); + offscreen = fl_create_offscreen (w, h); + created = true; + } +} + +FltkViewBase::BackBuffer *FltkViewBase::backBuffer; +bool FltkViewBase::backBufferInUse; + +FltkViewBase::FltkViewBase (int x, int y, int w, int h, const char *label): + Fl_Group (x, y, w, h, label) +{ + Fl_Group::current(0); + canvasWidth = 1; + canvasHeight = 1; + bgColor = FL_WHITE; + mouse_x = mouse_y = 0; + focused_child = NULL; + exposeArea = NULL; + if (backBuffer == NULL) { + backBuffer = new BackBuffer (); + } + box(FL_NO_BOX); + resizable(NULL); +} + +FltkViewBase::~FltkViewBase () +{ + cancelQueueDraw (); +} + +void FltkViewBase::setBufferedDrawing (bool b) { + if (b && backBuffer == NULL) { + backBuffer = new BackBuffer (); + } else if (!b && backBuffer != NULL) { + delete backBuffer; + backBuffer = NULL; + } +} + +void FltkViewBase::draw () +{ + int d = damage (); + + if ((d & FL_DAMAGE_USER1) && !(d & FL_DAMAGE_EXPOSE)) { + lout::container::typed::Iterator <core::Rectangle> it; + + for (it = drawRegion.rectangles (); it.hasNext (); ) { + draw (it.getNext (), DRAW_BUFFERED); + } + + drawRegion.clear (); + d &= ~FL_DAMAGE_USER1; + } + + if (d & FL_DAMAGE_CHILD) { + drawChildWidgets (); + d &= ~FL_DAMAGE_CHILD; + } + + if (d) { + dw::core::Rectangle rect ( + translateViewXToCanvasX (x ()), + translateViewYToCanvasY (y ()), + w (), + h ()); + + if (d == FL_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 X = translateCanvasXToViewX (rect->x); + int Y = translateCanvasYToViewY (rect->y); + int W, H; + + // fl_clip_box() can't handle values greater than SHRT_MAX! + if (X > x () + w () || Y > y () + h ()) + return; + + W = X + rect->width > x () + w () ? x () + w () - X : rect->width; + H = Y + rect->height > y () + h () ? y () + h () - Y : rect->height; + + fl_clip_box(X, Y, W, H, X, Y, W, H); + + core::Rectangle r (translateViewXToCanvasX (X), + translateViewYToCanvasY (Y), W, H); + + if (r.isEmpty ()) + return; + + exposeArea = &r; + + if (type == DRAW_BUFFERED && backBuffer && !backBufferInUse) { + backBufferInUse = true; + backBuffer->setSize (X + W, Y + H); // would be nicer to use (W, H)... + fl_begin_offscreen (backBuffer->offscreen); + fl_push_matrix (); + fl_color (bgColor); + fl_rectf (X, Y, W, H); + theLayout->expose (this, &r); + fl_pop_matrix (); + fl_end_offscreen (); + fl_copy_offscreen (X, Y, W, H, backBuffer->offscreen, X, Y); + 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 + fl_push_clip (X, Y, W, H); + fl_color (bgColor); + fl_rectf (X, Y, W, H); + theLayout->expose (this, &r); + fl_pop_clip (); + } else { + fl_color (bgColor); + fl_rectf (X, Y, W, H); + theLayout->expose (this, &r); + } + // DEBUG: + //fl_color(FL_RED); + //fl_rect(X, Y, W, H); + + exposeArea = NULL; +} + +void FltkViewBase::drawChildWidgets () { + for (int i = children () - 1; i >= 0; i--) { + Fl_Widget& w = *child(i); +#if 0 +PORT1.3 + if (w.damage() & DAMAGE_CHILD_LABEL) { + draw_outside_label(w); + w.set_damage(w.damage() & ~DAMAGE_CHILD_LABEL); + } +#endif + update_child(w); + } +} + +core::ButtonState getDwButtonState () +{ + int s1 = Fl::event_state (); + int s2 = (core::ButtonState)0; + + if (s1 & FL_SHIFT) s2 |= core::SHIFT_MASK; + if (s1 & FL_CTRL) s2 |= core::CONTROL_MASK; + if (s1 & FL_ALT) s2 |= core::META_MASK; + if (s1 & FL_BUTTON1) s2 |= core::BUTTON1_MASK; + if (s1 & FL_BUTTON2) s2 |= core::BUTTON2_MASK; + if (s1 & FL_BUTTON3) s2 |= core::BUTTON3_MASK; + + return (core::ButtonState)s2; +} + +/* + * We handle Tab to determine which FLTK widget should get focus. + * + * Presumably a proper solution that allows focusing links, etc., would live + * in Textblock and use iterators. + */ +int FltkViewBase::manageTabToFocus() +{ + int i, ret = 0; + Fl_Widget *old_child = NULL; + + if (this == Fl::focus()) { + // if we have focus, give it to a child. Go forward typically, + // or backward with Shift pressed. + if (!(Fl::event_state() & FL_SHIFT)) { + for (i = 0; i < children(); i++) { + if (child(i)->take_focus()) { + ret = 1; + break; + } + } + } else { + for (i = children() - 1; i >= 0; i--) { + if (child(i)->take_focus()) { + ret = 1; + break; + } + } + } + } else { + // tabbing between children + old_child = Fl::focus(); + + if (!(ret = Fl_Group::handle (FL_KEYBOARD))) { + // group didn't have any more children to focus. + Fl::focus(this); + return 1; + } else { + // which one did it focus? (Note i == children() if not found) + i = find(Fl::focus()); + } + } + if (ret) { + if (i >= 0 && i < children()) { + Fl_Widget *c = child(i); + int canvasX = translateViewXToCanvasX(c->x()), + canvasY = translateViewYToCanvasY(c->y()); + + theLayout->scrollTo(core::HPOS_INTO_VIEW, core::VPOS_INTO_VIEW, + canvasX, canvasY, c->w(), c->h()); + + // Draw the children who gained and lost focus. Otherwise a + // widget that had been only partly visible still shows its old + // appearance in the previously-visible portion. + core::Rectangle r(canvasX, canvasY, c->w(), c->h()); + + queueDraw(&r); + + if (old_child) { + r.x = translateViewXToCanvasX(old_child->x()); + r.y = translateViewYToCanvasY(old_child->y()); + r.width = old_child->w(); + r.height = old_child->h(); + queueDraw(&r); + } + } + } + return ret; +} + +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: + /* Hide the tooltip */ + theLayout->cancelTooltip(); + + processed = + theLayout->buttonPress (this, Fl::event_clicks () + 1, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState (), Fl::event_button ()); + _MSG("PUSH => %s\n", processed ? "true" : "false"); + if (processed) { + /* pressed dw content; give focus to the view */ + if (Fl::event_button() != FL_RIGHT_MOUSE) + Fl::focus(this); + return true; + } + break; + case FL_RELEASE: + processed = + theLayout->buttonRelease (this, Fl::event_clicks () + 1, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState (), Fl::event_button ()); + _MSG("RELEASE => %s\n", processed ? "true" : "false"); + if (processed) + return true; + break; + case FL_MOVE: + mouse_x = Fl::event_x(); + mouse_y = Fl::event_y(); + processed = + theLayout->motionNotify (this, + translateViewXToCanvasX (mouse_x), + translateViewYToCanvasY (mouse_y), + getDwButtonState ()); + _MSG("MOVE => %s\n", processed ? "true" : "false"); + if (processed) + return true; + break; + case FL_DRAG: + processed = + theLayout->motionNotify (this, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState ()); + _MSG("DRAG => %s\n", processed ? "true" : "false"); + if (processed) + return true; + break; + case FL_ENTER: + theLayout->enterNotify (this, + translateViewXToCanvasX (Fl::event_x ()), + translateViewYToCanvasY (Fl::event_y ()), + getDwButtonState ()); + break; + case FL_HIDE: + /* WORKAROUND: strangely, the tooltip window is not automatically hidden + * with its parent. Here we fake a LEAVE to achieve it. */ + case FL_LEAVE: + theLayout->leaveNotify (this, getDwButtonState ()); + break; + case FL_FOCUS: + if (focused_child && find(focused_child) < children()) { + /* strangely, find() == children() if the child is not found */ + focused_child->take_focus(); + } + return 1; + case FL_UNFOCUS: + focused_child = fl_oldfocus; + return 0; + case FL_KEYBOARD: + if (Fl::event_key() == FL_Tab) + return manageTabToFocus(); + break; + default: + break; + } + return Fl_Group::handle (event); +} + +// ---------------------------------------------------------------------- + +void FltkViewBase::setLayout (core::Layout *layout) +{ + theLayout = layout; + if (usesViewport()) + theLayout->viewportSizeChanged(this, w(), h()); +} + +void FltkViewBase::setCanvasSize (int width, int ascent, int descent) +{ + canvasWidth = width; + canvasHeight = ascent + descent; +} + +void FltkViewBase::setCursor (core::style::Cursor cursor) +{ + static Fl_Cursor mapDwToFltk[] = { + FL_CURSOR_CROSS, + FL_CURSOR_DEFAULT, + FL_CURSOR_HAND, + FL_CURSOR_MOVE, + FL_CURSOR_WE, + FL_CURSOR_NESW, + FL_CURSOR_NWSE, + FL_CURSOR_NS, + FL_CURSOR_NWSE, + FL_CURSOR_NESW, + FL_CURSOR_NS, + FL_CURSOR_WE, + FL_CURSOR_INSERT, + FL_CURSOR_WAIT, + FL_CURSOR_HELP + }; + + fl_cursor (mapDwToFltk[cursor]); +} + +void FltkViewBase::setBgColor (core::style::Color *color) +{ + bgColor = color ? + ((FltkColor*)color)->colors[dw::core::style::Color::SHADING_NORMAL] : + FL_WHITE; +} + +void FltkViewBase::startDrawing (core::Rectangle *area) +{ +} + +void FltkViewBase::finishDrawing (core::Rectangle *area) +{ +} + +void FltkViewBase::queueDraw (core::Rectangle *area) +{ + drawRegion.addRectangle (area); + damage (FL_DAMAGE_USER1); +} + +void FltkViewBase::queueDrawTotal () +{ + damage (FL_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) +{ + fl_color(((FltkColor*)color)->colors[shading]); + // we clip with a large border (5000px), as clipping causes artefacts + // with non-solid line styles. + // However it's still better than no clipping at all. + clipPoint (&x1, &y1, 5000); + clipPoint (&x2, &y2, 5000); + fl_line (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; + fl_line_style(FL_DASH + FL_CAP_ROUND, w, dashes); + + /* These formulas also work, but ain't pretty ;) + * fl_line_style(FL_DOT + FL_CAP_ROUND, w); + * dashes[0] = 1; dashes[1] = 3*width-2; dashes[2] = 0; + */ + } else if (type == core::style::LINE_DASHED) { + fl_line_style(FL_DASH + FL_CAP_ROUND, w); + } + + fl_color(((FltkColor*)color)->colors[shading]); + drawLine (color, shading, x1, y1, x2, y2); + + if (type != core::style::LINE_NORMAL) + fl_line_style(FL_SOLID); +} + +void FltkViewBase::drawRectangle (core::style::Color *color, + core::style::Color::Shading shading, + bool filled, + int X, int Y, int width, int height) +{ + fl_color(((FltkColor*)color)->colors[shading]); + if (width < 0) { + X += width; + width = -width; + } + if (height < 0) { + Y += height; + height = -height; + } + + int x1 = X; + int y1 = Y; + int x2 = X + width; + int y2 = 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); + + x1 = translateCanvasXToViewX (x1); + y1 = translateCanvasYToViewY (y1); + x2 = translateCanvasXToViewX (x2); + y2 = translateCanvasYToViewY (y2); + + if (filled) + fl_rectf (x1, y1, x2 - x1, y2 - y1); + else + fl_rect (x1, y1, x2 - x1, y2 - y1); +} + +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) +{ + fl_color(((FltkColor*)color)->colors[shading]); + int x = translateCanvasXToViewX (centerX) - width / 2; + int y = translateCanvasYToViewY (centerY) - height / 2; + + fl_arc(x, y, width, height, angle1, angle2); + if (filled) { + // WORKAROUND: We call both fl_arc and fl_pie due to a FLTK bug + // (STR #2703) that was present in 1.3.0. + fl_pie(x, y, width, height, angle1, angle2); + } +} + +void FltkViewBase::drawPolygon (core::style::Color *color, + core::style::Color::Shading shading, + bool filled, bool convex, core::Point *points, + int npoints) +{ + if (npoints > 0) { + fl_color(((FltkColor*)color)->colors[shading]); + + if (filled) { + if (convex) + fl_begin_polygon(); + else + fl_begin_complex_polygon(); + } else + fl_begin_loop(); + + for (int i = 0; i < npoints; i++) { + fl_vertex(translateCanvasXToViewX(points[i].x), + translateCanvasYToViewY(points[i].y)); + } + if (filled) { + if (convex) + fl_end_polygon(); + else + fl_end_complex_polygon(); + } else + fl_end_loop(); + } +} + +core::View *FltkViewBase::getClippingView (int x, int y, int width, int height) +{ + fl_push_clip (translateCanvasXToViewX (x), translateCanvasYToViewY (y), + width, height); + return this; +} + +void FltkViewBase::mergeClippingView (core::View *clippingView) +{ + fl_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::drawText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int X, int Y, const char *text, int len) +{ + //printf ("drawText (..., %d, %d, '", X, Y); + //for (int i = 0; i < len; i++) + // putchar (text[i]); + //printf ("'\n"); + + FltkFont *ff = (FltkFont*)font; + fl_font(ff->font, ff->size); + fl_color(((FltkColor*)color)->colors[shading]); + + if (!font->letterSpacing && !font->fontVariant) { + fl_draw(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]; + int c, cu, width; + + if (font->fontVariant == core::style::FONT_VARIANT_SMALL_CAPS) { + int sc_fontsize = lout::misc::roundInt(ff->size * 0.78); + for (curr = 0; next < len; curr = next) { + next = theLayout->nextGlyph(text, curr); + c = fl_utf8decode(text + curr, text + next, &nb); + if ((cu = fl_toupper(c)) == c) { + /* already uppercase, just draw the character */ + fl_font(ff->font, ff->size); + width = (int)fl_width(text + curr, next - curr); + if (curr && width) + viewX += font->letterSpacing; + fl_draw(text + curr, next - curr, viewX, viewY); + viewX += width; + } else { + /* make utf8 string for converted char */ + nb = fl_utf8encode(cu, chbuf); + fl_font(ff->font, sc_fontsize); + width = (int)fl_width(chbuf, nb); + if (curr && width) + viewX += font->letterSpacing; + fl_draw(chbuf, nb, viewX, viewY); + viewX += width; + } + } + } else { + while (next < len) { + next = theLayout->nextGlyph(text, curr); + width = (int)fl_width(text + curr, next - curr); + if (curr && width) + viewX += font->letterSpacing; + fl_draw(text + curr, next - curr, viewX, viewY); + viewX += width; + curr = next; + } + } + } +} + +/* + * "simple" in that it ignores letter-spacing, etc. This was added for image + * alt text where none of that matters. + */ +void FltkWidgetView::drawSimpleWrappedText (core::style::Font *font, + core::style::Color *color, + core::style::Color::Shading shading, + int X, int Y, int W, int H, + const char *text) +{ + FltkFont *ff = (FltkFont*)font; + fl_font(ff->font, ff->size); + fl_color(((FltkColor*)color)->colors[shading]); + fl_draw(text, + translateCanvasXToViewX (X), translateCanvasYToViewY (Y), + W, H, FL_ALIGN_TOP|FL_ALIGN_LEFT|FL_ALIGN_WRAP, NULL, 0); +} + +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 (Fl_Widget *widget, + core::Allocation *allocation) +{ + allocateFltkWidget (widget, allocation); + add (widget); +} + +void FltkWidgetView::removeFltkWidget (Fl_Widget *widget) +{ + remove (widget); +} + +void FltkWidgetView::allocateFltkWidget (Fl_Widget *widget, + core::Allocation *allocation) +{ + widget->resize (translateCanvasXToViewX (allocation->x), + translateCanvasYToViewY (allocation->y), + allocation->width, + allocation->ascent + allocation->descent); +} + +void FltkWidgetView::drawFltkWidget (Fl_Widget *widget, + core::Rectangle *area) +{ + draw_child (*widget); + draw_outside_label(*widget); +} + +} // namespace fltk +} // namespace dw |