summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Eidsness2013-12-12 08:07:59 (EST)
committer Doug Schaefer2013-12-12 14:05:53 (EST)
commitff690ab9535a09fda2c084eec8e41c484899210a (patch)
tree382766cfffcc18291c6c192b6504f14f2cee5e7b
parentc6c1ef94fc37b4a3cab7f2ef1b66e82ceaddc03d (diff)
downloadorg.eclipse.cdt-ff690ab9535a09fda2c084eec8e41c484899210a.zip
org.eclipse.cdt-ff690ab9535a09fda2c084eec8e41c484899210a.tar.gz
org.eclipse.cdt-ff690ab9535a09fda2c084eec8e41c484899210a.tar.bz2
Content assistant for Qt elementsrefs/changes/21/19721/4
This adds content assistants for QObject::connect function calls and Q_PROPERTY expansions. QObject::connect function calls look like: QObject::connect( sender, SIGNAL(someSignalFunction(int), receiver, SLOT(someSlotFunction(int)); The assistant provides proposals in the SIGNAL and SLOT expansions. The QObject for the corresponding type is used to create a list of signal or slot function signatures. Q_PROPERTY expansions look like: Q_PROPERTY( type name READ someFunction ... ) [The ... is a list of optional attributes.] The assistant proposes attribute names that have not yet been added. It also proposals appropriate values for the attribute. This patch also adds test cases for this feature. Change-Id: I0eb25235bb423c1cfcd743075331f90f269afea7 Signed-off-by: Andrew Eidsness <eclipse@jfront.com> Reviewed-on: https://git.eclipse.org/r/19721 Tested-by: Hudson CI Reviewed-by: Doug Schaefer <dschaefer@qnx.com> IP-Clean: Doug Schaefer <dschaefer@qnx.com>
-rw-r--r--qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF8
-rw-r--r--qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/ASTUtil.java217
-rw-r--r--qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtFunctionCallUtil.java132
-rw-r--r--qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReference.java108
-rw-r--r--qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceLocation.java82
-rw-r--r--qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceName.java287
-rw-r--r--qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QObject.java3
-rw-r--r--qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java50
-rw-r--r--qt/org.eclipse.cdt.qt.tests/pom.xml3
-rw-r--r--qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/AllQtTests.java3
-rw-r--r--qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QMakeTests.java (renamed from qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/SimpleTests.java)2
-rw-r--r--qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QtContentAssistantTests.java188
-rw-r--r--qt/org.eclipse.cdt.qt.ui/META-INF/MANIFEST.MF6
-rw-r--r--qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QObjectConnectCompletion.java326
-rw-r--r--qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QObjectDeclarationCompletion.java75
-rw-r--r--qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QPropertyCompletion.java59
-rw-r--r--qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QtCompletionProposalComputer.java393
-rw-r--r--qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QPropertyAttributeProposal.java506
-rw-r--r--qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QPropertyExpansion.java397
-rw-r--r--qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QtProposalContext.java94
-rw-r--r--qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QtTemplateProposal.java34
-rw-r--r--qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/ui/QtUIPlugin.java9
22 files changed, 2643 insertions, 339 deletions
diff --git a/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF b/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF
index e11a2c6..fd9fe45 100644
--- a/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF
+++ b/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF
@@ -12,6 +12,8 @@ Require-Bundle: org.eclipse.core.runtime,
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-ActivationPolicy: lazy
Bundle-Localization: plugin
-Export-Package: org.eclipse.cdt.qt.core,
- org.eclipse.cdt.qt.core.index,
- org.eclipse.cdt.internal.qt.core.index;x-friends:="org.eclipse.cdt.qt.tests"
+Export-Package: org.eclipse.cdt.internal.qt.core;x-friends:="org.eclipse.cdt.qt.ui",
+ org.eclipse.cdt.internal.qt.core.index;x-friends:="org.eclipse.cdt.qt.tests",
+ org.eclipse.cdt.internal.qt.core.parser;x-friends:="org.eclipse.cdt.qt.ui",
+ org.eclipse.cdt.qt.core,
+ org.eclipse.cdt.qt.core.index
diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/ASTUtil.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/ASTUtil.java
new file mode 100644
index 0000000..d75ebe7
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/ASTUtil.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.core;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
+import org.eclipse.cdt.core.dom.ast.IASTExpression;
+import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
+import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
+import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
+import org.eclipse.cdt.core.dom.ast.IASTName;
+import org.eclipse.cdt.core.dom.ast.IASTNode;
+import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
+import org.eclipse.cdt.core.dom.ast.IBinding;
+import org.eclipse.cdt.core.dom.ast.IType;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTExpression;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFieldReference;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTInitializerClause;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
+import org.eclipse.cdt.core.model.ICProject;
+import org.eclipse.cdt.core.model.ITranslationUnit;
+import org.eclipse.cdt.internal.core.dom.parser.ITypeContainer;
+import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPEvaluation;
+import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding;
+import org.eclipse.cdt.qt.core.index.IQMethod;
+import org.eclipse.cdt.qt.core.index.IQObject;
+import org.eclipse.core.resources.IProject;
+
+@SuppressWarnings("restriction")
+public class ASTUtil {
+
+ /**
+ * A convenience method to find the project that contains the given node. Returns null if
+ * the project cannot be found.
+ */
+ public static IProject getProject(IASTNode node) {
+ IASTTranslationUnit astTU = node.getTranslationUnit();
+ if (astTU == null)
+ return null;
+
+ ITranslationUnit tu = astTU.getOriginatingTranslationUnit();
+ if (tu == null)
+ return null;
+
+ ICProject cProject = tu.getCProject();
+ if (cProject == null)
+ return null;
+
+ return cProject.getProject();
+ }
+
+ // NOTE: This expression allows embedded line terminators (?s) for cases where the code looks like:
+ // QObject::connect( &a, SIGNAL(
+ // sig1(
+ // int
+ // ), ...
+ // The two patterns are nearly identical. The difference is because the first is for matching SIGNAL/
+ // SLOT expansions. The second is for matching the argument to that expansion.
+ public static final Pattern Regex_SignalSlotExpansion = Pattern.compile("(?s)(SIGNAL|SLOT)\\s*\\(\\s*(.*?)\\s*\\)\\s*");
+ public static final Pattern Regex_FunctionCall = Pattern.compile("(?s)\\s*(.*)\\s*\\(\\s*(.*?)\\s*\\)\\s*");
+
+ public static IType getBaseType(IType type) {
+ while (type instanceof ITypeContainer)
+ type = ((ITypeContainer) type).getType();
+ return type;
+ }
+
+ public static IType getBaseType(IASTNode node) {
+ if (node instanceof IASTIdExpression)
+ return getBaseType((IASTIdExpression) node);
+ if (node instanceof IASTFunctionCallExpression)
+ return getReceiverType((IASTFunctionCallExpression) node);
+ if (node instanceof IASTExpression)
+ return getBaseType(((IASTExpression) node).getExpressionType());
+
+ return null;
+ }
+
+ public static IType getBaseType(IASTInitializerClause init) {
+ if (!(init instanceof ICPPASTInitializerClause))
+ return null;
+
+ ICPPASTInitializerClause cppInit = (ICPPASTInitializerClause) init;
+ ICPPEvaluation eval = cppInit.getEvaluation();
+ return eval == null ? null : getBaseType(eval.getTypeOrFunctionSet(cppInit));
+ }
+
+ public static ICPPClassType getReceiverType(IASTFunctionCallExpression fncall) {
+
+ // NOTE: This cannot rely on the Evaluation because we're in a contest assist context.
+ // At this point is likely that the full function call is not complete, so at least
+ // some of the eval leads to a Problem. We don't need the Eval anyhow, just lookup
+ // the type of the receiver.
+
+ IASTExpression fnName = fncall.getFunctionNameExpression();
+ if (!(fnName instanceof ICPPASTFieldReference))
+ return null;
+
+ ICPPASTFieldReference fieldRef = (ICPPASTFieldReference) fnName;
+ ICPPASTExpression receiver = fieldRef.getFieldOwner();
+
+ IType recvType = getBaseType(receiver);
+ return recvType instanceof ICPPClassType ? (ICPPClassType) recvType : null;
+ }
+
+ /**
+ * Does not return null.
+ */
+ public static Collection<IQMethod> findMethods(IQObject qobj, QtSignalSlotReference ref) {
+ Set<IQMethod> bindings = new LinkedHashSet<IQMethod>();
+
+ Iterable<IQMethod> methods = null;
+ switch(ref.type)
+ {
+ case Signal:
+ methods = qobj.getSignals().withoutOverrides();
+ break;
+ case Slot:
+ methods = qobj.getSlots().withoutOverrides();
+ break;
+ }
+
+ if (methods != null) {
+ String qtNormalizedSig = QtMethodUtil.getQtNormalizedMethodSignature(ref.signature);
+ if (qtNormalizedSig == null)
+ return bindings;
+
+ for (IQMethod method : methods)
+ for(String signature : method.getSignatures())
+ if (signature.equals(qtNormalizedSig))
+ bindings.add(method);
+ }
+ return bindings;
+ }
+
+ public static IBinding resolveFunctionBinding(IASTFunctionCallExpression fnCall) {
+ IASTName fnName = null;
+ IASTExpression fnNameExpr = fnCall.getFunctionNameExpression();
+ if (fnNameExpr instanceof IASTIdExpression)
+ fnName = ((IASTIdExpression) fnNameExpr).getName();
+ else if (fnNameExpr instanceof ICPPASTFieldReference)
+ fnName = ((ICPPASTFieldReference) fnNameExpr).getFieldName();
+
+ return fnName == null ? null : fnName.resolveBinding();
+ }
+
+ public static ICPPASTVisibilityLabel findVisibilityLabel(ICPPMethod method, IASTNode ast) {
+ // the visibility cannot be found without an ast
+ if (ast == null)
+ return null;
+
+ // We need to get the CompTypeSpec in order to see the token that created the method's
+ // visibility specifier. The ast parameter will be either the method definition or a
+ // declaration. If it happens to be a declaration, then the CompTypeSpec is a parent of
+ // the AST and it can be accessed through public API. However, if the ast parameter happens
+ // to be a definition, then there isn't any public API (that I've found) to get to the
+ // CompTypeSpec. Instead, we cheat and use the InternalBinding.
+
+ MethodSpec methodSpec = new MethodSpec(ast);
+ if (methodSpec.clsSpec == null
+ && method instanceof ICPPInternalBinding)
+ {
+ ICPPInternalBinding internalBinding = (ICPPInternalBinding) method;
+ IASTNode[] decls = internalBinding.getDeclarations();
+ for (int i = 0; methodSpec.clsSpec == null && i < decls.length; ++i)
+ methodSpec = new MethodSpec(decls[i]);
+ }
+
+ if(methodSpec.clsSpec == null)
+ return null;
+
+ ICPPASTVisibilityLabel lastLabel = null;
+ for (IASTDeclaration decl : methodSpec.clsSpec.getMembers()) {
+ if (decl instanceof ICPPASTVisibilityLabel)
+ lastLabel = (ICPPASTVisibilityLabel) decl;
+ else if (decl == methodSpec.methodDecl)
+ return lastLabel;
+ }
+
+ return null;
+ }
+
+ private static class MethodSpec
+ {
+ public final ICPPASTCompositeTypeSpecifier clsSpec;
+ public final IASTNode methodDecl;
+
+ public MethodSpec( IASTNode node )
+ {
+ ICPPASTCompositeTypeSpecifier cls = null;
+ IASTNode mth = node;
+ while( mth != null && cls == null )
+ {
+ IASTNode parent = mth.getParent();
+ if (parent instanceof ICPPASTCompositeTypeSpecifier)
+ cls = (ICPPASTCompositeTypeSpecifier) parent;
+ else
+ mth = parent;
+ }
+
+ clsSpec = cls;
+ methodDecl = mth;
+ }
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtFunctionCallUtil.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtFunctionCallUtil.java
new file mode 100644
index 0000000..01a074d
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtFunctionCallUtil.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.core;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
+import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
+import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
+import org.eclipse.cdt.core.dom.ast.IASTName;
+import org.eclipse.cdt.core.dom.ast.IASTNode;
+import org.eclipse.cdt.core.dom.ast.IBinding;
+import org.eclipse.cdt.qt.core.QtKeywords;
+
+/**
+ * Utility for managing interaction with QObject::connect and QObject::disconnect function
+ * calls. These function calls can contain two expansions. The first is always SIGNAL,
+ * the second is either SIGNAL (which will cause the second signal to be emitted when the
+ * first is received) or SLOT (which will cause the slot to be evaluate when the signal
+ * is received). This class follows the Qt convention of calling the first SIGNAL expansion
+ * the sender and the second (which could be SIGNAL or SLOT) the receiver.
+ *
+ * In the following examples, the type of the signal is the type of the q_sender variable.
+ * The type of the method is the type of the q_receiver variable. The variable q_unrelated is
+ * some instance that is not needed for either case.
+ * <pre>
+ * QObject::connect( q_sender, SIGNAL(destroyed()), q_receiver, SIGNAL() );
+ * QObject::connect( q_sender, SIGNAL(destroyed()), q_receiver, SLOT(deleteLater()) );
+ * QObject::connect( q_sender, SIGNAL(destroyed()), q_receiver, SIGNAL(), Qt::AutoConnection );
+ * QObject::connect( q_sender, SIGNAL(destroyed()), q_receiver, SLOT(deleteLater()), Qt::AutoConnection );
+ * q_unrelated->connect( q_sender, SIGNAL(destroyed()), q_receiver, SIGNAL() );
+ * q_unrelated->connect( q_sender, SIGNAL(destroyed()), q_receiver, SLOT(deleteLater()) );
+ * q_unrelated->connect( q_sender, SIGNAL(destroyed()), q_receiver, SIGNAL(), Qt::AutoConnection );
+ * q_unrelated->connect( q_sender, SIGNAL(destroyed()), q_receiver, SLOT(deleteLater()), Qt::AutoConnection );
+ *
+ * q_receiver->connect( q_sender, SIGNAL(destroyed()), SIGNAL() );
+ * q_receiver->connect( q_sender, SIGNAL(destroyed()), SLOT() );
+ * q_receiver->connect( q_sender, SIGNAL(destroyed()), SIGNAL(), Qt::AutoConnection );
+ * q_receiver->connect( q_sender, SIGNAL(destroyed()), SLOT(), Qt::AutoConnection );
+ *
+ * QObject::disconnect( q_sender, SIGNAL(), q_receiver, SIGNAL() );
+ * QObject::disconnect( q_sender, SIGNAL(), q_receiver, SLOT() );
+ * q_unrelated->disconnect( q_sender, SIGNAL(), q_receiver, SIGNAL() );
+ * q_unrelated->disconnect( q_sender, SIGNAL(), q_receiver, SLOT() );
+ *
+ * q_sender->disconnect( SIGNAL(), q_receiver, SIGNAL() );
+ * q_sender->disconnect( SIGNAL(), q_receiver, SLOT() );
+ * q_sender->disconnect( SIGNAL(), q_receiver );
+ * q_sender->disconnect( SIGNAL() );
+ * q_sender->disconnect();
+ * </pre>
+ */
+public class QtFunctionCallUtil {
+
+ private static final Pattern SignalRegex = Pattern.compile("^\\s*" + QtKeywords.SIGNAL + ".*");
+ private static final Pattern MethodRegex = Pattern.compile("^\\s*(?:" + QtKeywords.SIGNAL + '|' + QtKeywords.SLOT + ").*");
+
+ /**
+ * Return true if the specified name is a QObject::connect or QObject::disconnect function
+ * and false otherwise.
+ */
+ public static boolean isQObjectFunctionCall(IASTCompletionContext astContext, boolean isPrefix, IASTName name) {
+ if (name == null)
+ return false;
+
+ // Bug332201: Qt content assist should always be applied to the most specific part of
+ // the target name.
+ IBinding[] funcBindings = astContext.findBindings(name.getLastName(), isPrefix);
+ for (IBinding funcBinding : funcBindings)
+ if (QtKeywords.is_QObject_connect(funcBinding)
+ || QtKeywords.is_QObject_disconnect(funcBinding))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Returns true if the given function call argument is a SIGNAL or SLOT expansion
+ * and false otherwise.
+ public static boolean isQtMethodExpansion(IASTInitializerClause arg) {
+ return MethodRegex.matcher(arg.getRawSignature()).matches();
+ }
+ */
+
+ /**
+ * If the given argument is a SIGNAL or SLOT expansion then find and return the node in the AST
+ * that will be used for this method. Returns null if the argument is not a Qt method call or
+ * if the associated node cannot be found.
+ */
+ public static IASTNode getTypeNode(IASTFunctionCallExpression call, IASTInitializerClause[] args, int argIndex) {
+ int sigExpIndex = getExpansionArgIndex(args, 0, SignalRegex);
+ if (argIndex == sigExpIndex)
+ return getSignalTargetNode(sigExpIndex, call, args);
+
+ int methodExpIndex = getExpansionArgIndex(args, sigExpIndex + 1, MethodRegex);
+ if (argIndex == methodExpIndex)
+ return getMethodTargetNode(methodExpIndex, sigExpIndex, call, args);
+
+ // Otherwise the given argument is not a SIGNAL or SLOT expansion.
+ return null;
+ }
+
+ private static IASTNode getSignalTargetNode(int sigExpIndex, IASTFunctionCallExpression call, IASTInitializerClause[] args) {
+ // When the SIGNAL expansion is first, the type is based on the receiver of
+ // the function call. Otherwise the type is the previous argument.
+ return sigExpIndex == 0 ? call : args[sigExpIndex - 1];
+ }
+
+ private static IASTNode getMethodTargetNode(int methodExpIndex, int sigExpIndex, IASTFunctionCallExpression call, IASTInitializerClause[] args) {
+ // If the method is right after the signal, then the type is based on the receiver
+ // of the function call. Otherwise the method type is based on the parameter right
+ // before the expansion.
+ return (methodExpIndex == (sigExpIndex + 1)) ? call : args[methodExpIndex - 1];
+ }
+
+ private static int getExpansionArgIndex(IASTInitializerClause[] args, int begin, Pattern macroNameRegex) {
+ for(int i = begin; i < args.length; ++i) {
+ IASTInitializerClause arg = args[i];
+ String raw = arg.getRawSignature();
+ Matcher m = macroNameRegex.matcher(raw);
+ if (m.matches())
+ return i;
+ }
+ return -1;
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReference.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReference.java
new file mode 100644
index 0000000..dac7389
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReference.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.core;
+
+import java.util.regex.Matcher;
+
+import org.eclipse.cdt.core.dom.ast.IASTName;
+import org.eclipse.cdt.core.dom.ast.IASTNode;
+import org.eclipse.cdt.core.dom.ast.IBinding;
+import org.eclipse.cdt.qt.core.QtKeywords;
+
+public class QtSignalSlotReference
+{
+ public static enum Type
+ {
+ Signal( "sender", "SIGNAL", "signal" ),
+ Slot ( "receiver", "SLOT", "slot" );
+
+ public final String roleName;
+ public final String macroName;
+ public final String paramName;
+
+ public boolean matches( Type other )
+ {
+ if( other == null )
+ return false;
+
+ // The signal parameter must be a SIGNAL, but the slot could be a
+ // SLOT or a SIGNAL.
+ return this == Signal ? other == Signal : true;
+ }
+
+ private Type( String roleName, String macroName, String paramName )
+ {
+ this.roleName = roleName;
+ this.macroName = macroName;
+ this.paramName = paramName;
+ }
+ }
+
+ public final IASTNode parent;
+ public final IASTNode node;
+ public final Type type;
+ public final String signature;
+ public final int offset;
+ public final int length;
+
+ private QtSignalSlotReference( IASTNode parent, IASTNode node, Type type, String signature, int offset, int length )
+ {
+ this.parent = parent;
+ this.node = node;
+ this.type = type;
+ this.signature = signature;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ public IASTName createName( IBinding binding )
+ {
+ return new QtSignalSlotReferenceName( parent, node, signature, offset, length, binding );
+ }
+
+ public static QtSignalSlotReference parse( IASTNode parent, IASTNode arg )
+ {
+ // This check will miss cases like:
+ // #define MY_SIG1 SIGNAL
+ // #define MY_SIG2(s) SIGNAL(s)
+ // #define MY_SIG3(s) SIGNAL(signal())
+ // connect( &a, MY_SIG1(signal()), ...
+ // connect( &a, MY_SIG2(signal()), ...
+ // connect( &a, MY_SIG2, ...
+ // This could be improved by adding tests when arg represents a macro expansion. However, I'm
+ // not sure if we would be able to follow the more complicated case of macros that call functions
+ // that use the SIGNAL macro. For now I've implemented the simpler check of forcing the call to
+ // use the SIGNAL/SLOT macro directly.
+ String raw = arg.getRawSignature();
+ Matcher m = ASTUtil.Regex_SignalSlotExpansion.matcher( raw );
+ if( ! m.matches() )
+ return null;
+
+ Type type;
+ String macroName = m.group( 1 );
+ if( QtKeywords.SIGNAL.equals( macroName ) )
+ type = Type.Signal;
+ else if( QtKeywords.SLOT.equals( macroName ) )
+ type = Type.Slot;
+ else
+ return null;
+
+ // Get the argument to the SIGNAL/SLOT macro and the offset/length of that argument within the
+ // complete function argument. E.g., with this argument to QObject::connect
+ // SIGNAL( signal(int) )
+ // the values are
+ // expansionArgs: "signal(int)"
+ // expansionOffset: 8
+ // expansionLength: 11
+ String expansionArgs = m.group( 2 );
+ int expansionOffset = m.start( 2 );
+ int expansionLength = m.end( 2 ) - expansionOffset;
+
+ return new QtSignalSlotReference( parent, arg, type, expansionArgs, expansionOffset, expansionLength );
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceLocation.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceLocation.java
new file mode 100644
index 0000000..5b36796
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceLocation.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.core;
+
+import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
+import org.eclipse.cdt.core.dom.ast.IASTImageLocation;
+import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement;
+
+/**
+ * The location of the signal/slot reference is stored as the location of the parent
+ * macro expansion + an offset, which is the number of characters between the start
+ * of the expansion and the start of the argument (including whitespace). E.g. in,
+ *
+ * <pre>
+ * SIGNAL( signal1( int ) )
+ * ^ ^ ^ c: end of reference name
+ * | +------------- b: start of reference name
+ * +--------------------- a: start of macro expansion
+ * </pre>
+ *
+ * The offset is b - a and length is c - a. This means that the result of 'Find
+ * References' will highlight just "signal( int )".
+ *
+ * @see QtSignalSlotReferenceName
+ */
+public class QtSignalSlotReferenceLocation implements IASTImageLocation {
+
+ private final IASTFileLocation referenceLocation;
+ private final int offset;
+ private final int length;
+
+ public QtSignalSlotReferenceLocation(IASTFileLocation referenceLocation, int offset, int length) {
+ this.referenceLocation = referenceLocation;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ @Override
+ public int getLocationKind() {
+ return IASTImageLocation.ARGUMENT_TO_MACRO_EXPANSION;
+ }
+
+ @Override
+ public int getNodeOffset() {
+ return referenceLocation.getNodeOffset() + offset;
+ }
+
+ @Override
+ public int getNodeLength() {
+ return length;
+ }
+
+ @Override
+ public String getFileName() {
+ return referenceLocation.getFileName();
+ }
+
+ @Override
+ public IASTFileLocation asFileLocation() {
+ return referenceLocation;
+ }
+
+ @Override
+ public int getEndingLineNumber() {
+ return referenceLocation.getEndingLineNumber();
+ }
+
+ @Override
+ public int getStartingLineNumber() {
+ return referenceLocation.getStartingLineNumber();
+ }
+
+ @Override
+ public IASTPreprocessorIncludeStatement getContextInclusionStatement() {
+ return referenceLocation.getContextInclusionStatement();
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceName.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceName.java
new file mode 100644
index 0000000..46f7259
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/QtSignalSlotReferenceName.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.core;
+
+import org.eclipse.cdt.core.dom.ILinkage;
+import org.eclipse.cdt.core.dom.ast.ASTNodeProperty;
+import org.eclipse.cdt.core.dom.ast.ASTVisitor;
+import org.eclipse.cdt.core.dom.ast.ExpansionOverlapsBoundaryException;
+import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
+import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
+import org.eclipse.cdt.core.dom.ast.IASTImageLocation;
+import org.eclipse.cdt.core.dom.ast.IASTName;
+import org.eclipse.cdt.core.dom.ast.IASTNameOwner;
+import org.eclipse.cdt.core.dom.ast.IASTNode;
+import org.eclipse.cdt.core.dom.ast.IASTNodeLocation;
+import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
+import org.eclipse.cdt.core.dom.ast.IBinding;
+import org.eclipse.cdt.core.parser.IToken;
+
+/**
+ * Signals are connected to slots by referencing them within an expansion of SIGNAL
+ * or SLOT. E.g.,
+ *
+ * <pre>
+ * class A : public QObject
+ * {
+ * Q_SIGNAL void signal1( int );
+ * Q_SLOT void slot1();
+ * };
+ * A a;
+ * QObject::connect( &a, SIGNAL( signal1( int ) ), &a, SLOT( slot1() ) );
+ * </pre>
+ *
+ * The goal is for 'Find References' on the function declarations to find the references
+ * in the macro expansions. The PDOM stores references as a linked list from the binding
+ * for the function.
+ *
+ * This class represents the name within the expansion, i.e., "signal1( int )" within
+ * "SIGNAL( signal1( int ) )" and "slot1()" within "SLOT( slot1() )".
+ */
+public class QtSignalSlotReferenceName implements IASTName {
+
+ private final IASTNode referenceNode;
+ private final String argument;
+ private final IBinding binding;
+ private final IASTImageLocation location;
+
+ private IASTNode parent;
+ private ASTNodeProperty propertyInParent;
+
+ public QtSignalSlotReferenceName(IASTNode parent, IASTNode referenceNode, String argument, int offset, int length, IBinding binding) {
+ this.parent = parent;
+ this.referenceNode = referenceNode;
+ this.argument = argument;
+ this.binding = binding;
+
+ IASTFileLocation referenceLocation = referenceNode.getFileLocation();
+ this.location
+ = referenceLocation == null
+ ? null
+ : new QtSignalSlotReferenceLocation(referenceLocation, offset, length);
+ }
+
+ @Override
+ public char[] toCharArray() {
+ return argument.toCharArray();
+ }
+
+ @Override
+ public char[] getSimpleID() {
+ return toCharArray();
+ }
+
+ @Override
+ public char[] getLookupKey() {
+ return toCharArray();
+ }
+
+ @Override
+ public IASTTranslationUnit getTranslationUnit() {
+ return referenceNode.getTranslationUnit();
+ }
+
+ @Override
+ public IASTFileLocation getFileLocation() {
+ return getImageLocation();
+ }
+
+ @Override
+ public IASTNodeLocation[] getNodeLocations() {
+ // The javadoc says that locations that are completely enclosed within a
+ // macro expansion return only the location of that expansion.
+ return referenceNode.getNodeLocations();
+ }
+
+ @Override
+ public String getContainingFilename() {
+ return referenceNode.getContainingFilename();
+ }
+
+ @Override
+ public boolean isPartOfTranslationUnitFile() {
+ return referenceNode.isPartOfTranslationUnitFile();
+ }
+
+ @Override
+ public IASTNode[] getChildren() {
+ return new IASTNode[0];
+ }
+
+ @Override
+ public IASTNode getParent() {
+ return parent;
+ }
+
+ @Override
+ public void setParent(IASTNode node) {
+ this.parent = node;
+ }
+
+ @Override
+ public ASTNodeProperty getPropertyInParent() {
+ return propertyInParent;
+ }
+
+ @Override
+ public void setPropertyInParent(ASTNodeProperty property) {
+ propertyInParent = property;
+ }
+
+ @Override
+ public boolean accept(ASTVisitor visitor) {
+ // The signal/slot reference has nothing to visit. It will have been
+ // reached by the reference node, so we can't visit that, and there is
+ // nothing else.
+ return false;
+ }
+
+ @Override
+ public String getRawSignature() {
+ // The raw signature of the reference is the text of the argument.
+ return argument;
+ }
+
+ @Override
+ public boolean contains(IASTNode node) {
+ // There aren't any nodes contained within the signal/slot reference.
+ return false;
+ }
+
+ @Override
+ public IToken getLeadingSyntax() throws ExpansionOverlapsBoundaryException, UnsupportedOperationException {
+ // The parent is the macro reference name, and this is the entire
+ // content of the arguments. Since there is nothing between these, there
+ // will not be any leading syntax.
+ return null;
+ }
+
+ @Override
+ public IToken getTrailingSyntax() throws ExpansionOverlapsBoundaryException, UnsupportedOperationException {
+ // The parent is the macro reference name, and this is the entire
+ // content of the arguments. Since there is nothing between these, there
+ // will not be any leading syntax.
+ return null;
+ }
+
+ @Override
+ public IToken getSyntax() throws ExpansionOverlapsBoundaryException {
+ // This reference to the signal/slot function is fully contained within
+ // a preprocessor node, which does not support syntax.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isFrozen() {
+ return referenceNode.isFrozen();
+ }
+
+ @Override
+ public boolean isActive() {
+ return referenceNode.isActive();
+ }
+
+ @Override
+ public int getRoleOfName(boolean allowResolution) {
+ return IASTNameOwner.r_reference;
+ }
+
+ @Override
+ public boolean isDeclaration() {
+ return false;
+ }
+
+ @Override
+ public boolean isReference() {
+ return true;
+ }
+
+ @Override
+ public boolean isDefinition() {
+ return false;
+ }
+
+ @Override
+ public IBinding getBinding() {
+ return binding;
+ }
+
+ @Override
+ public IBinding resolveBinding() {
+ return getBinding();
+ }
+
+ @Override
+ public IASTCompletionContext getCompletionContext() {
+ // Signal/slot references are fully contained within a macro expansion,
+ // so there is no completion context.
+ return null;
+ }
+
+ @Override
+ public ILinkage getLinkage() {
+ return referenceNode instanceof IASTName ? ((IASTName) referenceNode).getLinkage() : null;
+ }
+
+ @Override
+ public IASTImageLocation getImageLocation() {
+ return location;
+ }
+
+ @Override
+ public IASTName getLastName() {
+ // Signal/slot references are not qualified, so return itself.
+ return this;
+ }
+
+ @Override
+ public boolean isQualified() {
+ return false;
+ }
+
+ @Override
+ public IASTName copy() {
+ // Signal/slot references are preprocessor nodes, so they don't support
+ // copying.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public IASTName copy(CopyStyle style) {
+ // Signal/slot references are preprocessor nodes, so they don't support
+ // copying.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public IASTNode getOriginalNode() {
+ return this;
+ }
+
+ @Override
+ public void setBinding(IBinding binding) {
+ // Signal/slot references find their binding on instantiation, they
+ // never allow it to be replaced.
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public IBinding getPreBinding() {
+ return getBinding();
+ }
+
+ @Override
+ public IBinding resolvePreBinding() {
+ return getBinding();
+ }
+
+ @Override
+ public String toString() {
+ return "QtSignalSlotReference(" + new String(toCharArray()) + ')';
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QObject.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QObject.java
index 60157b7..9c95895 100644
--- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QObject.java
+++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QObject.java
@@ -48,6 +48,9 @@ public class QObject implements IQObject {
for(QtPDOMQObject base : pdomQObject.findBases()) {
QObject baseQObj = new QObject(qtIndex, cdtIndex, base);
this.bases.add(baseQObj);
+ baseSlots.addAll(baseQObj.getSlots().all());
+ baseSignals.addAll(baseQObj.getSignals().all());
+ baseInvokables.addAll(baseQObj.getInvokables().all());
baseProps.addAll(baseQObj.getProperties().all());
}
diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java
index cefcfc1..86e935b 100644
--- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java
+++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtKeywords.java
@@ -8,6 +8,10 @@
package org.eclipse.cdt.qt.core;
+import org.eclipse.cdt.core.dom.ast.DOMException;
+import org.eclipse.cdt.core.dom.ast.IBinding;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
+
/**
* Declares constants related to tokens that are special in Qt applications.
*/
@@ -36,4 +40,50 @@ public class QtKeywords {
public static final String SIGNALS = "signals";
public static final String SLOT = "SLOT";
public static final String SLOTS = "slots";
+
+ /**
+ * Returns true if the argument binding is for the QObject::connect function
+ * and false otherwise.
+ */
+ public static boolean is_QObject_connect(IBinding binding) {
+ if (binding == null)
+ return false;
+
+ // IBinding#getAdapter returns null when binding is an instance of
+ // PDOMCPPMethod.
+ if (!(binding instanceof ICPPFunction))
+ return false;
+
+ try {
+ String[] qualName = ((ICPPFunction) binding).getQualifiedName();
+ return qualName.length == 2
+ && QOBJECT.equals(qualName[0])
+ && CONNECT.equals(qualName[1]);
+ } catch (DOMException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the argument binding is for the QObject::disconnect function
+ * and false otherwise.
+ */
+ public static boolean is_QObject_disconnect(IBinding binding) {
+ if (binding == null)
+ return false;
+
+ // IBinding#getAdapter returns null when binding is an instance of
+ // PDOMCPPMethod.
+ if (!(binding instanceof ICPPFunction))
+ return false;
+
+ try {
+ String[] qualName = ((ICPPFunction) binding).getQualifiedName();
+ return qualName.length == 2
+ && QOBJECT.equals(qualName[0])
+ && DISCONNECT.equals(qualName[1]);
+ } catch (DOMException e) {
+ return false;
+ }
+ }
}
diff --git a/qt/org.eclipse.cdt.qt.tests/pom.xml b/qt/org.eclipse.cdt.qt.tests/pom.xml
index 00e5302..486b728 100644
--- a/qt/org.eclipse.cdt.qt.tests/pom.xml
+++ b/qt/org.eclipse.cdt.qt.tests/pom.xml
@@ -31,8 +31,7 @@
<artifactId>tycho-surefire-plugin</artifactId>
<version>${tycho-version}</version>
<configuration>
- <useUIHarness>false</useUIHarness>
- <!-- Core tests actually use eclipse.ui classes, see CProjectHelper -->
+ <useUIHarness>true</useUIHarness>
<argLine>${base.ui.test.vmargs} -ea -Xms256m -Xmx512m -XX:MaxPermSize=256M</argLine>
<includes>
<include>**/AllQtTests.*</include>
diff --git a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/AllQtTests.java b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/AllQtTests.java
index fb9ee92..6bae381 100644
--- a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/AllQtTests.java
+++ b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/AllQtTests.java
@@ -15,8 +15,9 @@ public class AllQtTests extends TestSuite {
public static Test suite() throws Exception {
return
new TestSuite(
- SimpleTests.class,
+ QMakeTests.class,
QObjectTests.class,
+ QtContentAssistantTests.class,
QtIndexTests.class,
QtRegressionTests.class);
}
diff --git a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/SimpleTests.java b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QMakeTests.java
index f3784f0..13aaf72 100644
--- a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/SimpleTests.java
+++ b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QMakeTests.java
@@ -18,7 +18,7 @@ import org.eclipse.cdt.internal.qt.core.index.QMakeInfo;
import org.eclipse.cdt.internal.qt.core.index.QMakeParser;
import org.eclipse.cdt.internal.qt.core.index.QMakeVersion;
-public class SimpleTests extends TestCase {
+public class QMakeTests extends TestCase {
public void testQMakeVersion() throws Exception {
// Make sure null is returned for invalid version strings.
diff --git a/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QtContentAssistantTests.java b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QtContentAssistantTests.java
new file mode 100644
index 0000000..48b18d0
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.tests/src/org/eclipse/cdt/qt/tests/QtContentAssistantTests.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.qt.tests;
+
+import junit.framework.TestCase;
+
+import org.eclipse.cdt.core.dom.ast.IASTCompletionNode;
+import org.eclipse.cdt.core.model.ICProject;
+import org.eclipse.cdt.core.model.ITranslationUnit;
+import org.eclipse.cdt.internal.qt.ui.assist.QPropertyExpansion;
+import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.ui.IEditorPart;
+
+public class QtContentAssistantTests extends TestCase {
+
+ public void testQPropertyProposals() throws Exception {
+
+ String decl = "Q_PROPERTY( type name READ accessor WRITE modifier )";
+ int atEnd = decl.length();
+ int atRParen = decl.indexOf(')');
+ int afterModifier = decl.indexOf("modifier") + "modifier".length();
+ int afterWRITE = decl.indexOf("WRITE") + "WRITE".length();
+ int inModifier = decl.indexOf("modifier") + 3;
+ int inWRITE = decl.indexOf("WRITE") + 3;
+
+ IDocument doc = new Document(decl);
+
+ // The expansion is not applicable when invoked after the closing paren
+ QPropertyExpansion exp = QPropertyExpansion.create(new Context(doc, atEnd));
+ assertNull(exp);
+
+ exp = QPropertyExpansion.create(new Context(doc, atRParen));
+ assertNotNull(exp);
+ assertNull(exp.getCurrIdentifier());
+ assertEquals("modifier", exp.getPrevIdentifier());
+
+ exp = QPropertyExpansion.create(new Context(doc, afterModifier));
+ assertNotNull(exp);
+ assertEquals("modifier", exp.getCurrIdentifier());
+ assertEquals("WRITE", exp.getPrevIdentifier());
+
+ exp = QPropertyExpansion.create(new Context(doc, afterWRITE));
+ assertNotNull(exp);
+ assertEquals("WRITE", exp.getCurrIdentifier());
+ assertEquals("accessor", exp.getPrevIdentifier());
+
+ exp = QPropertyExpansion.create(new Context(doc, inModifier));
+ assertNotNull(exp);
+ assertEquals("modifier", exp.getCurrIdentifier());
+ assertEquals("WRITE", exp.getPrevIdentifier());
+
+ exp = QPropertyExpansion.create(new Context(doc, inWRITE));
+ assertNotNull(exp);
+ assertEquals("WRITE", exp.getCurrIdentifier());
+ assertEquals("accessor", exp.getPrevIdentifier());
+ }
+
+ public void testQPropertyWithoutLeadingWhitespace() throws Exception {
+
+ String decl = "Q_PROPERTY(type name READ accessor )";
+ int atRParen = decl.indexOf(')');
+ IDocument doc = new Document(decl);
+
+ // The expansion should be created even when there is no leading whitesapce in the
+ // expansion parameter.
+ QPropertyExpansion exp = QPropertyExpansion.create(new Context(doc, atRParen));
+ assertNotNull(exp);
+ }
+
+ public void testQPropertyPrefixes() throws Exception {
+ String decl = "Q_PROPERTY( type name READ accessor WRITE )";
+ int len = decl.length();
+ IDocument doc = new Document(decl);
+
+ // The expansion is not applicable when invoked after the closing paren
+ QPropertyExpansion atEnd = QPropertyExpansion.create(new Context(doc, len));
+ assertNull(atEnd);
+
+ QPropertyExpansion inWS = QPropertyExpansion.create(new Context(doc, len - 2));
+ assertNotNull(inWS);
+ assertNull(inWS.getPrefix());
+ assertNull(inWS.getCurrIdentifier());
+ assertEquals("WRITE", inWS.getPrevIdentifier());
+
+ QPropertyExpansion afterWRITE = QPropertyExpansion.create(new Context(doc, len - 3));
+ assertNotNull(afterWRITE);
+ assertEquals("WRITE", afterWRITE.getPrefix());
+ assertEquals("WRITE", afterWRITE.getCurrIdentifier());
+ assertEquals("accessor", afterWRITE.getPrevIdentifier());
+
+ QPropertyExpansion inWRITE_e = QPropertyExpansion.create(new Context(doc, len - 4));
+ assertNotNull(inWRITE_e);
+ assertEquals("WRIT", inWRITE_e.getPrefix());
+ assertEquals("WRITE", inWRITE_e.getCurrIdentifier());
+ assertEquals("accessor", inWRITE_e.getPrevIdentifier());
+
+ QPropertyExpansion inWRITE_b = QPropertyExpansion.create(new Context(doc, len - 6));
+ assertNotNull(inWRITE_b);
+ assertEquals("WR", inWRITE_b.getPrefix());
+ assertEquals("WRITE", inWRITE_b.getCurrIdentifier());
+ assertEquals("accessor", inWRITE_b.getPrevIdentifier());
+
+ QPropertyExpansion startWRITE = QPropertyExpansion.create(new Context(doc, len - 8));
+ assertNotNull(startWRITE);
+ assertNull(startWRITE.getPrefix());
+ assertNull(startWRITE.getCurrIdentifier());
+ assertEquals("accessor", startWRITE.getPrevIdentifier());
+ }
+
+ // This implements only the parts that are known to be used in the QPropertyExpansion
+ // implementation.
+ private static class Context implements ICEditorContentAssistInvocationContext {
+
+ private final IDocument doc;
+ private final int contextOffset;
+ private final int invokedOffset;
+
+ public Context(IDocument doc, int invoked) {
+ this.doc = doc;
+ this.contextOffset = doc.get().indexOf('(') + 1;
+ this.invokedOffset = invoked;
+ }
+
+ @Override
+ public int getInvocationOffset() {
+ return invokedOffset;
+ }
+
+ @Override
+ public int getContextInformationOffset() {
+ return contextOffset;
+ }
+
+ @Override
+ public IDocument getDocument() {
+ return doc;
+ }
+
+ @Override
+ public boolean isContextInformationStyle() {
+ return false;
+ }
+
+ @Override
+ public ITextViewer getViewer() {
+ return null;
+ }
+
+ @Override
+ public ITranslationUnit getTranslationUnit() {
+ return null;
+ }
+
+ @Override
+ public ICProject getProject() {
+ return null;
+ }
+
+ @Override
+ public int getParseOffset() {
+ return 0;
+ }
+
+ @Override
+ public IEditorPart getEditor() {
+ return null;
+ }
+
+ @Override
+ public IASTCompletionNode getCompletionNode() {
+ return null;
+ }
+
+ @Override
+ public CharSequence computeIdentifierPrefix() throws BadLocationException {
+ return null;
+ }
+ };
+}
diff --git a/qt/org.eclipse.cdt.qt.ui/META-INF/MANIFEST.MF b/qt/org.eclipse.cdt.qt.ui/META-INF/MANIFEST.MF
index ddddef6..822d359 100644
--- a/qt/org.eclipse.cdt.qt.ui/META-INF/MANIFEST.MF
+++ b/qt/org.eclipse.cdt.qt.ui/META-INF/MANIFEST.MF
@@ -12,6 +12,10 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.cdt.core,
org.eclipse.cdt.qt.core;bundle-version="[1.1.0,2.0.0)",
org.eclipse.jface.text,
- org.eclipse.core.resources
+ org.eclipse.core.resources,
+ org.eclipse.ui.workbench.texteditor,
+ org.eclipse.ui.editors
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-ActivationPolicy: lazy
+Export-Package: org.eclipse.cdt.internal.qt.ui.assist;x-friends:="org.eclipse.cdt.qt.tests",
+ org.eclipse.cdt.qt.ui
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QObjectConnectCompletion.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QObjectConnectCompletion.java
new file mode 100644
index 0000000..a68dd4f
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QObjectConnectCompletion.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.ui;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.cdt.core.CCorePlugin;
+import org.eclipse.cdt.core.dom.ast.DOMException;
+import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
+import org.eclipse.cdt.core.dom.ast.IASTExpression;
+import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
+import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
+import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
+import org.eclipse.cdt.core.dom.ast.IASTName;
+import org.eclipse.cdt.core.dom.ast.IASTNode;
+import org.eclipse.cdt.core.dom.ast.IBinding;
+import org.eclipse.cdt.core.dom.ast.IType;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFieldReference;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
+import org.eclipse.cdt.internal.qt.core.ASTUtil;
+import org.eclipse.cdt.internal.qt.core.QtFunctionCallUtil;
+import org.eclipse.cdt.internal.ui.text.contentassist.CCompletionProposal;
+import org.eclipse.cdt.internal.ui.text.contentassist.RelevanceConstants;
+import org.eclipse.cdt.qt.core.QtKeywords;
+import org.eclipse.cdt.qt.core.index.IQMethod;
+import org.eclipse.cdt.qt.core.index.IQObject;
+import org.eclipse.cdt.qt.core.index.QtIndex;
+import org.eclipse.cdt.qt.ui.QtUIPlugin;
+import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+
+@SuppressWarnings("restriction")
+public class QObjectConnectCompletion {
+ // These suggestions are populated from the index, so the case is always an exact match.
+ // Secondly, these suggestions should appear above generic variable and method matches, since
+ // have based the calculation on the exact function that is being called.
+
+ private static final int MACRO_RELEVANCE
+ = RelevanceConstants.CASE_MATCH_RELEVANCE + RelevanceConstants.LOCAL_VARIABLE_TYPE_RELEVANCE + 2;
+ private static final int MACRO_PARAM_RELEVANCE
+ = RelevanceConstants.CASE_MATCH_RELEVANCE + RelevanceConstants.METHOD_TYPE_RELEVANCE + 1;
+
+ /**
+ * Different suggestions should be proposed for each parameter of the QObject::connect
+ * function call. The 'sender' parameter should suggest SIGNAL, but 'member' can be
+ * either SLOT or SIGNAL.
+ */
+ public enum Param {
+ Signal,
+ Member,
+ Generic
+ }
+
+ private final Param param;
+ private final Data data;
+
+ public QObjectConnectCompletion(Param param) {
+ this.param = param;
+ this.data = null;
+ }
+
+ public QObjectConnectCompletion(String replacement) {
+ this.param = Param.Generic;
+ this.data = new Data(replacement);
+ }
+
+ /**
+ * The data used to produce the completions varies depending on the role of the
+ * parameter that is being completed.
+ */
+ private static class Data
+ {
+ public final String replacement;
+ public final String display;
+ public final int cursorOffset;
+
+ public static final Data SIGNAL = new Data("SIGNAL()", "SIGNAL(a)", -1);
+ public static final Data SLOT = new Data("SLOT()", "SLOT(a)", -1);
+
+ public Data(String replacement) {
+ this(replacement, replacement, 0);
+ }
+
+ public Data(String replacement, String display, int cursorOffset) {
+ this.replacement = replacement;
+ this.display = display;
+ this.cursorOffset = cursorOffset;
+ }
+
+ public ICompletionProposal createProposal(ICEditorContentAssistInvocationContext context, int relevance) {
+ int repLength = replacement.length();
+ int repOffset = context.getInvocationOffset();
+ CCompletionProposal p
+ = new CCompletionProposal(replacement, repOffset, repLength, QtUIPlugin.getQtLogo(), display, relevance, context.getViewer());
+ p.setCursorPosition(repLength + cursorOffset);
+ return p;
+ }
+ }
+
+ private static void addProposal(Collection<ICompletionProposal> proposals, ICEditorContentAssistInvocationContext context, Data data, int relevance) {
+ if (data == null)
+ return;
+
+ ICompletionProposal proposal = data.createProposal(context, relevance);
+ if (proposal != null)
+ proposals.add(proposal);
+ }
+
+ private void addProposals(Collection<ICompletionProposal> proposals, ICEditorContentAssistInvocationContext context) {
+
+ if (data != null)
+ addProposal(proposals, context, data, MACRO_PARAM_RELEVANCE);
+ else
+ switch(param) {
+ case Signal:
+ addProposal(proposals, context, Data.SIGNAL, MACRO_RELEVANCE);
+ break;
+ case Member:
+ addProposal(proposals, context, Data.SLOT, MACRO_RELEVANCE);
+ addProposal(proposals, context, Data.SIGNAL, MACRO_RELEVANCE - 1);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private static boolean is_QObject_connect(ICEditorContentAssistInvocationContext context, IASTCompletionContext astContext, IASTName name) {
+
+ // Bug332201: Qt content assist should always be applied to the most specific part of
+ // the target name.
+ IBinding[] funcBindings = astContext.findBindings(name.getLastName(), !context.isContextInformationStyle());
+ for (IBinding funcBinding : funcBindings)
+ if (QtKeywords.is_QObject_connect(funcBinding))
+ return true;
+
+ return false;
+ }
+
+ // Copied from org.eclipse.cdt.internal.ui.text.CParameterListValidator
+ private static int indexOfClosingPeer(String code, char left, char right, int pos) {
+ int level = 0;
+ final int length = code.length();
+ while (pos < length) {
+ char ch = code.charAt(pos);
+ if (ch == left) {
+ ++level;
+ } else if (ch == right) {
+ if (--level == 0) {
+ return pos;
+ }
+ }
+ ++pos;
+ }
+ return -1;
+ }
+
+ // Copied from org.eclipse.cdt.internal.ui.text.CParameterListValidator
+ private static int[] computeCommaPositions(String code) {
+ final int length = code.length();
+ int pos = 0;
+ List<Integer> positions = new ArrayList<Integer>();
+ positions.add(new Integer(-1));
+ while (pos < length && pos != -1) {
+ char ch = code.charAt(pos);
+ switch (ch) {
+ case ',':
+ positions.add(new Integer(pos));
+ break;
+ case '(':
+ pos = indexOfClosingPeer(code, '(', ')', pos);
+ break;
+ case '<':
+ pos = indexOfClosingPeer(code, '<', '>', pos);
+ break;
+ case '[':
+ pos = indexOfClosingPeer(code, '[', ']', pos);
+ break;
+ default:
+ break;
+ }
+ if (pos != -1)
+ pos++;
+ }
+ positions.add(new Integer(length));
+
+ int[] fields = new int[positions.size()];
+ for (int i = 0; i < fields.length; i++)
+ fields[i] = positions.get(i).intValue();
+ return fields;
+ }
+
+ private static Collection<QObjectConnectCompletion> getCompletionsFor(IASTNode targetNode, IASTInitializerClause arg) {
+
+ IType targetType = ASTUtil.getBaseType(targetNode);
+ if (!(targetType instanceof ICPPClassType))
+ return null;
+ ICPPClassType cls = (ICPPClassType) targetType;
+
+ QtIndex qtIndex = QtIndex.getIndex(ASTUtil.getProject(targetNode));
+ if (qtIndex == null)
+ return null;
+
+ IQObject qobj = null;
+ try {
+ qobj = qtIndex.findQObject(cls.getQualifiedName());
+ } catch(DOMException e) {
+ CCorePlugin.log(e);
+ }
+
+ // QtIndex.findQObject will return null in some cases, e.g., when the parameter is null
+ if (qobj == null)
+ return null;
+
+ Collection<QObjectConnectCompletion> completions = new ArrayList<QObjectConnectCompletion>();
+ String raw = arg.getRawSignature();
+ if (raw.startsWith(QtKeywords.SIGNAL))
+ for(IQMethod method : qobj.getSignals().withoutOverrides())
+ for(String signature : method.getSignatures())
+ completions.add(new QObjectConnectCompletion(signature));
+ if (raw.startsWith(QtKeywords.SLOT))
+ for(IQMethod method : qobj.getSlots().withoutOverrides())
+ for(String signature : method.getSignatures())
+ completions.add(new QObjectConnectCompletion(signature));
+ return completions;
+ }
+
+ public static Collection<QObjectConnectCompletion> getConnectProposals(
+ ICEditorContentAssistInvocationContext context, IASTName name, IASTCompletionContext astContext, IASTNode astNode) {
+
+ if (QtFunctionCallUtil.isQObjectFunctionCall(astContext, !context.isContextInformationStyle(), name)) {
+ int parseOffset = context.getParseOffset();
+ int invocationOffset = context.getInvocationOffset();
+
+ String unparsed = "";
+ try {
+ unparsed = context.getDocument().get(parseOffset, invocationOffset - parseOffset);
+ } catch (BadLocationException e) {
+ CCorePlugin.log(e);
+ }
+
+ if (unparsed.length() > 0 && unparsed.charAt(0) == '(')
+ unparsed = unparsed.substring(1);
+
+ int[] commas = computeCommaPositions(unparsed);
+ switch (commas.length) {
+ case 2:
+ case 3:
+ // Across all possible connect/disconnect overloads, the first and second arguments
+ // can be SIGNAL expansion.
+ return Collections.singletonList(new QObjectConnectCompletion(QObjectConnectCompletion.Param.Signal));
+ case 4:
+ case 5:
+ // Across all possible connect/disconnect overloads, the first and second arguments
+ // can be SIGNAL or SLOT expansions.
+ return Collections.singletonList(new QObjectConnectCompletion(QObjectConnectCompletion.Param.Member));
+ }
+
+ return null;
+ }
+
+ if (astNode.getPropertyInParent() == IASTFunctionCallExpression.ARGUMENT) {
+ IASTNode parent = astNode.getParent();
+ if (!(parent instanceof IASTFunctionCallExpression))
+ return null;
+
+ // NOTE: QtConnectFunctionCall cannot be used here because that class expects a
+ // valid expression. During content assist the function is still being
+ // created.
+
+ IASTFunctionCallExpression call = (IASTFunctionCallExpression) parent;
+ IASTExpression nameExpr = call.getFunctionNameExpression();
+ IASTName funcName = null;
+ if (nameExpr instanceof IASTIdExpression)
+ funcName = ((IASTIdExpression) nameExpr).getName();
+ else if (nameExpr instanceof ICPPASTFieldReference)
+ funcName = ((ICPPASTFieldReference) nameExpr).getFieldName();
+
+ // If this isn't a QObject::connect or QObject::disconnect function call then
+ // look no further.
+ if (!QtFunctionCallUtil.isQObjectFunctionCall(astContext, !context.isContextInformationStyle(), funcName))
+ return null;
+
+ // In a content assist context the argument that is currently being entered is
+ // last in the function call.
+ IASTInitializerClause[] args = call.getArguments();
+ if (args == null
+ || args.length < 0)
+ return null;
+ int argIndex = args.length - 1;
+
+ // Find the type node that is used for this expansion.
+ IASTNode typeNode = QtFunctionCallUtil.getTypeNode(call, args, argIndex);
+ if (typeNode == null)
+ return null;
+
+ // Returns completions for the given expansion using the given type as the
+ // source for Qt methods.
+ return getCompletionsFor(typeNode, args[argIndex]);
+ }
+
+ return null;
+ }
+
+ public static Collection<ICompletionProposal> getProposals(
+ ICEditorContentAssistInvocationContext context, IASTName name, IASTCompletionContext astContext, IASTNode astNode) {
+
+ Collection<QObjectConnectCompletion> qtProposals = getConnectProposals(context, name, astContext, astNode);
+ if (qtProposals == null
+ || qtProposals.isEmpty())
+ return null;
+
+ Collection<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
+ for (QObjectConnectCompletion qtProposal : qtProposals)
+ qtProposal.addProposals(proposals, context);
+ return proposals;
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QObjectDeclarationCompletion.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QObjectDeclarationCompletion.java
new file mode 100644
index 0000000..3fc3543
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QObjectDeclarationCompletion.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.ui;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.cdt.core.dom.ast.IASTName;
+import org.eclipse.cdt.internal.corext.template.c.CContextType;
+import org.eclipse.cdt.internal.corext.template.c.TranslationUnitContext;
+import org.eclipse.cdt.internal.corext.template.c.TranslationUnitContextType;
+import org.eclipse.cdt.internal.ui.text.template.TemplateEngine.CTemplateProposal;
+import org.eclipse.cdt.qt.ui.QtUIPlugin;
+import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextSelection;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.templates.Template;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.ui.texteditor.ITextEditor;
+
+@SuppressWarnings("restriction")
+public class QObjectDeclarationCompletion {
+
+ private static final String TEMPLATE = "class ${name} : public ${QObject}\n{\nQ_OBJECT\n\n${cursor}\n};";
+
+ private final static TranslationUnitContextType context;
+ static {
+ context = new CContextType();
+ context.setId(CContextType.ID);
+ }
+
+ public static Collection<ICompletionProposal> getProposals(ICEditorContentAssistInvocationContext ctx, IASTName name) {
+
+ String token = name.getLastName().toString();
+ if (token.isEmpty()
+ || !"class".startsWith(token))
+ return null;
+
+ Position position = getPosition(ctx);
+ if (position == null)
+ return null;
+
+ TranslationUnitContext tuCtx = context.createContext(ctx.getDocument(), position, ctx.getTranslationUnit());
+ IRegion region = new Region(position.getOffset(), position.getLength());
+
+ Template template = new Template( "class", "QObject declaration", CContextType.ID, TEMPLATE, true);
+ return Collections.<ICompletionProposal>singletonList(new CTemplateProposal(template, tuCtx, region, QtUIPlugin.getQtLogo()));
+ }
+
+ private static Position getPosition(ICEditorContentAssistInvocationContext context) {
+ ITextEditor textEditor = (ITextEditor) context.getEditor().getAdapter(ITextEditor.class);
+ if (textEditor == null)
+ return null;
+
+ ISelectionProvider selectionProvider = textEditor.getSelectionProvider();
+ if (selectionProvider == null)
+ return null;
+
+ ISelection selection = selectionProvider.getSelection();
+ if (!(selection instanceof ITextSelection))
+ return null;
+
+ ITextSelection text = (ITextSelection) selection;
+ return new Position(text.getOffset(), text.getLength());
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QPropertyCompletion.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QPropertyCompletion.java
new file mode 100644
index 0000000..31e1734
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QPropertyCompletion.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.ui;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
+import org.eclipse.cdt.core.dom.ast.IASTName;
+import org.eclipse.cdt.core.dom.ast.IASTNode;
+import org.eclipse.cdt.internal.corext.template.c.CContextType;
+import org.eclipse.cdt.internal.qt.ui.assist.QPropertyExpansion;
+import org.eclipse.cdt.internal.qt.ui.assist.QtProposalContext;
+import org.eclipse.cdt.internal.qt.ui.assist.QtTemplateProposal;
+import org.eclipse.cdt.qt.core.QtKeywords;
+import org.eclipse.cdt.qt.ui.QtUIPlugin;
+import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.templates.Template;
+import org.eclipse.jface.text.templates.TemplateContextType;
+
+@SuppressWarnings("restriction")
+public class QPropertyCompletion {
+
+ private static final String CONTEXT_ID = QtUIPlugin.PLUGIN_ID + ".proposal.Q_PROPERTY";
+
+ private static final Template QPropertyTemplate
+ = new Template("Q_PROPERTY", "Q_PROPERTY declaration", CONTEXT_ID, "Q_PROPERTY( ${type} ${name} READ ${accessor} ${cursor} )", true);
+
+ public static Collection<ICompletionProposal> getAttributeProposals(ICEditorContentAssistInvocationContext context) {
+ QPropertyExpansion expansion = QPropertyExpansion.create(context);
+ return expansion == null
+ ? Collections.<ICompletionProposal>emptyList()
+ : expansion.getProposals(CONTEXT_ID, context);
+ }
+
+ public static Collection<ICompletionProposal> getProposals(
+ ICEditorContentAssistInvocationContext context, IASTName name, IASTCompletionContext astContext, IASTNode astNode) {
+
+ String token = name.getLastName().toString();
+ if (token.isEmpty()
+ || !QtKeywords.Q_PROPERTY.startsWith(token))
+ return Collections.emptyList();
+
+ TemplateContextType ctxType = new CContextType();
+ ctxType.setId(CONTEXT_ID);
+
+ QtProposalContext templateCtx = new QtProposalContext(context, ctxType);
+ Region region = new Region(templateCtx.getCompletionOffset(), templateCtx.getCompletionLength());
+
+ return Collections.<ICompletionProposal>singletonList(new QtTemplateProposal(QPropertyTemplate, templateCtx, region));
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QtCompletionProposalComputer.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QtCompletionProposalComputer.java
index 7851117..baa0e18 100644
--- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QtCompletionProposalComputer.java
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/QtCompletionProposalComputer.java
@@ -5,387 +5,118 @@
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
-
package org.eclipse.cdt.internal.qt.ui;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
-import org.eclipse.cdt.core.CCorePlugin;
-import org.eclipse.cdt.core.dom.ast.ASTTypeUtil;
import org.eclipse.cdt.core.dom.ast.IASTCompletionContext;
import org.eclipse.cdt.core.dom.ast.IASTCompletionNode;
-import org.eclipse.cdt.core.dom.ast.IASTExpression;
-import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression;
-import org.eclipse.cdt.core.dom.ast.IASTIdExpression;
-import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
-import org.eclipse.cdt.core.dom.ast.IBinding;
-import org.eclipse.cdt.core.dom.ast.IPointerType;
-import org.eclipse.cdt.core.dom.ast.IType;
-import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTInitializerClause;
-import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
-import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunction;
-import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
-import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
-import org.eclipse.cdt.core.dom.ast.tag.ITag;
-import org.eclipse.cdt.core.dom.ast.tag.ITagReader;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ITranslationUnit;
-import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPEvaluation;
-import org.eclipse.cdt.internal.ui.text.contentassist.CCompletionProposal;
import org.eclipse.cdt.internal.ui.text.contentassist.CContentAssistInvocationContext;
import org.eclipse.cdt.internal.ui.text.contentassist.ParsingBasedProposalComputer;
-import org.eclipse.cdt.internal.ui.text.contentassist.RelevanceConstants;
-import org.eclipse.cdt.qt.core.QtKeywords;
import org.eclipse.cdt.qt.core.QtNature;
-import org.eclipse.cdt.qt.core.QtPlugin;
import org.eclipse.cdt.qt.ui.QtUIPlugin;
-import org.eclipse.cdt.ui.CUIPlugin;
+import org.eclipse.cdt.ui.text.contentassist.ContentAssistInvocationContext;
import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
-import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
@SuppressWarnings("restriction")
public class QtCompletionProposalComputer extends ParsingBasedProposalComputer {
- private boolean isApplicable(ICEditorContentAssistInvocationContext context) {
- ITranslationUnit tu = context.getTranslationUnit();
- if (tu == null)
- return false;
-
- ICProject cProject = tu.getCProject();
- if (cProject == null)
- return false;
-
- IProject project = cProject.getProject();
- if (project == null)
- return false;
+ @Override
+ public List<ICompletionProposal> computeCompletionProposals(
+ ContentAssistInvocationContext context, IProgressMonitor monitor) {
+ // this is overridden in order to find proposals when the completion node is null
try {
- return project.hasNature(QtNature.ID);
- } catch (CoreException e) {
- CUIPlugin.log(e);
- return false;
- }
- }
-
- private static boolean is_QObject_connect(
- ICEditorContentAssistInvocationContext context,
- IASTCompletionContext astContext, IASTName name) {
- IASTName connectName = name.getLastName();
- if (!QtKeywords.CONNECT.equals(new String(connectName.getSimpleID())))
- return false;
-
- IBinding[] funcBindings = astContext.findBindings(connectName,
- !context.isContextInformationStyle());
- for (IBinding funcBinding : funcBindings)
- if (funcBinding instanceof ICPPFunction) {
- IBinding ownerBinding = ((ICPPFunction) funcBinding).getOwner();
- if (ownerBinding != null
- && QtKeywords.QOBJECT.equals(ownerBinding.getName()))
- return true;
- }
-
- return false;
- }
-
- private static class Completion {
- private final String replacement;
- private final String display;
- private final int cursorOffset;
-
- public static final Completion SIGNAL = new Completion("SIGNAL()",
- "SIGNAL(a)", -1);
- public static final Completion SLOT = new Completion("SLOT()",
- "SLOT(a)", -1);
-
- public Completion(String replacement) {
- this(replacement, replacement, 0);
- }
-
- public Completion(String replacement, String display, int cursorOffset) {
- this.replacement = replacement;
- this.display = display;
- this.cursorOffset = cursorOffset;
- }
-
- public ICompletionProposal createProposal(
- ICEditorContentAssistInvocationContext context) {
- int repLength = replacement.length();
- int repOffset = context.getInvocationOffset();
- CCompletionProposal p = new CCompletionProposal(replacement,
- repOffset, repLength, null, display,
- RelevanceConstants.DEFAULT_TYPE_RELEVANCE,
- context.getViewer());
- p.setCursorPosition(repLength + cursorOffset);
- return p;
- }
-
- @Override
- public String toString() {
- if (replacement == null)
- return super.toString();
- return replacement + '@' + cursorOffset;
- }
- }
-
- private static interface MethodFilter {
- public boolean keep(ICPPMethod method);
-
- public static class Qt {
- public static final MethodFilter Signal = new MethodFilter() {
- @Override
- public boolean keep(ICPPMethod method) {
- ITagReader tagReader = CCorePlugin.getTagService()
- .findTagReader(method);
- if (tagReader == null)
- return false;
-
- ITag tag = tagReader.getTag(QtPlugin.SIGNAL_SLOT_TAGGER_ID);
- if (tag == null)
- return false;
-
- int result = tag.getByte(0);
- return result != ITag.FAIL
- && ((result & QtPlugin.SignalSlot_Mask_signal) == QtPlugin.SignalSlot_Mask_signal);
- }
- };
-
- public static final MethodFilter Slot = new MethodFilter() {
- @Override
- public boolean keep(ICPPMethod method) {
- ITagReader tagReader = CCorePlugin.getTagService()
- .findTagReader(method);
- if (tagReader == null)
- return false;
-
- ITag tag = tagReader.getTag(QtPlugin.SIGNAL_SLOT_TAGGER_ID);
- if (tag == null)
- return false;
-
- int result = tag.getByte(0);
- return result != ITag.FAIL
- && ((result & QtPlugin.SignalSlot_Mask_slot) == QtPlugin.SignalSlot_Mask_slot);
- }
- };
- }
- }
-
- private static Iterable<ICPPMethod> filterMethods(final ICPPClassType cls,
- final MethodFilter filter) {
- return new Iterable<ICPPMethod>() {
- @Override
- public Iterator<ICPPMethod> iterator() {
- return new Iterator<ICPPMethod>() {
- private int index = 0;
- private final ICPPMethod[] methods = cls.getMethods();
+ if (context instanceof CContentAssistInvocationContext) {
+ CContentAssistInvocationContext cContext = (CContentAssistInvocationContext) context;
- @Override
- public boolean hasNext() {
- for (; index < methods.length; ++index)
- if (filter.keep(methods[index]))
- return true;
- return false;
- }
+ String prefix = null;
+ IASTCompletionNode completionNode = cContext.getCompletionNode();
+ // the parent implementation gives up when this condition is false
+ if (completionNode != null)
+ prefix = completionNode.getPrefix();
- @Override
- public ICPPMethod next() {
- return methods[index++];
- }
+ if (prefix == null)
+ prefix = cContext.computeIdentifierPrefix().toString();
- @Override
- public void remove() {
- }
- };
+ return computeCompletionProposals(cContext, completionNode, prefix);
}
- };
- }
-
- private static String getSignature(ICPPMethod method) {
- StringBuilder signature = new StringBuilder();
-
- signature.append(method.getName());
- signature.append('(');
- boolean first = true;
- for (ICPPParameter param : method.getParameters()) {
- if (first)
- first = false;
- else
- signature.append(", ");
- signature.append(ASTTypeUtil.getType(param.getType()));
+ } catch (Exception e) {
+ QtUIPlugin.log(e);
}
- signature.append(')');
- return signature.toString();
- }
-
- private static void addCompletionsFor(Collection<Completion> completions,
- IASTInitializerClause init, MethodFilter filter) {
- if (!(init instanceof ICPPASTInitializerClause))
- return;
-
- ICPPEvaluation eval = ((ICPPASTInitializerClause) init).getEvaluation();
- if (eval == null)
- return;
-
- IType type = eval.getTypeOrFunctionSet(init);
- while (type instanceof IPointerType)
- type = ((IPointerType) type).getType();
-
- if (type instanceof ICPPClassType)
- for (ICPPMethod signal : filterMethods((ICPPClassType) type, filter))
- completions.add(new Completion(getSignature(signal)));
+ return Collections.emptyList();
}
- // Copied from org.eclipse.cdt.internal.ui.text.CParameterListValidator
- private static int indexOfClosingPeer(String code, char left, char right,
- int pos) {
- int level = 0;
- final int length = code.length();
- while (pos < length) {
- char ch = code.charAt(pos);
- if (ch == left) {
- ++level;
- } else if (ch == right) {
- if (--level == 0) {
- return pos;
- }
- }
- ++pos;
- }
- return -1;
- }
-
- // Copied from org.eclipse.cdt.internal.ui.text.CParameterListValidator
- private static int[] computeCommaPositions(String code) {
- final int length = code.length();
- int pos = 0;
- List<Integer> positions = new ArrayList<Integer>();
- positions.add(new Integer(-1));
- while (pos < length && pos != -1) {
- char ch = code.charAt(pos);
- switch (ch) {
- case ',':
- positions.add(new Integer(pos));
- break;
- case '(':
- pos = indexOfClosingPeer(code, '(', ')', pos);
- break;
- case '<':
- pos = indexOfClosingPeer(code, '<', '>', pos);
- break;
- case '[':
- pos = indexOfClosingPeer(code, '[', ']', pos);
- break;
- default:
- break;
- }
- if (pos != -1)
- pos++;
- }
- positions.add(new Integer(length));
-
- int[] fields = new int[positions.size()];
- for (int i = 0; i < fields.length; i++)
- fields[i] = positions.get(i).intValue();
- return fields;
- }
+ private boolean isApplicable(ICEditorContentAssistInvocationContext context) {
+ ITranslationUnit tu = context.getTranslationUnit();
+ if (tu == null)
+ return false;
- private void addConnectParameterCompletions(
- List<ICompletionProposal> proposals,
- ICEditorContentAssistInvocationContext context,
- IASTCompletionNode completionNode, String prefix) {
- IASTName[] names = completionNode.getNames();
- List<Completion> completions = new LinkedList<Completion>();
+ ICProject cProject = tu.getCProject();
+ if (cProject == null)
+ return false;
- for (IASTName name : names) {
- // The node isn't properly hooked up, must have backtracked out of
- // this node
- if (name.getTranslationUnit() == null)
- continue;
+ IProject project = cProject.getProject();
+ if (project == null)
+ return false;
- IASTCompletionContext astContext = name.getCompletionContext();
- if (astContext == null || !(astContext instanceof IASTNode))
- continue;
- IASTNode astNode = (IASTNode) astContext;
+ return QtNature.hasNature(project);
+ }
- if (is_QObject_connect(context, astContext, name)) {
- int parseOffset = context.getParseOffset();
- int invocationOffset = context.getInvocationOffset();
+ @Override
+ protected List<ICompletionProposal> computeCompletionProposals(
+ CContentAssistInvocationContext context, IASTCompletionNode completionNode, String prefix) throws CoreException {
- String unparsed = "";
- try {
- unparsed = context.getDocument().get(parseOffset,
- invocationOffset - parseOffset);
- } catch (BadLocationException e) {
- QtUIPlugin.log(e);
- }
+ // make sure this is a Qt project
+ if (!isApplicable(context))
+ return Collections.emptyList();
- if (unparsed.length() > 0 && unparsed.charAt(0) == '(')
- unparsed = unparsed.substring(1);
+ List<ICompletionProposal> proposals = null;
- int[] commas = computeCommaPositions(unparsed);
- switch (commas.length) {
- case 3:
- completions.add(Completion.SIGNAL);
- break;
- case 5:
- completions.add(Completion.SLOT);
- break;
- }
- } else if (astNode.getPropertyInParent() == IASTFunctionCallExpression.ARGUMENT) {
- IASTNode parent = astNode.getParent();
- if (!(parent instanceof IASTFunctionCallExpression))
- continue;
- IASTFunctionCallExpression call = (IASTFunctionCallExpression) parent;
- IASTExpression nameExpr = call.getFunctionNameExpression();
- if (!(nameExpr instanceof IASTIdExpression))
+ if (completionNode != null) {
+ IASTName[] names = completionNode.getNames();
+ for (IASTName name : names) {
+ // the node isn't properly hooked up, must have backtracked out of this node
+ if (name.getTranslationUnit() == null)
continue;
- IASTIdExpression funcNameIdExpr = (IASTIdExpression) nameExpr;
- IASTName funcName = funcNameIdExpr.getName();
- if (!is_QObject_connect(context, astContext, funcName))
+ IASTCompletionContext astContext = name.getCompletionContext();
+ if (astContext == null || !(astContext instanceof IASTNode))
continue;
+ IASTNode astNode = (IASTNode) astContext;
- IASTInitializerClause[] args = call.getArguments();
- switch (args.length) {
- case 2:
- addCompletionsFor(completions, args[0],
- MethodFilter.Qt.Signal);
- break;
- case 4:
- addCompletionsFor(completions, args[2],
- MethodFilter.Qt.Slot);
- break;
- }
+ proposals = addAll(proposals, QObjectConnectCompletion.getProposals(context, name, astContext, astNode));
+ proposals = addAll(proposals, QObjectDeclarationCompletion.getProposals(context, name));
+ proposals = addAll(proposals, QPropertyCompletion.getProposals(context, name, astContext, astNode));
}
}
- for (Completion completion : completions) {
- ICompletionProposal proposal = completion.createProposal(context);
- if (proposal != null)
- proposals.add(proposal);
- }
+ // Attributes within Q_PROPERTY declarations
+ proposals = addAll(proposals, QPropertyCompletion.getAttributeProposals(context));
+
+ return proposals == null ? Collections.<ICompletionProposal>emptyList() : proposals;
}
- @Override
- protected List<ICompletionProposal> computeCompletionProposals(
- CContentAssistInvocationContext context,
- IASTCompletionNode completionNode, String prefix)
- throws CoreException {
- if (!isApplicable(context))
- return Collections.emptyList();
+ private static <T> List<T> addAll(List<T> list, Collection<T> toAdd) {
+ if (toAdd == null
+ || toAdd.isEmpty())
+ return list;
+
+ if (list == null)
+ return new ArrayList<T>(toAdd);
- List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
- addConnectParameterCompletions(proposals, context, completionNode,
- prefix);
- return proposals;
+ list.addAll(toAdd);
+ return list;
}
}
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QPropertyAttributeProposal.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QPropertyAttributeProposal.java
new file mode 100644
index 0000000..be8ad18
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QPropertyAttributeProposal.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.ui.assist;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.cdt.core.CCorePlugin;
+import org.eclipse.cdt.core.dom.ast.DOMException;
+import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
+import org.eclipse.cdt.core.dom.ast.IASTEqualsInitializer;
+import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
+import org.eclipse.cdt.core.dom.ast.IASTInitializer;
+import org.eclipse.cdt.core.dom.ast.IASTInitializerClause;
+import org.eclipse.cdt.core.dom.ast.IASTName;
+import org.eclipse.cdt.core.dom.ast.IASTNode;
+import org.eclipse.cdt.core.dom.ast.IASTNodeSelector;
+import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
+import org.eclipse.cdt.core.dom.ast.IBasicType;
+import org.eclipse.cdt.core.dom.ast.IBinding;
+import org.eclipse.cdt.core.dom.ast.IType;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPFunctionType;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPParameter;
+import org.eclipse.cdt.core.index.IIndex;
+import org.eclipse.cdt.core.model.ICProject;
+import org.eclipse.cdt.core.model.ITranslationUnit;
+import org.eclipse.cdt.internal.core.dom.parser.cpp.CPPParameter;
+import org.eclipse.cdt.internal.ui.text.contentassist.CCompletionProposal;
+import org.eclipse.cdt.qt.core.index.IQMethod;
+import org.eclipse.cdt.qt.core.index.IQObject;
+import org.eclipse.cdt.qt.core.index.IQProperty;
+import org.eclipse.cdt.qt.core.index.QtIndex;
+import org.eclipse.cdt.qt.ui.QtUIPlugin;
+import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+
+/**
+ * An attribute-based proposal depends on the both the attribute (the previous identifier) and the
+ * containing class definition. The class definition is not needed for all attribute types, but
+ * is used to build the list of proposals for attributes like READ, WRITE, etc.
+ */
+@SuppressWarnings("restriction")
+public class QPropertyAttributeProposal {
+ private final int relevance;
+ private final String identifier;
+ private final String display;
+
+ public QPropertyAttributeProposal(String identifier, int relevance) {
+ this(identifier, identifier, relevance);
+ }
+
+ public ICompletionProposal createProposal(String prefix, int offset) {
+ int prefixLen = prefix == null ? 0 : prefix.length();
+
+ String disp = identifier.equals(display) ? display : ( identifier + " - " + display );
+ return new CCompletionProposal(identifier.substring(prefixLen), offset, prefixLen, QtUIPlugin.getQtLogo(), disp, relevance);
+ }
+
+ private QPropertyAttributeProposal(String identifier, String display, int relevance) {
+ this.identifier = identifier;
+ this.display = display;
+ this.relevance = relevance;
+ }
+
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ public static Collection<QPropertyAttributeProposal> buildProposals(IQProperty.Attribute attr, ICEditorContentAssistInvocationContext context, IType type, String name) {
+ switch(attr) {
+ // propose true/false for bool Attributes
+ case DESIGNABLE:
+ case SCRIPTABLE:
+ case STORED:
+ case USER:
+ return Arrays.asList(
+ new QPropertyAttributeProposal("true", IMethodAttribute.BaseRelevance + 11),
+ new QPropertyAttributeProposal("false", IMethodAttribute.BaseRelevance + 10));
+
+ // propose appropriate methods for method-based attributes
+ case READ:
+ case WRITE:
+ case RESET:
+ return getMethodProposals(context, get(attr, type, name));
+
+ // propose appropriate signals for NOTIFY
+ case NOTIFY:
+ return getSignalProposals(context, get(attr, type, name));
+
+ default:
+ break;
+ }
+
+ return Collections.emptyList();
+ }
+
+ private static Collection<QPropertyAttributeProposal> getMethodProposals(ICEditorContentAssistInvocationContext context, IMethodAttribute methodAttribute) {
+
+ ICPPClassType cls = getEnclosingClassDefinition(context);
+ if (cls == null)
+ return Collections.emptyList();
+
+ // Return all the methods, including inherited and non-visible ones.
+ ICPPMethod[] methods = cls.getMethods();
+ List<ICPPMethod> filtered = new ArrayList<ICPPMethod>(methods.length);
+ for(ICPPMethod method : methods)
+ if (methodAttribute.keep(method))
+ filtered.add(method);
+
+ // TODO Choose the overload that is the best match -- closest parameter type and fewest
+ // parameters with default values.
+
+ List<QPropertyAttributeProposal> proposals = new ArrayList<QPropertyAttributeProposal>();
+ for(ICPPMethod method : getMethods(context, methodAttribute))
+ proposals.add(new QPropertyAttributeProposal(method.getName(), getDisplay(cls, method), methodAttribute.getRelevance(method)));
+
+ return proposals;
+ }
+
+ private static Collection<QPropertyAttributeProposal> getSignalProposals(ICEditorContentAssistInvocationContext context, IMethodAttribute methodAttribute) {
+ ICPPClassType cls = getEnclosingClassDefinition(context);
+ if (cls == null)
+ return Collections.emptyList();
+
+ ICProject cProject = context.getProject();
+ if (cProject == null)
+ return Collections.emptyList();
+
+ QtIndex qtIndex = QtIndex.getIndex(cProject.getProject());
+ if (qtIndex == null)
+ return Collections.emptyList();
+
+ IQObject qobj = null;
+ try {
+ qobj = qtIndex.findQObject(cls.getQualifiedName());
+ } catch(DOMException e) {
+ QtUIPlugin.log(e);
+ }
+
+ if (qobj == null)
+ return Collections.emptyList();
+
+ List<QPropertyAttributeProposal> proposals = new ArrayList<QPropertyAttributeProposal>();
+ for(IQMethod qMethod : qobj.getSignals().all())
+ proposals.add(new QPropertyAttributeProposal(qMethod.getName(), IMethodAttribute.BaseRelevance));
+
+ return proposals;
+ }
+
+ private static boolean isSameClass(ICPPClassType cls1, ICPPClassType cls2) {
+
+ // IType.isSameType doesn't work in this case. Given an instance of ICPPClassType, cls,
+ // the following returns false:
+ // cls.isSameType( cls.getMethods()[0].getOwner() )
+ //
+ // Instead we check the fully qualified names.
+
+ try {
+ String[] qn1 = cls1.getQualifiedName();
+ String[] qn2 = cls2.getQualifiedName();
+
+ if (qn1.length != qn2.length)
+ return false;
+
+ for(int i = 0; i < qn1.length; ++i)
+ if (!qn1[i].equals(qn2[i]))
+ return false;
+ return true;
+ } catch(DOMException e) {
+ return false;
+ }
+ }
+
+ private static String getDisplay(ICPPClassType referenceContext, ICPPMethod method) {
+
+ boolean includeClassname = !isSameClass(referenceContext, method.getClassOwner());
+
+ StringBuilder sig = new StringBuilder();
+ ICPPFunctionType type = method.getType();
+
+ sig.append(type.getReturnType().toString());
+ sig.append(' ');
+ if (includeClassname) {
+ sig.append(method.getOwner().getName());
+ sig.append("::");
+ }
+ sig.append(method.getName());
+ sig.append('(');
+ boolean first = true;
+ for(ICPPParameter param : method.getParameters()) {
+ if (first)
+ first = false;
+ else
+ sig.append(", ");
+
+ String defValue = null;
+ if (param instanceof CPPParameter) {
+ CPPParameter cppParam = (CPPParameter) param;
+ IASTInitializer defaultValue = cppParam.getDefaultValue();
+ if (defaultValue instanceof IASTEqualsInitializer) {
+ IASTInitializerClause clause = ((IASTEqualsInitializer) defaultValue).getInitializerClause();
+ defValue = clause.toString();
+ }
+ }
+
+ sig.append(defValue == null ? param.getType().toString() : defValue);
+ }
+ sig.append(')');
+ return sig.toString();
+ }
+
+ private static interface IMethodAttribute {
+ public boolean keep(ICPPMethod method);
+
+ public static final int BaseRelevance = 2000;
+ public int getRelevance(ICPPMethod method);
+
+ public static final IMethodAttribute Null = new IMethodAttribute() {
+ @Override
+ public boolean keep(ICPPMethod method) {
+ return false;
+ }
+
+ @Override
+ public int getRelevance(ICPPMethod method) {
+ return 0;
+ }
+ };
+ }
+
+ private static IMethodAttribute get(IQProperty.Attribute attr, IType type, String propertyName) {
+ switch(attr) {
+ case READ:
+ return new Read(type, propertyName);
+ case WRITE:
+ return new Write(type, propertyName);
+ case RESET:
+ return new Reset(type, propertyName);
+ default:
+ return IMethodAttribute.Null;
+ }
+ }
+
+ private static class Read implements IMethodAttribute {
+ private final IType type;
+ private final String propertyName;
+
+ public Read(IType type, String propertyName) {
+ this.type = type;
+ this.propertyName = propertyName;
+ }
+
+ // From the Qt docs, http://qt-project.org/doc/qt-4.8/properties.html:
+ // "A READ accessor function is required. It is for reading the property value. Ideally, a
+ // const function is used for this purpose, and it must return either the property's type
+ // or a pointer or reference to that type. e.g., QWidget::focus is a read-only property with
+ // READ function, QWidget::hasFocus().
+ @Override
+ public boolean keep(ICPPMethod method) {
+ // READ must have no params without default values
+ if (method.getParameters().length > 0
+ && !method.getParameters()[0].hasDefaultValue())
+ return false;
+
+ // Make sure the return type of the method can be assigned to the property's type.
+ IType retType = method.getType().getReturnType();
+ if (!isAssignable(retType, type))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int getRelevance(ICPPMethod method) {
+ String methodName = method.getName();
+ if (methodName == null)
+ return 0;
+
+ // exact match is the most relevant
+ if (methodName.equals(propertyName))
+ return BaseRelevance + 20;
+
+ // accessor with "get" prefix is the 2nd highest rank
+ if (methodName.equalsIgnoreCase("get" + propertyName))
+ return BaseRelevance + 19;
+
+ // method names that include the property name anywhere are the next
+ // most relevant
+ if (methodName.matches(".*(?i)" + propertyName + ".*"))
+ return BaseRelevance + 18;
+
+ // otherwise return default relevance
+ return 10;
+ }
+ }
+
+ private static class Write implements IMethodAttribute {
+ private final IType type;
+ private final String propertyName;
+
+ public Write(IType type, String propertyName) {
+ this.type = type;
+ this.propertyName = propertyName;
+ }
+
+ // From the Qt docs, http://qt-project.org/doc/qt-4.8/properties.html:
+ // A WRITE accessor function is optional. It is for setting the property value. It must
+ // return void and must take exactly one argument, either of the property's type or a
+ // pointer or reference to that type. e.g., QWidget::enabled has the WRITE function
+ // QWidget::setEnabled(). Read-only properties do not need WRITE functions. e.g., QWidget::focus
+ // has no WRITE function.
+ @Override
+ public boolean keep(ICPPMethod method) {
+
+ // The Qt moc doesn't seem to check that the return type is void, and I'm not sure why it
+ // would need to. This filter doesn't reject non-void methods.
+
+ // WRITE must have at least one parameter and no more than one param without default values
+ if (method.getParameters().length < 1
+ || (method.getParameters().length > 1
+ && !method.getParameters()[1].hasDefaultValue()))
+ return false;
+
+ // Make sure the property's type can be assigned to the type of the first parameter
+ IType paramType = method.getParameters()[0].getType();
+ if (!isAssignable(type, paramType))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int getRelevance(ICPPMethod method) {
+ String methodName = method.getName();
+ if (methodName == null)
+ return 0;
+
+ // exact match is the most relevant
+ if (methodName.equals(propertyName))
+ return BaseRelevance + 20;
+
+ // accessor with "get" prefix is the 2nd highest rank
+ if (methodName.equalsIgnoreCase("set" + propertyName))
+ return BaseRelevance + 19;
+
+ // method names that include the property name anywhere are the next
+ // most relevant
+ if (methodName.matches(".*(?i)" + propertyName + ".*"))
+ return BaseRelevance + 18;
+
+ // otherwise return default relevance
+ return 10;
+ }
+ }
+
+ private static class Reset implements IMethodAttribute {
+ private final IType type;
+ private final String propertyName;
+
+ public Reset(IType type, String propertyName) {
+ this.type = type;
+ this.propertyName = propertyName;
+ }
+
+ // From the Qt docs, http://qt-project.org/doc/qt-4.8/properties.html:
+ // A RESET function is optional. It is for setting the property back to its context
+ // specific default value. e.g., QWidget::cursor has the typical READ and WRITE
+ // functions, QWidget::cursor() and QWidget::setCursor(), and it also has a RESET
+ // function, QWidget::unsetCursor(), since no call to QWidget::setCursor() can mean
+ // reset to the context specific cursor. The RESET function must return void and take
+ // no parameters.
+ @Override
+ public boolean keep(ICPPMethod method) {
+
+ // RESET must have void return type
+ IType retType = method.getType().getReturnType();
+ if (!(retType instanceof IBasicType)
+ || ((IBasicType) retType).getKind() != IBasicType.Kind.eVoid)
+ return false;
+
+ // RESET must have no parameters
+ if (method.getParameters().length > 0)
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int getRelevance(ICPPMethod method) {
+ String methodName = method.getName();
+ if (methodName == null)
+ return 0;
+
+ // accessor with "reet" prefix is the most relevant
+ if (methodName.equalsIgnoreCase("reset" + propertyName))
+ return BaseRelevance + 20;
+
+ // method names that include the property name anywhere are the next
+ // most relevant
+ if (methodName.matches(".*(?i)" + propertyName + ".*"))
+ return BaseRelevance + 18;
+
+ // otherwise return default relevance
+ return 10;
+ }
+ }
+
+ private static ICPPClassType getEnclosingClassDefinition(ICEditorContentAssistInvocationContext context) {
+ try {
+ IIndex index = CCorePlugin.getIndexManager().getIndex(context.getProject());
+ ITranslationUnit tu = context.getTranslationUnit();
+ if (tu == null)
+ return null;
+
+ // Disable all unneeded parts of the parser.
+ IASTTranslationUnit astTU
+ = tu.getAST(
+ index,
+ ITranslationUnit.AST_SKIP_FUNCTION_BODIES
+ | ITranslationUnit.AST_SKIP_ALL_HEADERS
+ | ITranslationUnit.AST_CONFIGURE_USING_SOURCE_CONTEXT
+ | ITranslationUnit.AST_SKIP_TRIVIAL_EXPRESSIONS_IN_AGGREGATE_INITIALIZERS
+ | ITranslationUnit.AST_PARSE_INACTIVE_CODE);
+ if (astTU == null)
+ return null;
+
+ IASTNodeSelector selector = astTU.getNodeSelector(null);
+
+ // Macro expansions don't provide valid enclosing nodes. Backup until we are no longer in a
+ // macro expansions. A loop is needed because consecutive expansions have no valid node
+ // between them.
+ int offset = context.getInvocationOffset();
+ IASTNode enclosing;
+ do {
+ enclosing = selector.findEnclosingNode(offset, 0);
+ if (enclosing == null)
+ return null;
+
+ IASTFileLocation location = enclosing.getFileLocation();
+ if (location == null)
+ return null;
+
+ offset = location.getNodeOffset() - 1;
+ } while(offset > 0
+ && !(enclosing instanceof IASTCompositeTypeSpecifier));
+
+ if (!(enclosing instanceof IASTCompositeTypeSpecifier))
+ return null;
+
+ IASTName name = ((IASTCompositeTypeSpecifier) enclosing).getName();
+ if (name == null)
+ return null;
+
+ IBinding binding = name.getBinding();
+ if (binding == null)
+ return null;
+
+ return (ICPPClassType) binding.getAdapter(ICPPClassType.class);
+ } catch(CoreException e) {
+ QtUIPlugin.log(e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Find and return all methods that are accessible in the class definition that encloses the argument
+ * invocation context. Does not return null.
+ */
+ private static Collection<ICPPMethod> getMethods(ICEditorContentAssistInvocationContext context, IMethodAttribute methodAttribute) {
+
+ ICPPClassType cls = getEnclosingClassDefinition(context);
+ if (cls == null)
+ return Collections.emptyList();
+
+ // Return all the methods, including inherited and non-visible ones.
+ ICPPMethod[] methods = cls.getMethods();
+ List<ICPPMethod> filtered = new ArrayList<ICPPMethod>(methods.length);
+ for(ICPPMethod method : methods)
+ if (methodAttribute.keep(method))
+ filtered.add(method);
+
+ // TODO Choose the overload that is the best match -- closest parameter type and fewest
+ // parameters with default values.
+
+ return filtered;
+ }
+
+ private static boolean isAssignable(IType lhs, IType rhs) {
+ // TODO This needs a real assignment check. If the types are different by implicitly convertible
+ // then this should return true.
+ return lhs != null
+ && rhs.isSameType(lhs);
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QPropertyExpansion.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QPropertyExpansion.java
new file mode 100644
index 0000000..20ce678
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QPropertyExpansion.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.ui.assist;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
+import org.eclipse.cdt.core.dom.ast.IType;
+import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTypeId;
+import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;
+import org.eclipse.cdt.internal.corext.template.c.CContextType;
+import org.eclipse.cdt.internal.qt.core.parser.QtParser;
+import org.eclipse.cdt.internal.ui.text.CHeuristicScanner;
+import org.eclipse.cdt.internal.ui.text.Symbols;
+import org.eclipse.cdt.internal.ui.text.contentassist.CCompletionProposal;
+import org.eclipse.cdt.qt.core.QtKeywords;
+import org.eclipse.cdt.qt.core.index.IQProperty;
+import org.eclipse.cdt.qt.ui.QtUIPlugin;
+import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.templates.Template;
+import org.eclipse.jface.text.templates.TemplateContextType;
+
+/**
+ * A utility class for accessing parts of the Q_PROPERTY expansion that have already
+ * been entered as well as the offset of various parts of the declaration. This is
+ * used for things like proposing only parameters that are not already used, offering
+ * appropriate suggestions for a specific parameter, etc.
+ */
+@SuppressWarnings("restriction")
+public class QPropertyExpansion {
+
+ /** The full text of the expansion */
+ private final String expansion;
+
+ /** The offset of the first character in the attributes section. This is usually the
+ * start of READ. */
+ private final int startOfAttrs;
+
+ /** The offset of the cursor in the expansion. */
+ private final int cursor;
+
+ /** The parsed type of the property. */
+ private final IType type;
+
+ /** The parsed name of the property. This is the last identifier before the first attribute. */
+ private final String name;
+
+ /** The identifier at which the cursor is currently pointing. */
+ private final Identifier currIdentifier;
+
+ /** The identifier before the one where the cursor is pointing. This is needed to figure out what
+ * values are valid for an attribute like READ, WRITE, etc. */
+ private final Identifier prevIdentifier;
+
+ // The type/name section ends right before the first attribute.
+ private static final Pattern TYPENAME_REGEX;
+ static {
+ StringBuilder regexBuilder = new StringBuilder();
+ regexBuilder.append("^(?:Q_PROPERTY\\s*\\()?\\s*(.*?)(\\s+)(?:");
+ for(IQProperty.Attribute attr : IQProperty.Attribute.values()) {
+ if (attr.ordinal() > 0)
+ regexBuilder.append('|');
+ regexBuilder.append("(?:");
+ regexBuilder.append(attr.identifier);
+ regexBuilder.append(")");
+ }
+ regexBuilder.append(").*$");
+ TYPENAME_REGEX = Pattern.compile(regexBuilder.toString());
+ }
+
+ /**
+ * A small utility to store the important parts of an identifier. This is just the starting
+ * offset and the text of the identifier.
+ */
+ private static class Identifier {
+ public final int start;
+ public final String ident;
+ public Identifier(int start, String ident) {
+ this.start = start;
+ this.ident = ident;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(start) + ':' + ident;
+ }
+ }
+
+ public static QPropertyExpansion create(ICEditorContentAssistInvocationContext context) {
+
+ // Extract the substring that likely contributes to this Q_PROPERTY declaration. The declaration
+ // could be in any state of being entered, so use the HeuristicScanner to guess about the
+ // possible structure. The fixed assumptions are that the content assistant was invoked within
+ // the expansion parameter of Q_PROPERTY. We try to guess at the end of the String, which is
+ // either the closing paren (within 512 characters from the opening paren) or the current cursor
+ // location.
+
+ // The offset is always right after the opening paren, use it to get to a fixed point in the
+ // declaration.
+ int offset = context.getContextInformationOffset();
+ if (offset < 0)
+ return null;
+
+ IDocument doc = context.getDocument();
+ CHeuristicScanner scanner = new CHeuristicScanner(doc);
+
+ // We should only need to backup the length of Q_PROPERTY, but allow extra to deal
+ // with whitespace.
+ int lowerBound = Math.max(0, offset - 64);
+
+ // Allow up to 512 characters from the opening paren.
+ int upperBound = Math.min(doc.getLength(), offset + 512);
+
+ int openingParen = scanner.findOpeningPeer(offset, lowerBound, '(', ')');
+ if (openingParen == CHeuristicScanner.NOT_FOUND)
+ return null;
+
+ int token = scanner.previousToken(scanner.getPosition() - 1, lowerBound);
+ if (token != Symbols.TokenIDENT)
+ return null;
+
+ // Find the start of the previous identifier. This scans backward, so it stops one
+ // position before the identifier (unless the identifer is at the start of the content).
+ int begin = scanner.getPosition();
+ if (begin != 0)
+ ++begin;
+
+ String identifier = null;
+ try {
+ identifier = doc.get(begin, openingParen - begin);
+ } catch (BadLocationException e) {
+ QtUIPlugin.log(e);
+ }
+
+ if (!QtKeywords.Q_PROPERTY.equals(identifier))
+ return null;
+
+ // advance past the opening paren
+ ++openingParen;
+
+ String expansion = null;
+ int closingParen = scanner.findClosingPeer(openingParen, upperBound, '(', ')');
+
+ // This expansion is not applicable if the assistant was invoked after the closing paren.
+ if (closingParen != CHeuristicScanner.NOT_FOUND
+ && context.getInvocationOffset() > scanner.getPosition())
+ return null;
+
+ try {
+ if (closingParen != CHeuristicScanner.NOT_FOUND)
+ expansion = doc.get(openingParen, closingParen - openingParen);
+ else
+ expansion = doc.get(openingParen, context.getInvocationOffset() - openingParen );
+ } catch (BadLocationException e) {
+ QtUIPlugin.log(e);
+ }
+
+ if (expansion == null)
+ return null;
+
+ int cursor = context.getInvocationOffset();
+ Identifier currIdentifier = identifier(doc, scanner, cursor, lowerBound, upperBound);
+ if (currIdentifier == null)
+ return null;
+ Identifier prevIdentifier = identifier(doc, scanner, currIdentifier.start - 1, lowerBound, upperBound);
+
+ // There are two significant regions in a Q_PROPERTY declaration. The first is everything
+ // between the opening paren and the first parameter. This region specifies the type and the
+ // name. The other is the region that declares all the parameters. There is an arbitrary
+ // amount of whitespace between these regions.
+ //
+ // This function finds and returns the offset of the end of the region containing the type and
+ // name. Returns 0 if the type/name region cannot be found.
+ IType type = null;
+ String name = null;
+ int endOfTypeName = 0;
+ Matcher m = TYPENAME_REGEX.matcher(expansion);
+ if (m.matches()) {
+ endOfTypeName = openingParen + m.end(2);
+
+ // parse the type/name part and then extract the type and name from the result
+ ICPPASTTypeId typeId = QtParser.parseTypeId(m.group(1));
+ type = CPPVisitor.createType(typeId);
+
+ IASTDeclarator declarator = typeId.getAbstractDeclarator();
+ if (declarator != null
+ && declarator.getName() != null)
+ name = declarator.getName().toString();
+ }
+
+ return new QPropertyExpansion(expansion, endOfTypeName, cursor, type, name, prevIdentifier, currIdentifier);
+ }
+
+ private QPropertyExpansion(String expansion, int startOfAttrs, int cursor, IType type, String name, Identifier prev, Identifier curr) {
+ this.expansion = expansion;
+ this.startOfAttrs = startOfAttrs;
+ this.cursor = cursor;
+
+ this.type = type;
+ this.name = name;
+ this.prevIdentifier = prev;
+ this.currIdentifier = curr;
+ }
+
+ public String getCurrIdentifier() {
+ return currIdentifier.ident;
+ }
+
+ public String getPrevIdentifier() {
+ return prevIdentifier.ident;
+ }
+
+ public String getPrefix() {
+ if (currIdentifier.ident == null)
+ return null;
+
+ if (cursor > currIdentifier.start + currIdentifier.ident.length())
+ return null;
+
+ return currIdentifier.ident.substring(0, cursor - currIdentifier.start);
+ }
+
+ private static class Attribute {
+ public final IQProperty.Attribute attribute;
+ public final int relevance;
+
+ public Attribute(IQProperty.Attribute attribute) {
+ this.attribute = attribute;
+
+ // Give attribute proposals the same order as the Qt documentation.
+ switch(attribute) {
+ case READ: this.relevance = 11; break;
+ case WRITE: this.relevance = 10; break;
+ case RESET: this.relevance = 9; break;
+ case NOTIFY: this.relevance = 8; break;
+ case REVISION: this.relevance = 7; break;
+ case DESIGNABLE: this.relevance = 6; break;
+ case SCRIPTABLE: this.relevance = 5; break;
+ case STORED: this.relevance = 4; break;
+ case USER: this.relevance = 3; break;
+ case CONSTANT: this.relevance = 2; break;
+ case FINAL: this.relevance = 1; break;
+ default: this.relevance = 0; break;
+ }
+ }
+
+ public ICompletionProposal getProposal(String contextId, ICEditorContentAssistInvocationContext context) {
+
+ // Attributes without values propose only their own identifier.
+ if (!attribute.hasValue)
+ return new CCompletionProposal(attribute.identifier, context.getInvocationOffset(), 0, QtUIPlugin.getQtLogo(), attribute.identifier + " - Q_PROPERTY declaration parameter", relevance);
+
+ // Otherwise create a template where the content depends on the type of the attribute's parameter.
+ String display = attribute.identifier + ' ' + attribute.paramName;
+ String replacement = attribute.identifier;
+ if ("bool".equals(attribute.paramName))
+ replacement += " ${true}";
+ else if ("int".equals(attribute.paramName))
+ replacement += " ${0}";
+ else if (attribute.paramName != null)
+ replacement += " ${" + attribute.paramName + '}';
+
+ return templateProposal(contextId, context, display, replacement, relevance);
+ }
+ }
+
+ private static ICompletionProposal templateProposal(String contextId, ICEditorContentAssistInvocationContext context, String display, String replacement, int relevance) {
+ Template template = new Template(display, "Q_PROPERTY declaration parameter", contextId, replacement, true);
+
+ TemplateContextType ctxType = new CContextType();
+ ctxType.setId(contextId);
+
+ QtProposalContext templateCtx = new QtProposalContext(context, ctxType);
+ Region region = new Region(templateCtx.getCompletionOffset(), templateCtx.getCompletionLength());
+ return new QtTemplateProposal(template, templateCtx, region, relevance);
+ }
+
+ public List<ICompletionProposal> getProposals(String contextId, ICEditorContentAssistInvocationContext context) {
+
+ // Make no suggestions when the start of the current identifier is before the end of
+ // the "type name" portion of the declaration.
+ if (currIdentifier.start < startOfAttrs)
+ return Collections.emptyList();
+
+ // Propose nothing but READ as the first attribute. If the previous identifier is before
+ // the end of the typeName region, then we're currently at the first attribute.
+ if (prevIdentifier.start < startOfAttrs)
+ return Collections.singletonList(new Attribute(IQProperty.Attribute.READ).getProposal(contextId, context));
+
+ // If the previous token is an Attribute name that has a parameter then suggest appropriate
+ // values for that parameter. Otherwise suggest the other Attribute names.
+
+ String prefix = getPrefix();
+
+ // There are two types of proposals. If the previous identifier matches a known attribute name,
+ // then we propose possible values for that attribute. Otherwise we want to propose the identifiers
+ // that don't already appear in the expansion.
+ //
+ // This is implemented by iterating over the list of known attributes. If any of the attributes
+ // matches the previous identifier, then we build and return a list of valid proposals for that
+ // attribute.
+ //
+ // Otherwise, for each attribute we build a regular expression that checks to see if that token
+ // appears within the expansion. If it already appears, then the attribute is ignored. Otherwise
+ // it is added as an unspecified attribute. If the loop completes, then we create a list of proposals
+ // for from that unspecified list.
+
+ List<Attribute> unspecifiedAttributes = new ArrayList<Attribute>();
+ for(IQProperty.Attribute attr : IQProperty.Attribute.values()) {
+ if (attr.hasValue
+ && (prevIdentifier != null && attr.identifier.equals(prevIdentifier.ident))) {
+
+ Collection<QPropertyAttributeProposal> attrProposals = QPropertyAttributeProposal.buildProposals(attr, context, type, name);
+ if (attrProposals != null) {
+ List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
+ for(QPropertyAttributeProposal value : attrProposals)
+ if (prefix == null
+ || value.getIdentifier().startsWith(prefix))
+ proposals.add(value.createProposal(prefix, context.getInvocationOffset()));
+ return proposals;
+ }
+
+ return Collections.emptyList();
+ }
+
+ if (prefix != null) {
+ if (attr.identifier.startsWith(prefix)
+ &&(!expansion.matches(".*\\s+" + attr.identifier + "\\s+.*")
+ ||attr.identifier.equals(currIdentifier.ident)))
+ unspecifiedAttributes.add(new Attribute(attr));
+ } else if (!expansion.matches(".*\\s+" + attr.identifier + "\\s+.*"))
+ unspecifiedAttributes.add(new Attribute(attr));
+ }
+
+ List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
+ for(Attribute attr : unspecifiedAttributes) {
+ ICompletionProposal proposal = attr.getProposal(contextId, context);
+ if (proposal != null)
+ proposals.add(proposal);
+ }
+
+ return proposals;
+ }
+
+ private static Identifier identifier(IDocument doc, CHeuristicScanner scanner, int cursor, int lower, int upper) {
+ try {
+ // If the cursor is in whitespace, then the current identifier is null. Scan backward to find
+ // the start of this whitespace.
+ if (Character.isWhitespace(doc.getChar(cursor - 1))) {
+ int prev = scanner.findNonWhitespaceBackward(cursor, lower);
+ return new Identifier(Math.min(cursor, prev + 1), null);
+ }
+
+ int tok = scanner.previousToken(cursor, lower);
+ if (tok != CHeuristicScanner.TokenIDENT)
+ return null;
+ int begin = scanner.getPosition() + 1;
+
+ tok = scanner.nextToken(begin, upper);
+ if (tok != CHeuristicScanner.TokenIDENT)
+ return null;
+ int end = scanner.getPosition();
+
+ return new Identifier(begin, doc.get(begin, end - begin));
+ } catch(BadLocationException e) {
+ QtUIPlugin.log(e);
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ if (expansion == null)
+ return super.toString();
+
+ if (cursor >= expansion.length())
+ return expansion + '|';
+ if (cursor < 0)
+ return "|" + expansion;
+
+ return expansion.substring(0, cursor) + '|' + expansion.substring(cursor);
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QtProposalContext.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QtProposalContext.java
new file mode 100644
index 0000000..b4dc457
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QtProposalContext.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.ui.assist;
+
+import org.eclipse.cdt.core.dom.ast.IASTCompletionNode;
+import org.eclipse.cdt.internal.corext.template.c.CContext;
+import org.eclipse.cdt.ui.text.contentassist.ICEditorContentAssistInvocationContext;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.templates.Template;
+import org.eclipse.jface.text.templates.TemplateContextType;
+import org.eclipse.swt.graphics.Point;
+
+@SuppressWarnings("restriction")
+public class QtProposalContext extends CContext {
+
+ private final String contextId;
+
+ public QtProposalContext(ICEditorContentAssistInvocationContext context, TemplateContextType ctxType) {
+ super(ctxType, context.getDocument(), getCompletionPosition(context), context.getTranslationUnit());
+ this.contextId = ctxType.getId();
+ }
+
+ private static Position getCompletionPosition(ICEditorContentAssistInvocationContext context) {
+ // The normal CDT behaviour is to not offer template proposals when text is selected. I
+ // don't know why they avoid it, so I've opted to replace the selection instead.
+
+ int adjustment = 0;
+ IASTCompletionNode node = context.getCompletionNode();
+ if (node != null) {
+ String prefix = node.getPrefix();
+ if (prefix != null)
+ adjustment -= prefix.length();
+ }
+
+ int length = -adjustment;
+ ITextViewer viewer = context.getViewer();
+ if (viewer != null) {
+ Point selection = viewer.getSelectedRange();
+ if (selection != null
+ && selection.y > 0)
+ length += selection.y;
+ }
+
+ int offset = context.getInvocationOffset() + adjustment;
+ return new Position(offset, length);
+ }
+
+ @Override
+ public boolean canEvaluate(Template template) {
+ // The base implementation uses a length of 0 to create an empty string for the key
+ // and then refuses to apply the template. This override offers all templates that
+ // have the right ID. This is ok, because only the templates that apply were proposed.
+ return contextId.equals(template.getContextTypeId());
+ }
+
+ @Override
+ public int getStart() {
+ // The base implementation creates a different offset when the replacement length
+ // is not 0. We need to use the same start of the replacement region regardless of
+ // whether or not characters are selected.
+
+ try {
+ IDocument document= getDocument();
+
+ int start= getCompletionOffset();
+ int end= getCompletionOffset() + getCompletionLength();
+
+ while (start != 0 && isUnicodeIdentifierPartOrPoundSign(document.getChar(start - 1)))
+ start--;
+
+ while (start != end && Character.isWhitespace(document.getChar(start)))
+ start++;
+
+ if (start == end)
+ start= getCompletionOffset();
+
+ return start;
+ } catch (BadLocationException e) {
+ return super.getStart();
+ }
+ }
+
+ private boolean isUnicodeIdentifierPartOrPoundSign(char c) {
+ return Character.isUnicodeIdentifierPart(c) || c == '#';
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QtTemplateProposal.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QtTemplateProposal.java
new file mode 100644
index 0000000..4c7fd6c
--- /dev/null
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/internal/qt/ui/assist/QtTemplateProposal.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2013 QNX Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.cdt.internal.qt.ui.assist;
+
+import org.eclipse.cdt.qt.ui.QtUIPlugin;
+import org.eclipse.cdt.ui.text.ICCompletionProposal;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.templates.Template;
+import org.eclipse.jface.text.templates.TemplateContext;
+import org.eclipse.jface.text.templates.TemplateProposal;
+
+public class QtTemplateProposal extends TemplateProposal implements ICCompletionProposal {
+
+ // The Qt proposals are made more relevant than the default built- proposals.
+ private static int BASE_RELEVANCE = 1100;
+
+ public QtTemplateProposal(Template template, TemplateContext context, IRegion region) {
+ this(template, context, region, 0);
+ }
+
+ public QtTemplateProposal(Template template, TemplateContext context, IRegion region, int relevance) {
+ super(template, context, region, QtUIPlugin.getQtLogo(), BASE_RELEVANCE + relevance);
+ }
+
+ @Override
+ public String getIdString() {
+ return getDisplayString();
+ }
+}
diff --git a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/ui/QtUIPlugin.java b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/ui/QtUIPlugin.java
index a24fb4d..a88b877 100644
--- a/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/ui/QtUIPlugin.java
+++ b/qt/org.eclipse.cdt.qt.ui/src/org/eclipse/cdt/qt/ui/QtUIPlugin.java
@@ -11,6 +11,7 @@ import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
+import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
@@ -31,6 +32,14 @@ public class QtUIPlugin extends AbstractUIPlugin {
public QtUIPlugin() {
}
+ public static Image getQtLogo() {
+ return null;
+ }
+
+ public static Image getQtLogoLarge() {
+ return null;
+ }
+
/*
* (non-Javadoc)
* @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)