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:
042: package org.netbeans.modules.subversion;
043:
044: import org.netbeans.modules.subversion.ui.copy.*;
045: import org.netbeans.modules.subversion.ui.ignore.IgnoreAction;
046: import org.netbeans.modules.subversion.ui.status.StatusAction;
047: import org.netbeans.modules.subversion.ui.commit.CommitAction;
048: import org.netbeans.modules.subversion.ui.update.*;
049: import org.netbeans.modules.subversion.ui.diff.DiffAction;
050: import org.netbeans.modules.subversion.ui.diff.ExportDiffAction;
051: import org.netbeans.modules.subversion.ui.blame.BlameAction;
052: import org.netbeans.modules.subversion.ui.history.SearchHistoryAction;
053: import org.netbeans.modules.subversion.ui.project.ImportAction;
054: import org.netbeans.modules.subversion.ui.checkout.CheckoutAction;
055: import org.openide.util.actions.SystemAction;
056: import org.openide.util.NbBundle;
057: import org.openide.util.Utilities;
058: import org.openide.util.Lookup;
059: import org.openide.nodes.Node;
060: import org.netbeans.modules.subversion.util.SvnUtils;
061: import org.netbeans.modules.versioning.util.Utils;
062: import org.netbeans.modules.versioning.spi.VCSContext;
063: import org.netbeans.modules.versioning.spi.VCSAnnotator;
064: import org.netbeans.modules.versioning.spi.VersioningSupport;
065: import org.netbeans.api.project.Project;
066: import javax.swing.*;
067: import java.util.*;
068: import java.util.List;
069: import java.util.regex.Pattern;
070: import java.text.MessageFormat;
071: import java.io.File;
072: import java.awt.*;
073: import java.lang.reflect.Field;
074: import org.netbeans.modules.subversion.client.SvnClient;
075: import org.netbeans.modules.subversion.client.SvnClientExceptionHandler;
076: import org.netbeans.modules.subversion.ui.properties.SvnPropertiesAction;
077: import org.netbeans.modules.subversion.ui.relocate.RelocateAction;
078: import org.netbeans.modules.versioning.util.SystemActionBridge;
079: import org.netbeans.modules.diff.PatchAction;
080: import org.tigris.subversion.svnclientadapter.*;
081:
082: /**
083: * Annotates names for display in Files and Projects view (and possible elsewhere). Uses
084: * Filesystem support for this feature (to be replaced later in Core by something more generic).
085: *
086: * @author Maros Sandor
087: */
088: public class Annotator {
089:
090: private static MessageFormat uptodateFormat = getFormat("uptodateFormat"); // NOI18N
091: private static MessageFormat newLocallyFormat = getFormat("newLocallyFormat"); // NOI18N
092: private static MessageFormat addedLocallyFormat = getFormat("addedLocallyFormat"); // NOI18N
093: private static MessageFormat modifiedLocallyFormat = getFormat("modifiedLocallyFormat"); // NOI18N
094: private static MessageFormat removedLocallyFormat = getFormat("removedLocallyFormat"); // NOI18N
095: private static MessageFormat deletedLocallyFormat = getFormat("deletedLocallyFormat"); // NOI18N
096: private static MessageFormat newInRepositoryFormat = getFormat("newInRepositoryFormat"); // NOI18N
097: private static MessageFormat modifiedInRepositoryFormat = getFormat("modifiedInRepositoryFormat"); // NOI18N
098: private static MessageFormat removedInRepositoryFormat = getFormat("removedInRepositoryFormat"); // NOI18N
099: private static MessageFormat conflictFormat = getFormat("conflictFormat"); // NOI18N
100: private static MessageFormat mergeableFormat = getFormat("mergeableFormat"); // NOI18N
101: private static MessageFormat excludedFormat = getFormat("excludedFormat"); // NOI18N
102:
103: private static final int STATUS_TEXT_ANNOTABLE = FileInformation.STATUS_NOTVERSIONED_EXCLUDED
104: | FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY
105: | FileInformation.STATUS_VERSIONED_UPTODATE
106: | FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY
107: | FileInformation.STATUS_VERSIONED_CONFLICT
108: | FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY
109: | FileInformation.STATUS_VERSIONED_DELETEDLOCALLY
110: | FileInformation.STATUS_VERSIONED_ADDEDLOCALLY;
111:
112: private static final Pattern lessThan = Pattern.compile("<"); // NOI18N
113:
114: public static String ANNOTATION_REVISION = "revision";
115: public static String ANNOTATION_STATUS = "status";
116: public static String ANNOTATION_FOLDER = "folder";
117: public static String ANNOTATION_MIME_TYPE = "mime_type";
118:
119: public static String[] LABELS = new String[] { ANNOTATION_REVISION,
120: ANNOTATION_STATUS, ANNOTATION_FOLDER, ANNOTATION_MIME_TYPE };
121:
122: private final FileStatusCache cache;
123: private MessageFormat format;
124: private String emptyFormat;
125:
126: private boolean mimeTypeFlag;
127:
128: Annotator(Subversion svn) {
129: this .cache = svn.getStatusCache();
130: initDefaults();
131: }
132:
133: private void initDefaults() {
134: Field[] fields = Annotator.class.getDeclaredFields();
135: for (int i = 0; i < fields.length; i++) {
136: String name = fields[i].getName();
137: if (name.endsWith("Format")) { // NOI18N
138: initDefaultColor(name.substring(0, name.length() - 6));
139: }
140: }
141: refresh();
142: }
143:
144: public void refresh() {
145: String string = SvnModuleConfig.getDefault()
146: .getAnnotationFormat(); //System.getProperty("netbeans.experimental.svn.ui.statusLabelFormat"); // NOI18N
147: if (string != null && !string.trim().equals("")) {
148: mimeTypeFlag = string.indexOf("{mime_type}") > -1;
149: string = string.replaceAll("\\{revision\\}", "\\{0\\}"); // NOI18N
150: string = string.replaceAll("\\{status\\}", "\\{1\\}"); // NOI18N
151: string = string.replaceAll("\\{folder\\}", "\\{2\\}"); // NOI18N
152: string = string.replaceAll("\\{mime_type\\}", "\\{3\\}"); // NOI18N
153: format = new MessageFormat(string);
154: emptyFormat = format.format(
155: new String[] { "", "", "", "" },
156: new StringBuffer(), null).toString().trim();
157: }
158: }
159:
160: private void initDefaultColor(String name) {
161: String color = System.getProperty("svn.color." + name); // NOI18N
162: if (color == null)
163: return;
164: setAnnotationColor(name, color);
165: }
166:
167: /**
168: * Changes annotation color of files.
169: *
170: * @param name name of the color to change. Can be one of:
171: * newLocally, addedLocally, modifiedLocally, removedLocally, deletedLocally, newInRepository, modifiedInRepository,
172: * removedInRepository, conflict, mergeable, excluded.
173: * @param colorString new color in the format: 4455AA (RGB hexadecimal)
174: */
175: private void setAnnotationColor(String name, String colorString) {
176: try {
177: Field field = Annotator.class.getDeclaredField(name
178: + "Format"); // NOI18N
179: MessageFormat format = new MessageFormat("<font color=\""
180: + colorString
181: + "\">{0}</font><font color=\"#999999\">{1}</font>"); // NOI18N
182: field.set(null, format);
183: } catch (Exception e) {
184: throw new IllegalArgumentException("Invalid color name"); // NOI18N
185: }
186: }
187:
188: /**
189: * Adds rendering attributes to an arbitrary String based on a SVN status. The name is usually a file or folder
190: * display name and status is usually its SVN status as reported by FileStatusCache.
191: *
192: * @param name name to annotate
193: * @param info status that an object with the given name has
194: * @param file file this annotation belongs to. It is used to determine sticky tags for textual annotations. Pass
195: * null if you do not want textual annotations to appear in returned markup
196: * @return String html-annotated name that can be used in Swing controls that support html rendering. Note: it may
197: * also return the original name String
198: */
199: public String annotateNameHtml(String name, FileInformation info,
200: File file) {
201: name = htmlEncode(name);
202: int status = info.getStatus();
203: String textAnnotation;
204: boolean annotationsVisible = VersioningSupport
205: .getPreferences()
206: .getBoolean(
207: VersioningSupport.PREF_BOOLEAN_TEXT_ANNOTATIONS_VISIBLE,
208: false);
209: if (annotationsVisible && file != null
210: && (status & STATUS_TEXT_ANNOTABLE) != 0) {
211: if (format != null) {
212: textAnnotation = formatAnnotation(info, file);
213: } else {
214: String sticky = SvnUtils.getCopy(file);
215: if (status == FileInformation.STATUS_VERSIONED_UPTODATE
216: && sticky == null) {
217: textAnnotation = ""; // NOI18N
218: } else if (status == FileInformation.STATUS_VERSIONED_UPTODATE) {
219: textAnnotation = " [" + sticky + "]"; // NOI18N
220: } else if (sticky == null) {
221: String statusText = info.getShortStatusText();
222: if (!statusText.equals("")) {
223: textAnnotation = " ["
224: + info.getShortStatusText() + "]"; // NOI18N
225: } else {
226: textAnnotation = "";
227: }
228: } else {
229: textAnnotation = " [" + info.getShortStatusText()
230: + "; " + sticky + "]"; // NOI18N
231: }
232: }
233: } else {
234: textAnnotation = ""; // NOI18N
235: }
236: if (textAnnotation.length() > 0) {
237: textAnnotation = NbBundle.getMessage(Annotator.class,
238: "textAnnotation", textAnnotation);
239: }
240:
241: // aligned with SvnUtils.getComparableStatus
242:
243: if (0 != (status & FileInformation.STATUS_VERSIONED_CONFLICT)) {
244: return conflictFormat.format(new Object[] { name,
245: textAnnotation });
246: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MERGE)) {
247: return mergeableFormat.format(new Object[] { name,
248: textAnnotation });
249: } else if (0 != (status & FileInformation.STATUS_VERSIONED_DELETEDLOCALLY)) {
250: return deletedLocallyFormat.format(new Object[] { name,
251: textAnnotation });
252: } else if (0 != (status & FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY)) {
253: return removedLocallyFormat.format(new Object[] { name,
254: textAnnotation });
255: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY)) {
256: return newLocallyFormat.format(new Object[] { name,
257: textAnnotation });
258: } else if (0 != (status & FileInformation.STATUS_VERSIONED_ADDEDLOCALLY)) {
259: return addedLocallyFormat.format(new Object[] { name,
260: textAnnotation });
261: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY)) {
262: return modifiedLocallyFormat.format(new Object[] { name,
263: textAnnotation });
264:
265: // repository changes - lower annotator priority
266:
267: } else if (0 != (status & FileInformation.STATUS_VERSIONED_REMOVEDINREPOSITORY)) {
268: return removedInRepositoryFormat.format(new Object[] {
269: name, textAnnotation });
270: } else if (0 != (status & FileInformation.STATUS_VERSIONED_NEWINREPOSITORY)) {
271: return newInRepositoryFormat.format(new Object[] { name,
272: textAnnotation });
273: } else if (0 != (status & FileInformation.STATUS_VERSIONED_MODIFIEDINREPOSITORY)) {
274: return modifiedInRepositoryFormat.format(new Object[] {
275: name, textAnnotation });
276: } else if (0 != (status & FileInformation.STATUS_VERSIONED_UPTODATE)) {
277: return uptodateFormat.format(new Object[] { name,
278: textAnnotation });
279: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_EXCLUDED)) {
280: return excludedFormat.format(new Object[] { name,
281: textAnnotation });
282: } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NOTMANAGED)) {
283: return name;
284: } else if (status == FileInformation.STATUS_UNKNOWN) {
285: return name;
286: } else {
287: throw new IllegalArgumentException("Uncomparable status: "
288: + status); // NOI18N
289: }
290: }
291:
292: /**
293: * Applies custom format.
294: */
295: private String formatAnnotation(FileInformation info, File file) {
296: String statusString = ""; // NOI18N
297: int status = info.getStatus();
298: if (status != FileInformation.STATUS_VERSIONED_UPTODATE) {
299: statusString = info.getShortStatusText();
300: }
301:
302: String revisionString = ""; // NOI18N
303: String binaryString = ""; // NOI18N
304:
305: ISVNStatus snvStatus = info.getEntry(file);
306: if (snvStatus != null) {
307: revisionString = snvStatus.getRevision().toString();
308: if (mimeTypeFlag) {
309: binaryString = getMimeType(file);
310: }
311: }
312:
313: String stickyString = SvnUtils.getCopy(file);
314: if (stickyString == null) {
315: stickyString = ""; // NOI18N
316: }
317:
318: Object[] arguments = new Object[] { revisionString,
319: statusString, stickyString, binaryString };
320:
321: String annotation = format.format(arguments,
322: new StringBuffer(), null).toString().trim();
323: if (annotation.equals(emptyFormat)) {
324: return "";
325: } else {
326: return " " + annotation;
327: }
328: }
329:
330: private String getMimeType(File file) {
331: try {
332: SvnClient client = Subversion.getInstance()
333: .getClient(false);
334: ISVNProperty prop = client.propertyGet(file,
335: ISVNProperty.MIME_TYPE);
336: if (prop != null) {
337: String mime = prop.getValue();
338: return mime != null ? mime : "";
339: }
340: } catch (SVNClientException ex) {
341: SvnClientExceptionHandler.notifyException(ex, false, false);
342: return "";
343: }
344: return "";
345: }
346:
347: private String annotateFolderNameHtml(String name,
348: FileInformation info, File file) {
349: name = htmlEncode(name);
350: int status = info.getStatus();
351: String textAnnotation;
352: boolean annotationsVisible = VersioningSupport
353: .getPreferences()
354: .getBoolean(
355: VersioningSupport.PREF_BOOLEAN_TEXT_ANNOTATIONS_VISIBLE,
356: false);
357: if (annotationsVisible && file != null
358: && (status & FileInformation.STATUS_MANAGED) != 0) {
359:
360: if (format != null) {
361: textAnnotation = formatAnnotation(info, file);
362: } else {
363: String sticky;
364: ISVNStatus lstatus = info.getEntry(file);
365: if (lstatus != null && lstatus.getUrl() != null) {
366: sticky = SvnUtils.getCopy(lstatus.getUrl());
367: } else {
368: // slower
369: sticky = SvnUtils.getCopy(file);
370: }
371:
372: if (status == FileInformation.STATUS_VERSIONED_UPTODATE
373: && sticky == null) {
374: textAnnotation = ""; // NOI18N
375: } else if (status == FileInformation.STATUS_VERSIONED_UPTODATE) {
376: textAnnotation = " [" + sticky + "]"; // NOI18N
377: } else if (sticky == null) {
378: String statusText = info.getShortStatusText();
379: if (!statusText.equals("")) { // NOI18N
380: textAnnotation = " ["
381: + info.getShortStatusText() + "]"; // NOI18N
382: } else {
383: textAnnotation = ""; // NOI18N
384: }
385: } else {
386: textAnnotation = " [" + info.getShortStatusText()
387: + "; " + sticky + "]"; // NOI18N
388: }
389: }
390: } else {
391: textAnnotation = ""; // NOI18N
392: }
393: if (textAnnotation.length() > 0) {
394: textAnnotation = NbBundle.getMessage(Annotator.class,
395: "textAnnotation", textAnnotation); // NOI18N
396: }
397:
398: if (status == FileInformation.STATUS_UNKNOWN) {
399: return name;
400: } else if (match(status,
401: FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY)) {
402: return name;
403: } else if (match(status,
404: FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY)) {
405: return uptodateFormat.format(new Object[] { name,
406: textAnnotation });
407: } else if (match(status,
408: FileInformation.STATUS_VERSIONED_ADDEDLOCALLY)) {
409: return uptodateFormat.format(new Object[] { name,
410: textAnnotation });
411: } else if (match(status,
412: FileInformation.STATUS_VERSIONED_UPTODATE)) {
413: return uptodateFormat.format(new Object[] { name,
414: textAnnotation });
415: } else if (match(status,
416: FileInformation.STATUS_NOTVERSIONED_EXCLUDED)) {
417: return excludedFormat.format(new Object[] { name,
418: textAnnotation });
419: } else if (match(status,
420: FileInformation.STATUS_VERSIONED_DELETEDLOCALLY)) {
421: return name;
422: } else if (match(status,
423: FileInformation.STATUS_VERSIONED_NEWINREPOSITORY)) {
424: return name;
425: } else if (match(status,
426: FileInformation.STATUS_VERSIONED_MODIFIEDINREPOSITORY)) {
427: return name;
428: } else if (match(status,
429: FileInformation.STATUS_VERSIONED_REMOVEDINREPOSITORY)) {
430: return name;
431: } else if (match(status,
432: FileInformation.STATUS_NOTVERSIONED_NOTMANAGED)) {
433: return name;
434: } else if (match(status, FileInformation.STATUS_VERSIONED_MERGE)) {
435: return name;
436: } else if (match(status,
437: FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY)) {
438: return name;
439: } else if (match(status,
440: FileInformation.STATUS_VERSIONED_CONFLICT)) {
441: return name;
442: } else {
443: throw new IllegalArgumentException("Unknown status: "
444: + status); // NOI18N
445: }
446: }
447:
448: private static boolean match(int status, int mask) {
449: return (status & mask) != 0;
450: }
451:
452: private String htmlEncode(String name) {
453: if (name.indexOf('<') == -1)
454: return name;
455: return lessThan.matcher(name).replaceAll("<"); // NOI18N
456: }
457:
458: public String annotateNameHtml(File file, FileInformation info) {
459: return annotateNameHtml(file.getName(), info, file);
460: }
461:
462: public String annotateNameHtml(String name, VCSContext context,
463: int includeStatus) {
464: FileInformation mostImportantInfo = null;
465: File mostImportantFile = null;
466: boolean folderAnnotation = false;
467:
468: for (File file : context.getRootFiles()) {
469: FileInformation info = cache.getStatus(file);
470: int status = info.getStatus();
471: if ((status & includeStatus) == 0)
472: continue;
473:
474: if (isMoreImportant(info, mostImportantInfo)) {
475: mostImportantInfo = info;
476: mostImportantFile = file;
477: folderAnnotation = file.isDirectory();
478: }
479: }
480:
481: if (folderAnnotation == false
482: && context.getRootFiles().size() > 1) {
483: folderAnnotation = !Utils.shareCommonDataObject(context
484: .getRootFiles().toArray(
485: new File[context.getRootFiles().size()]));
486: }
487:
488: if (mostImportantInfo == null)
489: return null;
490: return folderAnnotation ? annotateFolderNameHtml(name,
491: mostImportantInfo, mostImportantFile)
492: : annotateNameHtml(name, mostImportantInfo,
493: mostImportantFile);
494: }
495:
496: private boolean isMoreImportant(FileInformation a, FileInformation b) {
497: if (b == null)
498: return true;
499: if (a == null)
500: return false;
501: return SvnUtils.getComparableStatus(a.getStatus()) < SvnUtils
502: .getComparableStatus(b.getStatus());
503: }
504:
505: String annotateName(String name, Set files) {
506: return null;
507: }
508:
509: /**
510: * Returns array of versioning actions that may be used to construct a popup menu. These actions
511: * will act on the supplied context.
512: *
513: * @param ctx context similar to {@link org.openide.util.ContextAwareAction#createContextAwareInstance(org.openide.util.Lookup)}
514: * @param destination
515: * @return Action[] array of versioning actions that may be used to construct a popup menu. These actions
516: * will act on currently activated nodes.
517: */
518: public static Action[] getActions(VCSContext ctx,
519: VCSAnnotator.ActionDestination destination) {
520: ResourceBundle loc = NbBundle.getBundle(Annotator.class);
521: Node[] nodes = ctx.getElements().lookupAll(Node.class).toArray(
522: new Node[0]);
523: File[] files = ctx.getRootFiles().toArray(
524: new File[ctx.getRootFiles().size()]);
525: Lookup context = ctx.getElements();
526: boolean noneVersioned = isNothingVersioned(files);
527: boolean onlyFolders = onlyFolders(files);
528: boolean onlyProjects = onlyProjects(nodes);
529:
530: List<Action> actions = new ArrayList<Action>(20);
531: if (destination == VCSAnnotator.ActionDestination.MainMenu) {
532: actions.add(SystemAction.get(CheckoutAction.class));
533: actions.add(SystemAction.get(ImportAction.class));
534: actions.add(SystemAction.get(RelocateAction.class));
535: actions.add(null);
536: actions.add(SystemAction
537: .get(UpdateWithDependenciesAction.class));
538: actions.add(null);
539: actions.add(SystemAction.get(StatusAction.class));
540: actions.add(SystemAction.get(DiffAction.class));
541: actions.add(SystemAction.get(UpdateAction.class));
542: actions.add(SystemAction.get(CommitAction.class));
543: actions.add(null);
544: actions.add(SystemAction.get(ExportDiffAction.class));
545: actions.add(SystemAction.get(PatchAction.class));
546: actions.add(null);
547: actions.add(SystemAction.get(CreateCopyAction.class));
548: actions.add(SystemAction.get(SwitchToAction.class));
549: actions.add(SystemAction.get(MergeAction.class));
550: actions.add(null);
551: actions.add(SystemAction.get(BlameAction.class));
552: actions.add(SystemAction.get(SearchHistoryAction.class));
553: actions.add(null);
554: actions.add(SystemAction
555: .get(RevertModificationsAction.class));
556: actions.add(SystemAction.get(ResolveConflictsAction.class));
557: actions.add(SystemAction.get(IgnoreAction.class));
558: actions.add(null);
559: actions.add(SystemAction.get(SvnPropertiesAction.class));
560: } else {
561: if (noneVersioned) {
562: actions.add(SystemActionBridge.createAction(
563: SystemAction.get(ImportAction.class)
564: .createContextAwareInstance(context),
565: loc.getString("CTL_PopupMenuItem_Import"),
566: context));
567: } else {
568: actions.add(SystemActionBridge.createAction(
569: SystemAction.get(StatusAction.class), loc
570: .getString("CTL_PopupMenuItem_Status"),
571: context));
572: actions.add(SystemActionBridge.createAction(
573: SystemAction.get(DiffAction.class), loc
574: .getString("CTL_PopupMenuItem_Diff"),
575: context));
576: actions.add(SystemActionBridge.createAction(
577: SystemAction.get(UpdateAction.class), loc
578: .getString("CTL_PopupMenuItem_Update"),
579: context));
580: if (onlyProjects) {
581: actions
582: .add(new SystemActionBridge(
583: SystemAction
584: .get(UpdateWithDependenciesAction.class),
585: loc
586: .getString("CTL_PopupMenuItem_UpdateWithDeps")));
587: }
588: actions.add(SystemActionBridge.createAction(
589: SystemAction.get(CommitAction.class), loc
590: .getString("CTL_PopupMenuItem_Commit"),
591: context));
592: actions.add(null);
593: actions.add(SystemActionBridge.createAction(
594: SystemAction.get(CreateCopyAction.class), loc
595: .getString("CTL_PopupMenuItem_Copy"),
596: context));
597: actions.add(SystemActionBridge.createAction(
598: SystemAction.get(SwitchToAction.class), loc
599: .getString("CTL_PopupMenuItem_Switch"),
600: context));
601: actions.add(SystemActionBridge.createAction(
602: SystemAction.get(MergeAction.class), loc
603: .getString("CTL_PopupMenuItem_Merge"),
604: context));
605: actions.add(null);
606: if (!onlyFolders) {
607: actions
608: .add(SystemActionBridge
609: .createAction(
610: SystemAction
611: .get(BlameAction.class),
612: ((BlameAction) SystemAction
613: .get(BlameAction.class))
614: .visible(nodes) ? loc
615: .getString("CTL_PopupMenuItem_HideAnnotations")
616: : loc
617: .getString("CTL_PopupMenuItem_ShowAnnotations"),
618: context));
619: }
620: actions
621: .add(SystemActionBridge
622: .createAction(
623: SystemAction
624: .get(SearchHistoryAction.class),
625: loc
626: .getString("CTL_PopupMenuItem_SearchHistory"),
627: context));
628: actions.add(null);
629: actions.add(SystemActionBridge.createAction(
630: SystemAction
631: .get(RevertModificationsAction.class),
632: loc.getString("CTL_PopupMenuItem_GetClean"),
633: context));
634: actions
635: .add(SystemActionBridge
636: .createAction(
637: SystemAction
638: .get(ResolveConflictsAction.class),
639: loc
640: .getString("CTL_PopupMenuItem_ResolveConflicts"),
641: context));
642: if (!onlyProjects) {
643: actions
644: .add(SystemActionBridge
645: .createAction(
646: SystemAction
647: .get(IgnoreAction.class),
648: ((IgnoreAction) SystemAction
649: .get(IgnoreAction.class))
650: .getActionStatus(nodes) == IgnoreAction.UNIGNORING ? loc
651: .getString("CTL_PopupMenuItem_Unignore")
652: : loc
653: .getString("CTL_PopupMenuItem_Ignore"),
654: context));
655: }
656: actions.add(null);
657: actions.add(SystemActionBridge.createAction(
658: SystemAction.get(SvnPropertiesAction.class),
659: loc.getString("CTL_PopupMenuItem_Properties"),
660: context));
661: }
662: }
663: return actions.toArray(new Action[actions.size()]);
664: }
665:
666: private static boolean isNothingVersioned(File[] files) {
667: FileStatusCache cache = Subversion.getInstance()
668: .getStatusCache();
669: for (File file : files) {
670: if ((cache.getStatus(file).getStatus() & FileInformation.STATUS_MANAGED) != 0)
671: return false;
672: }
673: return true;
674: }
675:
676: private static boolean onlyProjects(Node[] nodes) {
677: if (nodes == null || nodes.length == 0)
678: return false;
679: for (Node node : nodes) {
680: if (node.getLookup().lookup(Project.class) == null)
681: return false;
682: }
683: return true;
684: }
685:
686: private static boolean onlyFolders(File[] files) {
687: FileStatusCache cache = Subversion.getInstance()
688: .getStatusCache();
689: for (int i = 0; i < files.length; i++) {
690: if (files[i].isFile())
691: return false;
692: if (!files[i].exists()
693: && !cache.getStatus(files[i]).isDirectory())
694: return false;
695: }
696: return true;
697: }
698:
699: private static MessageFormat getFormat(String key) {
700: String format = NbBundle.getMessage(Annotator.class, key);
701: return new MessageFormat(format);
702: }
703:
704: private static final int STATUS_BADGEABLE = FileInformation.STATUS_VERSIONED_UPTODATE
705: | FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY
706: | FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY;
707:
708: public Image annotateIcon(Image icon, VCSContext context) {
709: boolean folderAnnotation = false;
710: for (File file : context.getRootFiles()) {
711: if (file.isDirectory()) {
712: folderAnnotation = true;
713: break;
714: }
715: }
716:
717: if (folderAnnotation == false
718: && context.getRootFiles().size() > 1) {
719: folderAnnotation = !Utils.shareCommonDataObject(context
720: .getRootFiles().toArray(
721: new File[context.getRootFiles().size()]));
722: }
723:
724: if (folderAnnotation == false) {
725: return null;
726: }
727:
728: FileStatusCache cache = Subversion.getInstance()
729: .getStatusCache();
730: boolean isVersioned = false;
731: for (Iterator i = context.getRootFiles().iterator(); i
732: .hasNext();) {
733: File file = (File) i.next();
734: if ((cache.getStatus(file).getStatus() & STATUS_BADGEABLE) != 0) {
735: isVersioned = true;
736: break;
737: }
738: }
739: if (!isVersioned)
740: return null;
741:
742: SvnModuleConfig config = SvnModuleConfig.getDefault();
743: boolean allExcluded = true;
744: boolean modified = false;
745:
746: Map map = cache.getAllModifiedFiles();
747: Map<File, FileInformation> modifiedFiles = new HashMap<File, FileInformation>();
748: for (Iterator i = map.keySet().iterator(); i.hasNext();) {
749: File file = (File) i.next();
750: FileInformation info = (FileInformation) map.get(file);
751: if ((info.getStatus() & FileInformation.STATUS_LOCAL_CHANGE) != 0)
752: modifiedFiles.put(file, info);
753: }
754:
755: for (Iterator i = context.getRootFiles().iterator(); i
756: .hasNext();) {
757: File file = (File) i.next();
758: if (VersioningSupport.isFlat(file)) {
759: for (Iterator j = modifiedFiles.keySet().iterator(); j
760: .hasNext();) {
761: File mf = (File) j.next();
762: if (mf.getParentFile().equals(file)) {
763: FileInformation info = (FileInformation) modifiedFiles
764: .get(mf);
765: if (info.isDirectory())
766: continue;
767: int status = info.getStatus();
768: if (status == FileInformation.STATUS_VERSIONED_CONFLICT) {
769: Image badge = Utilities
770: .loadImage(
771: "org/netbeans/modules/subversion/resources/icons/conflicts-badge.png",
772: true); // NOI18N
773: return Utilities.mergeImages(icon, badge,
774: 16, 9);
775: }
776: modified = true;
777: allExcluded &= config.isExcludedFromCommit(mf
778: .getAbsolutePath());
779: }
780: }
781: } else {
782: for (Iterator j = modifiedFiles.keySet().iterator(); j
783: .hasNext();) {
784: File mf = (File) j.next();
785: if (Utils.isAncestorOrEqual(file, mf)) {
786: FileInformation info = (FileInformation) modifiedFiles
787: .get(mf);
788: int status = info.getStatus();
789: if ((status == FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY || status == FileInformation.STATUS_VERSIONED_ADDEDLOCALLY)
790: && file.equals(mf)) {
791: continue;
792: }
793: if (status == FileInformation.STATUS_VERSIONED_CONFLICT) {
794: Image badge = Utilities
795: .loadImage(
796: "org/netbeans/modules/subversion/resources/icons/conflicts-badge.png",
797: true); // NOI18N
798: return Utilities.mergeImages(icon, badge,
799: 16, 9);
800: }
801: modified = true;
802: allExcluded &= config.isExcludedFromCommit(mf
803: .getAbsolutePath());
804: }
805: }
806: }
807: }
808:
809: if (modified && !allExcluded) {
810: Image badge = Utilities
811: .loadImage(
812: "org/netbeans/modules/subversion/resources/icons/modified-badge.png",
813: true); // NOI18N
814: return Utilities.mergeImages(icon, badge, 16, 9);
815: } else {
816: return null;
817: }
818: }
819:
820: }
|