Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

Styliser.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. /*
  2. * Copyright (c) 2006-2007 Chris Smith, Shane Mc Cormack, Gregory Holmes
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.ui.messages;
  23. import java.util.Locale;
  24. import javax.swing.text.BadLocationException;
  25. import javax.swing.text.SimpleAttributeSet;
  26. import javax.swing.text.StyleConstants;
  27. import javax.swing.text.StyledDocument;
  28. import com.dmdirc.logger.ErrorLevel;
  29. import com.dmdirc.logger.Logger;
  30. /**
  31. * The styliser applies IRC styles to text. Styles are indicated by various
  32. * control codes which are a de-facto IRC standard.
  33. * @author chris
  34. */
  35. public final class Styliser {
  36. /** The character used for marking up bold text. */
  37. public static final char CODE_BOLD = 2;
  38. /** The character used for marking up coloured text. */
  39. public static final char CODE_COLOUR = 3;
  40. /** The character used for marking up coloured text (using hex). */
  41. public static final char CODE_HEXCOLOUR = 4;
  42. /** The character used for stopping all formatting. */
  43. public static final char CODE_STOP = 15;
  44. /** The character used for marking up fixed pitch text. */
  45. public static final char CODE_FIXED = 17;
  46. /** The character used for marking up italic text. */
  47. public static final char CODE_ITALIC = 29;
  48. /** The character used for marking up underlined text. */
  49. public static final char CODE_UNDERLINE = 31;
  50. /** Creates a new instance of Styliser. */
  51. private Styliser() {
  52. }
  53. /**
  54. * Stylises the specified string and adds it to the passed StyledDocument.
  55. * @param doc The document which the output should be added to
  56. * @param add The line to be stylised and added
  57. */
  58. public static void addStyledString(final StyledDocument doc,
  59. final String add) {
  60. try {
  61. int offset = doc.getLength();
  62. int position = 0;
  63. final SimpleAttributeSet attribs = new SimpleAttributeSet();
  64. while (position < add.length()) {
  65. final String next = readUntilControl(add.substring(position));
  66. doc.insertString(offset, next, attribs);
  67. position += next.length();
  68. offset += next.length();
  69. if (position < add.length()) {
  70. position += readControlChars(add.substring(position),
  71. attribs, position == 0);
  72. }
  73. }
  74. } catch (BadLocationException ex) {
  75. Logger.error(ErrorLevel.WARNING, "Unable to insert styled string", ex);
  76. }
  77. }
  78. /**
  79. * Strips all recognised control codes from the input string.
  80. * @param input the String to be stripped
  81. * @return a copy of the input with control codes removed
  82. */
  83. public static String stipControlCodes(final String input) {
  84. final SimpleAttributeSet attribs = new SimpleAttributeSet();
  85. int position = 0;
  86. String output = "";
  87. while (position < input.length()) {
  88. final String next = readUntilControl(input.substring(position));
  89. output = output.concat(next);
  90. position += next.length();
  91. if (position < input.length()) {
  92. position += readControlChars(input.substring(position),
  93. attribs, position == 0);
  94. }
  95. }
  96. return output;
  97. }
  98. /**
  99. * Returns a substring of the input string such that no control codes are present
  100. * in the output. If the returned value isn't the same as the input, then the
  101. * character immediately after is a control character.
  102. * @param input The string to read from
  103. * @return A substring of the input containing no control characters
  104. */
  105. static String readUntilControl(final String input) {
  106. int pos = input.length();
  107. pos = checkChar(pos, input.indexOf(CODE_BOLD));
  108. pos = checkChar(pos, input.indexOf(CODE_UNDERLINE));
  109. pos = checkChar(pos, input.indexOf(CODE_STOP));
  110. pos = checkChar(pos, input.indexOf(CODE_COLOUR));
  111. pos = checkChar(pos, input.indexOf(CODE_HEXCOLOUR));
  112. pos = checkChar(pos, input.indexOf(CODE_ITALIC));
  113. pos = checkChar(pos, input.indexOf(CODE_FIXED));
  114. return input.substring(0, pos);
  115. }
  116. /**
  117. * Helper function used in readUntilControl. Checks if i is a valid index of
  118. * the string (i.e., it's not -1), and then returns the minimum of pos and i.
  119. * @param pos The current position in the string
  120. * @param i The index of the first occurance of some character
  121. * @return The new position (see implementation)
  122. */
  123. private static int checkChar(final int pos, final int i) {
  124. if (i < pos && i != -1) { return i; }
  125. return pos;
  126. }
  127. /**
  128. * Reads the first control character from the input string (and any arguments
  129. * it takes), and applies it to the specified attribute set.
  130. * @return The number of characters read as control characters
  131. * @param string The string to read from
  132. * @param attribs The attribute set that new attributes will be applied to
  133. * @param isStart Whether this is at the start of the string or not
  134. */
  135. private static int readControlChars(final String string,
  136. final SimpleAttributeSet attribs, final boolean isStart) {
  137. // Bold
  138. if (string.charAt(0) == CODE_BOLD) {
  139. toggleAttribute(attribs, StyleConstants.FontConstants.Bold);
  140. return 1;
  141. }
  142. // Underline
  143. if (string.charAt(0) == CODE_UNDERLINE) {
  144. toggleAttribute(attribs, StyleConstants.FontConstants.Underline);
  145. return 1;
  146. }
  147. // Italic
  148. if (string.charAt(0) == CODE_ITALIC) {
  149. toggleAttribute(attribs, StyleConstants.FontConstants.Italic);
  150. return 1;
  151. }
  152. // Fixed pitch
  153. if (string.charAt(0) == CODE_FIXED) {
  154. if (attribs.containsAttribute(StyleConstants.FontConstants.FontFamily, "monospaced")) {
  155. attribs.removeAttribute(StyleConstants.FontConstants.FontFamily);
  156. } else {
  157. attribs.removeAttribute(StyleConstants.FontConstants.FontFamily);
  158. attribs.addAttribute(StyleConstants.FontConstants.FontFamily, "monospaced");
  159. }
  160. return 1;
  161. }
  162. // Stop formatting
  163. if (string.charAt(0) == CODE_STOP) {
  164. resetAttributes(attribs);
  165. return 1;
  166. }
  167. // Colours
  168. if (string.charAt(0) == CODE_COLOUR) {
  169. int count = 1;
  170. // This isn't too nice!
  171. if (string.length() > count && isInt(string.charAt(count))) {
  172. int foreground = string.charAt(count) - '0';
  173. count++;
  174. if (string.length() > count && isInt(string.charAt(count))) {
  175. foreground = foreground * 10 + (string.charAt(count) - '0');
  176. count++;
  177. }
  178. foreground = foreground % 16;
  179. setForeground(attribs, String.valueOf(foreground));
  180. if (isStart) {
  181. setDefaultForeground(attribs, String.valueOf(foreground));
  182. }
  183. // Now background
  184. if (string.length() > count && string.charAt(count) == ','
  185. && string.length() > count + 1
  186. && isInt(string.charAt(count + 1))) {
  187. int background = string.charAt(count + 1) - '0';
  188. count += 2; // Comma and first digit
  189. if (string.length() > count && isInt(string.charAt(count))) {
  190. background = background * 10 + (string.charAt(count) - '0');
  191. count++;
  192. }
  193. background = background % 16;
  194. setBackground(attribs, String.valueOf(background));
  195. if (isStart) {
  196. setDefaultBackground(attribs, String.valueOf(background));
  197. }
  198. }
  199. } else {
  200. resetColour(attribs);
  201. }
  202. return count;
  203. }
  204. // Hex colours
  205. if (string.charAt(0) == CODE_HEXCOLOUR) {
  206. int count = 1;
  207. if (hasHexString(string, 1)) {
  208. setForeground(attribs, string.substring(1, 7).toUpperCase());
  209. if (isStart) {
  210. setDefaultForeground(attribs, string.substring(1, 7).toUpperCase());
  211. }
  212. count = count + 6;
  213. // Now for background
  214. if (string.charAt(count) == ',' && hasHexString(string, count + 1)) {
  215. count++;
  216. setBackground(attribs, string.substring(count, count + 6).toUpperCase());
  217. if (isStart) {
  218. setDefaultBackground(attribs, string.substring(count, count + 6).toUpperCase());
  219. }
  220. count += 6;
  221. }
  222. } else {
  223. resetColour(attribs);
  224. }
  225. return count;
  226. }
  227. return 0;
  228. }
  229. /**
  230. * Determines if the specified character represents a single integer (i.e. 0-9).
  231. * @param c The character to check
  232. * @return True iff the character is in the range [0-9], false otherwise
  233. */
  234. private static boolean isInt(final char c) {
  235. return c >= '0' && c <= '9';
  236. }
  237. /**
  238. * Determines if the specified character represents a single hex digit
  239. * (i.e., 0-F).
  240. * @param c The character to check
  241. * @return True iff the character is in the range [0-F], false otherwise
  242. */
  243. private static boolean isHex(final char c) {
  244. return isInt(c) || (c >= 'A' && c <= 'Z');
  245. }
  246. /**
  247. * Determines if the specified string has a 6-digit hex string starting at
  248. * the specified offset.
  249. * @param input The string to check
  250. * @param offset The offset to start at
  251. * @return True iff there is a hex string preset at the offset
  252. */
  253. private static boolean hasHexString(final String input, final int offset) {
  254. // If the string's too short, it can't have a hex string
  255. if (input.length() < offset + 6) {
  256. return false;
  257. }
  258. boolean res = true;
  259. for (int i = offset; i < 6 + offset; i++) {
  260. res = res && isHex(input.toUpperCase(Locale.getDefault()).charAt(i));
  261. }
  262. return res;
  263. }
  264. /**
  265. * Toggles the specified attribute. If the attribute exists in the attribute
  266. * set, it is removed. Otherwise, it is added with a value of Boolean.True.
  267. * @param attribs The attribute set to check
  268. * @param attrib The attribute to toggle
  269. */
  270. private static void toggleAttribute(final SimpleAttributeSet attribs,
  271. final Object attrib) {
  272. if (attribs.containsAttribute(attrib, Boolean.TRUE)) {
  273. attribs.removeAttribute(attrib);
  274. } else {
  275. attribs.addAttribute(attrib, Boolean.TRUE);
  276. }
  277. }
  278. /**
  279. * Resets all attributes in the specified attribute list.
  280. * @param attribs The attribute list whose attributes should be reset
  281. */
  282. private static void resetAttributes(final SimpleAttributeSet attribs) {
  283. if (attribs.containsAttribute(StyleConstants.FontConstants.Bold, Boolean.TRUE)) {
  284. attribs.removeAttribute(StyleConstants.FontConstants.Bold);
  285. }
  286. if (attribs.containsAttribute(StyleConstants.FontConstants.Underline, Boolean.TRUE)) {
  287. attribs.removeAttribute(StyleConstants.FontConstants.Underline);
  288. }
  289. if (attribs.containsAttribute(StyleConstants.FontConstants.Italic, Boolean.TRUE)) {
  290. attribs.removeAttribute(StyleConstants.FontConstants.Italic);
  291. }
  292. if (attribs.containsAttribute(StyleConstants.FontConstants.FontFamily, "monospace")) {
  293. final Object defaultFont = attribs.getAttribute("DefaultFontFamily");
  294. attribs.removeAttribute(StyleConstants.FontConstants.FontFamily);
  295. attribs.addAttribute(StyleConstants.FontConstants.FontFamily, defaultFont);
  296. }
  297. resetColour(attribs);
  298. }
  299. /**
  300. * Resets the colour attributes in the specified attribute set.
  301. * @param attribs The attribute set whose colour attributes should be reset
  302. */
  303. private static void resetColour(final SimpleAttributeSet attribs) {
  304. if (attribs.isDefined(StyleConstants.Foreground)) {
  305. attribs.removeAttribute(StyleConstants.Foreground);
  306. }
  307. if (attribs.isDefined("DefaultForeground")) {
  308. attribs.addAttribute(StyleConstants.Foreground,
  309. attribs.getAttribute("DefaultForeground"));
  310. }
  311. if (attribs.isDefined(StyleConstants.Background)) {
  312. attribs.removeAttribute(StyleConstants.Background);
  313. }
  314. if (attribs.isDefined("DefaultBackground")) {
  315. attribs.addAttribute(StyleConstants.Background,
  316. attribs.getAttribute("DefaultBackground"));
  317. }
  318. }
  319. /**
  320. * Sets the foreground colour in the specified attribute set to the colour
  321. * corresponding to the specified colour code or hex.
  322. * @param attribs The attribute set to modify
  323. * @param foreground The colour code/hex of the new foreground colour
  324. */
  325. private static void setForeground(final SimpleAttributeSet attribs,
  326. final String foreground) {
  327. if (attribs.isDefined(StyleConstants.Foreground)) {
  328. attribs.removeAttribute(StyleConstants.Foreground);
  329. }
  330. attribs.addAttribute(StyleConstants.Foreground, ColourManager.parseColour(foreground));
  331. }
  332. /**
  333. * Sets the background colour in the specified attribute set to the colour
  334. * corresponding to the specified colour code or hex.
  335. * @param attribs The attribute set to modify
  336. * @param background The colour code/hex of the new background colour
  337. */
  338. private static void setBackground(final SimpleAttributeSet attribs,
  339. final String background) {
  340. if (attribs.isDefined(StyleConstants.Background)) {
  341. attribs.removeAttribute(StyleConstants.Background);
  342. }
  343. attribs.addAttribute(StyleConstants.Background, ColourManager.parseColour(background));
  344. }
  345. /**
  346. * Sets the default foreground colour (used after an empty ctrl+k or a ctrl+o).
  347. * @param attribs The attribute set to apply this default on
  348. * @param foreground The default foreground colour
  349. */
  350. private static void setDefaultForeground(final SimpleAttributeSet attribs,
  351. final String foreground) {
  352. attribs.addAttribute("DefaultForeground", ColourManager.parseColour(foreground));
  353. }
  354. /**
  355. * Sets the default background colour (used after an empty ctrl+k or a ctrl+o).
  356. * @param attribs The attribute set to apply this default on
  357. * @param background The default background colour
  358. */
  359. private static void setDefaultBackground(final SimpleAttributeSet attribs,
  360. final String background) {
  361. attribs.addAttribute("DefaultBackground", ColourManager.parseColour(background));
  362. }
  363. }