1 /******************************************************************************* 2 * Copyright (c) 2000, 2017 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 *******************************************************************************/ 14 package org.eclipse.compare; 15 16 import java.text.DateFormat; 17 import java.text.MessageFormat; 18 import java.util.ArrayList; 19 import java.util.Arrays; 20 import java.util.Calendar; 21 import java.util.Collections; 22 import java.util.Date; 23 import java.util.HashMap; 24 import java.util.HashSet; 25 import java.util.Iterator; 26 import java.util.List; 27 import java.util.ResourceBundle; 28 29 import org.eclipse.compare.internal.CompareContainer; 30 import org.eclipse.compare.internal.CompareUIPlugin; 31 import org.eclipse.compare.internal.ResizableDialog; 32 import org.eclipse.compare.internal.StructureCreatorDescriptor; 33 import org.eclipse.compare.internal.Utilities; 34 import org.eclipse.compare.structuremergeviewer.DiffNode; 35 import org.eclipse.compare.structuremergeviewer.ICompareInput; 36 import org.eclipse.compare.structuremergeviewer.IStructureComparator; 37 import org.eclipse.compare.structuremergeviewer.IStructureCreator; 38 import org.eclipse.core.runtime.Assert; 39 import org.eclipse.core.runtime.CoreException; 40 import org.eclipse.jface.dialogs.IDialogConstants; 41 import org.eclipse.jface.resource.ImageDescriptor; 42 import org.eclipse.jface.viewers.ISelection; 43 import org.eclipse.jface.viewers.IStructuredSelection; 44 import org.eclipse.jface.viewers.Viewer; 45 import org.eclipse.swt.SWT; 46 import org.eclipse.swt.events.SelectionAdapter; 47 import org.eclipse.swt.events.SelectionEvent; 48 import org.eclipse.swt.graphics.Image; 49 import org.eclipse.swt.layout.GridData; 50 import org.eclipse.swt.widgets.Button; 51 import org.eclipse.swt.widgets.Composite; 52 import org.eclipse.swt.widgets.Control; 53 import org.eclipse.swt.widgets.Display; 54 import org.eclipse.swt.widgets.Item; 55 import org.eclipse.swt.widgets.Label; 56 import org.eclipse.swt.widgets.Shell; 57 import org.eclipse.swt.widgets.Table; 58 import org.eclipse.swt.widgets.TableItem; 59 import org.eclipse.swt.widgets.Tree; 60 import org.eclipse.swt.widgets.TreeItem; 61 import org.eclipse.swt.widgets.Widget; 62 63 /** 64 * A dialog where one input element can be compared against 65 * a list of historic variants (editions) of the same input element. 66 * The dialog can be used to implement functions like "Compare/Replace with Version" or 67 * "Compare/Replace from Local History" on workspace resources. 68 * <p> 69 * In addition it is possible to specify a subsection of the input element 70 * (e.g. a method in a Java source file) by means of a "path". 71 * In this case the dialog compares only the subsection (as specified by the path) 72 * with the corresponding subsection in the list of editions. 73 * Only those editions are shown where the subsection differs from the same subsection in 74 * another edition thereby minimizing the number of presented variants. 75 * This functionality can be used to implement "Replace from Local History" 76 * for the Java language. 77 * <p> 78 * Subsections of an input element are determined by first finding an 79 * <code>IStructureCreator</code> for the input's type. 80 * Then the method <code>locate</code> is used to extract the subsection. 81 * <p> 82 * Each edition (variant in the list of variants) must implement the <code>IModificationDate</code> interface 83 * so that the dialog can sort the editions and present them in a tree structure where every 84 * node corresponds one day. 85 * <p> 86 * The functionality is surfaced in a single function <code>selectEdition</code>. 87 * <p> 88 * Clients may instantiate this class; it is not intended to be subclassed. 89 * </p> 90 * 91 * @see IModificationDate 92 * @see ITypedElement 93 * 94 * @deprecated Use an <code>org.eclipse.team.ui.history.IHistoryPageSource</code> in conjunction with 95 * the <code>org.eclipse.team.ui.history.IHistoryView</code> or a <code>HistoryPageCompareEditorInput</code>. 96 * For sub-file elements, a <code>org.eclipse.team.ui.history.ElementLocalHistoryPageSource</code> can be used. 97 * @noextend This class is not intended to be subclassed by clients. 98 */ 99 @Deprecated 100 public class EditionSelectionDialog extends ResizableDialog { 101 102 /** 103 * An item in an underlying edition. 104 */ 105 private static class Pair { 106 107 private ITypedElement fEdition; 108 private ITypedElement fItem; 109 private String fContent; 110 private IStructureCreator fStructureCreator; 111 private boolean fHasError= false; 112 Pair(IStructureCreator structureCreator, ITypedElement edition, ITypedElement item)113 Pair(IStructureCreator structureCreator, ITypedElement edition, ITypedElement item) { 114 fStructureCreator= structureCreator; 115 fEdition= edition; 116 fItem= item; 117 } 118 Pair(IStructureCreator structureCreator, ITypedElement edition)119 Pair(IStructureCreator structureCreator, ITypedElement edition) { 120 this(structureCreator, edition, edition); 121 } 122 getEdition()123 ITypedElement getEdition() { 124 return fEdition; 125 } 126 getItem()127 ITypedElement getItem() { 128 return fItem; 129 } 130 131 /* 132 * The content is lazily loaded 133 */ getContent()134 private String getContent() { 135 if (fContent == null) { 136 if (fStructureCreator != null) 137 fContent= fStructureCreator.getContents(fItem, false); 138 else { 139 if (fItem instanceof IStreamContentAccessor) { 140 IStreamContentAccessor sca= (IStreamContentAccessor) fItem; 141 try { 142 fContent= Utilities.readString(sca); 143 } catch (CoreException ex) { 144 // NeedWork 145 CompareUIPlugin.log(ex); 146 } 147 } 148 } 149 if (fContent == null) 150 fContent= ""; //$NON-NLS-1$ 151 } 152 return fContent; 153 } 154 155 @Override equals(Object other)156 public boolean equals(Object other) { 157 if (other != null && other.getClass() == getClass()) { 158 if (getContent().equals(((Pair)other).getContent())) 159 return true; 160 } 161 return super.equals(other); 162 } 163 164 @Override hashCode()165 public int hashCode() { 166 return getContent().hashCode(); 167 } 168 } 169 170 // Configuration options 171 private CompareConfiguration fCompareConfiguration; 172 private ArrayList<Object> fArrayList= new ArrayList<>(); 173 /** use a side-by-side compare viewer */ 174 private boolean fCompare= true; 175 /** show target on right hand side */ 176 private boolean fTargetIsRight= false; 177 /** hide entries which have identical content */ 178 private boolean fHideIdentical= true; 179 /** add mode if true, otherwise replace mode */ 180 private boolean fAddMode= false; 181 /** compare mode if true, otherwise replace/add mode */ 182 private boolean fCompareMode= false; 183 /** perform structure compare on editions */ 184 private boolean fStructureCompare= false; 185 /** allow for multiple selection */ 186 private boolean fMultiSelect= false; 187 188 /** 189 * Maps from members to their corresponding editions. 190 * Has only a single entry if dialog is used in "Replace" (and not "Add") mode. 191 */ 192 private HashMap<ITypedElement, List<Pair>> fMemberEditions; 193 /** 194 * Maps from members to their corresponding selected edition. 195 */ 196 private HashMap<List, ITypedElement> fMemberSelection; 197 /** The editions of the current selected member */ 198 private List fCurrentEditions; 199 private Thread fThread; 200 private Pair fTargetPair; 201 /** The selected edition in the edition viewer */ 202 private ITypedElement fSelectedItem; 203 private String fTitleArg; 204 private Image fTitleImage; 205 206 // SWT controls 207 private CompareViewerSwitchingPane fContentPane; 208 private Button fCommitButton; 209 private Table fMemberTable; 210 private CompareViewerPane fMemberPane; 211 private Tree fEditionTree; 212 private CompareViewerPane fEditionPane; 213 private Image fDateImage; 214 private Image fTimeImage; 215 private CompareViewerSwitchingPane fStructuredComparePane; 216 private Label statusLabel; 217 218 /** 219 * Creates a new modal, resizable dialog. 220 * Various titles, icons, and labels are configured from the given resource bundle. 221 * The following resource keys are used: 222 * <pre> 223 * key type description 224 * title String dialog title 225 * width Integer initial width of dialog 226 * height Integer initial height of dialog 227 * treeTitleFormat MessageFormat pane title for edition tree; arg 0 is the target 228 * dateIcon String icon for node in edition tree; path relative to plug-in 229 * timeIcon String icon for leaf in edition tree; path relative to plug-in 230 * todayFormat MessageFormat format string if date is todays date; arg 0 is date 231 * yesterdayFormat MessageFormat format string if date is yesterdays date; arg 0 is date 232 * dayFormat MessageFormat format string if date is any other date; arg 0 is date 233 * editionLabel String label for editions side of compare viewer; arg 0 is the date 234 * targetLabel String label for target side of compare viewer 235 * buttonLabel String label for OK button; default is IDialogConstants.OK_LABEL 236 * </pre> 237 * 238 * @param parent if not <code>null</code> the new dialog stays on top of this parent shell 239 * @param bundle <code>ResourceBundle</code> to configure the dialog 240 */ EditionSelectionDialog(Shell parent, ResourceBundle bundle)241 public EditionSelectionDialog(Shell parent, ResourceBundle bundle) { 242 super(parent, bundle); 243 } 244 getCompareConfiguration()245 private CompareConfiguration getCompareConfiguration() { 246 if (fCompareConfiguration == null) { 247 fCompareConfiguration= new CompareConfiguration(); 248 fCompareConfiguration.setLeftEditable(false); 249 fCompareConfiguration.setRightEditable(false); 250 fCompareConfiguration.setContainer(new CompareContainer() { 251 @Override 252 public void setStatusMessage(String message) { 253 if (statusLabel != null && !statusLabel.isDisposed()) { 254 if (message == null) { 255 statusLabel.setText(""); //$NON-NLS-1$ 256 } else { 257 statusLabel.setText(message); 258 } 259 } 260 } 261 }); 262 } 263 return fCompareConfiguration; 264 } 265 266 /** 267 * Sets the help context for this dialog. 268 * 269 * @param contextId the help context id. 270 * @since 3.2 271 */ 272 @Override setHelpContextId(String contextId)273 public void setHelpContextId(String contextId) { 274 super.setHelpContextId(contextId); 275 } 276 277 /** 278 * Sets an additional and optional argument for the edition pane's title. 279 * 280 * @param titleArgument an optional argument for the edition pane's title 281 * @since 2.0 282 */ setEditionTitleArgument(String titleArgument)283 public void setEditionTitleArgument(String titleArgument) { 284 fTitleArg= titleArgument; 285 } 286 287 /** 288 * Sets an optional image for the edition pane's title. 289 * 290 * @param titleImage an optional image for the edition pane's title 291 * @since 2.0 292 */ setEditionTitleImage(Image titleImage)293 public void setEditionTitleImage(Image titleImage) { 294 fTitleImage= titleImage; 295 } 296 297 /** 298 * Select the previous edition (presenting a UI). 299 * 300 * @param target the input object against which the editions are compared; must not be <code>null</code> 301 * @param inputEditions the list of editions (element type: <code>ITypedElement</code>s) 302 * @param ppath If <code>null</code> dialog shows full input; if non <code>null</code> it extracts a subsection 303 * @return returns the selected edition or <code>null</code> if error occurred. 304 * The returned <code>ITypedElement</code> is one of the original editions 305 * if <code>path</code> was <code>null</code>; otherwise 306 * it is an <code>ITypedElement</code> returned from <code>IStructureCreator.locate(path, item)</code> 307 * @since 2.0 308 */ selectPreviousEdition(final ITypedElement target, ITypedElement[] inputEditions, Object ppath)309 public ITypedElement selectPreviousEdition(final ITypedElement target, ITypedElement[] inputEditions, Object ppath) { 310 Assert.isNotNull(target); 311 fTargetPair= new Pair(null, target); 312 313 // sort input editions 314 final int count= inputEditions.length; 315 final IModificationDate[] editions= new IModificationDate[count]; 316 for (int i= 0; i < count; i++) 317 editions[i]= (IModificationDate) inputEditions[i]; 318 if (count > 1) 319 internalSort(editions); 320 321 // find StructureCreator if ppath is not null 322 IStructureCreator structureCreator= null; 323 if (ppath != null) { 324 String type= target.getType(); 325 StructureCreatorDescriptor scd= CompareUIPlugin.getDefault().getStructureCreator(type); 326 if (scd != null) 327 structureCreator= scd.createStructureCreator(); 328 } 329 330 if (fAddMode) { 331 // does not work in add mode 332 return null; 333 } 334 335 if (structureCreator != null) { 336 Pair pair= createPair(structureCreator, ppath, target); 337 if (pair != null) 338 fTargetPair= pair; 339 else 340 ppath= null; // couldn't extract item because of error 341 } 342 343 // from front (newest) to back (oldest) 344 for (int i= 0; i < count; i++) { 345 346 ITypedElement edition= (ITypedElement) editions[i]; 347 Pair pair= null; 348 349 if (structureCreator != null && ppath != null) { 350 // extract sub element from edition 351 pair= createPair(structureCreator, ppath, edition); 352 } else { 353 pair= new Pair(null, edition); 354 } 355 356 if (pair != null && pair.fHasError) 357 return null; 358 359 if (pair != null && !fTargetPair.equals(pair)) { 360 return pair.fItem; 361 } 362 } 363 364 // nothing found 365 return null; 366 } 367 368 /** 369 * Presents this modal dialog with the functionality described in the class comment above. 370 * 371 * @param target the input object against which the editions are compared; must not be <code>null</code> 372 * @param inputEditions the list of editions (element type: <code>ITypedElement</code>s) 373 * @param ppath If <code>null</code> dialog shows full input; if non <code>null</code> it extracts a subsection 374 * @return returns the selected edition or <code>null</code> if dialog was cancelled. 375 * The returned <code>ITypedElement</code> is one of the original editions 376 * if <code>path</code> was <code>null</code>; otherwise 377 * it is an <code>ITypedElement</code> returned from <code>IStructureCreator.locate(path, item)</code> 378 */ selectEdition(final ITypedElement target, ITypedElement[] inputEditions, Object ppath)379 public ITypedElement selectEdition(final ITypedElement target, ITypedElement[] inputEditions, Object ppath) { 380 381 Assert.isNotNull(target); 382 fTargetPair= new Pair(null, target); 383 384 // sort input editions 385 final int count= inputEditions.length; 386 final IModificationDate[] editions= new IModificationDate[count]; 387 for (int i= 0; i < count; i++) 388 editions[i]= (IModificationDate) inputEditions[i]; 389 if (count > 1) 390 internalSort(editions); 391 392 // find StructureCreator if ppath is not null 393 IStructureCreator structureCreator= null; 394 if (ppath != null) { 395 String type= target.getType(); 396 StructureCreatorDescriptor scd= CompareUIPlugin.getDefault().getStructureCreator(type); 397 if (scd != null) 398 structureCreator= scd.createStructureCreator(); 399 } 400 401 if (!fAddMode) { 402 // replace mode 403 404 if (structureCreator != null) { 405 Pair pair= createPair(structureCreator, ppath, target); 406 if (pair != null) 407 fTargetPair= pair; 408 else 409 ppath= null; // couldn't extract item because of error 410 } 411 412 // set the left and right labels for the compare viewer 413 String targetLabel= getTargetLabel(target, fTargetPair.getItem()); 414 if (fTargetIsRight) 415 getCompareConfiguration().setRightLabel(targetLabel); 416 else 417 getCompareConfiguration().setLeftLabel(targetLabel); 418 419 if (structureCreator != null && ppath != null) { // extract sub element 420 421 final IStructureCreator sc= structureCreator; 422 final Object path= ppath; 423 424 // construct the comparer thread 425 // and perform the background extract 426 fThread= new Thread() { 427 @Override 428 public void run() { 429 430 // from front (newest) to back (oldest) 431 for (int i= 0; i < count; i++) { 432 433 if (fEditionTree == null || fEditionTree.isDisposed()) 434 break; 435 ITypedElement edition= (ITypedElement) editions[i]; 436 437 // extract sub element from edition 438 Pair pair= createPair(sc, path, edition); 439 if (pair != null) 440 sendPair(pair); 441 } 442 sendPair(null); 443 } 444 }; 445 } else { 446 // create tree widget 447 create(); 448 449 // from front (newest) to back (oldest) 450 for (int i= 0; i < count; i++) 451 addMemberEdition(new Pair(null, (ITypedElement) editions[i])); 452 } 453 454 } else { 455 // add mode 456 final Object container= ppath; 457 Assert.isNotNull(container); 458 459 if (structureCreator == null) 460 return null; // error 461 462 // extract all elements of container 463 final HashSet<Object> current= new HashSet<>(); 464 IStructureComparator sco= structureCreator.locate(container, target); 465 if (sco != null) { 466 Object[] children= sco.getChildren(); 467 if (children != null) 468 Collections.addAll(current, children); 469 } 470 471 final IStructureCreator sc= structureCreator; 472 473 // construct the comparer thread 474 // and perform the background extract 475 fThread= new Thread() { 476 @Override 477 public void run() { 478 479 // from front (newest) to back (oldest) 480 for (int i= 0; i < count; i++) { 481 482 if (fEditionTree == null || fEditionTree.isDisposed()) 483 break; 484 ITypedElement edition= (ITypedElement) editions[i]; 485 486 IStructureComparator sco2= sc.locate(container, edition); 487 if (sco2 != null) { 488 Object[] children= sco2.getChildren(); 489 if (children != null) { 490 for (Object c : children) { 491 ITypedElement child = (ITypedElement) c; 492 if (!current.contains(child)) 493 sendPair(new Pair(sc, edition, child)); 494 } 495 } 496 } 497 } 498 sendPair(null); 499 } 500 }; 501 } 502 503 open(); 504 505 if (getReturnCode() == OK) 506 return fSelectedItem; 507 return null; 508 } 509 createPair(IStructureCreator sc, Object path, ITypedElement input)510 private Pair createPair(IStructureCreator sc, Object path, ITypedElement input) { 511 IStructureComparator scmp= sc.locate(path, input); 512 if (scmp == null && sc.getStructure(input) == null) { // parse error 513 Pair p= new Pair(sc, input); 514 p.fHasError= true; 515 return p; 516 } 517 if (scmp instanceof ITypedElement) 518 return new Pair(sc, input, (ITypedElement) scmp); 519 return null; 520 } 521 522 /** 523 * Controls whether identical entries are shown or not (default). 524 * This method must be called before <code>selectEdition</code>. 525 * 526 * @param hide if true identical entries are hidden; otherwise they are shown. 527 * @since 2.0 528 */ setHideIdenticalEntries(boolean hide)529 public void setHideIdenticalEntries(boolean hide) { 530 fHideIdentical= hide; 531 } 532 533 /** 534 * Controls whether workspace target is on the left (the default) or right hand side. 535 * 536 * @param isRight if true target is shown on right hand side. 537 * @since 2.0 538 */ setTargetIsRight(boolean isRight)539 public void setTargetIsRight(boolean isRight) { 540 fTargetIsRight= isRight; 541 } 542 543 /** 544 * Controls whether the <code>EditionSelectionDialog</code> is in 'add' mode 545 * or 'replace' mode (the default). 546 * 547 * @param addMode if true dialog is in 'add' mode. 548 * @since 2.0 549 */ setAddMode(boolean addMode)550 public void setAddMode(boolean addMode) { 551 fAddMode= addMode; 552 fMultiSelect= addMode; 553 } 554 555 /** 556 * Controls whether the <code>EditionSelectionDialog</code> is in 'compare' mode 557 * or 'add/replace' (the default) mode. 558 * 559 * @param compareMode if true dialog is in 'add' mode. 560 * @since 2.0 561 */ setCompareMode(boolean compareMode)562 public void setCompareMode(boolean compareMode) { 563 fCompareMode= compareMode; 564 fStructureCompare= fCompareMode && !fAddMode; 565 } 566 567 /** 568 * Returns the input target that has been specified with the most recent call 569 * to <code>selectEdition</code>. If a not <code>null</code> path was specified this method 570 * returns a subsection of this target (<code>IStructureCreator.locate(path, target)</code>) 571 * instead of the input target. 572 * <p> 573 * For example if the <code>target</code> is a Java compilation unit and <code>path</code> specifies 574 * a method, the value returned from <code>getTarget</code> will be the method not the compilation unit. 575 * 576 * @return the last specified target or a subsection thereof. 577 */ getTarget()578 public ITypedElement getTarget() { 579 return fTargetPair.getItem(); 580 } 581 582 /** 583 * Returns the editions that have been selected with the most 584 * recent call to <code>selectEdition</code>. 585 * 586 * @return the selected editions as an array. 587 * @since 2.1 588 */ getSelection()589 public ITypedElement[] getSelection() { 590 ArrayList<ITypedElement> result= new ArrayList<>(); 591 if (fMemberSelection != null) { 592 Iterator<Object> iter= fArrayList.iterator(); 593 while (iter.hasNext()) { 594 Object edition= iter.next(); 595 Object item= fMemberSelection.get(edition); 596 if (item != null) 597 result.add((ITypedElement) item); 598 } 599 } else if (fSelectedItem != null) 600 result.add(fSelectedItem); 601 return result.toArray(new ITypedElement[result.size()]); 602 } 603 604 /** 605 * Returns a label for identifying the target side of a compare viewer. 606 * This implementation extracts the value for the key "targetLabel" from the resource bundle 607 * and passes it as the format argument to <code>MessageFormat.format</code>. 608 * The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) 609 * is the name of the given input element. 610 * <p> 611 * Subclasses may override to create their own label. 612 * </p> 613 * 614 * @param target the target element for which a label must be returned 615 * @param item if a path has been specified in <code>selectEdition</code> a sub element of the given target; otherwise the same as target 616 * @return a label the target side of a compare viewer 617 */ getTargetLabel(ITypedElement target, ITypedElement item)618 protected String getTargetLabel(ITypedElement target, ITypedElement item) { 619 String format= null; 620 if (target instanceof ResourceNode) 621 format= Utilities.getString(fBundle, "workspaceTargetLabel", null); //$NON-NLS-1$ 622 if (format == null) 623 format= Utilities.getString(fBundle, "targetLabel"); //$NON-NLS-1$ 624 if (format == null) 625 format= "x{0}"; //$NON-NLS-1$ 626 627 return formatString(format, target.getName()); 628 } 629 formatString(String string, String variable)630 private String formatString(String string, String variable) { 631 // Only process the string if it contains a variable or an escaped quote (see bug 190023) 632 if (hasVariable(string) || hasDoubleQuotes(string)) 633 return MessageFormat.format(string, variable); 634 return string; 635 } 636 hasDoubleQuotes(String string)637 private boolean hasDoubleQuotes(String string) { 638 return string.contains("''"); //$NON-NLS-1$ 639 } 640 hasVariable(String string)641 private boolean hasVariable(String string) { 642 return string.contains("{0}"); //$NON-NLS-1$ 643 } 644 645 /** 646 * Returns a label for identifying the edition side of a compare viewer. 647 * This implementation extracts the value for the key "editionLabel" from the resource bundle 648 * and passes it as the format argument to <code>MessageFormat.format</code>. 649 * The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) 650 * is the formatted modification date of the given input element. 651 * <p> 652 * Subclasses may override to create their own label. 653 * </p> 654 * 655 * @param selectedEdition the selected edition for which a label must be returned 656 * @param item if a path has been specified in <code>selectEdition</code> a sub element of the given selectedEdition; otherwise the same as selectedEdition 657 * @return a label for the edition side of a compare viewer 658 */ getEditionLabel(ITypedElement selectedEdition, ITypedElement item)659 protected String getEditionLabel(ITypedElement selectedEdition, ITypedElement item) { 660 String format= null; 661 if (selectedEdition instanceof ResourceNode) 662 format= Utilities.getString(fBundle, "workspaceEditionLabel", null); //$NON-NLS-1$ 663 else if (selectedEdition instanceof HistoryItem) 664 format= Utilities.getString(fBundle, "historyEditionLabel", null); //$NON-NLS-1$ 665 if (format == null) 666 format= Utilities.getString(fBundle, "editionLabel"); //$NON-NLS-1$ 667 if (format == null) 668 format= "x{0}"; //$NON-NLS-1$ 669 670 671 String date= ""; //$NON-NLS-1$ 672 if (selectedEdition instanceof IModificationDate) { 673 long modDate= ((IModificationDate)selectedEdition).getModificationDate(); 674 date= DateFormat.getDateTimeInstance().format(new Date(modDate)); 675 } 676 677 return formatString(format, date); 678 } 679 680 /** 681 * Returns a label for identifying a node in the edition tree viewer. 682 * This implementation extracts the value for the key "workspaceTreeFormat" or 683 * "treeFormat" (in that order) from the resource bundle 684 * and passes it as the format argument to <code>MessageFormat.format</code>. 685 * The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) 686 * is the formatted modification date of the given input element. 687 * <p> 688 * Subclasses may override to create their own label. 689 * </p> 690 * 691 * @param edition the edition for which a label must be returned 692 * @param item if a path has been specified in <code>edition</code> a sub element of the given edition; otherwise the same as edition 693 * @param date this date will be returned as part of the formatted string 694 * @return a label of a node in the edition tree viewer 695 * @since 2.0 696 */ getShortEditionLabel(ITypedElement edition, ITypedElement item, Date date)697 protected String getShortEditionLabel(ITypedElement edition, ITypedElement item, Date date) { 698 String format= null; 699 if (edition instanceof ResourceNode) 700 format= Utilities.getString(fBundle, "workspaceTreeFormat", null); //$NON-NLS-1$ 701 if (format == null) 702 format= Utilities.getString(fBundle, "treeFormat", null); //$NON-NLS-1$ 703 if (format == null) 704 format= "x{0}"; //$NON-NLS-1$ 705 706 String ds= DateFormat.getTimeInstance().format(date); 707 return formatString(format, ds); 708 } 709 710 /** 711 * Returns an image for identifying the edition side of a compare viewer. 712 * This implementation extracts the value for the key "editionLabel" from the resource bundle 713 * and passes it as the format argument to <code>MessageFormat.format</code>. 714 * The single format argument for <code>MessageFormat.format</code> ("{0}" in the format string) 715 * is the formatted modification date of the given input element. 716 * <p> 717 * Subclasses may override to create their own label. 718 * </p> 719 * 720 * @param selectedEdition the selected edition for which a label must be returned 721 * @param item if a path has been specified in <code>selectEdition</code> a sub element of the given selectedEdition; otherwise the same as selectedEdition 722 * @return a label the edition side of a compare viewer 723 * @since 2.0 724 */ getEditionImage(ITypedElement selectedEdition, ITypedElement item)725 protected Image getEditionImage(ITypedElement selectedEdition, ITypedElement item) { 726 if (selectedEdition instanceof ResourceNode) 727 return selectedEdition.getImage(); 728 if (selectedEdition instanceof HistoryItem) { 729 if (fTimeImage == null) { 730 String iconName= Utilities.getString(fBundle, "timeIcon", "obj16/resource_obj.png"); //$NON-NLS-1$ //$NON-NLS-2$ 731 ImageDescriptor id= CompareUIPlugin.getImageDescriptor(iconName); 732 if (id != null) 733 fTimeImage= id.createImage(); 734 } 735 return fTimeImage; 736 } 737 return null; 738 } 739 740 @Override createDialogArea(Composite parent2)741 protected synchronized Control createDialogArea(Composite parent2) { 742 743 Composite parent= (Composite) super.createDialogArea(parent2); 744 745 getShell().setText(Utilities.getString(fBundle, "title")); //$NON-NLS-1$ 746 747 Splitter vsplitter= new Splitter(parent, SWT.VERTICAL); 748 vsplitter.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL 749 | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); 750 751 vsplitter.addDisposeListener( 752 e -> { 753 if (fCompareConfiguration != null) { 754 fCompareConfiguration.dispose(); 755 fCompareConfiguration= null; 756 } 757 if (fDateImage != null) { 758 fDateImage.dispose(); 759 fDateImage= null; 760 } 761 if (fTimeImage != null) { 762 fTimeImage.dispose(); 763 fTimeImage= null; 764 } 765 } 766 ); 767 768 if (fAddMode) { 769 // we need two panes: the left for the elements, the right one for the editions 770 Splitter hsplitter= new Splitter(vsplitter, SWT.HORIZONTAL); 771 772 fMemberPane= new CompareViewerPane(hsplitter, SWT.BORDER | SWT.FLAT); 773 fMemberPane.setText(Utilities.getString(fBundle, "memberPaneTitle")); //$NON-NLS-1$ 774 775 int flags= SWT.H_SCROLL | SWT.V_SCROLL; 776 if (fMultiSelect) 777 flags|= SWT.CHECK; 778 fMemberTable= new Table(fMemberPane, flags); 779 fMemberTable.addSelectionListener( 780 new SelectionAdapter() { 781 @Override 782 public void widgetSelected(SelectionEvent e) { 783 if (e.detail == SWT.CHECK) { 784 if (e.item instanceof TableItem) { 785 TableItem ti= (TableItem) e.item; 786 Object data= ti.getData(); 787 if (ti.getChecked()) 788 fArrayList.add(data); 789 else 790 fArrayList.remove(data); 791 792 if (fCommitButton != null) 793 fCommitButton.setEnabled(fArrayList.size() > 0); 794 795 fMemberTable.setSelection(new TableItem[] { ti }); 796 } 797 } 798 handleMemberSelect(e.item); 799 } 800 } 801 ); 802 fMemberPane.setContent(fMemberTable); 803 fMemberTable.setFocus(); 804 805 fEditionPane= new CompareViewerPane(hsplitter, SWT.BORDER | SWT.FLAT); 806 } else { 807 if (fStructureCompare) { 808 // we need two panes: the left for the elements, the right one for the structured diff 809 Splitter hsplitter= new Splitter(vsplitter, SWT.HORIZONTAL); 810 811 fEditionPane= new CompareViewerPane(hsplitter, SWT.BORDER | SWT.FLAT); 812 fStructuredComparePane= new CompareViewerSwitchingPane(hsplitter, SWT.BORDER | SWT.FLAT, true) { 813 @Override 814 protected Viewer getViewer(Viewer oldViewer, Object input) { 815 if (input instanceof ICompareInput) 816 return CompareUI.findStructureViewer(oldViewer, (ICompareInput)input, this, getCompareConfiguration()); 817 return null; 818 } 819 }; 820 fStructuredComparePane.addSelectionChangedListener( 821 e -> feedInput2(e.getSelection()) 822 ); 823 } else { 824 // only a single pane showing the editions 825 fEditionPane= new CompareViewerPane(vsplitter, SWT.BORDER | SWT.FLAT); 826 } 827 if (fTitleArg == null) 828 fTitleArg= fTargetPair.getItem().getName(); 829 String titleFormat= Utilities.getString(fBundle, "treeTitleFormat"); //$NON-NLS-1$ 830 String title= MessageFormat.format(titleFormat, fTitleArg ); 831 fEditionPane.setText(title); 832 if (fTitleImage != null) 833 fEditionPane.setImage(fTitleImage); 834 } 835 836 fEditionTree= new Tree(fEditionPane, SWT.H_SCROLL | SWT.V_SCROLL); 837 fEditionTree.addSelectionListener( 838 new SelectionAdapter() { 839 // public void widgetDefaultSelected(SelectionEvent e) { 840 // handleDefaultSelected(); 841 // } 842 @Override 843 public void widgetSelected(SelectionEvent e) { 844 feedInput(e.item); 845 } 846 } 847 ); 848 fEditionPane.setContent(fEditionTree); 849 850 // now start the thread (and forget about it) 851 if (fThread != null) { 852 fThread.start(); 853 fThread= null; 854 } 855 856 fContentPane= new CompareViewerSwitchingPane(vsplitter, SWT.BORDER | SWT.FLAT) { 857 @Override 858 protected Viewer getViewer(Viewer oldViewer, Object input) { 859 return CompareUI.findContentViewer(oldViewer, input, this, getCompareConfiguration()); 860 } 861 }; 862 vsplitter.setWeights(new int[] { 30, 70 }); 863 864 statusLabel = new Label(parent, SWT.NONE); 865 statusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 866 867 applyDialogFont(parent); 868 return parent; 869 } 870 871 @Override createButtonsForButtonBar(Composite parent)872 protected void createButtonsForButtonBar(Composite parent) { 873 String buttonLabel= Utilities.getString(fBundle, "buttonLabel", IDialogConstants.OK_LABEL); //$NON-NLS-1$ 874 if (fCompareMode) { 875 // only a 'Done' button 876 createButton(parent, IDialogConstants.CANCEL_ID, buttonLabel, false); 877 } else { 878 // a 'Cancel' and a 'Add/Replace' button 879 fCommitButton= createButton(parent, IDialogConstants.OK_ID, buttonLabel, true); 880 fCommitButton.setEnabled(false); 881 createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); 882 } 883 } 884 885 /** 886 * Overidden to disable dismiss on double click in compare mode. 887 * @since 2.0 888 */ 889 @Override okPressed()890 protected void okPressed() { 891 if (fCompareMode) { 892 // don't dismiss dialog 893 } else 894 super.okPressed(); 895 } 896 897 //---- private stuff ---------------------------------------------------------------------------------------- 898 899 /* 900 * Asynchroneously sends a Pair (or null) to the UI thread. 901 */ sendPair(final Pair pair)902 private void sendPair(final Pair pair) { 903 if (fEditionTree != null && !fEditionTree.isDisposed()) { 904 Display display= fEditionTree.getDisplay(); 905 display.asyncExec( 906 () -> addMemberEdition(pair) 907 ); 908 } 909 } 910 internalSort(IModificationDate[] keys)911 private static void internalSort(IModificationDate[] keys) { 912 Arrays.sort(keys, (d1, d2) -> { 913 long d= d2.getModificationDate() - d1.getModificationDate(); 914 if (d < 0) 915 return -1; 916 if (d > 0) 917 return 1; 918 return 0; 919 }); 920 } 921 922 /* 923 * Adds the given Pair to the member editions. 924 * If HIDE_IDENTICAL is true the new Pair is only added if its contents 925 * is different from the preceeding Pair. 926 * If the argument is <code>null</code> the message "No Editions found" is shown 927 * in the member or edition viewer. 928 */ addMemberEdition(Pair pair)929 private void addMemberEdition(Pair pair) { 930 931 if (pair == null) { // end of list of pairs 932 if (fMemberTable != null) { 933 if (!fMemberTable.isDisposed() && fMemberTable.getItemCount() == 0) { 934 if (fMultiSelect) { 935 fMemberTable.dispose(); 936 fMemberTable= new Table(fMemberPane, SWT.NONE); 937 fMemberPane.setContent(fMemberTable); 938 } 939 TableItem ti= new TableItem(fMemberTable, SWT.NONE); 940 ti.setText(Utilities.getString(fBundle, "noAdditionalMembersMessage")); //$NON-NLS-1$ 941 } 942 return; 943 } 944 if (fEditionTree != null && !fEditionTree.isDisposed() && fEditionTree.getItemCount() == 0) { 945 TreeItem ti= new TreeItem(fEditionTree, SWT.NONE); 946 ti.setText(Utilities.getString(fBundle, "notFoundInLocalHistoryMessage")); //$NON-NLS-1$ 947 } 948 return; 949 } 950 951 if (fMemberEditions == null) 952 fMemberEditions= new HashMap<>(); 953 if (fMultiSelect && fMemberSelection == null) 954 fMemberSelection= new HashMap<>(); 955 956 ITypedElement item= pair.getItem(); 957 List<Pair> editions= fMemberEditions.get(item); 958 if (editions == null) { 959 editions= new ArrayList<>(); 960 fMemberEditions.put(item, editions); 961 if (fMemberTable != null && !fMemberTable.isDisposed()) { 962 ITypedElement te= item; 963 String name= te.getName(); 964 965 // find position 966 TableItem[] items= fMemberTable.getItems(); 967 int where= items.length; 968 for (int i= 0; i < where; i++) { 969 String n= items[i].getText(); 970 if (n.compareTo(name) > 0) { 971 where= i; 972 break; 973 } 974 } 975 976 TableItem ti= new TableItem(fMemberTable, where, SWT.NULL); 977 ti.setImage(te.getImage()); 978 ti.setText(name); 979 ti.setData(editions); 980 } 981 } 982 if (fHideIdentical) { 983 Pair last= fTargetPair; 984 int size= editions.size(); 985 if (size > 0) 986 last= editions.get(size-1); 987 if (last != null && last.equals(pair)) 988 return; // don't add since the new one is equal to old 989 } 990 editions.add(pair); 991 992 if (!fAddMode || editions == fCurrentEditions) 993 addEdition(pair); 994 } 995 996 /* 997 * Returns the number of s since Jan 1st, 1970. 998 * The given date is converted to GMT and daylight saving is taken into account too. 999 */ dayNumber(long date)1000 private long dayNumber(long date) { 1001 int ONE_DAY_MS= 24*60*60 * 1000; // one day in milli seconds 1002 1003 Calendar calendar= Calendar.getInstance(); 1004 long localTimeOffset= calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET); 1005 1006 return (date + localTimeOffset) / ONE_DAY_MS; 1007 } 1008 1009 /* 1010 * Adds the given Pair to the edition tree. 1011 * It takes care of creating tree nodes for different dates. 1012 */ addEdition(Pair pair)1013 private void addEdition(Pair pair) { 1014 if (fEditionTree == null || fEditionTree.isDisposed()) 1015 return; 1016 1017 // find last day 1018 TreeItem[] days= fEditionTree.getItems(); 1019 TreeItem lastDay= null; 1020 if (days.length > 0) 1021 lastDay= days[days.length-1]; 1022 1023 boolean first= lastDay == null; 1024 1025 ITypedElement edition= pair.getEdition(); 1026 ITypedElement item= pair.getItem(); 1027 1028 long ldate= ((IModificationDate)edition).getModificationDate(); 1029 long day= dayNumber(ldate); 1030 Date date= new Date(ldate); 1031 if (lastDay == null || day != dayNumber(((Date)lastDay.getData()).getTime())) { 1032 lastDay= new TreeItem(fEditionTree, SWT.NONE); 1033 if (fDateImage == null) { 1034 String iconName= Utilities.getString(fBundle, "dateIcon", "obj16/day_obj.png"); //$NON-NLS-2$ //$NON-NLS-1$ 1035 ImageDescriptor id= CompareUIPlugin.getImageDescriptor(iconName); 1036 if (id != null) 1037 fDateImage= id.createImage(); 1038 } 1039 lastDay.setImage(fDateImage); 1040 String df= DateFormat.getDateInstance().format(date); 1041 long today= dayNumber(System.currentTimeMillis()); 1042 1043 String formatKey; 1044 if (day == today) 1045 formatKey= "todayFormat"; //$NON-NLS-1$ 1046 else if (day == today-1) 1047 formatKey= "yesterdayFormat"; //$NON-NLS-1$ 1048 else 1049 formatKey= "dayFormat"; //$NON-NLS-1$ 1050 String pattern= Utilities.getString(fBundle, formatKey); 1051 if (pattern != null) 1052 df= MessageFormat.format(pattern, df); 1053 lastDay.setText(df); 1054 lastDay.setData(date); 1055 } 1056 TreeItem ti= new TreeItem(lastDay, SWT.NONE); 1057 ti.setImage(getEditionImage(edition, item)); 1058 1059 String s= getShortEditionLabel(edition, item, date); 1060 if (pair.fHasError) { 1061 String pattern= Utilities.getString(fBundle, "parseErrorFormat"); //$NON-NLS-1$ 1062 s= MessageFormat.format(pattern, s); 1063 } 1064 ti.setText(s); 1065 1066 ti.setData(pair); 1067 1068 // determine selected TreeItem 1069 TreeItem selection= first ? ti : null; 1070 if (fMemberSelection != null) { 1071 Object selected= fMemberSelection.get(fCurrentEditions); 1072 if (selected != null) { 1073 if (selected == pair.getItem()) 1074 selection= ti; 1075 else 1076 selection= null; 1077 } 1078 } 1079 if (selection != null) { 1080 fEditionTree.setSelection(new TreeItem[] { selection }); 1081 if (!fAddMode) 1082 fEditionTree.setFocus(); 1083 feedInput(selection); 1084 } 1085 1086 if (first) // expand first node 1087 lastDay.setExpanded(true); 1088 } 1089 1090 /* 1091 * Feeds selection from member viewer to edition viewer. 1092 */ handleMemberSelect(Widget w)1093 private void handleMemberSelect(Widget w) { 1094 Object data= w.getData(); 1095 if (data instanceof List) { 1096 @SuppressWarnings("unchecked") 1097 List<Object> editions= (List<Object>) data; 1098 if (editions != fCurrentEditions) { 1099 fCurrentEditions= editions; 1100 fEditionTree.removeAll(); 1101 1102 String pattern= Utilities.getString(fBundle, "treeTitleFormat"); //$NON-NLS-1$ 1103 String title= MessageFormat.format(pattern, new Object[] { ((Item)w).getText() }); 1104 fEditionPane.setText(title); 1105 1106 Iterator<Object> iter= editions.iterator(); 1107 while (iter.hasNext()) { 1108 Object item= iter.next(); 1109 if (item instanceof Pair) 1110 addEdition((Pair) item); 1111 } 1112 } 1113 } 1114 } 1115 setInput(Object input)1116 private void setInput(Object input) { 1117 if (!fCompare && input instanceof ICompareInput) { 1118 ICompareInput ci= (ICompareInput) input; 1119 if (fTargetIsRight) 1120 input= ci.getLeft(); 1121 else 1122 input= ci.getRight(); 1123 } 1124 fContentPane.setInput(input); 1125 if (fStructuredComparePane != null) 1126 fStructuredComparePane.setInput(input); 1127 } 1128 1129 /* 1130 * Feeds selection from edition viewer to content (and structure) viewer. 1131 */ feedInput(Widget w)1132 private void feedInput(Widget w) { 1133 Object input= w.getData(); 1134 boolean isOK= false; 1135 if (input instanceof Pair) { 1136 Pair pair= (Pair) input; 1137 fSelectedItem= pair.getItem(); 1138 isOK= !pair.fHasError; 1139 1140 ITypedElement edition= pair.getEdition(); 1141 String editionLabel= getEditionLabel(edition, fSelectedItem); 1142 Image editionImage= getEditionImage(edition, fSelectedItem); 1143 1144 if (fAddMode) { 1145 if (fMemberSelection != null) 1146 fMemberSelection.put(fCurrentEditions, fSelectedItem); 1147 setInput(fSelectedItem); 1148 fContentPane.setText(editionLabel); 1149 fContentPane.setImage(editionImage); 1150 } else { 1151 getCompareConfiguration(); 1152 if (fTargetIsRight) { 1153 fCompareConfiguration.setLeftLabel(editionLabel); 1154 fCompareConfiguration.setLeftImage(editionImage); 1155 setInput(new DiffNode(fSelectedItem, fTargetPair.getItem())); 1156 } else { 1157 fCompareConfiguration.setRightLabel(editionLabel); 1158 fCompareConfiguration.setRightImage(editionImage); 1159 setInput(new DiffNode(fTargetPair.getItem(), fSelectedItem)); 1160 } 1161 } 1162 } else { 1163 fSelectedItem= null; 1164 setInput(null); 1165 } 1166 if (fCommitButton != null) { 1167 if (fMultiSelect) 1168 fCommitButton.setEnabled(isOK && fSelectedItem != null && fArrayList.size() > 0); 1169 else 1170 fCommitButton.setEnabled(isOK && fSelectedItem != null && fTargetPair.getItem() != fSelectedItem); 1171 } 1172 } 1173 1174 /* 1175 * Feeds selection from structure viewer to content viewer. 1176 */ feedInput2(ISelection sel)1177 private void feedInput2(ISelection sel) { 1178 if (sel instanceof IStructuredSelection) { 1179 IStructuredSelection ss= (IStructuredSelection) sel; 1180 if (ss.size() == 1) 1181 fContentPane.setInput(ss.getFirstElement()); 1182 } 1183 } 1184 } 1185