d158fdd83f99aa7e08e7b9a54a4c678d3bacc5cb
[Plinn.git] / HugePlinnFolder.py
1 # -*- coding: utf-8 -*-
2 #######################################################################################
3 # Plinn - http://plinn.org #
4 # Copyright (C) 2005-2007 Benoît PIN <benoit.pin@ensmp.fr> #
5 # #
6 # This program is free software; you can redistribute it and/or #
7 # modify it under the terms of the GNU General Public License #
8 # as published by the Free Software Foundation; either version 2 #
9 # of the License, or (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the Free Software #
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #
19 #######################################################################################
20 """ Plinn implementation of CMFBTree
21
22
23
24 """
25
26
27 from Products.BTreeFolder2.BTreeFolder2 import BTreeFolder2Base
28 from Products.ZCatalog.Lazy import LazyMap
29 from BTrees.IOBTree import IOBTree
30 from BTrees.OIBTree import OIBTree
31 from Folder import PlinnFolder
32 from zope.event import notify
33 try :
34 from zope.app.container.contained import notifyContainerModified
35 except ImportError :
36 ## Zope-2.13 compat
37 from zope.container.contained import notifyContainerModified
38 from events import ObjectPositionModified
39 from zope.component.factory import Factory
40 from Products.CMFCore.permissions import AddPortalFolders, \
41 ManageProperties, \
42 AccessContentsInformation
43 from AccessControl import ClassSecurityInfo
44 from Globals import InitializeClass
45 from types import StringType
46
47
48 class HugePlinnFolder(BTreeFolder2Base, PlinnFolder) :
49 """ Plinn Folder for large set of objects
50 """
51
52 security = ClassSecurityInfo()
53
54 __getitem__ = PlinnFolder.__getitem__
55
56 def __init__(self, id, title='') :
57 PlinnFolder.__init__(self, id, title)
58 BTreeFolder2Base.__init__(self, id)
59
60 def _initBTrees(self):
61 super(HugePlinnFolder, self)._initBTrees()
62 self._pos2id_index = IOBTree()
63 self._id2pos_index = OIBTree()
64
65 def _checkId(self, id, allow_dup=0) :
66 PlinnFolder._checkId(self, id, allow_dup)
67 BTreeFolder2Base._checkId(self, id, allow_dup)
68
69 security.declareProtected(AddPortalFolders, 'manage_addHugePlinnFolder')
70 def manage_addHugePlinnFolder(self, id, title='', REQUEST=None) :
71 """ Add new a new HugePlinnFolder object with id *id*.
72 """
73 ob = HugePlinnFolder(id, title)
74 self._setObject(id, ob)
75 if REQUEST is not None :
76 return self.folder_contents(self, REQUEST, portal_status_message='Folder added')
77
78 def _setOb(self, id, object):
79 super(HugePlinnFolder, self)._setOb(id, object)
80 pos = self.objectCount() - 1
81 self._pos2id_index[pos] = id
82 self._id2pos_index[id] = pos
83
84 def _delOb(self, id):
85 pos = self._id2pos_index[id]
86 self._id2pos_index.pop(id)
87
88 for p in xrange(pos+1, self.objectCount()) :
89 ident = self._pos2id_index[p]
90 self._pos2id_index[p-1] = ident
91 self._id2pos_index[ident] = p-1
92
93 self._pos2id_index.pop(self.objectCount()-1)
94
95 super(HugePlinnFolder, self)._delOb(id)
96
97 security.declareProtected(AccessContentsInformation, 'objectIds')
98 def objectIds(self, spec=None) :
99 if spec is not None :
100 return super(HugePlinnFolder, self).objectIds(spec)
101
102 pos2id = lambda pos : self._pos2id_index[pos]
103 return LazyMap(pos2id, xrange(self.objectCount()))
104
105
106
107 security.declareProtected(ManageProperties, 'moveObjectsByDelta')
108 def moveObjectsByDelta(self, ids, delta, subset_ids=None,
109 suppress_events=False):
110 """ Move specified sub-objects by delta.
111 """
112 if isinstance(ids, StringType):
113 ids = (ids,)
114
115 id2pos = self._id2pos_index
116 pos2id = self._pos2id_index
117 for id in ids :
118 oldPosition = id2pos[id]
119 newPosition = max(oldPosition + delta, 0)
120
121 shift = delta > 0 and 1 or -1
122 for p in xrange(oldPosition, newPosition, shift) :
123 ident = pos2id[p+shift]
124 pos2id[p] = ident
125 id2pos[ident] = p
126 if not suppress_events :
127 notify(ObjectPositionModified(self[ident], self, p))
128
129 id2pos[id] = newPosition
130 pos2id[newPosition] = id
131 if not suppress_events :
132 notify(ObjectPositionModified(self[id], self, newPosition))
133
134 if not suppress_events :
135 notifyContainerModified(self)
136
137 security.declareProtected(ManageProperties, 'moveObjectsAfter')
138 def moveObjectsAfter(self, ids, targetId, suppress_events=False):
139 assert targetId not in ids
140
141 # id2pos = dict(self._id2pos_index).copy()
142 # pos2id = dict(self._pos2id_index).copy()
143 id2pos = self._id2pos_index
144 pos2id = self._pos2id_index
145 targetPos = id2pos[targetId]
146 minMovedPos = min([id2pos[id] for id in ids])
147 maxMovedPos = max([id2pos[id] for id in ids])
148
149 for id in ids :
150 assert id == pos2id.pop(id2pos.pop(id))
151
152 id2posUpdate = {}
153 pos2idUpdate = {}
154
155 if targetPos < minMovedPos :
156 # selection moved before the first item position
157 for i, id in enumerate(ids) :
158 pos = i + targetPos + 1
159 id2posUpdate[id] = pos
160 pos2idUpdate[pos] = id
161
162 for id in IndexIterator(pos2id, maxMovedPos, start=targetPos+1):
163 pos = pos + 1
164 id2posUpdate[id] = pos
165 pos2idUpdate[pos] = id
166
167 elif targetPos > minMovedPos and targetPos < maxMovedPos :
168 print minMovedPos, maxMovedPos, targetPos
169 print "déposé entre la première et la dernière de la sélection"
170 raise NotImplementedError()
171 else :
172 # selection moved after the last item position
173 pos = minMovedPos
174 for id in IndexIterator(pos2id, targetPos, start=minMovedPos+1) :
175 id2posUpdate[id] = pos
176 pos2idUpdate[pos] = id
177 pos += 1
178
179 pos = targetPos - len(ids) + 1
180 for id in ids :
181 id2posUpdate[id] = pos
182 pos2idUpdate[pos] = id
183 pos +=1
184
185 id2pos.update(id2posUpdate)
186 pos2id.update(pos2idUpdate)
187
188 # just for debug
189 for pos in xrange(len(self)) :
190 assert pos2id.has_key(pos)
191 assert id2pos.has_key(pos2id[pos])
192
193 if not suppress_events :
194 for id, pos in id2posUpdate.items() :
195 notify(ObjectPositionModified(self[id], self, pos))
196
197 notifyContainerModified(self)
198
199 def getObjectPosition(self, id):
200 """ Get the position of an object by its id.
201 """
202 try :
203 return self._id2pos_index[id]
204 except KeyError :
205 raise ValueError('The object with the id "%s" does not exist.' % id)
206
207
208 class IndexIterator :
209 def __init__(self, d, maxPos, start=0, length=None) :
210 self.d = d
211 self.pos = start
212 self.maxPos = maxPos
213 self.length = length
214 self.fetchedValuesCpt = 0
215
216 def __iter__(self) :
217 return self
218
219 def next(self) :
220 try :
221 if self.pos > self.maxPos or \
222 self.fetchedValuesCpt == self.length:
223 raise StopIteration
224 v = self.d[self.pos]
225 self.pos = self.pos + 1
226 self.fetchedValuesCpt = self.fetchedValuesCpt + 1
227 return v
228 except KeyError :
229 self.pos = self.pos + 1
230 return self.next()
231
232
233 InitializeClass(HugePlinnFolder)
234 HugePlinnFolderFactory = Factory(HugePlinnFolder)
235 manage_addHugePlinnFolder = HugePlinnFolder.manage_addHugePlinnFolder.im_func