<?xml version="1.0"?>
<!--
    ***** BEGIN LICENSE BLOCK *****
    
    Copyright © 2009 Center for History and New Media
                     George Mason University, Fairfax, Virginia, USA
                     http://zotero.org
    
    This file is part of Zotero.
    
    Zotero is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    Zotero is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.
    
    You should have received a copy of the GNU Affero General Public License
    along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
    
    ***** END LICENSE BLOCK *****
-->

<!DOCTYPE bindings SYSTEM "chrome://zotero/locale/zotero.dtd">
<!-- <!DOCTYPE bindings SYSTEM "chrome://zotero/locale/attachmentbox.dtd"> -->

<bindings xmlns="http://www.mozilla.org/xbl"
		  xmlns:xbl="http://www.mozilla.org/xbl"
		  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
	
	<binding id="attachment-box">
		<resources>
			<stylesheet src="chrome://zotero/skin/bindings/attachmentbox.css"/>
			<stylesheet src="chrome://zotero-platform/content/attachmentbox.css"/>
		</resources>
		
		<implementation>
			<!--
				Public properties
			-->
			<field name="editable">false</field>
			<field name="clickableLink">false</field>
			<field name="displayButton">false</field>
			<field name="displayNote">false</field>
			
			<field name="buttonCaption"/>
			<field name="clickHandler"/>
			
			<!-- Modes are predefined settings groups for particular tasks -->
			<field name="_mode">"view"</field>
			<property name="mode" onget="return this._mode;">
				<setter>
				<![CDATA[
					Zotero.debug("Setting mode to '" + val + "'");
					
					this.editable = false;
					this.synchronous = false;
					this.displayURL = false;
					this.displayFileName = false;
					this.clickableLink = false;
					this.displayAccessed = false;
					this.displayPages = false;
					this.displayDateModified = false;
					this.displayIndexed = false;
					this.displayNote = false;
					this.displayNoteIfEmpty = false;
					
					switch (val) {
						case 'view':
							this.displayURL = true;
							this.displayFileName = true;
							this.clickableLink = true;
							this.displayAccessed = true;
							this.displayPages = true;
							this.displayIndexed = true;
							this.displayNote = true;
							this.displayDateModified = true;
							break;
						
						case 'edit':
							this.editable = true;
							this.displayURL = true;
							this.displayFileName = true;
							this.clickableLink = true;
							this.displayAccessed = true;
							this.displayPages = true;
							this.displayIndexed = true;
							this.displayNote = true;
							this.displayNoteIfEmpty = true;
							this.displayDateModified = true;
							break;
						
						case 'merge':
							this.synchronous = true;
							this.displayURL = true;
							this.displayFileName = true;
							this.displayAccessed = true;
							this.displayNote = true;
							this.displayDateModified = true;
							break;
						
						case 'mergeedit':
							this.synchronous = true;
							this.editable = true;
							this.displayURL = true;
							this.displayFileName = true;
							this.displayAccessed = true;
							this.displayNote = true;
							// Notes aren't currently editable in mergeedit pane
							this.displayNoteIfEmpty = false;
							this.displayDateModified = true;
							break;
						
						case 'filemerge':
							this.synchronous = true;
							this.displayURL = true;
							this.displayFileName = true;
							this.displayDateModified = true;
							break;
						
						default:
							throw ("Invalid mode '" + val + "' in attachmentbox.xml");
					}
					
					this._mode = val;
					document.getAnonymousNodes(this)[0].setAttribute('mode', val);
				]]>
				</setter>
			</property>
			
			<field name="_item"/>
			<property name="item" onget="return this._item;">
				<setter><![CDATA[
					if (!(val instanceof Zotero.Item)) {
						throw new Error("'item' must be a Zotero.Item");
					}
					this._item = val;
					this.refresh();
				]]></setter>
			</property>
			
			<!-- Methods -->
			
			<constructor>
			<![CDATA[
				this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'attachmentbox');
			]]>
			</constructor>
			
			<destructor>
			<![CDATA[
				Zotero.Notifier.unregisterObserver(this._notifierID);
			]]>
			</destructor>
			
			<method name="notify">
				<parameter name="event"/>
				<parameter name="type"/>
				<parameter name="ids"/>
				<body><![CDATA[
					if (event != 'modify' || !this.item || !this.item.id) return;
					for (let i = 0; i < ids.length; i++) {
						if (ids[i] != this.item.id) {
							continue;
						}
						this.refresh();
						break;
					}
				]]></body>
			</method>
			
			<method name="refresh">
				<body><![CDATA[
					Zotero.debug('Refreshing attachment box');
					
					var attachmentBox = document.getAnonymousNodes(this)[0];
					var title = this._id('title');
					var fileNameRow = this._id('fileNameRow');
					var urlField = this._id('url');
					var accessed = this._id('accessedRow');
					var pagesRow = this._id('pagesRow');
					var dateModifiedRow = this._id('dateModifiedRow');
					var indexStatusRow = this._id('indexStatusRow');
					var selectButton = this._id('select-button');
					
					// DEBUG: this is annoying -- we really want to use an abstracted
					// version of createValueElement() from itemPane.js
					// (ideally in an XBL binding)
					
					// Wrap title to multiple lines if necessary
					while (title.hasChildNodes()) {
						title.removeChild(title.firstChild);
					}
					var val = this.item.getField('title');
					
					if (typeof val != 'string') {
						val += "";
					}
					
					var firstSpace = val.indexOf(" ");
					// Crop long uninterrupted text, and use value attribute for empty field
					if ((firstSpace == -1 && val.length > 29 ) || firstSpace > 29 || val === "") {
						title.setAttribute('crop', 'end');
						title.setAttribute('value', val);
					}
					// Create a <description> element, essentially
					else {
						title.removeAttribute('value');
						title.appendChild(document.createTextNode(val));
					}
					
					if (this.editable) {
						title.className = 'zotero-clicky';
						
						// For the time being, use a silly little popup
						title.addEventListener('click', this.editTitle, false);
					}
					
					var isImportedURL = this.item.attachmentLinkMode ==
											Zotero.Attachments.LINK_MODE_IMPORTED_URL;
					
					// Metadata for URL's
					if (this.item.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL
							|| isImportedURL) {
						
						// URL
						if (this.displayURL) {
							var urlSpec = this.item.getField('url');
							urlField.setAttribute('value', urlSpec);
							urlField.setAttribute('hidden', false);
							if (this.clickableLink) {
								 urlField.onclick = function (event) {
									ZoteroPane_Local.loadURI(this.value, event)
								};
								urlField.className = 'zotero-text-link';
							}
							else {
								urlField.className = '';
							}
							urlField.hidden = false;
						}
						else {
							urlField.hidden = true;
						}
						
						// Access date
						if (this.displayAccessed) {
							this._id("accessed-label").value = Zotero.getString('itemFields.accessDate')
								+ Zotero.getString('punctuation.colon');
							this._id("accessed").value = Zotero.Date.sqlToDate(
									this.item.getField('accessDate'), true
								).toLocaleString();
							accessed.hidden = false;
						}
						else {
							accessed.hidden = true;
						}
					}
					// Metadata for files
					else {
						urlField.hidden = true;
						accessed.hidden = true;
					}
					
					if (this.item.attachmentLinkMode
								!= Zotero.Attachments.LINK_MODE_LINKED_URL
							&& this.displayFileName) {
						var fileName = this.item.attachmentFilename;
						
						if (fileName) {
							this._id("fileName-label").value = Zotero.getString('pane.item.attachments.filename')
								+ Zotero.getString('punctuation.colon');
							this._id("fileName").value = fileName;
							fileNameRow.hidden = false;
						}
						else {
							fileNameRow.hidden = true;
						}
					}
					else {
						fileNameRow.hidden = true;
					}
					
					// Page count
					if (this.displayPages) {
						Zotero.Fulltext.getPages(this.item.id)
						.tap(() => Zotero.Promise.check(this.item))
						.then(function (pages) {
							pages = pages ? pages.total : null;
							if (pages) {
								this._id("pages-label").value = Zotero.getString('itemFields.pages')
									+ Zotero.getString('punctuation.colon');
								this._id("pages").value = pages;
								pagesRow.hidden = false;
							}
							else {
								pagesRow.hidden = true;
							}
						}.bind(this));
					}
					else {
						pagesRow.hidden = true;
					}
					
					if (this.displayDateModified) {
						this._id("dateModified-label").value = Zotero.getString('itemFields.dateModified')
							+ Zotero.getString('punctuation.colon');
						// Conflict resolution uses a modal window, so promises won't work, but
						// the sync process passes in the file mod time as dateModified
						if (this.synchronous) {
							this._id("dateModified").value = Zotero.Date.sqlToDate(
								this.item.getField('dateModified'), true
							).toLocaleString();
							dateModifiedRow.hidden = false;
						}
						else {
							this.item.attachmentModificationTime
							.tap(() => Zotero.Promise.check(this._id))
							.then(function (mtime) {
								if (mtime) {
									this._id("dateModified").value = new Date(mtime).toLocaleString();
								}
								dateModifiedRow.hidden = !mtime;
							}.bind(this));
						}
					}
					else {
						dateModifiedRow.hidden = true;
					}
					
					// Full-text index information
					if (this.displayIndexed) {
						this.updateItemIndexedState()
						.tap(() => Zotero.Promise.check(this.item))
						.then(function () {
							indexStatusRow.hidden = false;
						});
					}
					else {
						indexStatusRow.hidden = true;
					}
					
					// Note editor
					var noteEditor = this._id('attachment-note-editor');
					if (this.displayNote && (this.displayNoteIfEmpty || this.item.getNote() != '')) {
						noteEditor.linksOnTop = true;
						noteEditor.hidden = false;
						
						// Don't make note editable (at least for now)
						if (this.mode == 'merge' || this.mode == 'mergeedit') {
							noteEditor.mode = 'merge';
							noteEditor.displayButton = false;
						}
						else {
							noteEditor.mode = this.mode;
						}
						noteEditor.parent = null;
						noteEditor.item = this.item;
					}
					else {
						noteEditor.hidden = true;
					}
					
					if (this.displayButton) {
						selectButton.label = this.buttonCaption;
						selectButton.hidden = false;
						selectButton.setAttribute('oncommand',
							'document.getBindingParent(this).clickHandler(this)');
					}
					else {
						selectButton.hidden = true;
					}
				]]></body>
			</method>
			
			
			<method name="editTitle">
				<body>
				<![CDATA[
					return Zotero.spawn(function* () {
						var item = document.getBindingParent(this).item;
						var oldTitle = item.getField('title');
						
						var nsIPS = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
								.getService(Components.interfaces.nsIPromptService);
						
						var newTitle = { value: oldTitle };
						var checkState = { value: Zotero.Prefs.get('lastRenameAssociatedFile') };
						
						while (true) {
							// Don't show "Rename associated file" option for
							// linked URLs
							if (item.attachmentLinkMode ==
									Zotero.Attachments.LINK_MODE_LINKED_URL) {
								var result = nsIPS.prompt(
									window,
									'',
									Zotero.getString('pane.item.attachments.rename.title'),
									newTitle,
									null,
									{}
								);
								
								// If they hit cancel or left it blank
								if (!result || !newTitle.value) {
									return;
								}
								
								break;
							}
							
							var result = nsIPS.prompt(
								window,
								'',
								Zotero.getString('pane.item.attachments.rename.title'),
								newTitle,
								Zotero.getString('pane.item.attachments.rename.renameAssociatedFile'),
								checkState
							);
							
							// If they hit cancel or left it blank
							if (!result || !newTitle.value) {
								return;
							}
							
							Zotero.Prefs.set('lastRenameAssociatedFile', checkState.value);
							
							// Rename associated file
							if (checkState.value) {
								var newFilename = newTitle.value.trim();
								if (newFilename.search(/\.\w{1,10}$/) == -1) {
									// User did not specify extension. Use current
									var oldExt = item.getFilename().match(/\.\w{1,10}$/);
									if (oldExt) newFilename += oldExt[0];
								}
								var renamed = yield item.renameAttachmentFile(newFilename);
								if (renamed == -1) {
									var confirmed = nsIPS.confirm(
										window,
										'',
										newFilename + ' exists. Overwrite existing file?'
									);
									if (!confirmed) {
										// If they said not to overwrite existing file,
										// start again
										continue;
									}
									
									// Force overwrite, but make sure we check that this doesn't fail
									renamed = yield item.renameAttachmentFile(newFilename, true);
								}
								
								if (renamed == -2) {
									nsIPS.alert(
										window,
										Zotero.getString('general.error'),
										Zotero.getString('pane.item.attachments.rename.error')
									);
									return;
								}
								else if (!renamed) {
									nsIPS.alert(
										window,
										Zotero.getString('pane.item.attachments.fileNotFound.title'),
										Zotero.getString('pane.item.attachments.fileNotFound.text')
									);
								}
							}
							
							break;
						}
						
						if (newTitle.value != oldTitle) {
							item.setField('title', newTitle.value);
							yield item.saveTx();
						}
					}.bind(this));
				]]>
				</body>
			</method>
			
			
			<method name="onViewClick">
				<parameter name="event"/>
				<body>
				<![CDATA[
					ZoteroPane_Local.viewAttachment(this.item.id, event, !this.editable);
				]]>
				</body>
			</method>
			
			
			<method name="onShowClick">
				<parameter name="event"/>
				<body>
				<![CDATA[
					ZoteroPane_Local.showAttachmentInFilesystem(this.item.id, event.originalTarget, !this.editable);
				]]>
				</body>
			</method>
			
			
			<!--
			 Update Indexed: (Yes|No|Partial) line
			-->
			<method name="updateItemIndexedState">
				<body><![CDATA[
					return Zotero.spawn(function* () {
						var indexStatus = this._id('index-status');
						var reindexButton = this._id('reindex');
						
						var status = yield Zotero.Fulltext.getIndexedState(this.item)
							.tap(() => Zotero.Promise.check(this.item));
						var str = 'fulltext.indexState.';
						switch (status) {
							case Zotero.Fulltext.INDEX_STATE_UNAVAILABLE:
								str += 'unavailable';
								break;
							case Zotero.Fulltext.INDEX_STATE_UNINDEXED:
								str = 'general.no';
								break;
							case Zotero.Fulltext.INDEX_STATE_PARTIAL:
								str += 'partial';
								break;
							case Zotero.Fulltext.INDEX_STATE_QUEUED:
								str += 'queued';
								break;
							case Zotero.Fulltext.INDEX_STATE_INDEXED:
								str = 'general.yes';
								break;
						}
						this._id("index-status-label").value = Zotero.getString('fulltext.indexState.indexed')
							+ Zotero.getString('punctuation.colon');
						indexStatus.value = Zotero.getString(str);
						
						// Reindex button tooltip (string stored in zotero.properties)
						var str = Zotero.getString('pane.items.menu.reindexItem');
						reindexButton.setAttribute('tooltiptext', str);
						
						var show = false;
						if (this.editable) {
							show = yield Zotero.Fulltext.canReindex(this.item)
								.tap(() => Zotero.Promise.check(this.item));
						}
						
						if (show) {
							reindexButton.setAttribute('hidden', false);
						}
						else {
							reindexButton.setAttribute('hidden', true);
						}
					}, this);
				]]></body>
			</method>
			
			
			<method name="_id">
				<parameter name="id"/>
				<body>
				<![CDATA[
					return document.getAnonymousNodes(this)[0].getElementsByAttribute('id', id)[0];
				]]>
				</body>
			</method>
		</implementation>
		
		<content>
			<vbox id="attachment-box" flex="1" orient="vertical"
				 	xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
				<vbox id="metadata">
					<label id="title"/>
					<label id="url" crop="end"
						ondragstart="var dt = event.dataTransfer; dt.setData('text/x-moz-url', this.value); dt.setData('text/uri-list', this.value); dt.setData('text/plain', this.value);"/>
					<grid>
						<columns>
							<column/>
							<column flex="1"/>
						</columns>
						<rows>
							<row id="fileNameRow">
								<label id="fileName-label"/>
								<label id="fileName" crop="end"/>
							</row>
							<row id="accessedRow">
								<label id="accessed-label"/>
								<label id="accessed"/>
							</row>
							<row id="pagesRow">
								<label id="pages-label"/>
								<label id="pages"/>
							</row>
							<row id="dateModifiedRow" hidden="true">
								<label id="dateModified-label"/>
								<label id="dateModified"/>
							</row>
							<row id="indexStatusRow">
								<label id="index-status-label"/>
								<hbox>
									<label id="index-status"/>
									<image id="reindex" onclick="this.hidden = true; setTimeout(function () { ZoteroPane_Local.reindexItem(); }, 50)"/>
								</hbox>
							</row>
						</rows>
					</grid>
				</vbox>
				
				<zoteronoteeditor id="attachment-note-editor" notitle="1" flex="1"/>
				
				<button id="select-button" hidden="true"/>
			</vbox>
		</content>
	</binding>
</bindings>
