1/*
2 * Copyright 2011-2013, Haiku, Inc. All rights reserved.
3 * Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de>
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include "FolderConfigWindow.h"
9
10#include <Alert.h>
11#include <Button.h>
12#include <Catalog.h>
13#include <ControlLook.h>
14#include <LayoutBuilder.h>
15#include <ListItem.h>
16#include <ScrollView.h>
17#include <SpaceLayoutItem.h>
18#include <StringView.h>
19
20#include <StringForSize.h>
21
22#include "Settings.h"
23#include "Utilities.h"
24
25
26#undef B_TRANSLATION_CONTEXT
27#define B_TRANSLATION_CONTEXT "IMAPFolderConfig"
28
29
30class EditableListItem {
31public:
32								EditableListItem();
33	virtual						~EditableListItem() {}
34
35	virtual void				MouseDown(BPoint where) = 0;
36	virtual	void				MouseUp(BPoint where) = 0;
37
38			void				SetListView(BListView* list)
39									{ fListView = list; }
40
41protected:
42			BListView*			fListView;
43};
44
45
46class CheckBoxItem : public BStringItem, public EditableListItem {
47public:
48								CheckBoxItem(const char* text, bool checked);
49
50			void				DrawItem(BView* owner, BRect itemRect,
51									bool drawEverything = false);
52
53			void				MouseDown(BPoint where);
54			void				MouseUp(BPoint where);
55
56			bool				Checked() { return fChecked; }
57private:
58			BRect				fBoxRect;
59			bool				fChecked;
60			bool				fMouseDown;
61};
62
63
64class EditListView : public BListView {
65public:
66								EditListView(const char* name,
67									list_view_type type
68										= B_SINGLE_SELECTION_LIST,
69									uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS
70										| B_NAVIGABLE);
71
72	virtual void				MouseDown(BPoint where);
73	virtual void				MouseUp(BPoint where);
74	virtual void				FrameResized(float newWidth, float newHeight);
75
76private:
77			EditableListItem*	fLastMouseDown;
78};
79
80
81class StatusWindow : public BWindow {
82public:
83	StatusWindow(BWindow* parent, const char* text)
84		:
85		BWindow(BRect(0, 0, 10, 10), B_TRANSLATE("status"), B_MODAL_WINDOW_LOOK,
86			B_MODAL_APP_WINDOW_FEEL, B_NO_WORKSPACE_ACTIVATION | B_NOT_ZOOMABLE
87				| B_AVOID_FRONT | B_NOT_RESIZABLE | B_NOT_ZOOMABLE
88				| B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS)
89	{
90		BLayoutBuilder::Group<>(this)
91			.SetInsets(B_USE_DEFAULT_SPACING)
92			.Add(new BStringView("text", text));
93		CenterIn(parent->Frame());
94	}
95};
96
97
98const uint32 kMsgApplyButton = '&Abu';
99const uint32 kMsgInit = '&Ini';
100
101
102// #pragma mark -
103
104
105EditableListItem::EditableListItem()
106	:
107	fListView(NULL)
108{
109
110}
111
112
113// #pragma mark -
114
115
116CheckBoxItem::CheckBoxItem(const char* text, bool checked)
117	:
118	BStringItem(text),
119	fChecked(checked),
120	fMouseDown(false)
121{
122}
123
124
125void
126CheckBoxItem::DrawItem(BView* owner, BRect itemRect, bool drawEverything)
127{
128	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
129	rgb_color lowColor = owner->LowColor();
130	uint32 flags = 0;
131	if (fMouseDown)
132		flags |= BControlLook::B_CLICKED;
133	if (fChecked)
134		flags |= BControlLook::B_ACTIVATED;
135
136	font_height fontHeight;
137	owner->GetFontHeight(&fontHeight);
138	BRect boxRect(0.0f, 2.0f, ceilf(3.0f + fontHeight.ascent),
139		ceilf(5.0f + fontHeight.ascent));
140
141	owner->PushState();
142
143	float left = itemRect.left;
144	fBoxRect.left = left + 3;
145	fBoxRect.top = itemRect.top + (itemRect.Height() - boxRect.Height()) / 2;
146	fBoxRect.right = fBoxRect.left + boxRect.Width();
147	fBoxRect.bottom = itemRect.top + boxRect.Height();
148
149	itemRect.left = fBoxRect.right + be_control_look->DefaultLabelSpacing();
150
151	if (IsSelected() || drawEverything) {
152		if (IsSelected()) {
153			owner->SetHighColor(tint_color(lowColor, B_DARKEN_2_TINT));
154			owner->SetLowColor(owner->HighColor());
155		} else
156			owner->SetHighColor(lowColor);
157
158		owner->FillRect(
159			BRect(left, itemRect.top, itemRect.left, itemRect.bottom));
160	}
161
162	be_control_look->DrawCheckBox(owner, fBoxRect, fBoxRect, base, flags);
163	owner->PopState();
164
165	BStringItem::DrawItem(owner, itemRect, drawEverything);
166}
167
168
169void
170CheckBoxItem::MouseDown(BPoint where)
171{
172	if (!fBoxRect.Contains(where))
173		return;
174
175	fMouseDown = true;
176
177	fListView->InvalidateItem(fListView->IndexOf(this));
178}
179
180
181void
182CheckBoxItem::MouseUp(BPoint where)
183{
184	if (!fMouseDown)
185		return;
186	fMouseDown = false;
187
188	if (fBoxRect.Contains(where)) {
189		if (fChecked)
190			fChecked = false;
191		else
192			fChecked = true;
193	}
194
195	fListView->InvalidateItem(fListView->IndexOf(this));
196}
197
198
199// #pragma mark -
200
201
202EditListView::EditListView(const char* name, list_view_type type, uint32 flags)
203	:
204	BListView(name, type, flags),
205	fLastMouseDown(NULL)
206{
207}
208
209
210void
211EditListView::MouseDown(BPoint where)
212{
213	BListView::MouseDown(where);
214
215	int32 index = IndexOf(where);
216	EditableListItem* handler = dynamic_cast<EditableListItem*>(ItemAt(index));
217	if (handler == NULL)
218		return;
219
220	fLastMouseDown = handler;
221	handler->MouseDown(where);
222	SetMouseEventMask(B_POINTER_EVENTS);
223}
224
225
226void
227EditListView::MouseUp(BPoint where)
228{
229	BListView::MouseUp(where);
230	if (fLastMouseDown) {
231		fLastMouseDown->MouseUp(where);
232		fLastMouseDown = NULL;
233	}
234
235	int32 index = IndexOf(where);
236	EditableListItem* handler = dynamic_cast<EditableListItem*>(ItemAt(index));
237	if (handler == NULL)
238		return;
239
240	handler->MouseUp(where);
241}
242
243
244void
245EditListView::FrameResized(float newWidth, float newHeight)
246{
247	Invalidate();
248}
249
250
251// #pragma mark -
252
253
254FolderConfigWindow::FolderConfigWindow(BRect parent, const BMessage& settings)
255	:
256	BWindow(BRect(0, 0, 350, 350), B_TRANSLATE("IMAP Folders"),
257		B_TITLED_WINDOW_LOOK, B_MODAL_APP_WINDOW_FEEL,
258		B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
259	fSettings("in", settings)
260{
261	fQuotaView = new BStringView("quota view",
262		B_TRANSLATE("Failed to fetch available storage."));
263	fQuotaView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
264		B_ALIGN_VERTICAL_CENTER));
265	fQuotaView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
266
267	fFolderListView = new EditListView(B_TRANSLATE("IMAP Folders"));
268	fFolderListView->SetExplicitPreferredSize(BSize(B_SIZE_UNLIMITED,
269		B_SIZE_UNLIMITED));
270
271	BButton* cancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
272		new BMessage(B_QUIT_REQUESTED));
273	BButton* applyButton = new BButton("apply", B_TRANSLATE("Apply"),
274		new BMessage(kMsgApplyButton));
275
276	BLayoutBuilder::Group<>(this, B_VERTICAL)
277		.SetInsets(B_USE_DEFAULT_SPACING)
278		.Add(fQuotaView)
279		.Add(new BScrollView("scroller", fFolderListView, 0, false, true))
280		.AddGroup(B_HORIZONTAL)
281			.AddGlue()
282			.Add(cancelButton)
283			.Add(applyButton);
284
285	PostMessage(kMsgInit);
286	CenterIn(parent);
287}
288
289
290void
291FolderConfigWindow::MessageReceived(BMessage* message)
292{
293	switch (message->what) {
294		case kMsgInit:
295			_LoadFolders();
296			break;
297
298		case kMsgApplyButton:
299			_ApplyChanges();
300			PostMessage(B_QUIT_REQUESTED);
301			break;
302
303		default:
304			BWindow::MessageReceived(message);
305	}
306}
307
308
309void
310FolderConfigWindow::_LoadFolders()
311{
312	StatusWindow* statusWindow = new StatusWindow(this,
313		B_TRANSLATE("Fetching IMAP folders, please be patient" B_UTF8_ELLIPSIS));
314	statusWindow->Show();
315
316	status_t status = fProtocol.Connect(fSettings.ServerAddress(),
317		fSettings.Username(), fSettings.Password(), fSettings.UseSSL());
318	if (status != B_OK) {
319		statusWindow->PostMessage(B_QUIT_REQUESTED);
320
321		// Show error message on screen
322		BString message = B_TRANSLATE("Could not connect to server "
323			"\"%server%\":\n%error%");
324		message.ReplaceFirst("%server%", fSettings.Server());
325		message.ReplaceFirst("%error%", strerror(status));
326		BAlert* alert = new BAlert("IMAP error", message.String(),
327			B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
328		alert->Go();
329
330		PostMessage(B_QUIT_REQUESTED);
331		return;
332	}
333
334	// TODO: don't get all of them at once, but retrieve them level by level
335	fFolderList.clear();
336	BString separator;
337	fProtocol.GetFolders(fFolderList, separator);
338	for (size_t i = 0; i < fFolderList.size(); i++) {
339		IMAP::FolderEntry& entry = fFolderList[i];
340		CheckBoxItem* item = new CheckBoxItem(
341			MailboxToFolderName(entry.folder, separator), entry.subscribed);
342		fFolderListView->AddItem(item);
343		item->SetListView(fFolderListView);
344	}
345
346	uint64 used, total;
347	if (fProtocol.GetQuota(used, total) == B_OK) {
348		BString quotaString;
349		char usedBuffer[128], totalBuffer[128];
350		quotaString.SetToFormat(B_TRANSLATE("Server storage: %s / %s used."),
351			string_for_size(used, usedBuffer, 128),
352			string_for_size(total, totalBuffer, 128));
353		fQuotaView->SetText(quotaString);
354	}
355
356	statusWindow->PostMessage(B_QUIT_REQUESTED);
357}
358
359
360void
361FolderConfigWindow::_ApplyChanges()
362{
363	bool haveChanges = false;
364	for (size_t i = 0; i < fFolderList.size(); i++) {
365		IMAP::FolderEntry& entry = fFolderList[i];
366		CheckBoxItem* item = (CheckBoxItem*)fFolderListView->ItemAt(i);
367		if (entry.subscribed != item->Checked()) {
368			haveChanges = true;
369			break;
370		}
371	}
372	if (!haveChanges)
373		return;
374
375	StatusWindow* status = new StatusWindow(this,
376		B_TRANSLATE("Updating subscriptions to IMAP folders, please be patient"
377		B_UTF8_ELLIPSIS));
378	status->Show();
379
380	for (size_t i = 0; i < fFolderList.size(); i++) {
381		IMAP::FolderEntry& entry = fFolderList[i];
382		CheckBoxItem* item = (CheckBoxItem*)fFolderListView->ItemAt(i);
383		if (entry.subscribed && !item->Checked())
384			fProtocol.UnsubscribeFolder(entry.folder);
385		else if (!entry.subscribed && item->Checked())
386			fProtocol.SubscribeFolder(entry.folder);
387	}
388
389	status->PostMessage(B_QUIT_REQUESTED);
390}
391
392