From 8b1bb92ef376b37a8b6d21c75c228cefb0de805f Mon Sep 17 00:00:00 2001 From: Jozef Tomek Date: Mon, 13 Nov 2023 22:34:21 +0100 Subject: [PATCH] Add styling enhancements to signatures (JavaElementLinks) in Javadoc --- .../JavaElementLabelComposerCore.java | 456 ++++++----- .../manipulation/JavaElementLabelsCore.java | 2 +- org.eclipse.jdt.ui/JavadocHoverStyleSheet.css | 49 +- org.eclipse.jdt.ui/JavadocViewStyleSheet.css | 47 ++ .../css/e4-dark_jdt_syntaxhighlighting.css | 1 + .../icons/full/dlcl16/wrap_all.png | Bin 0 -> 360 bytes .../icons/full/dlcl16/wrap_all@2x.png | Bin 0 -> 703 bytes .../icons/full/dview16/members.png | Bin 0 -> 661 bytes .../icons/full/dview16/types.png | Bin 0 -> 628 bytes .../icons/full/ovr16/mouse_cursor_ovr.png | Bin 0 -> 340 bytes .../icons/full/ovr16/mouse_cursor_ovr@2x.png | Bin 0 -> 404 bytes .../jdt/internal/ui/JavaPluginImages.java | 14 + .../internal/ui/infoviews/JavadocView.java | 42 +- .../ui/text/java/hover/JavadocHover.java | 94 ++- .../BindingLinkedLabelComposer.java | 14 +- .../ui/viewsupport/JavaElementLinks.java | 722 +++++++++++++++++- .../MenuVisibilityMenuItemsConfigurer.java | 92 +++ .../MouseListeningMenuItemsConfigurer.java | 105 +++ .../MouseListeningToolItemsConfigurer.java | 157 ++++ .../ReappearingMenuToolbarAction.java | 115 +++ .../ToolbarVisibilityToolItemsConfigurer.java | 93 +++ .../browser/CheckboxInBrowserUtil.java | 249 ++++++ .../CheckboxToggleInBrowserAction.java | 38 + ...HoverPreferenceStylingInBrowserAction.java | 76 ++ .../HoverStylingInBrowserMenuAction.java | 110 +++ .../JavadocEnrichmentImageDescriptor.java | 56 ++ .../javadoc/JavadocStylingMessages.java | 57 ++ .../javadoc/JavadocStylingMessages.properties | 36 + ...gnatureStylingColorPreferenceMenuItem.java | 84 ++ .../SignatureStylingColorSubMenuItem.java | 171 +++++ .../SignatureStylingMenuToolbarAction.java | 161 ++++ .../ToggleSignatureFormattingAction.java | 34 + .../ToggleSignatureStylingMenuAction.java | 96 +++ ...ggleSignatureTypeLevelsColoringAction.java | 34 + ...SignatureTypeParametersColoringAction.java | 35 + .../ToggleSignatureWrappingAction.java | 34 + .../eclipse/jdt/ui/PreferenceConstants.java | 8 + 37 files changed, 3066 insertions(+), 216 deletions(-) create mode 100644 org.eclipse.jdt.ui/icons/full/dlcl16/wrap_all.png create mode 100644 org.eclipse.jdt.ui/icons/full/dlcl16/wrap_all@2x.png create mode 100644 org.eclipse.jdt.ui/icons/full/dview16/members.png create mode 100644 org.eclipse.jdt.ui/icons/full/dview16/types.png create mode 100644 org.eclipse.jdt.ui/icons/full/ovr16/mouse_cursor_ovr.png create mode 100644 org.eclipse.jdt.ui/icons/full/ovr16/mouse_cursor_ovr@2x.png create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MenuVisibilityMenuItemsConfigurer.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MouseListeningMenuItemsConfigurer.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MouseListeningToolItemsConfigurer.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/ReappearingMenuToolbarAction.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/ToolbarVisibilityToolItemsConfigurer.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/CheckboxInBrowserUtil.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/CheckboxToggleInBrowserAction.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/HoverPreferenceStylingInBrowserAction.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/HoverStylingInBrowserMenuAction.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocEnrichmentImageDescriptor.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocStylingMessages.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocStylingMessages.properties create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingColorPreferenceMenuItem.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingColorSubMenuItem.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingMenuToolbarAction.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureFormattingAction.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureStylingMenuAction.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureTypeLevelsColoringAction.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureTypeParametersColoringAction.java create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureWrappingAction.java diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/core/manipulation/JavaElementLabelComposerCore.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/core/manipulation/JavaElementLabelComposerCore.java index b8cd1c2df55..6775c7326fc 100644 --- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/core/manipulation/JavaElementLabelComposerCore.java +++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/core/manipulation/JavaElementLabelComposerCore.java @@ -249,202 +249,40 @@ public void appendMethodLabel(IMethod method, long flags) { // type parameters if (getFlag(flags, JavaElementLabelsCore.M_PRE_TYPE_PARAMETERS)) { - if (resolvedKey != null) { - if (resolvedKey.isParameterizedMethod()) { - String[] typeArgRefs= resolvedKey.getTypeArguments(); - if (typeArgRefs.length > 0) { - appendTypeArgumentSignaturesLabel(method, typeArgRefs, flags); - fBuffer.append(' '); - } - } else { - String[] typeParameterSigs= Signature.getTypeParameters(resolvedSig); - if (typeParameterSigs.length > 0) { - appendTypeParameterSignaturesLabel(typeParameterSigs, flags); - fBuffer.append(' '); - } - } - } else if (method.exists()) { - ITypeParameter[] typeParameters= method.getTypeParameters(); - if (typeParameters.length > 0) { - appendTypeParametersLabels(typeParameters, flags); - fBuffer.append(' '); - } - } + appendMethodPrependedTypeParams(method, flags, resolvedKey, resolvedSig); } // return type if (getFlag(flags, JavaElementLabelsCore.M_PRE_RETURNTYPE) && method.exists() && !method.isConstructor()) { - String returnTypeSig= resolvedSig != null ? Signature.getReturnType(resolvedSig) : method.getReturnType(); - appendTypeSignatureLabel(method, returnTypeSig, flags); - fBuffer.append(' '); + appendMethodPrependedReturnType(method, flags, resolvedSig); } // qualification if (getFlag(flags, JavaElementLabelsCore.M_FULLY_QUALIFIED)) { - appendTypeLabel(method.getDeclaringType(), JavaElementLabelsCore.T_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS)); - fBuffer.append('.'); + appendMethodQualification(method, flags); } - fBuffer.append(getElementName(method)); + appendMethodName(method); // constructor type arguments if (getFlag(flags, JavaElementLabelsCore.T_TYPE_PARAMETERS) && method.exists() && method.isConstructor()) { - if (resolvedKey != null && resolvedSig != null && resolvedKey.isParameterizedType()) { - BindingKey declaringType= resolvedKey.getDeclaringType(); - if (declaringType != null) { - String[] declaringTypeArguments= declaringType.getTypeArguments(); - appendTypeArgumentSignaturesLabel(method, declaringTypeArguments, flags); - } - } + appendMethodConstructorTypeParams(method, flags, resolvedKey, resolvedSig); } // parameters - fBuffer.append('('); - String[] declaredParameterTypes= method.getParameterTypes(); - if (getFlag(flags, JavaElementLabelsCore.M_PARAMETER_TYPES | JavaElementLabelsCore.M_PARAMETER_NAMES)) { - String[] types= null; - int nParams= 0; - boolean renderVarargs= false; - boolean isPolymorphic= false; - if (getFlag(flags, JavaElementLabelsCore.M_PARAMETER_TYPES)) { - if (resolvedSig != null) { - types= Signature.getParameterTypes(resolvedSig); - } else { - types= declaredParameterTypes; - } - nParams= types.length; - renderVarargs= method.exists() && Flags.isVarargs(method.getFlags()); - if (renderVarargs - && resolvedSig != null - && declaredParameterTypes.length == 1 - && JavaModelUtil.isPolymorphicSignature(method)) { - renderVarargs= false; - isPolymorphic= true; - } - } - String[] names= null; - if (getFlag(flags, JavaElementLabelsCore.M_PARAMETER_NAMES) && method.exists()) { - names= method.getParameterNames(); - if (isPolymorphic) { - // handled specially below - } else if (types == null) { - nParams= names.length; - } else { // types != null - if (nParams != names.length) { - if (resolvedSig != null && types.length > names.length) { - // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=99137 - nParams= names.length; - String[] typesWithoutSyntheticParams= new String[nParams]; - System.arraycopy(types, types.length - nParams, typesWithoutSyntheticParams, 0, nParams); - types= typesWithoutSyntheticParams; - } else { - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=101029 - // JavaPlugin.logErrorMessage("JavaElementLabels: Number of param types(" + nParams + ") != number of names(" + names.length + "): " + method.getElementName()); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ - names= null; // no names rendered - } - } - } - } - - ILocalVariable[] annotatedParameters= null; - if (nParams > 0 && getFlag(flags, JavaElementLabelsCore.M_PARAMETER_ANNOTATIONS)) { - annotatedParameters= method.getParameters(); - } - - for (int i= 0; i < nParams; i++) { - if (i > 0) { - fBuffer.append(JavaElementLabelsCore.COMMA_STRING); - } - if (annotatedParameters != null && i < annotatedParameters.length) { - appendAnnotationLabels(annotatedParameters[i].getAnnotations(), flags); - } - - if (types != null) { - String paramSig= types[i]; - if (renderVarargs && (i == nParams - 1)) { - int newDim= Signature.getArrayCount(paramSig) - 1; - appendTypeSignatureLabel(method, Signature.getElementType(paramSig), flags); - for (int k= 0; k < newDim; k++) { - fBuffer.append('[').append(']'); - } - fBuffer.append(JavaElementLabelsCore.ELLIPSIS_STRING); - } else { - appendTypeSignatureLabel(method, paramSig, flags); - } - } - if (names != null) { - if (types != null) { - fBuffer.append(' '); - } - if (isPolymorphic) { - fBuffer.append(names[0] + i); - } else { - fBuffer.append(names[i]); - } - } - } - } else { - if (declaredParameterTypes.length > 0) { - fBuffer.append(JavaElementLabelsCore.ELLIPSIS_STRING); - } - } - fBuffer.append(')'); + appendMethodParams(method, flags, resolvedSig); if (getFlag(flags, JavaElementLabelsCore.M_EXCEPTIONS)) { - String[] types; - if (resolvedKey != null) { - types= resolvedKey.getThrownExceptions(); - } else { - types= method.exists() ? method.getExceptionTypes() : new String[0]; - } - if (types.length > 0) { - fBuffer.append(" throws "); //$NON-NLS-1$ - for (int i= 0; i < types.length; i++) { - if (i > 0) { - fBuffer.append(JavaElementLabelsCore.COMMA_STRING); - } - appendTypeSignatureLabel(method, types[i], flags); - } - } + appendMethodExceptions(method, flags, resolvedKey); } if (getFlag(flags, JavaElementLabelsCore.M_APP_TYPE_PARAMETERS)) { - int offset= fBuffer.length(); - if (resolvedKey != null) { - if (resolvedKey.isParameterizedMethod()) { - String[] typeArgRefs= resolvedKey.getTypeArguments(); - if (typeArgRefs.length > 0) { - fBuffer.append(' '); - appendTypeArgumentSignaturesLabel(method, typeArgRefs, flags); - } - } else { - String[] typeParameterSigs= Signature.getTypeParameters(resolvedSig); - if (typeParameterSigs.length > 0) { - fBuffer.append(' '); - appendTypeParameterSignaturesLabel(typeParameterSigs, flags); - } - } - } else if (method.exists()) { - ITypeParameter[] typeParameters= method.getTypeParameters(); - if (typeParameters.length > 0) { - fBuffer.append(' '); - appendTypeParametersLabels(typeParameters, flags); - } - } - if (getFlag(flags, JavaElementLabelsCore.COLORIZE) && offset != fBuffer.length()) { - setDecorationsStyle(offset); - } + appendMethodAppendedTypeParams(method, flags, resolvedKey, resolvedSig); } if (getFlag(flags, JavaElementLabelsCore.M_APP_RETURNTYPE) && method.exists() && !method.isConstructor()) { - int offset= fBuffer.length(); - fBuffer.append(JavaElementLabelsCore.DECL_STRING); - String returnTypeSig= resolvedSig != null ? Signature.getReturnType(resolvedSig) : method.getReturnType(); - appendTypeSignatureLabel(method, returnTypeSig, flags); - if (getFlag(flags, JavaElementLabelsCore.COLORIZE)) { - setDecorationsStyle(offset); - } + appendMethodAppendedReturnType(method, flags, resolvedSig); } // category @@ -453,12 +291,7 @@ public void appendMethodLabel(IMethod method, long flags) { // post qualification if (getFlag(flags, JavaElementLabelsCore.M_POST_QUALIFIED)) { - int offset= fBuffer.length(); - fBuffer.append(JavaElementLabelsCore.CONCAT_STRING); - appendTypeLabel(method.getDeclaringType(), JavaElementLabelsCore.T_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS)); - if (getFlag(flags, JavaElementLabelsCore.COLORIZE)) { - setQualifierStyle(offset); - } + appendMethodPostQualification(method, flags); } } catch (JavaModelException e) { @@ -470,6 +303,230 @@ public void appendMethodLabel(IMethod method, long flags) { } } + protected void appendMethodPrependedTypeParams(IMethod method, long flags, BindingKey resolvedKey, String resolvedSignature) throws JavaModelException { + if (resolvedKey != null) { + if (resolvedKey.isParameterizedMethod()) { + String[] typeArgRefs= resolvedKey.getTypeArguments(); + if (typeArgRefs.length > 0) { + appendTypeArgumentSignaturesLabel(method, typeArgRefs, flags); + fBuffer.append(' '); + } + } else { + String[] typeParameterSigs= Signature.getTypeParameters(resolvedSignature); + if (typeParameterSigs.length > 0) { + appendTypeParameterSignaturesLabel(typeParameterSigs, flags); + fBuffer.append(' '); + } + } + } else if (method.exists()) { + ITypeParameter[] typeParameters= method.getTypeParameters(); + if (typeParameters.length > 0) { + appendTypeParametersLabels(typeParameters, flags); + fBuffer.append(' '); + } + } + } + + protected void appendMethodPrependedReturnType(IMethod method, long flags, String resolvedSignature) throws JavaModelException { + String returnTypeSig= resolvedSignature != null ? Signature.getReturnType(resolvedSignature) : method.getReturnType(); + appendMethodParamTypeSignature(method, flags, returnTypeSig); + fBuffer.append(' '); + } + + protected void appendMethodName(IMethod method) { + fBuffer.append(getElementName(method)); + } + + protected void appendMethodQualification(IMethod method, long flags) { + appendTypeLabel(method.getDeclaringType(), JavaElementLabelsCore.T_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS)); + fBuffer.append('.'); + } + + protected void appendMethodConstructorTypeParams(IMethod method, long flags, BindingKey resolvedKey, String resolvedSignature) { + if (resolvedKey != null && resolvedSignature != null && resolvedKey.isParameterizedType()) { + BindingKey declaringType= resolvedKey.getDeclaringType(); + if (declaringType != null) { + String[] declaringTypeArguments= declaringType.getTypeArguments(); + appendTypeArgumentSignaturesLabel(method, declaringTypeArguments, flags); + } + } + } + + protected void appendMethodParams(IMethod method, long flags, String resolvedSignature) throws JavaModelException { + fBuffer.append('('); + if (getFlag(flags, JavaElementLabelsCore.M_PARAMETER_TYPES | JavaElementLabelsCore.M_PARAMETER_NAMES)) { + appendMethodParamsList(method, flags, resolvedSignature); + } else { + if (method.getParameterTypes().length > 0) { + fBuffer.append(JavaElementLabelsCore.ELLIPSIS_STRING); + } + } + fBuffer.append(')'); + } + + protected void appendMethodParamsList(IMethod method, long flags, String resolvedSignature) throws JavaModelException { + String[] declaredParameterTypes= method.getParameterTypes(); + String[] types= null; + int nParams= 0; + boolean renderVarargs= false; + boolean isPolymorphic= false; + if (getFlag(flags, JavaElementLabelsCore.M_PARAMETER_TYPES)) { + if (resolvedSignature != null) { + types= Signature.getParameterTypes(resolvedSignature); + } else { + types= declaredParameterTypes; + } + nParams= types.length; + renderVarargs= method.exists() && Flags.isVarargs(method.getFlags()); + if (renderVarargs + && resolvedSignature != null + && declaredParameterTypes.length == 1 + && JavaModelUtil.isPolymorphicSignature(method)) { + renderVarargs= false; + isPolymorphic= true; + } + } + String[] names= null; + if (getFlag(flags, JavaElementLabelsCore.M_PARAMETER_NAMES) && method.exists()) { + names= method.getParameterNames(); + if (isPolymorphic) { + // handled specially below + } else if (types == null) { + nParams= names.length; + } else { // types != null + if (nParams != names.length) { + if (resolvedSignature != null && types.length > names.length) { + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=99137 + nParams= names.length; + String[] typesWithoutSyntheticParams= new String[nParams]; + System.arraycopy(types, types.length - nParams, typesWithoutSyntheticParams, 0, nParams); + types= typesWithoutSyntheticParams; + } else { + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=101029 + // JavaPlugin.logErrorMessage("JavaElementLabels: Number of param types(" + nParams + ") != number of names(" + names.length + "): " + method.getElementName()); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + names= null; // no names rendered + } + } + } + } + + ILocalVariable[] annotatedParameters= null; + if (nParams > 0 && getFlag(flags, JavaElementLabelsCore.M_PARAMETER_ANNOTATIONS)) { + annotatedParameters= method.getParameters(); + } + + for (int i= 0; i < nParams; i++) { + IAnnotation[] annotations= null; + if (annotatedParameters != null && i < annotatedParameters.length) { + annotations= annotatedParameters[i].getAnnotations(); + } + String type= types == null ? null : types[i]; + String name= names == null ? null : (isPolymorphic ? names[0] + i : names[i]); + appendMethodParam(method, flags, annotations, type, name, renderVarargs, (i == nParams - 1)); + } + } + + protected void appendMethodParam(IMethod method, long flags, IAnnotation[] annotations, String paramSignature, String name, boolean renderVarargs, boolean isLast) throws JavaModelException { + if (annotations != null) { + appendAnnotationLabels(annotations, flags); + } + + if (paramSignature != null) { + if (renderVarargs && isLast) { + int newDim= Signature.getArrayCount(paramSignature) - 1; + appendMethodParamTypeSignature(method, flags, Signature.getElementType(paramSignature)); + for (int k= 0; k < newDim; k++) { + fBuffer.append('[').append(']'); + } + fBuffer.append(JavaElementLabelsCore.ELLIPSIS_STRING); + } else { + appendMethodParamTypeSignature(method, flags, paramSignature); + } + } + if (name != null) { + if (paramSignature != null) { + fBuffer.append(' '); + } + appendMethodParamName(name); + } + if (!isLast) { + fBuffer.append(JavaElementLabelsCore.COMMA_STRING); + } + } + + protected void appendMethodParamTypeSignature(IMethod method, long flags, String paramSignature) { + appendTypeSignatureLabel(method, paramSignature, flags); + } + + protected void appendMethodParamName(String name) { + fBuffer.append(name); + } + + protected void appendMethodExceptions(IMethod method, long flags, BindingKey resolvedKey) throws JavaModelException { + String[] types; + if (resolvedKey != null) { + types= resolvedKey.getThrownExceptions(); + } else { + types= method.exists() ? method.getExceptionTypes() : new String[0]; + } + if (types.length > 0) { + fBuffer.append(" throws "); //$NON-NLS-1$ + for (int i= 0; i < types.length; i++) { + if (i > 0) { + fBuffer.append(JavaElementLabelsCore.COMMA_STRING); + } + appendTypeSignatureLabel(method, types[i], flags); + } + } + } + + protected void appendMethodAppendedTypeParams(IMethod method, long flags, BindingKey resolvedKey, String resolvedSignature) throws JavaModelException { + int offset= fBuffer.length(); + if (resolvedKey != null) { + if (resolvedKey.isParameterizedMethod()) { + String[] typeArgRefs= resolvedKey.getTypeArguments(); + if (typeArgRefs.length > 0) { + fBuffer.append(' '); + appendTypeArgumentSignaturesLabel(method, typeArgRefs, flags); + } + } else { + String[] typeParameterSigs= Signature.getTypeParameters(resolvedSignature); + if (typeParameterSigs.length > 0) { + fBuffer.append(' '); + appendTypeParameterSignaturesLabel(typeParameterSigs, flags); + } + } + } else if (method.exists()) { + ITypeParameter[] typeParameters= method.getTypeParameters(); + if (typeParameters.length > 0) { + fBuffer.append(' '); + appendTypeParametersLabels(typeParameters, flags); + } + } + if (getFlag(flags, JavaElementLabelsCore.COLORIZE) && offset != fBuffer.length()) { + setDecorationsStyle(offset); + } + } + + protected void appendMethodAppendedReturnType(IMethod method, long flags, String resolvedSignature) throws JavaModelException { + int offset= fBuffer.length(); + fBuffer.append(JavaElementLabelsCore.DECL_STRING); + String returnTypeSig= resolvedSignature != null ? Signature.getReturnType(resolvedSignature) : method.getReturnType(); + appendMethodParamTypeSignature(method, flags, returnTypeSig); + if (getFlag(flags, JavaElementLabelsCore.COLORIZE)) { + setDecorationsStyle(offset); + } + } + + protected void appendMethodPostQualification(IMethod method, long flags) { + int offset= fBuffer.length(); + fBuffer.append(JavaElementLabelsCore.CONCAT_STRING); + appendTypeLabel(method.getDeclaringType(), JavaElementLabelsCore.T_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS)); + if (getFlag(flags, JavaElementLabelsCore.COLORIZE)) { + setQualifierStyle(offset); + } + } + @SuppressWarnings("unused") protected void appendCategoryLabel(IMember member, long flags) throws JavaModelException { // core does not implement this @@ -558,16 +615,16 @@ public void appendAnnotationValue(IAnnotation annotation, Object value, int valu * @param flags flags with render options * @throws JavaModelException ... */ - private void appendTypeParametersLabels(ITypeParameter[] typeParameters, long flags) throws JavaModelException { + protected void appendTypeParametersLabels(ITypeParameter[] typeParameters, long flags) throws JavaModelException { if (typeParameters.length > 0) { - fBuffer.append(getLT()); + appendLT(); for (int i = 0; i < typeParameters.length; i++) { if (i > 0) { fBuffer.append(JavaElementLabelsCore.COMMA_STRING); } appendTypeParameterWithBounds(typeParameters[i], flags); } - fBuffer.append(getGT()); + appendGT(); } } @@ -685,7 +742,7 @@ public void appendTypeParameterLabel(ITypeParameter typeParameter, long flags) { } } - private void appendTypeParameterWithBounds(ITypeParameter typeParameter, long flags) throws JavaModelException { + protected void appendTypeParameterWithBounds(ITypeParameter typeParameter, long flags) throws JavaModelException { fBuffer.append(getElementName(typeParameter)); if (typeParameter.exists()) { @@ -756,11 +813,9 @@ protected void appendTypeSignatureLabel(IJavaElement enclosingElement, String ty fBuffer.append('?'); } else { if (ch == Signature.C_EXTENDS) { - fBuffer.append("? extends "); //$NON-NLS-1$ - appendTypeSignatureLabel(enclosingElement, typeSig.substring(1), flags); + appendWildcardTypeSignature("? extends ", enclosingElement, typeSig.substring(1), flags); //$NON-NLS-1$ } else if (ch == Signature.C_SUPER) { - fBuffer.append("? super "); //$NON-NLS-1$ - appendTypeSignatureLabel(enclosingElement, typeSig.substring(1), flags); + appendWildcardTypeSignature("? super ", enclosingElement, typeSig.substring(1), flags); //$NON-NLS-1$ } } break; @@ -780,6 +835,11 @@ protected void appendTypeSignatureLabel(IJavaElement enclosingElement, String ty } } + protected void appendWildcardTypeSignature(String prefix, IJavaElement enclosingElement, String typeSignature, long flags) { + fBuffer.append(prefix); + appendTypeSignatureLabel(enclosingElement, typeSignature, flags); + } + private void appendTypeBoundsSignaturesLabel(IJavaElement enclosingElement, String[] typeArgsSig, long flags, boolean isIntersection) { for (int i = 0; i < typeArgsSig.length; i++) { if (i > 0) { @@ -818,16 +878,16 @@ protected String getMemberName(IJavaElement enclosingElement, String typeName, S return memberName; } - private void appendTypeArgumentSignaturesLabel(IJavaElement enclosingElement, String[] typeArgsSig, long flags) { + protected void appendTypeArgumentSignaturesLabel(IJavaElement enclosingElement, String[] typeArgsSig, long flags) { if (typeArgsSig.length > 0) { - fBuffer.append(getLT()); + appendLT(); for (int i = 0; i < typeArgsSig.length; i++) { if (i > 0) { fBuffer.append(JavaElementLabelsCore.COMMA_STRING); } appendTypeSignatureLabel(enclosingElement, typeArgsSig[i], flags); } - fBuffer.append(getGT()); + appendGT(); } } @@ -837,19 +897,26 @@ private void appendTypeArgumentSignaturesLabel(IJavaElement enclosingElement, St * @param typeParamSigs the type parameter signature * @param flags flags with render options */ - private void appendTypeParameterSignaturesLabel(String[] typeParamSigs, long flags) { + protected void appendTypeParameterSignaturesLabel(String[] typeParamSigs, long flags) { if (typeParamSigs.length > 0) { - fBuffer.append(getLT()); + appendLT(); for (int i = 0; i < typeParamSigs.length; i++) { if (i > 0) { fBuffer.append(JavaElementLabelsCore.COMMA_STRING); } fBuffer.append(Signature.getTypeVariable(typeParamSigs[i])); } - fBuffer.append(getGT()); + appendGT(); } } + /** + * Appends the string for rendering the '<' character. + */ + protected void appendLT() { + fBuffer.append(getLT()); + } + /** * Returns the string for rendering the '<' character. * @@ -859,6 +926,13 @@ protected String getLT() { return "<"; //$NON-NLS-1$ } + /** + * Appends the string for rendering the '>' character. + */ + protected void appendGT() { + fBuffer.append(getGT()); + } + /** * Returns the string for rendering the '>' character. * diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/core/manipulation/JavaElementLabelsCore.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/core/manipulation/JavaElementLabelsCore.java index 245e2e2d0a9..ba7acb2bdc6 100644 --- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/core/manipulation/JavaElementLabelsCore.java +++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/core/manipulation/JavaElementLabelsCore.java @@ -89,7 +89,7 @@ public final class JavaElementLabelsCore { public final static long M_APP_RETURNTYPE= 1L << 5; /** - * Method names contain return type (appended) + * Method names contain return type (prepended) * e.g. int foo */ public final static long M_PRE_RETURNTYPE= 1L << 6; diff --git a/org.eclipse.jdt.ui/JavadocHoverStyleSheet.css b/org.eclipse.jdt.ui/JavadocHoverStyleSheet.css index ff82b2b6f19..b824d0960b5 100644 --- a/org.eclipse.jdt.ui/JavadocHoverStyleSheet.css +++ b/org.eclipse.jdt.ui/JavadocHoverStyleSheet.css @@ -12,7 +12,7 @@ h4 { margin-top: 2em; margin-bottom: 0.3em; } h5 { margin-top: 0px; margin-bottom: 0px; } p { margin-top: 1em; margin-bottom: 1em; } pre { margin-left: 0.6em; } -ul { margin-top: 0px; margin-bottom: 1em; margin-left: 1em; padding-left: 1em;} +ul { margin-top: 0px; margin-bottom: 1em; margin-left: 1em; padding-left: 1em; } li { margin-top: 0px; margin-bottom: 0px; } li p { margin-top: 0px; margin-bottom: 0px; } ol { margin-top: 0px; margin-bottom: 1em; margin-left: 1em; padding-left: 1em; } @@ -33,6 +33,53 @@ em { font-style: italic; } var { font-style: italic; } th { font-weight: bold; } +/* Styling enhancements */ +div#previewWatermark { background-color: white; display: none; position: fixed; bottom: 2px; right: 2px; border: 2px solid black; color: black; border-radius: 5px; padding: 4px 4px 4px 4px; } +input#previewSwitch:checked ~ .styleSwitchParent div#previewWatermark { display: inline; } + +div#previewFormatting, +div#previewWrapping, +div#previewTypeParamsLevelsColoring, +div#previewTypeParamsRefsColoring { display: inline; opacity: 0.3; margin-left: 3px; margin-right: 3px; font-weight: normal; } + +input#previewSwitch:checked ~input#formattingSwitch:checked ~ .styleSwitchParent div#previewFormatting, +input#previewSwitch:checked ~input#wrappingSwitch:checked ~ .styleSwitchParent div#previewWrapping, +input#previewSwitch:checked ~input#typeParamsLevelsColoringSwitch:checked ~ .styleSwitchParent div#previewTypeParamsLevelsColoring, +input#previewSwitch:checked ~input#typeParamsRefsColoringSwitch:checked ~ .styleSwitchParent div#previewTypeParamsRefsColoring { opacity: 1; color: red; } + +input#previewSwitch, +input#formattingSwitch, +input#wrappingSwitch, +input#typeParamsRefsColoringSwitch, +input#typeParamsLevelsColoringSwitch { display: none; } + +input#wrappingSwitch:checked ~ .styleSwitchParent .methodPrependTypeParams { display: block} +input#wrappingSwitch:checked ~ .styleSwitchParent .methodParam { display: block; text-indent: 1em; } + +input#formattingSwitch:checked ~ .styleSwitchParent .typeParamsLevelNo1 .typeBracketsStart:before, +input#formattingSwitch:checked ~ .styleSwitchParent .methodParams:before { content: " "; } + +input#formattingSwitch:checked ~ .styleSwitchParent .methodQualifier { opacity: 0.6; } +input#formattingSwitch:checked ~ .styleSwitchParent .methodParamName { font-style: italic; } +input#formattingSwitch:checked ~ .styleSwitchParent .methodReturn { font-style: italic; } +input#formattingSwitch:checked ~ .styleSwitchParent .methodName { font-weight: 800; } + +input#formattingSwitch:checked ~ .styleSwitchParent .typeParamsLevel { font-weight: 200; } +input#formattingSwitch:checked ~ .styleSwitchParent .typeBrackets { font-weight: 800; } + +/* Start of dynamic type parameters levels styling section (do not edit this line) */ +input#typeParamsLevelsColoringSwitch:checked ~ .styleSwitchParent .typeParamsLevelNo-INDEX-, +input#typeParamsLevelsColoringSwitch:checked ~ .styleSwitchParent .typeParamsLevelNo-INDEX- a.header +{ color: -COLOR-; } +/* End of dynamic type parameters levels styling section (do not edit this line) */ + +/* Start of dynamic type parameters references styling section (do not edit this line) */ +input#typeParamsRefsColoringSwitch:checked ~ .styleSwitchParent .typeParamsReferenceNo-INDEX-, +input#typeParamsRefsColoringSwitch:checked ~ .styleSwitchParent .typeParamsReferenceNo-INDEX- a.header +{ color: -COLOR-; } +/* End of dynamic type parameters references styling section (do not edit this line) */ + + /* Workarounds for new Javadoc stylesheet (1.7) */ ul.blockList li.blockList, ul.blockListLast li.blockList { list-style:none; diff --git a/org.eclipse.jdt.ui/JavadocViewStyleSheet.css b/org.eclipse.jdt.ui/JavadocViewStyleSheet.css index 7cee7b77c76..7ad9db551aa 100644 --- a/org.eclipse.jdt.ui/JavadocViewStyleSheet.css +++ b/org.eclipse.jdt.ui/JavadocViewStyleSheet.css @@ -36,6 +36,53 @@ em { font-style: italic; } var { font-style: italic; } th { font-weight: bold; } +/* Styling enhancements */ +div#previewWatermark { background-color: white; display: none; position: fixed; bottom: 2px; right: 2px; border: 2px solid black; color: black; border-radius: 5px; padding: 4px 4px 4px 4px; } +input#previewSwitch:checked ~ .styleSwitchParent div#previewWatermark { display: inline; } + +div#previewFormatting, +div#previewWrapping, +div#previewTypeParamsLevelsColoring, +div#previewTypeParamsRefsColoring { display: inline; opacity: 0.3; margin-left: 3px; margin-right: 3px; font-weight: normal; } + +input#previewSwitch:checked ~input#formattingSwitch:checked ~ .styleSwitchParent div#previewFormatting, +input#previewSwitch:checked ~input#wrappingSwitch:checked ~ .styleSwitchParent div#previewWrapping, +input#previewSwitch:checked ~input#typeParamsLevelsColoringSwitch:checked ~ .styleSwitchParent div#previewTypeParamsLevelsColoring, +input#previewSwitch:checked ~input#typeParamsRefsColoringSwitch:checked ~ .styleSwitchParent div#previewTypeParamsRefsColoring { opacity: 1; color: red; } + +input#previewSwitch, +input#formattingSwitch, +input#wrappingSwitch, +input#typeParamsRefsColoringSwitch, +input#typeParamsLevelsColoringSwitch { display: none; } + +input#wrappingSwitch:checked ~ .styleSwitchParent .methodPrependTypeParams { display: block} +input#wrappingSwitch:checked ~ .styleSwitchParent .methodParam { display: block; text-indent: 1em; } + +input#formattingSwitch:checked ~ .styleSwitchParent .typeParamsLevelNo1 .typeBracketsStart:before, +input#formattingSwitch:checked ~ .styleSwitchParent .methodParams:before { content: " "; } + +input#formattingSwitch:checked ~ .styleSwitchParent .methodQualifier { opacity: 0.6; } +input#formattingSwitch:checked ~ .styleSwitchParent .methodParamName { font-style: italic; } +input#formattingSwitch:checked ~ .styleSwitchParent .methodReturn { font-style: italic; } +input#formattingSwitch:checked ~ .styleSwitchParent .methodName { font-weight: 800; } + +input#formattingSwitch:checked ~ .styleSwitchParent .typeParamsLevel { font-weight: 200; } +input#formattingSwitch:checked ~ .styleSwitchParent .typeBrackets { font-weight: 800; } + +/* Start of dynamic type parameters levels styling section (do not edit this line) */ +input#typeParamsLevelsColoringSwitch:checked ~ .styleSwitchParent .typeParamsLevelNo-INDEX-, +input#typeParamsLevelsColoringSwitch:checked ~ .styleSwitchParent .typeParamsLevelNo-INDEX- a.header +{ color: -COLOR-; } +/* End of dynamic type parameters levels styling section (do not edit this line) */ + +/* Start of dynamic type parameters references styling section (do not edit this line) */ +input#typeParamsRefsColoringSwitch:checked ~ .styleSwitchParent .typeParamsReferenceNo-INDEX-, +input#typeParamsRefsColoringSwitch:checked ~ .styleSwitchParent .typeParamsReferenceNo-INDEX- a.header +{ color: -COLOR-; } +/* End of dynamic type parameters references styling section (do not edit this line) */ + + /* Workarounds for new Javadoc stylesheet (1.7) */ ul.blockList li.blockList, ul.blockListLast li.blockList { list-style:none; diff --git a/org.eclipse.jdt.ui/css/e4-dark_jdt_syntaxhighlighting.css b/org.eclipse.jdt.ui/css/e4-dark_jdt_syntaxhighlighting.css index 30b226b7f77..fbd57ccb2c5 100644 --- a/org.eclipse.jdt.ui/css/e4-dark_jdt_syntaxhighlighting.css +++ b/org.eclipse.jdt.ui/css/e4-dark_jdt_syntaxhighlighting.css @@ -97,6 +97,7 @@ IEclipsePreferences#org-eclipse-jdt-ui:org-eclipse-jdt-ui { /* pseudo attribute 'semanticHighlighting.restrictedKeywords.color=204,108,29' 'semanticHighlighting.restrictedKeywords.bold=false' 'sourceHoverBackgroundColor=68,68,68' + 'javadocElementsStyling.darkModeDefaultColors=true' } diff --git a/org.eclipse.jdt.ui/icons/full/dlcl16/wrap_all.png b/org.eclipse.jdt.ui/icons/full/dlcl16/wrap_all.png new file mode 100644 index 0000000000000000000000000000000000000000..041c4f02c038b53624358dbcbdf8605b22b12b03 GIT binary patch literal 360 zcmV-u0hj)XP)?_WI~?j?DzZ81d@m)A&I7GST2`rHXBx} z6;0FNwXvD_MNt%7uh+nVq+cB*W`-oHs-mi@LBQBBvu_6gbzReS9cD%x$2jMDz&<95 zq98#m2@kSuTc*<~^ZA@KO=;V<*L)$rH?yy6^ddl(W!&%g-a(dSqiqaYD9e(vEdR_Q zfCz9tp92T$^_uN=J4oa?m>IDbCDC;qv)PQrVnLdw+-|qv&R?zf3|>X&T;RYt*9S&X z6eRILgBJ2Urzna+|F1>7{lQCo2kjlepL!yRV literal 0 HcmV?d00001 diff --git a/org.eclipse.jdt.ui/icons/full/dlcl16/wrap_all@2x.png b/org.eclipse.jdt.ui/icons/full/dlcl16/wrap_all@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a77cea620e6d5577ff51209ea2d3f3dd4e731eed GIT binary patch literal 703 zcmV;w0zmzVP)AwVav$}?;@hp1fYBrmw*X!u_`&h5nTfN`!M?4vCin>OG5D-Gp z>-A756wFOPeKr~m0D#-=hQr|i$8lSu<#LJ1WP+!sC*!ir&M-Zfl{e- lGLsn%nu{x7L@;LZ#$VcU2!9U=Mw|cu002ovPDHLkV1k6@K@jpU0=k?+^&H+qc{LL8C8V2yK|3lfHWG7$QaX_ot=$D zj!dX3o12^a-uo%%+yU@#a&q!m)!uIyIZ@1geRrP@x2(m0Y22~xn4~;Pk#bVL+ zdOe(TjE|2S5vc-UFkq!=%CLiD!%$^sX9sI70C}GO;TZlMsj9x-+uO@U1Y-={Zr7{o z8z2pi8tAWfyM40jFF@ls_O)8=sdMghtJS&RkgRYw3Gt`0K>$; zMY&v7AOx(491QZuW{@PwOp+wCC&kTXQ&jbewf0L81YcG4Mzh%zhHWQFG77x!=dLd= zFCPrVz||lKUd_zROap9fZT-x$?5?VQFnzHAH?6gogCMvJR99D51;BfMMnqngN~P&A z3<<-KQmJ%KL|%CBrv_h`5|NcCibkR+3Pj|Qh?M#}7w>)0FNXJ?{yFsC2R2C(3#^Qe zj$SVm3SzCrdtW>}JT!B2bDt(ACS0E9YrDI<*T%-iqCC&p*x1<3vg|(a*{JGSV8J=p z?Q}Z(ola-pIoAbl0TY!e^@0_P000000NkvXXu0mjf>UTZ| literal 0 HcmV?d00001 diff --git a/org.eclipse.jdt.ui/icons/full/dview16/types.png b/org.eclipse.jdt.ui/icons/full/dview16/types.png new file mode 100644 index 0000000000000000000000000000000000000000..e06d763acb7dfb81130fa21268957e00e54bc273 GIT binary patch literal 628 zcmV-)0*n2LP)tG{ATPWz6F5Gj^{c$evk5I#)R4UE7uKQFel_sL? zlhIg0Dze@ z#uzl4&0l*f1!nd{($cVdftq^6<9{<#KseYyH9)lMs>5 zBJ$|83)O0M#&MjAh@=60v(}at78X9J^ZZ?3U%x{{OV-+y>$-JjzE><3H_!cp)2LRf zcRkO0Gc`389~l|J?(Qz?^?DdZ(T!rU*f`Clvjm7}IiJtR6Nvd)d-%SOAPDsRe*bW6q5wcCg>BoI&1QPjG(1U?$sYg!2qADB2VofMZQK5s=<%ki zsxVDc-)uIINs>(2cd^}WA~y{^ZEP=0H`d>TnKS3NfJLC4o_uSLesPnfDi!4?}Kq1 mUoys?DW%OY4B>jce)$4teQD05S?7BI0000c{F00047NklAs=6bJDCod-dO+ap9L2M0euw?aU4>!=^VAwz^f5DF~>LYIbaonrR}>f)l{Rw(%p z$*9I5gIhtwq>B;LByDbb_wN08ynC=LYhYQ{03{Cv0N?lD%OaWtWzhxH>-AFT0yIs7 zBuW3G3*ek%JRU<7#Xk|fzHrVlnM}}VGzt;@ov77n&~+WHRx20LytaZMKvh+=+wBaZ zMZ*b#07X&IY&KJf}SY1$LVxJs>5Qj zz_AP)n z*L4wwA$q+YjiTsgx7*#N0Aoy?PN&W}egyD6o6TZbmXo^sBp`XeG#ZUeLdb(8Nm#Gf y0Pt$twpzG{ - [implementation] Streams not being closed in Javadoc views - https://bugs.eclipse.org/bugs/show_bug.cgi?id=214854 * Benjamin Muskalla - [javadoc view] NPE on enumerations - https://bugs.eclipse.org/bugs/show_bug.cgi?id=223586 * Stephan Herrmann - Contribution for Bug 403917 - [1.8] Render TYPE_USE annotations in Javadoc hover/view + * Jozef Tomek - add styling enhancements (issue 1073) *******************************************************************************/ package org.eclipse.jdt.internal.ui.infoviews; @@ -50,6 +51,7 @@ import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.Separator; +import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.dialogs.MessageDialogWithToggle; import org.eclipse.jface.internal.text.html.BrowserInput; import org.eclipse.jface.internal.text.html.HTMLPrinter; @@ -153,6 +155,9 @@ import org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2; import org.eclipse.jdt.internal.ui.viewsupport.BindingLinkedLabelComposer; import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; +import org.eclipse.jdt.internal.ui.viewsupport.MouseListeningToolItemsConfigurer; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; +import org.eclipse.jdt.internal.ui.viewsupport.javadoc.SignatureStylingMenuToolbarAction; /** @@ -353,6 +358,13 @@ protected boolean canEnableFor(IStructuredSelection selection) { // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=73558 private static final boolean WARNING_DIALOG_ENABLED= false; + /** + * Preference keys prefix for all preferences related to styling of HTML content for element labels + * inside Javadoc view. Postfixes are declared as constants in {@link JavaElementLinks}. + * @see JavaElementLinks + */ + private static final String HTML_STYLING_PREFERENCE_KEY_PREFIX= "javadocElementsStyling.javadocView."; //$NON-NLS-1$ + /** The HTML widget. */ private Browser fBrowser; /** The text widget. */ @@ -542,6 +554,10 @@ public void setSelection(ISelection selection) { } } + public static void initDefaults(IPreferenceStore store) { + JavaElementLinks.initDefaultPreferences(store, HTML_STYLING_PREFERENCE_KEY_PREFIX); + } + @Override protected void internalCreatePartControl(Composite parent) { try { @@ -621,6 +637,7 @@ private void listenForFontChanges() { } private static void initStyleSheet() { + fgStyleSheetLoaded= false; //TODO styling: remove if (fgStyleSheetLoaded) return; fgStyleSheetLoaded= true; @@ -672,6 +689,23 @@ protected void fillToolBar(IToolBarManager tbm) { tbm.add(fForthAction); tbm.add(new Separator()); + if (fIsUsingBrowserWidget) { + BrowserTextAccessor browserAccessor= new BrowserTextAccessor(fBrowser); + // toolbar widget is being re-created later so we need to do our setup then + var stylingMenuAction= new SignatureStylingMenuToolbarAction(fBrowser.getParent().getShell(), browserAccessor, HTML_STYLING_PREFERENCE_KEY_PREFIX, () -> fOriginalInput) { + // we take advantage of this method being called after toolbar item creation (in ActionContributionItem.fill()) which happens when whole toolbar is being re-created to be displayed + @Override + public void addPropertyChangeListener(IPropertyChangeListener listener) { + super.addPropertyChangeListener(listener); + setupMenuReopen(tbm); + MouseListeningToolItemsConfigurer.registerForToolBarManager((ToolBarManager) tbm, browserAccessor::applyChanges); + } + }; + stylingMenuAction.setId("JavadocView.SignatureStylingMenuToolbarAction"); //$NON-NLS-1$ + tbm.add(stylingMenuAction); + tbm.add(new Separator()); + } + super.fillToolBar(tbm); tbm.add(fOpenBrowserAction); } @@ -1065,7 +1099,7 @@ private String getJavadocHtml(IJavaElement[] result, IWorkbenchPart activePart, if (buffer.length() == 0) return null; - HTMLPrinter.insertPageProlog(buffer, 0, fForegroundColorRGB, fBackgroundColorRGB, fgStyleSheet); + HTMLPrinter.insertPageProlog(buffer, 0, fForegroundColorRGB, fBackgroundColorRGB, JavaElementLinks.modifyCssStyleSheet(fgStyleSheet, buffer)); if (base != null) { int endHeadIdx= buffer.indexOf(""); //$NON-NLS-1$ buffer.insert(endHeadIdx, "\n\n"); //$NON-NLS-1$ //$NON-NLS-2$ @@ -1093,9 +1127,9 @@ private String getInfoText(IJavaElement member, String constantValue, String def // setting haveSource to false lets the JavadocView *always* show qualified type names, // would need to track the source of our input to distinguish classfile/compilationUnit: boolean haveSource= false; - new BindingLinkedLabelComposer(member, label, haveSource).appendBindingLabel(binding, flags); + new BindingLinkedLabelComposer(member, label, haveSource, HTML_STYLING_PREFERENCE_KEY_PREFIX).appendBindingLabel(binding, flags); } else { - label= new StringBuffer(JavaElementLinks.getElementLabel(member, flags)); + label= new StringBuffer(JavaElementLinks.getElementLabel(member, flags, false, HTML_STYLING_PREFERENCE_KEY_PREFIX)); } if (member.getElementType() == IJavaElement.FIELD && constantValue != null) { label.append(constantValue); diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/hover/JavadocHover.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/hover/JavadocHover.java index 75c5d6b1368..d93367d8b67 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/hover/JavadocHover.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/hover/JavadocHover.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2019 IBM Corporation and others. + * Copyright (c) 2000, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -12,6 +12,7 @@ * IBM Corporation - initial API and implementation * Genady Beryozkin - [hovering] tooltip for constant string does not show constant value - https://bugs.eclipse.org/bugs/show_bug.cgi?id=85382 * Stephan Herrmann - Contribution for Bug 403917 - [1.8] Render TYPE_USE annotations in Javadoc hover/view + * Jozef Tomek - add styling enhancements (issue 1073) *******************************************************************************/ package org.eclipse.jdt.internal.ui.text.java.hover; @@ -22,6 +23,9 @@ import java.io.StringReader; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import org.osgi.framework.Bundle; @@ -31,6 +35,8 @@ import org.eclipse.swt.graphics.Drawable; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; @@ -41,13 +47,16 @@ import org.eclipse.core.resources.IFile; import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.Separator; import org.eclipse.jface.action.ToolBarManager; import org.eclipse.jface.internal.text.html.BrowserInformationControl; import org.eclipse.jface.internal.text.html.BrowserInformationControlInput; import org.eclipse.jface.internal.text.html.BrowserInput; import org.eclipse.jface.internal.text.html.HTMLPrinter; import org.eclipse.jface.internal.text.html.HTMLTextPresenter; +import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ColorRegistry; +import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; @@ -123,6 +132,7 @@ import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.JavaPluginImages; +import org.eclipse.jdt.internal.ui.actions.ActionMessages; import org.eclipse.jdt.internal.ui.actions.OpenBrowserUtil; import org.eclipse.jdt.internal.ui.actions.SimpleSelectionProvider; import org.eclipse.jdt.internal.ui.infoviews.JavadocView; @@ -131,6 +141,9 @@ import org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2; import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLabelComposer; import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; +import org.eclipse.jdt.internal.ui.viewsupport.MouseListeningToolItemsConfigurer; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; +import org.eclipse.jdt.internal.ui.viewsupport.javadoc.SignatureStylingMenuToolbarAction; /** @@ -142,6 +155,14 @@ public class JavadocHover extends AbstractJavaEditorTextHover { public static final String CONSTANT_VALUE_SEPARATOR= " : "; //$NON-NLS-1$ + /** + * Preference keys prefix for all preferences related to styling of HTML content for element labels + * inside Javadoc hover. Postfixes are declared as constants in {@link JavaElementLinks}. + * + * @see JavaElementLinks + */ + private static final String HTML_STYLING_PREFERENCE_KEY_PREFIX= "javadocElementsStyling.javadocHover."; //$NON-NLS-1$ + public static class FallbackInformationPresenter extends HTMLTextPresenter { public FallbackInformationPresenter() { super(false); @@ -358,6 +379,27 @@ public IInformationControl doCreateInformationControl(Shell parent) { tbm.add(openAttachedJavadocAction); } + var toolbarComposite= tbm.getControl().getParent(); + GridLayout layout= new GridLayout(4, false); + layout.marginHeight= 0; + layout.marginWidth= 0; + layout.horizontalSpacing= 0; + layout.verticalSpacing= 0; + toolbarComposite.setLayout(layout); + + ToolBarManager tbmSecondary= new ToolBarManager(SWT.FLAT); + tbmSecondary.createControl(toolbarComposite).setLayoutData(new GridData(SWT.END, SWT.BEGINNING, false, false)); + BrowserTextAccessor browserAccessor= new BrowserTextAccessor(iControl); + var stylingMenuAction= new SignatureStylingMenuToolbarAction(parent, browserAccessor, HTML_STYLING_PREFERENCE_KEY_PREFIX, + () -> iControl.getInput() == null ? null : iControl.getInput().getHtml()); + tbmSecondary.add(stylingMenuAction); + tbmSecondary.add(new Separator()); + tbmSecondary.add(new OpenInSystemBrowserAction(iControl)); + tbmSecondary.update(true); + stylingMenuAction.setupMenuReopen(tbmSecondary); + MouseListeningToolItemsConfigurer.registerForToolBarManager(tbmSecondary, browserAccessor::applyChanges); + tbmSecondary.getControl().moveAbove(toolbarComposite.getChildren()[2]); // move to be before resizeCanvas + IInputChangedListener inputChangeListener= newInput -> { backAction.update(); forwardAction.update(); @@ -385,6 +427,40 @@ public IInformationControl doCreateInformationControl(Shell parent) { } } + // TODO styling: remove + private static final class OpenInSystemBrowserAction extends Action { + static final ImageDescriptor DESC_VIEW_SOURCE = JavaPluginImages.createImageDescriptor( + JavaPlugin.getDefault().getBundle(), + JavaPluginImages.ICONS_PATH.append("eview16").append("source.png"), //$NON-NLS-1$//$NON-NLS-2$ + true); + private final BrowserInformationControl fInfoControl; + + public OpenInSystemBrowserAction(BrowserInformationControl infoControl) { + super(ActionMessages.OpenExternalJavadocAction_description); + fInfoControl= infoControl; + setId(OpenInSystemBrowserAction.class.getSimpleName()); + setDescription(ActionMessages.OpenExternalJavadocAction_description); + setToolTipText(ActionMessages.OpenExternalJavadocAction_tooltip); + setImageDescriptor(DESC_VIEW_SOURCE); + } + + @Override + public void run() { + JavadocBrowserInformationControlInput infoInput= (JavadocBrowserInformationControlInput) fInfoControl.getInput(); + debug_openInBrowser(infoInput, fInfoControl.getShell().getDisplay()); + } + } + + // TODO styling: remove + private static void debug_openInBrowser(JavadocBrowserInformationControlInput infoInput, Display display) { + try { + Path filePath = Files.createTempFile("eclipse-javadoc-generated", ".html"); //$NON-NLS-1$ //$NON-NLS-2$ + Files.writeString(filePath, infoInput.getHtml(), StandardCharsets.UTF_8); + OpenBrowserUtil.openExternal(filePath.toUri().toURL(), display); + } catch (Exception e) { + e.printStackTrace(); + } + } /** * Hover control creator. @@ -532,6 +608,10 @@ public IInformationControlCreator getInformationPresenterControlCreator() { return fPresenterControlCreator; } + public static void initDefaults(IPreferenceStore store) { + JavaElementLinks.initDefaultPreferences(store, HTML_STYLING_PREFERENCE_KEY_PREFIX); + } + private IWorkbenchSite getSite() { IEditorPart editor= getEditor(); if (editor == null) { @@ -785,7 +865,7 @@ public static JavadocBrowserInformationControlInput getHoverInfo(IJavaElement[] RGB fgRGB = registry.getRGB("org.eclipse.jdt.ui.Javadoc.foregroundColor"); //$NON-NLS-1$ RGB bgRGB= registry.getRGB("org.eclipse.jdt.ui.Javadoc.backgroundColor"); //$NON-NLS-1$ - HTMLPrinter.insertPageProlog(buffer, 0, fgRGB, bgRGB, JavadocHover.getStyleSheet()); + HTMLPrinter.insertPageProlog(buffer, 0, fgRGB, bgRGB, JavadocHover.getStyleSheet(buffer)); if (base != null) { int endHeadIdx= buffer.indexOf(""); //$NON-NLS-1$ buffer.insert(endHeadIdx, "\n\n"); //$NON-NLS-1$ //$NON-NLS-2$ @@ -806,9 +886,9 @@ private static String getInfoText(IJavaElement element, ITypeRoot editorInputEle StringBuilder label; if (binding != null) { - label= new StringBuilder(JavaElementLinks.getBindingLabel(binding, element, flags, haveSource)); + label= new StringBuilder(JavaElementLinks.getBindingLabel(binding, element, flags, haveSource, HTML_STYLING_PREFERENCE_KEY_PREFIX)); } else { - label= new StringBuilder(JavaElementLinks.getElementLabel(element, flags)); + label= new StringBuilder(JavaElementLinks.getElementLabel(element, flags, false, HTML_STYLING_PREFERENCE_KEY_PREFIX)); } if (element.getElementType() == IJavaElement.FIELD) { @@ -1027,10 +1107,13 @@ private static String formatWithHexValue(Object constantValue, String hexValue) /** * Returns the Javadoc hover style sheet with the current Javadoc font from the preferences. + * + * @param content html content which will use the style sheet * @return the updated style sheet * @since 3.4 */ - private static String getStyleSheet() { + private static String getStyleSheet(StringBuilder content) { + fgStyleSheet= null; //TODO styling: remove if (fgStyleSheet == null) { fgStyleSheet= loadStyleSheet("/JavadocHoverStyleSheet.css"); //$NON-NLS-1$ } @@ -1039,6 +1122,7 @@ private static String getStyleSheet() { FontData fontData= JFaceResources.getFontRegistry().getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0]; css= HTMLPrinter.convertTopLevelFont(css, fontData); } + css= JavaElementLinks.modifyCssStyleSheet(css, content); return css; } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/BindingLinkedLabelComposer.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/BindingLinkedLabelComposer.java index 62c53545739..663ed80a16e 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/BindingLinkedLabelComposer.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/BindingLinkedLabelComposer.java @@ -63,7 +63,11 @@ public class BindingLinkedLabelComposer extends JavaElementLinkedLabelComposer { private boolean fIsFromSource; public BindingLinkedLabelComposer(IJavaElement enclosingElement, StringBuffer buffer, boolean isFromSource) { - super(enclosingElement, buffer); + this(enclosingElement, buffer, isFromSource, null); + } + + public BindingLinkedLabelComposer(IJavaElement enclosingElement, StringBuffer buffer, boolean isFromSource, String stylingPreferenceKeysPrefix) { + super(enclosingElement, buffer, stylingPreferenceKeysPrefix); fEnclosingElement= enclosingElement; fIsFromSource= isFromSource; } @@ -437,14 +441,14 @@ else if (getFlag(flags, JavaElementLabels.T_CONTAINER_QUALIFIED)) } } else if (typeBinding.isParameterizedType()) { fBuffer.append(getTypeLink(typeBinding.getTypeDeclaration(), flags)); - fBuffer.append(getLT()); + appendLT(); ITypeBinding[] typeArguments= typeBinding.getTypeArguments(); for (int i= 0; i < typeArguments.length; i++) { if (i > 0) fBuffer.append(JavaElementLabels.COMMA_STRING); appendTypeBindingLabel(typeArguments[i], typeRefFlags); } - fBuffer.append(getGT()); + appendGT(); } else if (typeBinding.isTypeVariable()) { appendNameLink(typeBinding, typeBinding); if (getFlag(flags, TP_BOUNDS)) { @@ -506,13 +510,13 @@ private void appendTypeArgumentsBindingLabel(ITypeBinding[] parameters, String s if (parameters.length > 0) { if (separator != null) fBuffer.append(separator); - fBuffer.append(getLT()); + appendLT(); for (int i = 0; i < parameters.length; i++) { if (i > 0) fBuffer.append(JavaElementLabels.COMMA_STRING); appendTypeBindingLabel(parameters[i], flags); } - fBuffer.append(getGT()); + appendGT(); } } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/JavaElementLinks.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/JavaElementLinks.java index c0c2273a37d..36f0fdab2a6 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/JavaElementLinks.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/JavaElementLinks.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2022 IBM Corporation and others. + * Copyright (c) 2008, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -11,6 +11,7 @@ * Contributors: * IBM Corporation - initial API and implementation * Stephan Herrmann - Contribution for Bug 403917 - [1.8] Render TYPE_USE annotations in Javadoc hover/view + * Jozef Tomek - add styling enhancements (issue 1073) *******************************************************************************/ package org.eclipse.jdt.internal.ui.viewsupport; @@ -18,18 +19,38 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.LocationAdapter; import org.eclipse.swt.browser.LocationEvent; import org.eclipse.swt.browser.LocationListener; +import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Display; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; + +import org.eclipse.jdt.core.BindingKey; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageDeclaration; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.ITypeParameter; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.dom.IBinding; @@ -38,9 +59,12 @@ import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.ui.JavaElementLabels; +import org.eclipse.jdt.ui.PreferenceConstants; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.JavaUIMessages; +import org.eclipse.jdt.internal.ui.viewsupport.browser.HoverPreferenceStylingInBrowserAction.StylingPreference; +import org.eclipse.jdt.internal.ui.viewsupport.javadoc.JavadocStylingMessages; /** @@ -50,6 +74,61 @@ */ public class JavaElementLinks { + /** + * ID of the checkbox in generated HTML content that toggles formatting inside element labels. + */ + public static final String CHECKBOX_ID_FORMATTIG= "formattingSwitch"; //$NON-NLS-1$ + /** + * ID of the checkbox in generated HTML content that toggles wrapping inside element labels. + */ + public static final String CHECKBOX_ID_WRAPPING= "wrappingSwitch"; //$NON-NLS-1$ + /** + * ID of the checkbox in generated HTML content that toggles type parameters coloring inside element labels. + */ + public static final String CHECKBOX_ID_TYPE_PARAMETERS_REFERENCES_COLORING= "typeParamsRefsColoringSwitch"; //$NON-NLS-1$ + /** + * ID of the checkbox in generated HTML content that toggles type parameters levels coloring inside element labels. + */ + public static final String CHECKBOX_ID_TYPE_PARAMETERS_LEVELS_COLORING= "typeParamsLevelsColoringSwitch"; //$NON-NLS-1$ + /** + * ID of the checkbox in generated HTML content that toggles overlay when previewing styling. + */ + public static final String CHECKBOX_ID_PREVIEW= "previewSwitch"; //$NON-NLS-1$ + + private static final String PREFERENCE_KEY_POSTFIX_FORMATTING= "formatting"; //$NON-NLS-1$ + private static final String PREFERENCE_KEY_POSTFIX_WRAPPING= "wrapping"; //$NON-NLS-1$ + private static final String PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_REFERENCES_COLORING= "typeParamsReferencesColoring"; //$NON-NLS-1$ + private static final String PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_LEVELS_COLORING= "typeParamsLevelsColoring"; //$NON-NLS-1$ + + private static final String PREFERENCE_KEY_DARK_MODE_DEFAULT_COLORS= "javadocElementsStyling.darkModeDefaultColors"; //$NON-NLS-1$ + // both use 1-based indexing + private static final String PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR= "javadocElementsStyling.typesParamsReference_"; //$NON-NLS-1$ + private static final String PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR= "javadocElementsStyling.typesParamsLevel_"; //$NON-NLS-1$ + private static final String PREFERENCE_KEY_POSTFIX_COLOR= ".color"; //$NON-NLS-1$ + /** + * Maximum number of type parameters references / levels for which we support setting custom color + */ + private static final int MAX_COLOR_INDEX= 16; + + + private static final String CSS_CLASS_SWITCH_PARENT= "styleSwitchParent"; //$NON-NLS-1$ + // both use 1-based indexing + private static final String CSS_CLASS_TYPE_PARAMETERS_REFERENCE_PREFIX= "typeParamsReference typeParamsReferenceNo"; //$NON-NLS-1$ + private static final String CSS_CLASS_TYPE_PARAMETERS_LEVEL_PREFIX= "typeParamsLevel typeParamsLevelNo"; //$NON-NLS-1$ + + private static final String CSS_SECTION_START_TYPE_PARAMETERS_REFERENCES= "/* Start of dynamic type parameters references styling section (do not edit this line) */"; //$NON-NLS-1$ + private static final String CSS_SECTION_START_TYPE_PARAMETERS_LEVELS= "/* Start of dynamic type parameters levels styling section (do not edit this line) */"; //$NON-NLS-1$ + private static final String CSS_SECTION_END_TYPE_PARAMETERS_REFERENCES= "/* End of dynamic type parameters references styling section (do not edit this line) */"; //$NON-NLS-1$ + private static final String CSS_SECTION_END_TYPE_PARAMETERS_LEVELS= "/* End of dynamic type parameters levels styling section (do not edit this line) */"; //$NON-NLS-1$ + private static final String CSS_PLACEHOLDER_INDEX= "-INDEX-"; //$NON-NLS-1$ + private static final String CSS_PLACEHOLDER_COLOR= "-COLOR-"; //$NON-NLS-1$ + + + private static String[] CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_REFERENCES= new String[4]; + private static String[] CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_LEVELS= new String[4]; + private static final ReentrantLock CSS_FRAGMENTS_CACHE_LOCK= new ReentrantLock(); + private static final IPropertyChangeListener COLOR_PROPERTIES_CHANGE_LISTENER= JavaElementLinks::cssFragmentsCacheResetListener; + /** * A handler is asked to handle links to targets. * @@ -96,14 +175,42 @@ public interface ILinkHandler { static class JavaElementLinkedLabelComposer extends JavaElementLabelComposer { private final IJavaElement fElement; + private final boolean noEnhancements; + private final boolean enableWrapping; + private final boolean enableFormatting; + private final boolean enableTypeParamsColoring; + private final boolean enableTypeLevelsColoring; + + private boolean appendHoverParent= true; + private int nextNestingLevel= 1; + private Map typesIds= new TreeMap<>(); + private int nextTypeNo= 1; + private int nextParamNo= 1; + private boolean appendingMethodQualification= false; + private boolean typeStyleClassApplied= false; + private boolean inBoundedTypeParam= false; public JavaElementLinkedLabelComposer(IJavaElement member, StringBuffer buf) { + this(member, buf, null); + } + + public JavaElementLinkedLabelComposer(IJavaElement member, StringBuffer buf, String stylingPreferenceKeysPrefix) { super(buf); if (member instanceof IPackageDeclaration) { fElement= member.getAncestor(IJavaElement.PACKAGE_FRAGMENT); } else { fElement= member; } + if (stylingPreferenceKeysPrefix != null) { + noEnhancements= false; + enableWrapping= isStylingPreferenceAlways(stylingPreferenceKeysPrefix + PREFERENCE_KEY_POSTFIX_WRAPPING); + enableFormatting= isStylingPreferenceAlways(stylingPreferenceKeysPrefix + PREFERENCE_KEY_POSTFIX_FORMATTING); + enableTypeParamsColoring= isStylingPreferenceAlways(stylingPreferenceKeysPrefix + PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_REFERENCES_COLORING); + enableTypeLevelsColoring= isStylingPreferenceAlways(stylingPreferenceKeysPrefix + PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_LEVELS_COLORING); + } else { + noEnhancements= true; + enableWrapping= enableFormatting= enableTypeParamsColoring= enableTypeLevelsColoring= false; + } } @Override @@ -153,13 +260,32 @@ private String getPackageFragmentElementName(IJavaElement javaElement) { } @Override - protected String getGT() { - return ">"; //$NON-NLS-1$ + protected void appendGT() { + if (noEnhancements) { + fBuffer.append(">"); //$NON-NLS-1$ + } else { + fBuffer.append(""); //$NON-NLS-1$ + fBuffer.append(">"); //$NON-NLS-1$ + } + } @Override - protected String getLT() { - return "<"; //$NON-NLS-1$ + protected void appendLT() { + if (noEnhancements) { + fBuffer.append("<"); //$NON-NLS-1$ + } else { + fBuffer.append("<"); //$NON-NLS-1$ + + fBuffer.append(""); //$NON-NLS-1$ + } } @Override @@ -178,13 +304,39 @@ protected String getSimpleTypeName(IJavaElement enclosingElement, String typeSig } } + String retVal= typeName; try { String uri= createURI(JAVADOC_SCHEME, enclosingElement, qualifiedName, null, null); - return createHeaderLink(uri, typeName, title); + retVal= createHeaderLink(uri, typeName, title); } catch (URISyntaxException e) { JavaPlugin.log(e); - return typeName; } + + if (!noEnhancements && !inBoundedTypeParam) { + if ((Signature.getTypeSignatureKind(typeSig) == Signature.TYPE_VARIABLE_SIGNATURE && !typeStyleClassApplied) + || (Signature.getTypeSignatureKind(typeSig) == Signature.CLASS_TYPE_SIGNATURE && nextNestingLevel > 1)) { + return wrapWithTypeClass(typeName, retVal); + } else { + return retVal; + } + } else { + return retVal; + } + } + + private String wrapWithTypeClass(String typeName, String value) { + return "" //$NON-NLS-1$ + + value + + ""; //$NON-NLS-1$ + } + + private String getTypeStylingClass(String typeName) { + Integer typeId; + if ((typeId= typesIds.putIfAbsent(typeName, nextTypeNo)) == null) { + typeId= nextTypeNo++; + } + return CSS_CLASS_TYPE_PARAMETERS_REFERENCE_PREFIX + typeId; } @Override @@ -204,12 +356,300 @@ protected void appendAnnotationLabels(IAnnotation[] annotations, long flags) thr super.appendAnnotationLabels(annotations, flags); fBuffer.append(""); //$NON-NLS-1$ } + + @Override + public void appendElementLabel(IJavaElement element, long flags) { + if (noEnhancements) { + super.appendElementLabel(element, flags); + return; + } + if (appendingMethodQualification) { + // method label contains nested method label (eg. lambdas), we need to end method qualification if started + fBuffer.append(""); //$NON-NLS-1$ + appendingMethodQualification= false; + } + if (appendHoverParent) { + appendHoverParent= false; + + // styling preview checkbox + fBuffer.append(""); //$NON-NLS-1$ + + // wrapping checkbox + fBuffer.append(""); //$NON-NLS-1$ + + // formatting checkbox + fBuffer.append(""); //$NON-NLS-1$ + + // typeLevelsColoring checkbox + fBuffer.append(""); //$NON-NLS-1$ + + // typeParametersColoring checkbox + fBuffer.append(""); //$NON-NLS-1$ + + // encompassing for everything styled based on checkboxes checked state + fBuffer.append(""); //$NON-NLS-1$ //$NON-NLS-2$ + + // actual signature content + super.appendElementLabel(element, flags); + + // preview watermarks + fBuffer.append("
" + JavadocStylingMessages.JavadocStyling_stylingPreview_watermark + " - "); //$NON-NLS-1$ //$NON-NLS-2$ + fBuffer.append("
" //$NON-NLS-1$ + + JavadocStylingMessages.JavadocStyling_stylingPreview_typeParamsReferencesColoring + "
"); //$NON-NLS-1$ + fBuffer.append("
" //$NON-NLS-1$ + + JavadocStylingMessages.JavadocStyling_stylingPreview_typeParamsLevelsColoring + "
"); //$NON-NLS-1$ + fBuffer.append("
" //$NON-NLS-1$ + + JavadocStylingMessages.JavadocStyling_stylingPreview_formatting + "
"); //$NON-NLS-1$ + fBuffer.append("
" //$NON-NLS-1$ + + JavadocStylingMessages.JavadocStyling_stylingPreview_wrapping + "
"); //$NON-NLS-1$ + fBuffer.append("
"); //$NON-NLS-1$ + fBuffer.append("
"); //$NON-NLS-1$ + appendHoverParent= true; + } else { + super.appendElementLabel(element, flags); + } + } + + @Override + protected void appendMethodPrependedTypeParams(IMethod method, long flags, BindingKey resolvedKey, String resolvedSignature) throws JavaModelException { + if (noEnhancements) { + super.appendMethodPrependedTypeParams(method, flags, resolvedKey, resolvedSignature); + } else { + fBuffer.append(""); //$NON-NLS-1$ + super.appendMethodPrependedTypeParams(method, flags, resolvedKey, resolvedSignature); + fBuffer.append(""); //$NON-NLS-1$ + } + } + + @Override + protected void appendMethodPrependedReturnType(IMethod method, long flags, String resolvedSignature) throws JavaModelException { + if (noEnhancements) { + super.appendMethodPrependedReturnType(method, flags, resolvedSignature); + } else { + fBuffer.append(""); //$NON-NLS-1$ + super.appendMethodPrependedReturnType(method, flags, resolvedSignature); + fBuffer.append(""); //$NON-NLS-1$ + } + } + + @Override + protected void appendMethodQualification(IMethod method, long flags) { + if (noEnhancements) { + super.appendMethodQualification(method, flags); + } else { + appendingMethodQualification= true; + fBuffer.append(""); //$NON-NLS-1$ + super.appendMethodQualification(method, flags); + if (appendingMethodQualification) { + fBuffer.append(""); //$NON-NLS-1$ + appendingMethodQualification= false; + } + } + } + + @Override + protected void appendMethodName(IMethod method) { + if (noEnhancements) { + super.appendMethodName(method); + } else { + fBuffer.append(""); //$NON-NLS-1$ + super.appendMethodName(method); + fBuffer.append(""); //$NON-NLS-1$ + } + } + + @Override + protected void appendMethodParams(IMethod method, long flags, String resolvedSignature) throws JavaModelException { + if (noEnhancements) { + super.appendMethodParams(method, flags, resolvedSignature); + } else { + fBuffer.append(""); //$NON-NLS-1$ + super.appendMethodParams(method, flags, resolvedSignature); + fBuffer.append(""); //$NON-NLS-1$ + nextParamNo= 1; + } + } + + @Override + protected void appendMethodParam(IMethod method, long flags, IAnnotation[] annotations, String paramSignature, String name, boolean renderVarargs, boolean isLast) throws JavaModelException { + if (noEnhancements) { + super.appendMethodParam(method, flags, annotations, paramSignature, name, renderVarargs, isLast); + } else { + fBuffer.append(""); //$NON-NLS-1$ + super.appendMethodParam(method, flags, annotations, paramSignature, name, renderVarargs, isLast); + fBuffer.append(""); //$NON-NLS-1$ + } + } + + @Override + protected void appendMethodParamName(String name) { + if (noEnhancements) { + super.appendMethodParamName(name); + } else { + fBuffer.append(""); //$NON-NLS-1$ + super.appendMethodParamName(name); + fBuffer.append(""); //$NON-NLS-1$ + } + } + + @Override + protected void appendTypeParameterWithBounds(ITypeParameter typeParameter, long flags) throws JavaModelException { + if (noEnhancements) { + super.appendTypeParameterWithBounds(typeParameter, flags); + } else { + fBuffer.append(""); //$NON-NLS-1$ + inBoundedTypeParam= true; + super.appendTypeParameterWithBounds(typeParameter, flags); + inBoundedTypeParam= false; + fBuffer.append(""); //$NON-NLS-1$ + } + } + + @Override + protected void appendWildcardTypeSignature(String prefix, IJavaElement enclosingElement, String typeSignature, long flags) { + if (noEnhancements) { + super.appendWildcardTypeSignature(prefix, enclosingElement, typeSignature, flags); + } else { + int sigKind= Signature.getTypeSignatureKind(typeSignature); + if (sigKind == Signature.TYPE_VARIABLE_SIGNATURE || sigKind == Signature.CLASS_TYPE_SIGNATURE) { + typeStyleClassApplied= true; + String typeName= super.getSimpleTypeName(enclosingElement, typeSignature); + fBuffer.append(""); //$NON-NLS-1$ + } + super.appendWildcardTypeSignature(prefix, enclosingElement, typeSignature, flags); + if (typeStyleClassApplied) { + fBuffer.append(""); //$NON-NLS-1$ + typeStyleClassApplied= false; + } + } + } + + @Override + protected void appendTypeArgumentSignaturesLabel(IJavaElement enclosingElement, String[] typeArgsSig, long flags) { + if (inBoundedTypeParam) { + inBoundedTypeParam= false; + super.appendTypeArgumentSignaturesLabel(enclosingElement, typeArgsSig, flags); + inBoundedTypeParam= true; + } else { + super.appendTypeArgumentSignaturesLabel(enclosingElement, typeArgsSig, flags); + } + } } public static final String OPEN_LINK_SCHEME= CoreJavaElementLinks.OPEN_LINK_SCHEME; public static final String JAVADOC_SCHEME= CoreJavaElementLinks.JAVADOC_SCHEME; public static final String JAVADOC_VIEW_SCHEME= CoreJavaElementLinks.JAVADOC_VIEW_SCHEME; + public static void initDefaultPreferences(IPreferenceStore store) { + initDefaultColors(store); + store.addPropertyChangeListener(COLOR_PROPERTIES_CHANGE_LISTENER); + // taking advantage of PREFERENCE_KEY_DARK_MODE_DEFAULT_COLORS change instead of more complicated OSGi event listener + store.addPropertyChangeListener(JavaElementLinks::darkThemeChangeListener); + } + + public static void initDefaultColors(IPreferenceStore store) { + if (store.getBoolean(PREFERENCE_KEY_DARK_MODE_DEFAULT_COLORS)) { + var color= new RGB(177, 102, 218); // semanticHighlighting.typeArgument.color in css\e4-dark_jdt_syntaxhighlighting.css + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, 1), color); + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, 1), color); + + color= new RGB(255, 140, 0); // CSS 'DarkOrange' + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, 2), color); + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, 2), color); + + color= new RGB(144, 238, 144); // CSS 'LightGreen' + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, 3), color); + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, 3), color); + + color= new RGB(0, 191, 255); // CSS 'DeepSkyBlue' + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, 4), color); + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, 4), color); + } else { + // slightly brighter than SemanticHighlightings.TypeArgumentHighlighting's default color to work better on yellow-ish background + var color= new RGB(60, 179, 113); // CSS 'MediumSeaGreen' + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, 1), color); + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, 1), color); + + color= new RGB(255, 140, 0); // CSS 'DarkOrange' + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, 2), color); + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, 2), color); + + color= new RGB(153, 50, 204); // CSS 'DarkOrchid' + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, 3), color); + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, 3), color); + + color= new RGB(65, 105, 225); // CSS 'RoyalBlue' + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, 4), color); + PreferenceConverter.setDefault(store, getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, 4), color); + } + } + + public static void initDefaultPreferences(IPreferenceStore store, String keyPrefix) { + store.setDefault(keyPrefix + PREFERENCE_KEY_POSTFIX_FORMATTING, StylingPreference.ALWAYS.name()); + store.setDefault(keyPrefix + PREFERENCE_KEY_POSTFIX_WRAPPING, StylingPreference.ALWAYS.name()); + store.setDefault(keyPrefix + PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_REFERENCES_COLORING, StylingPreference.HOVER.name()); + store.setDefault(keyPrefix + PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_LEVELS_COLORING, StylingPreference.OFF.name()); + } + + private static void darkThemeChangeListener(PropertyChangeEvent event) { + if (PREFERENCE_KEY_DARK_MODE_DEFAULT_COLORS.equals(event.getProperty())) { + initDefaultColors(preferenceStore()); + cssFragmentsCacheResetListener(null); + } + } + + private static void cssFragmentsCacheResetListener(PropertyChangeEvent event) { + var changeOfTypeLevelsColor= false; + var changeOfTypeParamsColor= false; + if (event == null) { + changeOfTypeLevelsColor= changeOfTypeParamsColor= true; + } else { + changeOfTypeLevelsColor= event.getProperty().startsWith(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR); + changeOfTypeParamsColor= event.getProperty().startsWith(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR); + } + if (changeOfTypeLevelsColor || changeOfTypeParamsColor) { + try { + if (CSS_FRAGMENTS_CACHE_LOCK.tryLock(500, TimeUnit.MILLISECONDS)) { + try { + if (changeOfTypeLevelsColor) { + CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_LEVELS= new String[4]; + } + if (changeOfTypeParamsColor) { + CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_REFERENCES= new String[4]; + } + } finally { + CSS_FRAGMENTS_CACHE_LOCK.unlock() ; + } + } + } catch (InterruptedException e1) { + JavaPlugin.logErrorMessage("Interrupted while waiting for CSS fragments cache lock, cache reset unsuccessful"); //$NON-NLS-1$ + } + } + } + private JavaElementLinks() { // static only } @@ -400,10 +840,27 @@ public static String getElementLabel(IJavaElement element, long flags) { * @since 3.6 */ public static String getElementLabel(IJavaElement element, long flags, boolean linkAllNames) { + return getElementLabel(element, flags, linkAllNames, null); + } + + /** + * Returns the label for a Java element with the flags as defined by {@link JavaElementLabels}. + * Referenced element names in the label are rendered as header links. + * If linkAllNames is false, don't link the name of the given element + * + * @param element the element to render + * @param flags the rendering flags + * @param linkAllNames if true, link all names; if false, link all names except original element's name + * @param stylingPreferenceKeysPrefix prefix for preference keys related to styling of HTML content for element labels, + * null means no enhanced styling + * @return the label of the Java element + * @since 3.6 + */ + public static String getElementLabel(IJavaElement element, long flags, boolean linkAllNames, String stylingPreferenceKeysPrefix) { StringBuffer buf= new StringBuffer(); if (!Strings.USE_TEXT_PROCESSOR) { - new JavaElementLinkedLabelComposer(linkAllNames ? null : element, buf).appendElementLabel(element, flags); + new JavaElementLinkedLabelComposer(linkAllNames ? null : element, buf, stylingPreferenceKeysPrefix).appendElementLabel(element, flags); return Strings.markJavaElementLabelLTR(buf.toString()); } else { String label= JavaElementLabels.getElementLabel(element, flags); @@ -423,14 +880,261 @@ public static String getElementLabel(IJavaElement element, long flags, boolean l * @since 3.11 */ public static String getBindingLabel(IBinding binding, IJavaElement element, long flags, boolean haveSource) { + return getBindingLabel(binding, element, flags, haveSource, null); + } + + /** + * Returns the label for a binding with the flags as defined by {@link JavaElementLabels}. + * Referenced element names in the label are rendered as header links. + * + * @param binding the binding to render + * @param element the corresponding Java element, used for javadoc hyperlinks + * @param flags the rendering flags + * @param haveSource true when looking at an ICompilationUnit which enables the use of short type names + * @param stylingPreferenceKeysPrefix prefix for preference keys related to styling of HTML content for element labels, + * null means no enhanced styling + * @return the label of the binding + * @since 3.11 + */ + public static String getBindingLabel(IBinding binding, IJavaElement element, long flags, boolean haveSource, String stylingPreferenceKeysPrefix) { StringBuffer buf= new StringBuffer(); if (!Strings.USE_TEXT_PROCESSOR) { - new BindingLinkedLabelComposer(element, buf, haveSource).appendBindingLabel(binding, flags); + new BindingLinkedLabelComposer(element, buf, haveSource, stylingPreferenceKeysPrefix).appendBindingLabel(binding, flags); return Strings.markJavaElementLabelLTR(buf.toString()); } else { String label= JavaElementLabels.getElementLabel(element, flags); return label.replace("<", "<").replace(">", ">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ } } + + private static boolean isStylingPreferenceAlways(String key) { + return StylingPreference.ALWAYS == getPreference(key); + } + + public static StylingPreference getPreferenceForFormatting(String keyPrefix) { + return getPreference(keyPrefix + PREFERENCE_KEY_POSTFIX_FORMATTING); + } + + public static StylingPreference getPreferenceForWrapping(String keyPrefix) { + return getPreference(keyPrefix + PREFERENCE_KEY_POSTFIX_WRAPPING); + } + + public static StylingPreference getPreferenceForTypeParamsReferencesColoring(String keyPrefix) { + return getPreference(keyPrefix + PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_REFERENCES_COLORING); + } + + public static StylingPreference getPreferenceForTypeParamsLevelsColoring(String keyPrefix) { + return getPreference(keyPrefix + PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_LEVELS_COLORING); + } + + private static StylingPreference getPreference(String key) { + return StylingPreference.valueOf(preferenceStore().getString(key)); + } + + public static void setPreferenceForFormatting(String keyPrefix, StylingPreference value) { + setPreference(keyPrefix + PREFERENCE_KEY_POSTFIX_FORMATTING, value); + } + + public static void setPreferenceForWrapping(String keyPrefix, StylingPreference value) { + setPreference(keyPrefix + PREFERENCE_KEY_POSTFIX_WRAPPING, value); + } + + public static void setPreferenceForTypeParamsReferencesColoring(String keyPrefix, StylingPreference value) { + setPreference(keyPrefix + PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_REFERENCES_COLORING, value); + } + + public static void setPreferenceForTypeParamsLevelsColoring(String keyPrefix, StylingPreference value) { + setPreference(keyPrefix + PREFERENCE_KEY_POSTFIX_TYPE_PARAMETERS_LEVELS_COLORING, value); + } + + private static void setPreference(String key, StylingPreference value) { + preferenceStore().setValue(key, value.name()); + } + + public static RGB getColorPreferenceForTypeParamsReference(int referenceIndex) { + return getColorPreference(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, referenceIndex); + } + + public static RGB getColorPreferenceForTypeParamsLevel(int levelIndex) { + return getColorPreference(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, levelIndex); + } + + private static RGB getColorPreference(String keyPrefix, int index) { + var color= PreferenceConverter.getColor(preferenceStore(), getColorPreferenceKey(keyPrefix, index)); + if (PreferenceConverter.COLOR_DEFAULT_DEFAULT == color) { + // for unconfigured color indexes alternate between first 4 colors + return PreferenceConverter.getColor(preferenceStore(), getColorPreferenceKey(keyPrefix, 1 + ((index + 3) % 4))); + } + return color; + } + + public static void setColorPreferenceForTypeParamsReference(int referenceIndex, RGB color) { + setColorPreference(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, referenceIndex, color); + } + + public static void setColorPreferenceForTypeParamsLevel(int levelIndex, RGB color) { + setColorPreference(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, levelIndex, color); + } + + private static void setColorPreference(String keyPrefix, int index, RGB color) { + PreferenceConverter.setValue(preferenceStore(), getColorPreferenceKey(keyPrefix, index), color); + } + + public static String modifyCssStyleSheet(String css, StringBuilder buffer) { + int startPos= buffer.indexOf(CSS_CLASS_SWITCH_PARENT); + if (startPos < 0) { + return css; + } + StringBuilder cssContent= new StringBuilder(); + + int maxTypeParamNo= getMaxIndexOfStyle(buffer, StringBuilder::indexOf, CSS_CLASS_TYPE_PARAMETERS_REFERENCE_PREFIX); + int maxTypeLevelNo= getMaxIndexOfStyle(buffer, StringBuilder::indexOf, CSS_CLASS_TYPE_PARAMETERS_LEVEL_PREFIX); + + var locked= false; + try { + locked= CSS_FRAGMENTS_CACHE_LOCK.tryLock(100, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + JavaPlugin.logErrorMessage("Interrupted while waiting for CSS fragments cache lock, proceeding without using cache"); //$NON-NLS-1$ + } + try { + if (locked) { + if (CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_LEVELS.length < maxTypeLevelNo) { + CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_LEVELS= Arrays.copyOf(CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_LEVELS, maxTypeLevelNo); + } + if (CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_REFERENCES.length < maxTypeParamNo) { + CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_REFERENCES= Arrays.copyOf(CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_REFERENCES, maxTypeParamNo); + } + } + var processedUntil= processSection(css, cssContent, 0, CSS_SECTION_START_TYPE_PARAMETERS_LEVELS, CSS_SECTION_END_TYPE_PARAMETERS_LEVELS, + PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, maxTypeLevelNo, + (locked ? CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_LEVELS : null), true); + processedUntil= processSection(css, cssContent, processedUntil, CSS_SECTION_START_TYPE_PARAMETERS_REFERENCES, CSS_SECTION_END_TYPE_PARAMETERS_REFERENCES, + PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, maxTypeParamNo, + (locked ? CSS_FRAGMENTS_CACHE_TYPE_PARAMETERS_REFERENCES : null), false); + cssContent.append(css, processedUntil, css.length()); + return cssContent.toString(); + } catch (Exception e) { + JavaPlugin.log(e); + return css; + } finally { + if (locked) { + CSS_FRAGMENTS_CACHE_LOCK.unlock(); + } + } + } + + private static int processSection(String cssTemplate, StringBuilder outputCss, int previousEnd, + String sectionStartLine, String sectionEndLine, String preferenceKeyPrefix, + int iterations, String[] fragmentsCache, boolean upwards) { + var sectionStart= cssTemplate.indexOf(sectionStartLine); + outputCss.append(cssTemplate, previousEnd, sectionStart); + + sectionStart += sectionStartLine.length(); + var sectionEnd= cssTemplate.indexOf(sectionEndLine, sectionStart); + for (int i= upwards ? 0 : iterations - 1; upwards ? i < iterations : i >= 0 ; i = i + (upwards ? 1 : -1)) { + if (fragmentsCache != null && fragmentsCache.length > i && fragmentsCache[i] != null) { + // re-use cached fragment + outputCss.append(fragmentsCache[i]); + } else { + var section= cssTemplate.substring(sectionStart, sectionEnd); + var sectionBuf= new StringBuilder(section); + int pos; + int index = i + 1; // color styles in CSS and preference keys are 1-based + while ((pos= sectionBuf.indexOf(CSS_PLACEHOLDER_INDEX)) != -1) { + sectionBuf.replace(pos, pos + CSS_PLACEHOLDER_INDEX.length(), String.valueOf(index)); + } + pos= sectionBuf.indexOf(CSS_PLACEHOLDER_COLOR); + sectionBuf.replace(pos, pos + CSS_PLACEHOLDER_COLOR.length(), getCssColor(getColorPreference(preferenceKeyPrefix, index))); + if (fragmentsCache != null && fragmentsCache.length > i) { // cache fragment if possible + fragmentsCache[i]= sectionBuf.toString(); + } + outputCss.append(sectionBuf); + } + } + return sectionEnd + sectionEndLine.length(); + } + + private static String getCssColor(RGB color) { + // TODO styling: org.eclipse.jface.internal.text.html.HTMLPrinter does it differently (hex string) + return "rgb(" + color.red + ", " + color.green + ", " + color.blue + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + + public static int getNumberOfTypeParamsReferences(String content) { + return getMaxIndexOfStyle(content, String::indexOf, CSS_CLASS_TYPE_PARAMETERS_REFERENCE_PREFIX); + } + + public static int getNumberOfTypeParamsLevels(String content) { + return getMaxIndexOfStyle(content, String::indexOf, CSS_CLASS_TYPE_PARAMETERS_LEVEL_PREFIX); + } + + private static int getMaxIndexOfStyle(T content, BiFunction indexOfGetter, String stylePrefix) { + int i= 0; + while (indexOfGetter.apply(content, stylePrefix + ++i) != -1) { /* no-op */ } + return i - 1; + } + + public static Integer[] getColorPreferencesIndicesForTypeParamsReference() { + return getColorPreferencesIndices(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR); + } + + public static Integer[] getColorPreferencesIndicesForTypeParamsLevel() { + return getColorPreferencesIndices(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR); + } + + private static Integer[] getColorPreferencesIndices(String keyPrefix) { + List retVal= new ArrayList<>(MAX_COLOR_INDEX); + for (int i= 1; i <= MAX_COLOR_INDEX; i++) { + if (i <= 4) { + // pretend first 4 colors are always set (since we have defaults for them) + retVal.add(i); + } else { + String key= getColorPreferenceKey(keyPrefix, i); + if (preferenceStore().contains(key)) { + retVal.add(i); + } + } + } + return retVal.toArray(Integer[]::new); + } + + public static void resetAllColorPreferencesToDefaults() { + var store= preferenceStore(); + store.removePropertyChangeListener(COLOR_PROPERTIES_CHANGE_LISTENER); + try { + StringBuffer logMessage= new StringBuffer("Following custom color preferences were removed:"); //$NON-NLS-1$ + RGB customColor= null; + for (int i= 1; i <= MAX_COLOR_INDEX; i++) { + String key= getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_REFERENCE_COLOR, i); + if (!store.isDefault(key)) { + customColor= PreferenceConverter.getColor(store, key); + logMessage.append("\n\t").append(key).append(" = ").append(customColor); //$NON-NLS-1$ //$NON-NLS-2$ + store.setToDefault(key); + } + } + for (int i= 1; i <= MAX_COLOR_INDEX; i++) { + String key= getColorPreferenceKey(PREFERENCE_KEY_PREFIX_TYPE_PARAMETERS_LEVEL_COLOR, i); + if (!store.isDefault(key)) { + customColor= PreferenceConverter.getColor(store, key); + logMessage.append("\n\t").append(key).append(" = ").append(customColor); //$NON-NLS-1$ //$NON-NLS-2$ + store.setToDefault(key); + } + } + cssFragmentsCacheResetListener(null); + if (customColor != null) { + JavaPlugin.log(new Status(IStatus.INFO, JavaPlugin.getPluginId(), logMessage.toString())); + } + } finally { + store.addPropertyChangeListener(COLOR_PROPERTIES_CHANGE_LISTENER); + } + } + + private static String getColorPreferenceKey(String prefix, int index) { + return prefix + index + PREFERENCE_KEY_POSTFIX_COLOR; + } + + private static IPreferenceStore preferenceStore() { + return PreferenceConstants.getPreferenceStore(); + } + } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MenuVisibilityMenuItemsConfigurer.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MenuVisibilityMenuItemsConfigurer.java new file mode 100644 index 00000000000..0104b567671 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MenuVisibilityMenuItemsConfigurer.java @@ -0,0 +1,92 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport; + +import java.util.function.BiFunction; + +import org.eclipse.swt.events.MenuEvent; +import org.eclipse.swt.events.MenuListener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; + +import org.eclipse.jface.action.ActionContributionItem; + +public final class MenuVisibilityMenuItemsConfigurer { + + public static void registerForMenu(Menu menu, Runnable postHandleEventAction) { + menu.addMenuListener(new MenuListenerImpl(menu, postHandleEventAction)); + } + + private static class MenuListenerImpl implements MenuListener { + final Menu menu; + final Runnable postHandleEventAction; + + private MenuListenerImpl(Menu menu, Runnable postHandleEventAction) { + this.menu= menu; + this.postHandleEventAction= postHandleEventAction; + } + + @Override + public void menuShown(MenuEvent e) { + handleEvent(e, IMenuVisibilityMenuItemAction::menuShown); + } + + @Override + public void menuHidden(MenuEvent e) { + handleEvent(e, IMenuVisibilityMenuItemAction::menuHidden); + } + + private void handleEvent(MenuEvent e, BiFunction callback) { + boolean runPostAction= false; + for (MenuItem item : menu.getItems()) { + if (item.getData() instanceof ActionContributionItem + && ((ActionContributionItem) item.getData()).getAction() instanceof IMenuVisibilityMenuItemAction listener) { + runPostAction |= callback.apply(listener, e); + } + } + if (postHandleEventAction != null && runPostAction) { + postHandleEventAction.run(); + } + } + } + + /** + * Menu visibility listener tagging interface for menu item actions. If a menu item was created from {@link ActionContributionItem} + * and the action it is wrapping implements this interface, then action's methods defined in this interface are called + * on change of menu visibility. + *

+ * Methods return boolean to indicate whether some further post-handle action(s), that is specific to usage context, + * should be performed. + */ + public interface IMenuVisibilityMenuItemAction { + + /** + * Notification when the menu, that contains menu item wrapping an action that is target of this call, was shown. + * @param event event forwarded from menu's {@link MenuListener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean menuShown(MenuEvent event) { + return false; + } + + /** + * Notification when the menu, that contains menu item wrapping an action that is target of this call, was hidden. + * @param event event forwarded from menu's {@link MenuListener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean menuHidden(MenuEvent event) { + return false; + } + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MouseListeningMenuItemsConfigurer.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MouseListeningMenuItemsConfigurer.java new file mode 100644 index 00000000000..0cf57046625 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MouseListeningMenuItemsConfigurer.java @@ -0,0 +1,105 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ArmEvent; +import org.eclipse.swt.events.ArmListener; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; + +import org.eclipse.jface.action.ActionContributionItem; + +public final class MouseListeningMenuItemsConfigurer { + + final Runnable postHandleEventAction; + MenuItem lastEnteredItem= null; + IMouseListeningMenuItemAction lastEnteredItemAction= null; + + public static void registerForMenu(Menu menu, Runnable postHandleEventAction) { + var instance= new MouseListeningMenuItemsConfigurer(postHandleEventAction); + ArmListener listener= instance::handleEvent; + for (MenuItem item : menu.getItems()) { + item.addArmListener(listener); + } + menu.addListener(SWT.Hide, instance::menuHidden); + } + + private MouseListeningMenuItemsConfigurer(Runnable postHandleEventAction) { + this.postHandleEventAction= postHandleEventAction; + } + + private void handleEvent(ArmEvent event) { + var menuItem= (MenuItem) event.widget; + if (lastEnteredItem == menuItem) { + return; + } + boolean runPostAction= false; + if (lastEnteredItemAction != null) { + runPostAction |= lastEnteredItemAction.mouseExit(event); + lastEnteredItemAction= null; + } + lastEnteredItem= menuItem; + if (menuItem.getData() instanceof ActionContributionItem + && ((ActionContributionItem) menuItem.getData()).getAction() instanceof IMouseListeningMenuItemAction) { + lastEnteredItemAction= (IMouseListeningMenuItemAction) ((ActionContributionItem) menuItem.getData()).getAction(); + runPostAction |= lastEnteredItemAction.mouseEnter(event); + } + if (postHandleEventAction != null && runPostAction) { + postHandleEventAction.run(); + } + } + + @SuppressWarnings("unused") + private void menuHidden(Event e) { + boolean runPostAction= false; + if (lastEnteredItemAction != null) { + runPostAction= lastEnteredItemAction.mouseExit(null); + lastEnteredItemAction= null; + } + if (postHandleEventAction != null && runPostAction) { + postHandleEventAction.run(); + } + } + + /** + * Mouse hover listener tagging interface for menu item actions. If a menu item was created from {@link ActionContributionItem} + * and the action it is wrapping implements this interface, then action's methods defined in this interface are called + * on mouse hover over corresponding menu item. + *

+ * Methods return boolean to indicate whether some further post-handle action(s), that is specific to usage context, + * should be performed. + */ + public interface IMouseListeningMenuItemAction { + + /** + * Notification when mouse enters area of the widget of menu item wrapping an action that is target of this call. + * @param event event forwarded from menu's {@link ArmListener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean mouseEnter(ArmEvent event) { + return false; + } + + /** + * Notification when mouse exists area of the widget of menu item wrapping an action that is target of this call. + * @param event event forwarded from menu's {@link ArmListener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean mouseExit(ArmEvent event) { + return false; + } + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MouseListeningToolItemsConfigurer.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MouseListeningToolItemsConfigurer.java new file mode 100644 index 00000000000..f061ac8849e --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/MouseListeningToolItemsConfigurer.java @@ -0,0 +1,157 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.ToolBarManager; + +public final class MouseListeningToolItemsConfigurer { + + private static final String WIDGET_DATA_KEY= MouseListeningToolItemsConfigurer.class.getSimpleName(); + + final ToolBar toolbar; + final Runnable postHandleEventAction; + ToolItem lastEnteredItem= null; + IMouseListeningToolbarItemAction lastEnteredItemAction= null; + + public static void registerForToolBarManager(ToolBarManager tbm, Runnable postHandleEventAction) { + var toolbar= tbm.getControl(); + if (toolbar.getData(WIDGET_DATA_KEY) == null) { // prevent re-registrations on same toolbar widget + var instance= new MouseListeningToolItemsConfigurer(toolbar, postHandleEventAction); + Listener listener= instance::handleEvent; + toolbar.setData(WIDGET_DATA_KEY, listener); + toolbar.addListener(SWT.MouseEnter, listener); + toolbar.addListener(SWT.MouseExit, listener); + toolbar.addListener(SWT.MouseHover, listener); + toolbar.addListener(SWT.MouseMove, listener); + toolbar.addListener(SWT.MouseDown, listener); + } + } + + private MouseListeningToolItemsConfigurer(ToolBar toolbar, Runnable postHandleEventAction) { + this.toolbar= toolbar; + this.postHandleEventAction= postHandleEventAction; + } + + private void handleEvent(Event event) { + boolean runPostAction= false; + switch (event.type) { + case SWT.MouseMove: + case SWT.MouseEnter: + var toolItem= toolbar.getItem(new Point(event.x, event.y)); + if (lastEnteredItem != toolItem) { + if (lastEnteredItem != null) { + runPostAction |= lastEnteredItemAction.mouseExit(event); + lastEnteredItemAction= null; + lastEnteredItem= null; + } + if (toolItem != null + && toolItem.getData() instanceof ActionContributionItem + && ((ActionContributionItem) toolItem.getData()).getAction() instanceof IMouseListeningToolbarItemAction) { + lastEnteredItem= toolItem; + lastEnteredItemAction= (IMouseListeningToolbarItemAction) ((ActionContributionItem) toolItem.getData()).getAction(); + runPostAction |= lastEnteredItemAction.mouseEnter(event); + } + } else if (lastEnteredItem != null) { + runPostAction |= lastEnteredItemAction.mouseMove(event); + } + break; + case SWT.MouseExit: + if (lastEnteredItem != null) { + runPostAction |= lastEnteredItemAction.mouseExit(event); + lastEnteredItemAction= null; + lastEnteredItem= null; + } + break; + case SWT.MouseHover: + if (lastEnteredItem != null) { + runPostAction |= lastEnteredItemAction.mouseHover(event); + } + break; + case SWT.MouseDown: + if (lastEnteredItem != null && event.button == 1) { + runPostAction |= lastEnteredItemAction.mouseClick(event); + } + break; + default: + break; + } + if (postHandleEventAction != null && runPostAction) { + postHandleEventAction.run(); + } + } + + /** + * Toolbar events listener tagging interface for toolbar item actions. If a toolbar item was created from {@link ActionContributionItem} + * and the action it is wrapping implements this interface, then action's methods defined in this interface are called + * on mouse events over corresponding toolbar item. + *

+ * Methods return boolean to indicate whether some further post-handle action(s), that is specific to usage context, + * should be performed. + */ + public interface IMouseListeningToolbarItemAction { + + /** + * Notification when mouse enters area of the widget of toolbar item wrapping an action that is target of this call. + * @param event event forwarded from menu's {@link Listener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean mouseEnter(Event event) { + return false; + } + + /** + * Notification when mouse exits area of the widget of toolbar item wrapping an action that is target of this call. + * @param event event forwarded from menu's {@link Listener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean mouseExit(Event event) { + return false; + } + + /** + * Notification when mouse hovers over area of the widget of toolbar item wrapping an action that is target of this call. + * @param event event forwarded from menu's {@link Listener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean mouseHover(Event event) { + return false; + } + + /** + * Notification when mouse moves inside area of the widget of toolbar item wrapping an action that is target of this call. + * @param event event forwarded from menu's {@link Listener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean mouseMove(Event event) { + return false; + } + + /** + * Notification when mouse is clicked inside area of the widget of toolbar item wrapping an action that is target of this call. + * @param event event forwarded from menu's {@link Listener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean mouseClick(Event event) { + return false; + } + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/ReappearingMenuToolbarAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/ReappearingMenuToolbarAction.java new file mode 100644 index 00000000000..665c2c37798 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/ReappearingMenuToolbarAction.java @@ -0,0 +1,115 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport; + +import java.util.stream.Stream; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.ToolItem; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuCreator; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.resource.ImageDescriptor; + +import org.eclipse.jdt.internal.ui.JavaPlugin; + +public class ReappearingMenuToolbarAction extends Action implements IMenuCreator { + protected final Action[] actions; + private Menu menu= null; + private Point menuLocation; + + public ReappearingMenuToolbarAction(String text, ImageDescriptor image, Action... actions) { + super(text, IAction.AS_DROP_DOWN_MENU); + setImageDescriptor(image); + setHoverImageDescriptor(image); + this.actions= actions; + setId(ReappearingMenuToolbarAction.class.getSimpleName()); + setMenuCreator(this); + } + + public void setupMenuReopen(IToolBarManager toolbarManager) { + for (var item : toolbarManager.getItems()) { + if (item instanceof ActionContributionItem actionItem && actionItem.getAction() == this) { + if (actionItem.getWidget() != null) { + actionItem.getWidget().addListener(SWT.Selection, this::menuButtonSelected); + } else { + System.err.println("NULL actionItem.getWidget()"); //$NON-NLS-1$ // TODO remove + } + return; + } + } + } + + private void menuButtonSelected(Event e) { + if (e.detail == SWT.ARROW) { + // menu is being shown, save location used to position the menu so that we can later show it there + menuLocation= ((ToolItem) e.widget).getParent().toDisplay(new Point(e.x, e.y)); + } + } + + protected boolean menuCreated() { + return menu != null; + } + + @Override + public Menu getMenu(Control parent) { + if (menu == null) { + menu= new Menu(parent); + Stream.of(actions).forEach(this::addMenuItem); + } + return menu; + } + + @Override + public Menu getMenu(Menu parent) { + return null; + } + + protected void addMenuItem(Action action) { + var item= new ActionContributionItem(action); + item.fill(menu, -1); + ((MenuItem) item.getWidget()).addSelectionListener(SelectionListener.widgetSelectedAdapter(this::itemSelected)); + } + + @SuppressWarnings("unused") + private void itemSelected(SelectionEvent event) { + if (menuLocation != null) { + menu.setLocation(menuLocation); + menu.setVisible(true); // display again after item selection + } else { + JavaPlugin.logErrorMessage( + "Unable to display " //$NON-NLS-1$ + + getClass().getName() + + " again since no previous display location was set"); //$NON-NLS-1$ + } + } + + @Override + public void dispose() { + if (menu != null) { + menu.dispose(); + } + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/ToolbarVisibilityToolItemsConfigurer.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/ToolbarVisibilityToolItemsConfigurer.java new file mode 100644 index 00000000000..6fcd27939a7 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/ToolbarVisibilityToolItemsConfigurer.java @@ -0,0 +1,93 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport; + +import java.util.function.BiFunction; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.ToolBarManager; + +public final class ToolbarVisibilityToolItemsConfigurer { + + final ToolBar toolbar; + final Runnable postHandleEventAction; + + public static void registerForToolBarManager(ToolBarManager tbm, Runnable postHandleEventAction) { + var toolbar= tbm.getControl(); + var instance= new ToolbarVisibilityToolItemsConfigurer(toolbar, postHandleEventAction); + Listener listener= instance::handleEvent; + toolbar.getShell().addListener(SWT.Show, listener); + toolbar.getShell().addListener(SWT.Hide, listener); + } + + private ToolbarVisibilityToolItemsConfigurer(ToolBar toolbar, Runnable postHandleEventAction) { + this.toolbar= toolbar; + this.postHandleEventAction= postHandleEventAction; + } + + private void handleEvent(Event event) { + boolean runPostAction= false; + BiFunction callback = + switch (event.type) { + case SWT.Show -> IToolbarVisibilityToolItemAction::toolbarShown; + case SWT.Hide -> IToolbarVisibilityToolItemAction::toolbarHidden; + default -> throw new IllegalArgumentException(event.toString()); + }; + for (ToolItem toolItem : toolbar.getItems()) { + if (toolItem != null + && toolItem.getData() instanceof ActionContributionItem + && ((ActionContributionItem) toolItem.getData()).getAction() instanceof IToolbarVisibilityToolItemAction listener) { + runPostAction |= callback.apply(listener, event); + } + } + if (postHandleEventAction != null && runPostAction) { + postHandleEventAction.run(); + } + } + + /** + * Toolbar visibility listener tagging interface for toolbar item actions. If a toolbar item was created from {@link ActionContributionItem} + * and the action it is wrapping implements this interface, then action's methods defined in this interface are called + * on change of toolbar visibility. + *

+ * Methods return boolean to indicate whether some further post-handle action(s), that is specific to usage context, + * should be performed. + */ + public interface IToolbarVisibilityToolItemAction { + + /** + * Notification when the toolbar, that contains toolbar item wrapping an action that is target of this call, was shown. + * @param event event forwarded from toolbar's {@link Listener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean toolbarShown(Event event) { + return false; + } + + /** + * Notification when the toolbar, that contains toolbar item wrapping an action that is target of this call, was hidden. + * @param event event forwarded from toolbar's {@link Listener} + * @return true if context specific post-handle action(s) should be performed, false otherwise (default) + */ + default boolean toolbarHidden(Event event) { + return false; + } + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/CheckboxInBrowserUtil.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/CheckboxInBrowserUtil.java new file mode 100644 index 00000000000..477ae1c7929 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/CheckboxInBrowserUtil.java @@ -0,0 +1,249 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.browser; + +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.browser.LocationEvent; +import org.eclipse.swt.browser.LocationListener; + +import org.eclipse.core.runtime.ListenerList; + +import org.eclipse.jface.internal.text.html.BrowserInformationControl; + +import org.eclipse.jdt.internal.ui.JavaPlugin; + +public final class CheckboxInBrowserUtil { + private static final Pattern CHECKED_PATERN= Pattern.compile(" *checked(=['\"]\\S*?['\"])? *"); //$NON-NLS-1$ + + // not made thread-safe for now since we assume all getText/setText calls are done from display thread + public static class BrowserTextAccessor { + private final ListenerList listeners= new ListenerList<>(); + private Browser browser; + private String textCache; + + public BrowserTextAccessor(BrowserInformationControl iControl) { + // only way so far to get hold of reference to browser is to get it through LocationListener + iControl.addLocationListener(new BrowserTextAccessorLocationListener()); + } + + public BrowserTextAccessor(Browser browser) { + this.browser= browser; + browser.addLocationListener(new BrowserTextAccessorLocationListener()); + } + + boolean isInitlaized() { + return browser != null; + } + + /** + * Gets the working content of the browser, i.e. content set via last call to {@link #setText(String)}, if one was made, + * otherwise content fetched from browser if no {@link #setText(String)} call was made since last time actual content inside + * browser was changed (e.g. by {@link #applyChanges()}). + * @return working content of the browser + */ + String getText() { + if (textCache == null) { + textCache= browser.getText(); + } + return textCache; + } + + /** + * Sets working content of the browser, i.e. content to be set inside the browser on next call to {@link #applyChanges()}. + * @param text new content + */ + void setText(String text) { + textCache= text; + } + + /** + * Commits working content to the browser if any was set since last time actual content inside browser was changed + * (e.g. by {@link #applyChanges()}). + */ + public void applyChanges() { + if (textCache != null) { + browser.setText(textCache); + textCache= null; + } + } + + public void addContentChangedListener(IBrowserContentChangeListener listener) { + listeners.add(listener); + } + + public void removeContentChangedListener(IBrowserContentChangeListener listener) { + listeners.remove(listener); + } + + // to avoid BrowserTextAccessor itself implementing LocationListener + private class BrowserTextAccessorLocationListener implements LocationListener { + + @Override + public void changing(LocationEvent event) { + if (browser == null && event.widget instanceof Browser) { + browser= (Browser) event.widget; + } + } + + @Override + public void changed(LocationEvent event) { + textCache= null; + listeners.forEach(listener -> listener.browserContentChanged(BrowserTextAccessor.this::getText)); + } + } + + } + + /** + * Listener for browser content changes. + */ + public interface IBrowserContentChangeListener { + + /** + * Notification when content inside the browser accessed through {@link BrowserTextAccessor} was changed. + * @param contentAccessor supplier of the new browser content + */ + void browserContentChanged(Supplier contentAccessor); + } + + /** + * Handle returned by {@link #createCheckboxInBrowserLocator(String)}. + */ + public interface ICheckboxInBrowserLocator { + // handle interface + } + + private static class CheckboxInBrowserLocatorImpl implements ICheckboxInBrowserLocator { + private final String checkboxId; + private final Pattern checkboxHtmlFragment; + + public CheckboxInBrowserLocatorImpl(String checkboxId) { + this.checkboxId= checkboxId; + this.checkboxHtmlFragment= Pattern.compile("]*?id=['\"]" + checkboxId + "['\"][^>]*?>"); //$NON-NLS-1$ //$NON-NLS-2$; + } + + @Override + public String toString() { + return "CheckboxInBrowserLocatorImpl[checkboxId: " + checkboxId + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /** + * Creates checkbox locator handle required for using methods performing actions on specified checkbox inside the browser's content. + * @param checkboxId id attribute of the checkbox's <input> element + * @return checkbox locator handle + */ + public static ICheckboxInBrowserLocator createCheckboxInBrowserLocator(String checkboxId) { + return new CheckboxInBrowserLocatorImpl(checkboxId); + } + + /** + * Returns whether the target checkbox is present inside the browser's content. + * @param browserAccessor browser accessor accessing target browser content + * @param locator checkbox locator handle + * @return true if the target checkbox can be found inside the browser's content, false otherwise + */ + public static boolean isCheckboxPresentInBrowser(BrowserTextAccessor browserAccessor, ICheckboxInBrowserLocator locator) { + return Boolean.TRUE == actOnBrowser(browserAccessor, locator, + // fail-fast check first + (b, l) -> b.getText().contains(l.checkboxId) && l.checkboxHtmlFragment.matcher(b.getText()).find(), + (b, l) -> "unable to check presence of checkbox #" + l.checkboxId); //$NON-NLS-1$ + } + + /** + * Set checked attribute of the checkbox's <input> element inside the browser's content, + * i.e. if the checkbox should be "pre-selected". It does not simulate user toggling checkbox checked state (e.g. by mouse). + * @param browserAccessor browser accessor accessing target browser content + * @param locator checkbox locator handle + * @param enabled true if checked attribute should be present inside the <input>false otherwise + * @return previous presence state of the checkbox checked attribute, or {@link java.util.Optional#empty() empty()} + * if checkbox could not be found inside the browser's content + */ + public static Optional toggleCheckboxInBrowser(BrowserTextAccessor browserAccessor, ICheckboxInBrowserLocator locator, boolean enabled) { + return actOnBrowser(browserAccessor, locator, + enabled // following prevents creating new lambda instance on every call to this method + ? CheckboxInBrowserUtil::toggleCheckboxInBrowserOn + : CheckboxInBrowserUtil::toggleCheckboxInBrowserOff, + (b, l) -> "unable to toggle checkbox #" + l.checkboxId); //$NON-NLS-1$ + } + + private static Optional toggleCheckboxInBrowserOn(BrowserTextAccessor browserAccessor, CheckboxInBrowserLocatorImpl locator) { + return toggleCheckboxInBrowserImpl(browserAccessor, locator, true); + } + + private static Optional toggleCheckboxInBrowserOff(BrowserTextAccessor browserAccessor, CheckboxInBrowserLocatorImpl locator) { + return toggleCheckboxInBrowserImpl(browserAccessor, locator, false); + } + + private static Optional toggleCheckboxInBrowserImpl(BrowserTextAccessor browserAccessor, CheckboxInBrowserLocatorImpl locator, boolean enabled) { + String html= browserAccessor.getText(); + if (!html.contains(locator.checkboxId)) { // fail-fast check + // browser does not currently display content with target checkbox + return Optional.empty(); + } + var matcher= locator.checkboxHtmlFragment.matcher(browserAccessor.getText()); + if (matcher.find()) { + matcher.region(matcher.start(), matcher.end()).usePattern(CHECKED_PATERN); + if (enabled) { + if (!matcher.find()) { + int inputFragmentEnd= matcher.regionEnd() - 1; + StringBuilder sb= new StringBuilder(html.length() + 8); + sb.append(html, 0, inputFragmentEnd); + sb.append(" checked"); //$NON-NLS-1$ + sb.append(html, inputFragmentEnd, html.length()); + browserAccessor.setText(sb.toString()); + return Optional.of(Boolean.FALSE); + } else { + // checkbox was already checked in HTML + return Optional.of(Boolean.TRUE); + } + } else if (matcher.find()) { + StringBuilder sb= new StringBuilder(html.length() - (matcher.end() - matcher.start())); + sb.append(html, 0, matcher.start()); + sb.append(html, matcher.end(), html.length()); + browserAccessor.setText(sb.toString()); + return Optional.of(Boolean.TRUE); + } else { + // checkbox was already not checked in HTML + return Optional.of(Boolean.FALSE); + } + } else { + // browser does not currently display content with target checkbox + return Optional.empty(); + } + } + + private static R actOnBrowser(BrowserTextAccessor browserAccessor, ICheckboxInBrowserLocator locator, + BiFunction action, + BiFunction notInitializedMessageSuffixSupplier) { + if (locator instanceof CheckboxInBrowserLocatorImpl locatorImpl) { + if (browserAccessor.isInitlaized()) { + return action.apply(browserAccessor, locatorImpl); + } else { + // not really expected to happen, but if it does, let the user know, so that they can provide this info in case they decide to report + JavaPlugin.logErrorMessage("Browser widget not yet initialized: " //$NON-NLS-1$ + + notInitializedMessageSuffixSupplier.apply(browserAccessor, locatorImpl)); + return null; + } + } else { // illegal API usage + throw new IllegalArgumentException("Passed locator is invalid"); //$NON-NLS-1$ + } + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/CheckboxToggleInBrowserAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/CheckboxToggleInBrowserAction.java new file mode 100644 index 00000000000..909b454671d --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/CheckboxToggleInBrowserAction.java @@ -0,0 +1,38 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.browser; + +import org.eclipse.jface.action.Action; + +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.ICheckboxInBrowserLocator; + +/** + * Base class for actions that toggle checked "pre-selected" state of particular checkbox inside browser HTML content. + */ +public abstract class CheckboxToggleInBrowserAction extends Action { + private final ICheckboxInBrowserLocator checkboxLocator; + protected final BrowserTextAccessor browserAccessor; + + public CheckboxToggleInBrowserAction(String text, int style, BrowserTextAccessor browserTextAccessor, String checkboxId) { + super(text, style); + checkboxLocator= CheckboxInBrowserUtil.createCheckboxInBrowserLocator(checkboxId); + browserAccessor= browserTextAccessor; + } + + protected void toggleBrowserCheckbox(boolean enabled) { + CheckboxInBrowserUtil.toggleCheckboxInBrowser(browserAccessor, checkboxLocator, enabled); + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/HoverPreferenceStylingInBrowserAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/HoverPreferenceStylingInBrowserAction.java new file mode 100644 index 00000000000..295d1f56287 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/HoverPreferenceStylingInBrowserAction.java @@ -0,0 +1,76 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.browser; + +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; + +/** + * Base class for actions that toggle specific styling inside browser HTML content controlled by specified checkbox and support + * mouse hovering over corresponding GUI widget (e.g. toolbar or menu item) and {@link StylingPreference} states + * persisted in preference store. + */ +public abstract class HoverPreferenceStylingInBrowserAction extends CheckboxToggleInBrowserAction { + + protected StylingPreference currentPreference; + + public HoverPreferenceStylingInBrowserAction(String text, int style, BrowserTextAccessor browserAccessor, String checkboxId) { + super(text, style, browserAccessor, checkboxId); + } + + /** + * Enumeration of possible preferences for toggling specific type of additional styling inside browser viewer. + */ + public enum StylingPreference { + OFF, + ALWAYS, + HOVER + } + + protected boolean isCurrentPreferenceAlways() { + return currentPreference == StylingPreference.ALWAYS; + } + + protected abstract StylingPreference getPreferenceFromStore(); + + protected abstract void putPreferenceToStore(StylingPreference preference); + + protected abstract StylingPreference changeStylingPreference(StylingPreference oldPreference); + + protected void loadCurentPreference() { + currentPreference= getPreferenceFromStore(); + } + + protected boolean mouseEnter() { + if (!isCurrentPreferenceAlways()) { + toggleBrowserCheckbox(true); + return true; + } + return false; + } + + protected boolean mouseExit() { + if (!isCurrentPreferenceAlways()) { + toggleBrowserCheckbox(false); + return true; + } + return false; + } + + @Override + public void run() { + currentPreference= changeStylingPreference(currentPreference); + putPreferenceToStore(currentPreference); + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/HoverStylingInBrowserMenuAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/HoverStylingInBrowserMenuAction.java new file mode 100644 index 00000000000..ffa9bf8d752 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/browser/HoverStylingInBrowserMenuAction.java @@ -0,0 +1,110 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.browser; + +import org.eclipse.swt.events.ArmEvent; +import org.eclipse.swt.events.MenuEvent; +import org.eclipse.swt.widgets.Event; + +import org.eclipse.jface.action.IAction; + +import org.eclipse.jdt.internal.ui.viewsupport.MenuVisibilityMenuItemsConfigurer.IMenuVisibilityMenuItemAction; +import org.eclipse.jdt.internal.ui.viewsupport.MouseListeningMenuItemsConfigurer.IMouseListeningMenuItemAction; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; + +/** + * Base class for actions that toggle specific styling inside browser HTML content controlled by specified checkbox and support + * mouse hovering over corresponding menu item widget and + * {@link org.eclipse.jdt.internal.ui.viewsupport.browser.HoverPreferenceStylingInBrowserAction.StylingPreference} states + * persisted in preference store. + */ +public abstract class HoverStylingInBrowserMenuAction extends HoverPreferenceStylingInBrowserAction + implements IMouseListeningMenuItemAction, IMenuVisibilityMenuItemAction { + + + public HoverStylingInBrowserMenuAction(String text, BrowserTextAccessor browserAccessor, String checkboxId) { + super(text, IAction.AS_PUSH_BUTTON, browserAccessor, checkboxId); + } + + @Override + protected StylingPreference changeStylingPreference(StylingPreference oldPreference) { + return switch (oldPreference) { + case OFF -> StylingPreference.ALWAYS; + case HOVER -> StylingPreference.OFF; + case ALWAYS -> StylingPreference.HOVER; + }; + } + + @Override + public boolean mouseEnter(ArmEvent event) { + return mouseEnter(); + } + + @Override + public boolean mouseExit(ArmEvent event) { + return mouseExit(); + } + + @SuppressWarnings("unused") + public boolean menuButtonMouseEnter(Event event) { + if (currentPreference == StylingPreference.HOVER) { + toggleBrowserCheckbox(true); + return true; + } + return false; + } + + @SuppressWarnings("unused") + public boolean menuButtonMouseExit(Event event) { + if (currentPreference == StylingPreference.HOVER) { + toggleBrowserCheckbox(false); + return true; + } + return false; + } + + protected abstract void presentCurrentPreference(); + + @Override + public void loadCurentPreference() { + super.loadCurentPreference(); + presentCurrentPreference(); + } + + @Override + public boolean menuShown(MenuEvent e) { + loadCurentPreference(); + return false; + } + + @Override + public boolean menuHidden(MenuEvent e) { + return mouseExit(); + } + + @Override + public void run() { + super.run(); + presentCurrentPreference(); + + if (currentPreference == StylingPreference.OFF) { + // we just switched styling off so temporarily we want styling not applied + toggleBrowserCheckbox(false); + } else { + toggleBrowserCheckbox(true); // menu will be displayed again with mouse cursor again being on this item, thus same as being armed + } + browserAccessor.applyChanges(); + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocEnrichmentImageDescriptor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocEnrichmentImageDescriptor.java new file mode 100644 index 00000000000..eb7df62ffc6 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocEnrichmentImageDescriptor.java @@ -0,0 +1,56 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import org.eclipse.swt.graphics.Point; + +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; + +import org.eclipse.jdt.internal.ui.JavaPluginImages; + +/** + * Image descriptor adding mouse pointer overlay to the bottom right corner of the image + */ +class JavadocEnrichmentImageDescriptor extends CompositeImageDescriptor { + private final ImageDescriptor baseImage; + private final Point size; + + public JavadocEnrichmentImageDescriptor(ImageDescriptor baseImage) { + this.baseImage= baseImage; + CachedImageDataProvider provider= createCachedImageDataProvider(baseImage); + size= new Point(provider.getWidth(), provider.getHeight()); + } + + @Override + protected void drawCompositeImage(int width, int height) { + drawImage(createCachedImageDataProvider(baseImage), 0, 0); + drawCursorOverlay(); + } + + @Override + protected Point getSize() { + return size; + } + + private void drawCursorOverlay() { + CachedImageDataProvider provider= createCachedImageDataProvider(JavaPluginImages.DESC_OVR_MOUSE_CURSOR); + int x= size.x - provider.getWidth(); + int y= size.y - provider.getHeight(); + if (x >= 0 && y >= 0) { + drawImage(provider, x, y); + } + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocStylingMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocStylingMessages.java new file mode 100644 index 00000000000..b2a8fa1fb01 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocStylingMessages.java @@ -0,0 +1,57 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import org.eclipse.osgi.util.NLS; + +/** + * Helper class to get NLSed messages. + */ +public class JavadocStylingMessages extends NLS { + + private static final String BUNDLE_NAME= JavadocStylingMessages.class.getName(); + + private JavadocStylingMessages() { + // Do not instantiate + } + + public static String JavadocStyling_styling_typeParamsReferencesColoring; + public static String JavadocStyling_styling_typeParamsLevelsColoring; + public static String JavadocStyling_styling_formatting; + public static String JavadocStyling_styling_wrapping; + + public static String JavadocStyling_stylingMenu; + public static String JavadocStyling_noEnhancementsTooltip; + public static String JavadocStyling_colorPreferences_menu; + public static String JavadocStyling_colorPreferences_typeParameterReference; + public static String JavadocStyling_colorPreferences_typeParameterLevel; + public static String JavadocStyling_colorPreferences_resetAll; + public static String JavadocStyling_colorPreferences_noTypeParameters; + public static String JavadocStyling_colorPreferences_unusedTypeParameter; + public static String JavadocStyling_stylingTooltip_prefix; + public static String JavadocStyling_stylingTooltip_preference_off; + public static String JavadocStyling_stylingTooltip_preference_hover; + public static String JavadocStyling_stylingTooltip_preference_always; + + public static String JavadocStyling_stylingPreview_watermark; + public static String JavadocStyling_stylingPreview_typeParamsReferencesColoring; + public static String JavadocStyling_stylingPreview_typeParamsLevelsColoring; + public static String JavadocStyling_stylingPreview_formatting; + public static String JavadocStyling_stylingPreview_wrapping; + + static { + NLS.initializeMessages(BUNDLE_NAME, JavadocStylingMessages.class); + } + +} diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocStylingMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocStylingMessages.properties new file mode 100644 index 00000000000..78ba1e15ba5 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/JavadocStylingMessages.properties @@ -0,0 +1,36 @@ +############################################################################### +# Copyright (c) 2024 Jozef Tomek and others. +# +# This program and the accompanying materials +# are made available under the terms of the Eclipse Public License 2.0 +# which accompanies this distribution, and is available at +# https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# +# Contributors: +# Jozef Tomek - initial API and implementation +############################################################################### +JavadocStyling_styling_typeParamsReferencesColoring=Type Parameters References Coloring +JavadocStyling_styling_typeParamsLevelsColoring=Type Parameters Levels Coloring +JavadocStyling_styling_formatting=Formatting +JavadocStyling_styling_wrapping=Wrapping + +JavadocStyling_stylingMenu=Styling menu +JavadocStyling_noEnhancementsTooltip=No styling enhancements available for JavaDoc +JavadocStyling_colorPreferences_menu=Colors Preferences +JavadocStyling_colorPreferences_typeParameterReference=Type Parameter Reference #{0} +JavadocStyling_colorPreferences_typeParameterLevel=Type Parameters Level #{0} +JavadocStyling_colorPreferences_resetAll=Restore Defaults +JavadocStyling_colorPreferences_noTypeParameters=No Type Parameters In Javadoc +JavadocStyling_colorPreferences_unusedTypeParameter=Color not used In Javadoc +JavadocStyling_stylingTooltip_prefix=Current preference: {0} +JavadocStyling_stylingTooltip_preference_off=never applied +JavadocStyling_stylingTooltip_preference_hover=applied on mouse hover +JavadocStyling_stylingTooltip_preference_always=always applied + +JavadocStyling_stylingPreview_watermark=PREVIEW +JavadocStyling_stylingPreview_typeParamsReferencesColoring=TPR +JavadocStyling_stylingPreview_typeParamsLevelsColoring=TPL +JavadocStyling_stylingPreview_formatting=FMT +JavadocStyling_stylingPreview_wrapping=WRP \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingColorPreferenceMenuItem.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingColorPreferenceMenuItem.java new file mode 100644 index 00000000000..d5d45a0c3a6 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingColorPreferenceMenuItem.java @@ -0,0 +1,84 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.ImageDataProvider; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.ColorDialog; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.resource.ImageDescriptor; + +import org.eclipse.jdt.internal.corext.util.Messages; + +/** + * Menu item action for particular color index used for coloring aspect of javadoc styling that presents + * current color preference persisted in preference store and allows to change it via native color picker. + */ +class SignatureStylingColorPreferenceMenuItem extends Action implements ImageDataProvider { + private final Shell shell; + private final Integer colorIdx; + private final Function colorPreferenceGetter; + private final BiConsumer colorPreferenceSetter; + + SignatureStylingColorPreferenceMenuItem(Shell shell, String textPrefix, Integer colorIdx, Function colorPreferenceGetter, BiConsumer colorPreferenceSetter) { + super(Messages.format(textPrefix, colorIdx)); + this.shell= shell; + this.colorIdx= colorIdx; + this.colorPreferenceGetter= colorPreferenceGetter; + this.colorPreferenceSetter= colorPreferenceSetter; + setId(SignatureStylingColorPreferenceMenuItem.class.getSimpleName() + "_" + colorIdx); //$NON-NLS-1$ + setImageDescriptor(ImageDescriptor.createFromImageDataProvider(this)); + } + + @Override + public ImageData getImageData(int zoom) { + Image image= new Image(shell.getDisplay(), 16, 16); + GC gc= new GC(image); + + gc.setForeground(shell.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BORDER)); + gc.setBackground(new Color(shell.getDisplay(), getCurrentColor())); + gc.fillRectangle(image.getBounds()); + gc.setLineWidth(2); + gc.drawRectangle(image.getBounds()); + gc.dispose(); + + ImageData data= image.getImageData(zoom); + image.dispose(); + return data; + } + + private RGB getCurrentColor() { + return colorPreferenceGetter.apply(colorIdx); + } + + @Override + public void run() { + ColorDialog colorDialog= new ColorDialog(shell); + colorDialog.setRGB(getCurrentColor()); + RGB newColor= colorDialog.open(); + if (newColor != null) { + colorPreferenceSetter.accept(colorIdx, newColor); + } + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingColorSubMenuItem.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingColorSubMenuItem.java new file mode 100644 index 00000000000..4acea1775b5 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingColorSubMenuItem.java @@ -0,0 +1,171 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import java.util.NoSuchElementException; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.eclipse.swt.events.MenuEvent; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IMenuCreator; +import org.eclipse.jface.action.Separator; + +import org.eclipse.jdt.internal.corext.util.Messages; + +import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; +import org.eclipse.jdt.internal.ui.viewsupport.MenuVisibilityMenuItemsConfigurer.IMenuVisibilityMenuItemAction; + +/** + * Menu item action for building & presenting color preferences sub-menu of javadoc styling menu. + */ +class SignatureStylingColorSubMenuItem extends Action implements IMenuCreator, IMenuVisibilityMenuItemAction { + private final Shell parentShell; + private final Supplier javadocContentSupplier; + private Menu menu= null; + + public SignatureStylingColorSubMenuItem(Shell parent, Supplier javadocContentSupplier) { + super(JavadocStylingMessages.JavadocStyling_colorPreferences_menu, AS_DROP_DOWN_MENU); + this.parentShell= parent; + this.javadocContentSupplier= javadocContentSupplier; + setMenuCreator(this); + } + + @Override + public Menu getMenu(Menu parent) { + var content= javadocContentSupplier.get(); + if (menu == null && content != null) { + menu= new Menu(parent); + + new ActionContributionItem(new ResetSignatureStylingColorsPreferencesMenuItem()).fill(menu, -1); + new Separator().fill(menu, -1); + + int typeParamsReferencesCount= JavaElementLinks.getNumberOfTypeParamsReferences(content); + for (int i= 1; i <= typeParamsReferencesCount; i++) { + var item= new ActionContributionItem(new SignatureStylingColorPreferenceMenuItem( + parentShell, + JavadocStylingMessages.JavadocStyling_colorPreferences_typeParameterReference, + i, + JavaElementLinks::getColorPreferenceForTypeParamsReference, + JavaElementLinks::setColorPreferenceForTypeParamsReference)); + item.fill(menu, -1); + } + int typeParamsLevelsCount= JavaElementLinks.getNumberOfTypeParamsLevels(content); + for (int i= 1; i <= typeParamsLevelsCount; i++) { + var item= new ActionContributionItem(new SignatureStylingColorPreferenceMenuItem( + parentShell, + JavadocStylingMessages.JavadocStyling_colorPreferences_typeParameterLevel, + i, + JavaElementLinks::getColorPreferenceForTypeParamsLevel, + JavaElementLinks::setColorPreferenceForTypeParamsLevel)); + item.fill(menu, -1); + } + if (typeParamsReferencesCount == 0 && typeParamsLevelsCount == 0) { + new ActionContributionItem(new NoSignatureStylingTypeParametersMenuItem()).fill(menu, -1); + } + + var typeParamsReferenceIndices= JavaElementLinks.getColorPreferencesIndicesForTypeParamsReference(); + var typeParamsLevelIndices= JavaElementLinks.getColorPreferencesIndicesForTypeParamsLevel(); + + if (typeParamsReferenceIndices[typeParamsReferenceIndices.length - 1] > typeParamsReferencesCount + || typeParamsLevelIndices[typeParamsLevelIndices.length - 1] > typeParamsLevelsCount) { + new Separator().fill(menu, -1); + } + + for (int i= 0; i < typeParamsReferenceIndices.length; i++) { + if (typeParamsReferenceIndices[i] > typeParamsReferencesCount) { + new ActionContributionItem(new UnusedColorPreferenceMenuItem( + JavadocStylingMessages.JavadocStyling_colorPreferences_typeParameterReference, "ref", typeParamsReferenceIndices[i])) //$NON-NLS-1$ + .fill(menu, -1); + } + } + for (int i= 0; i < typeParamsLevelIndices.length; i++) { + if (typeParamsLevelIndices[i] > typeParamsLevelsCount) { + new ActionContributionItem(new UnusedColorPreferenceMenuItem( + JavadocStylingMessages.JavadocStyling_colorPreferences_typeParameterLevel, "lvl", typeParamsLevelIndices[i])) //$NON-NLS-1$ + .fill(menu, -1); + } + } + } + return menu; + } + + @Override + public Menu getMenu(Control parent) { + return null; + } + + @Override + public void dispose() { + if (menu != null) { + menu.dispose(); + menu= null; + } + } + + @Override + public boolean menuShown(MenuEvent e) { + if (menu != null) { + var parentMenu= ((Menu) e.widget); + // jface creates & displays proxies for sub-menus so just modifying items in sub-menu we return won't work, but we have to remove whole sub-menu item from menu + var menuItem= Stream.of(parentMenu.getItems()) + .filter(mi -> mi.getData() instanceof ActionContributionItem aci && aci.getAction() == this) + .findFirst().orElseThrow(() -> new NoSuchElementException( + "This " + //$NON-NLS-1$ + SignatureStylingColorSubMenuItem.class.getSimpleName() + + " instance not found inside menu being shown")); //$NON-NLS-1$ + // can't be done in menuHidden() since SWT.Selection is fired after SWT.Hide, thus run() action would not be executed since item would be disposed + menuItem.dispose(); + + // re-add the sub-mebu as new menu item (it's OK we don't do ReappearingMenuToolbarAction.addMenuItem() since for sub-menu items following is effectively the same) + var item= new ActionContributionItem(this); + item.fill(parentMenu, -1); + } + return false; + } + + private static final class ResetSignatureStylingColorsPreferencesMenuItem extends Action { + public ResetSignatureStylingColorsPreferencesMenuItem() { + super(JavadocStylingMessages.JavadocStyling_colorPreferences_resetAll); + setId(ResetSignatureStylingColorsPreferencesMenuItem.class.getSimpleName()); + } + + @Override + public void run() { + JavaElementLinks.resetAllColorPreferencesToDefaults(); + } + } + + private static final class NoSignatureStylingTypeParametersMenuItem extends Action { + public NoSignatureStylingTypeParametersMenuItem() { + super(JavadocStylingMessages.JavadocStyling_colorPreferences_noTypeParameters); + setId(NoSignatureStylingTypeParametersMenuItem.class.getSimpleName()); + setEnabled(false); + } + } + + private static final class UnusedColorPreferenceMenuItem extends Action { + public UnusedColorPreferenceMenuItem(String textPrefix, String idPostfix, int colorIdx) { + super(Messages.format(textPrefix, colorIdx)); + setId(UnusedColorPreferenceMenuItem.class.getSimpleName() + "_" + idPostfix + "_" + colorIdx); //$NON-NLS-1$ //$NON-NLS-2$ + setToolTipText(JavadocStylingMessages.JavadocStyling_colorPreferences_unusedTypeParameter); + setEnabled(false); + } + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingMenuToolbarAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingMenuToolbarAction.java new file mode 100644 index 00000000000..3931c35c820 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/SignatureStylingMenuToolbarAction.java @@ -0,0 +1,161 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MenuEvent; +import org.eclipse.swt.events.MenuListener; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.ToolItem; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.resource.ImageDescriptor; + +import org.eclipse.jdt.internal.ui.JavaPluginImages; +import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; +import org.eclipse.jdt.internal.ui.viewsupport.MenuVisibilityMenuItemsConfigurer; +import org.eclipse.jdt.internal.ui.viewsupport.MouseListeningMenuItemsConfigurer; +import org.eclipse.jdt.internal.ui.viewsupport.MouseListeningToolItemsConfigurer.IMouseListeningToolbarItemAction; +import org.eclipse.jdt.internal.ui.viewsupport.ReappearingMenuToolbarAction; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.IBrowserContentChangeListener; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.ICheckboxInBrowserLocator; +import org.eclipse.jdt.internal.ui.viewsupport.browser.HoverStylingInBrowserMenuAction; + +/** + * Toolbar item action for building & presenting javadoc styling menu. + */ +public class SignatureStylingMenuToolbarAction extends ReappearingMenuToolbarAction implements IMouseListeningToolbarItemAction, MenuListener, IBrowserContentChangeListener { + private final BrowserTextAccessor browserAccessor; + private final ICheckboxInBrowserLocator previewCheckboxLocator; + private boolean mouseExitCalled= false; + + public SignatureStylingMenuToolbarAction(Shell parent, BrowserTextAccessor browserAccessor, String preferenceKeyPrefix, Supplier javadocContentSupplier) { + this(JavadocStylingMessages.JavadocStyling_stylingMenu, JavaPluginImages.DESC_ETOOL_JDOC_HOVER_EDIT, browserAccessor, + new ToggleSignatureTypeParametersColoringAction(browserAccessor, preferenceKeyPrefix), + new ToggleSignatureTypeLevelsColoringAction(browserAccessor, preferenceKeyPrefix), + new ToggleSignatureFormattingAction(browserAccessor, preferenceKeyPrefix), + new ToggleSignatureWrappingAction(browserAccessor, preferenceKeyPrefix), + // widget for following action is being removed and re-added repeatedly, see SignatureStylingColorSubMenuItem.menuShown() + new SignatureStylingColorSubMenuItem(parent, javadocContentSupplier)); + // disable until content is loaded in browser + setEnabled(false); + setToolTipText(JavadocStylingMessages.JavadocStyling_noEnhancementsTooltip); + browserAccessor.addContentChangedListener(this); + } + + private SignatureStylingMenuToolbarAction(String text, ImageDescriptor image, BrowserTextAccessor browserAccessor, Action... actions) { + super(text, image, actions); + this.browserAccessor= browserAccessor; + previewCheckboxLocator= CheckboxInBrowserUtil.createCheckboxInBrowserLocator(JavaElementLinks.CHECKBOX_ID_PREVIEW); + setId(SignatureStylingMenuToolbarAction.class.getSimpleName()); + + // make sure actions have loaded preferences for hover to work + Stream.of(actions) + .filter(HoverStylingInBrowserMenuAction.class::isInstance) + .forEach(a -> ((HoverStylingInBrowserMenuAction) a).loadCurentPreference()); + } + + @Override + public void browserContentChanged(Supplier contentAccessor) { + var content= contentAccessor.get(); + if (content != null && !content.isBlank() // fail-fast + && CheckboxInBrowserUtil.isCheckboxPresentInBrowser(browserAccessor, previewCheckboxLocator)) { + setEnabled(true); + setToolTipText(null); + } else { + setEnabled(false); + setToolTipText(JavadocStylingMessages.JavadocStyling_noEnhancementsTooltip); + } + } + + @Override + public Menu getMenu(Control p) { + if (!menuCreated()) { + Menu retVal= super.getMenu(p); + Runnable browserContentSetter= browserAccessor::applyChanges; + MouseListeningMenuItemsConfigurer.registerForMenu(retVal, browserContentSetter); + MenuVisibilityMenuItemsConfigurer.registerForMenu(retVal, browserContentSetter); + retVal.addMenuListener(this); // must be last listener, since it commits browser text changes + return retVal; + } else { + return super.getMenu(p); + } + } + + @Override + public boolean mouseEnter(Event event) { + if (!isEnabled()) { + return false; + } + boolean retVal= false; + for (Action action : actions) { + if (action instanceof HoverStylingInBrowserMenuAction menuAction) { + retVal |= menuAction.menuButtonMouseEnter(event); + } + } + mouseExitCalled= false; + return retVal; + } + + @Override + public boolean mouseExit(Event event) { + if (!isEnabled()) { + return false; + } + for (Action action : actions) { + if (action instanceof HoverStylingInBrowserMenuAction menuAction) { + menuAction.menuButtonMouseExit(event); + } + } + return mouseExitCalled= true; + } + + @Override + public void runWithEvent(Event event) { + // simulate opening menu with arrow + Rectangle bounds= ((ToolItem) event.widget).getBounds(); + event.x= bounds.x; + event.y= bounds.y + bounds.height; + event.detail= SWT.ARROW; + ((ToolItem) event.widget).notifyListeners(SWT.Selection, event); + } + + @Override + public void menuShown(MenuEvent e) { + toggleBrowserPreviewCheckbox(true); + browserAccessor.applyChanges(); + } + + @Override + public void menuHidden(MenuEvent e) { + toggleBrowserPreviewCheckbox(false); + if (mouseExitCalled) { + // mouseExit() is not called after this when menu is being hidden after re-appearing, so trigger applyChanges() here + browserAccessor.applyChanges(); + } // else applyChanges() will be triggered from mouseExit() that will be executed after this + } + + private void toggleBrowserPreviewCheckbox(boolean enabled) { + CheckboxInBrowserUtil.toggleCheckboxInBrowser(browserAccessor, previewCheckboxLocator, enabled); + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureFormattingAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureFormattingAction.java new file mode 100644 index 00000000000..2b06a34d000 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureFormattingAction.java @@ -0,0 +1,34 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import org.eclipse.jdt.internal.ui.JavaPluginImages; +import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; + +public class ToggleSignatureFormattingAction extends ToggleSignatureStylingMenuAction { + + public ToggleSignatureFormattingAction(BrowserTextAccessor browserAccessor, String preferenceKeyPrefix) { + super(ToggleSignatureFormattingAction.class.getSimpleName(), + JavadocStylingMessages.JavadocStyling_styling_formatting, + browserAccessor, + JavaElementLinks.CHECKBOX_ID_FORMATTIG, + JavaElementLinks::getPreferenceForFormatting, + JavaElementLinks::setPreferenceForFormatting, + preferenceKeyPrefix, + JavaPluginImages.DESC_DLCL_TEMPLATE, + JavaPluginImages.DESC_ELCL_TEMPLATE, + new JavadocEnrichmentImageDescriptor(JavaPluginImages.DESC_ELCL_TEMPLATE)); + } +} diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureStylingMenuAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureStylingMenuAction.java new file mode 100644 index 00000000000..7ead628bdb0 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureStylingMenuAction.java @@ -0,0 +1,96 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.eclipse.swt.events.ArmEvent; +import org.eclipse.swt.events.MenuEvent; + +import org.eclipse.jface.resource.ImageDescriptor; + +import org.eclipse.jdt.internal.corext.util.Messages; + +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; +import org.eclipse.jdt.internal.ui.viewsupport.browser.HoverStylingInBrowserMenuAction; + +/** + * Base class for signature styling menu item actions that toggle specific styling inside browser HTML content controlled by specified checkbox + * and support mouse hovering and {@link org.eclipse.jdt.internal.ui.viewsupport.browser.HoverPreferenceStylingInBrowserAction.StylingPreference} states + * persisted in preference store and presented via different icons. + */ +public class ToggleSignatureStylingMenuAction extends HoverStylingInBrowserMenuAction { + final Function stylingPreferenceGetter; + final BiConsumer stylingPreferenceSaver; + final String preferenceKeyPrefix; + final ImageDescriptor preferenceOffImage; + final ImageDescriptor preferenceAlwaysImage; + final ImageDescriptor preferenceHoverImage; + + public ToggleSignatureStylingMenuAction(String id, String text, BrowserTextAccessor browserAccessor, String checkboxId, + Function preferenceGetter, BiConsumer preferenceSaver, String preferenceKeyPrefix, + ImageDescriptor toggleOffImage, ImageDescriptor toggleOnImage, ImageDescriptor preferenceHoverImage) { + super(text, browserAccessor, checkboxId); + this.stylingPreferenceGetter= preferenceGetter; + this.stylingPreferenceSaver= preferenceSaver; + this.preferenceKeyPrefix= preferenceKeyPrefix; + this.preferenceOffImage= toggleOffImage; + this.preferenceAlwaysImage= toggleOnImage; + this.preferenceHoverImage= preferenceHoverImage; + loadCurentPreference(); + setId(id); + } + + @Override + protected void presentCurrentPreference() { + setImageDescriptor( + switch (currentPreference) { + case OFF -> preferenceOffImage; + case HOVER -> preferenceHoverImage; + case ALWAYS -> preferenceAlwaysImage; + }); + String preference= switch (currentPreference) { + case OFF -> JavadocStylingMessages.JavadocStyling_stylingTooltip_preference_off; + case HOVER -> JavadocStylingMessages.JavadocStyling_stylingTooltip_preference_hover; + case ALWAYS -> JavadocStylingMessages.JavadocStyling_stylingTooltip_preference_always; + }; + setToolTipText(Messages.format(JavadocStylingMessages.JavadocStyling_stylingTooltip_prefix, preference)); + } + + @Override + protected StylingPreference getPreferenceFromStore() { + return stylingPreferenceGetter.apply(preferenceKeyPrefix); + } + + @Override + protected void putPreferenceToStore(StylingPreference preference) { + stylingPreferenceSaver.accept(preferenceKeyPrefix, preference); + } + + @Override + public boolean mouseExit(ArmEvent event) { + if (event != null) { + return super.mouseExit(event); + } else { // menu is just being closed + return false; // do not trigger browser text changes commit, done in SignatureStylingMenuToolbarAction.menuHidden() + } + } + + @Override + public boolean menuHidden(MenuEvent e) { + super.menuHidden(e); // ignore return value + return false; // do not trigger browser text changes commit, done in SignatureStylingMenuToolbarAction.menuHidden() + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureTypeLevelsColoringAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureTypeLevelsColoringAction.java new file mode 100644 index 00000000000..ba4662a3d89 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureTypeLevelsColoringAction.java @@ -0,0 +1,34 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import org.eclipse.jdt.internal.ui.JavaPluginImages; +import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; + +public class ToggleSignatureTypeLevelsColoringAction extends ToggleSignatureStylingMenuAction { + + public ToggleSignatureTypeLevelsColoringAction(BrowserTextAccessor browserAccessor, String preferenceKeyPrefix) { + super(ToggleSignatureTypeLevelsColoringAction.class.getSimpleName(), + JavadocStylingMessages.JavadocStyling_styling_typeParamsLevelsColoring, + browserAccessor, + JavaElementLinks.CHECKBOX_ID_TYPE_PARAMETERS_LEVELS_COLORING, + JavaElementLinks::getPreferenceForTypeParamsLevelsColoring, + JavaElementLinks::setPreferenceForTypeParamsLevelsColoring, + preferenceKeyPrefix, + JavaPluginImages.DESC_DVIEW_TYPES, + JavaPluginImages.DESC_VIEW_TYPES, + new JavadocEnrichmentImageDescriptor(JavaPluginImages.DESC_VIEW_TYPES)); + } +} diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureTypeParametersColoringAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureTypeParametersColoringAction.java new file mode 100644 index 00000000000..ed3aa55a30a --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureTypeParametersColoringAction.java @@ -0,0 +1,35 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import org.eclipse.jdt.internal.ui.JavaPluginImages; +import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; + +public class ToggleSignatureTypeParametersColoringAction extends ToggleSignatureStylingMenuAction { + + public ToggleSignatureTypeParametersColoringAction(BrowserTextAccessor browserAccessor, String preferenceKeyPrefix) { + super(ToggleSignatureTypeParametersColoringAction.class.getSimpleName(), + JavadocStylingMessages.JavadocStyling_styling_typeParamsReferencesColoring, + browserAccessor, + JavaElementLinks.CHECKBOX_ID_TYPE_PARAMETERS_REFERENCES_COLORING, + JavaElementLinks::getPreferenceForTypeParamsReferencesColoring, + JavaElementLinks::setPreferenceForTypeParamsReferencesColoring, + preferenceKeyPrefix, + JavaPluginImages.DESC_DVIEW_MEMBERS, + JavaPluginImages.DESC_VIEW_MEMBERS, + new JavadocEnrichmentImageDescriptor(JavaPluginImages.DESC_VIEW_MEMBERS)); + } + +} diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureWrappingAction.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureWrappingAction.java new file mode 100644 index 00000000000..e53db516176 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/viewsupport/javadoc/ToggleSignatureWrappingAction.java @@ -0,0 +1,34 @@ +/******************************************************************************* +* Copyright (c) 2024 Jozef Tomek and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Jozef Tomek - initial API and implementation +*******************************************************************************/ +package org.eclipse.jdt.internal.ui.viewsupport.javadoc; + +import org.eclipse.jdt.internal.ui.JavaPluginImages; +import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; +import org.eclipse.jdt.internal.ui.viewsupport.browser.CheckboxInBrowserUtil.BrowserTextAccessor; + +public class ToggleSignatureWrappingAction extends ToggleSignatureStylingMenuAction { + + public ToggleSignatureWrappingAction(BrowserTextAccessor browserAccessor, String preferenceKeyPrefix) { + super(ToggleSignatureWrappingAction.class.getSimpleName(), + JavadocStylingMessages.JavadocStyling_styling_wrapping, + browserAccessor, + JavaElementLinks.CHECKBOX_ID_WRAPPING, + JavaElementLinks::getPreferenceForWrapping, + JavaElementLinks::setPreferenceForWrapping, + preferenceKeyPrefix, + JavaPluginImages.DESC_DLCL_WRAP_ALL, + JavaPluginImages.DESC_ELCL_WRAP_ALL, + new JavadocEnrichmentImageDescriptor(JavaPluginImages.DESC_ELCL_WRAP_ALL)); + } +} diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java index ddc32bfd258..901d0c2eb2c 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java @@ -59,12 +59,15 @@ import org.eclipse.jdt.internal.ui.PreferenceConstantsCore; import org.eclipse.jdt.internal.ui.callhierarchy.CallHierarchyContentProvider; import org.eclipse.jdt.internal.ui.callhierarchy.ExpandWithConstructorsConfigurationBlock; +import org.eclipse.jdt.internal.ui.infoviews.JavadocView; import org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightings; import org.eclipse.jdt.internal.ui.preferences.NewJavaProjectPreferencePage; import org.eclipse.jdt.internal.ui.preferences.formatter.FormatterProfileManager; import org.eclipse.jdt.internal.ui.text.java.CompletionProposalComputerRegistry; import org.eclipse.jdt.internal.ui.text.java.ProposalSorterRegistry; +import org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover; import org.eclipse.jdt.internal.ui.text.spelling.SpellCheckEngine; +import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; /** @@ -4346,6 +4349,11 @@ public static void initializeDefaultValues(IPreferenceStore store) { store.setDefault(EDITOR_JAVA_CODEMINING_SHOW_REFERENCES_ON_METHODS, false); store.setDefault(EDITOR_JAVA_CODEMINING_SHOW_IMPLEMENTATIONS, false); store.setDefault(EDITOR_JAVA_CODEMINING_SHOW_PARAMETER_NAMES, false); + + // Javadoc hover & view + JavaElementLinks.initDefaultPreferences(store); + JavadocHover.initDefaults(store); + JavadocView.initDefaults(store); } /**