blob: 08a69e0f1642a076aec339616398cd2ee10eec60 [file] [log] [blame]
/*
* Copyright (c) 2013, 2015 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.IBinding;
import org.eclipse.cdt.core.dom.ast.IType;
/**
* 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
|| name.getSimpleID() == null
|| name.getSimpleID().length <= 0)
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;
}
/**
* 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 IType getTargetType(IASTFunctionCallExpression call, IASTInitializerClause[] args, int argIndex) {
int sigExpIndex = getExpansionArgIndex(args, 0, SignalRegex);
if (argIndex == sigExpIndex)
return getSignalTargetType(sigExpIndex, call, args);
int methodExpIndex = getExpansionArgIndex(args, sigExpIndex + 1, MethodRegex);
if (argIndex == methodExpIndex)
return getMethodTargetType(methodExpIndex, sigExpIndex, call, args);
// Otherwise the given argument is not a SIGNAL or SLOT expansion.
return null;
}
private static IType getSignalTargetType(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 ASTUtil.getBaseType(sigExpIndex == 0 ? call : args[sigExpIndex - 1]);
}
private static IType getMethodTargetType(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.
if (methodExpIndex == (sigExpIndex + 1))
return ASTUtil.getReceiverType(call);
return ASTUtil.getBaseType(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;
}
}