1/*
2 * Copyright 2012-2019, Adrien Destugues, pulkomandy@pulkomandy.tk
3 * Distributed under the terms of the MIT licence.
4 */
5
6
7#include "SerialWindow.h"
8
9#include <stdio.h>
10
11#include <Catalog.h>
12#include <FilePanel.h>
13#include <GroupLayout.h>
14#include <Menu.h>
15#include <MenuBar.h>
16#include <MenuItem.h>
17#include <ScrollView.h>
18#include <SerialPort.h>
19#include <StatusBar.h>
20
21#include "SerialApp.h"
22#include "TermView.h"
23
24
25#define B_TRANSLATION_CONTEXT "SerialWindow"
26
27
28const int SerialWindow::kBaudrates[] = { 50, 75, 110, 134, 150, 200, 300, 600,
29	1200, 1800, 2400, 4800, 9600, 19200, 31250, 38400, 57600, 115200, 230400
30};
31
32
33// The values for these constants are not in the expected order, so we have to
34// rely on this lookup table if we want to keep the menu items sorted.
35const int SerialWindow::kBaudrateConstants[] = { B_50_BPS, B_75_BPS, B_110_BPS,
36	B_134_BPS, B_150_BPS, B_200_BPS, B_300_BPS, B_600_BPS, B_1200_BPS,
37	B_1800_BPS, B_2400_BPS, B_4800_BPS, B_9600_BPS, B_19200_BPS, B_31250_BPS,
38	B_38400_BPS, B_57600_BPS, B_115200_BPS, B_230400_BPS
39};
40
41
42const char* SerialWindow::kWindowTitle =
43	B_TRANSLATE_MARK_SYSTEM_NAME("SerialConnect");
44
45
46SerialWindow::SerialWindow()
47	: BWindow(BRect(100, 100, 400, 400),
48		B_TRANSLATE_NOCOLLECT_SYSTEM_NAME(SerialWindow::kWindowTitle),
49		B_DOCUMENT_WINDOW, B_QUIT_ON_WINDOW_CLOSE | B_AUTO_UPDATE_SIZE_LIMITS)
50	, fLogFilePanel(NULL)
51	, fSendFilePanel(NULL)
52{
53	BMenuBar* menuBar = new BMenuBar(Bounds(), "menuBar");
54	menuBar->ResizeToPreferred();
55
56	BRect r = Bounds();
57	r.top = menuBar->Bounds().bottom + 1;
58	r.right -= B_V_SCROLL_BAR_WIDTH;
59	fTermView = new TermView(r);
60	fTermView->ResizeToPreferred();
61
62	r = fTermView->Frame();
63	r.left = r.right + 1;
64	r.right = r.left + B_V_SCROLL_BAR_WIDTH;
65	r.top -= 1;
66	r.bottom -= B_H_SCROLL_BAR_HEIGHT - 1;
67
68	BScrollBar* scrollBar = new BScrollBar(r, "scrollbar", NULL, 0, 0,
69		B_VERTICAL);
70
71	scrollBar->SetTarget(fTermView);
72
73	ResizeTo(r.right - 1, r.bottom + B_H_SCROLL_BAR_HEIGHT - 1);
74
75	r = fTermView->Frame();
76	r.top = r.bottom - 37;
77
78	fStatusBar = new BStatusBar(r, B_TRANSLATE("file transfer progress"),
79		NULL, NULL);
80	fStatusBar->SetResizingMode(B_FOLLOW_BOTTOM | B_FOLLOW_LEFT_RIGHT);
81	fStatusBar->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
82	fStatusBar->Hide();
83
84	AddChild(menuBar);
85	AddChild(fTermView);
86	AddChild(scrollBar);
87	AddChild(fStatusBar);
88
89	fConnectionMenu = new BMenu(B_TRANSLATE("Connection"));
90	fFileMenu = new BMenu(B_TRANSLATE("File"));
91	BMenu* settingsMenu = new BMenu(B_TRANSLATE("Settings"));
92	BMenu* editMenu = new BMenu(B_TRANSLATE("Edit"));
93
94	fConnectionMenu->SetRadioMode(true);
95
96	menuBar->AddItem(fConnectionMenu);
97	menuBar->AddItem(editMenu);
98	menuBar->AddItem(fFileMenu);
99	menuBar->AddItem(settingsMenu);
100
101	BMenuItem* logFile = new BMenuItem(
102		B_TRANSLATE("Log to file" B_UTF8_ELLIPSIS), new BMessage(kMsgLogfile));
103	fFileMenu->AddItem(logFile);
104
105	// The "send" items are disabled initially. They are enabled only once we
106	// are connected to a serial port.
107	BMessage* sendMsg = new BMessage(kMsgSendFile);
108	sendMsg->AddString("protocol", "xmodem");
109	BMenuItem* xmodemSend = new BMenuItem(
110		B_TRANSLATE("XModem send" B_UTF8_ELLIPSIS),
111		sendMsg);
112	fFileMenu->AddItem(xmodemSend);
113	xmodemSend->SetEnabled(false);
114
115	BMenuItem* rawSend = new BMenuItem(B_TRANSLATE("Raw send" B_UTF8_ELLIPSIS),
116		new BMessage(kMsgSendFile));
117	fFileMenu->AddItem(rawSend);
118	rawSend->SetEnabled(false);
119
120#if 0
121	// TODO implement this
122	BMenuItem* xmodemReceive = new BMenuItem(
123		"X/Y/Zmodem receive" B_UTF8_ELLIPSIS, NULL);
124	fFileMenu->AddItem(xmodemReceive);
125	xmodemReceive->SetEnabled(false);
126#endif
127
128	// Items for the edit menu
129	BMenuItem* clearScreen = new BMenuItem(B_TRANSLATE("Clear history"),
130		new BMessage(kMsgClear), 'L');
131	editMenu->AddItem(clearScreen);
132
133	BMenuItem* paste = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 'V');
134	editMenu->AddItem(paste);
135
136	// TODO copy (when we have selection), paste
137
138	// Configuring all this by menus may be a bit unhandy. Make a setting
139	// window instead ?
140	fBaudrateMenu = new BMenu(B_TRANSLATE("Baud rate"));
141	fBaudrateMenu->SetRadioMode(true);
142	settingsMenu->AddItem(fBaudrateMenu);
143
144	fParityMenu = new BMenu(B_TRANSLATE("Parity"));
145	fParityMenu->SetRadioMode(true);
146	settingsMenu->AddItem(fParityMenu);
147
148	fStopbitsMenu = new BMenu(B_TRANSLATE("Stop bits"));
149	fStopbitsMenu->SetRadioMode(true);
150	settingsMenu->AddItem(fStopbitsMenu);
151
152	fFlowcontrolMenu = new BMenu(B_TRANSLATE("Flow control"));
153	fFlowcontrolMenu->SetRadioMode(true);
154	settingsMenu->AddItem(fFlowcontrolMenu);
155
156	fDatabitsMenu = new BMenu(B_TRANSLATE("Data bits"));
157	fDatabitsMenu->SetRadioMode(true);
158	settingsMenu->AddItem(fDatabitsMenu);
159
160	fLineTerminatorMenu = new BMenu(B_TRANSLATE("Line terminator"));
161	fLineTerminatorMenu->SetRadioMode(true);
162	settingsMenu->AddItem(fLineTerminatorMenu);
163
164	BMessage* message = new BMessage(kMsgSettings);
165	message->AddInt32("parity", B_NO_PARITY);
166	BMenuItem* parityNone =
167		new BMenuItem(B_TRANSLATE_COMMENT("None", "Parity"), message);
168
169	message = new BMessage(kMsgSettings);
170	message->AddInt32("parity", B_ODD_PARITY);
171	BMenuItem* parityOdd = new BMenuItem(B_TRANSLATE_COMMENT("Odd", "Parity"),
172		message);
173
174	message = new BMessage(kMsgSettings);
175	message->AddInt32("parity", B_EVEN_PARITY);
176	BMenuItem* parityEven =
177		new BMenuItem(B_TRANSLATE_COMMENT("Even", "Parity"), message);
178
179	fParityMenu->AddItem(parityNone);
180	fParityMenu->AddItem(parityOdd);
181	fParityMenu->AddItem(parityEven);
182	fParityMenu->SetTargetForItems(be_app);
183
184	message = new BMessage(kMsgSettings);
185	message->AddInt32("databits", B_DATA_BITS_7);
186	BMenuItem* data7 = new BMenuItem("7", message);
187
188	message = new BMessage(kMsgSettings);
189	message->AddInt32("databits", B_DATA_BITS_8);
190	BMenuItem* data8 = new BMenuItem("8", message);
191
192	fDatabitsMenu->AddItem(data7);
193	fDatabitsMenu->AddItem(data8);
194	fDatabitsMenu->SetTargetForItems(be_app);
195
196	message = new BMessage(kMsgSettings);
197	message->AddInt32("stopbits", B_STOP_BITS_1);
198	BMenuItem* stop1 = new BMenuItem("1", message);
199
200	message = new BMessage(kMsgSettings);
201	message->AddInt32("stopbits", B_STOP_BITS_2);
202	BMenuItem* stop2 = new BMenuItem("2", message);
203
204	fStopbitsMenu->AddItem(stop1);
205	fStopbitsMenu->AddItem(stop2);
206	fStopbitsMenu->SetTargetForItems(be_app);
207
208	// Loop backwards to add fastest rates at top of menu
209	for (int i = sizeof(kBaudrates) / sizeof(kBaudrates[0]); --i >= 0;)
210	{
211		message = new BMessage(kMsgSettings);
212		message->AddInt32("baudrate", kBaudrateConstants[i]);
213
214		char buffer[7];
215		sprintf(buffer, "%d", kBaudrates[i]);
216		BMenuItem* item = new BMenuItem(buffer, message);
217
218		fBaudrateMenu->AddItem(item);
219	}
220
221	message = new BMessage(kMsgCustomBaudrate);
222	BMenuItem* custom =
223		new BMenuItem(B_TRANSLATE_COMMENT("custom" B_UTF8_ELLIPSIS,
224		"Baudrate"), message);
225	fBaudrateMenu->AddItem(custom);
226
227	fBaudrateMenu->SetTargetForItems(be_app);
228
229	message = new BMessage(kMsgSettings);
230	message->AddInt32("flowcontrol", B_HARDWARE_CONTROL);
231	BMenuItem* hardware =
232		new BMenuItem(B_TRANSLATE_COMMENT("Hardware", "Flowcontrol"), message);
233
234	message = new BMessage(kMsgSettings);
235	message->AddInt32("flowcontrol", B_SOFTWARE_CONTROL);
236	BMenuItem* software =
237		new BMenuItem(B_TRANSLATE_COMMENT("Software", "Flowcontrol"), message);
238
239	message = new BMessage(kMsgSettings);
240	message->AddInt32("flowcontrol", B_HARDWARE_CONTROL | B_SOFTWARE_CONTROL);
241	BMenuItem* both =
242		new BMenuItem(B_TRANSLATE_COMMENT("Both", "Flowcontrol"), message);
243
244	message = new BMessage(kMsgSettings);
245	message->AddInt32("flowcontrol", 0);
246	BMenuItem* noFlow =
247		new BMenuItem(B_TRANSLATE_COMMENT("None", "Flowcontrol"), message);
248
249	fFlowcontrolMenu->AddItem(hardware);
250	fFlowcontrolMenu->AddItem(software);
251	fFlowcontrolMenu->AddItem(both);
252	fFlowcontrolMenu->AddItem(noFlow);
253	fFlowcontrolMenu->SetTargetForItems(be_app);
254
255	message = new BMessage(kMsgSettings);
256	message->AddString("terminator", "\n");
257	BMenuItem* lf = new BMenuItem("LF (\\n)", message);
258
259	message = new BMessage(kMsgSettings);
260	message->AddString("terminator", "\r");
261	BMenuItem* cr = new BMenuItem("CR (\\r)", message);
262
263	message = new BMessage(kMsgSettings);
264	message->AddString("terminator", "\r\n");
265	BMenuItem* crlf = new BMenuItem("CR/LF (\\r\\n)", message);
266
267	fLineTerminatorMenu->AddItem(lf);
268	fLineTerminatorMenu->AddItem(cr);
269	fLineTerminatorMenu->AddItem(crlf);
270
271	CenterOnScreen();
272}
273
274
275SerialWindow::~SerialWindow()
276{
277	delete fLogFilePanel;
278	delete fSendFilePanel;
279}
280
281
282void SerialWindow::MenusBeginning()
283{
284	// remove all items from the menu
285	fConnectionMenu->RemoveItems(0, fConnectionMenu->CountItems(), true);
286
287	// fill it with the (updated) serial port list
288	BSerialPort serialPort;
289	int deviceCount = serialPort.CountDevices();
290	bool connected = false;
291
292	for (int i = 0; i < deviceCount; i++)
293	{
294		char buffer[256];
295		serialPort.GetDeviceName(i, buffer, 256);
296
297		BMessage* message = new BMessage(kMsgOpenPort);
298		message->AddString("port name", buffer);
299		BMenuItem* portItem = new BMenuItem(buffer, message);
300		portItem->SetTarget(be_app);
301
302		const BString& connectedPort = ((SerialApp*)be_app)->GetPort();
303
304		if (connectedPort == buffer) {
305			connected = true;
306			portItem->SetMarked(true);
307		}
308
309		fConnectionMenu->AddItem(portItem);
310	}
311
312	if (deviceCount > 0) {
313		fConnectionMenu->AddSeparatorItem();
314
315		BMenuItem* disconnect = new BMenuItem(B_TRANSLATE("Disconnect"),
316			new BMessage(kMsgOpenPort), 'Z', B_OPTION_KEY);
317		if (!connected)
318			disconnect->SetEnabled(false);
319		disconnect->SetTarget(be_app);
320		fConnectionMenu->AddItem(disconnect);
321	} else {
322		BMenuItem* noDevices =
323			new BMenuItem(B_TRANSLATE("<no serial port available>"), NULL);
324		noDevices->SetEnabled(false);
325		fConnectionMenu->AddItem(noDevices);
326	}
327}
328
329
330void SerialWindow::MessageReceived(BMessage* message)
331{
332	switch (message->what)
333	{
334		case kMsgOpenPort:
335		{
336			BString path;
337			bool open = (message->FindString("port name", &path) == B_OK);
338			int i = 1; // Skip "log to file", which woeks even when offline.
339			BMenuItem* item;
340			while((item = fFileMenu->ItemAt(i++)))
341			{
342				item->SetEnabled(open);
343			}
344			return;
345		}
346		case kMsgDataRead:
347		{
348			const char* bytes;
349			ssize_t length;
350			if (message->FindData("data", B_RAW_TYPE, (const void**)&bytes,
351					&length) == B_OK)
352				fTermView->PushBytes(bytes, length);
353			return;
354		}
355		case kMsgLogfile:
356		{
357			// Let's lazy init the file panel
358			if (fLogFilePanel == NULL) {
359				fLogFilePanel = new BFilePanel(B_SAVE_PANEL,
360					&be_app_messenger, NULL, B_FILE_NODE, false);
361				fLogFilePanel->SetMessage(message);
362			}
363			fLogFilePanel->Show();
364			return;
365		}
366		case kMsgSendFile:
367		{
368			// Let's lazy init the file panel
369			if (fSendFilePanel == NULL) {
370				fSendFilePanel = new BFilePanel(B_OPEN_PANEL,
371					&be_app_messenger, NULL, B_FILE_NODE, false);
372			}
373			fSendFilePanel->SetMessage(message);
374			fSendFilePanel->Show();
375			return;
376		}
377		case kMsgSettings:
378		{
379			int32 baudrate;
380			stop_bits stopBits;
381			data_bits dataBits;
382			parity_mode parity;
383			uint32 flowcontrol;
384			BString terminator;
385
386			if (message->FindInt32("databits", (int32*)&dataBits) == B_OK) {
387				for (int i = 0; i < fDatabitsMenu->CountItems(); i++) {
388					BMenuItem* item = fDatabitsMenu->ItemAt(i);
389					int32 code;
390					item->Message()->FindInt32("databits", &code);
391
392					if (code == dataBits)
393						item->SetMarked(true);
394				}
395			}
396
397			if (message->FindInt32("stopbits", (int32*)&stopBits) == B_OK) {
398				for (int i = 0; i < fStopbitsMenu->CountItems(); i++) {
399					BMenuItem* item = fStopbitsMenu->ItemAt(i);
400					int32 code;
401					item->Message()->FindInt32("stopbits", &code);
402
403					if (code == stopBits)
404						item->SetMarked(true);
405				}
406			}
407
408			if (message->FindInt32("parity", (int32*)&parity) == B_OK)
409			{
410				for (int i = 0; i < fParityMenu->CountItems(); i++) {
411					BMenuItem* item = fParityMenu->ItemAt(i);
412					int32 code;
413					item->Message()->FindInt32("parity", &code);
414
415					if (code == parity)
416						item->SetMarked(true);
417				}
418			}
419
420			if (message->FindInt32("flowcontrol", (int32*)&flowcontrol)
421					== B_OK) {
422				for (int i = 0; i < fFlowcontrolMenu->CountItems(); i++) {
423					BMenuItem* item = fFlowcontrolMenu->ItemAt(i);
424					int32 code;
425					item->Message()->FindInt32("flowcontrol", &code);
426
427					if (code == (int32)flowcontrol)
428						item->SetMarked(true);
429				}
430			}
431
432			if (message->FindInt32("baudrate", &baudrate) == B_OK) {
433				int i;
434				BMenuItem* item = NULL;
435				for (i = 0; i < fBaudrateMenu->CountItems(); i++) {
436					item = fBaudrateMenu->ItemAt(i);
437					int32 code = 0;
438					item->Message()->FindInt32("baudrate", &code);
439
440					if (baudrate == code) {
441						item->SetMarked(true);
442						break;
443					}
444				}
445
446				if (i == fBaudrateMenu->CountItems() && item != NULL) {
447					// Rate was not found, mark it as "custom".
448					// Since that is the last item in the menu, we still point
449					// to it.
450					item->SetMarked(true);
451					item->Message()->SetInt32("baudrate", baudrate);
452				}
453			}
454
455			if (message->FindString("terminator", &terminator) == B_OK) {
456				fTermView->SetLineTerminator(terminator);
457				for (int i = 0; i < fLineTerminatorMenu->CountItems(); i++) {
458					BMenuItem* item = fLineTerminatorMenu->ItemAt(i);
459					BString code;
460					item->Message()->FindString("terminator", &code);
461
462					if (terminator == code)
463						item->SetMarked(true);
464				}
465			}
466
467			return;
468		}
469		case kMsgClear:
470		{
471			fTermView->Clear();
472			return;
473		}
474		case B_PASTE:
475		{
476			fTermView->PasteFromClipboard();
477		}
478		case kMsgProgress:
479		{
480			// File transfer progress
481			int32 pos = message->FindInt32("pos");
482			int32 size = message->FindInt32("size");
483			BString label = message->FindString("info");
484
485			if (pos >= size) {
486				if (!fStatusBar->IsHidden()) {
487					fStatusBar->Hide();
488					fTermView->ResizeBy(0, fStatusBar->Bounds().Height() - 1);
489				}
490			} else {
491				BString text;
492				text.SetToFormat("%" B_PRId32 "/%" B_PRId32, pos, size);
493				fStatusBar->SetMaxValue(size);
494				fStatusBar->SetTo(pos, label, text);
495				if (fStatusBar->IsHidden()) {
496					fStatusBar->Show();
497					fTermView->ResizeBy(0, -(fStatusBar->Bounds().Height() - 1));
498				}
499			}
500			return;
501		}
502		default:
503			BWindow::MessageReceived(message);
504	}
505}
506