/* ***** BEGIN LICENSE BLOCK ***** Copyright (c) 2006 Center for History and New Media George Mason University, Fairfax, Virginia, USA http://chnm.gmu.edu Licensed under the Educational Community License, Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.opensource.org/licenses/ecl1.php Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ***** END LICENSE BLOCK ***** */ //////////////////////////////////////////////////////////////////////////////// /// /// ItemTreeView /// -- handles the link between an individual tree and the data layer /// -- displays only items (no collections, no hierarchy) /// //////////////////////////////////////////////////////////////////////////////// /* * Constructor for the ItemTreeView object */ Zotero.ItemTreeView = function(itemGroup, sourcesOnly) { this._initialized = false; this._itemGroup = itemGroup; this._sourcesOnly = sourcesOnly; this._callbacks = []; this._treebox = null; this._ownerDocument = null; this._needsSort = false; this._dataItems = []; this.rowCount = 0; this._unregisterID = Zotero.Notifier.registerObserver(this, ['item', 'collection-item']); } Zotero.ItemTreeView.prototype.addCallback = function(callback) { this._callbacks.push(callback); } Zotero.ItemTreeView.prototype._runCallbacks = function() { for each(var cb in this._callbacks) { cb(); } } /* * Called by the tree itself */ Zotero.ItemTreeView.prototype.setTree = function(treebox) { // Try to set the window document if not yet set if (treebox && !this._ownerDocument) { try { this._ownerDocument = treebox.treeBody.ownerDocument; } catch (e) {} } if (this._treebox) { if (this._needsSort) { this.sort(); } return; } this._treebox = treebox; if (this._ownerDocument.defaultView.ZoteroPane) { this._ownerDocument.defaultView.ZoteroPane.setItemsPaneMessage(Zotero.getString('pane.items.loading')); } // Generate the tree contents in a timer to allow message above to display var paneLoader = function(obj) { obj.refresh(); // Add a keypress listener for expand/collapse var expandAllRows = obj.expandAllRows; var collapseAllRows = obj.collapseAllRows; var tree = obj._treebox.treeBody.parentNode; tree.addEventListener('keypress', function(event) { var key = String.fromCharCode(event.which); if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) { expandAllRows(treebox); return; } else if (key == '-' && !(event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)) { collapseAllRows(treebox); return; } }, false); obj.sort(); //Zotero.debug('Running callbacks in itemTreeView.setTree()', 4); obj._runCallbacks(); if (obj._ownerDocument.defaultView.ZoteroPane) { obj._ownerDocument.defaultView.ZoteroPane.clearItemsPaneMessage(); } } this._ownerDocument.defaultView.setTimeout(paneLoader, 50, this); } /* * Reload the rows from the data access methods * (doesn't call the tree.invalidate methods, etc.) */ Zotero.ItemTreeView.prototype.refresh = function() { var oldRows = this.rowCount; this._dataItems = []; this.rowCount = 0; var cacheFields = ['title', 'date']; // Cache the visible fields so they don't load individually var visibleFields = this.getVisibleFields(); for each(var field in visibleFields) { if (field == 'year') { field = 'date'; } if (cacheFields.indexOf(field) == -1) { cacheFields = cacheFields.concat(field); } } Zotero.Items.cacheFields(cacheFields); var newRows = this._itemGroup.getChildItems(); for (var i=0, len=newRows.length; i < len; i++) { if (newRows[i] && (!this._sourcesOnly || (!newRows[i].isAttachment() && !newRows[i].isNote()))) { this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], 0, false), i+1); //item ref, before row } } this._refreshHashMap(); // Update the treebox's row count var diff = this.rowCount - oldRows; if (diff != 0) { this._treebox.rowCountChanged(0, diff); } } /* * Called by Zotero.Notifier on any changes to items in the data layer */ Zotero.ItemTreeView.prototype.notify = function(action, type, ids) { var madeChanges = false; var sort = false; this.selection.selectEventsSuppressed = true; var savedSelection = this.saveSelection(); if (this._treebox && this._treebox.treeBody) { // See if we're in the active window var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] .getService(Components.interfaces.nsIWindowMediator); if (wm.getMostRecentWindow("navigator:browser") == this._ownerDocument.defaultView){ var activeWindow = true; } } var quicksearch = this._ownerDocument.getElementById('zotero-tb-search'); // 'collection-item' ids are in the form collectionID-itemID if (type == 'collection-item') { var splitIDs = []; for each(var id in ids) { var split = id.split('-'); // Skip if not collection or not an item in this collection if (!this._itemGroup.isCollection() || split[0] != this._itemGroup.ref.getID()) { continue; } splitIDs.push(split[1]); } ids = splitIDs; } if((action == 'remove' && !this._itemGroup.isLibrary()) || action == 'delete') { //Since a remove involves shifting of rows, we have to do it in order //sort the ids by row var rows = new Array(); for(var i=0, len=ids.length; i 0) { rows.sort(function(a,b) { return a-b }); for(var i=0, len=rows.length; i= 0; i--) if(this.getLevel(i) < thisLevel) return i; return -1; } Zotero.ItemTreeView.prototype.hasNextSibling = function(row,afterIndex) { var thisLevel = this.getLevel(row); for(var i = afterIndex + 1; i < this.rowCount; i++) { var nextLevel = this.getLevel(i); if(nextLevel == thisLevel) return true; else if(nextLevel < thisLevel) return false; } } Zotero.ItemTreeView.prototype.toggleOpenState = function(row) { // Shouldn't happen but does if an item is dragged over a closed // container until it opens and then released, since the container // is no longer in the same place when the spring-load closes if (!this.isContainer(row)) { return; } var count = 0; //used to tell the tree how many rows were added/removed var thisLevel = this.getLevel(row); if(this.isContainerOpen(row)) { while((row + 1 < this._dataItems.length) && (this.getLevel(row + 1) > thisLevel)) { this._hideItem(row+1); count--; //count is negative when closing a container because we are removing rows } } else { var item = this._getItemAtRow(row).ref; //Get children var attachments = item.getAttachments(); var notes = item.getNotes(); var newRows; if(attachments && notes) newRows = notes.concat(attachments); else if(attachments) newRows = attachments; else if(notes) newRows = notes; if (newRows) { newRows = Zotero.Items.get(newRows); for(var i = 0; i < newRows.length; i++) { count++; this._showItem(new Zotero.ItemTreeView.TreeRow(newRows[i], thisLevel + 1, false), row + i + 1); // item ref, before row } } } this._treebox.beginUpdateBatch(); this._dataItems[row].isOpen = !this._dataItems[row].isOpen; this._treebox.rowCountChanged(row+1, count); //tell treebox to repaint these this._treebox.invalidateRow(row); this._treebox.endUpdateBatch(); this._refreshHashMap(); } Zotero.ItemTreeView.prototype.isSorted = function() { // We sort by the first column if none selected, so return true return true; } Zotero.ItemTreeView.prototype.cycleHeader = function(column) { for(var i=0, len=this._treebox.columns.count; i typeB) ? -1 : (typeA < typeB) ? 1 : 0; if (cmp) { return cmp; } break; case 'numChildren': cmp = b.numChildren() - a.numChildren(); if (cmp) { return cmp; } break; default: if (fieldA == undefined) { fieldA = a.getField(columnField, unformatted, true); if (typeof fieldA == 'string') { fieldA = fieldA.toLowerCase(); } cache[aItemID] = fieldA; } if (fieldB == undefined) { fieldB = b.getField(columnField, unformatted, true); if (typeof fieldB == 'string') { fieldB = fieldB.toLowerCase(); } cache[bItemID] = fieldB; } // Display rows with empty values last if (!emptyFirst[columnField]) { cmp = (fieldA == '' && fieldB != '') ? -1 : (fieldA != '' && fieldB == '') ? 1 : 0; if (cmp) { return cmp; } } cmp = (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0; if (cmp) { return cmp; } } if (columnField != 'firstCreator') { fieldA = a.getField('firstCreator'); fieldB = b.getField('firstCreator'); // Display rows with empty values last cmp = (fieldA == '' && fieldB != '') ? -1 : (fieldA != '' && fieldB == '') ? 1 : 0; if (cmp) { return cmp; } cmp = (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0; if (cmp) { return cmp; } } if (columnField != 'date') { fieldA = a.getField('date', true, true); fieldB = b.getField('date', true, true); // Display rows with empty values last cmp = (fieldA == '' && fieldB != '') ? -1 : (fieldA != '' && fieldB == '') ? 1 : 0; if (cmp) { return cmp; } cmp = (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0; if (cmp) { return cmp; } } fieldA = a.getField('dateModified'); fieldB = b.getField('dateModified'); return (fieldA > fieldB) ? -1 : (fieldA < fieldB) ? 1 : 0; } function doSort(a,b) { return columnSort(a,b); } function reverseSort(a,b) { return columnSort(a,b) * -1; } // Need to close all containers before sorting var openRows = new Array(); for (var i=0; i