#include <TCtrlLib/Help/TCtrlLibHelp.h>
#pragma hdrstop

NAMESPACE_UPP

#define LLOG(x) // LOG(x)

enum
{
	MAX_LRU = 9,
};

static bool FindTemplate(const char *tmpl, const char *s)
{
	char c;
	while((c = *tmpl++) && c != ';')
		switch(c) {
		case '*':
			do
				if(FindTemplate(tmpl, s))
					return true;
			while(*s++);
			return false;

		case '?':
			if(!*s++)
				return false;
			break;

		default:
			if(ToLower(c) != ToLower(*s++))
				return false;
			break;
		}
	return !*s;
}

static bool FindMultiTemplate(const char *tmpl, const char *s)
{
	while(*tmpl) {
		if(FindTemplate(tmpl, s))
			return true;
		while(*tmpl && *tmpl++ != ';')
			;
	}
	return false;
}

static String GetDefaultExt(const char *s)
{
	while(*s) {
		while(*s && *s != '.')
			s++;
		if(*s) {
			const char *b = ++s;
			bool wild = false;
			for(; *s && *s != ';' && *s != '.'; s++)
				if(*s == '*' || *s == '?')
					wild = true;
			if(!wild && s > b)
				return String(b, s);
			if(*s)
				s++;
		}
	}
	return Null;
}

//////////////////////////////////////////////////////////////////////
// DocList::DocInfo::

void DocList::DocInfo::Serialize(Stream& stream)
{
	stream % name % type;
}

//////////////////////////////////////////////////////////////////////
// DocList::

RegisterHelpTopicObjectTitle(DocList, t_("Open windows"))

DocList::DocList()
//: Dialog(IDTLIB_WINDOWS)
: doc_counter(0)
, lock(false)
, maximize(false)
{
	app_name = GetExeTitle();
//	app_name = typeid(*AfxGetApp()).name();
	window_list.AutoHideSb().NoHeader().NoGrid();
	window_list.AddIndex();
	window_list.AddColumn(t_("Window name"));
	window_list.WhenBar = THISBACK(StdBar);
	window_list.WhenLeftDouble = THISBACK(OnActivate);

	CtrlLayoutOKCancel(*this, DocListHelpTitle());
	HelpTopic("DlgDocList");

	ok     <<= THISBACK(OnActivate);
	close  <<= THISBACK(OnRemove);
	cancel <<= THISBACK(OnClose);

	WhenClose        = THISBACK(OnClose);
//	WhenHelpContents = THISBACK(OnHelpContents);
//	WhenHelpSearch   = THISBACK(OnHelpSearch);

//	recent_path      = GetExeDirFile("");
}

void DocList::Activate()
{
	LLOG("DocList::Activate");
//	AfxGetApp() -> m_pMainWnd = GetCWnd();
	window_list.SetFocus();
	TopWindow::Activate();
}

void DocList::Serialize(Stream& stream)
{
	int version = StreamHeading(stream, 3, 1, 3, "DocList");
	if(version >= 1) {
		StreamContainer(stream, lru);
		stream % store_docs;
		if(!store_docs)
			old_docs.Clear();
		StreamContainer(stream, old_docs);
		if(!store_docs)
			old_docs.Clear();
	}
	if(version >= 2)
		stream % maximize;
	if(version >= 3)
		stream % recent_path;
}

DocList& DocList::AppName(const char *_app_name)
{
	app_name = _app_name;
	for(int i = 0; i < doc_list.GetCount(); i++)
		doc_list[i].Change(DocCtrl::APPNAME);
//	ForEachX(doc_list, XCall(&DocCtrl::Change, DocCtrl::APPNAME));
	return *this;
}

void DocList::Insert(DocCtrl *document)
{
	ASSERT(GetIndex(document) < 0);
	int index = doc_list.GetCount();
	doc_list.Add(document);
	UpdateTable(index);
}

void DocList::Remove(DocCtrl *document)
{
	int ix = GetIndex(document);
	if(ix >= 0) {
		document -> doc_list = NULL;
		doc_list.Remove(ix);
		RefreshTable();
	}
}

void DocList::Detach(DocCtrl *document)
{
	int ix = GetIndex(document);
	if(ix >= 0) {
		document -> doc_list = NULL;
		doc_list.Detach(ix);
		RefreshTable();
	}
}

void DocList::Raise(DocCtrl *document)
{
	int ix = GetIndex(document);
	ASSERT(ix >= 0);
	if(ix > 0)
		doc_list.Move(ix, 0);
	UpdateTable(0);
}

Vector<DocCtrl *> DocList::FindDoc(const DocType& type) const
{
	Vector<DocCtrl *> result;
	for(DocVector::ConstIterator p = doc_list.Begin(), e = doc_list.End(); p < e; p++)
		if(p -> GetDocType() == &type)
			result.Add(const_cast<DocCtrl *>(&*p));
	return result;
}

bool DocList::IsDocAvail(const DocType& type) const
{
	for(int i = 0; i < doc_list.GetCount(); i++)
		if(doc_list[i].GetDocType() == &type)
			return true;
	return false;
}

void DocList::SetActive(int index, bool kill_init)
{
	doc_list[index].SetActive(kill_init);
}

DocCtrl *DocList::GetActive()
{
	int index = GetActiveIndex();
	return index >= 0 ? &doc_list[index] : NULL;
}

const DocCtrl *DocList::GetActive() const
{
	int index = GetActiveIndex();
	return index >= 0 ? &doc_list[index] : NULL;
}

int DocList::GetActiveIndex() const
{
	return doc_list.IsEmpty() ? -1 : 0;
}

int DocList::FindDoc(const char *filename) const
{
	for(DocVector::ConstIterator p = doc_list.Begin(), e = doc_list.End(); p < e; p++)
		if(!p -> IsUnnamed() && !ComparePath(p -> GetName(), filename))
			return p - doc_list.Begin();
	return -1;
}

DocCtrl *DocList::OpenDoc(const char *filename, int type_index)
{
	RemoveLRU(filename);
	if(OpenExistingDoc(filename))
		return GetActive();
	const DocType *type = NULL;
	if(type_index >= 0 && type_index < DocType::doc_types.GetCount())
		type = DocType::doc_types[type_index];
	else
		type = DocType::GetDocType(filename);
	if(!type) {
		Exclamation(NFormat(t_("Document [* \1%s\1] has an unknown type."), filename));
		return NULL;
	}

	DocCtrl *document = type -> CreateDocument(*this);
	if(document) {
		if(document -> Open(filename)) { // managed to open the file
			String fn = document -> GetName();
			if(document -> IsUnnamed())
				fn = filename;
			AddLRU(fn, type);
			document -> SetActive(true);
			SetRecentPath(GetFileDirectory(filename));
		}
		else {
			Remove(document);
			Exclamation(NFormat(t_("Error opening document [* \1%s\1]."), filename));
			document = NULL;
		}
	}

	return document;
}

bool DocList::OpenExistingDoc(const char *filename)
{
	int existing = FindDoc(filename);
	if(existing >= 0) {
		DocCtrl& doc = doc_list[existing];
		doc.SetActive();
		AddLRU(&doc);
		return true;
	}
	return false;
}

void DocList::RefreshTable()
{
	if(IsOpen())
		UpdateTable();
}

void DocList::UpdateTable(int new_ix)
{
	if(!IsOpen())
		return;

//	SyncTable sync(window_list);
//	sync.SetValue(new_ix);
	Vector<String> names;
	names.SetCount(doc_list.GetCount());
	int i;
	for(i = 0; i < doc_list.GetCount(); i++)
		names[i] = doc_list[i].GetName();
	Vector<int> order = GetSortOrder(names, GetLanguageInfo());
	window_list.Clear();
	for(i = 0; i < order.GetCount(); i++)
		window_list.Add(order[i], doc_list[order[i]].GetFullName());
	ok.Enable(!doc_list.IsEmpty());
	close.Enable(!doc_list.IsEmpty());
//	ForSortOrderX(doc_list, XMethod(&DocCtrl::GetName), CsStringComp(),
//		XApply(static_cast<void (TableArray::*)(Value, Value)>(&TableArray::Add),
//		window_list, XVal<int>(), XAt(doc_list, XMethod(&DocCtrl::GetFullName))));
//	XCall(&Ctrl::Enable, !doc_list.IsEmpty()) >> ok >> close;

	Update();
}

void DocList::Destroy()
{
	SetActiveTop();
//	DestroyWindow();
}

void DocList::SetActiveTop(DocCtrl *ignore)
{
	LLOG("DocList::SetActiveTop, ignore " << ::Name(ignore));
	if(lock && IsOpen()) {
		OnWindowList();
		lock = false;
		return;
	}

	for(DocVector::Iterator p = doc_list.Begin(), e = doc_list.End(); p < e; p++)
		if(&*p != &*ignore) {
			p -> SetActive();
			return;
		}

	LLOG("//DocList::SetActiveTop");

//	if(IsOpen())
//		DestroyWindow();
//	AfxGetApp() -> m_pMainWnd = 0;
//	PostQuitMessage(0);
}

void DocList::Change(DocCtrl& doc, int change)
{
	LLOG("DocList::Change(" << change << ")");
	if(change != DocCtrl::ACTIVATE)
		RefreshTable();
}

bool DocList::Close(int index)
{
	ASSERT(index >= 0 && index < doc_list.GetCount());
	return doc_list[index].CloseDoc();
}

void DocList::ToolFileNew(Bar& bar)
{
	bar.Add(t_("New"), CtrlImg::new_doc(), THISBACK(OnFileNew))
		.Key(K_CTRL_N)
		.Help(t_("Create a new document"))
		.Topic("DocListFileNew");
}

void DocList::OnFileNew()
{
	DocCtrl *doc = DocType::CreateAnyDocument(*this);
	if(doc)
		doc -> SetActive();
}

void DocList::ToolFileNewDefault(Bar& bar)
{
	bar.Add(t_("New"), CtrlImg::new_doc(), THISBACK(OnFileNewDefault))
		.Key(K_CTRL_N)
		.Help(t_("Create a new document"))
		.Topic("DocListFileNewDefault");
}

void DocList::OnFileNewDefault()
{
	DocCtrl *doc = DocType::CreateDefaultDocument(*this);
	if(doc)
		doc -> SetActive();
}

void DocList::ToolFileOpen(Bar& bar)
{
	bar.Add(t_("Open"), CtrlImg::open(), THISBACK(OnFileOpen))
		.Key(K_CTRL_O)
		.Help(t_("Open an existing document"))
		.Topic("DocListFileOpen");
}

void DocList::OnFileOpen()
{
	FileSelector fsel;
	DocType::SetOpenFileDlg(fsel);
	fsel <<= recent_path;
	fsel.AllFilesType();
	fsel.Multi();
	if(fsel.ExecuteOpen(t_("Open file")))
		for(int i = 0; i < fsel.GetCount(); i++)
			OpenDoc(fsel[i]);
}

void DocList::ToolFileSaveAll(Bar& bar)
{
	bar.Add(!this || !doc_list.IsEmpty(),
		t_("Save al&l"), THISBACK(OnFileSaveAll))
		.Help(t_("Save all open documents to the disk"))
		.Topic("DocListFileSaveAll");
}

void DocList::OnFileSaveAll()
{
	SaveAll();
}

bool DocList::ToolFileLru(Bar& bar)
{
	if(this && bar.IsMenuBar() && !bar.IsScanKeys() && !lru.IsEmpty()) {
		if(lru.GetCount() > MAX_LRU)
			lru.SetCount(MAX_LRU);
		bar.Separator();
		for(int i = 0; i < lru.GetCount(); i++)
			bar.Add('&' + FormatInt(i + 1) + ' ' + GetFileName(lru[i].name), THISBACK1(OnFileLru, i))
			.Help(NFormat(t_("Open file '%s'"), lru[i].name));
		return true;
	}
	return false;
}

void DocList::OnFileLru(int i)
{
	if(i >= 0 && i < lru.GetCount()) {
		DocInfo doc = lru[i];
		int d;
		for(d = 0; d < DocType::doc_types.GetCount(); d++)
			if(DocType::doc_types[d]->GetStreamName() == doc.type
			|| DocType::doc_types[d]->doc_name == doc.type) {
				OpenDoc(doc.name, d);
				break;
			}
//		OpenDoc(doc.name, FindXIndex(DocType::doc_types,
//			XDeref(XMethod(&DocType::GetStreamName)), doc.type));
	}
}

void DocList::ToolFileQuit(Bar& bar)
{
	bar.Add(t_("Quit"), THISBACK(OnFileQuit))
		.Help(t_("Quit application"))
		.Topic("DocListFileQuit")
		.Topic( "DocListFileQuit");
}

void DocList::OnFileQuit()
{
	if(CloseAll()) {
//		PostQuitMessage(0);
	}
}

void DocList::ToolFileIo(Bar& bar, DocCtrl *doc)
{
	ToolFileNew(bar);
	ToolFileOpen(bar);
	if(doc) {
		doc -> ToolFileSave(bar);
		doc -> ToolFileSaveAs(bar);
	}
	if(!this || !doc_list.IsEmpty())
		ToolFileSaveAll(bar);
	if(doc)
		doc -> ToolFileClose(bar);
}

void DocList::ToolFileMisc(Bar& bar)
{
	ToolFileLru(bar);
	bar.MenuSeparator();
	ToolFileQuit(bar);
}

void DocList::ToolWindowActivate(Bar& bar)
{
	if(this && !bar.IsScanKeys() && !doc_list.IsEmpty()) { // activations don't have any hotkeys so we won't bother expanding in scan mode
		Vector<DocCtrl *> docs;
		Vector<String> names;
		docs.SetCount(doc_list.GetCount());
		names.SetCount(doc_list.GetCount());
		int i;
		for(i = 0; i < doc_list.GetCount(); i++) {
			docs[i] = &doc_list[i];
			names[i] = doc_list[i].GetName();
		}
		IndexSort(names, docs, GetLanguageInfo());
//		SortX(docs, XDeref(XMethod(&DocCtrl::GetName)), CsStringComp());
		int active = FindIndex(docs, &doc_list[0]);
		if(docs.GetCount() > 9) { // remove extra documents
			int rem = max(active - 4 - min(docs.GetCount() - active, 4), 0);
			docs.Remove(0, rem);
			active -= rem;
			rem = max(docs.GetCount() - active - min(active, 4), 0) ;
			docs.SetCount(docs.GetCount() - rem);
		}

		for(i = 0; i < docs.GetCount(); i++) {
			DocCtrl *doc = docs[i];
			bar.Add(FormatInt(i + 1) + ' ' + doc -> GetShortName(), callback(doc, &DocCtrl::OnWindowActivate))
				.Check(i == active)
				.Help(NFormat(t_("Activate document '%s'?"), doc -> GetFullName()));
		}
		if(!docs.IsEmpty())
			ToolWindowList(bar);
	}
}

void DocList::ToolWindowList(Bar& bar)
{
	bar.Add(t_("Windows..."), THISBACK(OnWindowList))
		.Help(t_("Show open documents"))
		.Topic("DocListWindowList");
}

void DocList::OnWindowList()
{
	if(!IsOpen()) {
		Zoomable().Sizeable().Icon(CtrlImg::menu_window());
		TopWindow::OpenMain();
		UpdateTable(0);
	}
//	ActivateTopmostOwnedPopup(GetHWND());
}

void DocList::ToolHelpContents(Bar& bar)
{
	bar.Add(t_("Contents"), THISBACK(OnHelpContents))
		.Help(t_("Display alphabetic help contents"))
		.Topic("DocListHelpContents");
}

void DocList::OnHelpContents()
{
	OpenHelpTopicIndex(Null, false);
}

void DocList::ToolHelpSearch(Bar& bar)
{
	bar.Add(t_("Search"), THISBACK(OnHelpSearch))
		.Help(t_("Display help index for keyword searching"))
		.Topic("DocListHelpSearch");
}

void DocList::OnHelpSearch()
{
	OpenHelpTopicIndex(Null, true);
}

void DocList::ToolHelpBook(Bar& bar)
{
	bar.Add(t_("User manual"), THISBACK(OnHelpBook))
		.Help(t_("Typeset complete user documentation"))
		.Topic("DocListHelpBook");
}

void DocList::OnHelpBook()
{
	CreateHelpBook();
}

void DocList::OnWindowPrev()
{
	if(doc_list.GetCount() < 2) {
		DocCtrl *doc = GetActive();
		if(doc)
			doc -> info_bar.Temporary(t_("This is the only open window."));
		BeepExclamation();
	}
	else {
		doc_list.Move(0, doc_list.GetCount() - 1); // beware of Fidler's bug!!!
		SetActiveTop();
	}
}

void DocList::OnWindowNext()
{
	if(doc_list.GetCount() < 2) {
		DocCtrl *doc = GetActive();
		if(doc)
			doc -> info_bar.Temporary(t_("This is the only open window."));
		BeepExclamation();
	}
	else
		SetActive(GetDocCount() - 1);
}

void DocList::OnRemove()
{
	if(window_list.IsCursor()) {
		lock = true;
		if(Close(window_list.Get(0)))
			window_list.SetFocus();
		else
			lock = false;
	}
}

void DocList::OnActivate()
{
	if(window_list.IsCursor()) {
		SetActive(window_list.Get(0));
		window_list.SetFocus();
	}
}

void DocList::OnClose()
{
	TopWindow::Close();
}

void DocList::StdBar(Bar& bar)
{
	DocCtrl *doc = (window_list.IsCursor() ? &doc_list[window_list.Get(0)] : NULL);
	if(doc) {
		bar.Add(t_("Activate document '%s'?"), THISBACK(OnActivate));
		bar.Separator();
	}
	ToolFileIo(bar, doc);
	ToolFileMisc(bar);
}

int DocList::SaveAll()
{
	for(int i = 0; i < doc_list.GetCount(); i++)
		if(!doc_list[i].FlushDirty())
			return i;
	return -1;
}

RegisterHelpTopicObjectTitle(DlgDocListSave, t_("Close all documents"))

bool DocList::CloseAll(bool save)
{
	if(save) {
		WithDocListSaveLayout<TopWindow> dialog;
		CtrlLayoutOKCancel(dialog, DlgDocListSaveHelpTitle());
		dialog.HelpTopic("DlgDocListSave");
		ArrayOption save;
//		TableCheck save(0, 1);
		dialog.list.AddIndex();
		dialog.list.AddColumn(t_("Document"), 25);
		save.AddColumn(dialog.list, t_("Save"), 5);
		for(int i = 0; i < doc_list.GetCount(); i++)
			if(doc_list[i].IsDirty())
				dialog.list.Add(i, doc_list[i].GetShortName(), 1);

		if(dialog.list.GetCount() == 1) {
			if(SaveAll() >= 0)
				return false;
		}
		else if(dialog.list.GetCount() > 1) {
			switch(dialog.Run()) {
			case IDOK: {
					for(int i = 0; i < dialog.list.GetCount(); i++)
						if((int)dialog.list.Get(i, 2)) {
							int j = dialog.list.Get(i, 0);
							if(!doc_list[j].SaveAs())
								return false;
						}
				}
				break;

			case IDNO:
				break;

			case IDCANCEL:
				return false;
			}
		}
	}

	bool all = true;
	for(int i = 0; i < doc_list.GetCount(); i++) {
		DocCtrl& doc = doc_list[i];
		doc.Untouch();
		all &= doc.CloseDoc();
	}
	return all;
}

void DocList::AddLRU(const DocCtrl *doc)
{
	ASSERT(doc);
	if(!doc -> IsUnnamed() && !IsNull(doc -> GetName()))
		AddLRU(doc -> GetName(), doc -> GetDocType());
}

void DocList::AddLRU(const char *doc_name, const DocType *type)
{
	ASSERT(type);
	String type_name = type -> GetStreamName();
	if(!IsNull(type_name)) {
		RemoveLRU(doc_name);
		lru.Insert(0, DocInfo().Name(doc_name).Type(type_name));
		if(lru.GetCount() > MAX_LRU)
			lru.SetCount(MAX_LRU);
	}
}

void DocList::RemoveLRU(const char *doc_name)
{
	for(int i = lru.GetCount(); --i >= 0;)
		if(!CompareNoCase(lru[i].name, doc_name))
			lru.Remove(i);
}

void DocList::Run()
{
	if(doc_list.IsEmpty())
		Open();
	Ctrl::EventLoop();
/*
	while(IsOpen() && IsVisible() || !doc_list.IsEmpty()) {
		Ctrl::GuiSleep(1000);
		Ctrl::ProcessEvents();
	}
*/
}

//////////////////////////////////////////////////////////////////////
// DocType::

Vector<const DocType *> DocType::doc_types;

DocType::DocType(DocCtrl *(*create_doc)())
: create_doc(create_doc)
, detect_doc(NULL)
, doc_name(t_("Document"))
, default_name(t_("new"))
, paper(SGray)
, new_list(true)
{
}

int DocType::GetIndex() const
{
	return FindIndex(doc_types, this);
}

static String EncodeFilter(const String& filter, const String& ext)
{
	String f = filter;
	bool first = true;
	const char *s = ext;
	while(*s) {
		const char *p = s;
		while(*s && *s != ';')
			s++;
		if(s != p) {
			f << (first ? " (" : ", ");
			f.Cat(p, s - p);
			first = false;
		}
		if(*s)
			s++;
	}
	if(!first)
		f << ')';
	return f;
}

void DocType::SetOpenFileDlg(FileSelector& fsel)
{
	Index<String> exts;
	int i;
	for(i = 0; i < doc_types.GetCount(); i++) {
		const char *s = doc_types[i] -> extensions;
		while(*s) {
			const char *p = s;
			while(*s && *s != ';')
				s++;
			if(s != p)
				exts.FindAdd(String(p, s));
			if(*s)
				s++;
		}
	}
	if(exts.GetCount() > 1) {
		String list;
		for(const String *p = exts.Begin(), *e = exts.End(); p < e; p++)
			list << *p << ';';
		if(!list.IsEmpty())
			list.Trim(list.GetLength() - 1);
		fsel.Type(EncodeFilter(t_("Supported types"), list), list);
	}

	for(i = 0; i < doc_types.GetCount(); i++) {
		const DocType& dt = *doc_types[i];
		if(!IsNull(dt.filter))
			fsel.Type(EncodeFilter(dt.filter, dt.extensions), dt.extensions);
	}
}

void DocType::SetSaveFileDlg(FileSelector& fsel) const
{
	String ext = extensions;
	int f = ext.Find(';');
	if(f >= 0)
		ext.Trim(f);
	fsel.Type(EncodeFilter(filter, ext), ext);
	f = ext.Find('.');
	if(f >= 0)
		ext.Remove(0, f + 1);
	fsel.DefaultExt(ext);
}

DocType& DocType::DocName(const char *_ndn)
{
	doc_name = _ndn;
	return *this;
}

DocType& DocType::Filter(const char *_filter, const char *_defext)
{
	filter = _filter;
	extensions = _defext;
	return *this;
}

DocType& DocType::HelpTopic(const char *_ht)
{
	help_topic = _ht;
	return *this;
}

DocCtrl *DocType::CreateDocument(DocList& list) const
{
	ASSERT(create_doc);
	DocCtrl *doc = create_doc();
	if(!doc)
		return doc;
	doc -> SetDocType(this);
	doc -> SetDocList(&list);
	doc -> HelpTopic(help_topic);
	if(!doc -> New()) {
		delete doc;
		doc = NULL;
	}
	return doc;
}

DocType& DocType::StreamName(const char *sn)
{
	stream_name = sn;
	return *this;
}

String DocType::GetStreamName() const
{
	return !IsNull(stream_name)  ? stream_name
		:  !IsNull(help_topic)   ? help_topic
		:  doc_name;
}

const DocType *DocType::FindStreamName(String sn)
{
	int index = doc_types.GetCount();
	while(--index >= 0 && doc_types[index] -> GetStreamName() != sn)
		;
	return index >= 0 ? doc_types[index] : NULL;
}

const DocType *DocType::GetDocType(const char *filename)
{
	if(!filename || !*filename)
		return NULL;
	String fn = UPP::GetFileName(filename);
	for(const DocType *const *p = doc_types.Begin(), *const *e = doc_types.End(); p < e; p++)
		if(FindMultiTemplate((*p) -> GetExt(), fn))
			return *p;
	return NULL;
}

DocCtrl *DocType::CreateDefaultDocument(DocList& list)
{
	ASSERT(!doc_types.IsEmpty());
	return doc_types[0] -> CreateDocument(list);
}

RegisterHelpTopicObjectTitle(DlgDocListNew, t_("New..."))

DocCtrl *DocType::CreateAnyDocument(DocList& list)
{
	const DocType *type = NULL;
	static String recent_type;
 {
		Vector<const DocType*> avail_types;
		Vector<String> avail_names;
		int i;
		for(i = 0; i < doc_types.GetCount(); i++) {
			const DocType& doc = *doc_types[i];
			if(!IsNull(doc.GetDocName()) && doc.IsNewListEnabled() && doc.GetCreateDoc()) {
				avail_types.Add(&doc);
				avail_names.Add(doc.GetDocName());
			}
		}
		switch(avail_types.GetCount()) {
		case 0:  NEVER(); return NULL; // no document types available
		case 1:  return avail_types[0] -> CreateDocument(list);
		}

		IndexSort(avail_names, avail_types, GetLanguageInfo());
		WithDocListNewLayout<TopWindow> dialog;
		CtrlLayoutOKCancel(dialog, DlgDocListNewHelpTitle());
		dialog.HelpTopic("DlgDocListNew");
		dialog.types.NoHeader().AutoHideSb();
		dialog.types.WhenLeftDouble = dialog.Acceptor(IDOK);
		dialog.types.AddIndex();
		dialog.types.AddColumn(t_("Document type"));
		int x = -1;
		for(i = 0; i < avail_types.GetCount(); i++) {
			dialog.types.Add(i, avail_types[i] -> GetDocName());
			if(avail_types[i] -> GetStreamName() == recent_type)
				x = i;
		}
		dialog.types.SetCursor(max(0, x));
		if(dialog.Run() != IDOK || dialog.types.GetCursor() < 0)
			return NULL;
		type = avail_types[dialog.types.GetCursor()];
		recent_type = type -> GetStreamName();
	}

	return type -> CreateDocument(list);
}

int DocType::Add(const DocType& doctype)
{
	int index = doc_types.GetCount();
	doc_types.Add(&doctype);
	return index;
}

//////////////////////////////////////////////////////////////////////
// DocCtrl::

DocCtrl::DocCtrl()
: doc_type(NULL)
, doc_list(NULL)
, is_dirty(false)
, is_unnamed(true)
, is_read_only(false)
, is_closing(false)
{
	WhenClose = THISBACK(OnWindowClose);
	AddFrame(menu_bar);
	AddFrame(info_bar);
	AddFrame(tool_bar);
	Rescan();
//	AddIdleCallback(callback(&(Bar&)tool_bar, &Bar::Rescan));
}

DocCtrl::~DocCtrl()
{
	if(doc_list)
		doc_list -> Detach(this);
//	KillIdleCallback(callback(&(Bar&)tool_bar, &Bar::Rescan));
}

DocCtrl& DocCtrl::SetDocList(DocList *list)
{
	if(doc_list != list) {
		ASSERT(!doc_list);
		// currently we don't support document grafting
		// because it's complicated and it seems absolutely useless
		if(doc_list)
			doc_list -> Remove(this);
		if(doc_list = list)
			list -> Insert(this);
	}
	return *this;
}

void DocCtrl::Activate()
{
	LLOG("DocCtrl::Activate for " << ::Name(this));
	ASSERT(doc_list);
//	AfxGetApp() -> m_pMainWnd = GetCWnd();
	TopWindow::Activate();
//	info_bar.HelpDefault();
	doc_list -> Raise(this);
}

void DocCtrl::Serialize(Stream& stream)
{
}

bool DocCtrl::New()
{
	ASSERT(doc_list);
	is_unnamed = true;

	int i = doc_list -> GetNextDocIndex();
	filename = FormatInt(i);

	is_dirty = false;
	Change(NEW);
	return true;
}

bool DocCtrl::Open(const char *doc, bool readonly)
{
	FileIn file(doc);
	if(!file.IsOpen())
		return false;
	Serialize(file);
	Untouch();
	if(file.IsError())
		return false;
	SetName(doc, true);
	SetReadOnly(readonly);
	Change(OPEN);
	return true;
}

bool DocCtrl::SaveAs(const char *as)
{
	String fn;
	bool known;
	if(as)
		known = !IsNull(fn = as);
	else { // if(!IsUnnamed())
		fn = GetName();
		known = !IsUnnamed();
	}
	if(!known && IsNull(fn = QueryFileName()))
		return false;
	try {
		if(!Save(fn)) {
			Exclamation(t_("Error saving document. Check free disk space."));
			return false;
		}
	}
	catch(Exc e) {
		ShowExc(e);
		return false;
	}
	SetName(fn, true);
	Untouch();
	return true;
}

String DocCtrl::QueryFileName()
{
	FileSelector fsel;
	doc_type -> SetSaveFileDlg(fsel);
	fsel <<= Nvl(GetName(), doc_list -> GetRecentPath());
	if(!fsel.ExecuteSaveAs(t_("Save as")))
		return Null;
	return ~fsel;
}

bool DocCtrl::Save(const char *fn)
{
	FileOut file(fn);
	if(!file.IsOpen())
		return false;
	Serialize(file);
	if(file.IsError())
		return false;
	doc_list -> SetRecentPath(GetFileDirectory(fn));
	return true;
}

bool DocCtrl::IsTopWindow() const
{
	return IsEnabled(); // && GetHWND() && IsWindowEnabled(GetHWND());
}

bool DocCtrl::CloseDoc()
{
	if(is_closing)
		return true;
	if(!IsTopWindow() || !IsVisible() || !IsEnabled()) { // don't close disabled window, a dialog might be over it
		Exclamation(NFormat(t_("Error closing document [* \1%s\1]."), GetName()));
		return false;
	}
	is_closing = true;
	if(FlushDirty()) {
		DoClose();
		return true;
	}
	else {
		is_closing = false;
		return false;
	}
}

void DocCtrl::DoClose()
{
	Disable();
	is_closing = true;
	Change(CLOSE);
	Close();
	if(doc_list)
		doc_list -> Remove(this);
//	KillTimeCallback();
//	PostCallback(THISBACK(PostClose));
}

//void DocCtrl::PostClose()
//{
//	if(doc_list)
//		doc_list -> SetActiveTop(this);
//	DestroyWindow();
//	if(doc_list)
//		doc_list -> Remove(this);
//}

/*
bool DocCtrl::BeginApplModal()
{
	ASSERT(doc_list);
	return doc_list -> BeginApplModal();
}
*/

/*
void DocCtrl::EndApplModal()
{
	ASSERT(doc_list);
	doc_list -> EndApplModal();
}
*/

/*
bool DocCtrl::RunApplModal(Callback cb)
{
	ASSERT(doc_list);
	return doc_list -> RunApplModal(cb);
}
*/

void DocCtrl::State(int reason)
{
/*
	if(reason == SHOW || reason == ACTIVATE) {
		WINDOWPLACEMENT wp;
		wp.length = sizeof(wp);
		VERIFY(GetWindowPlacement(GetHWND(), &wp));
		doc_list -> Maximize(wp.showCmd == SW_SHOWMAXIMIZED);
		RefreshFrame();
	}
	else if(reason == DEACTIVATE)
		RefreshFrame();
*/
	Ctrl::State(reason);
//	Rescan();
}

void DocCtrl::Change(int reason)
{
	if(!doc_list)
		return;
	if(IsOpen()) {
		String name;
		if(IsOpenDoc())
			name = GetDirtyName();
		name << " - " << doc_list -> GetAppName();
		Title(name.ToWString());
	}
	doc_list -> Change(*this, reason);
	Rescan();
}

DocCtrl& DocCtrl::SetReadOnly(bool _readonly)
{
	if(is_read_only != _readonly)
		Change((is_read_only = _readonly) ? READ_ONLY : WRITEABLE);
	return *this;
}

void DocCtrl::Touch(bool _touch)
{
	if(is_dirty != _touch) {
		Change((is_dirty = _touch) ? TOUCH : UNTOUCH);
		Rescan();
	}
}

bool DocCtrl::FlushDirty()
{
	if(!IsDirty())
		return true;
	switch(PromptYesNoCancel(NFormat(t_("%s [* \1%s\1] has been edited. Save changes to the disk?"),
		Nvl(doc_type -> GetDocName(), t_("Document")), filename))) {
	case 1:  return SaveAs();
	case 0:  return true;
	default: return false;
	}
}

void DocCtrl::Paint(Draw& draw)
{
	draw.DrawRect(draw.GetClip(), doc_type -> paper);
}

void DocCtrl::ToolFileSave(Bar& bar)
{
	bar.Add(/*IsOpen() && IsTopWindow(),*/ t_("Save"), CtrlImg::save(), THISBACK(OnFileSave))
		.Key(K_CTRL_S)
		.Help(t_("Save document to the disk\f; you should close all dialog windows first"))
		.Topic("DocCtrlFileSave");
}

void DocCtrl::OnFileSave()
{
	SaveAs();
}

void DocCtrl::ToolFileSaveAs(Bar& bar)
{
	bar.Add(/*IsOpen() && IsTopWindow(),*/ t_("Save &as"), CtrlImg::save_as(), THISBACK(OnFileSaveAs))
		.Help(t_("Save document to another disc file\f; you should close all dialog windows first"))
		.Topic("DocCtrlFileSaveAs");
}

void DocCtrl::OnFileSaveAs()
{
	SaveAs("");
}

void DocCtrl::ToolFileClose(Bar& bar)
{
	bar.Add(IsTopWindow(), t_("Close"), THISBACK(OnFileClose))
		.Help(t_("Close active document\f; you should close all dialog windows first"))
		.Topic("DocCtrlFileClose");
}

void DocCtrl::OnFileClose()
{
	CloseDoc();
}

void DocCtrl::ToolFile(Bar& bar)
{
	doc_list -> ToolFileIo(bar, this);
	doc_list -> ToolFileMisc(bar);
}

void DocCtrl::ToolWindowToolBar(Bar& bar)
{
	bar.Add(t_("Toolbar"), THISBACK(OnWindowToolBar))
		.Check(tool_bar.IsVisible())
		.Help(t_("Show / hide window toolbar"))
		.Topic("DocCtrlWindowToolBar");
}

void DocCtrl::OnWindowToolBar()
{
	tool_bar.Show(!tool_bar.IsVisible());
}

void DocCtrl::ToolWindowInfoBar(Bar& bar)
{
	bar.Add(t_("Info bar"), THISBACK(OnWindowInfoBar))
		.Check(info_bar.IsVisible())
		.Help(t_("Show / hide window information bar"))
		.Topic("DocCtrlWindowInfoBar");
}

void DocCtrl::OnWindowInfoBar()
{
	info_bar.Show(!info_bar.IsVisible());
	info_bar.Layout();
}

void DocCtrl::AddToolWindow(Bar& bar)
{
	if(bar.IsMenuBar())
		bar.Add(t_("Window"), THISBACK(ToolWindow));
}

void DocCtrl::ToolWindow(Bar& bar)
{
	ToolWindowToolBar(bar);
	ToolWindowInfoBar(bar);
	bar.Separator();
	doc_list -> ToolWindowActivate(bar);
}

void DocCtrl::ToolHelp(Bar& bar)
{
//	ToolHelpDoc(bar);
	doc_list -> ToolHelpContents(bar);
	doc_list -> ToolHelpSearch(bar);
}

void DocCtrl::BarView(Bar& bar)
{
}

void DocCtrl::BarTool(Bar& bar)
{
	bar.Add(t_("File"), THISBACK(ToolFile));
	BarView(bar);
	AddToolWindow(bar);
	if(bar.IsMenuBar())
		bar.Add(t_("Help"), THISBACK(ToolHelp));
}

void DocCtrl::Rescan()
{
	menu_bar.Set(THISBACK(BarTool));
	tool_bar.Set(THISBACK(BarTool));
}

void DocCtrl::OnWindowActivate()
{
	SetActive();
}

void DocCtrl::OnWindowClose()
{
	CloseDoc();
}

void DocCtrl::SetActive(bool kill_init)
{
	LOG("DocCtrl::SetActive in " << UPP::Name(this));

	if(!IsEnabled())
		return;

	ASSERT(doc_list);

	bool max = doc_list -> IsMaximize();

	DocCtrl *active = doc_list -> GetActive();
	if(kill_init && active && active != this && active -> IsUnnamed() && !active -> IsDirty()) { // kill empty document
		LOGBLOCK("-> KillInit");
		Rect rc = active -> GetRect();
		SetRect(rc);
		max = active -> IsMaximized();
		active -> CloseDoc();
	}

	if(!IsOpen()) {
		LOGBLOCK("-> open window");
		LOG("Sizeable, Zoomable, Icon");
		Sizeable().Zoomable().Icon(doc_type -> GetSmallIcon(), doc_type -> GetLargeIcon());
		LOG("Layout");
		Layout();
		LOG("OpenMain");
		TopWindow::OpenMain();
		if(max)
			Maximize();
		LOG("OPENWINDOW");
		Change(OPENWINDOW);
	}

	menu_bar.WhenHelp = info_bar;
	LOG("Activate");
	Activate();
//	SetWantFocus();
//	ActivateTopmostOwnedPopup(GetHWND());
}

void DocCtrl::SetName(String name, bool clear_unnamed)
{
	if(!IsNull(name) && name != filename) {
		filename = name;
		if(clear_unnamed) {
			is_unnamed = false;
			doc_list -> AddLRU(this);
		}
		Change(RENAME);
	}
}

String DocCtrl::GetDirtyName() const
{
	String name = doc_type -> GetDocName();
	bool doc = !IsNull(name);
	if(doc)
		name << " (";
	name << GetFileName(GetName());
	if(IsDirty())
		name << " *";
	if(doc)
		name << ')';
	return name;
}

String DocCtrl::GetShortName() const
{
	String name = GetName();
	if(!IsUnnamed())
		name = GetFileName(name);
	return doc_type -> GetDocName() + " (" + name + ')';
}

String DocCtrl::GetFullName() const
{
	return doc_type -> GetDocName() + " (" + GetName() + ')';
}

bool DocCtrl::HotKey(dword key)
{
	if(Ctrl::HotKey(key))
		return true;
	switch(key) {
	case K_TAB: case K_SHIFT_TAB: return true; // do nothing on tab
	case K_CTRL_TAB:       doc_list -> OnWindowNext(); return true;
	case K_SHIFT_CTRL_TAB: doc_list -> OnWindowPrev(); return true;
	}
	return false;
}

#if 0
DocPokus::DocPokus()
{
}

DocPokus::~DocPokus()
{
}

void DocPokus::RegisterType()
{
	static DocType pokus = DocType(CreateDoc)
		.DocName("Pokusný dokument")
		.Filter("Pokusy", "*.pok");

	static DocType other = DocType(CreateDoc)
		.DocName("Ten samy dokument")
		.Filter("Prdy", "*.prd");

	DocType::Add(pokus);
	DocType::Add(other);
}

DocCtrl *DocPokus::CreateDoc()
{
	return new DocPokus;
}
#endif

END_UPP_NAMESPACE
