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