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->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