Skip to content
Snippets Groups Projects
Select Git revision
  • gmsh_4_12_2
  • master default protected
  • overlaps_tags_and_distributed_export
  • overlaps_tags_and_distributed_export_rebased
  • relaying
  • alphashapes
  • patches-4.14
  • steplayer
  • bl
  • pluginMeshQuality
  • fixBugsAmaury
  • hierarchical-basis
  • new_export_boris
  • oras_vs_osm
  • reassign_partitions
  • distributed_fwi
  • rename-classes
  • fix/fortran-api-example-t4
  • robust_partitions
  • reducing_files
  • fix_overlaps
  • gmsh_4_14_0
  • gmsh_4_13_1
  • gmsh_4_13_0
  • gmsh_4_12_1
  • gmsh_4_12_0
  • gmsh_4_11_1
  • gmsh_4_11_0
  • gmsh_4_10_5
  • gmsh_4_10_4
  • gmsh_4_10_3
  • gmsh_4_10_2
  • gmsh_4_10_1
  • gmsh_4_10_0
  • gmsh_4_9_5
  • gmsh_4_9_4
  • gmsh_4_9_3
  • gmsh_4_9_2
  • gmsh_4_9_1
  • gmsh_4_9_0
40 results

t1.c

Blame
  • openglWindow.cpp 30.44 KiB
    // Gmsh - Copyright (C) 1997-2019 C. Geuzaine, J.-F. Remacle
    //
    // See the LICENSE.txt file for license information. Please report all
    // issues on https://gitlab.onelab.info/gmsh/gmsh/issues.
    
    #include <stdio.h>
    #include <string.h>
    #include <FL/Fl_Tooltip.H>
    #include "openglWindow.h"
    #include "graphicWindow.h"
    #include "manipWindow.h"
    #include "contextWindow.h"
    #include "visibilityWindow.h"
    #include "GmshDefines.h"
    #include "GmshMessage.h"
    #include "GModel.h"
    #include "MElement.h"
    #include "PView.h"
    #include "PViewOptions.h"
    #include "Numeric.h"
    #include "FlGui.h"
    #include "OpenFile.h"
    #include "drawContext.h"
    #include "Context.h"
    #include "Trackball.h"
    #include "GamePad.h"
    
    // Navigator handler (read gamepad event if gamepad exists or question presence
    // of gamepad)
    static void navigator_handler(void *data)
    {
      openglWindow *gl_win = (openglWindow *)data;
      if(CTX::instance()->gamepad && CTX::instance()->gamepad->active) {
        if(gl_win->Nautilus == 0) {
          gl_win->Nautilus = new Navigator(CTX::instance()->gamepad->frequency,
                                           gl_win->getDrawContext());
        }
        gl_win->moveWithGamepad();
        Fl::add_timeout(CTX::instance()->gamepad->frequency, navigator_handler,
                        data);
      }
      else {
        if(gl_win->Nautilus) {
          delete gl_win->Nautilus;
          gl_win->Nautilus = 0;
        }
        Fl::add_timeout(3., navigator_handler, data);
      }
    }
    
    static void lassoZoom(drawContext *ctx, mousePosition &click1,
                          mousePosition &click2)
    {
      if(click1.win[0] == click2.win[0] || click1.win[1] == click2.win[1]) return;
    
      ctx->s[0] *= (double)ctx->viewport[2] / (click2.win[0] - click1.win[0]);
      ctx->s[1] *= (double)ctx->viewport[3] / (click2.win[1] - click1.win[1]);
      ctx->s[2] = std::min(ctx->s[0], ctx->s[1]); // bof...
    
      // recenter around the center of the lasso rectangle
      mousePosition tmp(click1);
      tmp.wnr[0] = 0.5 * (click1.wnr[0] + click2.wnr[0]);
      tmp.wnr[1] = 0.5 * (click1.wnr[1] + click2.wnr[1]);
      tmp.recenter(ctx);
    
      ctx->initPosition(false);
      drawContext::global()->draw();
      FlGui::instance()->manip->update();
    }
    
    openglWindow::openglWindow(int x, int y, int w, int h)
      : Fl_Gl_Window(x, y, w, h, "gl"), _lock(false), _drawn(false),
        _selection(ENT_NONE), _trySelection(0), Nautilus(0)
    {
      _ctx = new drawContext(this);
    
      for(int i = 0; i < 3; i++) _point[i] = 0.;
      for(int i = 0; i < 4; i++) _trySelectionXYWH[i] = 0;
      _lassoXY[0] = _lassoXY[1] = 0;
    
      addPointMode = 0;
      lassoMode = selectionMode = false;
      endSelection = undoSelection = invertSelection = quitSelection = 0;
      changeSelection = 0;
    
      if(CTX::instance()->gamepad)
        Fl::add_timeout(.5, navigator_handler, (void *)this);
    }
    
    openglWindow::~openglWindow()
    {
      delete _ctx;
      if(Nautilus) delete Nautilus;
    }
    
    void openglWindow::show()
    {
      Fl_Gl_Window::show();
      /* You can uncomment this if you cannot use the very latest FLTK 1.4 version
         patched for macOS mojave
    
    #if defined(__APPLE__) && (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14)
      Msg::Info("OpenGL hack for macOS 10.14: see http://www.fltk.org/str.php?L3496");
      resize(x(), y(), w()+1, h());
      resize(x(), y(), w()-1, h());
    #endif
    
      */
    }
    
    void openglWindow::_drawScreenMessage()
    {
      if(screenMessage[0].empty() && screenMessage[1].empty()) return;
    
      glColor4ubv((GLubyte *)&CTX::instance()->color.text);
      drawContext::global()->setFont(CTX::instance()->glFontEnum,
                                     CTX::instance()->glFontSize);
      double h = drawContext::global()->getStringHeight();
    
      if(screenMessage[0].size()) {
        const char *txt = screenMessage[0].c_str();
        double w = drawContext::global()->getStringWidth(txt);
        glRasterPos2d(_ctx->viewport[2] / 2. - w / 2., _ctx->viewport[3] - 1.2 * h);
        drawContext::global()->drawString(txt);
      }
      if(screenMessage[1].size()) {
        const char *txt = screenMessage[1].c_str();
        double w = drawContext::global()->getStringWidth(txt);
        glRasterPos2d(_ctx->viewport[2] / 2. - w / 2., _ctx->viewport[3] - 2.4 * h);
        drawContext::global()->drawString(txt);
      }
    }
    
    void openglWindow::_drawBorder()
    {
      if(!parent()) return;
      // draw thin border if the parent group has more than 2 opengl windows
      int numgl = 0;
      for(int i = 0; i < parent()->children(); i++) {
        if(parent()->child(i)->label() &&
           !strcmp(parent()->child(i)->label(), label()))
          numgl++;
      }
      if(numgl < 2) return;
    
      unsigned char r, g, b;
      Fl::get_color(color(), r, g, b);
      /* would need to redraw all gl's when _lastHandled is changed
       if(_lastHandled == this)
         Fl::get_color(FL_SELECTION_COLOR, r, g, b);
       else
         Fl::get_color(FL_BACKGROUND_COLOR, r, g, b);
      */
      glColor3ub(r, g, b);
      glLineWidth(1.0F);
      glBegin(GL_LINE_LOOP);
      glVertex2d(_ctx->viewport[0], _ctx->viewport[1]);
      glVertex2d(_ctx->viewport[2], _ctx->viewport[1]);
      glVertex2d(_ctx->viewport[2], _ctx->viewport[3]);
      glVertex2d(_ctx->viewport[0], _ctx->viewport[3]);
      glEnd();
    }
    
    void openglWindow::draw()
    {
      // some drawing routines can create data (STL triangulations, etc.): make sure
      // that we don't fire draw() while we are already drawing, e.g. due to an
      // impromptu Fl::check(). The same lock is also used in _select to guarantee
      // that we don't mix GL_RENDER and GL_SELECT rendering passes.
      _drawn = true;
      if(_lock) return;
      _lock = true;
    
      Msg::Debug("openglWindow::draw()");
    
      if(!context_valid()) {
        _ctx->invalidateQuadricsAndDisplayLists();
      }
    
      _ctx->viewport[0] = 0;
      _ctx->viewport[1] = 0;
      _ctx->viewport[2] = w();
      _ctx->viewport[3] = h();
      glViewport(0, 0, pixel_w(), pixel_h());
    
      if(lassoMode) {
        // draw the zoom or selection lasso on top of the current scene (without
        // using overlays!)
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho((double)_ctx->viewport[0], (double)_ctx->viewport[2],
                (double)_ctx->viewport[1], (double)_ctx->viewport[3], -1., 1.);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glColor3d(1., 1., 1.);
        glDisable(GL_DEPTH_TEST);
        glDrawBuffer(GL_FRONT_AND_BACK);
        if(selectionMode && CTX::instance()->mouseSelection) {
          glEnable(GL_LINE_STIPPLE);
          glLineStipple(1, 0x0F0F);
        }
        // glBlendEquation(GL_FUNC_ADD);
        glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
        glEnable(GL_BLEND);
        glLineWidth(0.2F);
        glBegin(GL_LINE_LOOP);
        glVertex2d(_click.win[0], _ctx->viewport[3] - _click.win[1]);
        glVertex2d(_lassoXY[0], _ctx->viewport[3] - _click.win[1]);
        glVertex2d(_lassoXY[0], _ctx->viewport[3] - _lassoXY[1]);
        glVertex2d(_click.win[0], _ctx->viewport[3] - _lassoXY[1]);
        glEnd();
        glBegin(GL_LINE_LOOP);
        glVertex2d(_click.win[0], _ctx->viewport[3] - _click.win[1]);
        glVertex2d(_curr.win[0], _ctx->viewport[3] - _click.win[1]);
        glVertex2d(_curr.win[0], _ctx->viewport[3] - _curr.win[1]);
        glVertex2d(_click.win[0], _ctx->viewport[3] - _curr.win[1]);
        glEnd();
        _lassoXY[0] = _curr.win[0];
        _lassoXY[1] = _curr.win[1];
        glDisable(GL_BLEND);
        glDisable(GL_LINE_STIPPLE);
        glEnable(GL_DEPTH_TEST);
        glDrawBuffer(GL_BACK);
      }
      else if(addPointMode) {
        // draw the whole scene and the point to add
        if(CTX::instance()->fastRedraw) {
          CTX::instance()->mesh.draw = 0;
          CTX::instance()->post.draw = 0;
        }
    
        glClearColor(
          (GLclampf)(CTX::instance()->unpackRed(CTX::instance()->color.bg) / 255.),
          (GLclampf)(CTX::instance()->unpackGreen(CTX::instance()->color.bg) /
                     255.),
          (GLclampf)(CTX::instance()->unpackBlue(CTX::instance()->color.bg) / 255.),
          0.0F);
    
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    
        _ctx->draw3d();
        glColor4ubv((GLubyte *)&CTX::instance()->color.geom.highlight[0]);
        float ps = CTX::instance()->geom.pointSize;
        if(_ctx->isHighResolution())
          ps *= CTX::instance()->highResolutionPointSizeFactor;
        glPointSize(ps);
        glBegin(GL_POINTS);
        glVertex3d(_point[0], _point[1], _point[2]);
        glEnd();
        _ctx->draw2d();
        _drawScreenMessage();
        _drawBorder();
        CTX::instance()->mesh.draw = 1;
        CTX::instance()->post.draw = 1;
      }
      else {
        // draw the whole scene
        if(CTX::instance()->printing && !CTX::instance()->print.background)
          glClearColor(1.0F, 1.0F, 1.0F, 0.0F);
        else
          glClearColor(
            (GLclampf)(CTX::instance()->unpackRed(CTX::instance()->color.bg) /
                       255.),
            (GLclampf)(CTX::instance()->unpackGreen(CTX::instance()->color.bg) /
                       255.),
            (GLclampf)(CTX::instance()->unpackBlue(CTX::instance()->color.bg) /
                       255.),
            0.0F);
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    
        if(CTX::instance()->camera && !CTX::instance()->stereo) {
          Camera *cam = &(_ctx->camera);
          if(!cam->on) cam->init();
          cam->giveViewportDimension(_ctx->viewport[2], _ctx->viewport[3]);
          glMatrixMode(GL_PROJECTION);
          glLoadIdentity();
    
          glFrustum(cam->glFleft, cam->glFright, cam->glFbottom, cam->glFtop,
                    cam->glFnear, cam->glFfar * cam->Lc);
    
          glMatrixMode(GL_MODELVIEW);
          glLoadIdentity();
          glDrawBuffer(GL_BACK);
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
          glLoadIdentity();
          gluLookAt(cam->position.x, cam->position.y, cam->position.z,
                    cam->target.x, cam->target.y, cam->target.z, cam->up.x,
                    cam->up.y, cam->up.z);
          _ctx->draw3d();
          _ctx->draw2d();
          if(CTX::instance()->gamepad && CTX::instance()->gamepad->active &&
             Nautilus)
            Nautilus->drawIcons();
          _drawScreenMessage();
          _drawBorder();
        }
        else if(CTX::instance()->stereo) {
          Camera *cam = &(_ctx->camera);
          if(!cam->on) cam->init();
          cam->giveViewportDimension(_ctx->viewport[2], _ctx->viewport[3]);
          XYZ eye = cam->eyesep / 2.0 * cam->right;
          // right eye
          glMatrixMode(GL_PROJECTION);
          glLoadIdentity();
          double left =
            -cam->screenratio * cam->wd2 - 0.5 * cam->eyesep * cam->ndfl;
          double right =
            cam->screenratio * cam->wd2 - 0.5 * cam->eyesep * cam->ndfl;
          double top = cam->wd2;
          double bottom = -cam->wd2;
          glFrustum(left, right, bottom, top, cam->glFnear, cam->glFfar * cam->Lc);
          glMatrixMode(GL_MODELVIEW);
          glDrawBuffer(GL_BACK_RIGHT);
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
          glLoadIdentity();
          gluLookAt(cam->position.x + eye.x, cam->position.y + eye.y,
                    cam->position.z + eye.z, cam->target.x + eye.x,
                    cam->target.y + eye.y, cam->target.z + eye.z, cam->up.x,
                    cam->up.y, cam->up.z);
          _ctx->draw3d();
          _ctx->draw2d();
          _drawScreenMessage();
          _drawBorder();
          // left eye
          glMatrixMode(GL_PROJECTION);
          glLoadIdentity();
          left = -cam->screenratio * cam->wd2 + 0.5 * cam->eyesep * cam->ndfl;
          right = cam->screenratio * cam->wd2 + 0.5 * cam->eyesep * cam->ndfl;
          top = cam->wd2;
          bottom = -cam->wd2;
          glFrustum(left, right, bottom, top, cam->glFnear, cam->glFfar * cam->Lc);
    
          glMatrixMode(GL_MODELVIEW);
          glDrawBuffer(GL_BACK_LEFT);
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
          glLoadIdentity();
          gluLookAt(cam->position.x - eye.x, cam->position.y - eye.y,
                    cam->position.z - eye.z, cam->target.x - eye.x,
                    cam->target.y - eye.y, cam->target.z - eye.z, cam->up.x,
                    cam->up.y, cam->up.z);
          _ctx->draw3d();
          _ctx->draw2d();
          _drawScreenMessage();
          _drawBorder();
        }
        else {
          _ctx->draw3d();
          _ctx->draw2d();
          _drawScreenMessage();
          _drawBorder();
        }
      }
      drawContext::global()->flushString();
      _lock = false;
    }
    
    openglWindow *openglWindow::_lastHandled = 0;
    
    void openglWindow::_setLastHandled(openglWindow *w)
    {
      _lastHandled = w;
      FlGui::instance()->visibility->updatePerWindow();
    }
    
    int openglWindow::handle(int event)
    {
      switch(event) {
      case FL_FOCUS: // accept the focus when I'm asked if I want it
      case FL_UNFOCUS: return 1;
    
      case FL_SHORTCUT:
      case FL_KEYBOARD:
        // override the default widget arrow-key-navigation
        if(FlGui::instance()->testArrowShortcuts()) return 1;
        return Fl_Gl_Window::handle(event);
    
      case FL_PUSH:
        if(Fl::event_clicks() == 1 && !selectionMode) {
          // double-click and not in selection mode
          std::vector<GVertex *> vertices;
          std::vector<GEdge *> edges;
          std::vector<GFace *> faces;
          std::vector<GRegion *> regions;
          std::vector<MElement *> elements;
          std::vector<SPoint2> points;
          std::vector<PView *> views;
          _select(ENT_ALL, false, false, true, Fl::event_x(), Fl::event_y(), 5, 5,
                  vertices, edges, faces, regions, elements, points, views);
          if(vertices.size() &&
             CTX::instance()->geom.doubleClickedPointCommand.size()) {
            CTX::instance()->geom.doubleClickedEntityTag = vertices[0]->tag();
            ParseString(CTX::instance()->geom.doubleClickedPointCommand, true);
          }
          else if(edges.size() &&
                  CTX::instance()->geom.doubleClickedCurveCommand.size()) {
            CTX::instance()->geom.doubleClickedEntityTag = edges[0]->tag();
            ParseString(CTX::instance()->geom.doubleClickedCurveCommand, true);
          }
          else if(faces.size() &&
                  CTX::instance()->geom.doubleClickedSurfaceCommand.size()) {
            CTX::instance()->geom.doubleClickedEntityTag = faces[0]->tag();
            ParseString(CTX::instance()->geom.doubleClickedSurfaceCommand, true);
          }
          else if(regions.size() &&
                  CTX::instance()->geom.doubleClickedVolumeCommand.size()) {
            CTX::instance()->geom.doubleClickedEntityTag = regions[0]->tag();
            ParseString(CTX::instance()->geom.doubleClickedVolumeCommand, true);
          }
          else if(views.size() &&
                  views[0]->getOptions()->doubleClickedCommand.size()) {
            CTX::instance()->post.doubleClickedView = views[0]->getIndex();
            ParseString(views[0]->getOptions()->doubleClickedCommand, true);
          }
          else if(points.size() &&
                  CTX::instance()->post.doubleClickedGraphPointCommand.size()) {
            CTX::instance()->post.doubleClickedGraphPointX = points[0].x();
            CTX::instance()->post.doubleClickedGraphPointY = points[0].y();
            ParseString(CTX::instance()->post.doubleClickedGraphPointCommand, true);
          }
          else { // popup quick access menu
            status_options_cb(0, (void *)"quick_access");
          }
          Fl::event_clicks(-1);
          return 1;
        }
        _setLastHandled(this);
        take_focus(); // force keyboard focus when we click in the window
        _curr.set(_ctx, Fl::event_x(), Fl::event_y());
        if(Fl::event_button() == 1 && !Fl::event_state(FL_SHIFT) &&
           !Fl::event_state(FL_ALT)) {
          if(!lassoMode && Fl::event_state(FL_CTRL)) {
            lassoMode = true;
            _lassoXY[0] = _curr.win[0];
            _lassoXY[1] = _curr.win[1];
          }
          else if(lassoMode) {
            lassoMode = false;
            if(selectionMode && CTX::instance()->mouseSelection) {
              // will try to select multiple entities
              _trySelection = 2;
              _trySelectionXYWH[0] = (int)(_click.win[0] + _curr.win[0]) / 2;
              _trySelectionXYWH[1] = (int)(_click.win[1] + _curr.win[1]) / 2;
              _trySelectionXYWH[2] = (int)fabs(_click.win[0] - _curr.win[0]);
              _trySelectionXYWH[3] = (int)fabs(_click.win[1] - _curr.win[1]);
            }
            else {
              if(CTX::instance()->camera) {
                Camera *cam = &(_ctx->camera);
                double dy = fabs(-_click.win[1] + _curr.win[1]);
                double dx = fabs(-_click.win[0] + _curr.win[0]);
                double factx = w() / fabs(dx);
                double facty = h() / fabs(dy);
                double fact = .8 * std::min(factx, facty);
                double x_med = (_click.win[0] + _curr.win[0]) / 2.;
                double y_med = (_click.win[1] + _curr.win[1]) / 2.;
                double theta_x = .96 * cam->radians * (w() / 2 - x_med) * 2. / h();
                double theta_y = .96 * cam->radians * (h() / 2 - y_med) * 2. / h();
                cam->moveRight(theta_x);
                cam->moveUp(theta_y);
                _ctx->camera.zoom(fact);
                _ctx->camera.update();
                redraw();
              }
              else {
                lassoZoom(_ctx, _click, _curr);
              }
            }
          }
          else if(CTX::instance()->mouseSelection) {
            // will try to select clicked entity
            _trySelection = 1;
            _trySelectionXYWH[0] = (int)_curr.win[0];
            _trySelectionXYWH[1] = (int)_curr.win[1];
            _trySelectionXYWH[2] = 5;
            _trySelectionXYWH[3] = 5;
          }
        }
        else if(Fl::event_button() == 2 ||
                (Fl::event_button() == 1 && Fl::event_state(FL_SHIFT))) {
          if(!lassoMode && Fl::event_state(FL_CTRL)) {
            // make zoom isotropic
            _ctx->s[1] = _ctx->s[0];
            _ctx->s[2] = _ctx->s[0];
            redraw();
          }
          else if(lassoMode) {
            lassoMode = false;
            if(selectionMode && CTX::instance()->mouseSelection) {
              // will try to unselect multiple entities
              _trySelection = -2;
              _trySelectionXYWH[0] = (int)(_click.win[0] + _curr.win[0]) / 2;
              _trySelectionXYWH[1] = (int)(_click.win[1] + _curr.win[1]) / 2;
              _trySelectionXYWH[2] = (int)fabs(_click.win[0] - _curr.win[0]);
              _trySelectionXYWH[3] = (int)fabs(_click.win[1] - _curr.win[1]);
            }
            else {
              lassoZoom(_ctx, _click, _curr);
            }
          }
          else if(CTX::instance()->mouseSelection) {
            // will try to unselect clicked entity
            _trySelection = -1;
            _trySelectionXYWH[0] = (int)_curr.win[0];
            _trySelectionXYWH[1] = (int)_curr.win[1];
            _trySelectionXYWH[2] = 5;
            _trySelectionXYWH[3] = 5;
          }
        }
        else {
          if(Fl::event_state(FL_CTRL) && !lassoMode) {
            if(CTX::instance()->useTrackball)
              _ctx->setQuaternion(0., 0., 0., 1.);
            else
              _ctx->r[0] = _ctx->r[1] = _ctx->r[2] = 0.;
            _ctx->t[0] = _ctx->t[1] = _ctx->t[2] = 0.;
            _ctx->s[0] = _ctx->s[1] = _ctx->s[2] = 1.;
            redraw();
          }
          else {
            lassoMode = false;
          }
        }
        _click.set(_ctx, Fl::event_x(), Fl::event_y());
        _prev.set(_ctx, Fl::event_x(), Fl::event_y());
        FlGui::instance()->manip->update();
        return 1;
    
      case FL_RELEASE:
        _curr.set(_ctx, Fl::event_x(), Fl::event_y());
        CTX::instance()->drawRotationCenter = 0;
        if(!lassoMode) {
          CTX::instance()->mesh.draw = 1;
          CTX::instance()->post.draw = 1;
          redraw();
        }
        _prev.set(_ctx, Fl::event_x(), Fl::event_y());
        return 1;
    
      case FL_MOUSEWHEEL: {
        _prev.set(_ctx, Fl::event_x(), Fl::event_y());
        double dy = Fl::event_dy();
        double fact =
          (5. * CTX::instance()->zoomFactor * fabs(dy) + h()) / (double)h();
        bool direction = (CTX::instance()->mouseInvertZoom) ? (dy <= 0) : (dy > 0);
        if(CTX::instance()->camera) {
          fact = (direction ? fact : 1. / fact);
          _ctx->camera.zoom(fact);
          _ctx->camera.update();
          redraw();
        }
        else {
          _ctx->s[0] *= (direction ? fact : 1. / fact);
          _ctx->s[1] = _ctx->s[0];
          _ctx->s[2] = _ctx->s[0];
          _prev.recenter(_ctx);
          redraw();
        }
      }
        FlGui::instance()->manip->update();
        return 1;
    
      case FL_DRAG:
        _curr.set(_ctx, Fl::event_x(), Fl::event_y());
        {
          double dx = _curr.win[0] - _prev.win[0];
          double dy = _curr.win[1] - _prev.win[1];
          if(lassoMode) {
            redraw();
          }
          else {
            if(Fl::event_state(FL_META)) {
              // will try to select or unselect entities on the fly
              _trySelection = Fl::event_state(FL_SHIFT) ? -1 : 1;
              _trySelectionXYWH[0] = (int)_curr.win[0];
              _trySelectionXYWH[1] = (int)_curr.win[1];
              _trySelectionXYWH[2] = 5;
              _trySelectionXYWH[3] = 5;
            }
            // (m1) and (!shift) and (!alt)  => rotation
            else if(Fl::event_button() == 1 && !Fl::event_state(FL_SHIFT) &&
                    !Fl::event_state(FL_ALT)) {
              if(CTX::instance()->useTrackball)
                _ctx->addQuaternion(
                  (2. * _prev.win[0] - w()) / w(), (h() - 2. * _prev.win[1]) / h(),
                  (2. * _curr.win[0] - w()) / w(), (h() - 2. * _curr.win[1]) / h());
              else {
                _ctx->r[1] +=
                  ((fabs(dx) > fabs(dy)) ? 180. * dx / (double)w() : 0.);
                _ctx->r[0] +=
                  ((fabs(dx) > fabs(dy)) ? 0. : 180. * dy / (double)h());
              }
            }
            // m2 or (m1 and shift) => zoom (only move in y is used)
            // but start point is the center of the homothety
            else if(Fl::event_button() == 2 ||
                    (Fl::event_button() == 1 && Fl::event_state(FL_SHIFT))) {
              // isotrop zoom in camera mode
              if(CTX::instance()->camera) {
                double dy = (int)_curr.win[1] - (int)_prev.win[1];
                double fact =
                  (CTX::instance()->zoomFactor * fabs(dy) + (double)h()) /
                  (double)h();
                fact = ((dy > 0) ? fact : 1. / fact);
                _ctx->camera.zoom(fact);
                _ctx->camera.update();
                redraw();
              }
              else {
                // move in y greater than move in x
                if(fabs(dy) > fabs(dx)) {
                  double fact =
                    (CTX::instance()->zoomFactor * fabs(dy) + h()) / (double)h();
                  _ctx->s[0] *= ((dy > 0) ? fact : 1. / fact);
                  _ctx->s[1] = _ctx->s[0];
                  _ctx->s[2] = _ctx->s[0];
                  _click.recenter(_ctx);
                }
                else if(!CTX::instance()->useTrackball)
                  _ctx->r[2] += -180. * dx / (double)w();
              }
            }
            // other case => translation
            else {
              if(CTX::instance()->camera) {
                Camera *cam = &(_ctx->camera);
                double theta_x = cam->radians *
                                 (-(double)_prev.win[0] + (double)_curr.win[0]) *
                                 2. / h();
                double theta_y = cam->radians *
                                 (-(double)_prev.win[1] + (double)_curr.win[1]) *
                                 2. / h();
                cam->moveRight(theta_x);
                cam->moveUp(theta_y);
              }
              else {
                _ctx->t[0] += (_curr.wnr[0] - _click.wnr[0]);
                _ctx->t[1] += (_curr.wnr[1] - _click.wnr[1]);
                _ctx->t[2] = 0.;
              }
            }
            CTX::instance()->drawRotationCenter = 1;
            if(CTX::instance()->fastRedraw) {
              CTX::instance()->mesh.draw = 0;
              CTX::instance()->post.draw = 0;
            }
            redraw();
          }
        }
        _prev.set(_ctx, Fl::event_x(), Fl::event_y());
        FlGui::instance()->manip->update();
        return 1;
    
      case FL_MOVE:
        _curr.set(_ctx, Fl::event_x(), Fl::event_y());
        if(lassoMode) {
          redraw();
        }
        else if(addPointMode && !Fl::event_state(FL_SHIFT)) {
          cursor(FL_CURSOR_CROSS, FL_BLACK, FL_WHITE);
          // find line in real space corresponding to current cursor position
          double p[3], d[3];
          _ctx->unproject(_curr.win[0], _curr.win[1], p, d);
          // find closest point to the center of gravity
          double r[3] = {CTX::instance()->cg[0] - p[0],
                         CTX::instance()->cg[1] - p[1],
                         CTX::instance()->cg[2] - p[2]},
                 t;
          t = prosca(r, d);
          for(int i = 0; i < 3; i++) {
            if(!FlGui::instance()->elementaryContext->frozenPointCoord(i)) {
              _point[i] = p[i] + t * d[i];
              if(CTX::instance()->geom.snap[i]) {
                double d = _point[i] / CTX::instance()->geom.snap[i];
                double f = floor(d);
                double c = ceil(d);
                double n = (d - f < c - d) ? f : c;
                _point[i] = n * CTX::instance()->geom.snap[i];
              }
            }
          }
          FlGui::instance()->elementaryContext->updatePoint(_point, addPointMode);
          redraw();
        }
        else { // hover mode
          if(_curr.win[0] != _prev.win[0] || _curr.win[1] != _prev.win[1]) {
            std::vector<GVertex *> vertices;
            std::vector<GEdge *> edges;
            std::vector<GFace *> faces;
            std::vector<GRegion *> regions;
            std::vector<MElement *> elements;
            std::vector<SPoint2> points;
            std::vector<PView *> views;
            bool res = _select(_selection, false, CTX::instance()->mouseHoverMeshes,
                               CTX::instance()->mouseHoverMeshes, (int)_curr.win[0],
                               (int)_curr.win[1], 5, 5, vertices, edges, faces,
                               regions, elements, points, views);
            if((_selection == ENT_ALL && res) ||
               (_selection == ENT_POINT && vertices.size()) ||
               (_selection == ENT_CURVE && edges.size()) ||
               (_selection == ENT_SURFACE && faces.size()) ||
               (_selection == ENT_VOLUME && regions.size()))
              cursor(FL_CURSOR_CROSS, FL_BLACK, FL_WHITE);
            else
              cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);
            std::string text, cmd;
            bool multiline = CTX::instance()->tooltips;
            if(vertices.size()) {
              text = vertices[0]->getInfoString(true, multiline);
              cmd = CTX::instance()->geom.doubleClickedPointCommand;
            }
            else if(edges.size()) {
              text = edges[0]->getInfoString(true, multiline);
              cmd = CTX::instance()->geom.doubleClickedCurveCommand;
            }
            else if(faces.size()) {
              text = faces[0]->getInfoString(true, multiline);
              cmd = CTX::instance()->geom.doubleClickedSurfaceCommand;
            }
            else if(regions.size()) {
              text = regions[0]->getInfoString(true, multiline);
              cmd = CTX::instance()->geom.doubleClickedVolumeCommand;
            }
            else if(elements.size()) {
              text = elements[0]->getInfoString(multiline);
            }
            else if(points.size()) {
              char tmp[256];
              sprintf(tmp, "Point (%g, %g)", points[0].x(), points[0].y());
              text = tmp;
              cmd = CTX::instance()->post.doubleClickedGraphPointCommand;
            }
            else if(views.size()) {
              char tmp[256];
              sprintf(tmp, "View[%d]", views[0]->getIndex());
              text = tmp;
              cmd = views[0]->getOptions()->doubleClickedCommand;
            }
            if(cmd.size()) {
              text += std::string(" - Double-click to execute\n\n");
              std::replace(cmd.begin(), cmd.end(), '\r', ' ');
              text += cmd;
            }
            if(CTX::instance()->tooltips)
              drawTooltip(text);
            else
              Msg::StatusBar(false, text.c_str());
          }
        }
        _prev.set(_ctx, Fl::event_x(), Fl::event_y());
        return 1;
    
      default: return Fl_Gl_Window::handle(event);
      }
    }
    
    int openglWindow::pixel_w()
    {
    #if(FL_MAJOR_VERSION == 1) && (FL_MINOR_VERSION == 3) && (FL_PATCH_VERSION >= 4)
      return Fl_Gl_Window::pixel_w();
    #elif(FL_MAJOR_VERSION == 1) && (FL_MINOR_VERSION >= 4)
      return Fl_Gl_Window::pixel_w();
    #else
      return w();
    #endif
    }
    
    int openglWindow::pixel_h()
    {
    #if(FL_MAJOR_VERSION == 1) && (FL_MINOR_VERSION == 3) && (FL_PATCH_VERSION >= 4)
      return Fl_Gl_Window::pixel_h();
    #elif(FL_MAJOR_VERSION == 1) && (FL_MINOR_VERSION >= 4)
      return Fl_Gl_Window::pixel_h();
    #else
      return h();
    #endif
    }
    
    bool openglWindow::_select(
      int type, bool multiple, bool mesh, bool post, int x, int y, int w, int h,
      std::vector<GVertex *> &vertices, std::vector<GEdge *> &edges,
      std::vector<GFace *> &faces, std::vector<GRegion *> &regions,
      std::vector<MElement *> &elements, std::vector<SPoint2> &points,
      std::vector<PView *> &views)
    {
      // same lock as in draw() to prevent firing up a GL_SELECT rendering pass
      // while a GL_RENDER pass is happening (due to the asynchronus nature of
      // Fl::check()s
      if(_lock) return false;
      _lock = true;
      make_current();
      bool ret = _ctx->select(type, multiple, mesh, post, x, y, w, h, vertices,
                              edges, faces, regions, elements, points, views);
      _lock = false;
      return ret;
    }
    
    char openglWindow::selectEntity(int type, std::vector<GVertex *> &vertices,
                                    std::vector<GEdge *> &edges,
                                    std::vector<GFace *> &faces,
                                    std::vector<GRegion *> &regions,
                                    std::vector<MElement *> &elements,
                                    std::vector<SPoint2> &points,
                                    std::vector<PView *> &views)
    {
      // force keyboard focus in GL window
      take_focus();
      _selection = type;
      _trySelection = 0;
      selectionMode = true;
      quitSelection = 0;
      changeSelection = 0;
      endSelection = 0;
      undoSelection = 0;
      invertSelection = 0;
    
      while(1) {
        vertices.clear();
        edges.clear();
        faces.clear();
        regions.clear();
        elements.clear();
        FlGui::instance()->wait();
        if(changeSelection) {
          Msg::Debug("Changing selection mode to %d", changeSelection);
          _selection = changeSelection;
          changeSelection = 0;
        }
        if(quitSelection) {
          _selection = ENT_NONE;
          selectionMode = false;
          lassoMode = false;
          addPointMode = 0;
          cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);
          return 'q';
        }
        if(endSelection) {
          _selection = ENT_NONE;
          endSelection = 0;
          return 'e';
        }
        if(undoSelection) {
          undoSelection = 0;
          return 'u';
        }
        if(invertSelection) {
          invertSelection = 0;
          return 'i';
        }
        if(_trySelection) {
          bool add = (_trySelection > 0) ? true : false;
          bool multi = (abs(_trySelection) > 1) ? true : false;
          _trySelection = 0;
          if(_selection == ENT_NONE) { // just report the mouse click
            selectionMode = false;
            return 'c';
          }
          else if(_select(_selection, multi, true, true, _trySelectionXYWH[0],
                          _trySelectionXYWH[1], _trySelectionXYWH[2],
                          _trySelectionXYWH[3], vertices, edges, faces, regions,
                          elements, points, views)) {
            _selection = ENT_NONE;
            selectionMode = false;
            if(add)
              return 'l';
            else
              return 'r';
          }
        }
      }
    }
    
    void openglWindow::drawTooltip(const std::string &text)
    {
      static char str[1024];
      strncpy(str, text.c_str(), sizeof(str));
      Fl_Tooltip::exit(0);
      bool enabled = Fl_Tooltip::enabled();
      if(!enabled) Fl_Tooltip::enable();
      double d1 = Fl_Tooltip::delay();
      double d2 = Fl_Tooltip::hoverdelay();
      Fl_Tooltip::delay(0);
      Fl_Tooltip::hoverdelay(0);
      Fl_Tooltip::enter_area(this, _curr.win[0], _curr.win[1], 100, 50, str);
      Fl_Tooltip::delay(d1);
      Fl_Tooltip::hoverdelay(d2);
      if(!enabled) Fl_Tooltip::disable();
    }
    
    void openglWindow::moveWithGamepad()
    {
      if(CTX::instance()->gamepad && CTX::instance()->gamepad->active && Nautilus) {
        if(!(_ctx->camera.on)) _ctx->camera.init();
        if(_drawn && (_lastHandled == this || _lastHandled == 0)) {
          Nautilus->move();
          this->flush();
        }
      }
    }