1/*
2 * Copyright 2007-2016, Haiku, Inc. All rights reserved.
3 * Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved.
4 * Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de>
5 * Distributed under the terms of the MIT License.
6 */
7
8
9#include "FilterConfigView.h"
10
11#include <stdio.h>
12
13#include <Alert.h>
14#include <Bitmap.h>
15#include <Box.h>
16#include <Catalog.h>
17#include <LayoutBuilder.h>
18#include <Locale.h>
19#include <MenuItem.h>
20#include <PopUpMenu.h>
21#include <ScrollView.h>
22
23
24#undef B_TRANSLATION_CONTEXT
25#define B_TRANSLATION_CONTEXT "Config Views"
26
27
28// FiltersConfigView
29const uint32 kMsgFilterMoved = 'flmv';
30const uint32 kMsgChainSelected = 'chsl';
31const uint32 kMsgAddFilter = 'addf';
32const uint32 kMsgRemoveFilter = 'rmfi';
33const uint32 kMsgFilterSelected = 'fsel';
34
35const uint32 kMsgItemDragged = 'itdr';
36
37
38class DragListView : public BListView {
39public:
40	DragListView(const char* name,
41			list_view_type type = B_SINGLE_SELECTION_LIST,
42			 BMessage* itemMovedMsg = NULL)
43		:
44		BListView(name, type),
45		fDragging(false),
46		fItemMovedMessage(itemMovedMsg)
47	{
48	}
49
50	virtual bool InitiateDrag(BPoint point, int32 index, bool wasSelected)
51	{
52		BRect frame(ItemFrame(index));
53		BBitmap *bitmap = new BBitmap(frame.OffsetToCopy(B_ORIGIN), B_RGBA32,
54			true);
55		BView *view = new BView(bitmap->Bounds(), NULL, 0, 0);
56		bitmap->AddChild(view);
57
58		if (view->LockLooper()) {
59			BListItem *item = ItemAt(index);
60			bool selected = item->IsSelected();
61
62			view->SetLowColor(225, 225, 225, 128);
63			view->FillRect(view->Bounds());
64
65			if (selected)
66				item->Deselect();
67			ItemAt(index)->DrawItem(view, view->Bounds(), true);
68			if (selected)
69				item->Select();
70
71			view->UnlockLooper();
72		}
73		fLastDragTarget = -1;
74		fDragIndex = index;
75		fDragging = true;
76
77		BMessage drag(kMsgItemDragged);
78		drag.AddInt32("index", index);
79		DragMessage(&drag, bitmap, B_OP_ALPHA, point - frame.LeftTop(), this);
80
81		return true;
82	}
83
84	void DrawDragTargetIndicator(int32 target)
85	{
86		PushState();
87		SetDrawingMode(B_OP_INVERT);
88
89		bool last = false;
90		if (target >= CountItems())
91			target = CountItems() - 1, last = true;
92
93		BRect frame = ItemFrame(target);
94		if (last)
95			frame.OffsetBy(0,frame.Height());
96		frame.bottom = frame.top + 1;
97
98		FillRect(frame);
99
100		PopState();
101	}
102
103	virtual void MouseMoved(BPoint point, uint32 transit, const BMessage *msg)
104	{
105		BListView::MouseMoved(point, transit, msg);
106
107		if ((transit != B_ENTERED_VIEW && transit != B_INSIDE_VIEW)
108			|| !fDragging)
109			return;
110
111		int32 target = IndexOf(point);
112		if (target == -1)
113			target = CountItems();
114
115		// correct the target insertion index
116		if (target == fDragIndex || target == fDragIndex + 1)
117			target = -1;
118
119		if (target == fLastDragTarget)
120			return;
121
122		// remove old target indicator
123		if (fLastDragTarget != -1)
124			DrawDragTargetIndicator(fLastDragTarget);
125
126		// draw new one
127		fLastDragTarget = target;
128		if (target != -1)
129			DrawDragTargetIndicator(target);
130	}
131
132	virtual void MouseUp(BPoint point)
133	{
134		if (fDragging) {
135			fDragging = false;
136			if (fLastDragTarget != -1)
137				DrawDragTargetIndicator(fLastDragTarget);
138		}
139		BListView::MouseUp(point);
140	}
141
142	virtual void MessageReceived(BMessage *msg)
143	{
144		switch (msg->what) {
145			case kMsgItemDragged:
146			{
147				int32 source = msg->FindInt32("index");
148				BPoint point = msg->FindPoint("_drop_point_");
149				ConvertFromScreen(&point);
150				int32 to = IndexOf(point);
151				if (to > fDragIndex)
152					to--;
153				if (to == -1)
154					to = CountItems() - 1;
155
156				if (source != to) {
157					MoveItem(source,to);
158
159					if (fItemMovedMessage != NULL) {
160						BMessage msg(fItemMovedMessage->what);
161						msg.AddInt32("from",source);
162						msg.AddInt32("to",to);
163						Messenger().SendMessage(&msg);
164					}
165				}
166				break;
167			}
168		}
169		BListView::MessageReceived(msg);
170	}
171
172private:
173	bool		fDragging;
174	int32		fLastDragTarget,fDragIndex;
175	BMessage	*fItemMovedMessage;
176};
177
178
179//	#pragma mark -
180
181
182class FilterSettingsView : public BBox {
183public:
184	FilterSettingsView(const BString& label, BMailSettingsView* settingsView)
185		:
186		BBox("filter"),
187		fSettingsView(settingsView)
188	{
189		SetLabel(label);
190
191		BView* contents = new BView("contents", 0);
192		AddChild(contents);
193
194		BLayoutBuilder::Group<>(contents, B_VERTICAL)
195			.SetInsets(B_USE_DEFAULT_SPACING)
196			.Add(fSettingsView);
197	}
198
199	status_t SaveInto(BMailAddOnSettings& settings) const
200	{
201		return fSettingsView->SaveInto(settings);
202	}
203
204private:
205			BMailSettingsView*	fSettingsView;
206};
207
208
209//	#pragma mark -
210
211
212FiltersConfigView::FiltersConfigView(BMailAccountSettings& account)
213	:
214	BGroupView(B_VERTICAL),
215	fAccount(account),
216	fDirection(kIncoming),
217	fInboundFilters(kIncoming),
218	fOutboundFilters(kOutgoing),
219	fFilterView(NULL),
220	fCurrentIndex(-1)
221{
222	BBox* box = new BBox("filters");
223	AddChild(box);
224
225	BView* contents = new BView(NULL, 0);
226	box->AddChild(contents);
227
228	BMessage* msg = new BMessage(kMsgChainSelected);
229	msg->AddInt32("direction", kIncoming);
230	BMenuItem* item = new BMenuItem(B_TRANSLATE("Incoming mail filters"), msg);
231	item->SetMarked(true);
232	BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING);
233	menu->AddItem(item);
234
235	msg = new BMessage(kMsgChainSelected);
236	msg->AddInt32("direction", kOutgoing);
237	item = new BMenuItem(B_TRANSLATE("Outgoing mail filters"), msg);
238	menu->AddItem(item);
239
240	fChainsField = new BMenuField(NULL, NULL, menu);
241	fChainsField->ResizeToPreferred();
242	box->SetLabel(fChainsField);
243
244	fListView = new DragListView(NULL, B_SINGLE_SELECTION_LIST,
245		new BMessage(kMsgFilterMoved));
246	fListView->SetSelectionMessage(new BMessage(kMsgFilterSelected));
247
248	menu = new BPopUpMenu(B_TRANSLATE("Add filter"));
249	menu->SetRadioMode(false);
250
251	fAddField = new BMenuField(NULL, NULL, menu);
252
253	fRemoveButton = new BButton(NULL, B_TRANSLATE("Remove"),
254		new BMessage(kMsgRemoveFilter));
255
256	BLayoutBuilder::Group<>(contents, B_VERTICAL)
257		.SetInsets(B_USE_DEFAULT_SPACING)
258		.Add(new BScrollView(NULL, fListView, 0, false, true))
259		.AddGroup(B_HORIZONTAL)
260			.Add(fAddField)
261			.Add(fRemoveButton)
262			.AddGlue();
263
264	_SetDirection(fDirection);
265}
266
267
268FiltersConfigView::~FiltersConfigView()
269{
270	// We need to remove the filter manually, as their add-on
271	// is not available anymore in the parent destructor.
272	if (fFilterView != NULL) {
273		RemoveChild(fFilterView);
274		delete fFilterView;
275	}
276}
277
278
279void
280FiltersConfigView::_SelectFilter(int32 index)
281{
282	Hide();
283
284	// remove old config view
285	if (fFilterView != NULL) {
286		RemoveChild(fFilterView);
287		_SaveConfig(fCurrentIndex);
288		delete fFilterView;
289		fFilterView = NULL;
290	}
291
292	if (index >= 0) {
293		// add new config view
294		BMailAddOnSettings* filterSettings
295			= _MailSettings()->FilterSettingsAt(index);
296		if (filterSettings != NULL) {
297			::FilterList* filters = _FilterList();
298			BMailSettingsView* view = filters->CreateSettingsView(fAccount,
299				*filterSettings);
300			if (view != NULL) {
301				fFilterView = new FilterSettingsView(
302					filters->DescriptiveName(filterSettings->AddOnRef(),
303						fAccount, NULL), view);
304				AddChild(fFilterView);
305			}
306		}
307	}
308
309	fCurrentIndex = index;
310	Show();
311}
312
313
314void
315FiltersConfigView::_SetDirection(direction direction)
316{
317	// remove the filter config view
318	_SelectFilter(-1);
319
320	for (int32 i = fListView->CountItems(); i-- > 0;) {
321		BStringItem *item = (BStringItem *)fListView->RemoveItem(i);
322		delete item;
323	}
324
325	fDirection = direction;
326	BMailProtocolSettings* protocolSettings = _MailSettings();
327	::FilterList* filters = _FilterList();
328	filters->Reload();
329
330	for (int32 i = 0; i < protocolSettings->CountFilterSettings(); i++) {
331		BMailAddOnSettings* settings = protocolSettings->FilterSettingsAt(i);
332		if (filters->InfoIndexFor(settings->AddOnRef()) < 0) {
333			fprintf(stderr, "Removed missing filter: %s\n",
334				settings->AddOnRef().name);
335			protocolSettings->RemoveFilterSettings(i);
336			i--;
337			continue;
338		}
339
340		fListView->AddItem(new BStringItem(filters->DescriptiveName(
341			settings->AddOnRef(), fAccount, settings)));
342	}
343
344	// remove old filter items
345	BMenu* menu = fAddField->Menu();
346	for (int32 i = menu->CountItems(); i-- > 0;) {
347		BMenuItem *item = menu->RemoveItem(i);
348		delete item;
349	}
350
351	for (int32 i = 0; i < filters->CountInfos(); i++) {
352		const FilterInfo& info = filters->InfoAt(i);
353
354		BMessage* msg = new BMessage(kMsgAddFilter);
355		msg->AddRef("filter", &info.ref);
356		BMenuItem* item = new BMenuItem(filters->SimpleName(i, fAccount), msg);
357		menu->AddItem(item);
358	}
359
360	menu->SetTargetForItems(this);
361}
362
363
364void
365FiltersConfigView::AttachedToWindow()
366{
367	fChainsField->Menu()->SetTargetForItems(this);
368	fListView->SetTarget(this);
369	fAddField->Menu()->SetTargetForItems(this);
370	fRemoveButton->SetTarget(this);
371}
372
373
374void
375FiltersConfigView::DetachedFromWindow()
376{
377	_SaveConfig(fCurrentIndex);
378}
379
380
381void
382FiltersConfigView::MessageReceived(BMessage *msg)
383{
384	switch (msg->what) {
385		case kMsgChainSelected:
386		{
387			direction dir;
388			if (msg->FindInt32("direction", (int32*)&dir) != B_OK)
389				break;
390
391			if (fDirection == dir)
392				break;
393
394			_SetDirection(dir);
395			break;
396		}
397		case kMsgAddFilter:
398		{
399			entry_ref ref;
400			if (msg->FindRef("filter", &ref) != B_OK)
401				break;
402
403			int32 index = _MailSettings()->AddFilterSettings(&ref);
404			if (index < 0)
405				break;
406
407			fListView->AddItem(new BStringItem(_FilterList()->DescriptiveName(
408				ref, fAccount, _MailSettings()->FilterSettingsAt(index))));
409			break;
410		}
411		case kMsgRemoveFilter:
412		{
413			int32 index = fListView->CurrentSelection();
414			if (index < 0)
415				break;
416			BStringItem* item = (BStringItem*)fListView->RemoveItem(index);
417			delete item;
418
419			_SelectFilter(-1);
420			_MailSettings()->RemoveFilterSettings(index);
421			break;
422		}
423		case kMsgFilterSelected:
424		{
425			int32 index = -1;
426			if (msg->FindInt32("index",&index) != B_OK)
427				break;
428
429			_SelectFilter(index);
430			break;
431		}
432		case kMsgFilterMoved:
433		{
434			int32 from = msg->FindInt32("from");
435			int32 to = msg->FindInt32("to");
436			if (from == to)
437				break;
438
439			if (!_MailSettings()->MoveFilterSettings(from, to)) {
440				BAlert* alert = new BAlert("E-mail",
441					B_TRANSLATE("The filter could not be moved. Deleting "
442						"filter."), B_TRANSLATE("OK"));
443				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
444				alert->Go();
445				fListView->RemoveItem(to);
446				break;
447			}
448
449			break;
450		}
451		default:
452			BView::MessageReceived(msg);
453			break;
454	}
455}
456
457
458BMailProtocolSettings*
459FiltersConfigView::_MailSettings()
460{
461	return fDirection == kIncoming
462		? &fAccount.InboundSettings() : &fAccount.OutboundSettings();
463}
464
465
466FilterList*
467FiltersConfigView::_FilterList()
468{
469	return fDirection == kIncoming ? &fInboundFilters : &fOutboundFilters;
470}
471
472
473void
474FiltersConfigView::_SaveConfig(int32 index)
475{
476	if (fFilterView != NULL) {
477		BMailAddOnSettings* settings = _MailSettings()->FilterSettingsAt(index);
478		if (settings != NULL)
479			fFilterView->SaveInto(*settings);
480	}
481}
482