1/*
2 * Copyright 2013-2015, Rene Gollent, rene@gollent.com.
3 * Distributed under the terms of the MIT License.
4 */
5#include "SignalsConfigView.h"
6
7#include <Box.h>
8#include <Button.h>
9#include <LayoutBuilder.h>
10#include <MenuField.h>
11
12#include <AutoDeleter.h>
13#include <AutoLocker.h>
14
15#include "table/TableColumns.h"
16
17#include "AppMessageCodes.h"
18#include "MessageCodes.h"
19#include "SignalDispositionEditWindow.h"
20#include "SignalDispositionMenu.h"
21#include "SignalDispositionTypes.h"
22#include "UiUtils.h"
23#include "UserInterface.h"
24
25
26enum {
27	MSG_ADD_DISPOSITION_EXCEPTION			= 'adex',
28	MSG_EDIT_DISPOSITION_EXCEPTION			= 'edex',
29	MSG_REMOVE_DISPOSITION_EXCEPTION		= 'rdex'
30};
31
32
33struct SignalDispositionInfo {
34	SignalDispositionInfo(int32 _signal, int32 _disposition)
35		:
36		signal(_signal),
37		disposition(_disposition)
38	{
39	}
40
41	int32 signal;
42	int32 disposition;
43};
44
45
46
47class SignalsConfigView::SignalDispositionModel : public TableModel {
48public:
49
50	SignalDispositionModel(::Team* team)
51		:
52		fDispositions(),
53		fTeam(team)
54	{
55	}
56
57	virtual ~SignalDispositionModel()
58	{
59	}
60
61	virtual int32 CountColumns() const
62	{
63		return 2;
64	}
65
66	virtual int32 CountRows() const
67	{
68		return fDispositions.CountItems();
69	}
70
71	virtual bool GetValueAt(int32 rowIndex, int32 columnIndex, BVariant& value)
72	{
73		SignalDispositionInfo* info = fDispositions.ItemAt(rowIndex);
74		if (info == NULL)
75			return false;
76
77		switch (columnIndex) {
78			case 0:
79			{
80				BString tempValue;
81				value.SetTo(UiUtils::SignalNameToString(info->signal,
82					tempValue));
83				return true;
84			}
85			case 1:
86			{
87				value.SetTo(UiUtils::SignalDispositionToString(
88						info->disposition), B_VARIANT_DONT_COPY_DATA);
89				return true;
90			}
91			default:
92				break;
93		}
94
95		return false;
96	}
97
98	bool SignalDispositionInfoAt(int32 rowIndex, SignalDispositionInfo*& _info)
99	{
100		_info = fDispositions.ItemAt(rowIndex);
101		if (_info == NULL)
102			return false;
103
104		return true;
105	}
106
107	void Update(int32 signal, int32 disposition)
108	{
109		for (int32 i = 0; i < fDispositions.CountItems(); i++) {
110			SignalDispositionInfo* info = fDispositions.ItemAt(i);
111			if (info->signal == signal) {
112				info->disposition = disposition;
113				NotifyRowsChanged(i, 1);
114				return;
115			}
116		}
117
118		SignalDispositionInfo* info = new(std::nothrow) SignalDispositionInfo(
119			signal, disposition);
120		if (info == NULL)
121			return;
122
123		ObjectDeleter<SignalDispositionInfo> infoDeleter(info);
124		if (!fDispositions.AddItem(info))
125			return;
126
127		infoDeleter.Detach();
128		NotifyRowsAdded(fDispositions.CountItems() - 1, 1);
129	}
130
131	void Remove(int32 signal)
132	{
133		for (int32 i = 0; i < fDispositions.CountItems(); i++) {
134			SignalDispositionInfo* info = fDispositions.ItemAt(i);
135			if (info->signal == signal) {
136				fDispositions.RemoveItemAt(i);
137				delete info;
138				NotifyRowsRemoved(i, 1);
139				return;
140			}
141		}
142	}
143
144	status_t Init()
145	{
146		AutoLocker< ::Team> locker(fTeam);
147
148		const SignalDispositionMappings& dispositions
149			= fTeam->GetSignalDispositionMappings();
150
151		for (SignalDispositionMappings::const_iterator it
152			= dispositions.begin(); it != dispositions.end(); ++it) {
153			SignalDispositionInfo* info
154				= new(std::nothrow) SignalDispositionInfo(it->first,
155				it->second);
156			if (info == NULL)
157				return B_NO_MEMORY;
158
159			ObjectDeleter<SignalDispositionInfo> infoDeleter(info);
160
161			if (!fDispositions.AddItem(info))
162				return B_NO_MEMORY;
163
164			infoDeleter.Detach();
165		}
166
167		return B_OK;
168	}
169
170private:
171	typedef BObjectList<SignalDispositionInfo> DispositionList;
172
173private:
174	DispositionList		fDispositions;
175	::Team*				fTeam;
176};
177
178
179SignalsConfigView::SignalsConfigView(::Team* team,
180	UserInterfaceListener* listener)
181	:
182	BGroupView(B_VERTICAL),
183	fTeam(team),
184	fListener(listener),
185	fDefaultSignalDisposition(NULL),
186	fDispositionExceptions(NULL),
187	fAddDispositionButton(NULL),
188	fEditDispositionButton(NULL),
189	fRemoveDispositionButton(NULL),
190	fDispositionModel(NULL),
191	fEditWindow(NULL)
192{
193	SetName("Signals");
194	fTeam->AddListener(this);
195}
196
197
198SignalsConfigView::~SignalsConfigView()
199{
200	fTeam->RemoveListener(this);
201	BMessenger(fEditWindow).SendMessage(B_QUIT_REQUESTED);
202}
203
204
205SignalsConfigView*
206SignalsConfigView::Create(::Team* team, UserInterfaceListener* listener)
207{
208	SignalsConfigView* self = new SignalsConfigView(team, listener);
209
210	try {
211		self->_Init();
212	} catch (...) {
213		delete self;
214		throw;
215	}
216
217	return self;
218}
219
220
221void
222SignalsConfigView::AttachedToWindow()
223{
224	fDefaultSignalDisposition->Menu()->SetTargetForItems(this);
225	fAddDispositionButton->SetTarget(this);
226	fEditDispositionButton->SetTarget(this);
227	fRemoveDispositionButton->SetTarget(this);
228
229	fEditDispositionButton->SetEnabled(false);
230	fRemoveDispositionButton->SetEnabled(false);
231
232	AutoLocker< ::Team> teamLocker(fTeam);
233	_UpdateSignalConfigState();
234
235	BGroupView::AttachedToWindow();
236}
237
238
239void
240SignalsConfigView::MessageReceived(BMessage* message)
241{
242	switch (message->what) {
243		case MSG_SIGNAL_DISPOSITION_EDIT_WINDOW_CLOSED:
244		{
245			fEditWindow = NULL;
246		}
247		case MSG_SET_DEFAULT_SIGNAL_DISPOSITION:
248		{
249			int32 disposition;
250			if (message->FindInt32("disposition", &disposition) != B_OK)
251				break;
252
253			fListener->SetDefaultSignalDispositionRequested(disposition);
254			break;
255		}
256		case MSG_ADD_DISPOSITION_EXCEPTION:
257		case MSG_EDIT_DISPOSITION_EXCEPTION:
258		{
259			if (fEditWindow != NULL) {
260				AutoLocker<BWindow> lock(fEditWindow);
261				if (lock.IsLocked())
262					fEditWindow->Activate(true);
263			} else {
264				int32 signal = 0;
265				if (message->what == MSG_EDIT_DISPOSITION_EXCEPTION) {
266					TableSelectionModel* model
267						= fDispositionExceptions->SelectionModel();
268					SignalDispositionInfo* info;
269					if (fDispositionModel->SignalDispositionInfoAt(
270							model->RowAt(0), info)) {
271						signal = info->signal;
272					}
273				}
274
275				try {
276					fEditWindow = SignalDispositionEditWindow::Create(fTeam,
277						signal, fListener, this);
278					if (fEditWindow != NULL)
279						fEditWindow->Show();
280	           	} catch (...) {
281	           		// TODO: notify user
282	           	}
283			}
284			break;
285		}
286		case MSG_REMOVE_DISPOSITION_EXCEPTION:
287		{
288			TableSelectionModel* model
289				= fDispositionExceptions->SelectionModel();
290			for (int32 i = 0; i < model->CountRows(); i++) {
291				SignalDispositionInfo* info;
292				if (fDispositionModel->SignalDispositionInfoAt(model->RowAt(i),
293						info)) {
294					fListener->RemoveCustomSignalDispositionRequested(
295						info->signal);
296				}
297			}
298			break;
299		}
300		case MSG_SET_CUSTOM_SIGNAL_DISPOSITION:
301		{
302			int32 signal;
303			int32 disposition;
304			if (message->FindInt32("signal", &signal) == B_OK
305				&& message->FindInt32("disposition", &disposition) == B_OK) {
306				fDispositionModel->Update(signal, disposition);
307			}
308			break;
309		}
310		case MSG_REMOVE_CUSTOM_SIGNAL_DISPOSITION:
311		{
312			int32 signal;
313			if (message->FindInt32("signal", &signal) == B_OK)
314				fDispositionModel->Remove(signal);
315			break;
316		}
317		default:
318			BGroupView::MessageReceived(message);
319			break;
320	}
321}
322
323
324void
325SignalsConfigView::CustomSignalDispositionChanged(
326	const Team::CustomSignalDispositionEvent& event)
327{
328	BMessage message(MSG_SET_CUSTOM_SIGNAL_DISPOSITION);
329	message.AddInt32("signal", event.Signal());
330	message.AddInt32("disposition", event.Disposition());
331
332	BMessenger(this).SendMessage(&message);
333}
334
335
336void
337SignalsConfigView::CustomSignalDispositionRemoved(
338	const Team::CustomSignalDispositionEvent& event)
339{
340	BMessage message(MSG_REMOVE_CUSTOM_SIGNAL_DISPOSITION);
341	message.AddInt32("signal", event.Signal());
342
343	BMessenger(this).SendMessage(&message);
344}
345
346
347void
348SignalsConfigView::TableSelectionChanged(Table* table)
349{
350	TableSelectionModel* model = fDispositionExceptions->SelectionModel();
351	int32 rowCount = model->CountRows();
352	fEditDispositionButton->SetEnabled(rowCount == 1);
353	fRemoveDispositionButton->SetEnabled(rowCount > 0);
354}
355
356
357void
358SignalsConfigView::_Init()
359{
360	SignalDispositionMenu* dispositionMenu = new SignalDispositionMenu(
361		"signalDispositionsMenu",
362		new BMessage(MSG_SET_DEFAULT_SIGNAL_DISPOSITION));
363
364	BGroupView* customDispositionsGroup = new BGroupView();
365	BLayoutBuilder::Group<>(customDispositionsGroup, B_VERTICAL, 0.0)
366		.SetInsets(B_USE_SMALL_INSETS)
367		.Add(fDispositionExceptions = new Table("customDispositions",
368			B_WILL_DRAW, B_FANCY_BORDER))
369		.AddGroup(B_HORIZONTAL)
370			.SetInsets(B_USE_SMALL_INSETS)
371			.AddGlue()
372			.Add(fAddDispositionButton = new BButton("Add" B_UTF8_ELLIPSIS,
373				new BMessage(MSG_ADD_DISPOSITION_EXCEPTION)))
374			.Add(fEditDispositionButton = new BButton("Edit" B_UTF8_ELLIPSIS,
375				new BMessage(MSG_EDIT_DISPOSITION_EXCEPTION)))
376			.Add(fRemoveDispositionButton = new BButton("Remove",
377				new BMessage(MSG_REMOVE_DISPOSITION_EXCEPTION)))
378		.End();
379
380	BBox* dispositionsBox = new BBox("custom dispositions box");
381	dispositionsBox->AddChild(customDispositionsGroup);
382	dispositionsBox->SetLabel("Custom dispositions");
383
384	BLayoutBuilder::Group<>(this, B_VERTICAL)
385		.SetInsets(B_USE_DEFAULT_SPACING)
386		.Add(fDefaultSignalDisposition = new BMenuField(
387			"stopTypes", "Default disposition:", dispositionMenu))
388		.Add(dispositionsBox);
389
390	dispositionMenu->SetLabelFromMarked(true);
391
392	// columns
393	fDispositionExceptions->AddColumn(new StringTableColumn(0, "Signal",
394		80, 40, 1000, B_TRUNCATE_END, B_ALIGN_LEFT));
395	fDispositionExceptions->AddColumn(new StringTableColumn(1, "Disposition",
396		200, 40, 1000, B_TRUNCATE_END, B_ALIGN_RIGHT));
397	fDispositionExceptions->SetExplicitMinSize(BSize(400.0, 200.0));
398
399
400	fDispositionModel = new SignalDispositionModel(fTeam);
401	if (fDispositionModel->Init() != B_OK) {
402		delete fDispositionModel;
403		return;
404	}
405
406	fDispositionExceptions->SetTableModel(fDispositionModel);
407	fDispositionExceptions->AddTableListener(this);
408}
409
410
411void
412SignalsConfigView::_UpdateSignalConfigState()
413{
414	int32 defaultDisposition = fTeam->DefaultSignalDisposition();
415	fDefaultSignalDisposition->Menu()->ItemAt(defaultDisposition)
416		->SetMarked(true);
417}
418