1 /*
2 ** Copyright 2003-2008, Double Precision Inc.
3 **
4 ** See COPYING for distribution information.
5 */
6 
7 #include "config.h"
8 #include "curses/mycurses.H"
9 #include "curses/cursesmainscreen.H"
10 #include "curses/cursesstatusbar.H"
11 #include "curseshierarchy.H"
12 #include "gettext.H"
13 #include "filter.H"
14 #include "passwordlist.H"
15 #include "libmail/mail.H"
16 #include "libmail/misc.H"
17 #include "libmail/maildir.H"
18 #include "libmail/logininfo.H"
19 #include "myfolder.H"
20 #include "mymessage.H"
21 #include "myserver.H"
22 #include "myservercallback.H"
23 #include "myserverpromptinfo.H"
24 #include "myserverlogincallback.H"
25 #include "myserverremoteconfig.H"
26 #include "opensubfolders.H"
27 #include "specialfolder.H"
28 #include "globalkeys.H"
29 #include "addressbook.H"
30 #include "colors.H"
31 #include "acl.H"
32 #include <errno.h>
33 #include <set>
34 #include <algorithm>
35 
36 using namespace std;
37 
38 extern struct CustomColor color_fl_accountName,
39 	color_fl_accountType,
40 	color_fl_folderName,
41 	color_fl_folderDirName,
42 	color_fl_messageCount;
43 
44 extern Gettext::Key key_ADDFOLDER;
45 extern Gettext::Key key_RENAME;
46 extern Gettext::Key key_DELFOLDER;
47 extern Gettext::Key key_FILTER;
48 extern Gettext::Key key_EDIT;
49 extern Gettext::Key key_SPECIAL;
50 extern Gettext::Key key_ABORT;
51 extern Gettext::Key key_ADDDIRECTORY;
52 extern Gettext::Key key_DOWN;
53 extern Gettext::Key key_UP;
54 
55 extern Gettext::Key key_ADDRESSBOOK;
56 extern Gettext::Key key_DRAFTS;
57 extern Gettext::Key key_REMOTE;
58 extern Gettext::Key key_SENT;
59 extern Gettext::Key key_TOP;
60 extern Gettext::Key key_RESET;
61 extern Gettext::Key key_PERMS;
62 extern Gettext::Key key_PERMVIEW;
63 extern Gettext::Key key_PERMEDIT;
64 
65 extern CursesStatusBar *statusBar;
66 extern CursesMainScreen *mainScreen;
67 
68 extern void hierarchyScreen(void *);
69 extern void mainMenu(void *);
70 extern void editAccountScreen(void *);
71 extern void quitScreen(void *);
72 extern bool isMaildir(string);
73 
74 extern char32_t ucplus, ucasterisk;
75 
DrawIterator(CursesHierarchy * meArg,bool doEraseArg,bool allArg)76 CursesHierarchy::DrawIterator::DrawIterator(CursesHierarchy *meArg,
77 					    bool doEraseArg,
78 					    bool allArg)
79 	: me(meArg), doErase(doEraseArg), all(allArg)
80 {
81 }
82 
~DrawIterator()83 CursesHierarchy::DrawIterator::~DrawIterator()
84 {
85 }
86 
visit(Hierarchy::Folder * f)87 bool CursesHierarchy::DrawIterator::visit(Hierarchy::Folder *f)
88 {
89 	bool rc=me->drawErase(f, doErase);
90 
91 	if (all)
92 		rc=true;
93 	return rc;
94 }
95 
visit(Hierarchy::Server * s)96 bool CursesHierarchy::DrawIterator::visit(Hierarchy::Server *s)
97 {
98 	bool rc=me->drawErase(s, doErase);
99 
100 	if (all)
101 		rc=true;
102 	return rc;
103 }
104 
105 /////////////////////
106 
KeyHandlerIterator(CursesHierarchy * meArg,const Curses::Key & keyArg)107 CursesHierarchy::KeyHandlerIterator::KeyHandlerIterator(CursesHierarchy *meArg,
108 							const Curses::Key
109 							&keyArg)
110 	: me(meArg), key(keyArg), consumed(false)
111 {
112 }
113 
~KeyHandlerIterator()114 CursesHierarchy::KeyHandlerIterator::~KeyHandlerIterator()
115 {
116 }
117 
visit(Hierarchy::Folder * f)118 bool CursesHierarchy::KeyHandlerIterator::visit(Hierarchy::Folder *f)
119 {
120 	consumed=me->processKeyInFocus(f, key);
121 	return true;
122 }
123 
visit(Hierarchy::Server * s)124 bool CursesHierarchy::KeyHandlerIterator::visit(Hierarchy::Server *s)
125 {
126 	consumed=me->processKeyInFocus(s, key);
127 	return true;
128 }
129 
130 
131 
132 
133 /////////////////////
134 
135 int CursesHierarchy::currentRowNum=0;
136 
CursesHierarchy(Hierarchy * h,CursesContainer * parent)137 CursesHierarchy::CursesHierarchy(Hierarchy *h, CursesContainer *parent) :
138 	Curses(parent), HierarchyDisplay(h),
139 	CursesKeyHandler(PRI_SCREENHANDLER),
140 	selectingFolder(false),
141 	folderSelected(NULL),
142 	serverSelected(NULL)
143 {
144 	h->screenOpened();
145 }
146 
~CursesHierarchy()147 CursesHierarchy::~CursesHierarchy()
148 {
149 }
150 
drawErase(Hierarchy::Folder * folder,bool doErase)151 bool CursesHierarchy::drawErase(Hierarchy::Folder *folder, bool doErase)
152 {
153 	const mail::folder *f=folder->getFolder();
154 
155 	Hierarchy::Server *s=folder->getParent()->toServer();
156 
157 	size_t n=folder->getMessageCount();
158 	size_t u=folder->getUnreadCount();
159 
160 	string render1, render2;
161 
162 	int color1;
163 
164 	if (s && s->getServer()->server
165 	    && s->getServer()->server->hasCapability(LIBMAIL_SINGLEFOLDER))
166 	{
167 		const char *fmt;
168 
169 		color1=color_fl_folderName.fcolor;
170 
171 		if (n || u)
172 		{
173 			fmt=u ? _("%1% messages, %2% unread")
174 				: _("%1% messages");
175 		}
176 		else
177 		{
178 			fmt=_("*** empty ***");
179 		}
180 
181 		render1=_("Folder: ");
182 		render2=Gettext(fmt) << n << u;
183 	}
184 	else
185 	{
186 		const char32_t *pfix=f->hasSubFolders() || !f->hasMessages()
187 			? f->hasMessages() ? &ucasterisk:&ucplus:NULL;
188 
189 		color1=color_fl_folderDirName.fcolor;
190 
191 		if (pfix == 0)
192 			color1=color_fl_folderName.fcolor;
193 		else
194 		{
195 			std::u32string buf;
196 
197 			buf.push_back(*pfix);
198 
199 			render1=unicode::iconvert::convert(buf,
200 							unicode_default_chset()
201 							);
202 			render1 += " ";
203 		}
204 
205 		render1 += f->getName();
206 		render1 += " ";
207 
208 		if (n || u)
209 			render2 = Gettext( u ?
210 					   _("(%1% messages, %2% unread)"):
211 					   _("(%1% messages)")) << n << u;
212 	}
213 
214 	return drawErase(render1, color1, render2,
215 			 color_fl_messageCount.fcolor,
216 			 folder, doErase);
217 }
218 
drawErase(Hierarchy::Server * server,bool doErase)219 bool CursesHierarchy::drawErase(Hierarchy::Server *server, bool doErase)
220 {
221 	string name= Gettext("%1%: ") <<
222 		server->getServer()->serverName;
223 
224 	return drawErase(name,
225 			 color_fl_accountName.fcolor,
226 			 server->getServer()->serverDescr,
227 			 color_fl_accountType.fcolor,
228 			 server, doErase);
229 }
230 
drawErase(string render1,int color1,string render2,int color2,Hierarchy::Entry * e,bool doErase)231 bool CursesHierarchy::drawErase(string render1,
232 				int color1,
233 				string render2,
234 				int color2,
235 				Hierarchy::Entry *e,
236 				bool doErase)
237 {
238 	u32string w1, w2;
239 
240 	unicode::iconvert::convert(render1, unicode_default_chset(), w1);
241 	unicode::iconvert::convert(render2, unicode_default_chset(), w2);
242 
243 	widecharbuf wcbuf1, wcbuf2;
244 
245 	wcbuf1.init_unicode(w1.begin(), w1.end());
246 	wcbuf2.init_unicode(w2.begin(), w2.end());
247 
248 	wcbuf1.expandtabs(0);
249 	wcbuf2.expandtabs(0);
250 
251 	w1.clear();
252 	w2.clear();
253 	wcbuf1.tounicode(w1);
254 	wcbuf2.tounicode(w2);
255 
256 	Curses::CursesAttr attribute1;
257 	Curses::CursesAttr attribute2;
258 
259 	attribute1.setFgColor(color1);
260 	attribute2.setFgColor(color2);
261 
262 	if (doErase)
263 	{
264 		w1.clear();
265 		w1.insert(w1.end(), wcbuf1.wcwidth(0), ' ');
266 		w2.clear();
267 		w2.insert(w2.end(), wcbuf2.wcwidth(0), ' ');
268 	}
269 	else if (currentRowNum == e->getRow())
270 	{
271 		attribute1.setReverse();
272 		attribute2.setReverse();
273 	}
274 
275 	int col1=e->getNestingLevel()*2+2;
276 
277 	bool flag1=writeText(w1, e->getRow(), col1, attribute1);
278 
279 	col1 += wcbuf1.wcwidth(0);
280 
281 	bool flag2=writeText(w2, e->getRow(), col1, attribute2);
282 
283 	return (flag1 && flag2);
284 }
285 
286 	// Inherited from Curses:
287 
getWidth()288 int CursesHierarchy::getWidth() const
289 {
290 	return getScreenWidth();
291 }
292 
getHeight()293 int CursesHierarchy::getHeight() const
294 {
295 	return getHierarchy()->getLastRow()->getRow()+1;
296 }
297 
draw()298 void CursesHierarchy::draw()
299 {
300 	DrawIterator iterator(this, false, true);
301 
302 	getHierarchy()->root.prefixIterate(iterator);
303 }
304 
draw(Hierarchy::Entry * e,bool doErase)305 void CursesHierarchy::draw(Hierarchy::Entry *e, bool doErase)
306 {
307 	DrawIterator iterator(this, doErase, false);
308 
309 	e->visit(iterator);
310 }
311 
isFocusable()312 bool CursesHierarchy::isFocusable()
313 {
314 	return true;
315 }
316 
317 // Navigate folder hierarchy
318 
processKeyInFocus(const Curses::Key & key)319 bool CursesHierarchy::processKeyInFocus(const Curses::Key &key)
320 {
321 	Hierarchy::Entry *oldEntry=getHierarchy()->getEntry(currentRowNum);
322 
323 	if (key == key.UP || key == key.PGUP)
324 	{
325 		size_t sh;
326 		size_t firstShownRow;
327 
328 		getVerticalViewport(firstShownRow, sh);
329 
330 		if (sh < 1)
331 			sh=1;
332 
333 		if ((size_t)currentRowNum < sh)
334 			sh=currentRowNum;
335 		else if (sh > 1)
336 			--sh;
337 
338 		if (key == key.UP)
339 			sh=1;
340 
341 		Hierarchy::Entry *newEntry=NULL;
342 		size_t n=currentRowNum;
343 
344 		while (n > 0)
345 		{
346 			--n;
347 
348 			if (sh > 0)
349 				--sh;
350 
351 			Hierarchy::Entry *e=getHierarchy()->getEntry(n);
352 
353 			if (e != NULL)
354 			{
355 				newEntry=e;
356 				currentRowNum=n;
357 				if (sh == 0)
358 					break;
359 			}
360 		}
361 
362 		if (newEntry)
363 		{
364 			draw(oldEntry, false);
365 			draw(newEntry, false);
366 			return true;
367 		}
368 		else
369 		{
370 			Curses *p=getParent();
371 
372 			if (p)
373 				p->scrollTo(0);
374 		}
375 		return true;
376 	}
377 
378 	if (key == key.DOWN || key == key.PGDN)
379 	{
380 		size_t h=getHeight();
381 		size_t n=currentRowNum;
382 
383 		size_t firstRow, sh;
384 
385 		getVerticalViewport(firstRow, sh);
386 
387 		if (sh < 1)
388 			sh=1;
389 
390 		if (firstRow + sh - 1 > (size_t)currentRowNum)
391 			sh=firstRow + sh - 1 - currentRowNum;
392 		else if (sh > 1)
393 			--sh;
394 
395 		if (key == key.DOWN)
396 			sh=1;
397 
398 		Hierarchy::Entry *newEntry=NULL;
399 
400 		while (++n < h)
401 		{
402 			if (sh > 0)
403 				--sh;
404 
405 			Hierarchy::Entry *e=getHierarchy()->getEntry(n);
406 
407 			if (e != NULL)
408 			{
409 				newEntry=e;
410 				currentRowNum=n;
411 				if (sh == 0)
412 					break;
413 			}
414 		}
415 
416 		if (newEntry)
417 		{
418 			draw(oldEntry, false);
419 			draw(newEntry, false);
420 			return true;
421 		}
422 
423 		return true;
424 	}
425 
426 	if (!oldEntry)
427 		return false;
428 
429 	// Over to the other processKeyInFocus() functions...
430 
431 	KeyHandlerIterator kh(this, key);
432 
433 	oldEntry->visit(kh);
434 	return kh.isConsumed();
435 }
436 
processKeyInFocus(Hierarchy::Folder * f,const Curses::Key & key)437 bool CursesHierarchy::processKeyInFocus(Hierarchy::Folder *f,
438 					const Curses::Key &key)
439 {
440 	if (key == key.ENTER)
441 	{
442 		if (f->getFolder()->hasMessages())
443 		{
444 			Hierarchy::Entry *e=f;
445 
446 			while ((e=e->getParent()) != NULL)
447 			{
448 				Hierarchy::Server *s=e->toServer();
449 
450 				if (s != NULL)
451 				{
452 					if (selectingFolder)
453 					{
454 						folderSelected=f->getFolder();
455 						serverSelected=s->getServer();
456 
457 						if (folderSelected &&
458 						    serverSelected)
459 						{
460 							Curses::keepgoing
461 								=false;
462 							return true;
463 						}
464 						folderSelected=NULL;
465 						serverSelected=NULL;
466 						break;
467 					}
468 
469 					s->getServer()->
470 						openFolder(f->getFolder());
471 					break;
472 				}
473 			}
474 		}
475 		else
476 		{
477 			if (f->size() > 0)
478 				closeSubFolders(f);
479 			else
480 				openSubFolders(f);
481 		}
482 		return true;
483 	}
484 
485 	if (key == '+' || key == '=')
486 	{
487 		if (f->size() == 0)
488 			openSubFolders(f);
489 		return true;
490 	}
491 
492 	if (key == '-')
493 	{
494 		if (f->size() > 0)
495 			closeSubFolders(f);
496 		return true;
497 	}
498 	return false;
499 }
500 
501 // Helper class that returns my Folder object.
502 
getCurrentFolder()503 Hierarchy::Folder *CursesHierarchy::getCurrentFolder()
504 {
505 	Hierarchy::Entry *e=getHierarchy()->getEntry(currentRowNum);
506 
507 	if (!e)
508 		return NULL;
509 
510 	return e->toFolder();
511 }
512 
513 // Helper class that finds my server object.
514 
515 class CursesHierarchyFindServerIterator : public Hierarchy::EntryIterator {
516 public:
517 	myServer *server;
518 
519 	CursesHierarchyFindServerIterator();
520 	~CursesHierarchyFindServerIterator();
521 
522 	bool visit(Hierarchy::Server *s);
523 	static myServer *find(Hierarchy::Entry *e);
524 };
525 
CursesHierarchyFindServerIterator()526 CursesHierarchyFindServerIterator::CursesHierarchyFindServerIterator()
527 	: server(NULL)
528 {
529 }
530 
~CursesHierarchyFindServerIterator()531 CursesHierarchyFindServerIterator::~CursesHierarchyFindServerIterator()
532 {
533 }
534 
visit(Hierarchy::Server * s)535 bool CursesHierarchyFindServerIterator::visit(Hierarchy::Server *s)
536 {
537 	server=s->getServer();
538 	return true;
539 }
540 
find(Hierarchy::Entry * e)541 myServer *CursesHierarchyFindServerIterator::find(Hierarchy::Entry *e)
542 {
543 	CursesHierarchyFindServerIterator iterator;
544 
545 	while (e != NULL && iterator.server == NULL)
546 	{
547 		e->visit(iterator);
548 		e=e->getParent();
549 	}
550 	return iterator.server;
551 }
552 
openSubFolders(Hierarchy::Folder * f)553 void CursesHierarchy::openSubFolders(Hierarchy::Folder *f)
554 {
555 	myServer *s=CursesHierarchyFindServerIterator::find(f);
556 
557 	if (!s)
558 		return;
559 
560 	statusBar->clearstatus();
561 	statusBar->status(Gettext(_("Opening %1%..."))
562 			  << f->getFolder()->getName());
563 
564 	OpenSubFoldersCallback openSubfolders;
565 	{
566 		myServer::Callback callback;
567 		f->getFolder()->readSubFolders(openSubfolders, callback);
568 		myServer::eventloop(callback);
569 	}
570 
571 	sort(openSubfolders.folders.begin(),
572 	     openSubfolders.folders.end(),
573 	     mail::folder::sort(false));
574 
575 	vector<mail::folder *>::iterator
576 		b=openSubfolders.folders.begin(),
577 		e=openSubfolders.folders.end();
578 
579 	drawEraseBelow(f, true);
580 
581 	b=openSubfolders.folders.begin();
582 	e=openSubfolders.folders.end();
583 
584 	vector<Hierarchy::Folder *> folders;
585 
586 	Hierarchy::Server *parentServer=f->getServer();
587 
588 	set<string> topmostFolders;
589 
590 	if (parentServer) // Opening top-level folders.
591 	{
592 		vector<Hierarchy::Entry *>::iterator tb=parentServer->begin(),
593 			te=parentServer->end();
594 
595 		while (tb != te)
596 		{
597 			Hierarchy::Folder *f= (*tb)->toFolder();
598 
599 			tb++;
600 
601 			if (f)
602 				topmostFolders.insert(f->getFolder()
603 						      ->getPath());
604 		}
605 	}
606 
607 	while (b != e)
608 	{
609 		string fid= (*b)->getPath();
610 
611 		// Do not show folders listed at the topmost level
612 
613 		if (topmostFolders.count(fid) > 0)
614 		{
615 			b++;
616 			continue;
617 		}
618 
619 		Hierarchy::Folder *child=
620 			new Hierarchy::Folder(myServer::hierarchy, f,
621 					      *b++);
622 
623 		if (!child)
624 			outofmemory();
625 		folders.push_back(child);
626 	}
627 
628 	drawEraseBelow(f, false); // Redraw the display below.
629 
630 	s->openedHierarchy(f, openSubfolders.folders);
631 
632 	vector<Hierarchy::Folder *>::iterator sb=folders.begin(),
633 		se=folders.end();
634 
635 	while (sb != se)
636 	{
637 		(*sb++)->updateInfo(s, true); // Get folder sizes
638 	}
639 }
640 
641 //
642 // Make sure that 'e' is visible.  This is usually needed after opening
643 // subfolders.  If the parent folder is on the last line of the display then
644 // there's no immediate visual indication that subfolders were opened.
645 // Fix this by temporarily fudging the focus to be on the first opened
646 // subfolder, if its off the screen then getCursorPosition() will scroll it.
647 // Afterwards restore cursor position where it was.
648 
visible(Hierarchy::Entry * e)649 void CursesHierarchy::visible(Hierarchy::Entry *e)
650 {
651 	Hierarchy::Entry *oldEntry=getHierarchy()->getEntry(currentRowNum);
652 
653 	if (!oldEntry)
654 		return;
655 
656 	int saveRowNum=currentRowNum;
657 
658 	int dummyrow;
659 	int dummycol;
660 
661 	currentRowNum=e->getRow();
662 	getCursorPosition(dummyrow, dummycol);
663 	currentRowNum=saveRowNum;
664 	draw( e, false);
665 	draw( oldEntry, false);
666 }
667 
closeSubFolders(Hierarchy::Folder * f)668 void CursesHierarchy::closeSubFolders(Hierarchy::Folder *f)
669 {
670 	drawEraseBelow(f, true);
671 	f->clear();
672 	drawEraseBelow(f, false);
673 }
674 
675 //
676 // Make sure that this server is logged into.
677 
autologin(myServer * ss)678 bool CursesHierarchy::autologin(myServer *ss)
679 {
680 	if (ss->server)
681 		return true;
682 
683 	string savedPwd;
684 
685 	if (!PasswordList::passwordList.check(ss->url, savedPwd) ||
686 	    !ss->login(savedPwd))
687 	{
688 		ss->disconnect();
689 		PasswordList::passwordList.remove(ss->url,
690 						  _("Login failed"));
691 
692 		myServerLoginCallback loginCallback;
693 
694 		if (!ss->login(loginCallback))
695 		{
696 			ss->disconnect();
697 			statusBar->beepError();
698 			return false;
699 		}
700 
701 		PasswordList::passwordList.save(ss->url, ss->password);
702 	}
703 
704 	return true;
705 }
706 
processKeyInFocus(Hierarchy::Server * s,const Curses::Key & key)707 bool CursesHierarchy::processKeyInFocus(Hierarchy::Server *s,
708 					const Curses::Key &key)
709 {
710 	if (key == key.ENTER)
711 	{
712 		bool doShowHierarchy=false;
713 
714 		//
715 		// Automatically open the folder of a SINGLEFOLDER account.
716 		// Now, we used to call ss->showHierarchy() immediately
717 		// after autologin() returned true.  What happened was that
718 		// this resulted in a new line being shown on the screen,
719 		// that contained the number of messages in the folder.
720 		// Hierarchy::Folder::processKeyInFocus() would then get
721 		// called next, which opens the folder.
722 		//
723 		// The initial reaction is to hit enter again, after the
724 		// folder size is displayed; however the folder open process
725 		// is already kicked off automatically, and is already in
726 		// progress.
727 		//
728 		// To fix this, CursesMainScreen::Lock structure was created
729 		// which suppresses screen output, and ss->showHierarchy
730 		// is called while the output lock is being held (in the case
731 		// where the account is a SINGLEFOLDER account.  This looks
732 		// much nicer.
733 		//
734 
735 		myServer *ss=s->getServer();
736 
737 		if (ss->server == NULL ||
738 		    s->size() == 0)
739 		{
740 			if (autologin(ss))
741 				doShowHierarchy=true;
742 			else
743 				return true;
744 		}
745 
746 		// Automatically open the folder in single-folder accounts.
747 
748 		if (ss->server &&
749 		    ss->server->hasCapability(LIBMAIL_SINGLEFOLDER))
750 		{
751 			CursesMainScreen::Lock updateLock(mainScreen);
752 
753 			if (doShowHierarchy)
754 				ss->showHierarchy();
755 
756 			if (ss->server && s->size() == 1)
757 			{
758 				Hierarchy::Folder *f=(*s->begin())->toFolder();
759 
760 				if (f)
761 					processKeyInFocus(f, key);
762 			}
763 		}
764 		else
765 		{
766 			if (doShowHierarchy)
767 				ss->showHierarchy();
768 		}
769 
770 		return true;
771 	}
772 
773 	return false;
774 }
775 
getCursorPosition(int & row,int & col)776 int CursesHierarchy::getCursorPosition(int &row, int &col)
777 {
778 	Hierarchy::Entry *e;
779 	bool refocus=false;
780 
781 	// Try to adjust cursor position
782 
783 	while ((e=getHierarchy()->getEntry(currentRowNum)) == NULL)
784 	{
785 		if (currentRowNum == 0)
786 		{
787 			while (currentRowNum < 10)
788 			{
789 				e=getHierarchy()->getEntry(++currentRowNum);
790 				if (e)
791 				{
792 					refocus=true;
793 					break;
794 				}
795 			}
796 			if (e == NULL)
797 				currentRowNum=0;
798 			break;
799 		}
800 		--currentRowNum;
801 		refocus=true;
802 	}
803 
804 	if (refocus && e)
805 		draw(e);
806 
807 	row=currentRowNum;
808 	if (e)
809 	{
810 		col=e->getNestingLevel() * 2 + 1;
811 
812 		Curses::getCursorPosition(row, col);
813 		return 0;
814 	}
815 	col=0;
816 	Curses::getCursorPosition(row, col);
817 	return 0;
818 }
819 
820 //
821 // On entry to the curses screen, try to reposition the cursor where we left
822 // off.
823 
requestFocus()824 void CursesHierarchy::requestFocus()
825 {
826 	Hierarchy::Entry *e=getHierarchy()->getEntry(currentRowNum);
827 
828 	if (e)
829 	{
830 		if (e->toFolder() != NULL || e->toServer() != NULL)
831 		{
832 			Curses::requestFocus();
833 			return;
834 		}
835 	}
836 
837 	vector<Hierarchy::Entry *>::iterator sb, se, fb, fe;
838 
839 	sb=myServer::hierarchy.root.begin();
840 	se=myServer::hierarchy.root.end();
841 
842 	while (sb != se)
843 	{
844 		fb=(*sb)->begin();
845 		fe=(*sb)->end();
846 
847 		while (fb != fe)
848 		{
849 			currentRowNum=(*fb)->getRow();
850 			Curses::requestFocus();
851 			return;
852 		}
853 
854 		sb++;
855 	}
856 
857 	currentRowNum=0;
858 	Curses::requestFocus();
859 }
860 
861 //
862 // This node's children were added or removed.  We need to move a portion of
863 // the hierarchy display.
864 //
865 
drawEraseBelow(Hierarchy::Entry * e,bool doErase)866 void CursesHierarchy::drawEraseBelow(Hierarchy::Entry *e, bool doErase)
867 {
868 	if (doErase)
869 	{
870 		// When we're about to do something (first step is to erase
871 		// the entry) make sure that's where the cursor is.
872 
873 		currentRowNum=e->getRow();
874 		int dummyRow, dummyCol;
875 		getCursorPosition(dummyRow, dummyCol); // Perhaps a scroll?
876 	}
877 	else	// After we did something, renumber rows.
878 	{
879 		Hierarchy::EntryIteratorAssignRow iterator(e->getRow());
880 
881 		e->prefixIterateAfter(iterator);
882 	}
883 
884 	DrawIterator iterator(this, doErase, false);
885 
886 	e->prefixIterateAfter( iterator );
887 }
888 
889 ///////////////////////////////////////////////////////////////////////////
890 
891 class CursesHierarchyAddFolderKeyHandler : public CursesKeyHandler {
892 public:
893 	bool addDirectory;
894 
895 	CursesHierarchyAddFolderKeyHandler();
896 	~CursesHierarchyAddFolderKeyHandler();
897 	bool processKey(const Curses::Key &key);
898 	bool listKeys( vector< pair<string, string> > &list);
899 };
900 
901 
902 
CursesHierarchyAddFolderKeyHandler()903 CursesHierarchyAddFolderKeyHandler::CursesHierarchyAddFolderKeyHandler()
904 		: CursesKeyHandler(PRI_STATUSOVERRIDEHANDLER),
905 		  addDirectory(false)
906 {
907 }
908 
~CursesHierarchyAddFolderKeyHandler()909 CursesHierarchyAddFolderKeyHandler::~CursesHierarchyAddFolderKeyHandler()
910 {
911 }
912 
processKey(const Curses::Key & key)913 bool CursesHierarchyAddFolderKeyHandler::processKey(const Curses::Key &key)
914 {
915 	if (key == key_ADDDIRECTORY)
916 	{
917 		addDirectory=true;
918 		statusBar->fieldEnter();
919 		return true;
920 	}
921 
922 	return false;
923 }
924 
925 bool CursesHierarchyAddFolderKeyHandler
listKeys(vector<pair<string,string>> & list)926 ::listKeys(vector< pair<string, string> > &list)
927 {
928 	list.push_back( make_pair(Gettext::
929 				  keyname(_("ADDDIRECTORY:^D")),
930 				  _("Add Folder Directory")));
931 	return true;
932 }
933 
934 ///////////
935 //
936 // Helper class to reverify folder's existence after a DELETE
937 
938 
939 class CursesHierarchyDeleteFolderVerifyCallback :
940 	public mail::callback::folderList {
941 
942 	CursesHierarchy *me;
943 	Hierarchy::Folder *parent, *deleted;
944 
945 public:
946 	CursesHierarchyDeleteFolderVerifyCallback(CursesHierarchy *meArg,
947 						  Hierarchy::Folder *parentArg,
948 						  Hierarchy::Folder
949 						  *deletedArg);
950 	~CursesHierarchyDeleteFolderVerifyCallback();
951 	void success(const vector<const mail::folder *> &folders);
952 };
953 
954 CursesHierarchyDeleteFolderVerifyCallback::
CursesHierarchyDeleteFolderVerifyCallback(CursesHierarchy * meArg,Hierarchy::Folder * parentArg,Hierarchy::Folder * deletedArg)955 CursesHierarchyDeleteFolderVerifyCallback(CursesHierarchy *meArg,
956 					  Hierarchy::Folder *parentArg,
957 					  Hierarchy::Folder *deletedArg)
958 	: me(meArg), parent(parentArg), deleted(deletedArg)
959 {
960 }
961 
962 CursesHierarchyDeleteFolderVerifyCallback::
~CursesHierarchyDeleteFolderVerifyCallback()963 ~CursesHierarchyDeleteFolderVerifyCallback()
964 {
965 }
966 
967 void CursesHierarchyDeleteFolderVerifyCallback
success(const vector<const mail::folder * > & folders)968 ::success(const vector<const mail::folder *> &folders)
969 {
970 	vector<const mail::folder *>::const_iterator b=folders.begin(),
971 		e=folders.end();
972 
973 	const mail::folder *updatedFolder=NULL;
974 
975 	while (b != e)
976 	{
977 		const mail::folder *f= *b++;
978 		if ( f->getName() ==
979 		     deleted->getFolder()->getName())
980 		{
981 			updatedFolder=f;
982 			break;
983 		}
984 	}
985 
986 	me->processDeletedFolder(parent, deleted, updatedFolder);
987 }
988 
visit(Hierarchy::Folder * f)989 bool CursesHierarchy::RefreshIterator::visit(Hierarchy::Folder* f)
990 {
991 	if(f != NULL && this->cs != NULL)
992 		f->updateInfo(this->cs, true);
993 	return true;
994 }
995 
visit(Hierarchy::Server * s)996 bool CursesHierarchy::RefreshIterator::visit(Hierarchy::Server* s)
997 {
998 	if(s != NULL)
999 		this->cs = s->getServer();
1000 	return true;
1001 }
1002 
refreshAllFolders()1003 void CursesHierarchy::refreshAllFolders()
1004 {
1005 	RefreshIterator iterator;
1006 	getHierarchy()->root.prefixIterate(iterator);
1007 }
1008 
1009 ///////////
processKey(const Curses::Key & key)1010 bool CursesHierarchy::processKey(const Curses::Key &key)
1011 {
1012 	if (selectingFolder)
1013 	{
1014 		if (key == key_ABORT)
1015 		{
1016 			folderSelected=NULL;
1017 			Curses::keepgoing=false;
1018 			return true;
1019 		}
1020 
1021 		return false;
1022 	}
1023 
1024 	if (key == key_ADDFOLDER)
1025 	{
1026 		bool adddir;
1027 		string folder;
1028 
1029 		Hierarchy::Folder *f=getCurrentFolder();
1030 
1031 		myServer *serverPtr;
1032 
1033 		if (f == NULL) // Add shortcut to a server manually.
1034 		{
1035 			Hierarchy::Entry *e=getHierarchy()
1036 				->getEntry(currentRowNum);
1037 
1038 			if (!e || !e->toServer())
1039 				return true;
1040 
1041 			myServer::promptInfo response=myServer
1042 				::prompt(myServer::
1043 					 promptInfo(Gettext(_("Add folder/directory in %1%: "))
1044 						    << (serverPtr=e->toServer()
1045 							->getServer())
1046 						    ->serverName));
1047 			if (response.aborted() || serverPtr->server == NULL)
1048 				return true;
1049 
1050 			string path=response;
1051 
1052 			myServer *ms=e->toServer()->getServer();
1053 
1054 			if (ms->server == NULL && !autologin(ms))
1055 				return true;
1056 
1057 			if (path.size() > 0)
1058 			{
1059 				path=ms->server->translatePath(path);
1060 
1061 				if (path.size() == 0)
1062 				{
1063 					statusBar->status(Gettext(_("Invalid folder name: %s"))
1064 							  << strerror(errno));
1065 					statusBar->beepError();
1066 					return true;
1067 				}
1068 			}
1069 
1070 			myServer::Callback callback;
1071 			myServer::CreateFolderCallback searchCallback;
1072 
1073 			ms->server->findFolder(path,
1074 					       searchCallback, callback);
1075 
1076 			if (!myServer::eventloop(callback))
1077 				return true;
1078 
1079 			if (searchCallback.folderFound == NULL)
1080 			{
1081 				statusBar->status(_("Folder not found."));
1082 				statusBar->beepError();
1083 				return true;
1084 			}
1085 
1086 			if (searchCallback.folderFound->hasMessages() &&
1087 			    searchCallback.folderFound->hasSubFolders())
1088 			{
1089 				response=
1090 					myServer
1091 					::prompt(myServer::
1092 						 promptInfo(Gettext(_("Add %1% as a folder directory? (Y/N) "))
1093 							    << (string)response
1094 						    ).yesno());
1095 
1096 				if (response.aborted()
1097 				    || serverPtr->server == NULL)
1098 					return true;
1099 
1100 				if ((string)response == "Y")
1101 					searchCallback.folderFound
1102 						->hasMessages(false);
1103 				else
1104 					searchCallback.folderFound
1105 						->hasSubFolders(false);
1106 
1107 			}
1108 			drawEraseBelow(ms->hierarchyEntry, true);
1109 
1110 			Hierarchy::Folder *hf=
1111 				new Hierarchy::Folder(e->getHierarchy(),
1112 						      ms->hierarchyEntry,
1113 						      searchCallback
1114 						      .folderFound);
1115 
1116 			if (!hf)
1117 				outofmemory();
1118 
1119 			drawEraseBelow(ms->hierarchyEntry, false);
1120 			ms->updateHierarchy();
1121 			hf->updateInfo(ms, false);
1122 			return true;
1123 		}
1124 		else
1125 		{
1126 			Hierarchy::Server *s=f->getServer();
1127 
1128 			if (!s || !(serverPtr=s->getServer()))
1129 				return true;
1130 		}
1131 
1132 		if (!f->getFolder()->hasSubFolders() &&
1133 		    f->size() == 0)
1134 		{
1135 			// "A" on a top-level folder (not a folder directory)
1136 			// is a noop.
1137 
1138 			Hierarchy::Entry *e=f->getParent();
1139 
1140 			if (e == NULL || (f=e->toFolder()) == NULL)
1141 				return true;
1142 		}
1143 
1144 		{
1145 			CursesHierarchyAddFolderKeyHandler add_key_handler;
1146 
1147 			myServer::promptInfo response=
1148 				myServer
1149 				::prompt(myServer
1150 					 ::promptInfo(Gettext(_("Create new folder in %1%: "))
1151 						      << f->getFolder()->getName()
1152 						      ));
1153 
1154 			if (response.aborted() || serverPtr->server == NULL)
1155 				return true;
1156 			folder=response;
1157 			adddir=add_key_handler.addDirectory;
1158 		}
1159 
1160 		if (adddir)
1161 		{
1162 			myServer::promptInfo response=
1163 				myServer
1164 				::prompt(myServer
1165 					 ::promptInfo(Gettext(_("Create new folder directory in %1%: "))
1166 						      << f->getFolder()->getName()
1167 						      )
1168 					 .initialValue(folder));
1169 
1170 			if (response.aborted() || serverPtr->server == NULL)
1171 				return true;
1172 			folder=response;
1173 		}
1174 
1175 		if (folder.size() == 0)
1176 			return true;
1177 
1178 		if (f->size() == 0) // Make sure it's open now...
1179 			openSubFolders(f);
1180 
1181 		statusBar->clearstatus();
1182 
1183 		// If this folder already exists, we must now be creating
1184 		// a folder that both has subfolders, and messages.
1185 		// Search for an existing folder with the same name.
1186 
1187 		vector<Hierarchy::Entry *>::iterator b=f->begin(), e=f->end();
1188 
1189 		Hierarchy::Folder *oldFolder=NULL;
1190 
1191 		bool hasSubfolders=false;
1192 		bool hasMessages=false;
1193 
1194 		while (b != e)
1195 		{
1196 			Hierarchy::Entry *entryPtr= *b++;
1197 
1198 			Hierarchy::Folder *p= entryPtr->toFolder();
1199 
1200 			if (p != NULL &&
1201 			    folder == p->getFolder()->getName())
1202 			{
1203 				oldFolder=p;
1204 				hasMessages=p->getFolder()->hasMessages();
1205 				hasSubfolders=p->getFolder()->hasSubFolders();
1206 				break;
1207 			}
1208 		}
1209 
1210 		if (adddir)
1211 			hasSubfolders=true;
1212 		else
1213 			hasMessages=true;
1214 
1215 
1216 		myServer::Callback callback;
1217 		myServer::CreateFolderCallback newsubfolder;
1218 
1219 		f->getFolder()->createSubFolder(folder, adddir,
1220 						newsubfolder, callback);
1221 
1222 		if (!myServer::eventloop(callback)
1223 		    || newsubfolder.folderFound == NULL)
1224 			return true;
1225 
1226 		mail::folder *newFolder=newsubfolder.folderFound;
1227 
1228 		// Now, update the hierarchy tree.
1229 
1230 		newFolder->hasMessages(hasMessages);
1231 		newFolder->hasSubFolders(hasSubfolders);
1232 
1233 		drawEraseBelow(f, true);
1234 
1235 		Hierarchy::Folder *addFolder=
1236 			new Hierarchy::Folder(f->getHierarchy(),
1237 					      f, newFolder);
1238 
1239 		if (!addFolder)
1240 			outofmemory();
1241 
1242 		if (oldFolder)
1243 		{
1244 			addFolder->setMessageCount(oldFolder->
1245 						   getMessageCount());
1246 			addFolder->setUnreadCount(oldFolder->
1247 						  getUnreadCount());
1248 
1249 			delete oldFolder;
1250 		}
1251 
1252 		f->resortChildren();
1253 		drawEraseBelow(f, false);
1254 
1255 		Hierarchy::Entry *oldCursor
1256 			=getHierarchy()->getEntry(currentRowNum);
1257 
1258 		currentRowNum=addFolder->getRow();
1259 
1260 		if (oldCursor)
1261 			draw(oldCursor, false);
1262 		draw(addFolder, false);
1263 
1264 		return true;
1265 	}
1266 
1267 	if (key == key_RENAME)
1268 	{
1269 		Hierarchy::Folder *oldFolder=getCurrentFolder(), *parent;
1270 		Hierarchy::Entry *parentEntry;
1271 
1272 		// Cannot rename top level folders.
1273 		if (!oldFolder || !(parentEntry=oldFolder->getParent()) ||
1274 		    !(parent=parentEntry->toFolder()))
1275 		{
1276 			statusBar->status("Cannot rename this folder.");
1277 			statusBar->beepError();
1278 			return true;
1279 		}
1280 
1281 		Hierarchy::Server *s=oldFolder->getServer();
1282 		myServer *serverPtr;
1283 
1284 		if (!s || !(serverPtr=s->getServer()))
1285 			return true;
1286 
1287 		myServer::promptInfo response=
1288 			myServer
1289 			::prompt(myServer::
1290 				 promptInfo(_("New name: ")));
1291 
1292 		if (response.aborted() || serverPtr->server == NULL)
1293 			return true;
1294 
1295 		string newname=(string)response;
1296 
1297 		if (newname.size() == 0)
1298 			return true;
1299 
1300 		myServer::Callback callback;
1301 		myServer::CreateFolderCallback newsubfolder;
1302 
1303 		oldFolder->getFolder()->renameFolder(parent->getFolder(),
1304 						     newname,
1305 						     newsubfolder, callback);
1306 
1307 		if (!myServer::eventloop(callback)
1308 		    || newsubfolder.folderFound == NULL)
1309 			return true;
1310 
1311 		Hierarchy::Entry *oldCursor
1312 			=getHierarchy()->getEntry(currentRowNum);
1313 
1314 		drawEraseBelow(parentEntry, true);
1315 
1316 		if (oldCursor)
1317 			draw(oldCursor, true);
1318 
1319 		Hierarchy::Folder *addFolder=
1320 			new Hierarchy::Folder(parentEntry->getHierarchy(),
1321 					      parentEntry,
1322 					      newsubfolder.folderFound);
1323 
1324 		delete oldFolder;
1325 
1326 		parent->resortChildren();
1327 		drawEraseBelow(parent, false);
1328 
1329 		oldCursor=getHierarchy()->getEntry(currentRowNum);
1330 
1331 		currentRowNum=addFolder->getRow();
1332 
1333 		draw(addFolder, false);
1334 		if (oldCursor)
1335 			draw(oldCursor, false);
1336 
1337 		return true;
1338 	}
1339 
1340 	if (key == key_PERMS)
1341 	{
1342 		Hierarchy::Folder *currentFolder=getCurrentFolder();
1343 		Hierarchy::Server *s;
1344 
1345 		if (!currentFolder ||
1346 		    !(s=currentFolder->getServer()))
1347 			return true;
1348 
1349 		myServer::promptInfo prompt=
1350 			myServer::promptInfo(_("Permissions: (V/E) ")
1351 					     )
1352 			.option(key_PERMVIEW,
1353 				_("V"),
1354 				_("View permissions"))
1355 			.option(key_PERMEDIT,
1356 				_("E"),
1357 				_("Edit permissions"));
1358 
1359 		prompt=myServer::prompt(prompt);
1360 
1361 		if (prompt.abortflag)
1362 		{
1363 			return true;
1364 		}
1365 
1366 		currentFolder=getCurrentFolder();
1367 
1368 		if (!currentFolder ||
1369 		    !(s=currentFolder->getServer()))
1370 			return true;
1371 
1372 		u32string ka;
1373 
1374 		unicode::iconvert::convert((string)prompt,
1375 					unicode_default_chset(), ka);
1376 
1377 		if (ka.size() == 0)
1378 			return true;
1379 
1380 		if (key_PERMVIEW == ka[0])
1381 		{
1382 			statusBar->clearstatus();
1383 			statusBar->status(_("Checking folder permissions..."));
1384 
1385 			string perms;
1386 			myServer::Callback callback;
1387 
1388 			currentFolder->getFolder()->
1389 				getMyRights(callback, perms);
1390 
1391 			if (!myServer::eventloop(callback))
1392 				return true;
1393 
1394 			string permsVerbose="";
1395 			size_t i;
1396 
1397 			for (i=0; i<perms.size(); i++)
1398 			{
1399 				if (permsVerbose.size() > 0)
1400 					permsVerbose += ", ";
1401 				permsVerbose += Acl(perms.substr(i, 1))
1402 					.getDescription();
1403 			}
1404 			if (permsVerbose.size() == 0)
1405 				permsVerbose=_("No permissions");
1406 			statusBar->clearstatus();
1407 			statusBar->
1408 				status(Gettext(_("You have the following "
1409 						 "permissions on this folder:"
1410 						 "\n\n%1%."))
1411 				       << permsVerbose);
1412 			return true;
1413 		}
1414 
1415 		if (key_PERMEDIT == ka[0])
1416 		{
1417 			Curses::keepgoing=false;
1418 			myServer::nextScreen=&Acl::editScreen;
1419 			myServer::nextScreenArg=currentFolder;
1420 			return true;
1421 		}
1422 		return true;
1423 	}
1424 
1425 	if (key == key_RESET)
1426 	{
1427 		const char * const fldnames[]=
1428 			{"FROM", "REPLY-TO", "FCC", "CUSTOM", "SIGNKEY", NULL};
1429 
1430 		Hierarchy::Folder *currentFolder=getCurrentFolder();
1431 		Hierarchy::Server *s;
1432 
1433 		if (!currentFolder ||
1434 		    !(s=currentFolder->getServer()))
1435 
1436 		{
1437 			statusBar->clearstatus();
1438 			statusBar->status(_("This folder does not contain"
1439 					    " messages."));
1440 			statusBar->beepError();
1441 			return true;
1442 		}
1443 
1444 		myServer::promptInfo prompt=
1445 			myServer::promptInfo(Gettext(_("Reset memorized "
1446 						       "headers for %1%? "
1447 						       "(Y/N) ")) <<
1448 					     currentFolder->getFolder()->
1449 					     getName()).yesno();
1450 
1451 		prompt=myServer::prompt(prompt);
1452 
1453 		if (prompt.abortflag || (string)prompt != "Y" ||
1454 		    myServer::nextScreen)
1455 			return true;
1456 
1457 		size_t i=0;
1458 
1459 		for (i=0; fldnames[i]; i++)
1460 			s->getServer()->
1461 				updateFolderConfiguration(currentFolder
1462 							  ->getFolder(),
1463 							  fldnames[i], "");
1464 		myServer::saveconfig();
1465 
1466 		statusBar->clearstatus();
1467 		statusBar->status(_("Memorized headers cleared."));
1468 		return true;
1469 	}
1470 
1471 
1472 	if (key == key_DELFOLDER)
1473 	{
1474 		Hierarchy::Entry *e=getHierarchy()->getEntry(currentRowNum);
1475 
1476 		Hierarchy::Server *server;
1477 
1478 		myServer *serverPtr;
1479 
1480 		if (e && (server=e->toServer()) != NULL) // Deleting server
1481 		{
1482 			serverPtr=server->getServer();
1483 
1484 			if (!serverPtr)
1485 				return true;
1486 
1487 			if (myServer::server_list.size() <= 1)
1488 			{
1489 				statusBar->clearstatus();
1490 				statusBar->status(_("Cannot remove the only mail account."));
1491 				statusBar->beepError();
1492 				return true;
1493 			}
1494 
1495 			myServer::promptInfo response=
1496 				myServer
1497 				::prompt(myServer::
1498 					 promptInfo(Gettext(_("Remove account %1%? (Y/N) "))
1499 						    << server->getServer()
1500 						    ->serverName).yesno());
1501 
1502 			if ((string)response == "Y")
1503 			{
1504 				Hierarchy::Entry *parent=e->getParent();
1505 
1506 				if (parent)
1507 				{
1508 					// Find the previous server entry.
1509 
1510 					vector<Hierarchy::Entry *>::iterator
1511 						ce=parent->begin(),
1512 						cb=parent->end();
1513 
1514 					Hierarchy::Entry *erasePtr=NULL;
1515 					Hierarchy::Entry *drawPtr=NULL;
1516 
1517 					while (ce != cb)
1518 					{
1519 						Hierarchy::Entry *ptr=*ce++;
1520 
1521 						if (ptr == e)
1522 							break;
1523 						erasePtr=drawPtr=ptr;
1524 					}
1525 
1526 					if (!erasePtr)
1527 					{
1528 						erasePtr=e;
1529 
1530 						ce=parent->begin();
1531 						cb=parent->end();
1532 
1533 						while (ce != cb)
1534 						{
1535 							Hierarchy::Entry
1536 								*ptr=*ce++;
1537 
1538 							if (ptr == e)
1539 								continue;
1540 							drawPtr=ptr;
1541 							break;
1542 						}
1543 					}
1544 
1545 					string auxfile=serverPtr->newsrc;
1546 
1547 					serverPtr->disconnect();
1548 
1549 					CursesMainScreen::Lock
1550 						logoutLock(mainScreen);
1551 					int rowSave=erasePtr->getRow();
1552 
1553 					// If nothing else has this URL,
1554 					// delete all special folders for
1555 					// this URL
1556 
1557 					vector<myServer *>::iterator
1558 						b=myServer::server_list
1559 						.begin(),
1560 						e=myServer::server_list.end();
1561 					size_t cnt=0;
1562 
1563 					while (b != e)
1564 					{
1565 						if ((*b)->url==serverPtr->url)
1566 							++cnt;
1567 						b++;
1568 					}
1569 
1570 					if (cnt < 2)
1571 						SpecialFolder
1572 							::deleteAccount(serverPtr
1573 									->url);
1574 
1575 					drawEraseBelow(erasePtr, true);
1576 					PasswordList::passwordList.remove
1577 						(serverPtr->url, "");
1578 					bool wasRemote=mail::account
1579 						::isRemoteUrl(serverPtr->url);
1580 					delete server;
1581 					delete serverPtr;
1582 					myServer::saveconfig(wasRemote);
1583 					drawPtr->setRow(rowSave);
1584 					drawEraseBelow(drawPtr, false);
1585 
1586 					if (auxfile.size() > 0)
1587 					{
1588 						if (unlink(auxfile.c_str()) < 0
1589 						    && errno == EISDIR)
1590 						{
1591 							mail::maildir::
1592 								maildirdestroy
1593 								(auxfile);
1594 						}
1595 					}
1596 				}
1597 			}
1598 			return true;
1599 		}
1600 
1601 		Hierarchy::Folder *f=getCurrentFolder();
1602 
1603 		if (f == NULL || (server=f->getServer()) == NULL ||
1604 		    (serverPtr=server->getServer()) == NULL)
1605 			return true;
1606 
1607 		e=f->getParent();
1608 
1609 		Hierarchy::Folder *parent;
1610 
1611 		if (e && (server=e->toServer()))
1612 		{
1613 			myServer::promptInfo response=
1614 				myServer
1615 				::prompt(myServer::
1616 					 promptInfo(Gettext(_("Remove %1% from %2%? (Y/N) "))
1617 						    << f->getFolder()
1618 						    ->getName()
1619 						    << server->getServer()
1620 						    ->serverName).yesno());
1621 
1622 			if ((string)response == "Y" && serverPtr->server)
1623 			{
1624 				drawEraseBelow(server, true);
1625 				delete f;
1626 				drawEraseBelow(server, false);
1627 				server->getServer()->updateHierarchy();
1628 			}
1629 			statusBar->clearstatus();
1630 			return true;
1631 		}
1632 
1633 		if (e == NULL || (parent=e->toFolder()) == NULL)
1634 			return true;
1635 
1636 		Hierarchy::Folder *deletedFolder=NULL;
1637 
1638 		if (f->getFolder()->hasSubFolders() ||
1639 		    !f->getFolder()->hasMessages())
1640 		{
1641 			myServer::promptInfo response=
1642 				myServer
1643 				::prompt(myServer
1644 					 ::promptInfo(Gettext(_("Delete folder directory %1%? (Y/N) "))
1645 						      << f->getFolder()->getName()
1646 						      )
1647 					 .yesno());
1648 
1649 			if (!serverPtr->server)
1650 				return true;
1651 
1652 			if ((string)response == "Y")
1653 			{
1654 				statusBar->clearstatus();
1655 				myServer::Callback callback;
1656 
1657 				f->getFolder()->destroy(callback, true);
1658 				if (!myServer::eventloop(callback))
1659 					return true;
1660 				deletedFolder=f;
1661 			}
1662 		}
1663 
1664 		if (!deletedFolder && f->getFolder()->hasMessages())
1665 		{
1666 			myServer::promptInfo response=
1667 				myServer
1668 				::prompt(myServer
1669 					 ::promptInfo(Gettext(_("Delete folder %1%? (Y/N) "))
1670 						      << f->getFolder()->getName()
1671 						      )
1672 					 .yesno());
1673 
1674 			if (!serverPtr->server)
1675 				return true;
1676 
1677 			if ((string)response == "Y")
1678 			{
1679 				statusBar->clearstatus();
1680 				myServer::Callback callback;
1681 
1682 				f->getFolder()->destroy(callback, false);
1683 				if (!myServer::eventloop(callback))
1684 					return true;
1685 				deletedFolder=f;
1686 			}
1687 		}
1688 
1689 		if (!deletedFolder)
1690 			return true;
1691 
1692 		myServer::Callback callback;
1693 		CursesHierarchyDeleteFolderVerifyCallback
1694 			verify(this, parent, deletedFolder);
1695 
1696 		parent->getFolder()->readSubFolders(verify, callback);
1697 
1698 		myServer::eventloop(callback);
1699 		return true;
1700 	}
1701 
1702 	if (key == key_FILTER)
1703 	{
1704 		Hierarchy::Folder *f=getCurrentFolder();
1705 
1706 		if (f)
1707 		{
1708 			Curses::keepgoing=false;
1709 			myServer::nextScreen=&Filter::filterEditScreen;
1710 			myServer::nextScreenArg=f;
1711 			return true;
1712 		}
1713 
1714 		statusBar->clearstatus();
1715 		statusBar->status(_("Only a folder may be filtered."));
1716 		statusBar->beepError();
1717 		return true;
1718 	}
1719 
1720 	if (key == key_UP) // CTRL-U
1721 	{
1722 		Hierarchy::Folder *f=getCurrentFolder();
1723 		Hierarchy::Entry *e;
1724 
1725 		if (f)
1726 		{
1727 			Hierarchy::Entry *e=f->getParent();
1728 
1729 			if (e && e->toServer())
1730 			{
1731 				f->MoveUp();
1732 				e->toServer()->getServer()->updateHierarchy();
1733 				return true;
1734 			}
1735 		}
1736 		else if ((e=getHierarchy()->getEntry(currentRowNum)) != NULL
1737 			 && e->toServer())
1738 		{
1739 			myServer *ms=e->toServer()->getServer();
1740 
1741 			vector<myServer *>::iterator msl=
1742 				find(myServer::server_list.begin(),
1743 				     myServer::server_list.end(), ms);
1744 
1745 			if (msl != myServer::server_list.end() &&
1746 			    msl != myServer::server_list.begin())
1747 			{
1748 				// Move the server structures
1749 
1750 				vector<myServer *>::iterator msp=msl;
1751 
1752 				--msp;
1753 
1754 				myServer *swap= *msp;
1755 
1756 				*msp=*msl;
1757 
1758 				*msl=swap;
1759 
1760 				// Move the stuff on screen.
1761 
1762 				ms->hierarchyEntry->MoveUp();
1763 				myServer::saveconfig();
1764 			}
1765 			return true;
1766 		}
1767 		statusBar->clearstatus();
1768 		statusBar->status(_("Only top level folders or servers may be rearranged."));
1769 		statusBar->beepError();
1770 		return true;
1771 	}
1772 
1773 	if (key == key_DOWN) // CTRL-D
1774 	{
1775 		Hierarchy::Folder *f=getCurrentFolder();
1776 		Hierarchy::Entry *e;
1777 
1778 		if (f)
1779 		{
1780 			Hierarchy::Entry *e=f->getParent();
1781 
1782 			if (e && e->toServer())
1783 			{
1784 				f->MoveDown();
1785 				e->toServer()->getServer()->updateHierarchy();
1786 
1787 				Hierarchy::Folder *ff=getCurrentFolder();
1788 
1789 				// Follow the cursor
1790 				currentRowNum=f->getRow();
1791 				drawErase(f, false);
1792 				drawErase(ff, false);
1793 				return true;
1794 			}
1795 		}
1796 		else if ((e=getHierarchy()->getEntry(currentRowNum)) != NULL
1797 			 && e->toServer())
1798 		{
1799 			myServer *ms=e->toServer()->getServer();
1800 
1801 			vector<myServer *>::iterator msl=
1802 				find(myServer::server_list.begin(),
1803 				     myServer::server_list.end(), ms);
1804 
1805 			if (msl != myServer::server_list.end())
1806 			{
1807 				// Move the server structure
1808 
1809 				vector<myServer *>::iterator msp=msl;
1810 
1811 				msp++;
1812 
1813 				if (msp != myServer::server_list.end())
1814 				{
1815 					myServer *swap= *msp;
1816 
1817 					*msp=*msl;
1818 
1819 					*msl=swap;
1820 
1821 					// Move what's on the screen.
1822 					ms->hierarchyEntry->MoveDown();
1823 
1824 					currentRowNum=ms->hierarchyEntry
1825 						->getRow();
1826 
1827 					drawErase(swap->hierarchyEntry, false);
1828 					drawErase(ms->hierarchyEntry, false);
1829 					myServer::saveconfig();
1830 				}
1831 			}
1832 			return true;
1833 		}
1834 		statusBar->clearstatus();
1835 		statusBar->status(_("Only top level folders or servers may be rearranged."));
1836 		statusBar->beepError();
1837 		return true;
1838 
1839 	}
1840 
1841 	if (key == key_EDIT)
1842 	{
1843 		Hierarchy::Entry *e=getHierarchy()->getEntry(currentRowNum);
1844 
1845 		if (!e)
1846 			return true;
1847 
1848 		Hierarchy::Server *s=e->toServer();
1849 
1850 		if (!s)
1851 		{
1852 			statusBar->clearstatus();
1853 			statusBar->status(_("Current selection not an account."));
1854 			statusBar->beepError();
1855 			return true;
1856 		}
1857 
1858 		string url=s->getServer()->url;
1859 
1860 		{
1861 			mail::loginInfo loginInfo;
1862 
1863 			if (mail::loginUrlDecode(url, loginInfo) &&
1864 			    (loginInfo.method == "imap" ||
1865 			     loginInfo.method == "imaps" ||
1866 			     loginInfo.method == "pop3" ||
1867 			     loginInfo.method == "pop3s" ||
1868 			     loginInfo.method == "pop3maildrop" ||
1869 			     loginInfo.method == "pop3maildrops" ||
1870 			     loginInfo.method == "nntp" ||
1871 			     loginInfo.method == "nntps"))
1872 			{
1873 				myServer::nextScreen= &editAccountScreen;
1874 				myServer::nextScreenArg=s->getServer();
1875 				Curses::keepgoing=false;
1876 				return true;
1877 			}
1878 		}
1879 		statusBar->clearstatus();
1880 		statusBar->status(_("This is a local mail account. "
1881 				    "Create another local account from "
1882 				    "the main menu."));
1883 		statusBar->beepError();
1884 		return true;
1885 	}
1886 
1887 	if (key == key_SPECIAL)
1888 	{
1889 		Hierarchy::Folder *f=getCurrentFolder();
1890 
1891 		if (!f)
1892 		{
1893 			statusBar->clearstatus();
1894 			statusBar->status(_("Current selection not a folder."));
1895 			statusBar->beepError();
1896 			return true;
1897 		}
1898 
1899 		myServer *serverPtr=CursesHierarchyFindServerIterator::find(f);
1900 
1901 		if (!serverPtr)
1902 			return true;
1903 
1904 		mail::folder *folder=f->getFolder()->clone();
1905 
1906 		if (!folder)
1907 			outofmemory();
1908 
1909 		try {
1910 			myServer::promptInfo prompt=
1911 				myServer::prompt(myServer::promptInfo(_("Use folder as: "))
1912 						 .option(key_ADDRESSBOOK,
1913 							 Gettext::keyname(_("ADDRESSBOOK_K:A")),
1914 							 _("Address Book"))
1915 						 .option(key_DRAFTS,
1916 							 Gettext::keyname(_("DRAFTS_K:D")),
1917 							 _("Drafts"))
1918 						 .option(key_SENT,
1919 							 Gettext::keyname(_("SENT_K:S")),
1920 							 _("Sent mail"))
1921 						 .option(key_TOP,
1922 							 Gettext::keyname(_("TOP_K:T")),
1923 							 _("Top level folder"))
1924 						 .option(key_REMOTE,
1925 							 Gettext::keyname(_("REMOTE_K:R")),
1926 							 _("Remote configuration"))
1927 						 );
1928 
1929 			if (prompt.abortflag)
1930 			{
1931 				delete folder;
1932 				return true;
1933 			}
1934 
1935 			u32string ka;
1936 
1937 			unicode::iconvert::convert(((string)prompt),
1938 						unicode_default_chset(),
1939 						ka);
1940 
1941 			if (ka.size() == 0 || !serverPtr->server)
1942 			{
1943 				delete folder;
1944 				return true;
1945 			}
1946 
1947 			if (key_ADDRESSBOOK == ka[0])
1948 			{
1949 				myServer::promptInfo prompt=
1950 					myServer::prompt(myServer::promptInfo(_("Name of this address book: "))
1951 							 .initialValue(Gettext(_("%1% on %2%")) <<
1952 								       folder->getName() << serverPtr->serverName)
1953 							 );
1954 
1955 				string name=prompt;
1956 
1957 				if (prompt.abortflag || name.size() == 0)
1958 				{
1959 					delete folder;
1960 					return true;
1961 				}
1962 
1963 				AddressBook *abook=
1964 					new AddressBook();
1965 
1966 				if (!abook)
1967 					outofmemory();
1968 
1969 				try {
1970 					abook->init(name,
1971 						    serverPtr->url,
1972 						    folder->toString());
1973 
1974 					AddressBook::addressBookList
1975 						.insert(AddressBook::
1976 							addressBookList.end(),
1977 							abook);
1978 				} catch (...) {
1979 					delete abook;
1980 					LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
1981 				}
1982 
1983 				delete folder;
1984 				myServer::saveconfig();
1985 				statusBar->clearstatus();
1986 				statusBar->status(_("Address Book created."));
1987 				return true;
1988 
1989 			}
1990 
1991 			if (key_TOP == ka[0])
1992 			{
1993 				serverPtr->addTopLevelFolderRedraw(folder->
1994 								   toString());
1995 				delete folder;
1996 				return true;
1997 			}
1998 
1999 			if (!folder->hasMessages())
2000 			{
2001 				statusBar->clearstatus();
2002 				statusBar->status(_("This folder cannot store messages, only subfolders."));
2003 				statusBar->beepError();
2004 				delete folder;
2005 				return true;
2006 			}
2007 
2008 			if (key_DRAFTS == ka[0])
2009 			{
2010 				SpecialFolder &f=SpecialFolder::folders
2011 					.find(DRAFTS)->second;
2012 
2013 				f.setSingleFolder(serverPtr->url,
2014 						  folder->toString());
2015 			}
2016 
2017 			if (key_SENT == ka[0])
2018 			{
2019 				myServer::promptInfo prompt=
2020 					myServer::prompt(myServer::promptInfo(_("Nickname for this sent mail folder: "))
2021 							 .initialValue(Gettext(_("%1% in %2%"))
2022 								       << folder->getName()
2023 								       << serverPtr->serverName));
2024 
2025 				string nameStr=prompt;
2026 
2027 				if (prompt.abortflag || nameStr.size() == 0)
2028 				{
2029 					delete folder;
2030 					return true;
2031 				}
2032 
2033 
2034 				SpecialFolder &f=SpecialFolder::folders
2035 					.find(SENT)->second;
2036 
2037 				nameStr=Gettext::toutf8(nameStr);
2038 
2039 				if (f.findFolder(nameStr))
2040 				{
2041 					statusBar->clearstatus();
2042 					statusBar->status(_("This nickname already exists."));
2043 					statusBar->beepError();
2044 					delete folder;
2045 					return true;
2046 				}
2047 
2048 				f.addFolder(nameStr, serverPtr->url,
2049 					    folder->toString());
2050 			}
2051 
2052 			if (key_REMOTE == ka[0])
2053 			{
2054 				myServer::promptInfo prompt=
2055 					myServer::prompt(myServer::promptInfo(_("Import remote configuration (requires restart)? (Y/N): "))
2056 							 .yesno());
2057 
2058 
2059 				if (prompt.abortflag)
2060 				{
2061 					delete folder;
2062 					return true;
2063 				}
2064 
2065 				myServer::remoteConfigURL=serverPtr->url;
2066 				myServer::remoteConfigPassword=
2067 					serverPtr->password;
2068 				myServer::remoteConfigFolder=folder->getPath();
2069 
2070 				if (myServer::remoteConfigAccount)
2071 				{
2072 					myServer::remoteConfigAccount
2073 						->logout();
2074 					delete myServer::remoteConfigAccount;
2075 					myServer::remoteConfigAccount=NULL;
2076 				}
2077 
2078 				if ((myServer::remoteConfigAccount=
2079 				     new myServer::remoteConfig()) == NULL)
2080 				{
2081 					LIBMAIL_THROW((strerror(errno)));
2082 				}
2083 
2084 				if ((string)prompt == "Y")
2085 				{
2086 					myServer::saveconfig(false, true);
2087 
2088 					try {
2089 						quitScreen(NULL);
2090 					} catch (...) {
2091 					}
2092 
2093 					throw "RESET";
2094 				}
2095 			}
2096 			myServer::saveconfig(true);
2097 			delete folder;
2098 		} catch (...) {
2099 			delete folder;
2100 			LIBMAIL_THROW(LIBMAIL_THROW_EMPTY);
2101 		}
2102 		return true;
2103 	}
2104 
2105 	Hierarchy::Entry *e=getHierarchy()
2106 		->getEntry(currentRowNum);
2107 
2108 	myServer *s=e ? CursesHierarchyFindServerIterator::find(e):NULL;
2109 
2110 	if (GlobalKeys::processKey(key, GlobalKeys::LISTFOLDERS, s))
2111 		return true;
2112 
2113 	return false;
2114 }
2115 
2116 //
2117 // Folder's been deleted, supposedly, and we've listed the parent hierarchy.
2118 // Figure out how to update the display.
2119 //
2120 
processDeletedFolder(Hierarchy::Folder * parent,Hierarchy::Folder * deleted,const mail::folder * updated)2121 void CursesHierarchy::processDeletedFolder(Hierarchy::Folder *parent,
2122 					   Hierarchy::Folder *deleted,
2123 					   const mail::folder *updated)
2124 {
2125 	drawEraseBelow(parent, true);
2126 
2127 	if (updated) // Folder still exists
2128 		deleted->updateFolder(updated);
2129 	else
2130 	{
2131 		delete deleted;
2132 		deleted=NULL;
2133 	}
2134 
2135 	parent->resortChildren();
2136 	drawEraseBelow(parent, false);
2137 
2138 	Hierarchy::Entry *oldCursor
2139 		=getHierarchy()->getEntry(currentRowNum);
2140 
2141 	if (deleted == NULL)
2142 		deleted=parent;
2143 
2144 	currentRowNum=deleted->getRow();
2145 
2146 	if (oldCursor)
2147 		draw(oldCursor, false);
2148 	draw(deleted, false);
2149 }
2150 
listKeys(vector<pair<string,string>> & list)2151 bool CursesHierarchy::listKeys( vector< pair<string, string> > &list)
2152 {
2153 	list.push_back( make_pair(Gettext::keyname(_("HIER_K:+/-")),
2154 				  _("Subfolders")));
2155 
2156 	if (selectingFolder)
2157 	{
2158 		list.push_back( make_pair(Gettext::keyname(_("ENTER_K:ENTER")),
2159 					  _("Select folder")));
2160 		list.push_back( make_pair(Gettext::keyname(_("ABORT_K:^C")),
2161 					  _("Cancel")));
2162 		return true;
2163 	}
2164 
2165 	GlobalKeys::listKeys(list, GlobalKeys::LISTFOLDERS);
2166 
2167 	list.push_back( make_pair(Gettext::keyname(_("EDIT_K:E")),
2168 				  _("Edit Acct")));
2169 	list.push_back( make_pair(Gettext::keyname(_("FILTER_K:F")),
2170 				  _("Filter")));
2171 	list.push_back( make_pair(Gettext::keyname(_("ADDFOLDER_K:A")),
2172 				  _("Add")));
2173 	list.push_back( make_pair(Gettext::keyname(_("DELFOLDER_K:D")),
2174 				  _("Delete")));
2175 	list.push_back( make_pair(Gettext::keyname(_("MOVEUP:^U")),
2176 				  _("Move up")));
2177 	list.push_back( make_pair(Gettext::keyname(_("MOVEDN:^D")),
2178 				  _("Move down")));
2179 	list.push_back( make_pair(Gettext::keyname(_("SPECIAL:U")),
2180 				  _("Use special")));
2181 	list.push_back( make_pair(Gettext::keyname(_("RENAME:R")),
2182 				  _("Rename")));
2183 	list.push_back( make_pair(Gettext::keyname(_("RESET:^R")),
2184 				  _("Reset hdrs")));
2185 	list.push_back( make_pair(Gettext::keyname(_("PERMS:P")),
2186 				  _("Permissions")));
2187 	return false;
2188 }
2189