class QButton | .h |
constructor | QButton(QWindow *parent,QRect *ipos,cstring itext) : QWindow(parent,ipos->x,ipos->y,ipos->wid,ipos->hgt) |
destructor | ~QButton() |
Align | void Align(int a) |
BorderOff | void BorderOff() |
EnableShadow | void EnableShadow() |
DisableShadow | void DisableShadow() |
SetText | void SetText(cstring ntext) |
SetTextColor | void SetTextColor(QColor *col) |
SetKeyPropagation | void SetKeyPropagation(bool yn) |
Paint | void Paint(QRect *r) |
Flat functions |
glDrawBuffer | glDrawBuffer(GL_FRONT); cv->SetColor(255,255,0); cv->Rectfill(0,0,100,50);*/ |
class QButton | .h |
EvButtonPress | bool EvButtonPress(int button,int x,int y) |
EvMotionNotify | bool EvMotionNotify(int x,int y) Mouse moves track changes in arm state Always passes event on |
EvButtonRelease | bool EvButtonRelease(int button,int x,int y) |
EvKeyPress | bool EvKeyPress(int key,int x,int y) |
EvEnter | bool EvEnter() |
EvExit | bool EvExit() |
ShortCut | void ShortCut(int key,int mod) |
GetShortCut | int GetShortCut(int *modX) Retrieve shortcut key (and modifier if ptr is not 0) |
SetEventType | void 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 |
SetBitMap | void 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);
}