Files
flenser/dw/fltkviewport.cc

673 lines
19 KiB
C++

/*
* Dillo Widget
*
* Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
* Copyright 2024 Rodrigo Arias Mallo <rodarima@gmail.com>
*
* 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 <FL/Fl.H>
#include <FL/fl_draw.H>
#include <FL/names.h>
#include <stdio.h>
#include "../lout/msg.h"
#include "../lout/debug.hh"
using namespace lout;
using namespace lout::object;
using namespace lout::container::typed;
namespace dw {
namespace fltk {
/*
* Lets SHIFT+{Left,Right} go to the parent
*/
class CustScrollbar : public Fl_Scrollbar
{
public:
CustScrollbar(int x, int y, int w, int h) : Fl_Scrollbar(x,y,w,h) {};
int handle(int e) {
if (e == FL_SHORTCUT && Fl::event_state() == FL_SHIFT &&
(Fl::event_key() == FL_Left || Fl::event_key() == FL_Right))
return 0;
return Fl_Scrollbar::handle(e);
}
};
FltkViewport::FltkViewport (int X, int Y, int W, int H, const char *label):
FltkWidgetView (X, Y, W, H, label)
{
DBG_OBJ_CREATE ("dw::fltk::FltkViewport");
hscrollbar = new CustScrollbar (x (), y (), 1, 1);
hscrollbar->type(FL_HORIZONTAL);
hscrollbar->callback (hscrollbarCallback, this);
hscrollbar->hide();
add (hscrollbar);
vscrollbar = new Fl_Scrollbar (x (), y(), 1, 1);
vscrollbar->type(FL_VERTICAL);
vscrollbar->callback (vscrollbarCallback, this);
vscrollbar->hide();
add (vscrollbar);
hasDragScroll = 1;
scrollX = scrollY = scrollDX = scrollDY = 0;
horScrolling = verScrolling = dragScrolling = 0;
scrollbarPageMode = false;
pageOverlap = 50;
pageScrolling = core::NONE_CMD;
pageScrollDelay = 0.300;
pageScrollInterval = 0.100;
gadgetOrientation[0] = GADGET_HORIZONTAL;
gadgetOrientation[1] = GADGET_HORIZONTAL;
gadgetOrientation[2] = GADGET_VERTICAL;
gadgetOrientation[3] = GADGET_HORIZONTAL;
}
FltkViewport::~FltkViewport ()
{
DBG_OBJ_DELETE ();
}
void FltkViewport::adjustScrollbarsAndGadgetsAllocation ()
{
int hdiff = 0, vdiff = 0;
int visibility = 0;
_MSG(" >>FltkViewport::adjustScrollbarsAndGadgetsAllocation\n");
if (hscrollbar->visible ())
visibility |= 1;
if (vscrollbar->visible ())
visibility |= 2;
if (gadgets.size () > 0) {
switch (gadgetOrientation [visibility]) {
case GADGET_VERTICAL:
hdiff = SCROLLBAR_THICKNESS;
vdiff = SCROLLBAR_THICKNESS * gadgets.size ();
break;
case GADGET_HORIZONTAL:
hdiff = SCROLLBAR_THICKNESS * gadgets.size ();
vdiff = SCROLLBAR_THICKNESS;
break;
}
} else {
hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
}
if (scrollbarOnLeft) {
hscrollbar->resize(x () + hdiff, y () + h () - SCROLLBAR_THICKNESS,
w () - hdiff, SCROLLBAR_THICKNESS);
vscrollbar->resize(x (), y (),
SCROLLBAR_THICKNESS, h () - vdiff);
} else {
hscrollbar->resize(x (), y () + h () - SCROLLBAR_THICKNESS,
w () - hdiff, SCROLLBAR_THICKNESS);
vscrollbar->resize(x () + w () - SCROLLBAR_THICKNESS, y (),
SCROLLBAR_THICKNESS, h () - vdiff);
}
//int X = x () + w () - SCROLLBAR_THICKNESS;
//int Y = y () + h () - SCROLLBAR_THICKNESS;
for( auto &widget : gadgets )
{
widget->resize(x (), y (), SCROLLBAR_THICKNESS, SCROLLBAR_THICKNESS);
/* FIXME: This has no effect */
#if 0
switch (gadgetOrientation [visibility]) {
case GADGET_VERTICAL:
Y -= SCROLLBAR_THICKNESS;
break;
case GADGET_HORIZONTAL:
X -= SCROLLBAR_THICKNESS;
break;
}
#endif
}
adjustScrollbarValues();
}
void FltkViewport::adjustScrollbarValues ()
{
hscrollbar->value (scrollX, hscrollbar->w (), 0, canvasWidth);
vscrollbar->value (scrollY, vscrollbar->h (), 0, canvasHeight);
}
void FltkViewport::hscrollbarChanged ()
{
scroll (hscrollbar->value () - scrollX, 0);
}
void FltkViewport::vscrollbarChanged ()
{
scroll (0, vscrollbar->value () - scrollY);
}
void FltkViewport::vscrollbarCallback (Fl_Widget *vscrollbar,void *viewportPtr)
{
((FltkViewport*)viewportPtr)->vscrollbarChanged ();
}
void FltkViewport::hscrollbarCallback (Fl_Widget *hscrollbar,void *viewportPtr)
{
((FltkViewport*)viewportPtr)->hscrollbarChanged ();
}
// ----------------------------------------------------------------------
void FltkViewport::resize(int X, int Y, int W, int H)
{
bool dimension_changed = W != w() || H != h();
Fl_Group::resize(X, Y, W, H);
if (dimension_changed) {
theLayout->viewportSizeChanged (this, W, H);
adjustScrollbarsAndGadgetsAllocation ();
}
}
void FltkViewport::draw_area (void *data, int x, int y, int w, int h)
{
FltkViewport *vp = (FltkViewport*) data;
fl_push_clip(x, y, w, h);
vp->FltkWidgetView::draw ();
for( auto &widget: vp->gadgets )
{
vp->draw_child (*widget);
}
fl_pop_clip();
}
/*
* Draw the viewport.
*
* + Damage flags come in different ways, draw() should cope with them all.
* + Damage flags are alive for visible and hidden widgets.
* + FL_DAMAGE_CHILD can flag scroll bars or embedded FLTK widgets.
*/
void FltkViewport::draw ()
{
const int d = damage(),
vis_vs = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0,
vis_hs = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0,
draw = d & (FL_DAMAGE_ALL | FL_DAMAGE_EXPOSE),
draw_vs = vis_vs && vscrollbar->damage (),
draw_hs = vis_hs && hscrollbar->damage ();
_MSG("FltkViewport::draw d=%d => ", d);
// scrollbars
if (draw || draw_vs)
draw_child (*vscrollbar);
if (draw || draw_hs)
draw_child (*hscrollbar);
if (draw && vis_vs && vis_hs) {
fl_color(FL_BACKGROUND_COLOR);
if (scrollbarOnLeft) {
fl_rectf(x(), y()+h()-vis_hs, vis_vs, vis_hs);
} else {
fl_rectf(x()+w()-vis_vs, y()+h()-vis_hs, vis_vs, vis_hs);
}
}
// main area
if (d == FL_DAMAGE_CHILD && (draw_vs || draw_hs)) {
_MSG("none\n");
} else if (d == (FL_DAMAGE_SCROLL | FL_DAMAGE_CHILD)) {
int x = this->x();
if (scrollbarOnLeft)
x += vis_vs;
fl_scroll(x, y(), w() - vis_vs, h() - vis_hs,
-scrollDX, -scrollDY, draw_area, this);
_MSG("fl_scroll()\n");
} else {
int x = this->x();
if (scrollbarOnLeft)
x += vis_vs;
draw_area(this, x, y(), w() - vis_vs, h() - vis_hs);
_MSG("draw_area()\n");
}
scrollDX = 0;
scrollDY = 0;
}
int FltkViewport::handle (int event)
{
int ret = 0;
_MSG("FltkViewport::handle %s\n", fl_eventnames[event]);
switch(event) {
case FL_KEYBOARD:
/* When the viewport has focus (and not one of its children), FLTK
* sends the event here. Returning zero tells FLTK to resend the
* event as SHORTCUT, which we finally route to the parent. */
/* As we don't know the exact keybindings set by the user, we ask for
* all of them (except for the minimum needed to keep form navigation).*/
if (Fl::event_key() != FL_Tab || Fl::event_ctrl())
return 0;
break;
case FL_SHORTCUT:
/* send it to the parent (UI) */
return 0;
case FL_FOCUS:
/** \bug Draw focus box. */
break;
case FL_UNFOCUS:
/** \bug Undraw focus box. */
break;
case FL_PUSH:
if (vscrollbar->visible() && Fl::event_inside(vscrollbar)) {
if (scrollbarPageMode ^ (bool) Fl::event_shift()) {
/* Check top and bottom actions first */
int yclick = Fl::event_y();
int ytop = y() + SCROLLBAR_THICKNESS;
int ybottom = y() + h() - SCROLLBAR_THICKNESS;
if (hscrollbar->visible())
ybottom -= SCROLLBAR_THICKNESS;
if (yclick <= ytop) {
scroll(core::TOP_CMD);
return 1;
} else if (yclick >= ybottom) {
scroll(core::BOTTOM_CMD);
return 1;
}
if (Fl::event_button() == FL_LEFT_MOUSE)
pageScrolling = core::SCREEN_DOWN_CMD;
else if (Fl::event_button() == FL_RIGHT_MOUSE)
pageScrolling = core::SCREEN_UP_CMD;
else
pageScrolling = core::NONE_CMD;
if (pageScrolling != core::NONE_CMD) {
scroll(pageScrolling);
/* Repeat until released */
if (!Fl::has_timeout(repeatPageScroll, this))
Fl::add_timeout(pageScrollDelay, repeatPageScroll, this);
return 1;
}
}
if (vscrollbar->handle(event)) {
verScrolling = 1;
}
} else if (hscrollbar->visible() && Fl::event_inside(hscrollbar)) {
if (hscrollbar->handle(event))
horScrolling = 1;
} else if (FltkWidgetView::handle(event) == 0 &&
Fl::event_button() == FL_MIDDLE_MOUSE) {
if (!hasDragScroll) {
/* let the parent widget handle it... */
return 0;
} else {
/* receive FL_DRAG and FL_RELEASE */
dragScrolling = 1;
dragX = Fl::event_x();
dragY = Fl::event_y();
setCursor (core::style::CURSOR_MOVE);
}
}
return 1;
break;
case FL_DRAG:
if (Fl::event_inside(this))
Fl::remove_timeout(selectionScroll);
if (dragScrolling) {
scroll(dragX - Fl::event_x(), dragY - Fl::event_y());
dragX = Fl::event_x();
dragY = Fl::event_y();
return 1;
} else if (verScrolling) {
vscrollbar->handle(event);
return 1;
} else if (horScrolling) {
hscrollbar->handle(event);
return 1;
} else if (!Fl::event_inside(this)) {
mouse_x = Fl::event_x();
mouse_y = Fl::event_y();
if (!Fl::has_timeout(selectionScroll, this))
Fl::add_timeout(0.025, selectionScroll, this);
}
break;
case FL_MOUSEWHEEL:
if ((vscrollbar->visible() && Fl::event_inside(vscrollbar)) ||
Fl::event_shift()) {
if (Fl::event_dy() > 0) {
scroll(core::SCREEN_DOWN_CMD);
return 1;
} else if (Fl::event_dy() < 0) {
scroll(core::SCREEN_UP_CMD);
return 1;
}
}
return (Fl::event_dx() ? hscrollbar : vscrollbar)->handle(event);
break;
case FL_RELEASE:
Fl::remove_timeout(repeatPageScroll);
Fl::remove_timeout(selectionScroll);
if (Fl::event_button() == FL_MIDDLE_MOUSE) {
setCursor (core::style::CURSOR_DEFAULT);
} else if (verScrolling) {
ret = vscrollbar->handle(event);
} else if (horScrolling) {
ret = hscrollbar->handle(event);
}
horScrolling = verScrolling = dragScrolling = 0;
break;
case FL_ENTER:
if (vscrollbar->visible() && Fl::event_inside(vscrollbar))
return vscrollbar->handle(event);
if (hscrollbar->visible() && Fl::event_inside(hscrollbar))
return hscrollbar->handle(event);
/* could be the result of, e.g., closing another window. */
mouse_x = Fl::event_x();
mouse_y = Fl::event_y();
positionChanged();
break;
case FL_MOVE:
/* Use LEAVE in order not to be over a link, etc., anymore. */
if (vscrollbar->visible() && Fl::event_inside(vscrollbar)) {
(void)FltkWidgetView::handle(FL_LEAVE);
return vscrollbar->handle(event);
}
if (hscrollbar->visible() && Fl::event_inside(hscrollbar)) {
(void)FltkWidgetView::handle(FL_LEAVE);
return hscrollbar->handle(event);
}
break;
case FL_LEAVE:
mouse_x = mouse_y = -1;
break;
}
return ret ? ret : FltkWidgetView::handle (event);
}
// ----------------------------------------------------------------------
void FltkViewport::setCanvasSize (int width, int ascent, int descent)
{
FltkWidgetView::setCanvasSize (width, ascent, descent);
adjustScrollbarValues ();
}
/*
* This is used to simulate mouse motion (e.g., when scrolling).
*/
void FltkViewport::positionChanged ()
{
if (!dragScrolling && mouse_x >= x() && mouse_x < x()+w() && mouse_y >= y()
&& mouse_y < y()+h())
(void)theLayout->motionNotify (this,
translateViewXToCanvasX (mouse_x),
translateViewYToCanvasY (mouse_y),
(core::ButtonState)0);
}
/*
* For scrollbars, this currently sets the same step to both vertical and
* horizontal. It may be differentiated if necessary.
*/
void FltkViewport::setScrollStep(int step)
{
vscrollbar->linesize(step);
hscrollbar->linesize(step);
}
void FltkViewport::setPageOverlap(int overlap)
{
pageOverlap = overlap;
}
void FltkViewport::setScrollbarPageMode(bool enable)
{
scrollbarPageMode = enable;
}
bool FltkViewport::usesViewport ()
{
return true;
}
int FltkViewport::getHScrollbarThickness ()
{
return SCROLLBAR_THICKNESS;
}
int FltkViewport::getVScrollbarThickness ()
{
return SCROLLBAR_THICKNESS;
}
void FltkViewport::scrollTo (int x, int y)
{
int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
x = std::min (x, canvasWidth - w() + hdiff);
x = std::max (x, 0);
y = std::min (y, canvasHeight - h() + vdiff);
y = std::max (y, 0);
if (x == scrollX && y == scrollY) {
return;
}
/* multiple calls to scroll can happen before a redraw occurs.
* scrollDX and scrollDY can therefore be non-zero here.
*/
updateCanvasWidgets (x - scrollX, y - scrollY);
scrollDX += x - scrollX;
scrollDY += y - scrollY;
scrollX = x;
scrollY = y;
adjustScrollbarValues ();
damage(FL_DAMAGE_SCROLL);
theLayout->scrollPosChanged (this, scrollX, scrollY);
positionChanged();
}
void FltkViewport::scroll (int dx, int dy)
{
scrollTo (scrollX + dx, scrollY + dy);
}
void FltkViewport::scroll (core::ScrollCommand cmd)
{
int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;
if (cmd == core::SCREEN_UP_CMD) {
scroll (0, -h () + pageOverlap + vdiff);
} else if (cmd == core::SCREEN_DOWN_CMD) {
scroll (0, h () - pageOverlap - vdiff);
} else if (cmd == core::SCREEN_LEFT_CMD) {
scroll (-w() + pageOverlap + hdiff, 0);
} else if (cmd == core::SCREEN_RIGHT_CMD) {
scroll (w() - pageOverlap - hdiff, 0);
} else if (cmd == core::LINE_UP_CMD) {
scroll (0, -vscrollbar->linesize ());
} else if (cmd == core::LINE_DOWN_CMD) {
scroll (0, vscrollbar->linesize ());
} else if (cmd == core::LEFT_CMD) {
scroll (-hscrollbar->linesize (), 0);
} else if (cmd == core::RIGHT_CMD) {
scroll (hscrollbar->linesize (), 0);
} else if (cmd == core::TOP_CMD) {
scrollTo (scrollX, 0);
} else if (cmd == core::BOTTOM_CMD) {
scrollTo (scrollX, canvasHeight); /* gets adjusted in scrollTo () */
}
}
/*
* Scrolling in response to selection where the cursor is outside the view.
*/
void FltkViewport::selectionScroll ()
{
int distance;
int dx = 0, dy = 0;
if ((distance = x() - mouse_x) > 0)
dx = -distance * hscrollbar->linesize () / 48 - 1;
else if ((distance = mouse_x - (x() + w())) > 0)
dx = distance * hscrollbar->linesize () / 48 + 1;
if ((distance = y() - mouse_y) > 0)
dy = -distance * vscrollbar->linesize () / 48 - 1;
else if ((distance = mouse_y - (y() + h())) > 0)
dy = distance * vscrollbar->linesize () / 48 + 1;
scroll (dx, dy);
}
void FltkViewport::selectionScroll (void *data)
{
((FltkViewport *)data)->selectionScroll ();
Fl::repeat_timeout(0.025, selectionScroll, data);
}
void FltkViewport::repeatPageScroll ()
{
scroll(pageScrolling);
Fl::repeat_timeout(pageScrollInterval, repeatPageScroll, this);
}
void FltkViewport::repeatPageScroll (void *data)
{
((FltkViewport *)data)->repeatPageScroll ();
}
void FltkViewport::setScrollbarOnLeft (bool enable)
{
scrollbarOnLeft = enable ? 1 : 0;
adjustScrollbarsAndGadgetsAllocation();
damage(FL_DAMAGE_ALL);
}
void FltkViewport::setViewportSize (int width, int height,
int hScrollbarThickness,
int vScrollbarThickness)
{
int adjustReq =
(hscrollbar->visible() ? !hScrollbarThickness : hScrollbarThickness) ||
(vscrollbar->visible() ? !vScrollbarThickness : vScrollbarThickness);
_MSG("FltkViewport::setViewportSize old_w,old_h=%dx%d -> w,h=%dx%d\n"
"\t hThick=%d hVis=%d, vThick=%d vVis=%d, adjustReq=%d\n",
w(),h(),width,height,
hScrollbarThickness,hscrollbar->visible(),
vScrollbarThickness,vscrollbar->visible(), adjustReq);
(hScrollbarThickness > 0) ? hscrollbar->show () : hscrollbar->hide ();
(vScrollbarThickness > 0) ? vscrollbar->show () : vscrollbar->hide ();
/* If no scrollbar, go to the beginning */
scroll(hScrollbarThickness ? 0 : -scrollX,
vScrollbarThickness ? 0 : -scrollY);
/* Adjust when scrollbar visibility changes */
if (adjustReq)
adjustScrollbarsAndGadgetsAllocation ();
}
void FltkViewport::updateCanvasWidgets (int dx, int dy)
{
// scroll all child widgets except scroll bars
for (int i = children () - 1; i > 0; i--) {
Fl_Widget *widget = child (i);
if (widget == hscrollbar || widget == vscrollbar)
continue;
widget->position(widget->x () - dx, widget->y () - dy);
}
}
int FltkViewport::translateViewXToCanvasX (int X)
{
return X - x () + scrollX;
}
int FltkViewport::translateViewYToCanvasY (int Y)
{
return Y - y () + scrollY;
}
int FltkViewport::translateCanvasXToViewX (int X)
{
return X + x () - scrollX;
}
int FltkViewport::translateCanvasYToViewY (int Y)
{
return Y + y () - scrollY;
}
// ----------------------------------------------------------------------
void FltkViewport::setGadgetOrientation (bool hscrollbarVisible,
bool vscrollbarVisible,
FltkViewport::GadgetOrientation
gadgetOrientation)
{
this->gadgetOrientation[(hscrollbarVisible ? 0 : 1) |
(vscrollbarVisible ? 0 : 2)] = gadgetOrientation;
adjustScrollbarsAndGadgetsAllocation ();
}
void FltkViewport::addGadget (std::unique_ptr< Fl_Widget > gadget)
{
/** \bug Reparent? */
gadgets.push_back( std::move( gadget ) );
adjustScrollbarsAndGadgetsAllocation ();
}
} // namespace fltk
} // namespace dw