• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • tdeui
 

tdeui

  • tdeui
kpassivepopup.cpp
1 /*
2  * copyright : (C) 2001-2002 by Richard Moore
3  * copyright : (C) 2004-2005 by Sascha Cunz
4  * License : This file is released under the terms of the LGPL, version 2.
5  * email : rich@kde.org
6  * email : sascha.cunz@tiscali.de
7  */
8 
9 #include <tdeconfig.h>
10 
11 #include <tqapplication.h>
12 #include <tqlabel.h>
13 #include <tqlayout.h>
14 #include <tqtimer.h>
15 #include <tqvbox.h>
16 #include <tqpainter.h>
17 #include <tqtooltip.h>
18 #include <tqbitmap.h>
19 #include <tqpointarray.h>
20 
21 #include <kdebug.h>
22 #include <kdialog.h>
23 #include <kpixmap.h>
24 #include <kpixmapeffect.h>
25 #include <tdeglobalsettings.h>
26 
27 #include "config.h"
28 #ifdef Q_WS_X11
29 #include <netwm.h>
30 #endif
31 
32 #include "kpassivepopup.h"
33 #include "kpassivepopup.moc"
34 
35 class KPassivePopup::Private
36 {
37 public:
38  int popupStyle;
39  TQPointArray surround;
40  TQPoint anchor;
41  TQPoint fixedPosition;
42 };
43 
44 static const int DEFAULT_POPUP_TYPE = KPassivePopup::Boxed;
45 static const int DEFAULT_POPUP_TIME = 6*1000;
46 static const int POPUP_FLAGS = TQt::WStyle_Customize | TQt::WDestructiveClose | TQt::WX11BypassWM
47  | TQt::WStyle_StaysOnTop | TQt::WStyle_Tool | TQt::WStyle_NoBorder;
48 
49 KPassivePopup::KPassivePopup( TQWidget *parent, const char *name, WFlags f )
50  : TQFrame( 0, name, (WFlags)(f ? (int)f : POPUP_FLAGS) ),
51  window( parent ? parent->winId() : 0L ), msgView( 0 ), topLayout( 0 ),
52  hideDelay( DEFAULT_POPUP_TIME ), hideTimer( new TQTimer( this, "hide_timer" ) ),
53  m_autoDelete( false )
54 {
55  init( DEFAULT_POPUP_TYPE );
56 }
57 
58 KPassivePopup::KPassivePopup( WId win, const char *name, WFlags f )
59  : TQFrame( 0, name, (WFlags)(f ? (int)f : POPUP_FLAGS) ),
60  window( win ), msgView( 0 ), topLayout( 0 ),
61  hideDelay( DEFAULT_POPUP_TIME ), hideTimer( new TQTimer( this, "hide_timer" ) ),
62  m_autoDelete( false )
63 {
64  init( DEFAULT_POPUP_TYPE );
65 }
66 
67 KPassivePopup::KPassivePopup( int popupStyle, TQWidget *parent, const char *name, WFlags f )
68  : TQFrame( 0, name, (WFlags)(f ? (int)f : POPUP_FLAGS) ),
69  window( parent ? parent->winId() : 0L ), msgView( 0 ), topLayout( 0 ),
70  hideDelay( DEFAULT_POPUP_TIME ), hideTimer( new TQTimer( this, "hide_timer" ) ),
71  m_autoDelete( false )
72 {
73  init( popupStyle );
74 }
75 
76 KPassivePopup::KPassivePopup( int popupStyle, WId win, const char *name, WFlags f )
77  : TQFrame( 0, name, (WFlags)(f ? (int)f : POPUP_FLAGS) ),
78  window( win ), msgView( 0 ), topLayout( 0 ),
79  hideDelay( DEFAULT_POPUP_TIME ), hideTimer( new TQTimer( this, "hide_timer" ) ),
80  m_autoDelete( false )
81 {
82  init( popupStyle );
83 }
84 
85 void KPassivePopup::init( int popupStyle )
86 {
87  d = new Private;
88  d->popupStyle = popupStyle;
89  if( popupStyle == Boxed )
90  {
91  setFrameStyle( TQFrame::Box| TQFrame::Plain );
92  setLineWidth( 2 );
93  }
94  else if( popupStyle == Balloon )
95  {
96  setPalette(TQToolTip::palette());
97  setAutoMask(TRUE);
98  }
99  connect( hideTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( hide() ) );
100  connect( this, TQT_SIGNAL( clicked() ), TQT_SLOT( hide() ) );
101 }
102 
103 KPassivePopup::~KPassivePopup()
104 {
105  delete d;
106 }
107 
108 void KPassivePopup::setView( TQWidget *child )
109 {
110  delete msgView;
111  msgView = child;
112 
113  delete topLayout;
114  topLayout = new TQVBoxLayout( this, d->popupStyle == Balloon ? 22 : KDialog::marginHint(), KDialog::spacingHint() );
115  topLayout->addWidget( msgView );
116  topLayout->activate();
117 }
118 
119 void KPassivePopup::setView( const TQString &caption, const TQString &text,
120  const TQPixmap &icon )
121 {
122  // kdDebug() << "KPassivePopup::setView " << caption << ", " << text << endl;
123  setView( standardView( caption, text, icon, this ) );
124 }
125 
126 static void truncateStringToFit(TQString &string, TQFont font, int max_width) {
127  bool truncated = false;
128  TQFontMetrics fm(font);
129  while (fm.width(string) > max_width) {
130  string.truncate(string.length() - 1);
131  truncated = true;
132  }
133  if (truncated) {
134  string += " ...";
135  }
136 }
137 
138 TQVBox * KPassivePopup::standardView(const TQString& caption,
139  const TQString& text,
140  const TQPixmap& icon,
141  TQWidget *parent)
142 {
143  TQString sizedCaption = caption;
144  TQString sizedText = text;
145 
146 #ifdef Q_WS_X11
147  int max_width;
148 
149  NETRootInfo info( tqt_xdisplay(),
150  NET::NumberOfDesktops |
151  NET::CurrentDesktop |
152  NET::WorkArea,
153  -1, false );
154  info.activate();
155  NETRect workArea = info.workArea(info.currentDesktop());
156  max_width = workArea.size.width / 3;
157 #endif
158 
159  TQVBox *vb = new TQVBox( parent ? parent : this );
160  vb->setSpacing( KDialog::spacingHint() );
161 
162  TQHBox *hb=0;
163  if ( !icon.isNull() ) {
164  hb = new TQHBox( vb );
165  hb->setMargin( 0 );
166  hb->setSpacing( KDialog::spacingHint() );
167  ttlIcon = new TQLabel( hb, "title_icon" );
168  ttlIcon->setPixmap( icon );
169  ttlIcon->setAlignment( AlignLeft );
170  }
171 
172  if ( !sizedCaption.isEmpty() ) {
173  ttl = new TQLabel( sizedCaption, hb ? hb : vb, "title_label" );
174  TQFont fnt = ttl->font();
175 #ifdef Q_WS_X11
176  truncateStringToFit(sizedCaption, fnt, max_width);
177  ttl->setText(sizedCaption);
178 #endif
179  fnt.setBold( true );
180  ttl->setFont( fnt );
181  ttl->setAlignment( Qt::AlignHCenter );
182  if ( hb ) {
183  hb->setStretchFactor( ttl, 10 ); // enforce centering
184  }
185  }
186 
187  if ( !sizedText.isEmpty() ) {
188  msg = new TQLabel( sizedText, vb, "msg_label" );
189 #ifdef Q_WS_X11
190  TQStringList textLines = TQStringList::split("\n", sizedText, true);
191  for (TQStringList::Iterator it = textLines.begin(); it != textLines.end(); ++it) {
192  truncateStringToFit(*it, msg->font(), max_width);
193  }
194 
195  // Limit message to 5 lines of text
196  if (textLines.count() > 5) {
197  int count = 3;
198  TQStringList truncatedLines;
199  for (TQStringList::Iterator it = textLines.begin(); it != textLines.end(); ++it) {
200  truncatedLines.append(*it);
201  if (count > 5) {
202  truncatedLines.append("...");
203  break;
204  }
205  count++;
206  }
207  textLines = truncatedLines;
208  }
209  sizedText = textLines.join("\n");
210  msg->setText(sizedText);
211 #endif
212  msg->setAlignment( AlignLeft );
213  }
214 
215  return vb;
216 }
217 
218 void KPassivePopup::setView( const TQString &caption, const TQString &text )
219 {
220  setView( caption, text, TQPixmap() );
221 }
222 
223 void KPassivePopup::setTimeout( int delay )
224 {
225  hideDelay = delay;
226  if( hideTimer->isActive() )
227  {
228  if( delay ) {
229  hideTimer->changeInterval( delay );
230  } else {
231  hideTimer->stop();
232  }
233  }
234 }
235 
236 void KPassivePopup::setAutoDelete( bool autoDelete )
237 {
238  m_autoDelete = autoDelete;
239 }
240 
241 void KPassivePopup::mouseReleaseEvent( TQMouseEvent *e )
242 {
243  emit clicked();
244  emit clicked( e->pos() );
245 }
246 
247 //
248 // Main Implementation
249 //
250 
251 void KPassivePopup::show()
252 {
253  TQSize desiredSize = sizeHint();
254 
255  if (size() != desiredSize) {
256  resize(desiredSize);
257  }
258 
259  if (d->fixedPosition.isNull()) {
260  positionSelf();
261  }
262  else {
263  if( d->popupStyle == Balloon ) {
264  setAnchor(d->fixedPosition);
265  }
266  else {
267  move(d->fixedPosition);
268  }
269  }
270  TQFrame::show();
271 
272  int delay = hideDelay;
273  if ( delay < 0 ) {
274  delay = DEFAULT_POPUP_TIME;
275  }
276 
277  if ( delay > 0 ) {
278  hideTimer->start( delay );
279  }
280 }
281 
282 void KPassivePopup::show(const TQPoint &p)
283 {
284  d->fixedPosition = p;
285  show();
286 }
287 
288 void KPassivePopup::hideEvent( TQHideEvent * )
289 {
290  hideTimer->stop();
291  emit( hidden( this ) );
292  if ( m_autoDelete )
293  deleteLater();
294 }
295 
296 TQRect KPassivePopup::defaultArea() const
297 {
298 #ifdef Q_WS_X11
299  NETRootInfo info( tqt_xdisplay(),
300  NET::NumberOfDesktops |
301  NET::CurrentDesktop |
302  NET::WorkArea,
303  -1, false );
304  info.activate();
305  NETRect workArea = info.workArea( info.currentDesktop() );
306  TQRect r;
307  r.setRect( workArea.pos.x, workArea.pos.y, 0, 0 ); // top left
308 #else
309  // FIX IT
310  TQRect r;
311  r.setRect( 100, 100, 200, 200 ); // top left
312 #endif
313  return r;
314 }
315 
316 void KPassivePopup::positionSelf()
317 {
318  TQRect target;
319 
320 #ifdef Q_WS_X11
321  if ( !window ) {
322  target = defaultArea();
323  }
324 
325  else {
326  NETWinInfo ni( tqt_xdisplay(), window, tqt_xrootwin(),
327  NET::WMIconGeometry | NET::WMKDESystemTrayWinFor );
328 
329  // Figure out where to put the popup. Note that we must handle
330  // windows that skip the taskbar cleanly
331  if ( ni.kdeSystemTrayWinFor() ) {
332  NETRect frame, win;
333  ni.kdeGeometry( frame, win );
334  target.setRect( win.pos.x, win.pos.y,
335  win.size.width, win.size.height );
336  }
337  else if ( ni.state() & NET::SkipTaskbar ) {
338  target = defaultArea();
339  }
340  else {
341  NETRect r = ni.iconGeometry();
342  target.setRect( r.pos.x, r.pos.y, r.size.width, r.size.height );
343  if ( target.isNull() ) { // bogus value, use the exact position
344  NETRect dummy;
345  ni.kdeGeometry( dummy, r );
346  target.setRect( r.pos.x, r.pos.y,
347  r.size.width, r.size.height);
348  }
349  }
350  }
351 #else
352  target = defaultArea();
353 #endif
354  moveNear( target );
355 }
356 
357 void KPassivePopup::moveNear( TQRect target )
358 {
359  TQPoint pos = target.topLeft();
360  int x = pos.x();
361  int y = pos.y();
362  int w = width();
363  int h = height();
364 
365  TQRect r = TDEGlobalSettings::desktopGeometry(TQPoint(x+w/2,y+h/2));
366 
367  if( d->popupStyle == Balloon )
368  {
369  // find a point to anchor to
370  if( x + w > r.width() ){
371  x = x + target.width();
372  }
373 
374  if( y + h > r.height() ){
375  y = y + target.height();
376  }
377  } else
378  {
379  if ( x < r.center().x() )
380  x = x + target.width();
381  else
382  x = x - w;
383 
384  // It's apparently trying to go off screen, so display it ALL at the bottom.
385  if ( (y + h) > r.bottom() )
386  y = r.bottom() - h;
387 
388  if ( (x + w) > r.right() )
389  x = r.right() - w;
390  }
391  if ( y < r.top() )
392  y = r.top();
393 
394  if ( x < r.left() )
395  x = r.left();
396 
397  if( d->popupStyle == Balloon )
398  setAnchor( TQPoint( x, y ) );
399  else
400  move( x, y );
401 }
402 
403 void KPassivePopup::setAnchor(const TQPoint &anchor)
404 {
405  d->anchor = anchor;
406  updateMask();
407 }
408 
409 void KPassivePopup::paintEvent( TQPaintEvent* pe )
410 {
411  if( d->popupStyle == Balloon ) {
412  TQPainter p;
413  p.begin( this );
414  p.drawPolygon( d->surround );
415  }
416  else {
417  TQFrame::paintEvent( pe );
418  }
419 }
420 
421 void KPassivePopup::updateMask()
422 {
423  // get screen-geometry for screen our anchor is on
424  // (geometry can differ from screen to screen!
425  TQRect deskRect = TDEGlobalSettings::desktopGeometry(d->anchor);
426 
427  int xh = 70, xl = 40;
428  if( width() < 80 )
429  xh = xl = 40;
430  else if( width() < 110 )
431  xh = width() - 40;
432 
433  bool bottom = (d->anchor.y() + height()) > ((deskRect.y() + deskRect.height()-48));
434  bool right = (d->anchor.x() + width()) > ((deskRect.x() + deskRect.width()-48));
435 
436  TQPoint corners[4] = {
437  TQPoint( width() - 50, 10 ),
438  TQPoint( 10, 10 ),
439  TQPoint( 10, height() - 50 ),
440  TQPoint( width() - 50, height() - 50 )
441  };
442 
443  TQBitmap mask( width(), height(), true );
444  TQPainter p( &mask );
445  TQBrush brush( Qt::white, Qt::SolidPattern );
446  p.setBrush( brush );
447 
448  int i = 0, z = 0;
449  for (; i < 4; ++i) {
450  TQPointArray corner;
451  corner.makeArc(corners[i].x(), corners[i].y(), 40, 40, i * 16 * 90, 16 * 90);
452 
453  d->surround.resize( z + corner.count() );
454  for (unsigned int s = 0; s < corner.count() - 1; s++) {
455  d->surround.setPoint( z++, corner[s] );
456  }
457 
458  if (bottom && i == 2) {
459  if (right) {
460  d->surround.resize( z + 3 );
461  d->surround.setPoint( z++, TQPoint( width() - xh, height() - 11 ) );
462  d->surround.setPoint( z++, TQPoint( width() - 20, height() ) );
463  d->surround.setPoint( z++, TQPoint( width() - xl, height() - 11 ) );
464  } else {
465  d->surround.resize( z + 3 );
466  d->surround.setPoint( z++, TQPoint( xl, height() - 11 ) );
467  d->surround.setPoint( z++, TQPoint( 20, height() ) );
468  d->surround.setPoint( z++, TQPoint( xh, height() - 11 ) );
469  }
470  } else if (!bottom && i == 0) {
471  if (right) {
472  d->surround.resize( z + 3 );
473  d->surround.setPoint( z++, TQPoint( width() - xl, 10 ) );
474  d->surround.setPoint( z++, TQPoint( width() - 20, 0 ) );
475  d->surround.setPoint( z++, TQPoint( width() - xh, 10 ) );
476  } else {
477  d->surround.resize( z + 3 );
478  d->surround.setPoint( z++, TQPoint( xh, 10 ) );
479  d->surround.setPoint( z++, TQPoint( 20, 0 ) );
480  d->surround.setPoint( z++, TQPoint( xl, 10 ) );
481  }
482  }
483  }
484 
485  d->surround.resize( z + 1 );
486  d->surround.setPoint( z, d->surround[0] );
487  p.drawPolygon( d->surround );
488  setMask(mask);
489 
490  move( right ? d->anchor.x() - width() + 20 : ( d->anchor.x() < 11 ? 11 : d->anchor.x() - 20 ),
491  bottom ? d->anchor.y() - height() : ( d->anchor.y() < 11 ? 11 : d->anchor.y() ) );
492 
493  update();
494 }
495 
496 //
497 // Convenience Methods
498 //
499 
500 KPassivePopup *KPassivePopup::message( const TQString &caption, const TQString &text,
501  const TQPixmap &icon,
502  TQWidget *parent, const char *name, int timeout )
503 {
504  return message( DEFAULT_POPUP_TYPE, caption, text, icon, parent, name, timeout );
505 }
506 
507 KPassivePopup *KPassivePopup::message( const TQString &text, TQWidget *parent, const char *name )
508 {
509  return message( DEFAULT_POPUP_TYPE, TQString::null, text, TQPixmap(), parent, name );
510 }
511 
512 KPassivePopup *KPassivePopup::message( const TQString &caption, const TQString &text,
513  TQWidget *parent, const char *name )
514 {
515  return message( DEFAULT_POPUP_TYPE, caption, text, TQPixmap(), parent, name );
516 }
517 
518 KPassivePopup *KPassivePopup::message( const TQString &caption, const TQString &text,
519  const TQPixmap &icon, WId parent, const char *name, int timeout )
520 {
521  return message( DEFAULT_POPUP_TYPE, caption, text, icon, parent, name, timeout );
522 }
523 
524 KPassivePopup *KPassivePopup::message( int popupStyle, const TQString &caption, const TQString &text,
525  const TQPixmap &icon,
526  TQWidget *parent, const char *name, int timeout )
527 {
528  KPassivePopup *pop = new KPassivePopup( popupStyle, parent, name );
529  pop->setAutoDelete( true );
530  pop->setView( caption, text, icon );
531  pop->hideDelay = timeout;
532  pop->show();
533 
534  return pop;
535 }
536 
537 KPassivePopup *KPassivePopup::message( int popupStyle, const TQString &text, TQWidget *parent, const char *name )
538 {
539  return message( popupStyle, TQString::null, text, TQPixmap(), parent, name );
540 }
541 
542 KPassivePopup *KPassivePopup::message( int popupStyle, const TQString &caption, const TQString &text,
543  TQWidget *parent, const char *name )
544 {
545  return message( popupStyle, caption, text, TQPixmap(), parent, name );
546 }
547 
548 KPassivePopup *KPassivePopup::message( int popupStyle, const TQString &caption, const TQString &text,
549  const TQPixmap &icon, WId parent, const char *name, int timeout )
550 {
551  KPassivePopup *pop = new KPassivePopup( popupStyle, parent, name );
552  pop->setAutoDelete( true );
553  pop->setView( caption, text, icon );
554  pop->hideDelay = timeout;
555  pop->show();
556 
557  return pop;
558 }
KDialog::marginHint
static int marginHint()
Return the number of pixels you shall use between a dialog edge and the outermost widget(s) according...
Definition: kdialog.cpp:104
KPassivePopup::setAnchor
void setAnchor(const TQPoint &anchor)
Sets the anchor of this balloon.
Definition: kpassivepopup.cpp:403
KPassivePopup::clicked
void clicked()
Emitted when the popup is clicked.
KPassivePopup::defaultArea
TQRect defaultArea() const
If no relative window (eg taskbar button, system tray window) is available, use this rectangle (pass ...
Definition: kpassivepopup.cpp:296
KPassivePopup::setTimeout
void setTimeout(int delay)
Sets the delay for the popup is removed automatically.
Definition: kpassivepopup.cpp:223
KPassivePopup::moveNear
void moveNear(TQRect target)
Moves the popup to be adjacent to the icon of the specified rectangle.
Definition: kpassivepopup.cpp:357
NETPoint::y
int y
KPassivePopup::message
static KPassivePopup * message(const TQString &text, TQWidget *parent, const char *name=0)
Convenience method that displays popup with the specified message beside the icon of the specified wi...
Definition: kpassivepopup.cpp:507
KPassivePopup::Balloon
Information will appear in a comic-alike balloon.
Definition: kpassivepopup.h:80
KPassivePopup::autoDelete
bool autoDelete() const
Definition: kpassivepopup.h:171
KPassivePopup::hidden
void hidden(KPassivePopup *)
Emitted when the popup is hidden.
KPassivePopup::standardView
TQVBox * standardView(const TQString &caption, const TQString &text, const TQPixmap &icon, TQWidget *parent=0L)
Returns a widget that is used as standard view if one of the setView() methods taking the TQString ar...
Definition: kpassivepopup.cpp:138
KPassivePopup::mouseReleaseEvent
virtual void mouseReleaseEvent(TQMouseEvent *e)
Reimplemented to detect mouse clicks.
Definition: kpassivepopup.cpp:241
KPassivePopup::paintEvent
virtual void paintEvent(TQPaintEvent *pe)
Overwrite to paint the border when PopupStyle == Balloon.
Definition: kpassivepopup.cpp:409
KPassivePopup::hideEvent
virtual void hideEvent(TQHideEvent *)
Reimplemented to destroy the object when autoDelete() is enabled.
Definition: kpassivepopup.cpp:288
NETRect::size
NETSize size
NETPoint::x
int x
TDEGlobalSettings::desktopGeometry
static TQRect desktopGeometry(const TQPoint &point)
KDialog::spacingHint
static int spacingHint()
Return the number of pixels you shall use between widgets inside a dialog according to the KDE standa...
Definition: kdialog.cpp:110
NETRect::pos
NETPoint pos
KPassivePopup::timeout
int timeout() const
Returns the delay before the popup is removed automatically.
Definition: kpassivepopup.h:157
KPassivePopup::setAutoDelete
virtual void setAutoDelete(bool autoDelete)
Enables / disables auto-deletion of this widget when the timeout occurs.
Definition: kpassivepopup.cpp:236
KPassivePopup
A dialog-like popup that displays messages without interupting the user.
Definition: kpassivepopup.h:66
KPassivePopup::~KPassivePopup
virtual ~KPassivePopup()
Cleans up.
Definition: kpassivepopup.cpp:103
NETRect
KPassivePopup::setView
void setView(TQWidget *child)
Sets the main view to be the specified widget (which must be a child of the popup).
Definition: kpassivepopup.cpp:108
KPassivePopup::positionSelf
virtual void positionSelf()
This method positions the popup.
Definition: kpassivepopup.cpp:316
NETSize::width
int width
KPassivePopup::KPassivePopup
KPassivePopup(TQWidget *parent=0, const char *name=0, WFlags f=0)
Creates a popup for the specified widget.
Definition: kpassivepopup.cpp:49
NETSize::height
int height
KPassivePopup::Boxed
Information will appear in a framed box (default)
Definition: kpassivepopup.h:79
KPassivePopup::show
virtual void show()
Reimplemented to reposition the popup.
Definition: kpassivepopup.cpp:251
KPassivePopup::updateMask
void updateMask()
Updates the transparency mask.
Definition: kpassivepopup.cpp:421

tdeui

Skip menu "tdeui"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

tdeui

Skip menu "tdeui"
  • arts
  • dcop
  • dnssd
  • interfaces
  •   kspeech
  •     interface
  •     library
  •   tdetexteditor
  • kate
  • kded
  • kdoctools
  • kimgio
  • kjs
  • libtdemid
  • libtdescreensaver
  •     tdecore
  • tdeabc
  • tdecmshell
  • tdecore
  • tdefx
  • tdehtml
  • tdeinit
  • tdeio
  •   bookmarks
  •   httpfilter
  •   kpasswdserver
  •   kssl
  • tdeioslave
  •   http
  •   tdefile
  •   tdeio
  •   tdeioexec
  • tdemdi
  •   tdemdi
  • tdenewstuff
  • tdeparts
  • tdeprint
  • tderandr
  • tderesources
  • tdespell2
  • tdesu
  • tdeui
  • tdeunittest
  • tdeutils
  • tdewallet
Generated for tdeui by doxygen 1.8.8
This website is maintained by Timothy Pearson.