1 // ----------------------------------------------------------------------------
2 //      FTextRXTX.cxx
3 //
4 // Copyright (C) 2007-2010
5 //              Stelios Bounanos, M0GLD
6 //
7 // Copyright (C) 2008-2010
8 //              Dave Freese, W1HKJ
9 //
10 // This file is part of fldigi.
11 //
12 // fldigi is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // fldigi is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 // ----------------------------------------------------------------------------
25 
26 #include <config.h>
27 
28 #include <string>
29 #include <cstring>
30 #include <cstdlib>
31 #include <cstdio>
32 #include <sys/stat.h>
33 #include <map>
34 #include <iostream>
35 #include <fstream>
36 #include <sstream>
37 #include <algorithm>
38 #include <iomanip>
39 
40 #include <FL/Fl_Tooltip.H>
41 
42 #include "FTextView.h"
43 #include "main.h"
44 #include "trx.h"
45 #include "macros.h"
46 #include "main.h"
47 #include "fl_digi.h"
48 
49 #include "cw.h"
50 
51 #include "fileselect.h"
52 #include "font_browser.h"
53 
54 #include "ascii.h"
55 #include "configuration.h"
56 
57 #include "qrunner.h"
58 
59 #include "mfsk.h"
60 #include "icons.h"
61 #include "globals.h"
62 #include "re.h"
63 #include "strutil.h"
64 #include "dxcc.h"
65 #include "locator.h"
66 #include "logsupport.h"
67 #include "status.h"
68 #include "gettext.h"
69 #include "arq_io.h"
70 #include "fl_digi.h"
71 #include "strutil.h"
72 
73 #include "debug.h"
74 
75 #include "contest.h"
76 #include "counties.h"
77 
78 using namespace std;
79 
80 
81 // Fl_Scrollbar wrapper to draw marks on the slider background.
82 // Currently only implemented for a vertical scrollbar.
83 class MVScrollbar : public Fl_Scrollbar
84 {
85 	struct mark_t {
86 		double pos;
87 		Fl_Color color;
mark_tMVScrollbar::mark_t88 		mark_t(double pos_, Fl_Color color_) : pos(pos_), color(color_) { }
89 	};
90 
91 public:
MVScrollbar(int X,int Y,int W,int H,const char * l=0)92 	MVScrollbar(int X, int Y, int W, int H, const char* l = 0)
93 		: Fl_Scrollbar(X, Y, W, H, l), draw_marks(false) { }
94 
95 	void draw(void);
mark(Fl_Color c)96 	void mark(Fl_Color c) { marks.push_back(mark_t(maximum() - 1.0, c)); redraw(); }
has_marks(void)97 	bool has_marks(void) { return !marks.empty(); }
show_marks(bool b)98 	void show_marks(bool b) { draw_marks = b; redraw(); }
clear(void)99 	void clear(void) { marks.clear(); redraw(); }
100 
101 private:
102 	vector<mark_t> marks;
103 	bool draw_marks;
104 };
105 
106 /*
107 		RX_MENU_QRZ_THIS, RX_MENU_CALL, RX_MENU_NAME, RX_MENU_QTH,
108 		RX_MENU_STATE, RX_MENU_COUNTY, RX_MENU_PROVINCE,
109 		RX_MENU_COUNTRY, RX_MENU_LOC,
110 		RX_MENU_RST_IN, RX_MENU_RST_OUT,
111 		RX_MENU_XCHG, RX_MENU_SERIAL,
112 		RX_MENU_CLASS, RX_MENU_SECTION,
113 
114 		RX_MENU_SS_SER, RX_MENU_SS_PRE, RX_MENU_SS_CHK, RX_MENU_SS_SEC,
115 
116 		RX_MENU_CQZONE, RX_MENU_CQSTATE,
117 		RX_MENU_1010_NR,
118 		RX_MENU_AGE,
119 
120 		RX_MENU_CHECK,
121 		RX_MENU_NAQP,
122 		RX_MENU_SCOUT,
123 		RX_MENU_TROOP,
124 		RX_MENU_POWER,
125 
126 		RX_MENU_QSOP_STATE,
127 		RX_MENU_QSOP_COUNTY,
128 		RX_MENU_QSOP_SERNO,
129 		RX_MENU_QSOP_NAME,
130 		RX_MENU_QSOP_XCHG,
131 		RX_MENU_QSOP_CAT,
132 
133 		RX_MENU_DIV,
134 
135 		RX_MENU_COPY,
136 		RX_MENU_CLEAR,
137 		RX_MENU_SELECT_ALL,
138 		RX_MENU_SAVE,
139 		RX_MENU_WRAP,
140 
141 		RX_MENU_ALL_ENTRY,
142 
143 		RX_MENU_SCROLL_HINTS,
144 
145 		RX_MENU_NUM_ITEMS
146 */
147 
148 Fl_Menu_Item FTextRX::menu[] = {
149 	{ icons::make_icon_label(_("Look up call"), net_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
150 	{ icons::make_icon_label(_("Call"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
151 	{ icons::make_icon_label(_("Name"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
152 	{ icons::make_icon_label(_("QTH"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
153 	{ icons::make_icon_label(_("State"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
154 	{ icons::make_icon_label(_("County"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
155 	{ icons::make_icon_label(_("Province"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
156 	{ icons::make_icon_label(_("Country"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
157 	{ icons::make_icon_label(_("Locator"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
158 	{ icons::make_icon_label(_("RST(r)"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
159 	{ icons::make_icon_label(_("RST(s)"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
160 	{ icons::make_icon_label(_("Exchange In"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
161 	{ icons::make_icon_label(_("Rx Serial #"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
162 	{ icons::make_icon_label(_("Class"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
163 	{ icons::make_icon_label(_("Section"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
164 
165 	{ icons::make_icon_label(_("SS ser #"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
166 	{ icons::make_icon_label(_("SS prec"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
167 	{ icons::make_icon_label(_("SS check"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
168 	{ icons::make_icon_label(_("SS section"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
169 
170 	{ icons::make_icon_label(_("CQ zone"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
171 	{ icons::make_icon_label(_("CQ STATE"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
172 
173 	{ icons::make_icon_label(_("1010 Nr"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
174 
175 	{ icons::make_icon_label(_("Kid's Age"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
176 
177 	{ icons::make_icon_label(_("Round Up Chk"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
178 	{ icons::make_icon_label(_("NAQP xchg"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
179 	{ icons::make_icon_label(_("JOTA scout"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
180 	{ icons::make_icon_label(_("JOTA troop"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
181 	{ icons::make_icon_label(_("POWER(r)"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
182 
183 	{ icons::make_icon_label(_("QSOp state"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
184 	{ icons::make_icon_label(_("QSOp county"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
185 	{ icons::make_icon_label(_("QSOp serno"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
186 	{ icons::make_icon_label(_("QSOp name"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
187 	{ icons::make_icon_label(_("QSOp xchg"), enter_key_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
188 	{ icons::make_icon_label(_("QSOp category"), enter_key_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
189 
190 	{ icons::make_icon_label(_("Insert marker"), insert_link_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
191 
192 	{ 0 }, // VIEW_MENU_COPY
193 	{ 0 }, // VIEW_MENU_CLEAR
194 	{ 0 }, // VIEW_MENU_SELECT_ALL
195 	{ 0 }, // VIEW_MENU_SAVE
196 	{ 0 }, // VIEW_MENU_WRAP
197 
198 	{ _("All entries"),      0, 0, 0, FL_MENU_TOGGLE, FL_NORMAL_LABEL },
199 
200 	{ _("Scroll hints"),      0, 0, 0, FL_MENU_TOGGLE, FL_NORMAL_LABEL },
201 	{ 0 }
202 };
203 
204 /// FTextRX constructor.
205 /// We remove \c Fl_Text_Display_mod::buffer_modified_cb from the list of callbacks
206 /// because we want to scroll depending on the visibility of the last line; @see
207 /// changed_cb.
208 /// @param x
209 /// @param y
210 /// @param w
211 /// @param h
212 /// @param l
FTextRX(int x,int y,int w,int h,const char * l)213 FTextRX::FTextRX(int x, int y, int w, int h, const char *l)
214         : FTextView(x, y, w, h, l)
215 {
216 	memcpy(menu + RX_MENU_COPY, FTextView::menu, (FTextView::menu->size() - 1) * sizeof(*FTextView::menu));
217 	context_menu = menu;
218 	init_context_menu();
219 	menu[RX_MENU_ALL_ENTRY].clear();
220 	menu[RX_MENU_SCROLL_HINTS].clear();
221 	menu[RX_MENU_WRAP].hide();
222 	// Replace the scrollbar widget
223 	MVScrollbar* mvsb = new MVScrollbar(mVScrollBar->x(), mVScrollBar->y(),
224 					    mVScrollBar->w(), mVScrollBar->h(), NULL);
225 	mvsb->show_marks(false);
226 	mvsb->callback(mVScrollBar->callback(), mVScrollBar->user_data());
227 	remove(mVScrollBar);
228 	delete mVScrollBar;
229 	Fl_Group::add(mVScrollBar = mvsb);
230 	mFastDisplay = 1;
231 	num_words = 1;
232 }
233 
~FTextRX()234 FTextRX::~FTextRX()
235 {
236 }
237 
238 /// Handles fltk events for this widget.
239 
240 /// We only care about mouse presses (to display the popup menu and prevent
241 /// pasting) and keyboard events (to make sure no text can be inserted).
242 /// Everything else is passed to the base class handle().
243 ///
244 /// @param event
245 ///
246 /// @return
247 ///
handle(int event)248 int FTextRX::handle(int event)
249 {
250 	static Fl_Cursor cursor;
251 
252 	switch (event) {
253 	case FL_DRAG:
254 		if (Fl::event_button() != FL_LEFT_MOUSE)
255 			return 1;
256 		break;
257 	case FL_PUSH:
258 		if (!Fl::event_inside(this))
259 			break;
260 		switch (Fl::event_button()) {
261 		case FL_LEFT_MOUSE:
262 			if (progdefaults.rxtext_clicks_qso_data) {
263 				if (handle_clickable(Fl::event_x() - x(), Fl::event_y() - y()))
264 					return 1;
265 				if (handle_qso_data(Fl::event_x() - x(), Fl::event_y() - y()))
266 					return 1;
267 			}
268 			goto out;
269 		case FL_MIDDLE_MOUSE:
270 			if (cursor != FL_CURSOR_HAND) {
271 				if (handle_qso_data(Fl::event_x() - x(), Fl::event_y() - y())) {
272 					return 1;
273 				}
274 			}
275 			goto out;
276  		case FL_RIGHT_MOUSE:
277 			handle_context_menu();
278 			return 1;
279  		default:
280  			goto out;
281  		}
282 		break;
283 	case FL_RELEASE:
284 			break;
285 	case FL_MOVE: {
286 		int p = xy_to_position(Fl::event_x(), Fl::event_y(), Fl_Text_Display_mod::CURSOR_POS);
287 		if ((unsigned char)sbuf->byte_at(p) >= CLICK_START + FTEXT_DEF) {
288 			if (cursor != FL_CURSOR_HAND)
289 				window()->cursor(cursor = FL_CURSOR_HAND);
290 			return 1;
291 		}
292 		else
293 			cursor = FL_CURSOR_INSERT;
294 		break;
295 	}
296 	// catch some text-modifying events that are not handled by kf_* functions
297 	case FL_KEYBOARD:
298 		break;
299 	case FL_PASTE:
300 		return 0;
301 	case FL_ENTER:
302 		if (!progdefaults.rxtext_tooltips || Fl_Tooltip::delay() == 0.0f)
303 			break;
304 		tooltips.enabled = Fl_Tooltip::enabled();
305 		tooltips.delay = Fl_Tooltip::delay();
306 		Fl_Tooltip::enable(1);
307 		Fl_Tooltip::delay(0.0f);
308 		Fl::add_timeout(tooltips.delay / 2.0, dxcc_tooltip, this);
309 		break;
310 	case FL_LEAVE:
311 		window()->cursor(FL_CURSOR_DEFAULT);
312 		if (!progdefaults.rxtext_tooltips || Fl_Tooltip::delay() != 0.0f)
313 			break;
314 		Fl_Tooltip::enable(tooltips.enabled);
315 		Fl_Tooltip::delay(tooltips.delay);
316 		Fl::remove_timeout(dxcc_tooltip, this);
317 		break;
318 	}
319 
320 out:
321 	return FTextView::handle(event);
322 }
323 
324 /// Adds a char to the buffer
325 ///
326 /// @param c The character
327 /// @param attr The attribute (@see enum text_attr_e); RECV if omitted.
328 ///
329 
add(unsigned int c,int attr)330 void FTextRX::add(unsigned int c, int attr)
331 {
332 	if (c == '\r')
333 		return;
334 
335 	char s[] = { '\0', '\0', char( FTEXT_DEF + attr ), '\0' };
336 	const char *cp = &s[0];
337 
338 	// The user may have moved the cursor by selecting text or
339 	// scrolling. Place it at the end of the buffer.
340 	if (mCursorPos != tbuf->length())
341 		insert_position(tbuf->length());
342 
343 	switch (c) {
344 	case '\b':
345 		// we don't call kf_backspace because it kills selected text
346 		if (s_text.length()) {
347 			int character_start = tbuf->utf8_align(tbuf->length() - 1);
348 			int character_length = fl_utf8len1(tbuf->byte_at(character_start));
349 
350 			tbuf->remove(character_start, tbuf->length());
351 			sbuf->remove(character_start, sbuf->length());
352 			s_text.resize(s_text.length() - character_length);
353 			s_style.resize(s_style.length() - character_length);
354 		}
355 		break;
356 	case '\n':
357 		// maintain the scrollback limit, if we have one
358 		if (max_lines > 0 && tbuf->count_lines(0, tbuf->length()) >= max_lines) {
359 			int le = tbuf->line_end(0) + 1; // plus 1 for the newline
360 			tbuf->remove(0, le);
361 			sbuf->remove(0, le);
362 		}
363 		s_text.clear();
364 		s_style.clear();
365 		insert("\n");
366 		sbuf->append(s + 2);
367 		break;
368 	default:
369 		if ((c < ' ' || c == 127) && attr != CTRL) // look it up
370 			cp = ascii[(unsigned char)c];
371 		else  // insert verbatim
372 			s[0] = c;
373 
374 		for (int i = 0; cp[i]; ++i) {
375 			s_text += cp[i];
376 			s_style += s[2];
377 		}
378 
379 		fl_font( textfont(), textsize() );
380 		int lwidth = (int)fl_width( s_text.c_str(), s_text.length());
381 		bool wrapped = false;
382 		if ( lwidth >= (text_area.w - mVScrollBar->w() - LEFT_MARGIN - RIGHT_MARGIN)) {
383 			if (c != ' ') {
384 				size_t p = s_text.rfind(' ');
385 				if (p != string::npos) {
386 					s_text.erase(0, p+1);
387 					s_style.erase(0, p+1);
388 					if (s_text.length() < 10) { // wrap and delete trailing space
389 						tbuf->remove(tbuf->length() - s_text.length(), tbuf->length());
390 						sbuf->remove(sbuf->length() - s_style.length(), sbuf->length());
391 						insert("\n"); // always insert new line
392 						sbuf->append(s + 2);
393 						insert(s_text.c_str());
394 						sbuf->append(s_style.c_str());
395 						wrapped = true;
396 					}
397 				}
398 			}
399 			if (!wrapped) { // add a new line if not wrapped
400 				insert("\n");
401 				sbuf->append(s + 2);
402 				s_text.clear();
403 				s_style.clear();
404 				if (c != ' ') { // add character if not a space (no leading spaces)
405 					for (int i = 0; cp[i]; ++i) {
406 						sbuf->append(s + 2);
407 						s_style.append(s + 2);
408 					}
409 					s_text.append(cp);
410 					insert(cp);
411 				}
412 			}
413 		} else {
414 			for (int i = 0; cp[i]; ++i)
415 				sbuf->append(s + 2);
416 			insert(cp);
417 		}
418 		break;
419 	}
420 
421 // test for bottom of text visibility
422 	if (// !mFastDisplay &&
423 		(mVScrollBar->value() >= mNBufferLines - mNVisibleLines + mVScrollBar->linesize() - 1))
424 		show_insert_position();
425 }
426 
set_all_entry(bool b)427 void FTextRX::set_all_entry(bool b)
428 {
429 	if (b)
430 		menu[RX_MENU_ALL_ENTRY].set();
431 	else
432 		menu[RX_MENU_ALL_ENTRY].clear();
433 }
434 
set_scroll_hints(bool b)435 void FTextRX::set_scroll_hints(bool b)
436 {
437 	if (b)
438 		menu[RX_MENU_SCROLL_HINTS].set();
439 	else
440 		menu[RX_MENU_SCROLL_HINTS].clear();
441 	static_cast<MVScrollbar*>(mVScrollBar)->show_marks(b);
442 }
443 
mark(FTextBase::TEXT_ATTR attr)444 void FTextRX::mark(FTextBase::TEXT_ATTR attr)
445 {
446 	if (attr == NATTR)
447 		attr = CLICK_START;
448 	static_cast<MVScrollbar*>(mVScrollBar)->mark(styles[attr].color);
449 }
450 
clear(void)451 void FTextRX::clear(void)
452 {
453 	FTextBase::clear();
454 	s_text.clear();
455 	s_style.clear();
456 	static_cast<MVScrollbar*>(mVScrollBar)->clear();
457 }
458 
setFont(Fl_Font f,int attr)459 void FTextRX::setFont(Fl_Font f, int attr)
460 {
461 	FTextBase::setFont(f, attr);
462 }
463 
handle_clickable(int x,int y)464 int FTextRX::handle_clickable(int x, int y)
465 {
466 	int pos;
467 	unsigned int style;
468 
469 	pos = xy_to_position(x + this->x(), y + this->y(), CURSOR_POS);
470 	// return unless clickable style
471 	if ((style = (unsigned char)sbuf->byte_at(pos)) < CLICK_START + FTEXT_DEF)
472 		return 0;
473 
474 	int start, end;
475 	for (start = pos-1; start >= 0; start--)
476 		if ((unsigned char)sbuf->byte_at(start) != style)
477 			break;
478 	start++;
479 	int len = sbuf->length();
480 	for (end = pos+1; end < len; end++)
481 		if ((unsigned char)sbuf->byte_at(end) != style)
482 			break;
483 
484 	switch (style - FTEXT_DEF) {
485 	case QSY:
486 		handle_qsy(start, end);
487 		return 1;
488 		break;
489 	// ...
490 	default:
491 		break;
492 	}
493 	return 0;
494 }
495 
handle_qsy(int start,int end)496 void FTextRX::handle_qsy(int start, int end)
497 {
498 	char* text = tbuf->text_range(start, end);
499 
500 	extern map<string, qrg_mode_t> qrg_marks;
501 	map<string, qrg_mode_t>::const_iterator i;
502 	if ((i = qrg_marks.find(text)) != qrg_marks.end()) {
503 		const qrg_mode_t& m = i->second;
504 		if (active_modem->get_mode() != m.mode)
505 			init_modem_sync(m.mode);
506 		qsy(m.rfcarrier, m.carrier);
507 	}
508 
509 	free(text);
510 }
511 
512 static fre_t rst("^[1-5][123456789nN]{2}$", REG_EXTENDED | REG_NOSUB);
513 static fre_t loc("[a-r]{2}[[:digit:]]{2}([a-x]{2})?", REG_EXTENDED | REG_ICASE);
514 static fre_t call("([[:alnum:]]?[[:alpha:]/]+[[:digit:]]+[[:alnum:]/]+)", REG_EXTENDED);
515 
set_cbo_county(string str)516 void set_cbo_county(string str)
517 {
518 	inpCounty->value(str.c_str());
519 	inpSQSO_county1->value(str.c_str());
520 	inpSQSO_county2->value(str.c_str());
521 
522 	Cstates st;
523 	if (inpState->value()[0])
524 		cboCountyQSO->value(
525 			string(st.state_short(inpState->value())).append(" ").
526 			append(st.county(inpState->value(), inpCounty->value())).c_str());
527 	else
528 		cboCountyQSO->clear_entry();
529 	cboCountyQSO->redraw();
530 }
531 
set_QSO_call(const char * s)532 void set_QSO_call(const char *s)
533 {
534 	if (progdefaults.clear_fields)
535 		clearQSO();
536 	std::string call = ucasestr(s);
537 
538 	inpCall1->value(call.c_str());
539 	inpCall2->value(call.c_str());
540 	inpCall3->value(call.c_str());
541 	inpCall4->value(call.c_str());
542 
543 	if (progStatus.timer && (Fl::event() != FL_HIDE))
544 		stopMacroTimer();
545 
546 	sDate_on = sDate_off = zdate();
547 	sTime_on = sTime_off = ztime();
548 
549 	inpTimeOn->value(inpTimeOff->value(), inpTimeOff->size());
550 	inpTimeOn1->value(inpTimeOff->value(), inpTimeOff->size());
551 	inpTimeOn2->value(inpTimeOff->value(), inpTimeOff->size());
552 	inpTimeOn3->value(inpTimeOff->value(), inpTimeOff->size());
553 	inpTimeOn4->value(inpTimeOff->value(), inpTimeOff->size());
554 	inpTimeOn5->value(inpTimeOff->value(), inpTimeOff->size());
555 
556 	updateOutSerNo();
557 }
558 
set_cbo_Country(std::string c)559 void set_cbo_Country(std::string c)
560 {
561 	cboCountryQSO->value(c.c_str());
562 	cboCountryAICW2->value(c.c_str());
563 	cboCountryAIDX2->value(c.c_str());
564 	cboCountryCQDX2->value(c.c_str());
565 	cboCountryCQ2->value(c.c_str());
566 	cboCountryIARI2->value(c.c_str());
567 	cboCountryRTU2->value(c.c_str());
568 //	cboCountryWAE2->value(c.c_str());
569 
570 	if (progdefaults.logging == LOG_JOTA)
571 		inp_JOTA_spc->value(c.c_str());
572 	if (progdefaults.logging == LOG_ARR)
573 		inpXchgIn->value(c.c_str());
574 
575 }
576 
set_zone(std::string z)577 void set_zone(std::string z)
578 {
579 	inp_CQDXzone1->value(z.c_str());
580 	inp_CQDXzone2->value(z.c_str());
581 	inp_CQzone1->value(z.c_str());
582 	inp_CQzone2->value(z.c_str());
583 }
584 
set_name(std::string nm)585 void set_name(std::string nm)
586 {
587 	inpName->value(nm.c_str());
588 	inpName1->value(nm.c_str());
589 	inpName2->value(nm.c_str());
590 	inp_1010_name2->value(nm.c_str());
591 	inp_ARR_Name2->value(nm.c_str());
592 	inpNAQPname2->value(nm.c_str());
593 	inp_ASCR_name2->value(nm.c_str());
594 }
595 
set_rst_in(std::string rst)596 void set_rst_in(std::string rst)
597 {
598 	for (size_t n = 0; n < rst.length(); n++)
599 		if (rst[n] == 'N' || rst[n] == 'n') rst[n] = '9';
600 	inpRstIn->value(rst.c_str());
601 	inpRTU_RSTin2->value(rst.c_str());
602 	inpRstIn1->value(rst.c_str());
603 	inpRstIn2->value(rst.c_str());
604 	inpRstIn3->value(rst.c_str());
605 	inpRstIn4->value(rst.c_str());
606 	inpRstIn_AICW2->value(rst.c_str());
607 	inpRstIn_SQSO2->value(rst.c_str());
608 	inpRstIn_WPX2->value(rst.c_str());
609 	inp_IARI_RSTin2->value(rst.c_str());
610 //	inpRstIn_WAE2->value(rst.c_str());
611 }
612 
set_rst_out(std::string rst)613 void set_rst_out(std::string rst)
614 {
615 	for (size_t n = 0; n < rst.length(); n++)
616 		if (rst[n] == 'N' || rst[n] == 'n') rst[n] = '9';
617 	inpRstOut->value(rst.c_str());
618 	inpRstOut1->value(rst.c_str());
619 	inpRstOut2->value(rst.c_str());
620 	inpRstOut3->value(rst.c_str());
621 	inpRstOut4->value(rst.c_str());
622 	inpRstOut_AICW2->value(rst.c_str());
623 	inpRstOut_SQSO2->value(rst.c_str());
624 	inpRstOut_WPX2->value(rst.c_str());
625 	inp_IARI_RSTout2->value(rst.c_str());
626 //	inpRstOut_WAE2->value(rst.c_str());
627 }
628 
set_rst(std::string rst)629 void set_rst(std::string rst)
630 {
631 	if (inpRstIn->value()[0] == 0)
632 		set_rst_in(rst);
633 	else
634 		set_rst_out(rst);
635 }
636 
set_state(std::string s)637 void set_state(std::string s)
638 {
639 	s = ucasestr(s);
640 	inpState->value(s.c_str());
641 	inpState1->value(s.c_str());
642 	inp_CQstate1->value(s.c_str());
643 	inp_CQstate2->value(s.c_str());
644 	inp_KD_state1->value(s.c_str());
645 	inp_KD_state2->value(s.c_str());
646 	inpSQSO_state1->value(s.c_str());
647 	inpSQSO_state2->value(s.c_str());
648 }
649 
set_province(std::string pr)650 void set_province(std::string pr)
651 {
652 	pr = ucasestr(pr);
653 	inpVEprov->value(pr.c_str());
654 	inp_KD_VEprov1->value(pr.c_str());
655 	inp_KD_VEprov2->value(pr.c_str());
656 }
657 
set_serno_in(std::string s)658 void set_serno_in(std::string s)
659 {
660 	inpSerNo->value(s.c_str());
661 	inpSerNo1->value(s.c_str());
662 	inpSerNo2->value(s.c_str());
663 	inpSerNo3->value(s.c_str());
664 	inpSerNo4->value(s.c_str());
665 	inpSerNo_WPX1->value(s.c_str());
666 	inpSerNo_WPX2->value(s.c_str());
667 	inpRTU_serno1->value(s.c_str());
668 	inpRTU_serno2->value(s.c_str());
669 	inpSQSO_serno1->value(s.c_str());
670 	inpSQSO_serno2->value(s.c_str());
671 	inp_IARI_SerNo1->value(s.c_str());
672 	inp_IARI_SerNo2->value(s.c_str());
673 //	inpSerNo_WAE1->value(s.c_str());
674 //	inpSerNo_WAE2->value(s.c_str());
675 }
676 
parseSQSO(std::string str)677 void parseSQSO(std::string str)
678 {
679 	if (std::string(QSOparties.qso_parties[progdefaults.SQSOcontest].state) == "7QP" &&
680 		str.length() == 5 && !inpState->value()[0]) {
681 		set_state(str.substr(0,2).c_str());
682 		set_cbo_county(str);
683 		return;
684 	}
685 
686 	if (std::string(QSOparties.qso_parties[progdefaults.SQSOcontest].state) == "6NE" &&
687 		str.length() == 5 && !inpState->value()[0]) {
688 		set_state(str.substr(str.length() - 2, 2).c_str());
689 		set_cbo_county(str);
690 		return;
691 	}
692 
693 	if (progdefaults.SQSOinstate) {
694 		if (state_test(str)) {
695 			set_state(str);
696 			return;
697 		}
698 	}
699 
700 	std::string st = inpState->value();
701 	std::string inState = QSOparties.qso_parties[progdefaults.SQSOcontest].state;
702 
703 	if (st == "6NE" || st == "7QP") {
704 		st.clear();
705 	} else if (st.empty())
706 		st = inState;
707 
708 	bool chkC = check_field(str, cCNTY, st);
709 	bool chkP = check_field(str, cDIST, st);
710 	bool chkCin = check_field(str, cCNTY, inState);
711 	bool chkPin = check_field(str, cDIST, inState);
712 
713 	if (	QSOparties.qso_parties[progdefaults.SQSOcontest].st &&
714 			!st.empty() &&
715 			progdefaults.SQSOlogcounty &&
716 			(chkC || chkP) ) {
717 		if (progdefaults.SQSOinstate && !inpState->value()[0])
718 			set_state(st);
719 		set_cbo_county(states.cnty_short(st, str));
720 		return;
721 	}
722 
723 	if ((chkCin || chkPin) && inpCounty->value()[0] == 0) {
724 		set_state(st.c_str());
725 		set_cbo_county(states.cnty_short(st, str));
726 		return;
727 	}
728 
729 	if (progdefaults.SQSOlogstate &&
730 		check_field(str, cSTATE) && !inpState->value()[0]) {
731 		set_state(str);
732 		return;
733 	}
734 
735 	if (progdefaults.SQSOlogstate &&
736 		check_field(str, cVE_PROV) && !inpState->value()[0]) {
737 		set_state(str);
738 		return;
739 	}
740 
741 	if (section_test(str) && !inpState->value()[0]) {
742 		set_state(str);
743 		return;
744 	}
745 
746 	if (check_field(str, cCOUNTRY)) {
747 		cboCountry->value(country_match.c_str());
748 		return;
749 	}
750 
751 	if (progdefaults.SQSOlogserno && check_field(str, cNUMERIC) && !inpSerNo->value()[0]) {
752 		set_serno_in(str);
753 		return;
754 	}
755 
756 	{
757 		bool bCAT = (QSOparties.qso_parties[progdefaults.SQSOcontest].cat[0]);
758 		string category = ucasestr(str);
759 		if (bCAT &&
760 			(category == "CLB" || category == "MOB" || category == "QRP" || category == "STD")) {
761 			inpSQSO_category->value(category.c_str());
762 			return;
763 		}
764 	}
765 
766 	if (!inpName->value()[0] && !isdigit(str[0])
767 		&& !chkC  && !chkP && !chkCin && !chkPin) {
768 		set_name(str);
769 		return;
770 	}
771 
772 	if (check_field(str, cRST) ) {
773 		set_rst(str);
774 	}
775 
776 }
777 
778 // capture 1, 2, or 3 sequential words from RX text
779 // 1 - left click on word
780 // 2 - shift-left click on first word
781 // 3 - ctrl-left click on first word
782 // 4 - shift-ctrl-left click on first word
783 
handle_qso_data(int start,int end)784 int FTextRX::handle_qso_data(int start, int end)
785 {
786 	if (start < 0 || end < 0) return 0;
787 
788 	num_words = 1;
789 
790 	if (Fl::event_state() & FL_SHIFT) {
791 		num_words = 2;
792 	}
793 	if (Fl::event_state() & FL_CTRL) {
794 		num_words = 3;
795 		if (Fl::event_state() & FL_SHIFT) {
796 			num_words = 4;
797 		}
798 	}
799 
800 	char *sz = get_word(start, end, progdefaults.nonwordchars.c_str(), num_words);
801 
802 	if (!sz)
803 		return 0;
804 
805 	std::string sz_str = sz;
806 	free(sz);
807 
808 	if (sz_str.empty())
809 		return 0;
810 
811 	while (sz_str[sz_str.length() -1] <= ' ') sz_str.erase(sz_str.length() - 1);
812 
813 // remove leading substrings such as 'loc:' 'qth:' 'op:' etc
814 	size_t sp = std::string::npos;
815 	if ((sp = sz_str.find(":")) != std::string::npos)
816 		sz_str.erase(0, sp+1);
817 
818 	if (sz_str.empty())
819 		return 0;
820 
821 	char* s = (char *)sz_str.c_str();
822 	char* p = (char *)sz_str.c_str();
823 
824 	if (progdefaults.logging != LOG_QSO) {
825 		if (loc.match(s)) { // force maidenhead match to exchange
826 							// or it will overwrite the call
827 			inpXchgIn->position(inpXchgIn->size());
828 			if (inpXchgIn->size()) inpXchgIn->insert(" ", 1);
829 			if (progdefaults.logging == LOG_VHF) {
830 				inpLoc->value(s);
831 				DupCheck();
832 			} else {
833 				inpXchgIn->insert(s);
834 				log_callback(inpXchgIn);
835 				DupCheck();
836 			}
837 		} else if (call.match(s)) { // point p to substring
838 			const regmatch_t& offsets = call.suboff()[1];
839 			p = s + offsets.rm_so;
840 			*(s + offsets.rm_eo) = '\0';
841 
842 			set_QSO_call(p);
843 
844 			Fl::copy(p, strlen(p), 1);  // copy to clipboard
845 
846 			if (std::string(QSOparties.qso_parties[progdefaults.SQSOcontest].state) == "6NE") {
847 				set_state("");
848 			}
849 
850 			const dxcc *e = dxcc_lookup(p);
851 			if (e) {
852 				std::ostringstream zone;
853 				zone << e->cq_zone;
854 				set_zone(zone.str());
855 
856 				std::string cntry = e->country;
857 				if (cntry.find("United States") != std::string::npos)
858 					cntry = "USA";
859 				set_cbo_Country(cntry);
860 			}
861 			DupCheck();
862 
863 		} else {
864 			std::string str = ucasestr(s);
865 			if (cut_numeric_test(str)) str = cut_to_numeric(str);
866 
867 			switch (progdefaults.logging) {
868 			case LOG_FD:
869 				if (check_field(str, cFD_SECTION) && !inpSection->value()[0]) {
870 					inpSection->value(str.c_str());
871 					break;
872 				}
873 				if (check_field(str, cFD_CLASS) && !inpClass->value()[0]) {
874 					inpClass->value(str.c_str());
875 					break;
876 				}
877 				if (check_field(str, cRST)) {
878 					set_rst(str);
879 					break;
880 				}
881 				break;
882 			case LOG_WFD:
883 				if (check_field(str, cFD_SECTION) && !inpSection->value()[0]) {
884 					inpSection->value(str.c_str());
885 					break;
886 				}
887 				if (check_field(str, cWFD_CLASS) && !inpClass->value()[0]) {
888 					inpClass->value(str.c_str());
889 					break;
890 				}
891 				if (check_field(str, cRST)) {
892 					set_rst(str);
893 					break;
894 				}
895 				break;
896 			case LOG_CQWW_DX:
897 				if (check_field(str, cCOUNTRY)) {
898 					set_cbo_Country(country_match);
899 					break;
900 				}
901 				if (check_field(str, cNUMERIC) && !inp_CQzone->value()[0]) {
902 					set_zone(str);
903 					break;
904 				}
905 				if (check_field(str, cRST)) {
906 					if (!inpRstIn->value()[0])
907 						set_rst_in(str);
908 					else if (!inpRstOut->value()[0])
909 						set_rst_out(str);
910 				}
911 				break;
912 			case LOG_CQWW_RTTY :
913 				if ( (check_field(str, cSTATE) || check_field(str, cVE_PROV)) &&
914 					!inp_CQstate->value()[0] ) {
915 					inp_CQstate->value(str.c_str());
916 					break;
917 				}
918 				if (check_field(str, cCOUNTRY)) {
919 					set_cbo_Country(country_match);
920 					break;
921 				}
922 				if (check_field(str, cNUMERIC) && !inp_CQzone->value()[0]) {
923 					set_zone(str);
924 					break;
925 				}
926 				if (check_field(str, cRST)) {
927 					set_rst(str);
928 					break;
929 				}
930 				break;
931 			case LOG_KD:
932 				if (!check_field(str, cNUMERIC) && inpName->value()[0] == 0) {
933 					set_name(s);
934 					break;
935 				}
936 				if (!check_field(str, cRST) &&
937 					check_field(str, cNUMERIC) &&
938 					inp_KD_age->value()[0] == 0) {
939 					inp_KD_age->value(str.c_str());
940 					break;
941 				}
942 				if (check_field(str, cSTATE) && !inpState->value()[0]) {
943 					set_state(str);
944 					break;
945 				}
946 				if (check_field(str, cVE_PROV) && !inpVEprov->value()[0]) {
947 					set_province(str);
948 					break;
949 				}
950 				if (check_field(str, cRST)) {
951 					set_rst(str);
952 					break;
953 				}
954 				if (!inpXchgIn->value()[0]) {
955 					inpXchgIn->position(inpXchgIn->size());
956 					if (inpXchgIn->size())
957 						inpXchgIn->insert(" ", 1);
958 					inpXchgIn->insert(str.c_str());
959 				}
960 				break;
961 			case LOG_ASCR:
962 				if (check_field(str, cASCR_CLASS) && !inpClass->value()[0]) {
963 					inpClass->value(str.c_str());
964 					break;
965 				}
966 				if (check_field(str, cSTATE) && !inpXchgIn->value()[0]) {
967 					inpXchgIn->value(str.c_str());
968 					break;
969 				}
970 				if (check_field(str, cVE_PROV) && !inpXchgIn->value()[0]) {
971 					inpXchgIn->value(str.c_str());
972 					break;
973 				}
974 				if (check_field(str, cRST)) {
975 					set_rst(str);
976 					break;
977 				}
978 				if (!inpName->value()[0]) {
979 					set_name(s);
980 					break;
981 				}
982 				inpXchgIn->value(s);
983 				break;
984 			case LOG_ARR:						// rookie roundup
985 				if (check_field(str, cRST)) {
986 					set_rst(str);
987 					break;
988 				}
989 				if (check_field(s, cROOKIE) && !inp_ARR_check->value()[0]) {
990 					if (strlen(s) > 2)
991 						inp_ARR_check->value(s + 2);
992 					else
993 						inp_ARR_check->value(s);
994 					break;
995 				}
996 				if (!inpName->value()[0]) {
997 					set_name(s);
998 					break;
999 				}
1000 				if (check_field(str, cCHECK) && !inpXchgIn->value()[0]) {
1001 					inpXchgIn->value(str.c_str());
1002 					break;
1003 				}
1004 				if (!inpXchgIn->value()[0]) {
1005 					inpXchgIn->value(s);
1006 					break;
1007 				}
1008 				break;
1009 			case LOG_AICW:
1010 				if (check_field(str, cCOUNTRY)) {
1011 					set_cbo_Country(country_match);
1012 					break;
1013 				}
1014 				if (check_field(str, cNUMERIC) && !inpSPCnum->value()[0]) {
1015 					inpSPCnum->value(str.c_str());
1016 					break;
1017 				}
1018 				if (check_field(str, cRST)) {
1019 					set_rst(str);
1020 					break;
1021 				}
1022 				break;
1023 			case LOG_1010:
1024 				if (check_field(str, c1010) && !inp_1010_nr->value()[0]) {
1025 					inp_1010_nr->value(str.c_str());
1026 					break;
1027 				}
1028 				if (check_field(str, cRST)) {
1029 					set_rst(str);
1030 					break;
1031 				}
1032 				if (check_field(str, cSTATE) && !inpXchgIn->value()[0]) {
1033 					inpXchgIn->value(str.c_str());
1034 					break;
1035 				}
1036 				if (check_field(str, cVE_PROV) && !inpXchgIn->value()[0]) {
1037 					inpXchgIn->value(str.c_str());
1038 					break;
1039 				}
1040 				if (!inpName->value()[0]) {
1041 					set_name(s);
1042 					break;
1043 				}
1044 				inpXchgIn->value(s);
1045 				break;
1046 			case LOG_NAQP:
1047 				if (!inpName->value()[0]) {
1048 					set_name(s);
1049 					break;
1050 				} else if (!inpSPCnum->value()[0]) {
1051 					inpSPCnum_NAQP1->value(s);
1052 					inpSPCnum_NAQP2->value(s);
1053 					inpXchgIn1->value(s);
1054 					inpXchgIn2->value(s);
1055 					break;
1056 				}
1057 				if (check_field(str, cRST)) {
1058 					set_rst(str);
1059 					break;
1060 				}
1061 				break;
1062 			case LOG_CWSS:
1063 				if (check_field(str, cSS_SEC) && !inp_SS_Section->value()[0]) {
1064 					inp_SS_Section->value(str.c_str());
1065 					break;
1066 				}
1067 				if (cut_numeric_test(str) && !inpSerNo->value()[0]) {
1068 					set_serno_in(str);
1069 					break;
1070 				}
1071 				if (check_field(str, cSS_PREC) && !inp_SS_Precedence->value()[0]) {
1072 					inp_SS_Precedence->value(s);
1073 					break;
1074 				}
1075 				if (check_field(str, cSS_CHK) && !inp_SS_Check->value()[0]) {
1076 					inp_SS_Check->value(str.c_str());
1077 					break;
1078 				}
1079 				if (check_field(str, cRST)) {
1080 					set_rst(str);
1081 					break;
1082 				}
1083 				break;
1084 			case LOG_CQ_WPX:
1085 				if (cut_numeric_test(str) && !inpSerNo->value()[0]) {
1086 					set_serno_in(str);
1087 					break;
1088 				}
1089 				if (check_field(str, cCOUNTRY)) {
1090 					set_cbo_Country(country_match);
1091 					break;
1092 				}
1093 				if (check_field(str, cRST)) {
1094 					set_rst(str);
1095 					break;
1096 				}
1097 				break;
1098 			case LOG_RTTY: // ARRL RTTY Round Up
1099 				if (check_field(str, cSTATE) || check_field(str, cVE_PROV)) {
1100 					set_state(str);
1101 					break;
1102 				}
1103 				if (check_field(str, cCOUNTRY)) {
1104 					set_cbo_Country(country_match);
1105 					break;
1106 				}
1107 				if (check_field(str, cRST)) {
1108 					set_rst(str);
1109 					break;
1110 				}
1111 				if (cut_numeric_test(str) && !inpSerNo->value()[0]) {
1112 					set_serno_in(str);
1113 					break;
1114 				}
1115 				break;
1116 			case LOG_IARI:
1117 				if (check_field(str, cITALIAN)) {
1118 					inp_IARI_PR1->value(ucasestr(str).c_str());
1119 					inp_IARI_PR2->value(ucasestr(str).c_str());
1120 					break;
1121 				}
1122 				if (check_field(str, cCOUNTRY)) {
1123 					set_cbo_Country(country_match);
1124 					break;
1125 				}
1126 				if (check_field(str, cRST)) {
1127 					set_rst(str);
1128 					break;
1129 				}
1130 				if (cut_numeric_test(str) && !inpSerNo->value()[0]) {
1131 					set_serno_in(str);
1132 					break;
1133 				}
1134 				break;
1135 			case LOG_NAS:
1136 				if (cut_numeric_test(str) && !inpSerNo->value()[0]) {
1137 					set_serno_in(str);
1138 					break;
1139 				}
1140 				if (check_field(str, cSTATE) && !inpXchgIn->value()[0]) {
1141 					inpXchgIn->value(str.c_str());
1142 					break;
1143 				}
1144 				if (check_field(str, cVE_PROV) && !inpXchgIn->value()[0]) {
1145 					inpXchgIn->value(str.c_str());
1146 					break;
1147 				}
1148 				if (check_field(str, cCOUNTRY) && !inpXchgIn->value()[0]) {
1149 					set_cbo_Country(str);
1150 					inpXchgIn->value(str.c_str());
1151 					break;
1152 				}
1153 				if (inpName->value()[0] == 0 && !inpName->value()[0]) {
1154 					set_name(str);
1155 					break;
1156 				}
1157 				if (check_field(s, cRST)) {
1158 					set_rst(s);
1159 					break;
1160 				}
1161 				break;
1162 			case LOG_AIDX:
1163 				if (check_field(str, cNUMERIC) && !inpSerNo->value()[0]) {
1164 					set_serno_in(str);
1165 					break;
1166 				}
1167 				if (check_field(str, cCOUNTRY) && !inpXchgIn->value()[0]) {
1168 					set_cbo_Country(str);
1169 					inpXchgIn->value(str.c_str());
1170 					break;
1171 				}
1172 				if (check_field(str, cRST)) {
1173 					set_rst(str);
1174 					break;
1175 				}
1176 				break;
1177 			case LOG_JOTA:
1178 				if (check_field(str, cRST)) {
1179 					set_rst(str);
1180 					break;
1181 				}
1182 				if (cut_numeric_test(str) && !inp_JOTA_troop->value()[0]) {
1183 					inp_JOTA_troop->value(str.c_str());
1184 					break;
1185 				}
1186 				if (check_field(str, cSTATE)) {
1187 					set_state(str);
1188 					break;
1189 				}
1190 				if (check_field(str, cVE_PROV)) {
1191 					set_province(str);
1192 					break;
1193 				}
1194 				if (check_field(str, cCOUNTRY)) {
1195 					set_cbo_Country(str);
1196 					inpXchgIn->value(str.c_str());
1197 					break;
1198 				}
1199 				inp_JOTA_scout->value(str.c_str());
1200 				break;
1201 //			case LOG_WAE:
1202 //				if (!inpSerNo->value()[0] && check_field(str, cNUMERIC)) {
1203 //					set_serno_in(str);
1204 //					break;
1205 //				}
1206 //				if (check_field(str, cCOUNTRY)) {
1207 //					cboCountryCQ1->value(country_match.c_str());
1208 //					cboCountryCQ2->value(country_match.c_str());
1209 //					cboCountry->value(country_match.c_str());
1210 //				}
1211 //				if (check_field(s, cRST)) {
1212 //					set_rst(s);
1213 //					break;
1214 //				}
1215 //				break;
1216 			case LOG_VHF:
1217 				if (check_field(str, cRST))
1218 					set_rst(str);
1219 				break;
1220 			case LOG_SQSO:
1221 				parseSQSO(str);
1222 				break;
1223 			case LOG_BART:
1224 			// CALL, NAME, SERIAL, EXCHANGE
1225 				if (!cut_numeric_test(s) && !inpName->value()[0]) {
1226 					set_name(p);
1227 					break;
1228 				} else if (cut_numeric_test(str) && !inpSerNo->value()[0]) {
1229 					set_serno_in(str);
1230 					break;
1231 				}
1232 			case LOG_GENERIC:
1233 			default:
1234 			// EXCHANGE
1235 				inpXchgIn->position(inpXchgIn->size());
1236 				if (inpXchgIn->size()) inpXchgIn->insert(" ", 1);
1237 				if (cut_numeric_test(str))
1238 					inpXchgIn->insert(str.c_str());
1239 				else
1240 					inpXchgIn->insert(s);
1241 				log_callback(inpXchgIn);
1242 			}
1243 		}
1244 		DupCheck();
1245 		restoreFocus(91);
1246 		return 1;
1247 	} else {
1248 		if (loc.match(s) && inpCall->value()[0]) {
1249 			inpLoc->value(p);
1250 			log_callback(inpLoc);
1251 			restoreFocus();
1252 			DupCheck();
1253 			return 1;
1254 		} else if (call.match(s)) { // point p to substring
1255 			const regmatch_t& offsets = call.suboff()[1];
1256 			p = s + offsets.rm_so;
1257 			*(s + offsets.rm_eo) = '\0';
1258 			Fl::copy(p, strlen(p), 1);  // copy to clipboard
1259 			set_QSO_call(p);
1260 			log_callback(inpCall);
1261 			restoreFocus();
1262 			return 1;
1263 		} else if (rst.match(s)) {
1264 			set_rst(s);
1265 			restoreFocus();
1266 			return 1;
1267 		} else if (!inpName->value()[0]) {
1268 			set_name(p);
1269 			restoreFocus();
1270 			return 1;
1271 		} else if (!inpQTH->value()[0]) {
1272 			inpQTH->value(p);
1273 			log_callback(inpQTH);
1274 			restoreFocus();
1275 			return 1;
1276 		} else if (!inpState->value()[0]) {
1277 			set_state(p);
1278 			log_callback(inpState);
1279 			restoreFocus();
1280 			DupCheck();
1281 			return 1;
1282 		}
1283 	}
1284 	return 0;
1285 }
1286 
handle_context_menu(void)1287 void FTextRX::handle_context_menu(void)
1288 {
1289 	bool contest_ui = (progdefaults.logging != LOG_QSO);
1290 
1291 	num_words = 1;
1292 
1293 	if (Fl::event_state() & FL_SHIFT) {
1294 		num_words = 2;
1295 	}
1296 	if (Fl::event_state() & FL_CTRL) {
1297 		num_words = 3;
1298 		if (Fl::event_state() & FL_SHIFT) {
1299 			num_words = 4;
1300 		}
1301 	}
1302 
1303 	unsigned shown[RX_MENU_NUM_ITEMS];
1304 	for (int i = 0; i < RX_MENU_NUM_ITEMS; shown[i++] = 0); // all hidden
1305 
1306 #define show_item(x_) (shown[x_] = 1)
1307 #define hide_item(x_) (shown[x_] = 0)
1308 #define test_item(x_) (shown[x_] == 1)
1309 
1310 	show_item(RX_MENU_CALL);
1311 
1312 	if (contest_ui) {
1313 		switch (progdefaults.logging) {
1314 		case LOG_FD:
1315 		case LOG_WFD:
1316 			show_item(RX_MENU_CLASS);
1317 			show_item(RX_MENU_SECTION);
1318 			break;
1319 		case LOG_CQ_WPX:
1320 			show_item(RX_MENU_RST_IN);
1321 			show_item(RX_MENU_RST_OUT);
1322 			show_item(RX_MENU_CQZONE);
1323 			show_item(RX_MENU_COUNTRY);
1324 			show_item(RX_MENU_SERIAL);
1325 			break;
1326 		case LOG_CQWW_DX:
1327 			show_item(RX_MENU_RST_IN);
1328 			show_item(RX_MENU_RST_OUT);
1329 			show_item(RX_MENU_CQZONE);
1330 			show_item(RX_MENU_COUNTRY);
1331 			break;
1332 		case LOG_CQWW_RTTY:
1333 			show_item(RX_MENU_RST_IN);
1334 			show_item(RX_MENU_RST_OUT);
1335 			show_item(RX_MENU_CQZONE);
1336 			show_item(RX_MENU_CQSTATE);
1337 			show_item(RX_MENU_COUNTRY);
1338 			break;
1339 		case LOG_ASCR:
1340 			show_item(RX_MENU_NAME);
1341 			show_item(RX_MENU_CLASS);
1342 			show_item(RX_MENU_RST_IN);
1343 			show_item(RX_MENU_RST_OUT);
1344 			show_item(RX_MENU_XCHG);
1345 			break;
1346 		case LOG_VHF:
1347 			show_item(RX_MENU_RST_IN);
1348 			show_item(RX_MENU_RST_OUT);
1349 			show_item(RX_MENU_LOC);
1350 			break;
1351 		case LOG_CWSS:
1352 			show_item(RX_MENU_SS_SER);
1353 			show_item(RX_MENU_SS_PRE);
1354 			show_item(RX_MENU_SS_CHK);
1355 			show_item(RX_MENU_SS_SEC);
1356 			show_item(RX_MENU_RST_IN);
1357 			break;
1358 		case LOG_1010:
1359 			show_item(RX_MENU_NAME);
1360 			show_item(RX_MENU_1010_NR);
1361 			show_item(RX_MENU_XCHG);
1362 			break;
1363 		case LOG_ARR:
1364 			show_item(RX_MENU_NAME);
1365 			show_item(RX_MENU_CHECK);
1366 			show_item(RX_MENU_XCHG);
1367 			break;
1368 		case LOG_AICW:
1369 			show_item(RX_MENU_RST_IN);
1370 			show_item(RX_MENU_RST_OUT);
1371 			show_item(RX_MENU_POWER);
1372 			show_item(RX_MENU_COUNTRY);
1373 			break;
1374 		case LOG_KD:
1375 			show_item(RX_MENU_RST_IN);
1376 			show_item(RX_MENU_RST_OUT);
1377 			show_item(RX_MENU_NAME);
1378 			show_item(RX_MENU_AGE);
1379 			show_item(RX_MENU_STATE);
1380 			show_item(RX_MENU_PROVINCE);
1381 			show_item(RX_MENU_XCHG);
1382 			break;
1383 		case LOG_NAS:
1384 			show_item(RX_MENU_NAME);
1385 			show_item(RX_MENU_SERIAL);
1386 			show_item(RX_MENU_STATE);
1387 			show_item(RX_MENU_PROVINCE);
1388 			show_item(RX_MENU_COUNTRY);
1389 			break;
1390 		case LOG_AIDX:
1391 			show_item(RX_MENU_SERIAL);
1392 			show_item(RX_MENU_RST_IN);
1393 			show_item(RX_MENU_RST_OUT);
1394 			show_item(RX_MENU_COUNTRY);
1395 			break;
1396 		case LOG_NAQP:
1397 			show_item(RX_MENU_NAME);
1398 			show_item(RX_MENU_NAQP);
1399 			break;
1400 		case LOG_RTTY:
1401 			show_item(RX_MENU_RST_IN);
1402 			show_item(RX_MENU_RST_OUT);
1403 			show_item(RX_MENU_STATE);
1404 			show_item(RX_MENU_COUNTRY);
1405 			show_item(RX_MENU_SERIAL);
1406 			break;
1407 		case LOG_IARI:
1408 			show_item(RX_MENU_RST_IN);
1409 			show_item(RX_MENU_RST_OUT);
1410 			show_item(RX_MENU_COUNTRY);
1411 			show_item(RX_MENU_PROVINCE);
1412 			show_item(RX_MENU_SERIAL);
1413 			break;
1414 		case LOG_JOTA:
1415 			show_item(RX_MENU_RST_IN);
1416 			show_item(RX_MENU_RST_OUT);
1417 			show_item(RX_MENU_SCOUT);
1418 			show_item(RX_MENU_TROOP);
1419 			show_item(RX_MENU_STATE);
1420 			show_item(RX_MENU_PROVINCE);
1421 			show_item(RX_MENU_COUNTRY);
1422 			break;
1423 		case LOG_SQSO:
1424 			show_item(RX_MENU_RST_IN);
1425 			show_item(RX_MENU_RST_OUT);
1426 			show_item(RX_MENU_QSOP_STATE);
1427 			show_item(RX_MENU_QSOP_COUNTY);
1428 			show_item(RX_MENU_QSOP_SERNO);
1429 			show_item(RX_MENU_QSOP_NAME);
1430 			show_item(RX_MENU_QSOP_CAT);
1431 			break;
1432 //		case LOG_WAE:
1433 //			show_item(RX_MENU_RST_IN);
1434 //			show_item(RX_MENU_RST_OUT);
1435 //			show_item(RX_MENU_SERIAL);
1436 //			show_item(RX_MENU_COUNTRY);
1437 //			break;
1438 		case LOG_BART:
1439 			show_item(RX_MENU_NAME);
1440 			show_item(RX_MENU_SERIAL);
1441 			show_item(RX_MENU_XCHG);
1442 			show_item(RX_MENU_RST_IN);
1443 			break;
1444 		case LOG_GENERIC:
1445 		default:
1446 			show_item(RX_MENU_SERIAL);
1447 			show_item(RX_MENU_XCHG);
1448 			show_item(RX_MENU_RST_IN);
1449 			break;
1450 		}
1451 	}
1452 	else {
1453 		show_item(RX_MENU_NAME);
1454 		show_item(RX_MENU_QTH);
1455 		show_item(RX_MENU_RST_IN);
1456 		show_item(RX_MENU_RST_OUT);
1457 // "Look up call" shown only in non-contest mode
1458 		if (progdefaults.QRZWEB != QRZWEBNONE || progdefaults.QRZXML != QRZXMLNONE)
1459 			show_item(RX_MENU_QRZ_THIS);
1460 		menu[RX_MENU_CALL].flags |= FL_MENU_DIVIDER;
1461 	}
1462 
1463 	if (menu[RX_MENU_ALL_ENTRY].value()) {
1464 		for (size_t i = RX_MENU_NAME; i <= RX_MENU_RST_OUT; i++)
1465 			show_item(i);
1466 		menu[RX_MENU_CALL].flags &= ~FL_MENU_DIVIDER;
1467 	}
1468 
1469 
1470 	if (static_cast<MVScrollbar*>(mVScrollBar)->has_marks())
1471 		menu[RX_MENU_SCROLL_HINTS].show();
1472 	else
1473 		menu[RX_MENU_SCROLL_HINTS].hide();
1474 
1475 	for (size_t i = RX_MENU_QRZ_THIS; i < RX_MENU_DIV; i++) {
1476 		if (test_item(i))
1477 			menu[i].show();
1478 		else
1479 			menu[i].hide();
1480 	}
1481 
1482 #undef show_item
1483 #undef hide_item
1484 #undef test_item
1485 
1486 	// availability of editing items depend on buffer state
1487 	icons::set_active(&menu[RX_MENU_COPY], tbuf->selected());
1488 	icons::set_active(&menu[RX_MENU_CLEAR], tbuf->length());
1489 	icons::set_active(&menu[RX_MENU_SELECT_ALL], tbuf->length());
1490 	icons::set_active(&menu[RX_MENU_SAVE], tbuf->length());
1491 
1492 	if (wrap)
1493 		menu[RX_MENU_WRAP].set();
1494 	else
1495 		menu[RX_MENU_WRAP].clear();
1496 
1497 	show_context_menu();
1498 }
1499 
1500 /// The context menu handler
1501 ///
1502 /// @param val
1503 ///
menu_cb(size_t item)1504 void FTextRX::menu_cb(size_t item)
1505 {
1506 	Fl_Input2* input = 0;
1507 
1508 	std::string s;
1509 	char* str = get_word(popx, popy, "", 1, false);
1510 
1511 	if (str) {
1512 		s = str;
1513 		free(str);
1514 	}
1515 
1516 // remove leading substrings such as 'loc:' 'qth:' 'op:' etc
1517 	size_t sp = std::string::npos;
1518 	if ((sp = s.find(":")) != std::string::npos)
1519 		s.erase(0, sp+1);
1520 
1521 	if (!s.empty())
1522 		while (s[s.length() -1] <= ' ') s.erase(s.length() - 1);
1523 
1524 	if (s.empty()) {
1525 		switch (item) {
1526 			case RX_MENU_CLEAR:
1527 				clear();
1528 				break;
1529 			case RX_MENU_SELECT_ALL:
1530 				tbuf->select(0, tbuf->length());
1531 				break;
1532 			case RX_MENU_SAVE:
1533 				saveFile();
1534 				break;
1535 			case RX_MENU_ALL_ENTRY:
1536 				menu[item].flags ^= FL_MENU_VALUE;
1537 				if (menu[item].value())
1538 					handle_context_menu();
1539 				break;
1540 			case RX_MENU_WRAP:
1541 				set_word_wrap(!wrap, true);
1542 				break;
1543 			case RX_MENU_DIV:
1544 				note_qrg(false, "\n", "\n");
1545 				break;
1546 			case RX_MENU_SCROLL_HINTS:
1547 				menu[item].flags ^= FL_MENU_VALUE;
1548 				static_cast<MVScrollbar*>(mVScrollBar)->show_marks(menu[item].value());
1549 				break;
1550 			default: ;
1551 		}
1552 		return;
1553 	}
1554 
1555 	switch (item) {
1556 		case RX_MENU_QRZ_THIS:
1557 			menu_cb(RX_MENU_CALL);
1558 			extern void CALLSIGNquery();
1559 			CALLSIGNquery();
1560 			break;
1561 		case RX_MENU_CALL:
1562 			input = inpCall;
1563 			break;
1564 		case RX_MENU_NAME:
1565 			input = inpName;
1566 			break;
1567 		case RX_MENU_QTH:
1568 			input = inpQTH;
1569 			break;
1570 		case RX_MENU_STATE:
1571 			if (progdefaults.logging == LOG_NAS)
1572 				input = inpXchgIn;
1573 			else if (progdefaults.logging == LOG_JOTA)
1574 				input = inp_JOTA_spc;
1575 			else
1576 				input = inpState;
1577 			break;
1578 		case RX_MENU_LOC:
1579 			input = inpLoc;
1580 			break;
1581 		case RX_MENU_RST_IN:
1582 			input = inpRstIn;
1583 			break;
1584 		case RX_MENU_RST_OUT:
1585 			input = inpRstOut;
1586 			break;
1587 		case RX_MENU_SERIAL:
1588 			if (progdefaults.logging == LOG_IARI)
1589 				input = inpXchgIn;
1590 			else
1591 				input = inpSerNo;
1592 			break;
1593 		case RX_MENU_XCHG:
1594 			input = inpXchgIn;
1595 			break;
1596 		case RX_MENU_POWER:
1597 			input = inpSPCnum;
1598 			break;
1599 		case RX_MENU_CLASS:
1600 			input = inpClass;
1601 			break;
1602 		case RX_MENU_SECTION:
1603 			input = inpSection;
1604 			break;
1605 		case RX_MENU_SS_SER:
1606 			input = inp_SS_SerialNoR;
1607 			break;
1608 		case RX_MENU_SS_PRE:
1609 			input = inp_SS_Precedence;
1610 			break;
1611 		case RX_MENU_SS_CHK:
1612 			input = inp_SS_Check;
1613 			break;
1614 		case RX_MENU_SS_SEC:
1615 			input = inp_SS_Section;
1616 			break;
1617 		case RX_MENU_CQZONE:
1618 			input = inp_CQzone;
1619 			break;
1620 		case RX_MENU_CQSTATE:
1621 			input = inp_CQstate;
1622 			break;
1623 		case RX_MENU_1010_NR:
1624 			input = inp_1010_nr;
1625 			break;
1626 		case RX_MENU_AGE:
1627 			input = inp_KD_age;
1628 			break;
1629 		case RX_MENU_CHECK:
1630 			input = inp_ARR_check;
1631 			break;
1632 		case RX_MENU_NAQP:
1633 			input = inpSPCnum;
1634 			break;
1635 		case RX_MENU_SCOUT:
1636 			input = inp_JOTA_scout;
1637 			break;
1638 		case RX_MENU_TROOP:
1639 			input = inp_JOTA_troop;
1640 			break;
1641 		case RX_MENU_QSOP_STATE:
1642 			input = inpState;
1643 			break;
1644 		case RX_MENU_QSOP_COUNTY:
1645 			input = inpCounty;
1646 			break;
1647 		case RX_MENU_QSOP_SERNO:
1648 			input = inpSerNo;
1649 			break;
1650 		case RX_MENU_QSOP_NAME:
1651 			input = inpName;
1652 			break;
1653 		case RX_MENU_QSOP_XCHG:
1654 			input = inpXchgIn;
1655 			break;
1656 		case RX_MENU_QSOP_CAT:
1657 			input = inpXchgIn;
1658 			break;
1659 		case RX_MENU_DIV:
1660 			note_qrg(false, "\n", "\n");
1661 			break;
1662 		case RX_MENU_COPY:
1663 			kf_copy(Fl::event_key(), this);
1664 			break;
1665 		case RX_MENU_CLEAR:
1666 			clear();
1667 			break;
1668 		case RX_MENU_SELECT_ALL:
1669 			tbuf->select(0, tbuf->length());
1670 			break;
1671 		case RX_MENU_SAVE:
1672 			saveFile();
1673 			break;
1674 		case RX_MENU_ALL_ENTRY:
1675 			menu[item].flags ^= FL_MENU_VALUE;
1676 			if (menu[item].value())
1677 				handle_context_menu();
1678 			break;
1679 		case RX_MENU_WRAP:
1680 			set_word_wrap(!wrap, true);
1681 			break;
1682 		case RX_MENU_SCROLL_HINTS:
1683 			menu[item].flags ^= FL_MENU_VALUE;
1684 			static_cast<MVScrollbar*>(mVScrollBar)->show_marks(menu[item].value());
1685 			break;
1686 		case RX_MENU_COUNTRY:
1687 			if (progdefaults.logging == LOG_NAS)
1688 				inpXchgIn->value(s.c_str());
1689 			else if (progdefaults.logging == LOG_JOTA)
1690 				inp_JOTA_spc->value(s.c_str());
1691 			else
1692 				cboCountry->value(s.c_str());
1693 			return;
1694 		case RX_MENU_PROVINCE:
1695 			if (progdefaults.logging == LOG_NAS)
1696 				inpXchgIn->value(s.c_str());
1697 			else if (progdefaults.logging == LOG_JOTA)
1698 				inp_JOTA_spc->value(s.c_str());
1699 			else if (progdefaults.logging == LOG_IARI)
1700 				inpXchgIn->value(s.c_str());
1701 			else
1702 				set_province(s);
1703 			return;
1704 		case RX_MENU_COUNTY:
1705 			set_cbo_county(s.c_str());
1706 			return;
1707 		default:
1708 			return;
1709 	}
1710 
1711 	restoreFocus(92);
1712 
1713 	if (!input)
1714 		return;
1715 
1716 	if (item == RX_MENU_XCHG) { // append
1717 		input->position(input->size());
1718 		if (input->size())
1719 			input->insert(" ", 1);
1720 		input->insert(s.c_str());
1721 	}
1722 	else if (item == RX_MENU_SECTION) {
1723 		if (check_field(ucasestr(s).c_str(), cFD_SECTION))
1724 			input->value(ucasestr(s).c_str());
1725 	} else if (item == RX_MENU_CLASS) {
1726 		if (check_field(ucasestr(s).c_str(), cFD_CLASS))
1727 			input->value(ucasestr(s).c_str());
1728 	} else if (item == RX_MENU_RST_IN) {
1729 		if (check_field(s, cRST)) {
1730 			set_rst_in(s);
1731 		}
1732 	} else if (item == RX_MENU_RST_OUT) {
1733 		if (check_field(s, cRST))
1734 			set_rst_out(s);
1735 	} else {
1736 		if (input == inpCounty)
1737 			set_cbo_county(s.c_str());
1738 		else {
1739 			input->value(s.c_str());
1740 			log_callback(input);
1741 		}
1742 	}
1743 }
1744 
dxcc_lookup_call(int x,int y)1745 const char* FTextRX::dxcc_lookup_call(int x, int y)
1746 {
1747 	char* s = get_word(x - this->x(), y - this->y(), progdefaults.nonwordchars.c_str());
1748 	char* mem = s;
1749 	if (!(s && *s && call.match(s))) {
1750 		free(s);
1751 		return 0;
1752 	}
1753 
1754 	double lon1, lat1, lon2 = 360.0, lat2 = 360.0, distance, azimuth;
1755 	static string tip;
1756 	ostringstream stip;
1757 	const dxcc* e = 0;
1758 	cQsoRec* qso = 0;
1759 	unsigned char qsl;
1760 
1761 	// prevent locator-only lookup if Ctrl is held
1762 	if (!(Fl::event_state() & FL_CTRL) && loc.match(s)) {
1763 		const vector<regmatch_t>& v = loc.suboff();
1764 		s += v[0].rm_so;
1765 		*(s + v[0].rm_eo) = '\0';
1766 		if (QRB::locator2longlat(&lon2, &lat2, s) != QRB::QRB_OK)
1767 			goto ret;
1768 		e = 0; qsl = 0; qso = 0;
1769 	}
1770 	else {
1771 		e = dxcc_lookup(s);
1772 		qsl = qsl_lookup(s);
1773 		qso = SearchLog(s);
1774 	}
1775 
1776 	if (qso && QRB::locator2longlat(&lon2, &lat2, qso->getField(GRIDSQUARE)) != QRB::QRB_OK)
1777 		lon2 = lat2 = 360.0;
1778 
1779 	if (e) {
1780 		// use dxcc data if we didn't have a good locator string in the log file
1781 		if (lon2 == 360.0)
1782 			lon2 = -e->longitude;
1783 		if (lat2 == 360.0)
1784 			lat2 = e->latitude;
1785 		stip << e->country << " (" << e->continent
1786 		     << " GMT" << fixed << showpos << setprecision(1) << -e->gmt_offset << noshowpos
1787 		     << ") CQ-" << e->cq_zone << " ITU-" << e->itu_zone << '\n';
1788 	}
1789 
1790 	if (QRB::locator2longlat(&lon1, &lat1, progdefaults.myLocator.c_str()) == QRB::QRB_OK &&
1791 	    QRB::qrb(lon1, lat1, lon2, lat2, &distance, &azimuth) == QRB::QRB_OK) {
1792 			if (progdefaults.us_units) {
1793 				stip << "QTE " << fixed << setprecision(0) << azimuth << '\260' << " ("
1794 					<< QRB::azimuth_long_path(azimuth) << '\260' << ")  QRB "
1795 					<< distance * 0.62168188 << "mi"<< " (" <<
1796 					QRB::distance_long_path(distance) * 0.62168188 <<
1797 					"mi)\n";
1798 			}
1799 			else {
1800 				stip << "QTE " << fixed << setprecision(0) << azimuth << '\260' << " ("
1801 					<< QRB::azimuth_long_path(azimuth) << '\260' << ")  QRB "
1802 					<< distance << "km(" <<
1803 					QRB::distance_long_path(distance) << "km)\n";
1804 			}
1805 	}
1806 
1807 	if (qso) {
1808 		const char* info[] = {
1809 			qso->getField(NAME), qso->getField(QTH), qso->getField(QSO_DATE),
1810 			qso->getField(BAND), qso->getField(ADIF_MODE)
1811 		};
1812 		// name & qth
1813 		if (*info[0])
1814 			join(stip << "* ", info, 2, _(" in "), true) << '\n';
1815 		// other info
1816 		join(stip << "* " << _("Last QSO") << ": ", info+2, 3, ", ", true) << '\n';
1817 	}
1818 	if (qsl) {
1819 		stip << "* QSL: ";
1820 		for (unsigned char i = 0; i < QSL_END; i++)
1821 			if (qsl & (1 << i))
1822 				stip << qsl_names[i] << ' ';
1823 		stip << '\n';
1824 	}
1825 
1826 ret:
1827 	free(mem);
1828 	tip = stip.str();
1829 	return tip.empty() ? 0 : tip.c_str();
1830 }
1831 
dxcc_tooltip(void * obj)1832 void FTextRX::dxcc_tooltip(void* obj)
1833 {
1834 	struct point {
1835 		int x, y;
1836 		bool operator==(const point& p) { return x == p.x && y == p.y; }
1837 		bool operator!=(const point& p) { return !(*this == p); }
1838 	};
1839 	static point p[3] = { {0, 0}, {0, 0}, {0, 0} };
1840 
1841 	memmove(p, p+1, 2 * sizeof(point));
1842 	p[2].x = Fl::event_x(); p[2].y = Fl::event_y();
1843 
1844 	static const char* tip = 0;
1845 	FTextRX* v = reinterpret_cast<FTextRX*>(obj);
1846 	// look up word under cursor if we have been called twice with the cursor
1847 	// at the same position, and if the cursor was previously somewhere else
1848 	if (p[2] == p[1] && p[2] != p[0]  &&  ((tip = v->dxcc_lookup_call(p[2].x, p[2].y))))
1849 		Fl_Tooltip::enter_area(v, p[2].x, p[2].y, 100, 100, tip);
1850 	else if (p[2] != p[1])
1851 		Fl_Tooltip::exit(v);
1852 
1853 	Fl::repeat_timeout(tip ? Fl_Tooltip::hoverdelay() : v->tooltips.delay / 2.0, dxcc_tooltip, obj);
1854 }
1855 
1856 
1857 // ----------------------------------------------------------------------------
1858 
1859 
1860 Fl_Menu_Item FTextTX::menu[] = {
1861 	{ icons::make_icon_label(_("Transmit"), tx_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
1862 	{ icons::make_icon_label(_("Receive"), rx_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
1863 	{ icons::make_icon_label(_("Abort"), process_stop_icon), 0, 0, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL },
1864 	{ icons::make_icon_label(_("Send image..."), image_icon), 0, 0, 0, 0, _FL_MULTI_LABEL },
1865 
1866 	{ 0 }, // EDIT_MENU_CUT
1867 	{ 0 }, // EDIT_MENU_COPY
1868 	{ 0 }, // EDIT_MENU_PASTE
1869 	{ 0 }, // EDIT_MENU_CLEAR
1870 	{ 0 }, // EDIT_MENU_READ
1871 	{ 0 }, // EDIT_MENU_WRAP
1872 
1873 	{ _("Spec Char"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
1874 		{ "¢ - cent", 0, 0, 0, 0, FL_NORMAL_LABEL },
1875 		{ "£ - pound", 0, 0, 0, 0, FL_NORMAL_LABEL },
1876 		{ "µ - micro", 0, 0, 0, 0, FL_NORMAL_LABEL },
1877 		{ "° - degree", 0, 0, 0, 0, FL_NORMAL_LABEL },
1878 		{ "¿ - iques", 0, 0, 0, 0, FL_NORMAL_LABEL },
1879 		{ "× - times", 0, 0, 0, 0, FL_NORMAL_LABEL },
1880 		{ "÷ - divide", 0, 0, 0, 0, FL_NORMAL_LABEL },
1881 		{ _("A"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
1882 			{ "À - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1883 			{ "à - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1884 			{ "Á - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1885 			{ "á - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1886 			{ "Â - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1887 			{ "â - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1888 			{ "Ã - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
1889 			{ "ã - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
1890 			{ "Ä - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1891 			{ "ä - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1892 			{ "Å - ring", 0, 0, 0, 0, FL_NORMAL_LABEL },
1893 			{ "å - ring", 0, 0, 0, 0, FL_NORMAL_LABEL },
1894 			{ "Æ - aelig", 0, 0, 0, 0, FL_NORMAL_LABEL },
1895 			{ "æ - aelig", 0, 0, 0, 0, FL_NORMAL_LABEL },
1896 			{0,0,0,0,0,0,0,0,0},
1897 		{ _("E"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
1898 			{ "È - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1899 			{ "è - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1900 			{ "É - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1901 			{ "é - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1902 			{ "Ê - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1903 			{ "Ê - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1904 			{ "Ë - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1905 			{ "ë - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1906 			{0,0,0,0,0,0,0,0,0},
1907 		{ _("I"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
1908 			{ "Ì - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1909 			{ "ì - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1910 			{ "Í - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1911 			{ "í - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1912 			{ "Î - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1913 			{ "î - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1914 			{ "Ï - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1915 			{ "ï - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1916 			{0,0,0,0,0,0,0,0,0},
1917 		{ _("N"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
1918 			{ "Ñ - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
1919 			{ "ñ - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
1920 			{0,0,0,0,0,0,0,0,0},
1921 		{ _("O"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
1922 			{ "Ò - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1923 			{ "ò - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1924 			{ "Ó - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1925 			{ "ó - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1926 			{ "Ô - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1927 			{ "ô - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1928 			{ "Õ - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
1929 			{ "õ - tilde", 0, 0, 0, 0, FL_NORMAL_LABEL },
1930 			{ "Ö - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1931 			{ "ö - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1932 			{ "Ø - slash", 0, 0, 0, 0, FL_NORMAL_LABEL },
1933 			{ "ø - slash", 0, 0, 0, 0, FL_NORMAL_LABEL },
1934 			{0,0,0,0,0,0,0,0,0},
1935 		{ _("U"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
1936 			{ "Ù - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1937 			{ "ù - grave", 0, 0, 0, 0, FL_NORMAL_LABEL },
1938 			{ "Ú - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1939 			{ "ú - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1940 			{ "Û - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1941 			{ "û - circ", 0, 0, 0, 0, FL_NORMAL_LABEL },
1942 			{ "Ü - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1943 			{ "ü - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1944 			{0,0,0,0,0,0,0,0,0},
1945 		{ _("Y"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
1946 			{ "Ý - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1947 			{ "ý - acute", 0, 0, 0, 0, FL_NORMAL_LABEL },
1948 			{ "ÿ - umlaut", 0, 0, 0, 0, FL_NORMAL_LABEL },
1949 			{0,0,0,0,0,0,0,0,0},
1950 		{ _("Other"), 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL },
1951 			{ "ß - szlig", 0, 0, 0, 0, FL_NORMAL_LABEL },
1952 			{ "Ç - cedil", 0, 0, 0, 0, FL_NORMAL_LABEL },
1953 			{ "ç - cedil", 0, 0, 0, 0, FL_NORMAL_LABEL },
1954 			{ "Ð - eth", 0, 0, 0, 0, FL_NORMAL_LABEL },
1955 			{ "ð - eth", 0, 0, 0, 0, FL_NORMAL_LABEL },
1956 			{ "Þ - thorn", 0, 0, 0, 0, FL_NORMAL_LABEL },
1957 			{0,0,0,0,0,0,0,0,0},
1958 		{0,0,0,0,0,0,0,0,0},
1959 	{ 0 }
1960 };
1961 
1962 // needed by our static kf functions, which may restrict editing depending on
1963 // the transmit cursor position
1964 int *FTextTX::ptxpos;
1965 
FTextTX(int x,int y,int w,int h,const char * l)1966 FTextTX::FTextTX(int x, int y, int w, int h, const char *l)
1967 	: FTextEdit(x, y, w, h, l),
1968           PauseBreak(false), txpos(0), bkspaces(0)
1969 {
1970 	ptxpos = &txpos;
1971 
1972 	change_keybindings();
1973 
1974 	memcpy(menu + TX_MENU_CUT, FTextEdit::menu, (FTextEdit::menu->size() - 1) * sizeof(*FTextEdit::menu));
1975 	context_menu = menu;
1976 	init_context_menu();
1977 	utf8_txpos = txpos = 0;
1978 }
1979 
1980 /// Handles fltk events for this widget.
1981 /// We pass keyboard events to handle_key() and handle mouse3 presses to show
1982 /// the popup menu. We also disallow mouse2 events in the transmitted text area.
1983 /// Everything else is passed to the base class handle().
1984 ///
1985 /// @param event
1986 ///
1987 /// @return
1988 ///
handle(int event)1989 int FTextTX::handle(int event)
1990 {
1991 	if ( !(Fl::event_inside(this) || (event == FL_KEYBOARD && Fl::focus() == this)) )
1992 		return FTextEdit::handle(event);
1993 
1994 	switch (event) {
1995 	case FL_KEYBOARD:
1996 		if (active_modem->get_mode() == MODE_FSQ) {
1997 			if (Fl::event_key() == FL_Enter || Fl::event_key() == FL_KP_Enter) {
1998 				fsq_transmit(0);
1999 				return 1;
2000 			}
2001 		}
2002 		return handle_key(Fl::event_key()) ? 1 : FTextEdit::handle(event);
2003 	case FL_PUSH:
2004 		if (Fl::event_button() == FL_MIDDLE_MOUSE &&
2005 		    xy_to_position(Fl::event_x(), Fl::event_y(), CHARACTER_POS) < txpos)
2006 			return 1; // ignore mouse2 text pastes inside the transmitted text
2007 	}
2008 
2009 	return FTextEdit::handle(event);
2010 }
2011 
2012 /// Clears the buffer.
2013 /// Also resets the transmit position, stored backspaces and tx pause flag.
2014 ///
clear(void)2015 void FTextTX::clear(void)
2016 {
2017 	FTextEdit::clear();
2018 	txpos = 0;
2019 	utf8_txpos = 0;
2020 	bkspaces = 0;
2021 	PauseBreak = false;
2022 }
2023 
2024 /// Clears the sent text.
2025 /// Also resets the transmit position, stored backspaces and tx pause flag.
2026 ///
clear_sent(void)2027 void FTextTX::clear_sent(void)
2028 {
2029  	tbuf->remove(0, utf8_txpos);
2030  	sbuf->remove(0, utf8_txpos);
2031 	txpos = 0;
2032 	utf8_txpos = 0;
2033 	bkspaces = 0;
2034 	PauseBreak = false;
2035 	set_word_wrap(restore_wrap);
2036 }
2037 
2038 /// Returns boolean <eot> end of text
2039 ///
2040 /// true if empty buffer
2041 /// false if characters remain
2042 ///
eot(void)2043 bool FTextTX::eot(void)
2044 {
2045 	return (insert_position() == txpos);
2046 }
2047 
2048 /// Returns the next character to be transmitted.
2049 ///
2050 /// @return The next character, or ETX if the transmission has been paused, or
2051 /// NUL if no text should be transmitted.
2052 ///
nextChar(void)2053 int FTextTX::nextChar(void)
2054 {
2055 	int c;
2056 
2057 	if (bkspaces) {
2058 		--bkspaces;
2059 		c = '\b';
2060 	}
2061 	else if (PauseBreak) {
2062 		PauseBreak = false;
2063 		c = GET_TX_CHAR_ETX;//0x03;
2064 	} else if (insert_position() <= utf8_txpos) { // empty buffer or cursor inside transmitted text
2065 		c = -1;
2066 	} else {
2067 		if ((c = tbuf->char_at(utf8_txpos)) > 0) {
2068 			int n = fl_utf8bytes(c);
2069 
2070 			REQ(FTextTX::changed_cb, utf8_txpos, 0, 0, -1, static_cast<const char *>(0), this);
2071 			REQ(FTextTX::changed_cb, utf8_txpos+1, 0, 0, -1, static_cast<const char *>(0), this);
2072 			++txpos;
2073 			utf8_txpos += n;
2074 		} else
2075 			c = -1;
2076 	}
2077 	return c;
2078 }
2079 
2080 // called by xmlrpc thread
2081 // called by macro execution
add_text(string s)2082 void FTextTX::add_text(string s)
2083 {
2084 	for (size_t n = 0; n < s.length(); n++) {
2085 		if (s[n] == '\b') {
2086 			int ipos = insert_position();
2087 			if (tbuf->length()) {
2088 				if (ipos > 0 && txpos == ipos) {
2089 					bkspaces++;
2090 					txpos--;
2091 					int nn;
2092 					tbuf->get_char_at(utf8_txpos, nn);
2093 					utf8_txpos -= nn;
2094 				}
2095 				tbuf->remove(tbuf->length() - 1, tbuf->length());
2096 				sbuf->remove(sbuf->length() - 1, sbuf->length());
2097 				redraw();
2098 			}
2099 		} else {
2100 //LOG_DEBUG("%04x ", s[n] & 0x00FF);
2101 			add(s[n] & 0xFF, RECV);
2102 		}
2103 	}
2104 }
2105 
setFont(Fl_Font f,int attr)2106 void FTextTX::setFont(Fl_Font f, int attr)
2107 {
2108 	FTextBase::setFont(f, attr);
2109 }
2110 
2111 /// Handles keyboard shorcuts
2112 ///
2113 /// @param key
2114 //   pressed key
2115 ///
2116 /// @return
2117 //   1 if shortcut is handled, otherwise 0.
2118 ///
handle_key_shortcuts(int key)2119 int FTextTX::handle_key_shortcuts(int key)
2120 {
2121 	std::string etag = "";
2122 
2123 	switch (key) {
2124 	case 'c': // add <CALL> for SC-c
2125 	case 'm': // add <MYCALL> for SC-m
2126 	case 'n': // add <NAME> for SC-n
2127 	case 'r': // add <RST>rcvd for SC-r
2128 	case 's': // add <RST>sent for SC-s
2129 	case 'l': // add <MYLOC> for SC-l
2130 	case 'h': // add <MYQTH> for SC-h
2131 	case 'a': // add <ANTENNA> for SC-a
2132 	case 'g': // add <BEL> 0x07
2133 		if ((Fl::event_state() & FL_CTRL) && (Fl::event_state() & FL_SHIFT))
2134 //		if ((Fl::event_state() & (FL_CTRL | FL_SHIFT))) // investigate why this doesn't work...
2135 		{
2136 			switch (key)
2137 			{
2138 			case 'c':
2139 				etag = inpCall->value();
2140 				break;
2141 			case 'm':
2142 				etag = progdefaults.myCall;
2143 				break;
2144 			case 'n':
2145 				etag = inpName->value();
2146 				break;
2147 			case 'r':
2148 				{
2149 					std::string s;
2150 					etag = (s = inpRstIn->value()).length() ? s : std::string("599");
2151 				}
2152 				break;
2153 			case 's':
2154 				{
2155 					std::string s;
2156 					etag = (s = inpRstOut->value()).length() ? s : std::string("599");
2157 				}
2158 				break;
2159 			case 'l':
2160 				etag = progdefaults.myLocator;
2161 				break;
2162 			case 'h':
2163 				etag = progdefaults.myQth;
2164 				break;
2165 			case 'a':
2166 				etag = progdefaults.myAntenna;
2167 				break;
2168 			case 'g':
2169 				etag = "\007";
2170 				break;
2171 			default:
2172 				break;
2173 			}
2174 
2175 			// Add text + space if length is > 0
2176 			if (etag.length()) {
2177 				add_text(etag);
2178 				if (etag != "\007")
2179 					add_text(" ");
2180 				return 1;
2181 			}
2182 		}
2183 		break;
2184 
2185 	default:
2186 		break;
2187 	}
2188 
2189 	return 0;
2190 }
2191 
2192 /// Handles keyboard events to override Fl_Text_Editor_mod's handling of some
2193 /// keystrokes.
2194 ///
2195 /// @param key
2196 ///
2197 /// @return
2198 ///
handle_key(int key)2199 int FTextTX::handle_key(int key)
2200 {
2201 
2202 	if (handle_key_shortcuts(key))
2203 	    return 1;
2204 
2205 	switch (key) {
2206 	case FL_Escape: // set stop flag and clear
2207 	{
2208 		static time_t t[2] = { 0, 0 };
2209 		static unsigned char i = 0;
2210 		if (t[i] == time(&t[!i])) { // two presses in a second: abort transmission
2211 			if (trx_state == STATE_TX)
2212 				menu_cb(TX_MENU_ABORT);
2213 			t[i = !i] = 0;
2214 			return 1;
2215 		}
2216 		i = !i;
2217 	}
2218 
2219 		if (trx_state == STATE_TX && active_modem->get_stopflag() == false) {
2220 			kf_select_all(0, this);
2221 			kf_copy(0, this);
2222 			clear();
2223 			if (arq_text_available)
2224 				AbortARQ();
2225 			active_modem->set_stopflag(true);
2226 		}
2227 
2228 		if (trx_state == STATE_TUNE)
2229 			abort_tx();
2230 
2231 		stopMacroTimer();
2232 		return 1;
2233 
2234 	case 't': // transmit for C-t
2235 		if (trx_state == STATE_RX && Fl::event_state() & FL_CTRL) {
2236 			menu_cb(TX_MENU_TX);
2237 			return 1;
2238 		}
2239 		break;
2240 	case 'r':// receive for C-r
2241 		if (Fl::event_state() & FL_CTRL) {
2242 			menu_cb(TX_MENU_RX);
2243 			return 1;
2244 		}
2245 		else if (!(Fl::event_state() & (FL_META | FL_ALT)))
2246 			break;
2247 		// fall through to (un)pause for M-r or A-r
2248 
2249 	case FL_Pause:
2250 		if (trx_state != STATE_TX) {
2251 			start_tx();
2252 		}
2253 		else
2254 			PauseBreak = true;
2255 		return 1;
2256 	case (FL_KP + '+'):
2257 		if (active_modem == cw_modem)
2258 			active_modem->incWPM();
2259 		return 1;
2260 	case (FL_KP + '-'):
2261 		if (active_modem == cw_modem)
2262 			active_modem->decWPM();
2263 		return 1;
2264 	case (FL_KP + '*'):
2265 		if (active_modem == cw_modem)
2266 			active_modem->toggleWPM();
2267 		return 1;
2268 	case FL_Tab:
2269 		if (active_modem == fsq_modem) return 1;
2270 		// In non-CW modes: Tab and Ctrl-tab both pause until user moves the
2271 		// cursor to let some more text through. Another (ctrl-)tab goes back to
2272 		// the end of the buffer and resumes sending.
2273 
2274 		// In CW mode: Tab pauses, skips rest of buffer, applies the
2275 		// SKIP style, then resumes sending when new text is entered.
2276 		// Ctrl-tab does the same thing as for all other modes.
2277 		if (utf8_txpos != insert_position())
2278 			insert_position(utf8_txpos);
2279 		else
2280 			insert_position(tbuf->length());
2281 		if (!(Fl::event_state() & FL_CTRL) && active_modem == cw_modem) {
2282 			int n = tbuf->length() - utf8_txpos;
2283 			char s[n + 1];
2284 			memset(s, FTEXT_DEF + SKIP, n);
2285 			s[n] = 0;
2286 			sbuf->replace(utf8_txpos, sbuf->length(), s);
2287 			insert_position(tbuf->length());
2288 			redisplay_range(utf8_txpos, insert_position());
2289 			utf8_txpos = insert_position();
2290 		}
2291 		return 1;
2292 	// Move cursor, or search up/down with the Meta/Alt modifiers
2293 	case FL_Left:
2294 		if (Fl::event_state() & (FL_META | FL_ALT)) {
2295 			if (active_modem == fsq_modem) return 1;
2296 			active_modem->searchDown();
2297 			return 1;
2298 		}
2299 		return 0;
2300 	case FL_Right:
2301 		if (Fl::event_state() & (FL_META | FL_ALT)) {
2302 			if (active_modem == fsq_modem) return 1;
2303 			active_modem->searchUp();
2304 			return 1;
2305 		}
2306 		return 0;
2307 		// queue a BS and decr. the txpos, unless the cursor is in the tx text
2308 	case FL_BackSpace:
2309 	{
2310 		int ipos = insert_position();
2311 		if (utf8_txpos > 0 && utf8_txpos == ipos) {
2312 			bkspaces++;
2313 			utf8_txpos = tbuf->prev_char(ipos);
2314 			txpos--;
2315 		}
2316 		return 0;
2317 	}
2318 // alt - 1 / 2 changes macro sets
2319 	case '1':
2320 	case '2':
2321 	case '3':
2322 	case '4':
2323 		if (Fl::event_state() & FL_ALT) {
2324 			if (active_modem == fsq_modem) return 1;
2325 			static char lbl[2] = "1";
2326 			altMacros = key - '1';
2327 			if (progdefaults.mbar_scheme > MACRO_SINGLE_BAR_MAX) {
2328 				if (!altMacros) altMacros = 1;
2329 				for (int i = 0; i < NUMMACKEYS; i++) {
2330 					btnMacro[NUMMACKEYS + i]->label(
2331 						macros.name[(altMacros * NUMMACKEYS) + i].c_str());
2332 					btnMacro[NUMMACKEYS + i]->redraw_label();
2333 				}
2334 				lbl[0] = key;
2335 				btnAltMacros2->label(lbl);
2336 				btnAltMacros2->redraw_label();
2337 			} else {
2338 				for (int i = 0; i < NUMMACKEYS; i++) {
2339 					btnMacro[i]->label(
2340 						macros.name[(altMacros * NUMMACKEYS) + i].c_str());
2341 					btnMacro[i]->redraw_label();
2342 				}
2343 				lbl[0] = key;
2344 				btnAltMacros1->label(lbl);
2345 				btnAltMacros1->redraw_label();
2346 			}
2347 			return 1;
2348 		}
2349 		break;
2350 	default:
2351 		break;
2352 	}
2353 
2354 	if (insert_position() < txpos)
2355 		return 1;
2356 
2357 // insert a macro
2358 	if (key >= FL_F && key <= FL_F_Last) {
2359 		return handle_key_macro(key);
2360 	}
2361 // read ctl-ddd, where d is a digit, as ascii characters (in base 10)
2362 // and insert verbatim; e.g. ctl-001 inserts a <soh>
2363 	if (Fl::event_state() & FL_CTRL && (key >= FL_KP) && (key <= FL_KP + '9'))
2364 		return handle_key_ascii(key);
2365 
2366 // restart the numeric keypad entries.
2367 	ascii_cnt = 0;
2368 	ascii_chr = 0;
2369 
2370 	return 0;
2371 }
2372 
2373 /// Inserts the macro for function key \c key.
2374 ///
2375 /// @param key An integer in the range [FL_F, FL_F_Last]
2376 ///
2377 /// @return 1
2378 ///
handle_key_macro(int key)2379 int FTextTX::handle_key_macro(int key)
2380 {
2381 	key -= FL_F + 1;
2382 
2383 	if (active_modem == fsq_modem) {
2384 		if (key == 0) fsq_repeat_last_heard();
2385 		if (key == 1) fsq_repeat_last_command();
2386 		return 1;
2387 	}
2388 	if (key > 11)
2389 		return 0;
2390 
2391 	if (progdefaults.mbar_scheme > MACRO_SINGLE_BAR_MAX) {
2392 		if (Fl::event_state(FL_SHIFT))
2393 			key += altMacros * NUMMACKEYS;
2394 	} else {
2395 		key += altMacros * NUMMACKEYS;
2396 	}
2397 	if (!(macros.text[key]).empty())
2398 		macros.execute(key);
2399 
2400 	return 1;
2401 }
2402 
handle_dnd_drag(int pos)2403 int FTextTX::handle_dnd_drag(int pos)
2404 {
2405 	if (pos >= txpos) {
2406 		return FTextEdit::handle_dnd_drag(pos);
2407 	}
2408 	else // refuse drop inside transmitted text
2409 		return 0;
2410 }
2411 
2412 /// Handles mouse-3 clicks by displaying the context menu
2413 ///
2414 /// @param val
2415 ///
handle_context_menu(void)2416 void FTextTX::handle_context_menu(void)
2417 {
2418 	// adjust Abort/Transmit/Receive menu items
2419 	switch (trx_state) {
2420 	case STATE_TX:
2421 		menu[TX_MENU_TX].hide();
2422 		menu[TX_MENU_RX].show();
2423 		menu[TX_MENU_ABORT].show();
2424 		break;
2425 	case STATE_TUNE:
2426 		menu[TX_MENU_TX].hide();
2427 		menu[TX_MENU_RX].show();
2428 		menu[TX_MENU_ABORT].hide();
2429 		break;
2430 	default:
2431 		menu[TX_MENU_TX].show();
2432 		menu[TX_MENU_RX].hide();
2433 		menu[TX_MENU_ABORT].hide();
2434 		break;
2435 	}
2436 
2437 	bool modify_text_ok = insert_position() >= txpos;
2438 	bool selected = tbuf->selected();
2439  	icons::set_active(&menu[TX_MENU_MFSK16_IMG], active_modem->get_cap() & modem::CAP_IMG);
2440 	icons::set_active(&menu[TX_MENU_CLEAR], tbuf->length());
2441 	icons::set_active(&menu[TX_MENU_CUT], selected && modify_text_ok);
2442 	icons::set_active(&menu[TX_MENU_COPY], selected);
2443 	icons::set_active(&menu[TX_MENU_PASTE], modify_text_ok);
2444 	icons::set_active(&menu[TX_MENU_READ], modify_text_ok);
2445 
2446 	if (wrap)
2447 		menu[TX_MENU_WRAP].set();
2448 	else
2449 		menu[TX_MENU_WRAP].clear();
2450 
2451 	show_context_menu();
2452 }
2453 
2454 /// The context menu handler
2455 ///
2456 /// @param val
2457 ///
menu_cb(size_t item)2458 void FTextTX::menu_cb(size_t item)
2459 {
2460   	switch (item) {
2461   	case TX_MENU_TX:
2462 		active_modem->set_stopflag(false);
2463 		start_tx();
2464 		break;
2465 	case TX_MENU_ABORT:
2466 		char panic[200];
2467 		snprintf(panic, sizeof(panic), "*** Don't panic *** %s", progdefaults.myName.c_str());
2468 		put_status(panic, 5.0);
2469 		abort_tx();
2470   		break;
2471   	case TX_MENU_RX:
2472  		if (trx_state == STATE_TX) {
2473  			insert_position(tbuf->length());
2474  			add("^r", CTRL);
2475  		}
2476  		else
2477  			abort_tx();
2478   		break;
2479 	case TX_MENU_MFSK16_IMG:
2480 		{
2481 			trx_mode md = active_modem->get_mode();
2482 		if (md == MODE_IFKP)
2483 			ifkp_showTxViewer();
2484 		else if (md >= MODE_THOR_FIRST && md <= MODE_THOR_LAST)
2485 			thor_showTxViewer();
2486 		else
2487 			showTxViewer(0, 0);
2488 		break;
2489 		}
2490 	case TX_MENU_CLEAR:
2491 		clear();
2492 		break;
2493 	case TX_MENU_CUT:
2494 		kf_cut(0, this);
2495 		break;
2496 	case TX_MENU_COPY:
2497 		kf_copy(0, this);
2498 		break;
2499 	case TX_MENU_PASTE:
2500 		kf_paste(0, this);
2501 		break;
2502 	case TX_MENU_READ: {
2503 		restore_wrap = wrap;
2504 		set_word_wrap(false);
2505 		readFile();
2506 		break;
2507 	}
2508 	case TX_MENU_WRAP:
2509 		set_word_wrap(!wrap, true);
2510 		break;
2511 	default:
2512 		if (FTextTX::menu[item].flags == 0) { // not an FL_SUB_MENU
2513 			add(FTextTX::menu[item].text[0]);
2514 			add(FTextTX::menu[item].text[1]);
2515 		}
2516 	}
2517 }
2518 
2519 /// Overrides some useful Fl_Text_Edit keybindings that we want to keep working,
2520 /// provided that they don't try to change chunks of transmitted text.
2521 ///
change_keybindings(void)2522 void FTextTX::change_keybindings(void)
2523 {
2524 	struct {
2525 		Fl_Text_Editor_mod::Key_Func function, override;
2526 	} fbind[] = {
2527 		{ Fl_Text_Editor_mod::kf_default, FTextTX::kf_default },
2528 		{ Fl_Text_Editor_mod::kf_enter,   FTextTX::kf_enter   },
2529 		{ Fl_Text_Editor_mod::kf_delete,  FTextTX::kf_delete  },
2530 		{ Fl_Text_Editor_mod::kf_cut,     FTextTX::kf_cut     },
2531 		{ Fl_Text_Editor_mod::kf_paste,   FTextTX::kf_paste   }
2532 	};
2533 	int n = sizeof(fbind) / sizeof(fbind[0]);
2534 
2535 	// walk the keybindings linked list and replace items containing
2536 	// functions for which we have an override in fbind
2537 	for (Fl_Text_Editor_mod::Key_Binding *k = key_bindings; k; k = k->next) {
2538 		for (int i = 0; i < n; i++)
2539 			if (fbind[i].function == k->function)
2540 				k->function = fbind[i].override;
2541 	}
2542 }
2543 
2544 // The kf_* functions below call the corresponding Fl_Text_Editor_mod routines, but
2545 // may make adjustments so that no transmitted text is modified.
2546 
kf_default(int c,Fl_Text_Editor_mod * e)2547 int FTextTX::kf_default(int c, Fl_Text_Editor_mod* e)
2548 {
2549 	return e->insert_position() < *ptxpos ? 1 : Fl_Text_Editor_mod::kf_default(c, e);
2550 }
2551 
kf_enter(int c,Fl_Text_Editor_mod * e)2552 int FTextTX::kf_enter(int c, Fl_Text_Editor_mod* e)
2553 {
2554 	return e->insert_position() < *ptxpos ? 1 : Fl_Text_Editor_mod::kf_enter(c, e);
2555 }
2556 
kf_delete(int c,Fl_Text_Editor_mod * e)2557 int FTextTX::kf_delete(int c, Fl_Text_Editor_mod* e)
2558 {
2559 	// single character
2560 	if (!e->buffer()->selected()) {
2561                 if (e->insert_position() >= *ptxpos &&
2562                     e->insert_position() != e->buffer()->length())
2563                         return Fl_Text_Editor_mod::kf_delete(c, e);
2564                 else
2565                         return 1;
2566         }
2567 
2568 	// region: delete as much as we can
2569 	int start, end;
2570 	e->buffer()->selection_position(&start, &end);
2571 	if (*ptxpos >= end)
2572 		return 1;
2573 	if (*ptxpos > start)
2574 		e->buffer()->select(*ptxpos, end);
2575 
2576 	return Fl_Text_Editor_mod::kf_delete(c, e);
2577 }
2578 
kf_cut(int c,Fl_Text_Editor_mod * e)2579 int FTextTX::kf_cut(int c, Fl_Text_Editor_mod* e)
2580 {
2581 	if (e->buffer()->selected()) {
2582 		int start, end;
2583 		e->buffer()->selection_position(&start, &end);
2584 		if (*ptxpos >= end)
2585 			return 1;
2586 		if (*ptxpos > start)
2587 			e->buffer()->select(*ptxpos, end);
2588 	}
2589 
2590 	return Fl_Text_Editor_mod::kf_cut(c, e);
2591 }
2592 
kf_paste(int c,Fl_Text_Editor_mod * e)2593 int FTextTX::kf_paste(int c, Fl_Text_Editor_mod* e)
2594 {
2595 	return e->insert_position() < *ptxpos ? 1 : Fl_Text_Editor_mod::kf_paste(c, e);
2596 }
2597 
2598 // ----------------------------------------------------------------------------
2599 
2600 
draw(void)2601 void MVScrollbar::draw(void)
2602 {
2603 	Fl_Scrollbar::draw();
2604 
2605 	if (marks.empty() || !draw_marks)
2606 		return;
2607 
2608 	assert((type() & FL_HOR_SLIDER) == 0);
2609 
2610 	// Calculate the slider knob position and height.  For a vertical scrollbar,
2611 	// the scroll buttons' height is the scrollbar width and the minimum knob
2612 	// height is half that.
2613 	int H = h() - Fl::box_dh(box()) - 2 * w(); // internal height (minus buttons)
2614 	int slider_h = (int)(slider_size() * H + 0.5);
2615 	int min_h = (w() - Fl::box_dw(box())) / 2 + 1;
2616 	if (slider_h < min_h)
2617 		slider_h = min_h;
2618 	double val = (Fl_Slider::value() - minimum()) / (maximum() - minimum());
2619 	int slider_y = (int)(val * (H - slider_h) + 0.5) + w(); // relative to y()
2620 	// This would draw a green rectangle around the slider knob:
2621 	// fl_color(FL_GREEN);
2622 	// fl_rect(x(), y() + slider_y, w() - Fl::box_dw(box()), slider_h);
2623 
2624 	int x1 = x() + Fl::box_dx(box()), x2 = x1 + w() - Fl::box_dw(box()) - 1, ypos;
2625 	// Convert stored scrollbar values to vertical positions and draw
2626 	// lines inside the widget if they don't overlap with the knob area.
2627 	for (vector<mark_t>::const_iterator i = marks.begin(); i != marks.end(); ++i) {
2628 		ypos = static_cast<int>(w() + H * i->pos / maximum());
2629 		// Don't draw over slider knob
2630 		if ((ypos > slider_y && ypos < slider_y + slider_h) ||
2631 		    (ypos < slider_y + slider_h && ypos > slider_y))
2632 			continue;
2633 		ypos += y();
2634 		fl_color(i->color);
2635 		fl_line(x1, ypos, x2, ypos);
2636 	}
2637 }
2638