1 /*******************************************************************************
2  * Copyright (c) 2000, 2016 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Roland Tepp (roland@videobet.com) - patch (see Bugzilla #107197)
14  *******************************************************************************/
15 package org.eclipse.ui.forms;
16 
17 import java.util.ArrayList;
18 import java.util.Iterator;
19 
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.custom.SashForm;
22 import org.eclipse.swt.graphics.GC;
23 import org.eclipse.swt.graphics.Point;
24 import org.eclipse.swt.layout.GridData;
25 import org.eclipse.swt.layout.GridLayout;
26 import org.eclipse.swt.widgets.Composite;
27 import org.eclipse.swt.widgets.Control;
28 import org.eclipse.swt.widgets.Event;
29 import org.eclipse.swt.widgets.Listener;
30 import org.eclipse.swt.widgets.Sash;
31 import org.eclipse.ui.forms.widgets.FormToolkit;
32 import org.eclipse.ui.forms.widgets.ScrolledForm;
33 
34 /**
35  * This class implements the 'master/details' UI pattern suitable for inclusion
36  * in a form. The block consists of two parts: 'master' and 'details' in a sash
37  * form that allows users to change the relative ratio on the page. The master
38  * part needs to be created by the users of this class. The details part is
39  * created by the block.
40  * <p>
41  * The master part is responsible for adding itself as a form part and firing
42  * selection events. The details part catches the selection events and tries to
43  * load a page registered to handle the selected object(s). The page shows the
44  * details of the selected object(s) and allows users to edit them.
45  * <p>
46  * Details pages can be registered statically using 'registerPage' or
47  * dynamically through the use of 'IDetailsPageProvider' in case where different
48  * pages need to be shown for objects of the same type depending on their state.
49  * <p>
50  * Subclasses are required to implement abstract methods of this class. Master
51  * part must be created and at least one details page should be registered in
52  * order to show details of the objects selected in the master part. Tool bar
53  * actions can be optionally added to the tool bar manager.
54  *
55  * @see DetailsPart
56  * @see IDetailsPage
57  * @see IDetailsPageProvider
58  * @since 3.0
59  */
60 public abstract class MasterDetailsBlock {
61 	/**
62 	 * Details part created by the block. No attempt should be made to access
63 	 * this field inside <code>createMasterPart</code> because it has not been
64 	 * created yet and will be <code>null</code>.
65 	 */
66 	protected DetailsPart detailsPart;
67 
68 	/**
69 	 * The form that is the parent of both master and details part. The form
70 	 * allows users to change the ratio between the two parts.
71 	 */
72 	protected SashForm sashForm;
73 
74 	static final int DRAGGER_SIZE = 40;
75 
76 	class MDSashForm extends SashForm {
77 		ArrayList<Sash> sashes = new ArrayList<>();
78 		Listener listener = e -> {
79 			switch (e.type) {
80 			case SWT.MouseEnter:
81 				e.widget.setData("hover", Boolean.TRUE); //$NON-NLS-1$
82 				((Control) e.widget).redraw();
83 				break;
84 			case SWT.MouseExit:
85 				e.widget.setData("hover", null); //$NON-NLS-1$
86 				((Control) e.widget).redraw();
87 				break;
88 			case SWT.Paint:
89 				onSashPaint(e);
90 				break;
91 			case SWT.Resize:
92 				hookSashListeners();
93 				break;
94 			}
95 		};
MDSashForm(Composite parent, int style)96 		public MDSashForm(Composite parent, int style) {
97 			super(parent, style);
98 		}
99 
100 		@Override
layout(boolean changed)101 		public void layout(boolean changed) {
102 			super.layout(changed);
103 			hookSashListeners();
104 		}
105 
106 		@Override
layout(Control [] children)107 		public void layout(Control [] children) {
108 			super.layout(children);
109 			hookSashListeners();
110 		}
111 
hookSashListeners()112 		private void hookSashListeners() {
113 			purgeSashes();
114 			Control [] children = getChildren();
115 			for (Control element : children) {
116 				if (element instanceof Sash) {
117 					Sash sash = (Sash)element;
118 					if (sashes.contains(sash))
119 						continue;
120 					sash.addListener(SWT.Paint, listener);
121 					sash.addListener(SWT.MouseEnter, listener);
122 					sash.addListener(SWT.MouseExit, listener);
123 					sashes.add(sash);
124 				}
125 			}
126 		}
purgeSashes()127 		private void purgeSashes() {
128 			for (Iterator<Sash> iter=sashes.iterator(); iter.hasNext();) {
129 				Sash sash = iter.next();
130 				if (sash.isDisposed())
131 					iter.remove();
132 			}
133 		}
134 	}
135 
136 	/**
137 	 * Creates the content of the master/details block inside the managed form.
138 	 * This method should be called as late as possible inside the parent part.
139 	 *
140 	 * @param managedForm
141 	 *            the managed form to create the block in
142 	 */
createContent(IManagedForm managedForm)143 	public void createContent(IManagedForm managedForm) {
144 		final ScrolledForm form = managedForm.getForm();
145 		createContent(managedForm, form.getBody());
146 	}
147 	/**
148 	 * Creates the content of the master/details block inside the parent composite.
149 	 * This method should be called as late as possible inside the parent part.
150 	 *
151 	 * @param managedForm
152 	 *            the managed form to create the block in
153 	 * @since 3.4
154 	 */
createContent(IManagedForm managedForm, Composite parent)155 	public void createContent(IManagedForm managedForm, Composite parent) {
156 		final ScrolledForm form = managedForm.getForm();
157 		FormToolkit toolkit = managedForm.getToolkit();
158 		applyLayout(parent);
159 		sashForm = new MDSashForm(parent, SWT.NULL);
160 		sashForm.setData("form", managedForm); //$NON-NLS-1$
161 		toolkit.adapt(sashForm, false, false);
162 		sashForm.setMenu(parent.getMenu());
163 		applyLayoutData(sashForm);
164 		createMasterPart(managedForm, sashForm);
165 		createDetailsPart(managedForm, sashForm);
166 		hookResizeListener();
167 		createToolBarActions(managedForm);
168 		form.updateToolBar();
169 	}
170 
171 	/**
172 	 * Applies layout data to the sash form containing master and detail parts
173 	 * of the master/details block.
174 	 *
175 	 * <p>The default implementation fills the whole parent composite area.
176 	 * Override this method if master/details block is to be placed on a form
177 	 * along with other components.</p>
178 	 *
179 	 * <p>Implementations that override this method should also override
180 	 * <code>applyLayout(Composite)</code> method implementation.</p>
181 	 *
182 	 * @param sashForm The sash form to be laid out on the parent composite.
183 	 * @see #applyLayout(Composite)
184 	 * @since 3.4
185 	 */
applyLayoutData(SashForm sashForm)186 	protected void applyLayoutData(SashForm sashForm) {
187 		sashForm.setLayoutData(new GridData(GridData.FILL_BOTH));
188 	}
189 
190 	/**
191 	 * Applies layout to the parent composite of the master/details block.
192 	 *
193 	 * <p>The default implementation fills the whole parent composite area.
194 	 * Override this method if master/details block is to be placed on a form
195 	 * along with other components.</p>
196 	 *
197 	 * <p>Implementations that override this method should also override
198 	 * <code>applyLayoutData(SashForm)</code> method implementation.</p>
199 	 *
200 	 * @param parent parent composite for the master/details block
201 	 * @see #applyLayoutData(SashForm)
202 	 * @since 3.4
203 	 */
applyLayout(final Composite parent)204 	protected void applyLayout(final Composite parent) {
205 		GridLayout layout = new GridLayout();
206 		layout.marginWidth = 0;
207 		layout.marginHeight = 0;
208 		parent.setLayout(layout);
209 	}
210 
hookResizeListener()211 	private void hookResizeListener() {
212 		Listener listener = ((MDSashForm)sashForm).listener;
213 		Control [] children = sashForm.getChildren();
214 		for (Control element : children) {
215 			if (element instanceof Sash) continue;
216 			element.addListener(SWT.Resize, listener);
217 		}
218 	}
219 
220 	/**
221 	 * Implement this method to create a master part in the provided parent.
222 	 * Typical master parts are section parts that contain tree or table viewer.
223 	 *
224 	 * @param managedForm
225 	 *            the parent form
226 	 * @param parent
227 	 *            the parent composite
228 	 */
createMasterPart(IManagedForm managedForm, Composite parent)229 	protected abstract void createMasterPart(IManagedForm managedForm,
230 			Composite parent);
231 
232 	/**
233 	 * Implement this method to statically register pages for the expected
234 	 * object types. This mechanism can be used when there is 1-&gt;1 mapping
235 	 * between object classes and details pages.
236 	 *
237 	 * @param detailsPart
238 	 *            the details part
239 	 */
registerPages(DetailsPart detailsPart)240 	protected abstract void registerPages(DetailsPart detailsPart);
241 
242 	/**
243 	 * Implement this method to create form tool bar actions and add them to the
244 	 * form tool bar if desired.
245 	 *
246 	 * @param managedForm
247 	 *            the form that owns the tool bar
248 	 */
createToolBarActions(IManagedForm managedForm)249 	protected abstract void createToolBarActions(IManagedForm managedForm);
250 
createDetailsPart(final IManagedForm mform, Composite parent)251 	private void createDetailsPart(final IManagedForm mform, Composite parent) {
252 		detailsPart = new DetailsPart(mform, parent, SWT.NULL);
253 		mform.addPart(detailsPart);
254 		registerPages(detailsPart);
255 	}
256 
onSashPaint(Event e)257 	private void onSashPaint(Event e) {
258 		Sash sash = (Sash)e.widget;
259 		IManagedForm form = (IManagedForm)sash.getParent().getData("form"); //$NON-NLS-1$
260 		FormColors colors = form.getToolkit().getColors();
261 		boolean vertical = (sash.getStyle() & SWT.VERTICAL)!=0;
262 		GC gc = e.gc;
263 		Boolean hover = (Boolean)sash.getData("hover"); //$NON-NLS-1$
264 		gc.setBackground(colors.getColor(IFormColors.TB_BG));
265 		gc.setForeground(colors.getColor(IFormColors.TB_BORDER));
266 		Point size = sash.getSize();
267 		if (vertical) {
268 			if (hover!=null)
269 				gc.fillRectangle(0, 0, size.x, size.y);
270 			//else
271 				//gc.drawLine(1, 0, 1, size.y-1);
272 		}
273 		else {
274 			if (hover!=null)
275 				gc.fillRectangle(0, 0, size.x, size.y);
276 			//else
277 				//gc.drawLine(0, 1, size.x-1, 1);
278 		}
279 	}
280 }
281