kopia lustrzana https://github.com/jamescoxon/dl-fldigi
1526 wiersze
40 KiB
C++
1526 wiersze
40 KiB
C++
// ----------------------------------------------------------------------------
|
||
// FTextRXTX.cxx
|
||
//
|
||
// Copyright (C) 2007-2010
|
||
// Stelios Bounanos, M0GLD
|
||
//
|
||
// Copyright (C) 2008-2010
|
||
// Dave Freese, W1HKJ
|
||
//
|
||
// This file is part of fldigi.
|
||
//
|
||
// fldigi 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 3 of the License, or
|
||
// (at your option) any later version.
|
||
//
|
||
// fldigi 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, see <http://www.gnu.org/licenses/>.
|
||
// ----------------------------------------------------------------------------
|
||
|
||
#include <config.h>
|
||
|
||
#include <string>
|
||
#include <cstring>
|
||
#include <cstdlib>
|
||
#include <cstdio>
|
||
#include <sys/stat.h>
|
||
#include <map>
|
||
#include <iostream>
|
||
#include <fstream>
|
||
#include <sstream>
|
||
#include <algorithm>
|
||
#include <iomanip>
|
||
|
||
#include <FL/Fl_Tooltip.H>
|
||
|
||
#include "FTextView.h"
|
||
#include "main.h"
|
||
#include "trx.h"
|
||
#include "macros.h"
|
||
#include "main.h"
|
||
#include "fl_digi.h"
|
||
|
||
#include "cw.h"
|
||
|
||
#include "fileselect.h"
|
||
#include "font_browser.h"
|
||
|
||
#include "ascii.h"
|
||
#include "configuration.h"
|
||
#include "qrunner.h"
|
||
|
||
#include "mfsk.h"
|
||
#include "icons.h"
|
||
#include "globals.h"
|
||
#include "re.h"
|
||
#include "strutil.h"
|
||
#include "dxcc.h"
|
||
#include "locator.h"
|
||
#include "logsupport.h"
|
||
#include "status.h"
|
||
#include "gettext.h"
|
||
#include "arq_io.h"
|
||
#include "fl_digi.h"
|
||
|
||
#include "debug.h"
|
||
|
||
|
||
using namespace std;
|
||
|
||
|
||
// Fl_Scrollbar wrapper to draw marks on the slider background.
|
||
// Currently only implemented for a vertical scrollbar.
|
||
class MVScrollbar : public Fl_Scrollbar
|
||
{
|
||
struct mark_t {
|
||
double pos;
|
||
Fl_Color color;
|
||
mark_t(double pos_, Fl_Color color_) : pos(pos_), color(color_) { }
|
||
};
|
||
|
||
public:
|
||
MVScrollbar(int X, int Y, int W, int H, const char* l = 0)
|
||
: Fl_Scrollbar(X, Y, W, H, l), draw_marks(false) { }
|
||
|
||
void draw(void);
|
||
void mark(Fl_Color c) { marks.push_back(mark_t(maximum() - 1.0, c)); redraw(); }
|
||
bool has_marks(void) { return !marks.empty(); }
|
||
void show_marks(bool b) { draw_marks = b; redraw(); }
|
||
void clear(void) { marks.clear(); redraw(); }
|
||
|
||
private:
|
||
vector<mark_t> marks;
|
||
bool draw_marks;
|
||
};
|
||
|
||
Fl_Menu_Item FTextRX::menu[] = {
|
||
{ icons::make_icon_label(_("Look up call"), net_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Call"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Name"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("QTH"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("State"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Province"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Country"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Locator"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("RST(r)"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Exchange In"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Serial number"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Insert marker"), insert_link_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
|
||
{ 0 }, // VIEW_MENU_COPY
|
||
{ 0 }, // VIEW_MENU_CLEAR
|
||
{ 0 }, // VIEW_MENU_SELECT_ALL
|
||
{ 0 }, // VIEW_MENU_SAVE
|
||
{ 0 }, // VIEW_MENU_WRAP
|
||
|
||
{ _("Quick entry"), 0, 0, 0, FL_MENU_TOGGLE, FL_NORMAL_LABEL },
|
||
{ _("Scroll hints"), 0, 0, 0, FL_MENU_TOGGLE, FL_NORMAL_LABEL },
|
||
{ 0 }
|
||
};
|
||
|
||
/// FTextRX constructor.
|
||
/// We remove \c Fl_Text_Display_mod::buffer_modified_cb from the list of callbacks
|
||
/// because we want to scroll depending on the visibility of the last line; @see
|
||
/// changed_cb.
|
||
/// @param x
|
||
/// @param y
|
||
/// @param w
|
||
/// @param h
|
||
/// @param l
|
||
FTextRX::FTextRX(int x, int y, int w, int h, const char *l)
|
||
: FTextView(x, y, w, h, l)
|
||
{
|
||
memcpy(menu + RX_MENU_COPY, FTextView::menu, (FTextView::menu->size() - 1) * sizeof(*FTextView::menu));
|
||
context_menu = menu;
|
||
init_context_menu();
|
||
menu[RX_MENU_QUICK_ENTRY].clear();
|
||
menu[RX_MENU_SCROLL_HINTS].clear();
|
||
menu[RX_MENU_WRAP].hide();
|
||
// Replace the scrollbar widget
|
||
MVScrollbar* mvsb = new MVScrollbar(mVScrollBar->x(), mVScrollBar->y(),
|
||
mVScrollBar->w(), mVScrollBar->h(), NULL);
|
||
mvsb->show_marks(false);
|
||
mvsb->callback(mVScrollBar->callback(), mVScrollBar->user_data());
|
||
remove(mVScrollBar);
|
||
delete mVScrollBar;
|
||
Fl_Group::add(mVScrollBar = mvsb);
|
||
mFastDisplay = 1;
|
||
}
|
||
|
||
FTextRX::~FTextRX()
|
||
{
|
||
}
|
||
|
||
/// Handles fltk events for this widget.
|
||
|
||
/// We only care about mouse presses (to display the popup menu and prevent
|
||
/// pasting) and keyboard events (to make sure no text can be inserted).
|
||
/// Everything else is passed to the base class handle().
|
||
///
|
||
/// @param event
|
||
///
|
||
/// @return
|
||
///
|
||
int FTextRX::handle(int event)
|
||
{
|
||
static Fl_Cursor cursor;
|
||
|
||
switch (event) {
|
||
case FL_DRAG:
|
||
if (Fl::event_button() != FL_LEFT_MOUSE)
|
||
return 1;
|
||
break;
|
||
case FL_PUSH:
|
||
if (!Fl::event_inside(this))
|
||
break;
|
||
switch (Fl::event_button()) {
|
||
case FL_LEFT_MOUSE:
|
||
if (Fl::event_shift() || progdefaults.rxtext_clicks_qso_data) {
|
||
if (handle_clickable(Fl::event_x() - x(), Fl::event_y() - y()))
|
||
return 1;
|
||
if (handle_qso_data(Fl::event_x() - x(), Fl::event_y() - y()))
|
||
return 1;
|
||
}
|
||
goto out;
|
||
case FL_MIDDLE_MOUSE:
|
||
if (cursor != FL_CURSOR_HAND) {
|
||
if (handle_qso_data(Fl::event_x() - x(), Fl::event_y() - y())) {
|
||
return 1;
|
||
}
|
||
}
|
||
goto out;
|
||
case FL_RIGHT_MOUSE:
|
||
handle_context_menu();
|
||
return 1;
|
||
default:
|
||
goto out;
|
||
}
|
||
break;
|
||
case FL_RELEASE:
|
||
break;
|
||
case FL_MOVE: {
|
||
int p = xy_to_position(Fl::event_x(), Fl::event_y(), Fl_Text_Display_mod::CURSOR_POS);
|
||
if ((unsigned char)sbuf->byte_at(p) >= CLICK_START + FTEXT_DEF) {
|
||
if (cursor != FL_CURSOR_HAND)
|
||
window()->cursor(cursor = FL_CURSOR_HAND);
|
||
return 1;
|
||
}
|
||
else
|
||
cursor = FL_CURSOR_INSERT;
|
||
break;
|
||
}
|
||
// catch some text-modifying events that are not handled by kf_* functions
|
||
case FL_KEYBOARD:
|
||
break;
|
||
case FL_ENTER:
|
||
if (!progdefaults.rxtext_tooltips || Fl_Tooltip::delay() == 0.0f)
|
||
break;
|
||
tooltips.enabled = Fl_Tooltip::enabled();
|
||
tooltips.delay = Fl_Tooltip::delay();
|
||
Fl_Tooltip::enable(1);
|
||
Fl_Tooltip::delay(0.0f);
|
||
Fl::add_timeout(tooltips.delay / 2.0, dxcc_tooltip, this);
|
||
break;
|
||
case FL_LEAVE:
|
||
if (!progdefaults.rxtext_tooltips || Fl_Tooltip::delay() != 0.0f)
|
||
break;
|
||
Fl_Tooltip::enable(tooltips.enabled);
|
||
Fl_Tooltip::delay(tooltips.delay);
|
||
Fl::remove_timeout(dxcc_tooltip, this);
|
||
break;
|
||
}
|
||
|
||
out:
|
||
return FTextView::handle(event);
|
||
}
|
||
|
||
/// Adds a char to the buffer
|
||
///
|
||
/// @param c The character
|
||
/// @param attr The attribute (@see enum text_attr_e); RECV if omitted.
|
||
///
|
||
|
||
void FTextRX::add(unsigned int c, int attr)
|
||
{
|
||
if (c == '\r')
|
||
return;
|
||
|
||
char s[] = { '\0', '\0', char( FTEXT_DEF + attr ), '\0' };
|
||
const char *cp = &s[0];
|
||
|
||
// The user may have moved the cursor by selecting text or
|
||
// scrolling. Place it at the end of the buffer.
|
||
if (mCursorPos != tbuf->length())
|
||
insert_position(tbuf->length());
|
||
|
||
switch (c) {
|
||
case '\b':
|
||
// we don't call kf_backspace because it kills selected text
|
||
if (s_text.length()) {
|
||
int character_start = tbuf->utf8_align(tbuf->length() - 1);
|
||
int character_length = fl_utf8len1(tbuf->byte_at(character_start));
|
||
|
||
tbuf->remove(character_start, tbuf->length());
|
||
sbuf->remove(character_start, sbuf->length());
|
||
s_text.resize(s_text.length() - character_length);
|
||
s_style.resize(s_style.length() - character_length);
|
||
}
|
||
break;
|
||
case '\n':
|
||
// maintain the scrollback limit, if we have one
|
||
if (max_lines > 0 && tbuf->count_lines(0, tbuf->length()) >= max_lines) {
|
||
int le = tbuf->line_end(0) + 1; // plus 1 for the newline
|
||
tbuf->remove(0, le);
|
||
sbuf->remove(0, le);
|
||
}
|
||
s_text.clear();
|
||
s_style.clear();
|
||
insert("\n");
|
||
sbuf->append(s + 2);
|
||
break;
|
||
default:
|
||
if ((c < ' ' || c == 127) && attr != CTRL) // look it up
|
||
cp = ascii[(unsigned char)c];
|
||
else // insert verbatim
|
||
s[0] = c;
|
||
|
||
for (int i = 0; cp[i]; ++i) {
|
||
s_text += cp[i];
|
||
s_style += s[2];
|
||
}
|
||
|
||
fl_font( textfont(), textsize() );
|
||
int lwidth = (int)fl_width( s_text.c_str(), s_text.length());
|
||
bool wrapped = false;
|
||
if ( lwidth >= (text_area.w - mVScrollBar->w() - LEFT_MARGIN - RIGHT_MARGIN)) {
|
||
if (c != ' ') {
|
||
size_t p = s_text.rfind(' ');
|
||
if (p != string::npos) {
|
||
s_text.erase(0, p+1);
|
||
s_style.erase(0, p+1);
|
||
if (s_text.length() < 10) { // wrap and delete trailing space
|
||
tbuf->remove(tbuf->length() - s_text.length(), tbuf->length());
|
||
sbuf->remove(sbuf->length() - s_style.length(), sbuf->length());
|
||
insert("\n"); // always insert new line
|
||
sbuf->append(s + 2);
|
||
insert(s_text.c_str());
|
||
sbuf->append(s_style.c_str());
|
||
wrapped = true;
|
||
}
|
||
}
|
||
}
|
||
if (!wrapped) { // add a new line if not wrapped
|
||
insert("\n");
|
||
sbuf->append(s + 2);
|
||
s_text.clear();
|
||
s_style.clear();
|
||
if (c != ' ') { // add character if not a space (no leading spaces)
|
||
for (int i = 0; cp[i]; ++i) {
|
||
sbuf->append(s + 2);
|
||
s_style.append(s + 2);
|
||
}
|
||
s_text.append(cp);
|
||
insert(cp);
|
||
}
|
||
}
|
||
} else {
|
||
for (int i = 0; cp[i]; ++i)
|
||
sbuf->append(s + 2);
|
||
insert(cp);
|
||
}
|
||
break;
|
||
}
|
||
|
||
// test for bottom of text visibility
|
||
if (// !mFastDisplay &&
|
||
(mVScrollBar->value() >= mNBufferLines - mNVisibleLines + mVScrollBar->linesize() - 1))
|
||
show_insert_position();
|
||
}
|
||
|
||
void FTextRX::set_quick_entry(bool b)
|
||
{
|
||
if (b)
|
||
menu[RX_MENU_QUICK_ENTRY].set();
|
||
else
|
||
menu[RX_MENU_QUICK_ENTRY].clear();
|
||
}
|
||
|
||
void FTextRX::set_scroll_hints(bool b)
|
||
{
|
||
if (b)
|
||
menu[RX_MENU_SCROLL_HINTS].set();
|
||
else
|
||
menu[RX_MENU_SCROLL_HINTS].clear();
|
||
static_cast<MVScrollbar*>(mVScrollBar)->show_marks(b);
|
||
}
|
||
|
||
void FTextRX::mark(FTextBase::TEXT_ATTR attr)
|
||
{
|
||
if (attr == NATTR)
|
||
attr = CLICK_START;
|
||
static_cast<MVScrollbar*>(mVScrollBar)->mark(styles[attr].color);
|
||
}
|
||
|
||
void FTextRX::clear(void)
|
||
{
|
||
FTextBase::clear();
|
||
s_text.clear();
|
||
s_style.clear();
|
||
static_cast<MVScrollbar*>(mVScrollBar)->clear();
|
||
}
|
||
|
||
void FTextRX::setFont(Fl_Font f, int attr)
|
||
{
|
||
FTextBase::setFont(f, attr);
|
||
}
|
||
|
||
int FTextRX::handle_clickable(int x, int y)
|
||
{
|
||
int pos;
|
||
unsigned int style;
|
||
|
||
pos = xy_to_position(x + this->x(), y + this->y(), CURSOR_POS);
|
||
// return unless clickable style
|
||
if ((style = (unsigned char)sbuf->byte_at(pos)) < CLICK_START + FTEXT_DEF)
|
||
return 0;
|
||
|
||
int start, end;
|
||
for (start = pos-1; start >= 0; start--)
|
||
if ((unsigned char)sbuf->byte_at(start) != style)
|
||
break;
|
||
start++;
|
||
int len = sbuf->length();
|
||
for (end = pos+1; end < len; end++)
|
||
if ((unsigned char)sbuf->byte_at(end) != style)
|
||
break;
|
||
|
||
switch (style - FTEXT_DEF) {
|
||
case QSY:
|
||
handle_qsy(start, end);
|
||
return 1;
|
||
break;
|
||
// ...
|
||
default:
|
||
break;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
void FTextRX::handle_qsy(int start, int end)
|
||
{
|
||
char* text = tbuf->text_range(start, end);
|
||
|
||
extern map<string, qrg_mode_t> qrg_marks;
|
||
map<string, qrg_mode_t>::const_iterator i;
|
||
if ((i = qrg_marks.find(text)) != qrg_marks.end()) {
|
||
const qrg_mode_t& m = i->second;
|
||
if (active_modem->get_mode() != m.mode)
|
||
init_modem_sync(m.mode);
|
||
qsy(m.rfcarrier, m.carrier);
|
||
}
|
||
|
||
free(text);
|
||
}
|
||
|
||
static fre_t rst("^[1-5][1-9]{2}$", REG_EXTENDED | REG_NOSUB);
|
||
static fre_t loc("[a-r]{2}[[:digit:]]{2}([a-x]{2})?", REG_EXTENDED | REG_ICASE);
|
||
static fre_t call("([[:alnum:]]?[[:alpha:]/]+[[:digit:]]+[[:alnum:]/]+)", REG_EXTENDED);
|
||
|
||
int FTextRX::handle_qso_data(int start, int end)
|
||
{
|
||
char* s = get_word(start, end, progdefaults.nonwordchars.c_str());
|
||
if (!s)
|
||
return 0;
|
||
char* p = s;
|
||
|
||
Fl_Input2* target = 0;
|
||
|
||
if (QsoInfoFrame1B->visible()) {
|
||
if (call.match(s)) { // point p to substring
|
||
const regmatch_t& offsets = call.suboff()[1];
|
||
p = s + offsets.rm_so;
|
||
*(s + offsets.rm_eo) = '\0';
|
||
inpCall->value(p);
|
||
log_callback(inpCall);
|
||
Fl::copy(p, strlen(p), 1); // copy to clipboard
|
||
} else {
|
||
inpXchgIn->position(inpXchgIn->size());
|
||
if (inpXchgIn->size()) inpXchgIn->insert(" ", 1);
|
||
inpXchgIn->insert(s);
|
||
log_callback(inpXchgIn);
|
||
}
|
||
} else {
|
||
if (rst.match(s))
|
||
target = inpRstIn;
|
||
else if (loc.match(s))
|
||
target = inpLoc;
|
||
else if (call.match(s)) { // point p to substring
|
||
const regmatch_t& offsets = call.suboff()[1];
|
||
p = s + offsets.rm_so;
|
||
*(s + offsets.rm_eo) = '\0';
|
||
target = inpCall;
|
||
Fl::copy(p, strlen(p), 1); // copy to clipboard
|
||
}
|
||
else if (count_if(s, s + strlen(s), static_cast<int(*)(int)>(isdigit)))
|
||
target = inpQth;
|
||
else
|
||
target = *inpName->value() ? inpQth : inpName;
|
||
if (target) {
|
||
target->value(p);
|
||
log_callback(target);
|
||
free(s);
|
||
restoreFocus(91);
|
||
return 1;
|
||
}
|
||
}
|
||
free(s);
|
||
return 0;
|
||
}
|
||
|
||
void FTextRX::handle_context_menu(void)
|
||
{
|
||
bool contest_ui = progStatus.Rig_Contest_UI ||
|
||
(progStatus.contest && !progStatus.NO_RIGLOG && !progStatus.Rig_Log_UI);
|
||
|
||
unsigned shown = 0;
|
||
|
||
#define show_item(x_) (shown |= (1 << x_))
|
||
#define hide_item(x_) (shown &= ~(1 << x_))
|
||
#define test_item(x_) (shown & (1 << x_))
|
||
|
||
show_item(RX_MENU_CALL);
|
||
|
||
if (contest_ui || (progStatus.contest && !progStatus.NO_RIGLOG && !progStatus.Rig_Log_UI)) {
|
||
show_item(RX_MENU_SERIAL);
|
||
show_item(RX_MENU_XCHG);
|
||
}
|
||
// "Look up call" shown only in non-contest mode
|
||
else if (progdefaults.QRZWEB != QRZWEBNONE || progdefaults.QRZXML != QRZXMLNONE)
|
||
show_item(RX_MENU_QRZ_THIS);
|
||
|
||
if (menu[RX_MENU_QUICK_ENTRY].value()) {
|
||
for (size_t i = RX_MENU_NAME; i <= RX_MENU_RST_IN; i++)
|
||
show_item(i);
|
||
menu[RX_MENU_CALL].flags &= ~FL_MENU_DIVIDER;
|
||
}
|
||
else {
|
||
if (!contest_ui && !progStatus.contest) {
|
||
show_item(RX_MENU_NAME);
|
||
show_item(RX_MENU_QTH);
|
||
show_item(RX_MENU_RST_IN);
|
||
}
|
||
menu[RX_MENU_CALL].flags |= FL_MENU_DIVIDER;
|
||
}
|
||
|
||
if (static_cast<MVScrollbar*>(mVScrollBar)->has_marks())
|
||
menu[RX_MENU_SCROLL_HINTS].show();
|
||
else
|
||
menu[RX_MENU_SCROLL_HINTS].hide();
|
||
|
||
for (size_t i = RX_MENU_QRZ_THIS; i <= RX_MENU_XCHG; i++) {
|
||
if (test_item(i))
|
||
menu[i].show();
|
||
else
|
||
menu[i].hide();
|
||
}
|
||
|
||
#undef show_item
|
||
#undef hide_item
|
||
#undef test_item
|
||
|
||
// availability of editing items depend on buffer state
|
||
icons::set_active(&menu[RX_MENU_COPY], tbuf->selected());
|
||
icons::set_active(&menu[RX_MENU_CLEAR], tbuf->length());
|
||
icons::set_active(&menu[RX_MENU_SELECT_ALL], tbuf->length());
|
||
icons::set_active(&menu[RX_MENU_SAVE], tbuf->length());
|
||
|
||
if (wrap)
|
||
menu[RX_MENU_WRAP].set();
|
||
else
|
||
menu[RX_MENU_WRAP].clear();
|
||
|
||
show_context_menu();
|
||
}
|
||
|
||
/// The context menu handler
|
||
///
|
||
/// @param val
|
||
///
|
||
void FTextRX::menu_cb(size_t item)
|
||
{
|
||
Fl_Input2* input = 0;
|
||
switch (item) {
|
||
case RX_MENU_QRZ_THIS:
|
||
menu_cb(RX_MENU_CALL);
|
||
extern void CALLSIGNquery();
|
||
CALLSIGNquery();
|
||
break;
|
||
case RX_MENU_CALL:
|
||
input = inpCall;
|
||
break;
|
||
case RX_MENU_NAME:
|
||
input = inpName;
|
||
break;
|
||
case RX_MENU_QTH:
|
||
input = inpQth;
|
||
break;
|
||
case RX_MENU_STATE:
|
||
input = inpState;
|
||
break;
|
||
case RX_MENU_PROVINCE:
|
||
input = inpVEprov;
|
||
break;
|
||
case RX_MENU_COUNTRY:
|
||
input = inpCountry;
|
||
break;
|
||
case RX_MENU_LOC:
|
||
input = inpLoc;
|
||
break;
|
||
case RX_MENU_RST_IN:
|
||
input = inpRstIn;
|
||
break;
|
||
|
||
case RX_MENU_SERIAL:
|
||
input = inpSerNo;
|
||
break;
|
||
case RX_MENU_XCHG:
|
||
input = inpXchgIn;
|
||
break;
|
||
|
||
case RX_MENU_DIV:
|
||
note_qrg(false, "\n", "\n");
|
||
break;
|
||
case RX_MENU_COPY:
|
||
kf_copy(Fl::event_key(), this);
|
||
break;
|
||
case RX_MENU_CLEAR:
|
||
clear();
|
||
break;
|
||
case RX_MENU_SELECT_ALL:
|
||
tbuf->select(0, tbuf->length());
|
||
break;
|
||
|
||
case RX_MENU_SAVE:
|
||
saveFile();
|
||
break;
|
||
|
||
case RX_MENU_QUICK_ENTRY:
|
||
menu[item].flags ^= FL_MENU_VALUE;
|
||
if (menu[item].value())
|
||
handle_context_menu();
|
||
break;
|
||
|
||
case RX_MENU_WRAP:
|
||
set_word_wrap(!wrap, true);
|
||
break;
|
||
|
||
case RX_MENU_SCROLL_HINTS:
|
||
menu[item].flags ^= FL_MENU_VALUE;
|
||
static_cast<MVScrollbar*>(mVScrollBar)->show_marks(menu[item].value());
|
||
break;
|
||
}
|
||
|
||
restoreFocus(92);
|
||
|
||
if (!input)
|
||
return;
|
||
char* s = get_word(popx, popy, progdefaults.nonwordchars.c_str());
|
||
if (!s)
|
||
return;
|
||
|
||
if (item == RX_MENU_XCHG) { // append
|
||
input->position(input->size());
|
||
if (input->size())
|
||
input->insert(" ", 1);
|
||
input->insert(s);
|
||
}
|
||
else
|
||
input->value(s);
|
||
log_callback(input);
|
||
free(s);
|
||
}
|
||
|
||
const char* FTextRX::dxcc_lookup_call(int x, int y)
|
||
{
|
||
char* s = get_word(x - this->x(), y - this->y(), progdefaults.nonwordchars.c_str());
|
||
char* mem = s;
|
||
if (!(s && *s && call.match(s))) {
|
||
free(s);
|
||
return 0;
|
||
}
|
||
|
||
double lon1, lat1, lon2 = 360.0, lat2 = 360.0, distance, azimuth;
|
||
static string tip;
|
||
ostringstream stip;
|
||
const dxcc* e = 0;
|
||
cQsoRec* qso = 0;
|
||
unsigned char qsl;
|
||
|
||
// prevent locator-only lookup if Ctrl is held
|
||
if (!(Fl::event_state() & FL_CTRL) && loc.match(s)) {
|
||
const vector<regmatch_t>& v = loc.suboff();
|
||
s += v[0].rm_so;
|
||
*(s + v[0].rm_eo) = '\0';
|
||
if (QRB::locator2longlat(&lon2, &lat2, s) != QRB::QRB_OK)
|
||
goto ret;
|
||
e = 0; qsl = 0; qso = 0;
|
||
}
|
||
else {
|
||
e = dxcc_lookup(s);
|
||
qsl = qsl_lookup(s);
|
||
qso = SearchLog(s);
|
||
}
|
||
|
||
if (qso && QRB::locator2longlat(&lon2, &lat2, qso->getField(GRIDSQUARE)) != QRB::QRB_OK)
|
||
lon2 = lat2 = 360.0;
|
||
|
||
if (e) {
|
||
// use dxcc data if we didn't have a good locator string in the log file
|
||
if (lon2 == 360.0)
|
||
lon2 = -e->longitude;
|
||
if (lat2 == 360.0)
|
||
lat2 = e->latitude;
|
||
stip << e->country << " (" << e->continent
|
||
<< " GMT" << fixed << showpos << setprecision(1) << -e->gmt_offset << noshowpos
|
||
<< ") CQ-" << e->cq_zone << " ITU-" << e->itu_zone << '\n';
|
||
}
|
||
|
||
if (QRB::locator2longlat(&lon1, &lat1, progdefaults.myLocator.c_str()) == QRB::QRB_OK &&
|
||
QRB::qrb(lon1, lat1, lon2, lat2, &distance, &azimuth) == QRB::QRB_OK) {
|
||
if (progdefaults.us_units) {
|
||
stip << "QTE " << fixed << setprecision(0) << azimuth << '\260' << " ("
|
||
<< QRB::azimuth_long_path(azimuth) << '\260' << ") QRB "
|
||
<< distance * 0.62168188 << "mi"<< " (" <<
|
||
QRB::distance_long_path(distance) * 0.62168188 <<
|
||
"mi)\n";
|
||
}
|
||
else {
|
||
stip << "QTE " << fixed << setprecision(0) << azimuth << '\260' << " ("
|
||
<< QRB::azimuth_long_path(azimuth) << '\260' << ") QRB "
|
||
<< distance << "km(" <<
|
||
QRB::distance_long_path(distance) << "km)\n";
|
||
}
|
||
}
|
||
|
||
if (qso) {
|
||
const char* info[] = {
|
||
qso->getField(NAME), qso->getField(QTH), qso->getField(QSO_DATE),
|
||
qso->getField(BAND), qso->getField(MODE)
|
||
};
|
||
// name & qth
|
||
if (*info[0])
|
||
join(stip << "* ", info, 2, _(" in "), true) << '\n';
|
||
// other info
|
||
join(stip << "* " << _("Last QSO") << ": ", info+2, 3, ", ", true) << '\n';
|
||
}
|
||
if (qsl) {
|
||
stip << "* QSL: ";
|
||
for (unsigned char i = 0; i < QSL_END; i++)
|
||
if (qsl & (1 << i))
|
||
stip << qsl_names[i] << ' ';
|
||
stip << '\n';
|
||
}
|
||
|
||
ret:
|
||
free(mem);
|
||
tip = stip.str();
|
||
return tip.empty() ? 0 : tip.c_str();
|
||
}
|
||
|
||
void FTextRX::dxcc_tooltip(void* obj)
|
||
{
|
||
struct point {
|
||
int x, y;
|
||
bool operator==(const point& p) { return x == p.x && y == p.y; }
|
||
bool operator!=(const point& p) { return !(*this == p); }
|
||
};
|
||
static point p[3] = { {0, 0}, {0, 0}, {0, 0} };
|
||
|
||
memmove(p, p+1, 2 * sizeof(point));
|
||
p[2].x = Fl::event_x(); p[2].y = Fl::event_y();
|
||
|
||
static const char* tip = 0;
|
||
FTextRX* v = reinterpret_cast<FTextRX*>(obj);
|
||
// look up word under cursor if we have been called twice with the cursor
|
||
// at the same position, and if the cursor was previously somewhere else
|
||
if (p[2] == p[1] && p[2] != p[0] && ((tip = v->dxcc_lookup_call(p[2].x, p[2].y))))
|
||
Fl_Tooltip::enter_area(v, p[2].x, p[2].y, 100, 100, tip);
|
||
else if (p[2] != p[1])
|
||
Fl_Tooltip::exit(v);
|
||
|
||
Fl::repeat_timeout(tip ? Fl_Tooltip::hoverdelay() : v->tooltips.delay / 2.0, dxcc_tooltip, obj);
|
||
}
|
||
|
||
|
||
// ----------------------------------------------------------------------------
|
||
|
||
|
||
Fl_Menu_Item FTextTX::menu[] = {
|
||
{ icons::make_icon_label(_("Transmit"), tx_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Receive"), rx_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Abort"), process_stop_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
|
||
{ icons::make_icon_label(_("Send image..."), image_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
|
||
|
||
{ 0 }, // EDIT_MENU_CUT
|
||
{ 0 }, // EDIT_MENU_COPY
|
||
{ 0 }, // EDIT_MENU_PASTE
|
||
{ 0 }, // EDIT_MENU_CLEAR
|
||
{ 0 }, // EDIT_MENU_READ
|
||
{ 0 }, // EDIT_MENU_WRAP
|
||
|
||
{ _("Spec Char"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
|
||
{ "¢ - cent", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "£ - pound", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "µ - micro", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "° - degree", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "¿ - iques", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "× - times", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "÷ - divide", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ _("A"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
|
||
{ "À - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "à - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Á - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "á - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Â - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "â - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ã - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ã - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ä - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ä - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Å - ring", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "å - ring", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Æ - aelig", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "æ - aelig", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{0,0,0,0,0,0,0,0,0},
|
||
{ _("E"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
|
||
{ "È - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "è - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "É - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "é - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ê - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ê - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ë - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ë - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{0,0,0,0,0,0,0,0,0},
|
||
{ _("I"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
|
||
{ "Ì - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ì - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Í - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "í - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Î - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "î - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ï - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ï - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{0,0,0,0,0,0,0,0,0},
|
||
{ _("N"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
|
||
{ "Ñ - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ñ - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{0,0,0,0,0,0,0,0,0},
|
||
{ _("O"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
|
||
{ "Ò - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ò - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ó - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ó - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ô - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ô - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Õ - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "õ - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ö - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ö - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ø - slash", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ø - slash", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{0,0,0,0,0,0,0,0,0},
|
||
{ _("U"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
|
||
{ "Ù - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ù - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ú - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ú - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Û - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "û - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ü - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ü - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{0,0,0,0,0,0,0,0,0},
|
||
{ _("Y"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
|
||
{ "Ý - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ý - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ÿ - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{0,0,0,0,0,0,0,0,0},
|
||
{ _("Other"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
|
||
{ "ß - szlig", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ç - cedil", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ç - cedil", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Ð - eth", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "ð - eth", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{ "Þ - thorn", 0, 0, 0, 0, FL_NORMAL_LABEL },
|
||
{0,0,0,0,0,0,0,0,0},
|
||
{0,0,0,0,0,0,0,0,0},
|
||
{ 0 }
|
||
};
|
||
|
||
// needed by our static kf functions, which may restrict editing depending on
|
||
// the transmit cursor position
|
||
int *FTextTX::ptxpos;
|
||
|
||
FTextTX::FTextTX(int x, int y, int w, int h, const char *l)
|
||
: FTextEdit(x, y, w, h, l),
|
||
PauseBreak(false), txpos(0), bkspaces(0)
|
||
{
|
||
ptxpos = &txpos;
|
||
|
||
change_keybindings();
|
||
|
||
memcpy(menu + TX_MENU_CUT, FTextEdit::menu, (FTextEdit::menu->size() - 1) * sizeof(*FTextEdit::menu));
|
||
context_menu = menu;
|
||
init_context_menu();
|
||
utf8_txpos = txpos = 0;
|
||
}
|
||
|
||
/// Handles fltk events for this widget.
|
||
/// We pass keyboard events to handle_key() and handle mouse3 presses to show
|
||
/// the popup menu. We also disallow mouse2 events in the transmitted text area.
|
||
/// Everything else is passed to the base class handle().
|
||
///
|
||
/// @param event
|
||
///
|
||
/// @return
|
||
///
|
||
int FTextTX::handle(int event)
|
||
{
|
||
if ( !(Fl::event_inside(this) || (event == FL_KEYBOARD && Fl::focus() == this)) )
|
||
return FTextEdit::handle(event);
|
||
|
||
switch (event) {
|
||
case FL_KEYBOARD:
|
||
if (active_modem->get_mode() == MODE_FSQ) {
|
||
if (Fl::event_key() == FL_Enter || Fl::event_key() == FL_KP_Enter) {
|
||
fsq_transmit(0);
|
||
return 1;
|
||
}
|
||
}
|
||
return handle_key(Fl::event_key()) ? 1 : FTextEdit::handle(event);
|
||
case FL_PUSH:
|
||
if (Fl::event_button() == FL_MIDDLE_MOUSE &&
|
||
xy_to_position(Fl::event_x(), Fl::event_y(), CHARACTER_POS) < txpos)
|
||
return 1; // ignore mouse2 text pastes inside the transmitted text
|
||
}
|
||
|
||
return FTextEdit::handle(event);
|
||
}
|
||
|
||
/// Clears the buffer.
|
||
/// Also resets the transmit position, stored backspaces and tx pause flag.
|
||
///
|
||
void FTextTX::clear(void)
|
||
{
|
||
FTextEdit::clear();
|
||
txpos = 0;
|
||
utf8_txpos = 0;
|
||
bkspaces = 0;
|
||
PauseBreak = false;
|
||
}
|
||
|
||
/// Clears the sent text.
|
||
/// Also resets the transmit position, stored backspaces and tx pause flag.
|
||
///
|
||
void FTextTX::clear_sent(void)
|
||
{
|
||
tbuf->remove(0, utf8_txpos);
|
||
sbuf->remove(0, utf8_txpos);
|
||
txpos = 0;
|
||
utf8_txpos = 0;
|
||
bkspaces = 0;
|
||
PauseBreak = false;
|
||
set_word_wrap(restore_wrap);
|
||
}
|
||
|
||
/// Returns boolean <eot> end of text
|
||
///
|
||
/// true if empty buffer
|
||
/// false if characters remain
|
||
///
|
||
bool FTextTX::eot(void)
|
||
{
|
||
return (insert_position() == txpos);
|
||
}
|
||
|
||
/// Returns the next character to be transmitted.
|
||
///
|
||
/// @return The next character, or ETX if the transmission has been paused, or
|
||
/// NUL if no text should be transmitted.
|
||
///
|
||
int FTextTX::nextChar(void)
|
||
{
|
||
int c;
|
||
|
||
if (bkspaces) {
|
||
--bkspaces;
|
||
c = '\b';
|
||
}
|
||
else if (PauseBreak) {
|
||
PauseBreak = false;
|
||
c = GET_TX_CHAR_ETX;//0x03;
|
||
} else if (insert_position() <= utf8_txpos) { // empty buffer or cursor inside transmitted text
|
||
c = -1;
|
||
} else {
|
||
if ((c = tbuf->char_at(utf8_txpos)) > 0) {
|
||
int n = fl_utf8bytes(c);
|
||
|
||
REQ(FTextTX::changed_cb, utf8_txpos, 0, 0, -1, static_cast<const char *>(0), this);
|
||
REQ(FTextTX::changed_cb, utf8_txpos+1, 0, 0, -1, static_cast<const char *>(0), this);
|
||
++txpos;
|
||
utf8_txpos += n;
|
||
} else
|
||
c = -1;
|
||
}
|
||
return c;
|
||
}
|
||
|
||
// called by xmlrpc thread
|
||
// called by macro execution
|
||
void FTextTX::add_text(string s)
|
||
{
|
||
for (size_t n = 0; n < s.length(); n++) {
|
||
if (s[n] == '\b') {
|
||
int ipos = insert_position();
|
||
if (tbuf->length()) {
|
||
if (ipos > 0 && txpos == ipos) {
|
||
bkspaces++;
|
||
txpos--;
|
||
int nn;
|
||
tbuf->get_char_at(utf8_txpos, nn);
|
||
utf8_txpos -= nn;
|
||
}
|
||
tbuf->remove(tbuf->length() - 1, tbuf->length());
|
||
sbuf->remove(sbuf->length() - 1, sbuf->length());
|
||
redraw();
|
||
}
|
||
} else {
|
||
//LOG_DEBUG("%04x ", s[n] & 0x00FF);
|
||
add(s[n] & 0xFF, RECV);
|
||
}
|
||
}
|
||
}
|
||
|
||
void FTextTX::setFont(Fl_Font f, int attr)
|
||
{
|
||
FTextBase::setFont(f, attr);
|
||
}
|
||
|
||
/// Handles keyboard shorcuts
|
||
///
|
||
/// @param key
|
||
// pressed key
|
||
///
|
||
/// @return
|
||
// 1 if shortcut is handled, otherwise 0.
|
||
///
|
||
int FTextTX::handle_key_shortcuts(int key)
|
||
{
|
||
std::string etag = "";
|
||
|
||
switch (key) {
|
||
case 'c': // add <CALL> for SC-c
|
||
case 'm': // add <MYCALL> for SC-m
|
||
case 'n': // add <NAME> for SC-n
|
||
case 'r': // add <RST> for SC-r
|
||
case 'l': // add <MYLOC> for SC-l
|
||
case 'h': // add <MYQTH> for SC-h
|
||
case 'a': // add <ANTENNA> for SC-a
|
||
if ((Fl::event_state() & FL_CTRL) && (Fl::event_state() & FL_SHIFT))
|
||
// if ((Fl::event_state() & (FL_CTRL | FL_SHIFT))) // investigate why this doesn't work...
|
||
{
|
||
switch (key)
|
||
{
|
||
case 'c':
|
||
etag = inpCall->value();
|
||
break;
|
||
case 'm':
|
||
etag = progdefaults.myCall;
|
||
break;
|
||
case 'n':
|
||
etag = inpName->value();
|
||
break;
|
||
case 'r':
|
||
{
|
||
std::string s;
|
||
etag = (s = inpRstIn->value()).length() ? s : std::string("599");
|
||
}
|
||
break;
|
||
case 'l':
|
||
etag = progdefaults.myLocator;
|
||
break;
|
||
case 'h':
|
||
etag = progdefaults.myQth;
|
||
break;
|
||
case 'a':
|
||
etag = progdefaults.myAntenna;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// Add text + space if length is > 0
|
||
if (etag.length())
|
||
add_text(etag + std::string(" "));
|
||
|
||
return 1;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/// Handles keyboard events to override Fl_Text_Editor_mod's handling of some
|
||
/// keystrokes.
|
||
///
|
||
/// @param key
|
||
///
|
||
/// @return
|
||
///
|
||
int FTextTX::handle_key(int key)
|
||
{
|
||
|
||
if (handle_key_shortcuts(key))
|
||
return 1;
|
||
|
||
switch (key) {
|
||
case FL_Escape: // set stop flag and clear
|
||
{
|
||
static time_t t[2] = { 0, 0 };
|
||
static unsigned char i = 0;
|
||
if (t[i] == time(&t[!i])) { // two presses in a second: abort transmission
|
||
if (trx_state == STATE_TX)
|
||
menu_cb(TX_MENU_ABORT);
|
||
t[i = !i] = 0;
|
||
return 1;
|
||
}
|
||
i = !i;
|
||
}
|
||
|
||
if (trx_state == STATE_TX && active_modem->get_stopflag() == false) {
|
||
kf_select_all(0, this);
|
||
kf_copy(0, this);
|
||
clear();
|
||
if (arq_text_available)
|
||
AbortARQ();
|
||
active_modem->set_stopflag(true);
|
||
}
|
||
|
||
if (trx_state == STATE_TUNE)
|
||
abort_tx();
|
||
|
||
stopMacroTimer();
|
||
return 1;
|
||
|
||
case 't': // transmit for C-t
|
||
if (trx_state == STATE_RX && Fl::event_state() & FL_CTRL) {
|
||
menu_cb(TX_MENU_TX);
|
||
return 1;
|
||
}
|
||
break;
|
||
case 'r':// receive for C-r
|
||
if (Fl::event_state() & FL_CTRL) {
|
||
menu_cb(TX_MENU_RX);
|
||
return 1;
|
||
}
|
||
else if (!(Fl::event_state() & (FL_META | FL_ALT)))
|
||
break;
|
||
// fall through to (un)pause for M-r or A-r
|
||
|
||
case FL_Pause:
|
||
if (trx_state != STATE_TX) {
|
||
start_tx();
|
||
}
|
||
else
|
||
PauseBreak = true;
|
||
return 1;
|
||
case (FL_KP + '+'):
|
||
if (active_modem == cw_modem)
|
||
active_modem->incWPM();
|
||
return 1;
|
||
case (FL_KP + '-'):
|
||
if (active_modem == cw_modem)
|
||
active_modem->decWPM();
|
||
return 1;
|
||
case (FL_KP + '*'):
|
||
if (active_modem == cw_modem)
|
||
active_modem->toggleWPM();
|
||
return 1;
|
||
case FL_Tab:
|
||
if (active_modem == fsq_modem) return 1;
|
||
// In non-CW modes: Tab and Ctrl-tab both pause until user moves the
|
||
// cursor to let some more text through. Another (ctrl-)tab goes back to
|
||
// the end of the buffer and resumes sending.
|
||
|
||
// In CW mode: Tab pauses, skips rest of buffer, applies the
|
||
// SKIP style, then resumes sending when new text is entered.
|
||
// Ctrl-tab does the same thing as for all other modes.
|
||
if (utf8_txpos != insert_position())
|
||
insert_position(utf8_txpos);
|
||
else
|
||
insert_position(tbuf->length());
|
||
if (!(Fl::event_state() & FL_CTRL) && active_modem == cw_modem) {
|
||
int n = tbuf->length() - utf8_txpos;
|
||
char s[n + 1];
|
||
memset(s, FTEXT_DEF + SKIP, n);
|
||
s[n] = 0;
|
||
sbuf->replace(utf8_txpos, sbuf->length(), s);
|
||
insert_position(tbuf->length());
|
||
redisplay_range(utf8_txpos, insert_position());
|
||
utf8_txpos = insert_position();
|
||
}
|
||
return 1;
|
||
// Move cursor, or search up/down with the Meta/Alt modifiers
|
||
case FL_Left:
|
||
if (Fl::event_state() & (FL_META | FL_ALT)) {
|
||
if (active_modem == fsq_modem) return 1;
|
||
active_modem->searchDown();
|
||
return 1;
|
||
}
|
||
return 0;
|
||
case FL_Right:
|
||
if (Fl::event_state() & (FL_META | FL_ALT)) {
|
||
if (active_modem == fsq_modem) return 1;
|
||
active_modem->searchUp();
|
||
return 1;
|
||
}
|
||
return 0;
|
||
// queue a BS and decr. the txpos, unless the cursor is in the tx text
|
||
case FL_BackSpace:
|
||
{
|
||
int ipos = insert_position();
|
||
if (utf8_txpos > 0 && utf8_txpos == ipos) {
|
||
bkspaces++;
|
||
utf8_txpos = tbuf->prev_char(ipos);
|
||
txpos--;
|
||
}
|
||
return 0;
|
||
}
|
||
// alt - 1 / 2 changes macro sets
|
||
case '1':
|
||
case '2':
|
||
case '3':
|
||
case '4':
|
||
if (Fl::event_state() & FL_ALT) {
|
||
if (active_modem == fsq_modem) return 1;
|
||
static char lbl[2] = "1";
|
||
altMacros = key - '1';
|
||
if (progdefaults.mbar_scheme > MACRO_SINGLE_BAR_MAX) {
|
||
if (!altMacros) altMacros = 1;
|
||
for (int i = 0; i < NUMMACKEYS; i++) {
|
||
btnMacro[NUMMACKEYS + i]->label(
|
||
macros.name[(altMacros * NUMMACKEYS) + i].c_str());
|
||
btnMacro[NUMMACKEYS + i]->redraw_label();
|
||
}
|
||
lbl[0] = key;
|
||
btnAltMacros2->label(lbl);
|
||
btnAltMacros2->redraw_label();
|
||
} else {
|
||
for (int i = 0; i < NUMMACKEYS; i++) {
|
||
btnMacro[i]->label(
|
||
macros.name[(altMacros * NUMMACKEYS) + i].c_str());
|
||
btnMacro[i]->redraw_label();
|
||
}
|
||
lbl[0] = key;
|
||
btnAltMacros1->label(lbl);
|
||
btnAltMacros1->redraw_label();
|
||
}
|
||
return 1;
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
if (insert_position() < txpos)
|
||
return 1;
|
||
|
||
// insert a macro
|
||
if (key >= FL_F && key <= FL_F_Last) {
|
||
return handle_key_macro(key);
|
||
}
|
||
// read ctl-ddd, where d is a digit, as ascii characters (in base 10)
|
||
// and insert verbatim; e.g. ctl-001 inserts a <soh>
|
||
if (Fl::event_state() & FL_CTRL && (key >= FL_KP) && (key <= FL_KP + '9'))
|
||
return handle_key_ascii(key);
|
||
|
||
// restart the numeric keypad entries.
|
||
ascii_cnt = 0;
|
||
ascii_chr = 0;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/// Inserts the macro for function key \c key.
|
||
///
|
||
/// @param key An integer in the range [FL_F, FL_F_Last]
|
||
///
|
||
/// @return 1
|
||
///
|
||
int FTextTX::handle_key_macro(int key)
|
||
{
|
||
key -= FL_F + 1;
|
||
|
||
if (active_modem == fsq_modem) {
|
||
if (key == 0) fsq_repeat_last_heard();
|
||
if (key == 1) fsq_repeat_last_command();
|
||
return 1;
|
||
}
|
||
if (key > 11)
|
||
return 0;
|
||
|
||
if (progdefaults.mbar_scheme > MACRO_SINGLE_BAR_MAX) {
|
||
if (Fl::event_state(FL_SHIFT))
|
||
key += altMacros * NUMMACKEYS;
|
||
} else {
|
||
key += altMacros * NUMMACKEYS;
|
||
}
|
||
if (!(macros.text[key]).empty())
|
||
macros.execute(key);
|
||
|
||
return 1;
|
||
}
|
||
|
||
int FTextTX::handle_dnd_drag(int pos)
|
||
{
|
||
if (pos >= txpos) {
|
||
return FTextEdit::handle_dnd_drag(pos);
|
||
}
|
||
else // refuse drop inside transmitted text
|
||
return 0;
|
||
}
|
||
|
||
/// Handles mouse-3 clicks by displaying the context menu
|
||
///
|
||
/// @param val
|
||
///
|
||
void FTextTX::handle_context_menu(void)
|
||
{
|
||
// adjust Abort/Transmit/Receive menu items
|
||
switch (trx_state) {
|
||
case STATE_TX:
|
||
menu[TX_MENU_TX].hide();
|
||
menu[TX_MENU_RX].show();
|
||
menu[TX_MENU_ABORT].show();
|
||
break;
|
||
case STATE_TUNE:
|
||
menu[TX_MENU_TX].hide();
|
||
menu[TX_MENU_RX].show();
|
||
menu[TX_MENU_ABORT].hide();
|
||
break;
|
||
default:
|
||
menu[TX_MENU_TX].show();
|
||
menu[TX_MENU_RX].hide();
|
||
menu[TX_MENU_ABORT].hide();
|
||
break;
|
||
}
|
||
|
||
bool modify_text_ok = insert_position() >= txpos;
|
||
bool selected = tbuf->selected();
|
||
icons::set_active(&menu[TX_MENU_MFSK16_IMG], active_modem->get_cap() & modem::CAP_IMG);
|
||
icons::set_active(&menu[TX_MENU_CLEAR], tbuf->length());
|
||
icons::set_active(&menu[TX_MENU_CUT], selected && modify_text_ok);
|
||
icons::set_active(&menu[TX_MENU_COPY], selected);
|
||
icons::set_active(&menu[TX_MENU_PASTE], modify_text_ok);
|
||
icons::set_active(&menu[TX_MENU_READ], modify_text_ok);
|
||
|
||
if (wrap)
|
||
menu[TX_MENU_WRAP].set();
|
||
else
|
||
menu[TX_MENU_WRAP].clear();
|
||
|
||
show_context_menu();
|
||
}
|
||
|
||
/// The context menu handler
|
||
///
|
||
/// @param val
|
||
///
|
||
void FTextTX::menu_cb(size_t item)
|
||
{
|
||
switch (item) {
|
||
case TX_MENU_TX:
|
||
active_modem->set_stopflag(false);
|
||
start_tx();
|
||
break;
|
||
case TX_MENU_ABORT:
|
||
char panic[200];
|
||
snprintf(panic, sizeof(panic), "*** Don't panic *** %s", progdefaults.myName.c_str());
|
||
put_status(panic, 5.0);
|
||
abort_tx();
|
||
break;
|
||
case TX_MENU_RX:
|
||
if (trx_state == STATE_TX) {
|
||
insert_position(tbuf->length());
|
||
add("^r", CTRL);
|
||
}
|
||
else
|
||
abort_tx();
|
||
break;
|
||
case TX_MENU_MFSK16_IMG:
|
||
{
|
||
trx_mode md = active_modem->get_mode();
|
||
if (md == MODE_IFKP)
|
||
ifkp_showTxViewer();
|
||
else if (md >= MODE_THOR_FIRST && md <= MODE_THOR_LAST)
|
||
thor_showTxViewer();
|
||
else
|
||
showTxViewer(0, 0);
|
||
break;
|
||
}
|
||
case TX_MENU_CLEAR:
|
||
clear();
|
||
break;
|
||
case TX_MENU_CUT:
|
||
kf_cut(0, this);
|
||
break;
|
||
case TX_MENU_COPY:
|
||
kf_copy(0, this);
|
||
break;
|
||
case TX_MENU_PASTE:
|
||
kf_paste(0, this);
|
||
break;
|
||
case TX_MENU_READ: {
|
||
restore_wrap = wrap;
|
||
set_word_wrap(false);
|
||
readFile();
|
||
break;
|
||
}
|
||
case TX_MENU_WRAP:
|
||
set_word_wrap(!wrap, true);
|
||
break;
|
||
default:
|
||
if (FTextTX::menu[item].flags == 0) // not an FL_SUB_MENU
|
||
add(FTextTX::menu[item].text[0]); add(FTextTX::menu[item].text[1]);
|
||
}
|
||
}
|
||
|
||
/// Overrides some useful Fl_Text_Edit keybindings that we want to keep working,
|
||
/// provided that they don't try to change chunks of transmitted text.
|
||
///
|
||
void FTextTX::change_keybindings(void)
|
||
{
|
||
struct {
|
||
Fl_Text_Editor_mod::Key_Func function, override;
|
||
} fbind[] = {
|
||
{ Fl_Text_Editor_mod::kf_default, FTextTX::kf_default },
|
||
{ Fl_Text_Editor_mod::kf_enter, FTextTX::kf_enter },
|
||
{ Fl_Text_Editor_mod::kf_delete, FTextTX::kf_delete },
|
||
{ Fl_Text_Editor_mod::kf_cut, FTextTX::kf_cut },
|
||
{ Fl_Text_Editor_mod::kf_paste, FTextTX::kf_paste }
|
||
};
|
||
int n = sizeof(fbind) / sizeof(fbind[0]);
|
||
|
||
// walk the keybindings linked list and replace items containing
|
||
// functions for which we have an override in fbind
|
||
for (Fl_Text_Editor_mod::Key_Binding *k = key_bindings; k; k = k->next) {
|
||
for (int i = 0; i < n; i++)
|
||
if (fbind[i].function == k->function)
|
||
k->function = fbind[i].override;
|
||
}
|
||
}
|
||
|
||
// The kf_* functions below call the corresponding Fl_Text_Editor_mod routines, but
|
||
// may make adjustments so that no transmitted text is modified.
|
||
|
||
int FTextTX::kf_default(int c, Fl_Text_Editor_mod* e)
|
||
{
|
||
return e->insert_position() < *ptxpos ? 1 : Fl_Text_Editor_mod::kf_default(c, e);
|
||
}
|
||
|
||
int FTextTX::kf_enter(int c, Fl_Text_Editor_mod* e)
|
||
{
|
||
return e->insert_position() < *ptxpos ? 1 : Fl_Text_Editor_mod::kf_enter(c, e);
|
||
}
|
||
|
||
int FTextTX::kf_delete(int c, Fl_Text_Editor_mod* e)
|
||
{
|
||
// single character
|
||
if (!e->buffer()->selected()) {
|
||
if (e->insert_position() >= *ptxpos &&
|
||
e->insert_position() != e->buffer()->length())
|
||
return Fl_Text_Editor_mod::kf_delete(c, e);
|
||
else
|
||
return 1;
|
||
}
|
||
|
||
// region: delete as much as we can
|
||
int start, end;
|
||
e->buffer()->selection_position(&start, &end);
|
||
if (*ptxpos >= end)
|
||
return 1;
|
||
if (*ptxpos > start)
|
||
e->buffer()->select(*ptxpos, end);
|
||
|
||
return Fl_Text_Editor_mod::kf_delete(c, e);
|
||
}
|
||
|
||
int FTextTX::kf_cut(int c, Fl_Text_Editor_mod* e)
|
||
{
|
||
if (e->buffer()->selected()) {
|
||
int start, end;
|
||
e->buffer()->selection_position(&start, &end);
|
||
if (*ptxpos >= end)
|
||
return 1;
|
||
if (*ptxpos > start)
|
||
e->buffer()->select(*ptxpos, end);
|
||
}
|
||
|
||
return Fl_Text_Editor_mod::kf_cut(c, e);
|
||
}
|
||
|
||
int FTextTX::kf_paste(int c, Fl_Text_Editor_mod* e)
|
||
{
|
||
return e->insert_position() < *ptxpos ? 1 : Fl_Text_Editor_mod::kf_paste(c, e);
|
||
}
|
||
|
||
// ----------------------------------------------------------------------------
|
||
|
||
|
||
void MVScrollbar::draw(void)
|
||
{
|
||
Fl_Scrollbar::draw();
|
||
|
||
if (marks.empty() || !draw_marks)
|
||
return;
|
||
|
||
assert((type() & FL_HOR_SLIDER) == 0);
|
||
|
||
// Calculate the slider knob position and height. For a vertical scrollbar,
|
||
// the scroll buttons' height is the scrollbar width and the minimum knob
|
||
// height is half that.
|
||
int H = h() - Fl::box_dh(box()) - 2 * w(); // internal height (minus buttons)
|
||
int slider_h = (int)(slider_size() * H + 0.5);
|
||
int min_h = (w() - Fl::box_dw(box())) / 2 + 1;
|
||
if (slider_h < min_h)
|
||
slider_h = min_h;
|
||
double val = (Fl_Slider::value() - minimum()) / (maximum() - minimum());
|
||
int slider_y = (int)(val * (H - slider_h) + 0.5) + w(); // relative to y()
|
||
// This would draw a green rectangle around the slider knob:
|
||
// fl_color(FL_GREEN);
|
||
// fl_rect(x(), y() + slider_y, w() - Fl::box_dw(box()), slider_h);
|
||
|
||
int x1 = x() + Fl::box_dx(box()), x2 = x1 + w() - Fl::box_dw(box()) - 1, ypos;
|
||
// Convert stored scrollbar values to vertical positions and draw
|
||
// lines inside the widget if they don't overlap with the knob area.
|
||
for (vector<mark_t>::const_iterator i = marks.begin(); i != marks.end(); ++i) {
|
||
ypos = static_cast<int>(w() + H * i->pos / maximum());
|
||
// Don't draw over slider knob
|
||
if ((ypos > slider_y && ypos < slider_y + slider_h) ||
|
||
(ypos < slider_y + slider_h && ypos > slider_y))
|
||
continue;
|
||
ypos += y();
|
||
fl_color(i->color);
|
||
fl_line(x1, ypos, x2, ypos);
|
||
}
|
||
}
|