﻿$engine JScript
$uname snegopatwnd
$dname Показ окна Снегопата

var ActiveIniList
var wndStateProfilePath = "Snegopat/WndOpened"

// Скрипт для работы с окном Снегопата

// подключу глобальные контексты
addins.byUniqueName("global").object.connectGlobals(SelfScript)

// Класс для работы со списком аддинов
function AddinList(path)
{
	this.path = path
	this.textDoc = v8New("ТекстовыйДокумент")
	
	var file = v8New("Файл", path)
	if(file.Существует())
		this.textDoc.Прочитать(path)
	else
	{
		this.textDoc.УстановитьТекст("// Список загружаемых аддинов\r\nГруппа Проверка\r\n\tscript:scripts\\autosave.js\r\nКонецГруппы\r\n")
		try{
			this.textDoc.Записать(path)
		}catch(e){}
		if(!file.Существует())
			throw "Не удалось записать файл " + path
	}
	this.lastModify = file.ПолучитьВремяИзменения()
	this.parseFile();
}

AddinList.prototype.openIni = function()
{
	this.textDoc.Показать(this.path, this.path)
}
// Объект описывающий группу в файле настройки
function addinListGroup(name, startLine, parent)
{
	this.name = name
	this.startLine = startLine
	this.endLine = null
	this.childs = new Array()
	this.addins = new Array()
	this.parent = parent
	if(parent)
	{
		parent.childs.push(this)
		this.level = parent.level + 1
	}
	else
		this.level = 0
	this.group = null
}

addinListGroup.prototype.findChild = function(name)
{
	for(var i in this.childs)
	{
		if(this.childs[i].name == name)
			return this.childs[i]
	}
	return null	
}

// Объект описывающий addin в файле настройки
function addinListAddin(uri, active, lineNum, parent)
{
	this.uri = uri
	this.active = active
	this.lineNum = lineNum
	this.parent = parent
	this.addins = null
	parent.addins.push(this)
}

// Парсит файл загрузки аддинов и загружает их.
// Файл состоит из групп аддинов и строк загрузки аддинов.
// Группа аддинов обозначается как
//  Группа Имя группы
//   .... аддины и вложенные группы
//  КонецГруппы
// 
// Файл парсится построчно. Каждая строка СокрЛП'ится. Пустые строки пропускаются.
// Строки могут быть:
// Комментарий - начинается с "//". Пропускается.
// Начало группы - начинается с "Группа" (в любом регистре), за которой идут один или несколько пробельных символов, за которыми идет Имя группы
// Конец группы - "КонецГруппы" (в любом регистре)
// Временно отключенный аддин - начинается с "#".
// Все остальные строки считаются аддинами и пытаются загрузиться.
// В общем виде менеджер аддинов Снегопата использует отдельные загрузчики для разных видов аддинов.
// Какой загрузчик использовать, определяется по части строки загрузки до первого символа ":" (будем называть ее "протокол")
// По протоколу выбирается загрузчик, и строка загрузки передается уже ему.
// Как он ее интерпретирует - это уже его дело. Состав загрузчиков может быть расширен.
// Пока в снегопате реализован загрузка по протоколу "script:"
// Загрузчик скриптов рассматривает строку как указание пути к файлу скрипта.
// Путь может быть неполным, поиск осуществляется стандартно (WinAPI SearchFile), начиная от каталога, в котором лежит snegopat.dll
AddinList.prototype.parseFile = function()
{
	this.root = new addinListGroup("Пользовательские аддины", 0, null)
	var currentGroup = this.root
	
	var lineCount = this.textDoc.КоличествоСтрок()
	for(var lineNum = 1; lineNum <= lineCount; lineNum++)
	{
		var line = this.textDoc.ПолучитьСтроку(lineNum).replace(/^\s*|\s*$/g, '')	// Это такой СокрЛП по JScript'овски
		if(0 == line.length || '//' == line.substr(0, 2))	// Пропускаем пустые строки и комментарии
			continue
		// Проверим, не заголовок ли это группы
		var isGroup = line.match(/^группа\s+(.+)$/i)
		if(isGroup)
		{
			var nameOfGroup = isGroup[1]
			var testGroup = currentGroup.findChild(nameOfGroup)
			if(testGroup)
			{
				Message("Разбор списка аддинов. Файл " + this.path+ ", строка " + lineNum + ": группа " + nameOfGroup + " уже есть", mExc1)
				currentGroup = testGroup
			}
			else
				currentGroup = new addinListGroup(nameOfGroup, lineNum, currentGroup)
		}
		// Проверим, не конец ли это группы
		else if(isGroup = line.match(/^конецгруппы$/i))
		{
			if(!currentGroup.parent)
				Message("Разбор списка аддинов. Файл " + this.path + ", строка " + lineNum + ": Лишний КонецГруппы", mExc1)
			else
			{
				currentGroup.endLine = lineNum
				currentGroup = currentGroup.parent
			}
		}
		// Значит, это строка загрузки аддина
		else
		{
			var active = true
			if('#' == line.charAt(0))
			{
				active = false
				line = line.substr(1)
			}
			new addinListAddin(line, active, lineNum, currentGroup)
		}
	}
	var stackDepth = 0
	while(currentGroup.parent)
	{
		stackDepth++
		this.textDoc.ДобавитьСтроку("КонецГруппы")
		currentGroup.endLine = lineNum
		lineNum++
		currentGroup = currentGroup.parent
	}
	if(stackDepth)
		Message("Разбор списка аддинов. Файл " + this.path+ ": Не хватает КонецГруппы, " + stackDepth + " шт.", mInfo)
}

function AboutDlg()
{
	this.form = loadScriptForm("core\\std\\forms\\about.ssf", this)
	this.form.ЭлементыФормы.Версия.Заголовок = "Текущая версия: " + sVersion
	this.form.ОткрытьМодально()
}

AboutDlg.prototype.СсылкаНажатие = function(Элемент)
{
	ЗапуститьПриложение("http://snegopat.ru")
}

AboutDlg.prototype.ОКНажатие = function(Элемент)
{
	this.form.Закрыть()
}

function SnegopatWnd()
{
	SnegopatWnd.one = this
	this.form = loadScriptForm("core\\std\\forms\\snegopat.ssf", this)
	
	// Получим ссылку на коллекцию кнопок контекстного меню
	// Добавим колонку для хранения ссылки на группу/аддин
	this.form.AddinsTree.Колонки.Добавить("object")
	with(this.form)
	{
		КлючУникальности = "SnegopatMainForm"
		КлючСохраненияПоложенияОкна = "SnegopatMainForm"
		this.refreshAddinTree()
	}
	this.menu = this.form.ЭлементыФормы.CmdBar.Кнопки.Найти("Действия").Кнопки
	this.btnForGroup = new Array()
	this.btnForAddin = new Array()
	this.btnForGroup.push(this.menu.Найти("ДобавитьГруппу"))
	var loadIdx = this.menu.Индекс(this.btnForGroup[0]) + 1
	var loadersString = new VBArray(addins.getLoaderCommands()).toArray()
	for(var k in loadersString)
	{
		var cmd = loadersString[k].split('|')
		this.btnForGroup.push(this.menu.Вставить(loadIdx, cmd[1], v8New("ПеречислениеТипКнопкиКоманднойПанели").Действие, cmd[0] + " ...", v8New("Действие", "ХочуЗагрузить")))
		loadIdx++
	}
	this.btnForAddin.push(this.menu.Найти("UnloadScript"))
	this.btnForAddin.push(this.menu.Найти("RestartCurrentScript"))
	
	this.scriptsMenuItems = v8New("Структура"); 
	
	// Восстановим состояние окна
	profileRoot.createValue(wndStateProfilePath, true, pflSnegopat)
	var isWndOpened = profileRoot.getValue(wndStateProfilePath)
	if(isWndOpened)
		this.form.Открыть()
}

SnegopatWnd.prototype.ХочуЗагрузить = function(Кнопка)
{
	this.loadedGroup = this.form.ЭлементыФормы.AddinsTree.ТекущаяСтрока.object
	try{
		addins.selectAndLoad(Кнопка.val.Имя, this.loadedGroup)
	}catch(e)
	{
		Message("Ошибка при загрузке: " + e.description)
	}
	delete this.loadedGroup
}

SnegopatWnd.prototype.refreshAddinTree = function()
{
    var rows = this.form.AddinsTree.Строки
	rows.Очистить()
	this.fillAddinTree(rows, addins.root)
}

SnegopatWnd.prototype.fillAddinTree = function(rows, group)
{
	for(var child = group.child; child; child = child.next)
	{
		var row = rows.Добавить()
		row.Picture = 0
		row.Addin = child.name
		row.object = child
		this.fillAddinTree(row.Строки, child)
	}
	for(var i = 0, count = group.addinsCount; i < count; i++)
	{
		var row = rows.Добавить()
		var addin = group.addin(i)
		row.Picture = 2
		row.Addin = addin.displayName
		row.uniqueName = addin.uniqueName
		row.fullPath = addin.fullPath
		row.object = addin
	}
}
 
SnegopatWnd.prototype.AddinsTreeПриВыводеСтроки = function(Элемент, ОформлениеСтроки, ДанныеСтроки)
{
    var isAddin = ДанныеСтроки.val.Picture != 0
}

SnegopatWnd.prototype.AddinsTreeПриАктивизацииСтроки = function(Элемент)
{
	var current = Элемент.val.ТекущаяСтрока
	var isAddin = current.Picture != 0
	var isInUserGroup = false
	if(ActiveIniList)
	{
		var testGroup = isAddin ? current.Родитель : current
		while(testGroup)
		{
			if(testGroup.object == ActiveIniList.root.group)
			{
				isInUserGroup = true
				break
			}
			testGroup = testGroup.Родитель
		}
	}
	for(var k in this.btnForGroup)	// Команды для работы с группами доступны если стоим на пользовательской группе
		this.btnForGroup[k].Доступность = !isAddin && isInUserGroup
	for(var k in this.btnForAddin)
	{
		this.btnForAddin[k].Доступность = isAddin && isInUserGroup
		if(isAddin)
			this.btnForAddin[k].Текст = this.btnForAddin[k].Текст.replace(/\s+.*|$/, ' ' + current.object.displayName)
		else
			this.btnForAddin[k].Текст = this.btnForAddin[k].Текст.replace(/\s+.*$/, '')
	}
}

SnegopatWnd.prototype.ПриОткрытии = function()
{
	//Message("Открыли окно снегопата")
}

SnegopatWnd.prototype.ПриЗакрытии = function()
{
	//Message("Закрыли окно снегопата")
}

SnegopatWnd.prototype.CmdBaractSnegopatSettings = function(Кнопка)
{
	addins.byUniqueName("settings").invokeMacros("ОткрытьНастройкиСнегопата")
}

SnegopatWnd.prototype.SetupKeyboard = function(Кнопка)
{
	addins.byUniqueName("hotkeys").invokeMacros("РедактироватьГорячиеКлавиши")
}

SnegopatWnd.prototype.CmdBarВыполнитьМакрос = function(Кнопка)
{
	addins.byUniqueName("SnegopatMainScript").invokeMacros("ВыбратьИВыполнитьМакрос")
}

SnegopatWnd.prototype.CmdBarДобавитьГруппу = function(Кнопка)
{
	/*
	// В JScript'е это не работает, тк не поддерживается передача параметров по ссылке :(
	var text = ''
	globalContext("{38406666-F954-489E-BB5B-B0E6C0C81AFB}").ВвестиСтроку(text, "Укажите название группы")
	Message(text)
	*/
	// Приходится извращаться через VB
	var vbs = addins.byUniqueName("vbs").object
	//var input = vbs.DoEval('InputBox("Укажите название группы", "Снегопат")')	// Это некошерно, некрасивое окно появляется
	vbs.result = ""
	input = vbs.DoExecute('InputString result, "Укажите название группы"')
	if(!input.length)
		return
	var currentRow = this.form.ЭлементыФормы.AddinsTree.ТекущаяСтрока
	// Проверим, что нет такой группы
	for(var child = currentRow.object.child ; child; child = child.next)
	{
		if(child.name == input)
		{
			MessageBox("Группа с именем " + input + " уже существует")
			return
		}
	}
	var group = currentRow.object.addGroup(input)
	if(group)
	{
		var row = currentRow.Строки.Добавить()
		row.Picture = 0
		row.Addin = group.name
		row.object = group
		this.form.ЭлементыФормы.AddinsTree.ТекущаяСтрока = row
	}
}

SnegopatWnd.prototype.CmdBarEditScriptForm = function(Кнопка)
{
	addins.byUniqueName("SnegopatMainScript").invokeMacros("ОткрытьФормуСкрипта")
}

SnegopatWnd.prototype.CmdBarNewScriptForm = function(Кнопка)
{
	addins.byUniqueName("SnegopatMainScript").invokeMacros("НоваяФормаСкрипта")
}

SnegopatWnd.prototype.CmdBarОткрытьИни = function(Кнопка)
{
	ActiveIniList.openIni()
}

SnegopatWnd.prototype.onLoadAddin = function(addin)
{
	if(this.loadedGroup)	// Это загрузился аддин по нашей команде "Загрузить"
	{
		var row = this.form.ЭлементыФормы.AddinsTree.ТекущаяСтрока.Строки.Добавить()
		row.Picture = 2
		row.Addin = addin.displayName
		row.uniqueName = addin.uniqueName
		row.fullPath = addin.fullPath
		row.object = addin
		this.form.ЭлементыФормы.AddinsTree.ТекущаяСтрока = row
	}
	else if(this.reloadAddinRow)	// Это загрузился аддин по команде "Перезагрузить"
	{
		this.reloadAddinRow.Addin = addin.displayName
		this.reloadAddinRow.uniqueName = addin.uniqueName
		this.reloadAddinRow.fullPath = addin.fullPath
		this.reloadAddinRow.object = addin
	}
	else	// Аддин типа где-то со стороны загрузился
		this.refreshAddinTree()
}

SnegopatWnd.prototype.onUnLoadAddin = function(addin)
{
	if(this.unloadedAddin)	// Это выгружается аддин по команде "Выгрузить аддин"
		return
	this.refreshAddinTree()
}

// Выгрузить текущий аддин
SnegopatWnd.prototype.CmdBarUnloadScript = function(Кнопка)
{
	var currentRow = this.form.ЭлементыФормы.AddinsTree.ТекущаяСтрока
	this.unloadedAddin = currentRow.object
	var displayName = this.unloadedAddin.displayName

	try{
		addins.unloadAddin(this.unloadedAddin);
	}
	catch(e)
	{
		Message("Ошибка при выгрузке аддина " + displayName + ": " + e.description)
	}
	if(!this.unloadedAddin.uniqueName.length)	// аддин реально выгрузился
		currentRow.Родитель.Строки.Удалить(currentRow)
	delete this.unloadedAddin
}

SnegopatWnd.prototype.CmdBarRestartCurrentScript = function(Кнопка)
{
	// Сначала надо выгрузить аддин
	var currentRow = this.form.ЭлементыФормы.AddinsTree.ТекущаяСтрока
	this.unloadedAddin = currentRow.object
	var displayName = this.unloadedAddin.displayName
	var loadString = this.unloadedAddin.fullPath
	var addinGroup = this.unloadedAddin.group

	try{
		addins.unloadAddin(this.unloadedAddin);
	}
	catch(e)
	{
		Message("Ошибка при выгрузке аддина " + displayName + ": " + e.description)
	}
	if(!this.unloadedAddin.uniqueName.length)	// аддин реально выгрузился
	{
		this.reloadAddinRow = currentRow
		try{
			addins.loadAddin(loadString, addinGroup)
		}catch(e)
		{
			Message("Ошибка при загрузке: " + e.description)
		}
		delete this.reloadAddinRow
	}
	delete this.unloadedAddin
}

SnegopatWnd.prototype.CmdBarAbout = function(Кнопка)
{
	new AboutDlg()
}

function Designer::beforeExitApp()
{
	var isWndOpened = SnegopatWnd.one && SnegopatWnd.one.form.Открыта()
	profileRoot.setValue(wndStateProfilePath, isWndOpened)
}

function Designer::onLoadAddin(addin)
{
	if(SnegopatWnd.one)
		SnegopatWnd.one.onLoadAddin(addin)
}

function Designer::onUnLoadAddin(addin)
{
	if(SnegopatWnd.one)
		SnegopatWnd.one.onUnLoadAddin(addin)
}

function getAddinList(path)
{
	ActiveIniList = new AddinList(path)
	return ActiveIniList
}

function getSnegopatWnd()
{
	if(!SnegopatWnd.one)
		new SnegopatWnd()
	return SnegopatWnd.one
}

// Возвращает следующий по порядку идентификатор элемента контекстного меню.
SnegopatWnd.prototype.GetNextContextMenuId = function() 
{
	if (!this.contextMenuHandler) 
	{
		return 'cmdExtMenuItem1';
	}
	
	return 'cmdExtMenuItem' + this.contextMenuHandlers.Количество();
}

// Обработчик выбора добавленного пункта контекстного меню.
SnegopatWnd.prototype.OnExtMenuItemClick = function(Кнопка)
{
	var handler = this.contextMenuHandlers.Получить(Кнопка.val.Имя);
	if (handler) 
	{ 
		handler(this.form.ЭлементыФормы.AddinsTree.ТекущаяСтрока);
	}
}

// Добавить пункт в контекстное меню окна Снегопата.
// Пример использования в стороннем скрипте:
//	
// 		var snegopatWnd = addins.byUniqueName("snegopatwnd").object.getSnegopatWnd();
//		snegopatWnd.AddContextMenuItem("Выполнить что-то", function(ТекущаяСтрока) { Message("Вызван обработчик"); } );
SnegopatWnd.prototype.AddContextMenuItem = function(title, handler) 
{
	var insertSplitter = false;
	
	if (!this.contextMenuHandlers) 
	{
		this.contextMenuHandlers = v8New("Соответствие");
		insertSplitter = true;
	}
	
	if (insertSplitter) 
	{
		this.menu.Добавить("cmdExtMenuSplitter", v8New("ПеречислениеТипКнопкиКоманднойПанели").Разделитель);
	}

	var newItemId = this.GetNextContextMenuId();
	this.contextMenuHandlers.Вставить(newItemId, handler);
	
	this.menu.Добавить(newItemId, v8New("ПеречислениеТипКнопкиКоманднойПанели").Действие, title, v8New("Действие", "OnExtMenuItemClick"));
	
}
