qprefixmatcher.cpp 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. /****************************************************************************
  2. **
  3. ** This file is part of the Qt Extended Opensource Package.
  4. **
  5. ** Copyright (C) 2009 Trolltech ASA.
  6. **
  7. ** Contact: Qt Extended Information (info@qtextended.org)
  8. **
  9. ** This file may be used under the terms of the GNU General Public License
  10. ** version 2.0 as published by the Free Software Foundation and appearing
  11. ** in the file LICENSE.GPL included in the packaging of this file.
  12. **
  13. ** Please review the following information to ensure GNU General Public
  14. ** Licensing requirements will be met:
  15. ** http://www.fsf.org/licensing/licenses/info/GPLv2.html.
  16. **
  17. **
  18. ****************************************************************************/
  19. #include "qprefixmatcher_p.h"
  20. #include <QDebug>
  21. class QPrefixMatcherTarget
  22. {
  23. public:
  24. QObject *target;
  25. int index;
  26. QPrefixMatcherTarget *next;
  27. };
  28. class QPrefixMatcherNode
  29. {
  30. public:
  31. QPrefixMatcherNode()
  32. {
  33. ch = 0;
  34. marker = 0;
  35. mayBeCommand = 0;
  36. type = QPrefixMatcher::Unknown;
  37. next = 0;
  38. children = 0;
  39. targets = 0;
  40. }
  41. ~QPrefixMatcherNode();
  42. ushort ch; // Character at this point in the prefix match.
  43. short marker : 1; // Non-zero if this is a complete prefix match.
  44. short mayBeCommand : 1; // Non-zero if prefix that may be a command.
  45. QPrefixMatcher::Type type; // Type associated with this node.
  46. QPrefixMatcherNode *next; // Next character at this level.
  47. QPrefixMatcherNode *children; // Children of this level.
  48. QPrefixMatcherTarget *targets; // Information about targets for this match.
  49. void remove( QObject *target );
  50. };
  51. QPrefixMatcherNode::~QPrefixMatcherNode()
  52. {
  53. // Delete the children associated with this node.
  54. QPrefixMatcherNode *c = children;
  55. QPrefixMatcherNode *cn;
  56. while ( c != 0 ) {
  57. cn = c->next;
  58. delete c;
  59. c = cn;
  60. }
  61. // Delete the targets associated with this node.
  62. QPrefixMatcherTarget *t = targets;
  63. QPrefixMatcherTarget *tn;
  64. while ( t != 0 ) {
  65. tn = t->next;
  66. delete t;
  67. t = tn;
  68. }
  69. }
  70. void QPrefixMatcherNode::remove( QObject *target )
  71. {
  72. // Remove the target from child nodes.
  73. QPrefixMatcherNode *c = children;
  74. while ( c != 0 ) {
  75. c->remove( target );
  76. c = c->next;
  77. }
  78. // Remove the target from this node.
  79. QPrefixMatcherTarget *t = targets;
  80. while ( t != 0 ) {
  81. if ( t->target == target )
  82. t->target = 0;
  83. t = t->next;
  84. }
  85. }
  86. QPrefixMatcher::QPrefixMatcher( QObject *parent )
  87. : QObject( parent )
  88. {
  89. root = new QPrefixMatcherNode();
  90. }
  91. QPrefixMatcher::~QPrefixMatcher()
  92. {
  93. delete root;
  94. }
  95. // Add a prefix to this matcher, together with a positive type code.
  96. // Optionally, an object target and slot can be supplied. When the
  97. // prefix is matched by "lookup", it will automatically dispatch
  98. // matching values to the indicated slot. The slot should have the
  99. // prototype "name(const QString&)".
  100. void QPrefixMatcher::add( const QString& prefix, QPrefixMatcher::Type type,
  101. bool mayBeCommand, QObject *target, const char *slot )
  102. {
  103. // The prefix needs at least one character.
  104. if ( prefix.isEmpty() )
  105. return;
  106. // Traverse the tree to find the insert position.
  107. QPrefixMatcherNode *node;
  108. QPrefixMatcherNode **reference;
  109. node = root->children;
  110. reference = &(root->children);
  111. for ( int posn = 0; posn < prefix.length(); ++posn ) {
  112. ushort ch = prefix[posn].unicode();
  113. while ( node != 0 && node->ch != ch ) {
  114. reference = &(node->next);
  115. node = node->next;
  116. }
  117. if ( !node ) {
  118. node = new QPrefixMatcherNode();
  119. if ( !node )
  120. return; // Just in case.
  121. node->ch = ch;
  122. *reference = node;
  123. }
  124. if ( ( posn + 1 ) < prefix.length() ) {
  125. reference = &(node->children);
  126. node = node->children;
  127. }
  128. }
  129. node->type = type;
  130. node->marker = 1;
  131. node->mayBeCommand = mayBeCommand;
  132. // Add the target slot information, if necessary.
  133. if ( target && slot ) {
  134. // Resolve the slot to an index that can be used with qt_metacall.
  135. if ( *slot >= '0' && *slot <= '9' )
  136. ++slot;
  137. QByteArray name = QMetaObject::normalizedSignature( slot );
  138. int index = target->metaObject()->indexOfMethod( name.constData() );
  139. if ( index == -1 ) {
  140. qDebug() << "QPrefixMatcher: "
  141. << target->metaObject()->className()
  142. << "::"
  143. << name
  144. << " is not an accessible slot";
  145. return;
  146. }
  147. // If we haven't seen this target before, then trap
  148. // its destroyed() signal so that we can stop sending
  149. // it signals when it disappears.
  150. if ( !targets.contains( target ) ) {
  151. targets.insert( target );
  152. connect( target, SIGNAL(destroyed()),
  153. this, SLOT(targetDestroyed()) );
  154. }
  155. // If the target is already on this node, don't add it again.
  156. QPrefixMatcherTarget *t = node->targets;
  157. while ( t != 0 ) {
  158. if ( t->target == target && t->index == index )
  159. return;
  160. t = t->next;
  161. }
  162. // Add the target information to the current node.
  163. t = new QPrefixMatcherTarget();
  164. t->target = target;
  165. t->index = index;
  166. t->next = node->targets;
  167. node->targets = t;
  168. }
  169. }
  170. // Determine if "command" appears to start with "prefix".
  171. static bool commandMatch( const QString& prefix, const QString& command )
  172. {
  173. QString pref = "AT" + prefix;
  174. if ( pref.length() > 0 && pref[ pref.length() - 1 ] == QChar(':') ) {
  175. return command.startsWith( pref.left( pref.length() - 1 ) );
  176. } else {
  177. return command.startsWith( pref );
  178. }
  179. }
  180. // Look up the prefix of a value, returning its type code.
  181. // If there is no match, return Unknown. If the prefix has an
  182. // associated slot, then activate the slot. The "command"
  183. // indicates the context of the lookup so that a prefix with the
  184. // "mayBeCommand" flag set will not result in slot invocation.
  185. QPrefixMatcher::Type QPrefixMatcher::lookup
  186. ( const QString& value, const QString& command ) const
  187. {
  188. QPrefixMatcherNode *current = root->children;
  189. QPrefixMatcherNode *best = 0;
  190. int bestPosn = 0;
  191. int posn;
  192. ushort ch;
  193. // Bail out if the tree is currently empty.
  194. if ( !current )
  195. return QPrefixMatcher::Unknown;
  196. // Scan the string for a suitable prefix match. We try to find
  197. // the longest such match so that "+CXYZ: W" will override "+CXYZ:".
  198. posn = 0;
  199. while ( posn < value.length() ) {
  200. ch = value[posn++].unicode();
  201. if ( ch >= 'a' && ch <= 'z' )
  202. ch = ch - 'a' + 'A';
  203. while ( current != 0 && current->ch != ch ) {
  204. current = current->next;
  205. }
  206. if ( !current ) {
  207. break;
  208. }
  209. if ( current->marker ) {
  210. best = current;
  211. bestPosn = posn;
  212. }
  213. current = current->children;
  214. }
  215. // Did we find something that matched?
  216. if ( best ) {
  217. if ( !best->mayBeCommand ||
  218. !commandMatch( value.left( bestPosn ), command ) ) {
  219. // Invoke the target slots associated with this match.
  220. QPrefixMatcherTarget *t = best->targets;
  221. void *a[2];
  222. while ( t != 0 ) {
  223. a[0] = (void *)0;
  224. a[1] = (void *)&value;
  225. if ( t->target ) {
  226. t->target->qt_metacall( QMetaObject::InvokeMetaMethod,
  227. t->index, a );
  228. }
  229. t = t->next;
  230. }
  231. return best->type;
  232. } else if ( best->type != QPrefixMatcher::Notification ) {
  233. return best->type;
  234. }
  235. }
  236. // If we get here, we were unable to find a match.
  237. return QPrefixMatcher::Unknown;
  238. }
  239. void QPrefixMatcher::targetDestroyed()
  240. {
  241. QObject *target = sender();
  242. if ( targets.contains( target ) ) {
  243. targets.remove( target );
  244. root->remove( target );
  245. }
  246. }