Logo Search packages:      
Sourcecode: unixcw version File versions  Download package

XcwcpApplication.cpp

/*
 * UnixCW - Unix CW (Morse code) training program
 * Copyright (C) 2001, 2002  Simon Baldwin (simonb@caldera.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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *
 * XcwcpApplication.cpp - Qt-based CW tutor program.
 *
 */

/* Include files. */
#include "XcwcpApplication.h"
#include "XcwcpMultiLineEdit.h"

#include <qiconset.h>
#include <qtoolbar.h>
#include <qtoolbutton.h>
#include <qcombobox.h>
#include <qspinbox.h>
#include <qlabel.h>
#include <qpopupmenu.h>
#include <qcheckbox.h>
#include <qmenubar.h>
#include <qkeycode.h>
#include <qstatusbar.h>
#include <qmessagebox.h>
#include <qapplication.h>
#include <qaccel.h>
#include <qtimer.h>
#include <qwhatsthis.h>

/* Libc include files. */
#include <cstdlib>
#include <ctime>
#include <cstdio>
#include <cctype>
#include <cerrno>

/* Xpm bitmap file includes. */
#include "start.xpm"
#include "stop.xpm"

/* Bulk practice string data, and CW library inclusions. */
#include "XcwcpWords.h"
#include "cwlib.h"

/* Strings displayed in 'about' dialog. */
const char  *about_caption    = "Xcwcp version 2.2";
const char  *about_text = \
"Xcwcp version 2.2  Copyright (C) 2002  Simon Baldwin\n\n"
"This program comes with ABSOLUTELY NO WARRANTY; for details\n"
"please see the file 'COPYING' supplied with the source code.\n"
"This is free software, and you are welcome to redistribute it\n"
"under certain conditions; again, see 'COPYING' for details.\n"   
"This program is released under the GNU General Public License.";

/* Strings for whats-this dialogs. */
const char  *startstop_whatsthis    = \
"When this button shows <img source=\"start\">, click it to begin "
"sending or receiving.  Only one window may send at a time.<br><br>"
"When the button shows <img source=\"stop\">, click it to finish "
"sending or receiving.\n\n";
const char  *mode_whatsthis = \
"This allows you to change what Xcwcp does.  Most of the available "
"selections generate random CW characters of one form or another.<br><br>"
"The exceptions are Send Keyboard CW, which sends the characters "
"that you type a the keyboard, and Receive Keyed CW, which will "
"decode CW that you key in using the mouse or keyboard.<br><br>"
"To key CW into Xcwcp for receive mode, use either the mouse or the "
"keyboard.  On the mouse, the left and right buttons form an Iambic "
"keyer, and the middle mouse button works as a straight key.<br><br>"
"On the keyboard, use the Left and Right cursor keys, or the Control "
"and Shift keys, for Iambic keyer control, and the Up or Down cursor "
"keys, or the Space, Enter, or Return keys, as a straight key.";
const char  *speed_whatsthis = \
"This controls the CW sending speed.  If you deselect adaptive "
"receive speed, it also controls the CW receiving speed.";
const char  *frequency_whatsthis = \
"This sets the frequency of the CW tone on the system sound card "
"or console.<br><br>"
"It affects both sent CW and receive sidetone.";
const char  *volume_whatsthis = \
"This sets the volume of the CW tone on the system sound card.  "
"It is not possible to control console sound volume, so in this "
"case, all values other than zero produce tones.<br><br>"
"The volume control affects both sent CW and receive sidetone.";
const char  *gap_whatsthis = \
"This sets the \"Farnsworth\" gap used in sending CW.  This gap is an "
"extra number of dit-length silences between CW characters.";
const char  *display_whatsthis = \
"This is the main display for Xcwcp.  The random CW characters that "
"Xcwcp generates, any keyboard input you type, and the CW that you "
"key into Xcwcp all appear here.<br><br>"
"You can clear the display contents from the File menu.<br><br>"
"The status bar shows the current character being sent, any character "
"received, and other general error and Xcwcp status information.";

/*
 * Import the bulk dictionary data into character arrays, so it can be
 * more easily manipulated and arranged.
 */
static const char *xcwcp_alphabetic[]     = { CWCP_ALPHABETIC };
static const char *xcwcp_numeric[]  = { CWCP_NUMERIC };
static const char *xcwcp_alphanumeric[]   = { CWCP_ALPHANUMERIC };
static const char *xcwcp_all_characters[] = { CWCP_ALL_CHARACTERS };
static const char *xcwcp_eish5[]          = { CWCP_EISH5 };
static const char *xcwcp_tmo0[]           = { CWCP_TMO0 };
static const char *xcwcp_auv4[]           = { CWCP_AUV4 };
static const char *xcwcp_ndb6[]           = { CWCP_NDB6 };
static const char *xcwcp_kxffrp[]         = { CWCP_KXffRP };
static const char *xcwcp_flyqc[]          = { CWCP_FLYQC };
static const char *xcwcp_wj1gz[]          = { CWCP_WJ1GZ };
static const char *xcwcp_x23789[]         = { CWCP_x23789 };
static const char *xcwcp_ffffff_1[] = { CWCP_ffffff_1 };
static const char *xcwcp_ffffff_2[] = { CWCP_ffffff_2 };
static const char *xcwcp_words[]          = { CWCP_3_LETTER_WORDS,
                                        CWCP_4_LETTER_WORDS,
                                        CWCP_5_LETTER_WORDS };
static const char *xcwcp_cw_words[] = { CWCP_CW_WORDS };
static const char *xcwcp_paris[]          = { CWCP_PARIS };

/*
 * Table of interface operating modes, their descriptions, related
 * dictionaries, and data on how to send for that mode.  The program is
 * always in one of these modes, indicated by current_mode, which is an
 * index into the modes[] array.
 */
typedef     enum {M_NULL,M_DICTIONARY,M_KEYBOARD,M_RECEIVE}
                        mode_type_t;      /* Enumerated mode type */
typedef     struct {
      const char        *description;     /* Text mode description */
      const mode_type_t type;       /* Mode type, dict or kb */
      const int         groupsize;  /* Size of groups if dict */
      const char        **wordlist; /* Word list if dictionary */
      const int         list_length;      /* Length of word list */
} smode_t;
#define     DICTIONARY(A)           &(A)[0], (sizeof (A) / sizeof ((A)[0]))
static const smode_t    modes[] = {
      { "Letter Groups",       M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_alphabetic) },
      { "Number Groups",       M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_numeric) },
      { "Alphanumeric Groups", M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_alphanumeric) },
      { "All Character Groups",M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_all_characters) },
      { "Short English Words", M_DICTIONARY,    1,
                              DICTIONARY (xcwcp_words) },
      { "CW Words",            M_DICTIONARY,    1,
                              DICTIONARY (xcwcp_cw_words) },
      { "PARIS Calibrate",     M_DICTIONARY,    1,
                              DICTIONARY (xcwcp_paris) },
      { "EISH5 Groups",  M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_eish5) },
      { "TMO0 Groups",   M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_tmo0) },
      { "AUV4 Groups",   M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_auv4) },
      { "NDB6 Groups",   M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_ndb6) },
      { "KX=-RP Groups",       M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_kxffrp) },
      { "FLYQC Groups",  M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_flyqc) },
      { "WJ1GZ Groups",  M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_wj1gz) },
      { "23789 Groups",  M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_x23789) },
      { ",?.;)/ Groups",       M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_ffffff_1) },
      { "\"'$(+:_ Groups",     M_DICTIONARY,    5,
                              DICTIONARY (xcwcp_ffffff_2) },
      { "Send Keyboard CW",    M_KEYBOARD,      0,    NULL, 0 },
      { "Receive Keyed CW",    M_RECEIVE, 0,    NULL, 0 },
      { NULL,                  M_NULL,    0,    NULL, 0 } };

/*
 * Table of keyboard translations to create a handful of prosigs from some
 * otherwise unused characters.
 */
typedef struct {
      const char        character;  /* Prosig character */
      const char        *prosig;    /* Prosig equivalent */
      const bool        combination;      /* True if characters sound as
                                       one single item */
} prosig_t;
static const prosig_t   prosigs[] = {
      { '%', "VA", true }, { '!', "BK", true }, { '@', "AR", true },
      { '^', "AA", true }, { '*', "AS", true }, { '&', "ES", false },
      { '#', "KN", true },
      { '\0', NULL, false } };

/*
 * We keep a pointer to the class currently actively using the CW library.
 * Since there is only one CW library, we need to make sure that only a single
 * Xcwcp instance is using it at any one time.  When NULL, no instance is
 * currently using the library.
 */
XcwcpApplication
      *XcwcpApplication::cwlib_user_object_reference  = NULL;


/*
 * XcwcpAppliction()
 *
 * Class constructor.  Creates the application main window an GUI frame,
 * and registers everything we need to register to get the application up
 * and running.
 */
XcwcpApplication::XcwcpApplication ()
      : QMainWindow (0, "Xcwcp", WDestructiveClose)
{
      /* Create a toolbar, and the start/stop button. */
      QToolBar *toolbar = new QToolBar (this, "Xcwcp Operations");
      toolbar->setLabel (tr ("Xcwcp Operations"));

      QPixmap start_pixmap = QPixmap (start_xpm);
      QPixmap stop_pixmap  = QPixmap (stop_xpm);
      QIconSet start_icon_set = QIconSet (start_pixmap);
      QIconSet stop_icon_set  = QIconSet (stop_pixmap);
      startstop_button = new QToolButton (start_icon_set, "Start/Stop",
                        QString::null, this, SLOT (startstop ()),
                        toolbar, "Start/Stop");
      startstop_button->setToggleButton (TRUE);

      /* Give the button two pixmaps, one for start, one for stop. */
      startstop_button->setOffIconSet (start_icon_set);
      startstop_button->setOnIconSet (stop_icon_set);

      /*
       * Add the mode selector combo box to the toolbar.  Add each mode
       * represented in the modes table to the combo box's contents.
       */
      toolbar->addSeparator ();
      mode_combo = new QComboBox (FALSE, toolbar, "Mode");
      for (const smode_t *modep = modes; modep->type != M_NULL; modep++)
            mode_combo->insertItem (modep->description);
      connect (mode_combo, SIGNAL (activated (int)),
                                    SLOT (mode_change ()));

      /*
       * Add the speed, frequency, volume, and gap spin boxes.  Connect
       * each to a value change function, so that we can immediately pass
       * on changes in these values to the CW library.
       */
      int   cwlib_min, cwlib_max;

      new QLabel ("Speed:", toolbar, "Speed Label" );
      toolbar->addSeparator ();
      cw_get_speed_limits (&cwlib_min, &cwlib_max);
      speed_spin = new QSpinBox (cwlib_min, cwlib_max, 1, toolbar, "Speed");
      speed_spin->setSuffix (" WPM");
      speed_spin->setValue (cw_get_send_speed ());
      connect (speed_spin, SIGNAL (valueChanged (int)),
                                    SLOT (speed_change ()));

      toolbar->addSeparator ();
      new QLabel ("Tone:", toolbar, "Frequency Label" );
      toolbar->addSeparator ();
      cw_get_frequency_limits (&cwlib_min, &cwlib_max);
      frequency_spin = new QSpinBox (cwlib_min, cwlib_max, 100, toolbar,
                                                "Frequency");
      frequency_spin->setSuffix (" Hz");
      frequency_spin->setValue (cw_get_frequency ());
      connect (frequency_spin, SIGNAL (valueChanged (int)),
                                    SLOT (frequency_change ()));

      toolbar->addSeparator ();
      new QLabel ("Volume:", toolbar, "Volume Label" );
      toolbar->addSeparator ();
      cw_get_volume_limits (&cwlib_min, &cwlib_max);
      volume_spin = new QSpinBox (cwlib_min, cwlib_max, 5, toolbar,
                                                "Volume");
      volume_spin->setSuffix (" %");
      volume_spin->setValue (cw_get_volume ());
      connect (volume_spin, SIGNAL (valueChanged (int)),
                                    SLOT (volume_change ()));

      toolbar->addSeparator ();
      new QLabel ("Gap:", toolbar, "Gap Label" );
      toolbar->addSeparator ();
      cw_get_gap_limits (&cwlib_min, &cwlib_max);
      gap_spin = new QSpinBox (cwlib_min, cwlib_max, 1, toolbar, "Gap");
      gap_spin->setSuffix (" dot(s)");
      gap_spin->setValue (cw_get_gap ());
      connect (gap_spin, SIGNAL (valueChanged (int)),
                                    SLOT (gap_change ()));
      toolbar->addSeparator ();

      /* Finally for the toolbar, add whatsthis. */
      QWhatsThis::whatsThisButton (toolbar);
      QMimeSourceFactory::defaultFactory ()->setPixmap
                                    ("start", start_pixmap);
      QMimeSourceFactory::defaultFactory ()->setPixmap
                                    ("stop", stop_pixmap);
      QWhatsThis::add (startstop_button, startstop_whatsthis);
      QWhatsThis::add (mode_combo, mode_whatsthis);
      QWhatsThis::add (speed_spin, speed_whatsthis);
      QWhatsThis::add (frequency_spin, frequency_whatsthis);
      QWhatsThis::add (volume_spin, volume_whatsthis);
      QWhatsThis::add (gap_spin, gap_whatsthis);

      /* Create the file popup menu. */
      QPopupMenu *file = new QPopupMenu (this, "File");
      menuBar ()->insertItem ("&File", file);

      file->insertItem ("&New Window", this, SLOT (new_xcwcp ()), CTRL+Key_N);
      file->insertSeparator ();
      file->insertItem ("&Clear Display", this, SLOT (clear ()), CTRL+Key_C);
      file->insertItem ("&Synchronise Speed", this, SLOT (sync_speed ()),
                                                CTRL+Key_P);
      file->insertSeparator ();
      file->insertItem (start_pixmap,
                    "&Start", this, SLOT (start ()), CTRL+Key_S);
      file->insertItem (stop_pixmap,
                    "S&top", this, SLOT (stop ()), CTRL+Key_T);
      file->insertSeparator ();
      file->insertItem ("&Close", this, SLOT (close ()), CTRL+Key_W);
      file->insertItem ("&Quit", qApp, SLOT (closeAllWindows ()), CTRL+Key_Q);

      /* Create the options popup menu. */
      QPopupMenu *options = new QPopupMenu (this, "Options");
      menuBar ()->insertItem ("&Options", options);

      reverse_paddles = new QCheckBox ("Reverse Paddles", this,
                                    "Reverse Paddles");
      options->insertItem (reverse_paddles);
      curtis_mode_b = new QCheckBox ("Curtis Mode B Timing", this,
                                    "Curtis Mode B Timing");
      connect (curtis_mode_b, SIGNAL (toggled (bool)),
                              SLOT (curtis_mode_b_change ()));
      options->insertItem (curtis_mode_b);
      adaptive_receive = new QCheckBox ("Adaptive CW Receive Speed", this,
                                    "Adaptive CW Receive Speed");
      adaptive_receive->setChecked (TRUE);
      connect (adaptive_receive, SIGNAL (toggled (bool)),
                              SLOT (adaptive_receive_change ()));
      options->insertItem (adaptive_receive);

      /* Create the help popup menu. */
      QPopupMenu *help = new QPopupMenu (this, "Help");
      menuBar ()->insertSeparator ();
      menuBar ()->insertItem ("&Help", help);

      help->insertItem ("&About", this, SLOT (about ()), Key_F1);

      /* Add the CW display widget, and complete the GUI initializations. */
      display = new XcwcpMultiLineEdit (this, "Display");
      display->setFocus ();
      display->setReadOnly (TRUE);
      display->setWordWrap (XcwcpMultiLineEdit::WidgetWidth);
      display->setWrapPolicy (XcwcpMultiLineEdit::Anywhere);
      QWhatsThis::add (display, display_whatsthis);
      setCentralWidget (display);
      statusBar ()->message ("Ready");
      resize (720, 320);

      /* Initialize our circular buffer, and buffer state. */
      cq_head = cq_tail = 0;
      cq_dequeue_state = CQ_IDLE;

      /* We are not actively using the CW library when we start up. */
      cwlib_use_state = STATE_IDLE;

      /*
       * Register the class handler as the CW library queue low callback.
       * It's important here that we register the static handler, since
       * once we have been into and out of 'C', all concept of 'this' is
       * lost.  It's the job of the static handler to work out which
       * class instance is using the CW library, and call its own
       * cwlib_callback_event() method.
       */
      if (cw_tone_queue_low_callback (cwlib_callback_event_static, 0) != 0)
          {
            perror ("cw_tone_queue_low_callback");
            exit (1);
          }

      /* Do the same thing for the CW library keying callback.  */
      cw_keying_callback (cwlib_keying_event_static);

      /* Initialize the saved receive speed to the current speed. */
      saved_receive_speed = cw_get_receive_speed ();

      /*
       * Start a regular timer for receive polling, with a period of
       * a second.
       */
      poll_timer = new QTimer (this, "Timer");
      connect (poll_timer, SIGNAL (timeout ()), SLOT (timer_event ()));
      poll_timer->start (1000, FALSE);
    
      /* Seed the random number generator. */
      struct timeval    timeofday;
      if (gettimeofday (&timeofday, NULL) == -1)
          {
            perror ("gettimeofday");
            exit (1);
          }
      srand (timeofday.tv_usec);
}


/*
 * ~XcwcpApplication()
 *
 * Class destructor.  For now, this is an empty method.
 */
XcwcpApplication::~XcwcpApplication ()
{
}


/*
 * cwlib_callback_event_static()
 *
 * This is the class-level handler for the callback from the CW library
 * indicating that its tone queue is nearly empty.  This method uses the
 * cwlib_user_object_reference static variable to determine which class
 * instance 'owns' the CW library at the moment (if any), then calls that
 * instance's specific callback method.
 */
void
XcwcpApplication::cwlib_callback_event_static ()
{
      /* If there is a CW library user, pass on the call to the instance. */
      if (cwlib_user_object_reference != NULL)
            cwlib_user_object_reference->cwlib_callback_event ();
}


/*
 * cwlib_callback_event()
 *
 * Instance-level handler for the callback from the CW library indicating
 * that its tone queue is nearly empty.  This method is always called either
 * from the SIGALRM handler from with the CW library, or internally with
 * SIGALRM blocked.
 */
void
XcwcpApplication::cwlib_callback_event ()
{
      /* If we are supposed to be idle, ignore this call. */
      if (cwlib_use_state != STATE_ACTIVE)
            return;

      /*
       * What to do depends on the program mode.  We might place some
       * random characters into the buffer.  For all cases, we try to
       * dequeue a character from the buffer to the CW sender.
       */
      int mode = mode_combo->currentItem ();
      if (modes[mode].type == M_DICTIONARY)
          {
            /*
             * Handle dictionary modes.
             *
             * See if the character queue needs a top up.
             */
            if (cq_head == cq_tail)
                {
                  /* Queue a before-group space. */
                  queue_character (' ');

                  /* Grab and buffer groupsize random words. */
                  for (int index = 0; index < modes[mode].groupsize;
                                                index++)
                      {
                        int random = rand ()
                                    % (modes[mode].list_length);
                        const char *word
                                    = modes[mode].wordlist[random];

                        /* Queue each character in the word. */
                        for ( const char *sptr = word;
                                          *sptr != '\0'; sptr++)
                              queue_character (*sptr);
                      }
                }

            /* Now dequeue the next character into the CW sender. */
            dequeue_character ();
            return;
          }

      if (modes[mode].type == M_KEYBOARD)
          {
            /* 
             * Handle keyboard modes.
             *
             * If we are interested in keyboard data just dequeue
             * whatever is in the character queue.
             */
            dequeue_character ();
            return;
          }
}


/*
 * dequeue_character()
 *
 * Called when the CW send buffer is nearly empty.  This routine takes a
 * character, if one is ready, from the send queue and sends it.
 */
void
XcwcpApplication::dequeue_character ()
{
      /* If dequeue is idle, ignore the call. */
      if (cq_dequeue_state == CQ_IDLE)
            return;

      /* If the queue is empty, just move to idle state and return. */
      if (cq_tail == cq_head)
          {
            cq_dequeue_state = CQ_IDLE;
            return;
          }

      /* Dequeue a character, since we have one available to handle. */
      if (cq_tail + 1 < (int)(sizeof (cq_queue)))
            cq_tail++;
      else
            cq_tail = 0;
      unsigned char c = cq_queue[cq_tail];

      /* Try to send the character to the CW send buffer. */
      if (cw_send_character (c) != 0)
         {
            /* Not sendable as directly; how about a prosig? */
            const prosig_t    *prosigp;
            for (prosigp = prosigs; prosigp->prosig != NULL
                                    && prosigp->character != c; )
                  prosigp++;
            if (prosigp->prosig == NULL)
                {
                  perror ("cw_send_character");
                  exit (1);
                }

            /*
             * Found a match, so send it as either partial or
             * complete characters.
             */
            if (prosigp->combination)
                {
                  const char  *sptr;
                  for (sptr = prosigp->prosig;
                                    *(sptr + 1) != '\0'; sptr++)
                      {
                        if (cw_send_character_partial (*sptr) != 0)
                            {
                              perror ("cw_send_character_partial");
                              exit (1);
                            }
                      }
                  if (cw_send_character (*sptr) != 0)
                      {
                        perror ("cw_send_character");
                        exit (1);
                      }
                }
            else
                {
                  if (cw_send_string
                        ((unsigned char*)prosigp->prosig) != 0)
                      {
                        perror ("cw_send_string");
                        exit (1);
                      }
                }
         }

      /* Update the status bar with the character being sent. */
      char message[80];
      sprintf (message, "Sending '%c' at %d WPM", c, cw_get_send_speed ());
      statusBar ()->message (message);
}


/*
 * queue_character()
 *
 * Queues a character for sending by the CW sender when ready.
 */
void
XcwcpApplication::queue_character (unsigned char c)
{
      /* Reject any characters that we won't be able to send. */
      if (cw_check_character (c) != 0)
         {
            /* Not sendable as a direct character, how about a prosig? */
            const prosig_t    *prosigp;
            for (prosigp = prosigs; prosigp->prosig != NULL
                                    && prosigp->character != c; )
                  prosigp++;
            if (prosigp->prosig == NULL)
                  return;
         }

      /* Calculate the new character queue head index. */
      int   new_cq_head;
      if (cq_head + 1 < (int)(sizeof (cq_queue)))
            new_cq_head = cq_head + 1;
      else
            new_cq_head = 0;

      /*
       * If the new value has hit the current queue tail, the queue is
       * full.  In this case, just ignore the character.
       */
      if (new_cq_head == cq_tail)
            return;

      /* Add the character to the queue.  */
      cq_queue[new_cq_head] = c;

      /* Add the same character to the display. */
      display->insert (QString (QChar (c)));

      /*
       * Update the head index, and if the dequeue is currently idle, set
       * it going.
       */
      if (cq_dequeue_state == CQ_IDLE)
          {
            cq_head = new_cq_head;
            cq_dequeue_state = CQ_ACTIVE;
          }
      else
            cq_head = new_cq_head;
}


/*
 * unqueue_character()
 *
 * Removes the last character queued, if possible, from the CW sender queue.
 */
void
XcwcpApplication::unqueue_character ()
{
      /* If the queue is empty there is nothing we can do. */
      if (cq_tail == cq_head)
            return;

      /* Calculate the new character queue head index. */
      int   new_cq_head;
      if (cq_head - 1 >= 0)
            new_cq_head = cq_head - 1;
      else
            new_cq_head = (int)(sizeof (cq_queue));

      /* Erase the character from the display. */
      display->backspace ();

      /* Erase the character by rewinding the head index. */
      cq_head = new_cq_head;
}


/*
 * poll_receive()
 *
 * Poll the CW library receive buffer for a complete character, and handle
 * anything found in it.
 */
void
XcwcpApplication::poll_receive ()
{
      /*
       * See if there is a character ready in the receive buffer, and if
       * there is, retrieve it and display it.  Ignore any receive error
       * status.
       */
      unsigned char     c;                /* Received character. */
      int         end_of_word;            /* End of word flag. */
      if (cw_receive_character (NULL, &c, &end_of_word, NULL) == 0)
          {
            /* Add the character, and a space if this is a word end. */
            display->insert (QString (QChar (c)));
            if (end_of_word)
                  display->insert (QString (QChar (' ')));

            /* Clear the receive buffer; all done with this one. */
            cw_clear_receive_buffer ();

            /* Update the status bar. */
            char message[80];
            sprintf (message, "Received '%c' at %d WPM", c,
                                    cw_get_receive_speed ());
            statusBar ()->message (message);
          }
      else
          {
            if (errno != EAGAIN
                        && errno != ERANGE
                        && errno != ENOENT)
                {
                  perror ("cw_receive_character");
                  exit (1);
                }
            if (errno == ENOENT)
                {
                  /* The data received was not identifiable. */
                  display->insert (QString (QChar ('?')));

                  /* Again, clear the receive buffer. */
                  cw_clear_receive_buffer ();

                  /* Update the status bar. */
                  char message[80];
                  sprintf (message,
                        "Unknown character received at %d WPM",
                                    cw_get_receive_speed ());
                  statusBar ()->message (message);
                }
          }
}


/*
 * cwlib_keying_event_static()
 *
 * This is the class-level handler for the keying callback from the CW
 * library indicating that the keying state changed.  This method uses the
 * cwlib_user_object_reference static variable to determine which class
 * instance 'owns' the CW library at the moment (if any), then calls that
 * instance's specific callback method.
 */
void
XcwcpApplication::cwlib_keying_event_static (int key_state)
{
      /* If there is a CW library user, pass on the call to the instance. */
      if (cwlib_user_object_reference != NULL)
            cwlib_user_object_reference->cwlib_keying_event (key_state);
}


/*
 * cwlib_keying_event()
 *
 * Instance-level handler for the keying callback from the CW library
 * indicating that the keying state changed.  The method handles the receive
 * of keyed CW, ignoring calls on non-receive modes.
 */
void
XcwcpApplication::cwlib_keying_event (int key_state)
{
      /* If we are supposed to be idle, ignore this call. */
      if (cwlib_use_state != STATE_ACTIVE)
            return;

      /* Also, ignore the call if not in a receive mode. */
      int mode = mode_combo->currentItem ();
      if (modes[mode].type == M_RECEIVE)
          {
            /*
             * If this is a tone start, poll to see if we have a complete
             * character yet, then pass on the tone state to the CW
             * receive library.  If a tone end, just pass it on.
             *
             * On polling, make a changeInterval call.  This is not to
             * change the interval, but rather to rewind the current
             * interval so it restarts at 0 again for the timer.  This
             * way, we don't call poll_receive too soon, and thus miss
             * an end-of-word delay by being a little too quick.
             */
            if (key_state)
                {
                  poll_receive ();
                  poll_timer->changeInterval (1000);
                  if (cw_start_receive_tone (NULL) != 0)
                      {
                        perror ("cw_start_receive_tone");
                        exit (1);
                      }
                }
            else
                {
                  if (cw_end_receive_tone (NULL) != 0)
                      {
                        if (errno == ENOMEM)
                            {
                              statusBar ()->message
                                    ("Receive buffer overrun");
                              cw_clear_receive_buffer ();
                            }
                        else if (errno == ENOENT)
                            {
                              statusBar ()->message
                                    ("Badly formed CW element");
                              cw_clear_receive_buffer ();
                            }
                        else if (errno != EAGAIN)
                            {
                              perror ("cw_end_receive_tone");
                              exit (1);
                            }
                      }
                }
          }
}


/*
 * about()
 *
 * Pop up a brief dialog about the application.
 */
void
XcwcpApplication::about ()
{
      QMessageBox::about (this, about_caption, about_text);
}


/*
 * closeEvent()
 *
 * Event handler for window close.  Requests a confirmation if we happen to
 * be busy sending.
 */
void
XcwcpApplication::closeEvent (QCloseEvent* event)
{
      /* If this instance is busy, offer a warning. */
      if (cwlib_use_state == STATE_ACTIVE)
          {
            switch (QMessageBox::warning (this, "Xcwcp",
                        "Busy - are you sure?",
                        "&Exit", "&Cancel", 0, 0, 1))
                {
                  case 0:
                        break;
                  case 1:
                        event->ignore ();
                        return;
                }

            /* Stop any current sending. */
            stop ();
          }

      /* Indicate the close is to continue. */
      event->accept ();
}


/*
 * startstop()
 *
 * Call start or stop depending on the current toggle state of the toolbar
 * button that calls this slot.
 */
void
XcwcpApplication::startstop ()
{
      /* If the toolbar button is toggled, call start.  Otherwise, stop. */
      if (startstop_button->isOn ())
            start ();
      else
            stop ();
}


/*
 * start()
 *
 * Start sending or receiving CW.
 */
void
XcwcpApplication::start ()
{
      /* If our use state is already active, do nothing. */
      if (cwlib_use_state == STATE_ACTIVE)
            return;

      /* If the CW library is in use by another instance, reject the call. */
      while (cwlib_user_object_reference != NULL)
          {
            switch (QMessageBox::warning (this, "Xcwcp",
                        "Another Xcwcp window is busy.",
                        "&Retry", "&Quit", 0, 0, 1))
                {
                  case 0:
                        continue;
                  case 1:
                        startstop_button->setOn (FALSE);
                        return;
                }
          }

      /* Grab the CW library sender for ourselves. */
      cwlib_user_object_reference = this;

      /* Produce a short start warning warble. */
      cw_flush_tone_queue ();
      cw_queue_tone (20000,  500);
      cw_queue_tone (20000, 1000);
      cw_tone_queue_wait ();

      /* Note that we are now busy. */
      cwlib_use_state = STATE_ACTIVE;

      /*
       * Synchronize the CW sender to our values of speed/tone/gap, and
       * curtis mode B.  We need to do this here since updates to the GUI
       * widgets are ignored if we aren't in fact active; this permits
       * multiple instances of the class to interoperate with the CW
       * library.  Sort of.  We can do it by just calling the slots for
       * the GUI widgets directly.
       */
      speed_change ();
      frequency_change ();
      volume_change ();
      gap_change ();
      curtis_mode_b_change ();

      /*
       * Call the adaptive receive change callback to synchronize up
       * the CW library with this instance's idea of receive tracking
       * and speed.
       */
      adaptive_receive_change ();

      /* Clear the receive buffer of any dangling rubbish. */
      cw_clear_receive_buffer ();

      /*
       * Make a simple call to the CW library callback event handler to
       * kick off the send process in the case of dictionary modes.
       */
      int mode = mode_combo->currentItem ();
      if (modes[mode].type == M_DICTIONARY)
          {
            cw_block_callback (TRUE);
            cwlib_callback_event ();
            cw_block_callback (FALSE);
          }

      /* Update the GUI. */
      startstop_button->setOn (TRUE);
      statusBar ()->message ("");
}


/*
 * stop()
 *
 * Empty the buffer of characters awaiting send, and halt the process of
 * refilling the buffer.
 */
void
XcwcpApplication::stop ()
{
      /* If our use state is already idle, do nothing. */
      if (cwlib_use_state == STATE_IDLE)
            return;

      /* We are no longer busy. */
      cwlib_use_state = STATE_IDLE;

      /*
       * Remove everything in the outgoing character queue, and force the
       * dequeue to idle manually.  We can't just let the callbacks do it,
       * since we've set the program state to idle, meaning that the
       * callbacks are now ignored.
       */
      cq_tail = cq_head;
      while (cq_dequeue_state == CQ_ACTIVE)
            dequeue_character ();

      /* Save the receive speed, for restore on next start. */
      saved_receive_speed = cw_get_receive_speed ();

      /* Cancel all pending tone output, and then produce a little warble. */
      cw_flush_tone_queue ();
      cw_queue_tone (20000,  500);
      cw_queue_tone (20000, 1000);
      cw_queue_tone (20000,  500);
      cw_queue_tone (20000, 1000);
      cw_tone_queue_wait ();

      /* Done with the CW library sender for now. */
      cwlib_user_object_reference = NULL;

      /* Update the GUI. */
      startstop_button->setOn (FALSE);
      statusBar ()->message ("Ready");
}


/*
 * new_xcwcp()
 *
 * Creates a new instance of the Xcwcp application.
 */
void
XcwcpApplication::new_xcwcp ()
{
      XcwcpApplication *xcwcp = new XcwcpApplication ();
      xcwcp->setCaption ("Xcwcp");
      xcwcp->show ();
}


/*
 * clear()
 *
 * Clears the display window of this Xcwcp instance.
 */
void
XcwcpApplication::clear ()
{
      /* If active, protect the display against callback updates. */
      if (cwlib_use_state == STATE_ACTIVE)
          {
            cw_block_callback (TRUE);
            display->clear ();
            cw_block_callback (FALSE);
          }
      else
            display->clear ();
}


/*
 * sync_speed()
 *
 * Forces the tracked receive speed into synchronization with the speed
 * spin box.
 */
void
XcwcpApplication::sync_speed ()
{
      /* As usual, ignore the call if this instance is not active. */
      if (cwlib_use_state == STATE_ACTIVE)
          {
            /* Ignore if not adaptive receive mode. */
            if (adaptive_receive->isChecked ())
                {
                  /*
                   * Force the issue by unsetting adaptive receive,
                   * setting the receive speed, then resetting
                   * adaptive receive again.
                   */
                  cw_disable_adaptive_receive ();
                  if (cw_set_receive_speed (speed_spin->value ()) != 0)
                      {
                        perror ("cw_set_receive_speed");
                        exit (1);
                      }
                  cw_enable_adaptive_receive ();
                }
          }
}


/*
 * speed_change()
 * frequency_change()
 * volume_change()
 * gap_change()
 *
 * Handle changes in the spin boxes for these CW parameters.  The only action
 * necessary is to write the new values out to the CW library.  The one thing
 * we do do is to only change parameters when we are active (i.e. have
 * control of the CW library).
 */
void
XcwcpApplication::speed_change ()
{
      if (cwlib_use_state == STATE_ACTIVE)
          {
            if (cw_set_send_speed (speed_spin->value ()) != 0)
                {
                  perror ("cw_set_send_speed");
                  exit (1);
                }
            if (!cw_get_adaptive_receive_state ())
                {
                  if (cw_set_receive_speed (speed_spin->value ()) != 0)
                      {
                        perror ("cw_set_receive_speed");
                        exit (1);
                      }
                }
          }
}
void
XcwcpApplication::frequency_change ()
{
      if (cwlib_use_state == STATE_ACTIVE)
          {
            if (cw_set_frequency (frequency_spin->value ()) != 0)
                {
                  perror ("cw_set_frequency");
                  exit (1);
                }
          }
}
void
XcwcpApplication::volume_change ()
{
      if (cwlib_use_state == STATE_ACTIVE)
          {
            if (cw_set_volume (volume_spin->value ()) != 0)
                {
                  perror ("cw_set_volume");
                  exit (1);
                }
          }
}
void
XcwcpApplication::gap_change ()
{
      if (cwlib_use_state == STATE_ACTIVE)
          {
            if (cw_set_gap (gap_spin->value ()) != 0)
                {
                  perror ("cw_set_gap");
                  exit (1);
                }
          }
}


/*
 * mode_change()
 *
 * Handle a change of mode.  If we are active, empty the character and tone
 * queue, wait for any pending tone or keyer operations, then, if a dictionary
 * mode, nudge the dequeue process to get things rolling again.  For receive
 * mode, synchronize receive speed to the current set speed.
 */
void
XcwcpApplication::mode_change ()
{
      if (cwlib_use_state == STATE_ACTIVE)
          {
            /*
             * Set ourselves temporarily idle, to stop queue refills.
             * Empty the character and tone queues, then go active again.
             */
            cwlib_use_state = STATE_IDLE;
            cq_tail = cq_head;
            while (cq_dequeue_state == CQ_ACTIVE)
                  dequeue_character ();
            cw_flush_tone_queue ();
            cwlib_use_state = STATE_ACTIVE;

            /* Clear the receive buffer of any dangling rubbish. */
            cw_clear_receive_buffer ();

            /*
             * If a dictionary mode, make a call to the library callback
             * event handler to nudge the send process.  Keyboard mode
             * gets a nudge on first key typed, and receive mode does not
             * use the character queue at all.
             */
            int mode = mode_combo->currentItem ();
            if (modes[mode].type == M_DICTIONARY)
                {
                  cw_block_callback (TRUE);
                  cwlib_callback_event ();
                  cw_block_callback (FALSE);
                }
          }
}


/*
 * curtis_mode_b_change()
 *
 * Called whenever the user request a change of Curtis iambic mode.  The
 * method simply passes the Curtis mode on to the CW library if active,
 * and ignores the call if not.
 */
void
XcwcpApplication::curtis_mode_b_change ()
{
      if (cwlib_use_state == STATE_ACTIVE)
          {
            /* Report the new Curtis mode B state to the CW library. */
            if (curtis_mode_b->isChecked ())
                  cw_enable_iambic_curtis_mode_b ();
            else
                  cw_disable_iambic_curtis_mode_b ();
          }
}


/*
 * adaptive_receive_change()
 *
 * Called whenever the user request a change of adaptive receive status.
 * The method passes the new receive speed tracking mode on to the CW
 * library if active, and if fixed speed receive is set, also sets the
 * hard receive speed to equal the send speed, otherwise, it restores the
 * previous tracked receive speed.
 */
void
XcwcpApplication::adaptive_receive_change ()
{
      if (cwlib_use_state == STATE_ACTIVE)
          {
            /* See what direction the change is in. */
            if (adaptive_receive->isChecked ())
                {
                  /* 
                   * If going to adaptive receive, first set the speed
                   * to the saved receive speed, then turn on adaptive
                   * receiving.
                   */
                  cw_disable_adaptive_receive ();
                  if (cw_set_receive_speed (saved_receive_speed) != 0)
                      {
                        perror ("cw_set_receive_speed");
                        exit (1);
                      }
                  cw_enable_adaptive_receive ();
                }
            else
                {
                  /*
                   * If going to fixed receive, save the current adaptive
                   * receive speed so we can restore it later, then turn
                   * off adaptive receive, and set the speed to equal
                   * the send speed as shown on the speed spin box.
                   */
                  saved_receive_speed = cw_get_receive_speed ();
                  cw_disable_adaptive_receive ();
                  if (cw_set_receive_speed (speed_spin->value ()) != 0)
                      {
                        perror ("cw_set_receive_speed");
                        exit (1);
                      }
                }
          }
}


/*
 * timer_event()
 *
 * Handle a timer event from the QTimer we set up on initialization.  This
 * timer is used for regular polling for completed receive characters, in
 * the case where there's no user keying for a little while.  We're just
 * dead lucky that Qt does not use SIGALRM, otherwise this would conflict
 * with the CW library.
 */
void
XcwcpApplication::timer_event ()
{
      /* If we are supposed to be idle, ignore this call. */
      if (cwlib_use_state != STATE_ACTIVE)
            return;

      /* Get the current mode, and ignore for non-CW receive modes. */
      int mode = mode_combo->currentItem ();
      if (modes[mode].type == M_RECEIVE)
          {
            /* Poll for complete receive characters. */
            poll_receive ();
          }
}


/*
 * key_event()
 *
 * Handle a key press event from the display widget.
 */
void
XcwcpApplication::key_event (QKeyEvent *event)
{
      /* If we are supposed to be idle, ignore this call. */
      if (cwlib_use_state != STATE_ACTIVE)
            return;

      /*
       * Get the current mode, and handle accordingly for keyboard send
       * and CW receive modes.  Ignore for dictionary modes.
       */
      int mode = mode_combo->currentItem ();
      if (modes[mode].type == M_KEYBOARD)
          {
            /*
             * Handle keyboard modes.
             *
             * Ignore all key releases; note only presses.
             */
            if (event->type () == QEvent::KeyPress)
                {
                  /*
                   * If the key was backspace, remove the last queued
                   * character, or at least try, and we are done.
                   */
                  if (event->key () == Key_Backspace)
                      {
                        cw_block_callback (TRUE);
                        unqueue_character ();
                        cw_block_callback (FALSE);
                        return;
                      }

                  /*
                   * Extract the ASCII keycode from the key event, and
                   * queue the character for sending.  Convert to
                   * uppercase.  Unsendable characters are ignored by
                   * queue_character().  If the dequeue is idle when
                   * we get to here, give it a nudge to start it taking
                   * characters out of our queue.
                   */
                  cw_block_callback (TRUE);
                  if (cq_dequeue_state == CQ_IDLE)
                      {
                        queue_character (toupper (event->ascii ()));
                        cwlib_callback_event ();
                      }
                  else
                        queue_character (toupper (event->ascii ()));
                  cw_block_callback (FALSE);
                }
            return;
          }

      if (modes[mode].type == M_RECEIVE)
          {
            /*
             * Handle receive modes.
             *
             * If this is a key press that it not the first one of
             * an autorepeating key, ignore the event.  This prevents
             * autorepeat from getting in the way of identifying the
             * real keyboard events we are after.
             */
            if (event->isAutoRepeat ())
                  return;

            /*
             * If this is the Space, UpArrow, DownArrow, Enter, or
             * Return key, use as a straight key.  If one wears out,
             * there's always the other ones.
             */
            if (event->key () == Key_Space
                        || event->key () == Key_Up
                        || event->key () == Key_Down
                        || event->key () == Key_Enter
                        || event->key () == Key_Return)
                {
                  if (event->type () == QEvent::KeyPress)
                        cw_straightkey_event (TRUE);
                  else
                        if (event->type () == QEvent::KeyRelease)
                              cw_straightkey_event (FALSE);
                  return;
                }

            /*
             * If this is the Ctrl/LeftArrow key, use as one of the
             * paddles.  Which paddle depends on the reverse_paddles
             * state.
             */
            if (event->key () == Key_Control
                        || event->key () == Key_Left)
                {
                  if (event->type () == QEvent::KeyPress)
                      {
                        if (!reverse_paddles->isChecked ())
                              cw_keyer_dot_paddle_event (TRUE);
                        else
                              cw_keyer_dash_paddle_event (TRUE);
                      }
                  else if (event->type () == QEvent::KeyRelease)
                      {
                        if (!reverse_paddles->isChecked ())
                              cw_keyer_dot_paddle_event (FALSE);
                        else
                              cw_keyer_dash_paddle_event (FALSE);
                      }
                  return;
                }

            /*
             * If this is the Shift/RightArrow key, use as the other
             * one of the paddles.
             */
            if (event->key () == Key_Shift
                        || event->key () == Key_Right)
                {
                  if (event->type () == QEvent::KeyPress)
                      {
                        if (!reverse_paddles->isChecked ())
                              cw_keyer_dash_paddle_event (TRUE);
                        else
                              cw_keyer_dot_paddle_event (TRUE);
                      }
                  else if (event->type () == QEvent::KeyRelease)
                      {
                        if (!reverse_paddles->isChecked ())
                              cw_keyer_dash_paddle_event (FALSE);
                        else
                              cw_keyer_dot_paddle_event (FALSE);
                      }
                  return;
                }
          }
}


/*
 * mouse_event()
 *
 * Handle a mouse button press or release.  These keying commands are, for
 * ease of handling, converted into equivalent key press/release events.
 */
void
XcwcpApplication::mouse_event (QMouseEvent *event)
{
      /* If we are supposed to be idle, ignore this call. */
      if (cwlib_use_state != STATE_ACTIVE)
            return;

      /* Also, we are not at all interested in mouse movements. */
      if (event->type () == QEvent::MouseMove)
            return;

      /*
       * Get the current mode, and ignore for dictionary and keyboard
       * send modes; we're only interested in receive mode.
       */
      int mode = mode_combo->currentItem ();
      if (modes[mode].type == M_RECEIVE)
          {
            /* Use the middle mouse button as a straight key. */
            if (event->button () == MidButton)
                {
                  if (event->type () == QEvent::MouseButtonPress
                              || event->type ()
                                    == QEvent::MouseButtonDblClick)
                        cw_straightkey_event (TRUE);
                  else
                        if (event->type ()
                                    == QEvent::MouseButtonRelease)
                              cw_straightkey_event (FALSE);
                  return;
                }

            /* Use left and right mouse buttons as paddles. */
            if (event->button () == LeftButton)
                {
                  if (event->type () == QEvent::MouseButtonPress
                              || event->type ()
                                    == QEvent::MouseButtonDblClick)
                      {
                        if (!reverse_paddles->isChecked ())
                              cw_keyer_dot_paddle_event (TRUE);
                        else
                              cw_keyer_dash_paddle_event (TRUE);
                      }
                  else if (event->type () == QEvent::MouseButtonRelease)
                      {
                        if (!reverse_paddles->isChecked ())
                              cw_keyer_dot_paddle_event (FALSE);
                        else
                              cw_keyer_dash_paddle_event (FALSE);
                      }
                  return;
                }
            if (event->button () == RightButton)
                {
                  if (event->type () == QEvent::MouseButtonPress
                              || event->type ()
                                    == QEvent::MouseButtonDblClick)
                      {
                        if (!reverse_paddles->isChecked ())
                              cw_keyer_dash_paddle_event (TRUE);
                        else
                              cw_keyer_dot_paddle_event (TRUE);
                      }
                  else if (event->type () == QEvent::MouseButtonRelease)
                      {
                        if (!reverse_paddles->isChecked ())
                              cw_keyer_dash_paddle_event (FALSE);
                        else
                              cw_keyer_dot_paddle_event (FALSE);
                      }
                  return;
                }
          }
}

Generated by  Doxygen 1.6.0   Back to index