Module: qbutton.cpp

class QButton
.h

constructorQButton(QWindow *parent,QRect *ipos,cstring itext)

: QWindow(parent,ipos->x,ipos->y,ipos->wid,ipos->hgt)

destructor~QButton()
Alignvoid Align(int a)
BorderOffvoid BorderOff()
EnableShadowvoid EnableShadow()
DisableShadowvoid DisableShadow()
SetTextvoid SetText(cstring ntext)
SetTextColorvoid SetTextColor(QColor *col)
SetKeyPropagationvoid SetKeyPropagation(bool yn)
Paintvoid Paint(QRect *r)

Flat functions
 

glDrawBufferglDrawBuffer(GL_FRONT);

cv->SetColor(255,255,0);
cv->Rectfill(0,0,100,50);*/


class QButton
.h

EvButtonPressbool EvButtonPress(int button,int x,int y)
EvMotionNotifybool EvMotionNotify(int x,int y)

Mouse moves track changes in arm state
Always passes event on

EvButtonReleasebool EvButtonRelease(int button,int x,int y)
EvKeyPressbool EvKeyPress(int key,int x,int y)
EvEnterbool EvEnter()
EvExitbool EvExit()
ShortCutvoid ShortCut(int key,int mod)
GetShortCutint GetShortCut(int *modX)

Retrieve shortcut key (and modifier if ptr is not 0)

SetEventTypevoid SetEventType(int eType)

If eType==0, normal click (QEvent::CLICK) events are generated.
'eType' may be QEvent::KEYPRESS, in which case a key event
is generated with the associated key shortcut. In case there
IS NO shortcut key, a normal click event will be generated
nevertheless. This is done to transparently handle shortcut keys,
but not lose any events in case a button has no shortcut key
but still has event type QEvent::KEYPRESS.
'eType' must be a user event or QEvent::KEYPRESS or CLICK

SetBitMapvoid SetBitMap(QBitMap *bmda,QRect *r)

Define a bitmap for both disarmed/armed states



/*
 * QButton - push button (generated or imagery)
 * 07-04-97: Created!
 * 21-03-98: Using X GC to paint (faster than GL ctx switching)!
 * 21-12-99: QLib3; button lives in parent QXWindow somewhere.
 * 06-11-00: Shadow can now be turned on/off per button.
 * NOTES:
 * - Uses a magic code (event.p) to distinguish between button-generated
 * key events and other key events (REAL or otherwise generated). Perhaps
 * a more elegant mechanism may be devised to avoid infinitely looping
 * keypress-buttonpress-keypress loops.
 * BUGS:
 * - Mixing borderless buttons with bitmaps doesn't paint correctly
 *   (fix it when it happens)
 * (C) MarketGraph/RVG
 */

#include <qlib/button.h>
#include <qlib/canvas.h>
#include <qlib/event.h>
#include <qlib/app.h>
#include <qlib/keys.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <qlib/debug.h>
DEBUG_ENABLE

#define QBSW	4		// Shadow size

// Magic code to detect key events GENERATED by button clicks
#define MAGIC_BUTTON_KEY ((void*)6775382)

// USEX controls whether we use X GC or OpenGL to draw the button
// (OpenGL ctx switching is slow on O2)
//#define USEX

QButton::QButton(QWindow *parent,QRect *ipos,cstring itext)
  : QWindow(parent,ipos->x,ipos->y,ipos->wid,ipos->hgt)
{
  colShadow1=new QColor(40,40,40);
  colShadow2=new QColor(70,70,70);
  colText=new QColor(0,0,0);
  font=app->GetSystemFont();
  if(itext)
    text=qstrdup(itext);
  else text=0;

  state=DISARMED;
  align=CENTER;
  bflags=0;

  // No shortcut key
  scKey=0; scMod=0;
  eventType=QEVENT_CLICK;
  bmDisarmed=bmArmed=0;
  bmSrcDA=bmSrcA=0;

  // Default is to use a tabstop
  SetTabStop();

  // Take minimal amount of events
  // Include keystrokes because a shortcut key may be assigned to each button
  Catch(CF_BUTTONPRESS|CF_BUTTONRELEASE|CF_KEYPRESS);
  CompressExpose();

#ifdef USEX
  PrefDefaultVisual();
#endif
  Create();
#ifdef USEX
  cv->UseX();
#endif

}

QButton::~QButton()
{
  if(text)qfree(text);
  if(bmSrcDA)delete bmSrcDA;
}

/********
* LOOKS *
********/
void QButton::Align(int a)
{ align=a;
}
void QButton::BorderOff()
{
  bflags|=NOBORDER;
}
void QButton::EnableShadow()
{
  bflags&=~NO_SHADOW;
}
void QButton::DisableShadow()
{
  bflags|=NO_SHADOW;
}
void QButton::SetText(cstring ntext)
{ if(text)qfree(text);
  text=qstrdup(ntext);
  Invalidate();
}
void QButton::SetTextColor(QColor *col)
{ colText->SetRGBA(col->GetR(),col->GetG(),col->GetB(),col->GetA());
}
void QButton::SetKeyPropagation(bool yn)
{
  if(yn)bflags|=PROPAGATE_KEY;
  else  bflags&=~PROPAGATE_KEY;
}

void QButton::Paint(QRect *r)
{ QRect rr;
  int sw;		// Shadow width/height

  if(!IsVisible())return;
//qdbg("Qbutton:Paint (%s)\n",text);
/*cv->Select();
glDrawBuffer(GL_FRONT);
cv->SetColor(255,255,0);
cv->Rectfill(0,0,100,50);*/

  // Shadow size
  if(bflags&NO_SHADOW)
  {
    sw=0;
  } else
  {
    sw=4;
    Restore();
  }

  QRect pos;
  GetXPos(&pos);
  pos.x=pos.y=0;		// Local

  if(bflags&NOBORDER)
  { // Special version
#ifdef ND_MARBLE
    //cv->SetColor(128,128,128);
    cv->SetColor(PX_LTGRAY);
    cv->Rectfill(pos.x,pos.y,pos.wid,pos.hgt);
#endif
    sw=0;
    goto do_text;
  }

  //printf("QB: restored\n");
  if(state==ARMED)
  { // Move box
    if(!(bflags&NO_SHADOW))
    { pos.x+=1; pos.y+=1;
    }
  }

  //qdbg("cv=%p\n",cv);
  // Paint insides
  cv->Insides(pos.x+2,pos.y+2,
    pos.wid-2*2-sw,pos.hgt-2*2-sw);

  // Paint border
  //if(app->GetWindowManager()->GetFocus()==this)
    //cv->Inline(pos.x,pos.y,pos.wid-sw,pos.hgt-sw);
  //else
  if(state==ARMED)
    cv->Inline(pos.x,pos.y,pos.wid-sw,pos.hgt-sw);
  else
    cv->Outline(pos.x,pos.y,pos.wid-sw,pos.hgt-sw);

  // Focus
  if(IsFocus())
  {
    cv->StippleRect(pos.x+4,pos.y+4,pos.wid-sw-2*4,pos.hgt-sw-2*4);
  }

  if(!(bflags&NO_SHADOW))
  {
    // Shadow color 1 (darker)
#ifdef USEX
    cv->SetColor(QApp::PX_DKGRAY);
#else
    if(state==ARMED)cv->SetColor(colShadow2);
    else            cv->SetColor(colShadow1);
#endif
    rr.x=pos.x+pos.wid-sw;
    rr.y=pos.y+sw;
    rr.wid=2; rr.hgt=pos.hgt-sw;
    if(state==ARMED)rr.hgt-=2;
    cv->Rectfill(&rr);
    rr.x=pos.x+sw; rr.y=pos.y+pos.hgt-sw;
    rr.wid=pos.wid-sw; rr.hgt=2;
    if(state==ARMED)rr.wid-=2;
    cv->Rectfill(&rr);
  
    if(state==ARMED)goto skip_shadow2;
  
    // Light shadow
    cv->SetColor(colShadow2);
    rr.x=pos.x+pos.wid-2;
    rr.y=pos.y+sw;
    rr.wid=2; rr.hgt=pos.hgt-sw;
    cv->Rectfill(&rr);
    rr.x=pos.x+sw; rr.y=pos.y+pos.hgt-2;
    rr.wid=pos.wid-sw; rr.hgt=2;
    cv->Rectfill(&rr);
  
skip_shadow2:;
  }

 do_text:
  // Draw text if any
  //qdbg("  draw text\n");
  if(text!=0&&bmDisarmed==0)
  { int tx,ty,twid,thgt;
#ifdef USEX
    cv->SetColor(QApp::PX_BLACK);
#else
    if(bflags&NOBORDER)
    { if(state==ARMED)cv->SetColor(colText->GetR()^255,0,0);
      else cv->SetColor(colText);
    } else cv->SetColor(colText);
#endif
    if(text[0]=='$')
    { // Magic code?
      if(!strcmp(text,QB_MAGIC_PLAY))
      { 
        twid=pos.wid*2/4; thgt=pos.hgt*2/4;	// Proportional
        tx=pos.x+(pos.wid-twid-sw)/2;
        ty=pos.y+(pos.hgt-thgt-sw)/2;
        cv->Triangle(tx,ty,tx+twid,ty+thgt/2,tx,ty+thgt);
        goto skip_text;
      } else if(!strcmp(text,QB_MAGIC_REVERSE))
      {
        twid=pos.wid*2/4; thgt=pos.hgt*2/4;	// Proportional
        tx=pos.x+(pos.wid-twid-sw)/2;
        ty=pos.y+(pos.hgt-thgt-sw)/2;
        cv->Triangle(tx+twid,ty,tx,ty+thgt/2,tx+twid,ty+thgt);
        goto skip_text;
      } else if(!strcmp(text,QB_MAGIC_REWIND))
      { 
        twid=pos.wid*2/4; thgt=pos.hgt*2/4;	// Proportional
        tx=pos.x+(pos.wid-twid-sw)/2;
        ty=pos.y+(pos.hgt-thgt-sw)/2;
        cv->Triangle(tx+twid/2,ty,tx+twid/2,ty+thgt,tx,ty+thgt/2);
        cv->Triangle(tx+twid,ty,tx+twid,ty+thgt,tx+twid/2,ty+thgt/2);
        goto skip_text;
      } else if(!strcmp(text,QB_MAGIC_FORWARD))
      { 
        twid=pos.wid*2/4; thgt=pos.hgt*2/4;	// Proportional
        tx=pos.x+(pos.wid-twid-sw)/2;
        ty=pos.y+(pos.hgt-thgt-sw)/2;
        cv->Triangle(tx,ty,tx+twid/2,ty+thgt/2,tx,ty+thgt);
        cv->Triangle(tx+twid/2,ty,tx+twid,ty+thgt/2,tx+twid/2,ty+thgt);
        goto skip_text;
      // Prop gadgets
      } else if(!strcmp(text,"$LEFT"))
      {
        twid=pos.wid*2/4; thgt=pos.hgt*2/4;	// Proportional
        tx=pos.x+(pos.wid-twid-sw)/2;
        ty=pos.y+(pos.hgt-thgt-sw)/2;
        cv->Triangle(tx+twid,ty,tx,ty+thgt/2,tx+twid,ty+thgt);
        goto skip_text;
      } else if(!strcmp(text,"$RIGHT"))
      {
        twid=pos.wid*2/4; thgt=pos.hgt*2/4;	// Proportional
        tx=pos.x+(pos.wid-twid-sw)/2;
        ty=pos.y+(pos.hgt-thgt-sw)/2;
        cv->Triangle(tx,ty,tx+twid-1,ty+thgt/2,tx,ty+thgt-1);
        goto skip_text;
      } else if(!strcmp(text,"$UP"))
      {
        twid=(pos.wid+1)*2/4; thgt=pos.hgt*1/4;	
        tx=pos.x+(pos.wid-twid-sw)/2;
        ty=pos.y+(pos.hgt-thgt-sw)/2;
//qdbg("QButton: tsize=%dx%d\n",twid,thgt);
        tx++; //twid--;
        cv->Triangle(tx,ty+thgt,tx+twid-1,ty+thgt,tx+twid/2-1,ty);
        //cv->Triangle(tx+twid/2,ty,tx,ty+thgt,tx+twid,ty+thgt);
        goto skip_text;
      } else if(!strcmp(text,"$DOWN"))
      {
        twid=(pos.wid+1)*2/4; thgt=pos.hgt*1/4;	
        tx=pos.x+(pos.wid-twid-sw)/2;
        ty=pos.y+(pos.hgt-thgt-sw)/2;
        cv->Triangle(tx,ty,tx+twid,ty,tx+twid/2,ty+thgt);
        goto skip_text;
      }
    }
//qdbg("  Setfont\n");
    if(font)
    { int a;
      cv->SetFont(font);
      // Center text in button
      thgt=font->GetHeight();
      twid=font->GetWidth(text);
      if(align==CENTER)
      { tx=(pos.wid-sw-twid)/2+pos.x;
        a=QCanvas::ALIGN_CENTERH|QCanvas::ALIGN_CENTERV;
      } else if(align==LEFT)
      { tx=pos.x+4;
        a=QCanvas::ALIGN_CENTERV;
      } else if(align==RIGHT)
      { tx=pos.x+pos.wid-4-twid-sw;
        a=QCanvas::ALIGN_CENTERV;
      }
      ty=(pos.hgt-thgt)/2+pos.y /*+font->GetAscent()*/ ;
      /*printf("twid=%d, hgt=%d, x=%d, y=%d (pos=%d,%d %dx%d)\n",
        twid,thgt,tx,ty,pos.x,pos.y,pos.wid,pos.hgt);*/
#ifdef OLD
      cv->Text(text,tx,ty);
#else
      // New version; may have multiple lines of text
      rr.x=pos.x; rr.y=pos.y;
      rr.wid=pos.wid-sw; rr.hgt=pos.hgt-sw;
      // Offset text if pressed in
      if(state==ARMED)
      { rr.x+=1; rr.y+=1;
      }
      //if(!a)r.y=ty;		// No centering? Center Y at least
      cv->TextML(text,&rr,a);
      //cv->Text(text,r.x,r.y);
#endif
    }
   skip_text:;
  }
  if(bmDisarmed)
  { cv->Blend(TRUE);
    cv->Blit(bmDisarmed,pos.x+2,pos.y+2,bmSrcDA->wid,bmSrcDA->hgt,
      bmSrcDA->x,bmSrcDA->y);
  }

  if(bflags&NOBORDER)
    return;
  if(state==ARMED)
  { // Move box back
    pos.x-=2; pos.y-=2;
  }

//qdbg("  paint RET\n");
}

/*********
* EVENTS *
*********/
bool QButton::EvButtonPress(int button,int x,int y)
{ 
  if(button!=1)return FALSE;

  // Take the focus
  if(!QWM->SetKeyboardFocus(this))return TRUE;

//qdbg("QButton::EvButtonPress\n");
  state=ARMED;
  Paint();
  //Focus(TRUE);
  // Grab pointer to get release
  app->GetWindowManager()->BeginMouseCapture(this);
  return TRUE;
}

bool QButton::EvMotionNotify(int x,int y)
// Mouse moves track changes in arm state
// Always passes event on
{
  QRect pos;

  if(state==DISARMED)return FALSE;
  // Armed or tracking
  GetPos(&pos); pos.x=pos.y=0;
//qdbg("QButton motion %d,%d; pos=%d,%d %dx%d\n",x,y,pos.x,pos.y,pos.wid,pos.hgt);
  if(pos.Contains(x,y))
  { if(state==ARMED)return FALSE;
    // Return to button
    state=ARMED;
    Paint();
  } else
  { if(state==TRACKING)return FALSE;
    // Moved out of button box
    state=TRACKING;
    Paint();
  }
  return FALSE;
}

bool QButton::EvButtonRelease(int button,int x,int y)
{ QEvent e;
  if(button!=1)return FALSE;

//qdbg("QButton::EvButtonRelease, this=%p, (%d,%d)\n",this,x,y);
  if(state==DISARMED)return FALSE;
  state=DISARMED;
  Paint();
  app->GetWindowManager()->EndMouseCapture();
  //Focus(FALSE);
  QRect pos;
  GetPos(&pos); pos.x=pos.y=0;
//qdbg("  pos=%d,%d %dx%d\n",pos.x,pos.y,pos.wid,pos.hgt);
  if(pos.Contains(x,y))
  { // Generate click event
    e.type=eventType;
    e.win=this;
    if(e.type==QEvent::KEYPRESS)
    { // Special case; generate key event
      if(scKey==0)
      { // No shortcut key; generate normal click event
        e.type=QEvent::CLICK;
      } else
      { // Generate shortcut key as an event
        e.n=scKey;
        e.xRoot=e.yRoot=0;
        // Hack to note this event being generated by us
        // Otherwise we would loop, thinking the button was pressed again
        e.p=MAGIC_BUTTON_KEY;
      }
    }
    PushEvent(&e);
  }
  return TRUE;
}

bool QButton::EvKeyPress(int key,int x,int y)
{
  QRect pos;

  // Was this event created by a button click? If so, don't do anything
  // or we would loop infinitely
  QEvent *e=app->GetCurrentEvent();
  if(e->p==MAGIC_BUTTON_KEY)
    return FALSE;

  if(IsFocus())
  { // When focused, special keys apply
#ifdef ND_SPACE_FOR_QUIZES_PLEASE
    if(key==QK_SPACE)
      goto pressed;
#endif
  }

  GetPos(&pos); pos.x=pos.y=0;
  //printf("QButton keypress $%x @%d,%d\n",key,x,y);
  if(scKey==key)
  { // Simulate button press
   pressed:
    EvButtonPress(1,x,y);
    QNap(CLK_TCK/50);			// Make sure it shows
    EvButtonRelease(1,pos.x,pos.y);	// Make sure it hits
    if(bflags&PROPAGATE_KEY)
    { // Pass on key to other event handlers
      return FALSE;
    } // else notice that the event was handled
    return TRUE;		// Eat the event
  }
  return QWindow::EvKeyPress(key,x,y);
}

bool QButton::EvEnter()
{ //Paint();
  return TRUE;
}
bool QButton::EvExit()
{ //Paint();
  return TRUE;
}

// Behavior
void QButton::ShortCut(int key,int mod)
{
  //printf("shortcut %x %x\n",key,mod);
  scKey=key;
  scMod=mod;
}
int QButton::GetShortCut(int *modX)
// Retrieve shortcut key (and modifier if ptr is not 0)
{
  if(modX)*modX=scMod;
  return scKey;
}

void QButton::SetEventType(int eType)
// If eType==0, normal click (QEvent::CLICK) events are generated.
// 'eType' may be QEvent::KEYPRESS, in which case a key event
// is generated with the associated key shortcut. In case there
// IS NO shortcut key, a normal click event will be generated
// nevertheless. This is done to transparently handle shortcut keys,
// but not lose any events in case a button has no shortcut key
// but still has event type QEvent::KEYPRESS.
// 'eType' must be a user event or QEvent::KEYPRESS or CLICK
{
  if(eType==0)
  { eventType=QEvent::CLICK;
  } else
  { QASSERT_V(eType==QEvent::KEYPRESS||eType>=QEVENT_USER);	// but ev type
    eventType=eType;
  }
}

//
// Bitmap support
//
void QButton::SetBitMap(QBitMap *bmda,QRect *r)
// Define a bitmap for both disarmed/armed states
{
  bmDisarmed=bmda;
  if(bmSrcDA)delete bmSrcDA;
  bmSrcDA=new QRect(r->x,r->y,r->wid,r->hgt);
  Size(r->wid+QBSW+4,r->hgt+QBSW+4);
}