]> git.argeo.org Git - lgpl/argeo-commons.git/blob - AbstractPageViewer.java
1d0c9620a2b880099f62eecedd0bf4856f75493c
[lgpl/argeo-commons.git] / AbstractPageViewer.java
1 package org.argeo.cms.ui.viewers;
2
3 import java.security.AccessControlContext;
4 import java.security.AccessController;
5 import java.security.PrivilegedAction;
6 import java.util.Observable;
7 import java.util.Observer;
8
9 import javax.jcr.Node;
10 import javax.jcr.RepositoryException;
11 import javax.jcr.Session;
12 import javax.security.auth.Subject;
13
14 import org.argeo.api.cms.CmsLog;
15 import org.argeo.api.cms.ux.CmsEditable;
16 import org.argeo.cms.ui.widgets.ScrolledPage;
17 import org.argeo.jcr.JcrException;
18 import org.eclipse.jface.viewers.ContentViewer;
19 import org.eclipse.jface.viewers.ISelection;
20 import org.eclipse.jface.viewers.StructuredSelection;
21 import org.eclipse.swt.SWT;
22 import org.eclipse.swt.events.FocusEvent;
23 import org.eclipse.swt.events.FocusListener;
24 import org.eclipse.swt.events.MouseAdapter;
25 import org.eclipse.swt.events.MouseListener;
26 import org.eclipse.swt.widgets.Composite;
27 import org.eclipse.swt.widgets.Control;
28 import org.eclipse.swt.widgets.Widget;
29 import org.xml.sax.SAXParseException;
30
31 /** Base class for viewers related to a page */
32 public abstract class AbstractPageViewer extends ContentViewer implements Observer {
33 private static final long serialVersionUID = 5438688173410341485L;
34
35 private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class);
36
37 private final boolean readOnly;
38 /** The basis for the layouts, typically a ScrolledPage. */
39 private final Composite page;
40 private final CmsEditable cmsEditable;
41
42 private MouseListener mouseListener;
43 private FocusListener focusListener;
44
45 private EditablePart edited;
46 private ISelection selection = StructuredSelection.EMPTY;
47
48 private AccessControlContext accessControlContext;
49
50 protected AbstractPageViewer(Section parent, int style, CmsEditable cmsEditable) {
51 // read only at UI level
52 readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
53
54 this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable;
55 if (this.cmsEditable instanceof Observable)
56 ((Observable) this.cmsEditable).addObserver(this);
57
58 if (cmsEditable.canEdit()) {
59 mouseListener = createMouseListener();
60 focusListener = createFocusListener();
61 }
62 page = findPage(parent);
63 accessControlContext = AccessController.getContext();
64 }
65
66 /**
67 * Can be called to simplify the called to isModelInitialized() and initModel()
68 */
69 protected void initModelIfNeeded(Node node) {
70 try {
71 if (!isModelInitialized(node))
72 if (getCmsEditable().canEdit()) {
73 initModel(node);
74 node.getSession().save();
75 }
76 } catch (RepositoryException e) {
77 throw new JcrException("Cannot initialize model", e);
78 }
79 }
80
81 /** Called if user can edit and model is not initialized */
82 protected Boolean isModelInitialized(Node node) throws RepositoryException {
83 return true;
84 }
85
86 /** Called if user can edit and model is not initialized */
87 protected void initModel(Node node) throws RepositoryException {
88 }
89
90 /** Create (retrieve) the MouseListener to use. */
91 protected MouseListener createMouseListener() {
92 return new MouseAdapter() {
93 private static final long serialVersionUID = 1L;
94 };
95 }
96
97 /** Create (retrieve) the FocusListener to use. */
98 protected FocusListener createFocusListener() {
99 return new FocusListener() {
100 private static final long serialVersionUID = 1L;
101
102 @Override
103 public void focusLost(FocusEvent event) {
104 }
105
106 @Override
107 public void focusGained(FocusEvent event) {
108 }
109 };
110 }
111
112 protected Composite findPage(Composite composite) {
113 if (composite instanceof ScrolledPage) {
114 return (ScrolledPage) composite;
115 } else {
116 if (composite.getParent() == null)
117 return composite;
118 return findPage(composite.getParent());
119 }
120 }
121
122 public void layoutPage() {
123 if (page != null)
124 page.layout(true, true);
125 }
126
127 protected void showControl(Control control) {
128 if (page != null && (page instanceof ScrolledPage))
129 ((ScrolledPage) page).showControl(control);
130 }
131
132 @Override
133 public void update(Observable o, Object arg) {
134 if (o == cmsEditable)
135 editingStateChanged(cmsEditable);
136 }
137
138 /** To be overridden in order to provide the actual refresh */
139 protected void refresh(Control control) throws RepositoryException {
140 }
141
142 /** To be overridden.Save the edited part. */
143 protected void save(EditablePart part) throws RepositoryException {
144 }
145
146 /** Prepare the edited part */
147 protected void prepare(EditablePart part, Object caretPosition) {
148 }
149
150 /** Notified when the editing state changed. Does nothing, to be overridden */
151 protected void editingStateChanged(CmsEditable cmsEditable) {
152 }
153
154 @Override
155 public void refresh() {
156 // TODO check actual context in order to notice a discrepancy
157 Subject viewerSubject = getViewerSubject();
158 Subject.doAs(viewerSubject, (PrivilegedAction<Void>) () -> {
159 try {
160 if (cmsEditable.canEdit() && !readOnly)
161 mouseListener = createMouseListener();
162 else
163 mouseListener = null;
164 refresh(getControl());
165 // layout(getControl());
166 if (!getControl().isDisposed())
167 layoutPage();
168 } catch (RepositoryException e) {
169 throw new JcrException("Cannot refresh", e);
170 }
171 return null;
172 });
173 }
174
175 @Override
176 public void setSelection(ISelection selection, boolean reveal) {
177 this.selection = selection;
178 }
179
180 protected void updateContent(EditablePart part) throws RepositoryException {
181 }
182
183 // LOW LEVEL EDITION
184 protected void edit(EditablePart part, Object caretPosition) {
185 try {
186 if (edited == part)
187 return;
188
189 if (edited != null && edited != part) {
190 EditablePart previouslyEdited = edited;
191 try {
192 stopEditing(true);
193 } catch (Exception e) {
194 notifyEditionException(e);
195 edit(previouslyEdited, caretPosition);
196 return;
197 }
198 }
199
200 part.startEditing();
201 edited = part;
202 updateContent(part);
203 prepare(part, caretPosition);
204 edited.getControl().addFocusListener(new FocusListener() {
205 private static final long serialVersionUID = 6883521812717097017L;
206
207 @Override
208 public void focusLost(FocusEvent event) {
209 stopEditing(true);
210 }
211
212 @Override
213 public void focusGained(FocusEvent event) {
214 }
215 });
216
217 layout(part.getControl());
218 showControl(part.getControl());
219 } catch (RepositoryException e) {
220 throw new JcrException("Cannot edit " + part, e);
221 }
222 }
223
224 protected void stopEditing(Boolean save) {
225 if (edited instanceof Widget && ((Widget) edited).isDisposed()) {
226 edited = null;
227 return;
228 }
229
230 assert edited != null;
231 if (edited == null) {
232 if (log.isTraceEnabled())
233 log.warn("Told to stop editing while not editing anything");
234 return;
235 }
236
237 try {
238 if (save)
239 save(edited);
240
241 edited.stopEditing();
242 EditablePart editablePart = edited;
243 Control control = ((EditablePart) edited).getControl();
244 edited = null;
245 // TODO make edited state management more robust
246 updateContent(editablePart);
247 layout(control);
248 } catch (RepositoryException e) {
249 throw new JcrException("Cannot stop editing", e);
250 } finally {
251 edited = null;
252 }
253 }
254
255 // METHODS AVAILABLE TO EXTENDING CLASSES
256 protected void saveEdit() {
257 if (edited != null)
258 stopEditing(true);
259 }
260
261 protected void cancelEdit() {
262 if (edited != null)
263 stopEditing(false);
264 }
265
266 /** Layout this controls from the related base page. */
267 public void layout(Control... controls) {
268 page.layout(controls);
269 }
270
271 /**
272 * Find the first {@link EditablePart} in the parents hierarchy of this control
273 */
274 protected EditablePart findDataParent(Control parent) {
275 if (parent instanceof EditablePart) {
276 return (EditablePart) parent;
277 }
278 if (parent.getParent() != null)
279 return findDataParent(parent.getParent());
280 else
281 throw new IllegalStateException("No data parent found");
282 }
283
284 // UTILITIES
285 /** Check whether the edited part is in a proper state */
286 protected void checkEdited() {
287 if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed())
288 throw new IllegalStateException("Edited should not be null or disposed at this stage");
289 }
290
291 /** Persist all changes. */
292 protected void persistChanges(Session session) throws RepositoryException {
293 session.save();
294 session.refresh(false);
295 // TODO notify that changes have been persisted
296 }
297
298 /** Convenience method using a Node in order to save the underlying session. */
299 protected void persistChanges(Node anyNode) throws RepositoryException {
300 persistChanges(anyNode.getSession());
301 }
302
303 /** Notify edition exception */
304 protected void notifyEditionException(Throwable e) {
305 Throwable eToLog = e;
306 if (e instanceof IllegalArgumentException)
307 if (e.getCause() instanceof SAXParseException)
308 eToLog = e.getCause();
309 log.error(eToLog.getMessage(), eToLog);
310 // if (log.isTraceEnabled())
311 // log.trace("Full stack of " + eToLog.getMessage(), e);
312 // TODO Light error notification popup
313 }
314
315 protected Subject getViewerSubject() {
316 Subject res = null;
317 if (accessControlContext != null) {
318 res = Subject.getSubject(accessControlContext);
319 }
320 if (res == null)
321 throw new IllegalStateException("No subject associated with this viewer");
322 return res;
323 }
324
325 // GETTERS / SETTERS
326 public boolean isReadOnly() {
327 return readOnly;
328 }
329
330 protected EditablePart getEdited() {
331 return edited;
332 }
333
334 public MouseListener getMouseListener() {
335 return mouseListener;
336 }
337
338 public FocusListener getFocusListener() {
339 return focusListener;
340 }
341
342 public CmsEditable getCmsEditable() {
343 return cmsEditable;
344 }
345
346 @Override
347 public ISelection getSelection() {
348 return selection;
349 }
350 }