qscimacro.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. // This module implements the QsciMacro class.
  2. //
  3. // Copyright (c) 2017 Riverbank Computing Limited <info@riverbankcomputing.com>
  4. //
  5. // This file is part of QScintilla.
  6. //
  7. // This file may be used under the terms of the GNU General Public License
  8. // version 3.0 as published by the Free Software Foundation and appearing in
  9. // the file LICENSE included in the packaging of this file. Please review the
  10. // following information to ensure the GNU General Public License version 3.0
  11. // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
  12. //
  13. // If you do not wish to use this file under the terms of the GPL version 3.0
  14. // then you may purchase a commercial license. For more information contact
  15. // info@riverbankcomputing.com.
  16. //
  17. // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
  18. // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  19. #include "Qsci/qscimacro.h"
  20. #include <QStringList>
  21. #include "Qsci/qsciscintilla.h"
  22. static int fromHex(unsigned char ch);
  23. // The ctor.
  24. QsciMacro::QsciMacro(QsciScintilla *parent)
  25. : QObject(parent), qsci(parent)
  26. {
  27. }
  28. // The ctor that initialises the macro.
  29. QsciMacro::QsciMacro(const QString &asc, QsciScintilla *parent)
  30. : QObject(parent), qsci(parent)
  31. {
  32. load(asc);
  33. }
  34. // The dtor.
  35. QsciMacro::~QsciMacro()
  36. {
  37. }
  38. // Clear the contents of the macro.
  39. void QsciMacro::clear()
  40. {
  41. macro.clear();
  42. }
  43. // Read a macro from a string.
  44. bool QsciMacro::load(const QString &asc)
  45. {
  46. bool ok = true;
  47. macro.clear();
  48. QStringList fields = asc.split(' ');
  49. int f = 0;
  50. while (f < fields.size())
  51. {
  52. Macro cmd;
  53. unsigned len;
  54. // Extract the 3 fixed fields.
  55. if (f + 3 > fields.size())
  56. {
  57. ok = false;
  58. break;
  59. }
  60. cmd.msg = fields[f++].toUInt(&ok);
  61. if (!ok)
  62. break;
  63. cmd.wParam = fields[f++].toULong(&ok);
  64. if (!ok)
  65. break;
  66. len = fields[f++].toUInt(&ok);
  67. if (!ok)
  68. break;
  69. // Extract any text.
  70. if (len)
  71. {
  72. if (f + 1 > fields.size())
  73. {
  74. ok = false;
  75. break;
  76. }
  77. QByteArray ba = fields[f++].toLatin1();
  78. const char *sp = ba.data();
  79. if (!sp)
  80. {
  81. ok = false;
  82. break;
  83. }
  84. // Because of historical bugs the length field is unreliable.
  85. bool embedded_null = false;
  86. unsigned char ch;
  87. while ((ch = *sp++) != '\0')
  88. {
  89. if (ch == '"' || ch <= ' ' || ch >= 0x7f)
  90. {
  91. ok = false;
  92. break;
  93. }
  94. if (ch == '\\')
  95. {
  96. int b1, b2;
  97. if ((b1 = fromHex(*sp++)) < 0 ||
  98. (b2 = fromHex(*sp++)) < 0)
  99. {
  100. ok = false;
  101. break;
  102. }
  103. ch = (b1 << 4) + b2;
  104. }
  105. if (ch == '\0')
  106. {
  107. // Don't add it now as it may be the terminating '\0'.
  108. embedded_null = true;
  109. }
  110. else
  111. {
  112. if (embedded_null)
  113. {
  114. // Add the pending embedded '\0'.
  115. cmd.text += '\0';
  116. embedded_null = false;
  117. }
  118. cmd.text += ch;
  119. }
  120. }
  121. if (!ok)
  122. break;
  123. }
  124. macro.append(cmd);
  125. }
  126. if (!ok)
  127. macro.clear();
  128. return ok;
  129. }
  130. // Write a macro to a string.
  131. QString QsciMacro::save() const
  132. {
  133. QString ms;
  134. QList<Macro>::const_iterator it;
  135. for (it = macro.begin(); it != macro.end(); ++it)
  136. {
  137. if (!ms.isEmpty())
  138. ms += ' ';
  139. unsigned len = (*it).text.size();
  140. QString m;
  141. ms += m.sprintf("%u %lu %u", (*it).msg, (*it).wParam, len);
  142. if (len)
  143. {
  144. // In Qt v3, if the length is greater than zero then it also
  145. // includes the '\0', so we need to make sure that Qt v4 writes the
  146. // '\0'. That the '\0' is written at all is a bug because
  147. // QCString::size() is used instead of QCString::length(). We
  148. // don't fix this so as not to break old macros. However this is
  149. // still broken because we have already written the unadjusted
  150. // length. So, in summary, the length field should be interpreted
  151. // as a zero/non-zero value, and the end of the data is either at
  152. // the next space or the very end of the data.
  153. ++len;
  154. ms += ' ';
  155. const char *cp = (*it).text.data();
  156. while (len--)
  157. {
  158. unsigned char ch = *cp++;
  159. if (ch == '\\' || ch == '"' || ch <= ' ' || ch >= 0x7f)
  160. {
  161. QString buf;
  162. ms += buf.sprintf("\\%02x", ch);
  163. }
  164. else
  165. ms += ch;
  166. }
  167. }
  168. }
  169. return ms;
  170. }
  171. // Play the macro.
  172. void QsciMacro::play()
  173. {
  174. if (!qsci)
  175. return;
  176. QList<Macro>::const_iterator it;
  177. for (it = macro.begin(); it != macro.end(); ++it)
  178. qsci->SendScintilla((*it).msg, (*it).wParam, (*it).text.data());
  179. }
  180. // Start recording.
  181. void QsciMacro::startRecording()
  182. {
  183. if (!qsci)
  184. return;
  185. macro.clear();
  186. connect(qsci, SIGNAL(SCN_MACRORECORD(unsigned int, unsigned long, void *)),
  187. SLOT(record(unsigned int, unsigned long, void *)));
  188. qsci->SendScintilla(QsciScintillaBase::SCI_STARTRECORD);
  189. }
  190. // End recording.
  191. void QsciMacro::endRecording()
  192. {
  193. if (!qsci)
  194. return;
  195. qsci->SendScintilla(QsciScintillaBase::SCI_STOPRECORD);
  196. qsci->disconnect(this);
  197. }
  198. // Record a command.
  199. void QsciMacro::record(unsigned int msg, unsigned long wParam, void *lParam)
  200. {
  201. Macro m;
  202. m.msg = msg;
  203. m.wParam = wParam;
  204. // Determine commands which need special handling of the parameters.
  205. switch (msg)
  206. {
  207. case QsciScintillaBase::SCI_ADDTEXT:
  208. m.text = QByteArray(reinterpret_cast<const char *>(lParam), wParam);
  209. break;
  210. case QsciScintillaBase::SCI_REPLACESEL:
  211. if (!macro.isEmpty() && macro.last().msg == QsciScintillaBase::SCI_REPLACESEL)
  212. {
  213. // This is the command used for ordinary user input so it's a
  214. // significant space reduction to append it to the previous
  215. // command.
  216. macro.last().text.append(reinterpret_cast<const char *>(lParam));
  217. return;
  218. }
  219. /* Drop through. */
  220. case QsciScintillaBase::SCI_INSERTTEXT:
  221. case QsciScintillaBase::SCI_APPENDTEXT:
  222. case QsciScintillaBase::SCI_SEARCHNEXT:
  223. case QsciScintillaBase::SCI_SEARCHPREV:
  224. m.text.append(reinterpret_cast<const char *>(lParam));
  225. break;
  226. }
  227. macro.append(m);
  228. }
  229. // Return the given hex character as a binary.
  230. static int fromHex(unsigned char ch)
  231. {
  232. if (ch >= '0' && ch <= '9')
  233. return ch - '0';
  234. if (ch >= 'a' && ch <= 'f')
  235. return ch - 'a' + 10;
  236. return -1;
  237. }