001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.mercurial;
042:
043: import org.netbeans.modules.mercurial.ui.clone.CloneAction;
044: import org.netbeans.modules.mercurial.ui.clone.CloneExternalAction;
045: import org.netbeans.modules.mercurial.ui.create.CreateAction;
046: import org.netbeans.modules.versioning.spi.VCSAnnotator;
047: import org.netbeans.modules.versioning.spi.VCSContext;
048: import org.netbeans.modules.versioning.spi.VersioningSupport;
049: import org.netbeans.modules.versioning.util.Utils;
050: import org.netbeans.api.project.Project;
051: import org.openide.util.RequestProcessor;
052: import org.openide.util.Utilities;
053: import org.openide.nodes.Node;
054: import org.openide.util.NbBundle;
055: import org.netbeans.modules.versioning.util.Utils;
056: import javax.swing.*;
057: import java.awt.Image;
058: import java.io.File;
059: import java.text.MessageFormat;
060: import java.util.*;
061: import java.util.logging.Level;
062: import java.util.regex.Pattern;
063: import java.util.concurrent.ConcurrentLinkedQueue;
064: import java.lang.reflect.Field;
065: import java.lang.Exception;
066: import org.netbeans.modules.mercurial.ui.annotate.AnnotateAction;
067: import org.netbeans.modules.mercurial.ui.commit.CommitAction;
068: import org.netbeans.modules.mercurial.ui.diff.DiffAction;
069: import org.netbeans.modules.mercurial.ui.diff.ExportDiffAction;
070: import org.netbeans.modules.mercurial.ui.diff.ImportDiffAction;
071: import org.netbeans.modules.mercurial.ui.ignore.IgnoreAction;
072: import org.netbeans.modules.mercurial.ui.log.IncomingAction;
073: import org.netbeans.modules.mercurial.ui.log.LogAction;
074: import org.netbeans.modules.mercurial.ui.log.OutAction;
075: import org.netbeans.modules.mercurial.ui.merge.MergeAction;
076: import org.netbeans.modules.mercurial.ui.properties.PropertiesAction;
077: import org.netbeans.modules.mercurial.ui.pull.FetchAction;
078: import org.netbeans.modules.mercurial.ui.pull.PullAction;
079: import org.netbeans.modules.mercurial.ui.pull.PullOtherAction;
080: import org.netbeans.modules.mercurial.ui.push.PushAction;
081: import org.netbeans.modules.mercurial.ui.push.PushOtherAction;
082: import org.netbeans.modules.mercurial.ui.rollback.BackoutAction;
083: import org.netbeans.modules.mercurial.ui.rollback.RollbackAction;
084: import org.netbeans.modules.mercurial.ui.rollback.StripAction;
085: import org.netbeans.modules.mercurial.ui.update.RevertModificationsAction;
086: import org.netbeans.modules.mercurial.ui.status.StatusAction;
087: import org.netbeans.modules.mercurial.ui.update.ConflictResolvedAction;
088: import org.netbeans.modules.mercurial.ui.update.ResolveConflictsAction;
089: import org.netbeans.modules.mercurial.ui.update.UpdateAction;
090: import org.netbeans.modules.mercurial.ui.view.ViewAction;
091: import org.netbeans.modules.mercurial.util.HgProjectUtils;
092: import org.netbeans.modules.mercurial.util.HgUtils;
093: import org.netbeans.modules.mercurial.util.HgCommand;
094: import org.netbeans.modules.mercurial.util.HgRepositoryContextCache;
095: import org.openide.DialogDisplayer;
096: import org.openide.NotifyDescriptor;
097:
098: /**
099: * Responsible for coloring file labels and file icons in the IDE and providing IDE with menu items.
100: *
101: * @author Maros Sandor
102: */
103: public class MercurialAnnotator extends VCSAnnotator {
104:
105: private static final int INITIAL_ACTION_ARRAY_LENGTH = 25;
106: private static MessageFormat uptodateFormat = getFormat("uptodateFormat"); // NOI18N
107: private static MessageFormat newLocallyFormat = getFormat("newLocallyFormat"); // NOI18N
108: private static MessageFormat addedLocallyFormat = getFormat("addedLocallyFormat"); // NOI18N
109: private static MessageFormat modifiedLocallyFormat = getFormat("modifiedLocallyFormat"); // NOI18N
110: private static MessageFormat removedLocallyFormat = getFormat("removedLocallyFormat"); // NOI18N
111: private static MessageFormat deletedLocallyFormat = getFormat("deletedLocallyFormat"); // NOI18N
112: private static MessageFormat excludedFormat = getFormat("excludedFormat"); // NOI18N
113: private static MessageFormat conflictFormat = getFormat("conflictFormat"); // NOI18N
114:
115: private static final int STATUS_TEXT_ANNOTABLE = FileInformation.STATUS_NOTVERSIONED_EXCLUDED
116: | FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY
117: | FileInformation.STATUS_VERSIONED_UPTODATE
118: | FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY
119: | FileInformation.STATUS_VERSIONED_CONFLICT
120: | FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY
121: | FileInformation.STATUS_VERSIONED_DELETEDLOCALLY
122: | FileInformation.STATUS_VERSIONED_ADDEDLOCALLY;
123:
124: private static final Pattern lessThan = Pattern.compile("<"); // NOI18N
125:
126: private static final int STATUS_BADGEABLE = FileInformation.STATUS_VERSIONED_UPTODATE
127: | FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY
128: | FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY;
129:
130: public static String ANNOTATION_REVISION = "revision"; // NOI18N
131: public static String ANNOTATION_STATUS = "status"; // NOI18N
132: public static String ANNOTATION_FOLDER = "folder"; // NOI18N
133:
134: public static String[] LABELS = new String[] { ANNOTATION_REVISION,
135: ANNOTATION_STATUS, ANNOTATION_FOLDER };
136:
137: private FileStatusCache cache;
138: private MessageFormat format;
139: private String emptyFormat;
140: private Boolean needRevisionForFormat;
141: private File folderToScan;
142: private ConcurrentLinkedQueue<File> dirsToScan = new ConcurrentLinkedQueue<File>();
143: private RequestProcessor.Task scanTask;
144: private static final RequestProcessor rp = new RequestProcessor(
145: "MercurialAnnotateScan", 1, true); // NOI18N
146:
147: public MercurialAnnotator() {
148: cache = Mercurial.getInstance().getFileStatusCache();
149: scanTask = rp.create(new ScanTask());
150: initDefaults();
151: }
152:
153: private void initDefaults() {
154: Field[] fields = MercurialAnnotator.class.getDeclaredFields();
155: for (int i = 0; i < fields.length; i++) {
156: String name = fields[i].getName();
157: if (name.endsWith("Format")) { // NOI18N
158: initDefaultColor(name.substring(0, name.length() - 6));
159: }
160: }
161: refresh();
162: }
163:
164: public void refresh() {
165: String string = HgModuleConfig.getDefault()
166: .getAnnotationFormat(); //System.getProperty("netbeans.experimental.svn.ui.statusLabelFormat"); // NOI18N
167: if (string != null && !string.trim().equals("")) { // NOI18N
168: needRevisionForFormat = isRevisionInAnnotationFormat(string);
169: string = string.replaceAll("\\{revision\\}", "\\{0\\}"); // NOI18N
170: string = string.replaceAll("\\{status\\}", "\\{1\\}"); // NOI18N
171: string = string.replaceAll("\\{folder\\}", "\\{2\\}"); // NOI18N
172: format = new MessageFormat(string);
173: emptyFormat = format.format(new String[] { "", "", "" },
174: new StringBuffer(), null).toString().trim(); // NOI18N
175: }
176: }
177:
178: public static boolean isRevisionInAnnotationFormat(String str) {
179: if (str.indexOf("{revision}") != -1) { // NOI18N
180: return true;
181: } else {
182: return false;
183: }
184: }
185:
186: private void initDefaultColor(String name) {
187: String color = System.getProperty("hg.color." + name); // NOI18N
188: if (color == null)
189: return;
190: setAnnotationColor(name, color);
191: }
192:
193: /**
194: * Changes annotation color of files.
195: *
196: * @param name name of the color to change. Can be one of:
197: * newLocally, addedLocally, modifiedLocally, removedLocally, deletedLocally, newInRepository, modifiedInRepository,
198: * removedInRepository, conflict, mergeable, excluded.
199: * @param colorString new color in the format: 4455AA (RGB hexadecimal)
200: */
201: private void setAnnotationColor(String name, String colorString) {
202: try {
203: Field field = MercurialAnnotator.class
204: .getDeclaredField(name + "Format"); // NOI18N
205: MessageFormat format = new MessageFormat("<font color=\""
206: + colorString
207: + "\">{0}</font><font color=\"#999999\">{1}</font>"); // NOI18N
208: field.set(null, format);
209: } catch (Exception e) {
210: throw new IllegalArgumentException("Invalid color name"); // NOI18N
211: }
212: }
213:
214: private static MessageFormat getFormat(String key) {
215: String format = NbBundle.getMessage(MercurialAnnotator.class,
216: key);
217: return new MessageFormat(format);
218: }
219:
220: public String annotateName(String name, VCSContext context) {
221: int includeStatus = FileInformation.STATUS_VERSIONED_UPTODATE
222: | FileInformation.STATUS_LOCAL_CHANGE
223: | FileInformation.STATUS_NOTVERSIONED_EXCLUDED;
224:
225: FileInformation mostImportantInfo = null;
226: File mostImportantFile = null;
227: boolean folderAnnotation = false;
228:
229: for (final File file : context.getRootFiles()) {
230: FileInformation info = cache.getCachedStatus(file, true);
231: if (info == null) {
232: File parentFile = file.getParentFile();
233: Mercurial.LOG
234: .log(Level.FINE,
235: "null cached status for: {0} {1} {2}",
236: new Object[] { file, folderToScan,
237: parentFile });
238: folderToScan = parentFile;
239: reScheduleScan(1000);
240: info = new FileInformation(
241: FileInformation.STATUS_VERSIONED_UPTODATE,
242: false);
243: }
244: int status = info.getStatus();
245: if ((status & includeStatus) == 0)
246: continue;
247:
248: if (isMoreImportant(info, mostImportantInfo)) {
249: mostImportantInfo = info;
250: mostImportantFile = file;
251: folderAnnotation = file.isDirectory();
252: }
253: }
254:
255: if (folderAnnotation == false
256: && context.getRootFiles().size() > 1) {
257: folderAnnotation = !Utils.shareCommonDataObject(context
258: .getRootFiles().toArray(
259: new File[context.getRootFiles().size()]));
260: }
261:
262: if (mostImportantInfo == null)
263: return null;
264: return folderAnnotation ? annotateFolderNameHtml(name,
265: mostImportantInfo, mostImportantFile)
266: : annotateNameHtml(name, mostImportantInfo,
267: mostImportantFile);
268: }
269:
270: public Image annotateIcon(Image icon, VCSContext context) {
271: boolean folderAnnotation = false;
272: for (File file : context.getRootFiles()) {
273: if (file.isDirectory()) {
274: folderAnnotation = true;
275: break;
276: }
277: }
278:
279: if (folderAnnotation == false
280: && context.getRootFiles().size() > 1) {
281: folderAnnotation = !Utils.shareCommonDataObject(context
282: .getRootFiles().toArray(
283: new File[context.getRootFiles().size()]));
284: }
285:
286: if (folderAnnotation == false) {
287: return null;
288: }
289:
290: boolean isVersioned = false;
291: for (Iterator i = context.getRootFiles().iterator(); i
292: .hasNext();) {
293: File file = (File) i.next();
294: // There is an assumption here that annotateName was already
295: // called and FileStatusCache.getStatus was scheduled if
296: // FileStatusCache.getCachedStatus returned null.
297: FileInformation info = cache.getCachedStatus(file, true);
298: if ((info != null && (info.getStatus() & STATUS_BADGEABLE) != 0)) {
299: isVersioned = true;
300: break;
301: }
302: }
303: if (!isVersioned)
304: return null;
305:
306: boolean allExcluded = true;
307: boolean modified = false;
308:
309: Map<File, FileInformation> map = cache.getAllModifiedFiles();
310: Map<File, FileInformation> modifiedFiles = new HashMap<File, FileInformation>();
311: for (Iterator i = map.keySet().iterator(); i.hasNext();) {
312: File file = (File) i.next();
313: FileInformation info = map.get(file);
314: if ((info.getStatus() & FileInformation.STATUS_LOCAL_CHANGE) != 0)
315: modifiedFiles.put(file, info);
316: }
317:
318: for (Iterator i = context.getRootFiles().iterator(); i
319: .hasNext();) {
320: File file = (File) i.next();
321: if (VersioningSupport.isFlat(file)) {
322: for (Iterator j = modifiedFiles.keySet().iterator(); j
323: .hasNext();) {
324: File mf = (File) j.next();
325: if (mf.getParentFile().equals(file)) {
326: FileInformation info = modifiedFiles.get(mf);
327: if (info.isDirectory())
328: continue;
329: int status = info.getStatus();
330: if (status == FileInformation.STATUS_VERSIONED_CONFLICT) {
331: Image badge = Utilities
332: .loadImage(
333: "org/netbeans/modules/mercurial/resources/icons/conflicts-badge.png",
334: true); // NOI18N
335: return Utilities.mergeImages(icon, badge,
336: 16, 9);
337: }
338: modified = true;
339: allExcluded &= isExcludedFromCommit(mf
340: .getAbsolutePath());
341: }
342: }
343: } else {
344: for (Iterator j = modifiedFiles.keySet().iterator(); j
345: .hasNext();) {
346: File mf = (File) j.next();
347: if (Utils.isAncestorOrEqual(file, mf)) {
348: FileInformation info = modifiedFiles.get(mf);
349: int status = info.getStatus();
350: if ((status == FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY || status == FileInformation.STATUS_VERSIONED_ADDEDLOCALLY)
351: && file.equals(mf)) {
352: continue;
353: }
354: if (status == FileInformation.STATUS_VERSIONED_CONFLICT) {
355: Image badge = Utilities
356: .loadImage(
357: "org/netbeans/modules/mercurial/resources/icons/conflicts-badge.png",
358: true); // NOI18N
359: return Utilities.mergeImages(icon, badge,
360: 16, 9);
361: }
362: modified = true;
363: allExcluded &= isExcludedFromCommit(mf
364: .getAbsolutePath());
365: }
366: }
367: }
368: }
369:
370: if (modified && !allExcluded) {
371: Image badge = Utilities
372: .loadImage(
373: "org/netbeans/modules/mercurial/resources/icons/modified-badge.png",
374: true); // NOI18N
375: return Utilities.mergeImages(icon, badge, 16, 9);
376: } else {
377: return null;
378: }
379: }
380:
381: public Action[] getActions(VCSContext ctx,
382: VCSAnnotator.ActionDestination destination) {
383: // TODO: get resource strings for all actions:
384: ResourceBundle loc = NbBundle
385: .getBundle(MercurialAnnotator.class);
386: Node[] nodes = ctx.getElements().lookupAll(Node.class).toArray(
387: new Node[0]);
388: File[] files = ctx.getRootFiles().toArray(
389: new File[ctx.getRootFiles().size()]);
390: File root = HgUtils.getRootFile(ctx);
391: boolean noneVersioned = root == null;
392: boolean onlyFolders = onlyFolders(files);
393: boolean onlyProjects = onlyProjects(nodes);
394:
395: List<Action> actions = new ArrayList<Action>(
396: INITIAL_ACTION_ARRAY_LENGTH);
397: if (destination == VCSAnnotator.ActionDestination.MainMenu) {
398: actions.add(new CreateAction(loc
399: .getString("CTL_MenuItem_Create"), ctx)); // NOI18N
400: actions.add(null);
401: actions.add(new StatusAction(loc
402: .getString("CTL_PopupMenuItem_Status"), ctx)); // NOI18N
403: actions.add(new DiffAction(loc
404: .getString("CTL_PopupMenuItem_Diff"), ctx)); // NOI18N
405: actions.add(new UpdateAction(loc
406: .getString("CTL_PopupMenuItem_Update"), ctx)); // NOI18N
407: actions.add(new CommitAction(loc
408: .getString("CTL_PopupMenuItem_Commit"), ctx)); // NOI18N
409: actions.add(null);
410: actions.add(new ExportDiffAction(loc
411: .getString("CTL_PopupMenuItem_ExportDiff"), ctx)); // NOI18N
412: actions.add(new ImportDiffAction(loc
413: .getString("CTL_PopupMenuItem_ImportDiff"), ctx)); // NOI18N
414:
415: actions.add(null);
416: if (root != null) {
417: actions.add(new CloneAction(NbBundle.getMessage(
418: MercurialAnnotator.class,
419: "CTL_PopupMenuItem_CloneLocal", // NOI18N
420: root.getName()), ctx));
421: }
422: actions.add(new CloneExternalAction(loc
423: .getString("CTL_PopupMenuItem_CloneOther"), ctx)); // NOI18N
424: actions.add(null);
425: actions.add(new FetchAction(NbBundle.getMessage(
426: MercurialAnnotator.class,
427: "CTL_PopupMenuItem_FetchLocal"), ctx)); // NOI18N
428: actions.add(new PushAction(NbBundle.getMessage(
429: MercurialAnnotator.class,
430: "CTL_PopupMenuItem_PushLocal"), ctx)); // NOI18N
431: actions.add(new PushOtherAction(loc
432: .getString("CTL_PopupMenuItem_PushOther"), ctx)); // NOI18N
433: actions.add(new PullAction(NbBundle.getMessage(
434: MercurialAnnotator.class,
435: "CTL_PopupMenuItem_PullLocal"), ctx)); // NOI18N
436: actions.add(new PullOtherAction(loc
437: .getString("CTL_PopupMenuItem_PullOther"), ctx)); // NOI18N
438: actions.add(new MergeAction(NbBundle
439: .getMessage(MercurialAnnotator.class,
440: "CTL_PopupMenuItem_Merge"), ctx)); // NOI18N
441: actions.add(null);
442: AnnotateAction tempA = new AnnotateAction(loc
443: .getString("CTL_PopupMenuItem_ShowAnnotations"),
444: ctx); // NOI18N
445: if (tempA.visible(nodes)) {
446: tempA = new AnnotateAction(
447: loc
448: .getString("CTL_PopupMenuItem_HideAnnotations"),
449: ctx); // NOI18N
450: }
451: actions.add(tempA);
452: actions.add(new LogAction(loc
453: .getString("CTL_PopupMenuItem_Log"), ctx)); // NOI18N
454: actions.add(new IncomingAction(NbBundle.getMessage(
455: MercurialAnnotator.class,
456: "CTL_PopupMenuItem_ShowIncoming"), ctx)); // NOI18N
457: actions.add(new OutAction(NbBundle.getMessage(
458: MercurialAnnotator.class,
459: "CTL_PopupMenuItem_ShowOut"), ctx)); // NOI18N
460: actions.add(new ViewAction(loc
461: .getString("CTL_PopupMenuItem_View"), ctx)); // NOI18N
462: actions.add(null);
463: actions.add(new RevertModificationsAction(NbBundle
464: .getMessage(MercurialAnnotator.class,
465: "CTL_PopupMenuItem_Revert"), ctx)); // NOI18N
466: actions.add(new StripAction(NbBundle
467: .getMessage(MercurialAnnotator.class,
468: "CTL_PopupMenuItem_Strip"), ctx)); // NOI18N
469: actions.add(new BackoutAction(NbBundle.getMessage(
470: MercurialAnnotator.class,
471: "CTL_PopupMenuItem_Backout"), ctx)); // NOI18N
472: actions.add(new RollbackAction(NbBundle.getMessage(
473: MercurialAnnotator.class,
474: "CTL_PopupMenuItem_Rollback"), ctx)); // NOI18N
475: actions.add(new ResolveConflictsAction(NbBundle.getMessage(
476: MercurialAnnotator.class,
477: "CTL_PopupMenuItem_Resolve"), ctx)); // NOI18N
478: if (!onlyProjects && !onlyFolders) {
479: IgnoreAction tempIA = new IgnoreAction(loc
480: .getString("CTL_PopupMenuItem_Ignore"), ctx); // NOI18N
481: actions.add(tempIA);
482: }
483: actions.add(null);
484: actions.add(new PropertiesAction(loc
485: .getString("CTL_PopupMenuItem_Properties"), ctx)); // NOI18N
486: } else {
487: if (noneVersioned) {
488: actions.add(new CreateAction(loc
489: .getString("CTL_PopupMenuItem_Create"), ctx)); // NOI18N
490: } else {
491: actions.add(new StatusAction(loc
492: .getString("CTL_PopupMenuItem_Status"), ctx)); // NOI18N
493: actions.add(new DiffAction(loc
494: .getString("CTL_PopupMenuItem_Diff"), ctx)); // NOI18N
495: actions.add(new UpdateAction(loc
496: .getString("CTL_PopupMenuItem_Update"), ctx)); // NOI18N
497: actions.add(new CommitAction(loc
498: .getString("CTL_PopupMenuItem_Commit"), ctx)); // NOI18N
499: actions.add(null);
500: if (root != null) {
501: actions.add(new CloneAction(NbBundle.getMessage(
502: MercurialAnnotator.class,
503: "CTL_PopupMenuItem_CloneLocal", // NOI18N
504: root.getName()), ctx));
505: }
506:
507: actions.add(null);
508: actions.add(new FetchAction(NbBundle.getMessage(
509: MercurialAnnotator.class,
510: "CTL_PopupMenuItem_FetchLocal"), ctx)); // NOI18N
511: actions.add(new PushAction(NbBundle.getMessage(
512: MercurialAnnotator.class,
513: "CTL_PopupMenuItem_PushLocal"), ctx)); // NOI18N
514: actions.add(new PullAction(NbBundle.getMessage(
515: MercurialAnnotator.class,
516: "CTL_PopupMenuItem_PullLocal"), ctx)); // NOI18N
517: actions.add(new MergeAction(NbBundle.getMessage(
518: MercurialAnnotator.class,
519: "CTL_PopupMenuItem_Merge"), ctx)); // NOI18N
520: actions.add(null);
521:
522: if (!onlyFolders) {
523: AnnotateAction tempA = new AnnotateAction(
524: loc
525: .getString("CTL_PopupMenuItem_ShowAnnotations"),
526: ctx); // NOI18N
527: if (tempA.visible(nodes)) {
528: tempA = new AnnotateAction(
529: loc
530: .getString("CTL_PopupMenuItem_HideAnnotations"),
531: ctx); // NOI18N
532: }
533: actions.add(tempA);
534: }
535: actions.add(new LogAction(loc
536: .getString("CTL_PopupMenuItem_Log"), ctx)); // NOI18N
537: actions.add(new IncomingAction(NbBundle.getMessage(
538: MercurialAnnotator.class,
539: "CTL_PopupMenuItem_ShowIncoming"), ctx)); // NOI18N
540: actions.add(new OutAction(NbBundle.getMessage(
541: MercurialAnnotator.class,
542: "CTL_PopupMenuItem_ShowOut"), ctx)); // NOI18N
543: actions.add(new ViewAction(loc
544: .getString("CTL_PopupMenuItem_View"), ctx)); // NOI18N
545: actions.add(null);
546: actions.add(new RevertModificationsAction(NbBundle
547: .getMessage(MercurialAnnotator.class,
548: "CTL_PopupMenuItem_Revert"), ctx)); // NOI18N
549: actions.add(new StripAction(NbBundle.getMessage(
550: MercurialAnnotator.class,
551: "CTL_PopupMenuItem_Strip"), ctx)); // NOI18N
552: actions.add(new BackoutAction(NbBundle.getMessage(
553: MercurialAnnotator.class,
554: "CTL_PopupMenuItem_Backout"), ctx)); // NOI18N
555: actions.add(new RollbackAction(NbBundle.getMessage(
556: MercurialAnnotator.class,
557: "CTL_PopupMenuItem_Rollback"), ctx)); // NOI18N
558: actions.add(new ResolveConflictsAction(NbBundle
559: .getMessage(MercurialAnnotator.class,
560: "CTL_PopupMenuItem_Resolve"), ctx)); // NOI18N
561: if (!onlyProjects && !onlyFolders) {
562: actions.add(new ConflictResolvedAction(NbBundle
563: .getMessage(MercurialAnnotator.class,
564: "CTL_PopupMenuItem_MarkResolved"),
565: ctx)); // NOI18N
566:
567: IgnoreAction tempIA = new IgnoreAction(loc
568: .getString("CTL_PopupMenuItem_Ignore"), ctx); // NOI18N
569: actions.add(tempIA);
570: }
571: actions.add(null);
572: actions
573: .add(new PropertiesAction(
574: loc
575: .getString("CTL_PopupMenuItem_Properties"),
576: ctx)); // NOI18N
577: }
578: }
579: return actions.toArray(new Action[actions.size()]);
580: }
581:
582: /**
583: * Applies custom format.
584: */
585: private String formatAnnotation(FileInformation info, File file) {
586: String statusString = ""; // NOI18N
587: int status = info.getStatus();
588: if (status != FileInformation.STATUS_VERSIONED_UPTODATE) {
589: statusString = info.getShortStatusText();
590: }
591:
592: String revisionString = ""; // NOI18N
593: String binaryString = ""; // NOI18N
594:
595: if (needRevisionForFormat) {
596: if ((status & FileInformation.STATUS_NOTVERSIONED_EXCLUDED) == 0) {
597: try {
598: File repository = Mercurial.getInstance()
599: .getTopmostManagedParent(file);
600: String revStr = HgCommand.getLastRevision(
601: repository, file);
602: if (revStr != null) {
603: revisionString = revStr;
604: }
605: } catch (HgException ex) {
606: NotifyDescriptor.Exception e = new NotifyDescriptor.Exception(
607: ex);
608: DialogDisplayer.getDefault().notifyLater(e);
609: }
610: }
611: }
612:
613: //String stickyString = SvnUtils.getCopy(file);
614: String stickyString = null;
615: if (stickyString == null) {
616: stickyString = ""; // NOI18N
617: }
618:
619: Object[] arguments = new Object[] { revisionString,
620: statusString, stickyString, };
621:
622: String annotation = format.format(arguments,
623: new StringBuffer(), null).toString().trim();
624: if (annotation.equals(emptyFormat)) {
625: return ""; // NOI18N
626: } else {
627: return " " + annotation; // NOI18N
628: }
629: }
630:
631: public String annotateNameHtml(File file, FileInformation info) {
632: return annotateNameHtml(file.getName(), info, file);
633: }
634:
635: public String annotateNameHtml(String name,
636: FileInformation mostImportantInfo, File mostImportantFile) {
637: // Hg: The codes used to show the status of files are:
638: // M = modified
639: // A = added
640: // R = removed
641: // C = clean
642: // ! = deleted, but still tracked
643: // ? = not tracked
644: // I = ignored (not shown by default)
645:
646: name = htmlEncode(name);
647:
648: String textAnnotation;
649: boolean annotationsVisible = VersioningSupport
650: .getPreferences()
651: .getBoolean(
652: VersioningSupport.PREF_BOOLEAN_TEXT_ANNOTATIONS_VISIBLE,
653: false);
654: int status = mostImportantInfo.getStatus();
655:
656: if (annotationsVisible && mostImportantFile != null
657: && (status & STATUS_TEXT_ANNOTABLE) != 0) {
658: if (format != null) {
659: textAnnotation = formatAnnotation(mostImportantInfo,
660: mostImportantFile);
661: } else {
662: //String sticky = SvnUtils.getCopy(mostImportantFile);
663: String sticky = null;
664: if (status == FileInformation.STATUS_VERSIONED_UPTODATE
665: && sticky == null) {
666: textAnnotation = ""; // NOI18N
667: } else if (status == FileInformation.STATUS_VERSIONED_UPTODATE) {
668: textAnnotation = " [" + sticky + "]"; // NOI18N
669: } else if (sticky == null) {
670: String statusText = mostImportantInfo
671: .getShortStatusText();
672: if (!statusText.equals("")) { // NOI18N
673: textAnnotation = " ["
674: + mostImportantInfo
675: .getShortStatusText() + "]"; // NOI18N
676: } else {
677: textAnnotation = ""; // NOI18N
678: }
679: } else {
680: textAnnotation = " ["
681: + mostImportantInfo.getShortStatusText()
682: + "; " + sticky + "]"; // NOI18N
683: }
684: }
685: } else {
686: textAnnotation = ""; // NOI18N
687: }
688:
689: if (textAnnotation.length() > 0) {
690: textAnnotation = NbBundle.getMessage(
691: MercurialAnnotator.class, "textAnnotation",
692: textAnnotation); // NOI18N
693: }
694:
695: if (0 != (status & FileInformation.STATUS_NOTVERSIONED_EXCLUDED)) {
696: return excludedFormat.format(new Object[] { name,
697: textAnnotation });
698: } else if (0 != (status & FileInformation.STATUS_VERSIONED_DELETEDLOCALLY)) {
699: return deletedLocallyFormat.format(new Object[] { name,
700: textAnnotation });
701: } else if (0 != (status & FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY)) {
702: return removedLocallyFormat.format(new Object[] { name,
703: textAnnotation });
704: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY)) {
705: return newLocallyFormat.format(new Object[] { name,
706: textAnnotation });
707: } else if (0 != (status & FileInformation.STATUS_VERSIONED_ADDEDLOCALLY)) {
708: return addedLocallyFormat.format(new Object[] { name,
709: textAnnotation });
710: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY)) {
711: return modifiedLocallyFormat.format(new Object[] { name,
712: textAnnotation });
713: } else if (0 != (status & FileInformation.STATUS_VERSIONED_UPTODATE)) {
714: return uptodateFormat.format(new Object[] { name,
715: textAnnotation });
716: } else if (0 != (status & FileInformation.STATUS_VERSIONED_CONFLICT)) {
717: return conflictFormat.format(new Object[] { name,
718: textAnnotation });
719: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NOTMANAGED)) {
720: return name;
721: } else if (status == FileInformation.STATUS_UNKNOWN) {
722: return name;
723: } else {
724: throw new IllegalArgumentException("Uncomparable status: "
725: + status); // NOI18N
726: }
727: }
728:
729: private String htmlEncode(String name) {
730: if (name.indexOf('<') == -1)
731: return name;
732: return lessThan.matcher(name).replaceAll("<"); // NOI18N
733: }
734:
735: private String annotateFolderNameHtml(String name,
736: FileInformation mostImportantInfo, File mostImportantFile) {
737: String nameHtml = htmlEncode(name);
738: if (mostImportantInfo.getStatus() == FileInformation.STATUS_NOTVERSIONED_EXCLUDED) {
739: return excludedFormat.format(new Object[] { nameHtml, "" }); // NOI18N
740: }
741: String fileName = mostImportantFile.getName();
742: if (fileName.equals(name)) {
743: return uptodateFormat.format(new Object[] { nameHtml, "" }); // NOI18N
744: }
745:
746: // Label top level repository nodes with a repository name label when:
747: // Display Name (name) is different from its repo name (repo.getName())
748: fileName = null;
749: File repo = Mercurial.getInstance().getTopmostManagedParent(
750: mostImportantFile);
751: if (repo != null && repo.equals(mostImportantFile)) {
752: if (!repo.getName().equals(name)) {
753: fileName = repo.getName();
754: }
755: }
756: if (fileName != null)
757: return uptodateFormat.format(new Object[] { nameHtml,
758: " [" + fileName + "]" }); // NOI18N
759: else
760: return uptodateFormat.format(new Object[] { nameHtml, "" }); // NOI18N
761: }
762:
763: private boolean isMoreImportant(FileInformation a, FileInformation b) {
764: if (b == null)
765: return true;
766: if (a == null)
767: return false;
768: return getComparableStatus(a.getStatus()) < getComparableStatus(b
769: .getStatus());
770: }
771:
772: /**
773: * Gets integer status that can be used in comparators. The more important the status is for the user,
774: * the lower value it has. Conflict is 0, unknown status is 100.
775: *
776: * @return status constant suitable for 'by importance' comparators
777: */
778: public static int getComparableStatus(int status) {
779: if (0 != (status & FileInformation.STATUS_VERSIONED_CONFLICT)) {
780: return 0;
781: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MERGE)) {
782: return 1;
783: } else if (0 != (status & FileInformation.STATUS_VERSIONED_DELETEDLOCALLY)) {
784: return 10;
785: } else if (0 != (status & FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY)) {
786: return 11;
787: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY)) {
788: return 12;
789: } else if (0 != (status & FileInformation.STATUS_VERSIONED_ADDEDLOCALLY)) {
790: return 13;
791: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY)) {
792: return 14;
793: } else if (0 != (status & FileInformation.STATUS_VERSIONED_REMOVEDINREPOSITORY)) {
794: return 30;
795: } else if (0 != (status & FileInformation.STATUS_VERSIONED_NEWINREPOSITORY)) {
796: return 31;
797: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MODIFIEDINREPOSITORY)) {
798: return 32;
799: } else if (0 != (status & FileInformation.STATUS_VERSIONED_UPTODATE)) {
800: return 50;
801: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_EXCLUDED)) {
802: return 100;
803: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NOTMANAGED)) {
804: return 101;
805: } else if (status == FileInformation.STATUS_UNKNOWN) {
806: return 102;
807: } else {
808: throw new IllegalArgumentException("Uncomparable status: "
809: + status); // NOI18N
810: }
811: }
812:
813: private boolean isExcludedFromCommit(String absolutePath) {
814: return false;
815: }
816:
817: private boolean isNothingVersioned(File[] files) {
818: for (File file : files) {
819: if ((cache.getStatus(file).getStatus() & FileInformation.STATUS_MANAGED) != 0)
820: return false;
821: }
822: return true;
823: }
824:
825: private static boolean onlyProjects(Node[] nodes) {
826: if (nodes == null)
827: return false;
828: for (Node node : nodes) {
829: if (node.getLookup().lookup(Project.class) == null)
830: return false;
831: }
832: return true;
833: }
834:
835: private boolean onlyFolders(File[] files) {
836: for (int i = 0; i < files.length; i++) {
837: if (files[i].isFile())
838: return false;
839: if (!files[i].exists()
840: && !cache.getStatus(files[i]).isDirectory())
841: return false;
842: }
843: return true;
844: }
845:
846: private void reScheduleScan(int delayMillis) {
847: File dirToScan = dirsToScan.peek();
848: if (!folderToScan.equals(dirToScan)) {
849: if (!dirsToScan.offer(folderToScan)) {
850: Mercurial.LOG
851: .log(
852: Level.FINE,
853: "reScheduleScan failed to add to dirsToScan queue: {0} ",
854: folderToScan);
855: }
856: }
857: scanTask.schedule(delayMillis);
858: }
859:
860: private class ScanTask implements Runnable {
861: public void run() {
862: Thread.interrupted();
863: File dirToScan = dirsToScan.poll();
864: if (dirToScan != null) {
865: cache.getScannedFiles(dirToScan, null);
866: dirToScan = dirsToScan.peek();
867: if (dirToScan != null) {
868: scanTask.schedule(1000);
869: }
870: }
871: }
872: }
873: }
|