Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. /*
  2. *
  3. * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. * SOFTWARE.
  22. */
  23. package com.dmdirc.addons.ui_swing.components;
  24. import com.dmdirc.Channel;
  25. import com.dmdirc.addons.ui_swing.SwingController;
  26. import com.dmdirc.addons.ui_swing.UIUtilities;
  27. import com.dmdirc.addons.ui_swing.actions.NoNewlinesPasteAction;
  28. import com.dmdirc.addons.ui_swing.components.frames.ChannelFrame;
  29. import com.dmdirc.config.IdentityManager;
  30. import com.dmdirc.interfaces.ConfigChangeListener;
  31. import com.dmdirc.parser.interfaces.ChannelInfo;
  32. import com.dmdirc.parser.interfaces.Parser;
  33. import com.dmdirc.parser.interfaces.callbacks.ChannelTopicListener;
  34. import com.dmdirc.ui.IconManager;
  35. import com.dmdirc.ui.messages.ColourManager;
  36. import com.dmdirc.ui.messages.Styliser;
  37. import com.dmdirc.util.URLHandler;
  38. import java.awt.Color;
  39. import java.awt.event.ActionEvent;
  40. import java.awt.event.ActionListener;
  41. import java.awt.event.MouseEvent;
  42. import java.awt.event.MouseListener;
  43. import javax.swing.AbstractAction;
  44. import javax.swing.JButton;
  45. import javax.swing.JComponent;
  46. import javax.swing.JLabel;
  47. import javax.swing.JScrollPane;
  48. import javax.swing.KeyStroke;
  49. import javax.swing.event.DocumentEvent;
  50. import javax.swing.event.DocumentListener;
  51. import javax.swing.event.HyperlinkEvent;
  52. import javax.swing.event.HyperlinkListener;
  53. import javax.swing.text.AbstractDocument;
  54. import javax.swing.text.BadLocationException;
  55. import javax.swing.text.BoxView;
  56. import javax.swing.text.ComponentView;
  57. import javax.swing.text.DefaultStyledDocument;
  58. import javax.swing.text.Element;
  59. import javax.swing.text.GlyphView;
  60. import javax.swing.text.IconView;
  61. import javax.swing.text.LabelView;
  62. import javax.swing.text.ParagraphView;
  63. import javax.swing.text.SimpleAttributeSet;
  64. import javax.swing.text.StyleConstants;
  65. import javax.swing.text.StyledDocument;
  66. import javax.swing.text.StyledEditorKit;
  67. import javax.swing.text.View;
  68. import javax.swing.text.ViewFactory;
  69. import net.miginfocom.swing.MigLayout;
  70. /**
  71. * Component to show and edit topics for a channel.
  72. */
  73. public class TopicBar extends JComponent implements ActionListener,
  74. ConfigChangeListener, ChannelTopicListener, HyperlinkListener,
  75. MouseListener, DocumentListener {
  76. /**
  77. * A version number for this class. It should be changed whenever the class
  78. * structure is changed (or anything else that would prevent serialized
  79. * objects being unserialized with the new class).
  80. */
  81. private static final long serialVersionUID = 1;
  82. /** Topic text. */
  83. private final TextPaneInputField topicText;
  84. /** Edit button. */
  85. private final JButton topicEdit;
  86. /** Cancel button. */
  87. private final JButton topicCancel;
  88. /** Associated channel. */
  89. private Channel channel;
  90. /** Controller. */
  91. private SwingController controller;
  92. /** Empty Attrib set. */
  93. private SimpleAttributeSet as;
  94. /** Foreground Colour. */
  95. private Color foregroundColour;
  96. /** Background Colour. */
  97. private Color backgroundColour;
  98. /** the maximum length allowed for a topic. */
  99. private int topicLengthMax;
  100. /** Error icon. */
  101. private final JLabel errorIcon;
  102. /**
  103. * Instantiates a new topic bar.
  104. *
  105. * @param channelFrame Parent channel frame
  106. */
  107. public TopicBar(final ChannelFrame channelFrame) {
  108. this.channel = channelFrame.getChannel();
  109. controller = channelFrame.getController();
  110. topicText = new TextPaneInputField();
  111. topicLengthMax = channel.getServer().getParser().getMaxTopicLength();
  112. errorIcon =
  113. new JLabel(IconManager.getIconManager().getIcon("input-error"));
  114. //TODO issue 3251
  115. //if (channelFrame.getConfigManager().getOptionBool(controller.
  116. // getDomain(), "showfulltopic")) {
  117. // topicText.setEditorKit(new StyledEditorKit());
  118. //} else {
  119. topicText.setEditorKit(new WrapEditorKit());
  120. //}
  121. ((DefaultStyledDocument) topicText.getDocument()).setDocumentFilter(
  122. new NewlinesDocumentFilter());
  123. topicText.getActionMap().put("paste-from-clipboard",
  124. new NoNewlinesPasteAction());
  125. topicEdit = new ImageButton("edit", IconManager.getIconManager().
  126. getIcon("edit-inactive"), IconManager.getIconManager().
  127. getIcon("edit"));
  128. topicCancel = new ImageButton("cancel", IconManager.getIconManager().
  129. getIcon("close"), IconManager.getIconManager().
  130. getIcon("close-active"));
  131. new SwingInputHandler(topicText, channelFrame.getCommandParser(),
  132. channelFrame).setTypes(false,
  133. false, true, false);
  134. topicText.setFocusable(false);
  135. topicText.setEditable(false);
  136. topicCancel.setVisible(false);
  137. final JScrollPane sp = new JScrollPane(topicText);
  138. sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
  139. sp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
  140. setLayout(new MigLayout("fillx, ins 0, hidemode 3"));
  141. add(sp, "growx, pushx");
  142. add(errorIcon, "");
  143. add(topicCancel, "");
  144. add(topicEdit, "");
  145. channel.getChannelInfo().getParser().getCallbackManager().addCallback(
  146. ChannelTopicListener.class, this, channel.getChannelInfo().
  147. getName());
  148. topicText.addActionListener(this);
  149. topicEdit.addActionListener(this);
  150. topicCancel.addActionListener(this);
  151. topicText.getInputMap().put(KeyStroke.getKeyStroke("ENTER"),
  152. "enterButton");
  153. topicText.getActionMap().put("enterButton", new AbstractAction(
  154. "enterButton") {
  155. private static final long serialVersionUID = 1;
  156. /** {@inheritDoc} */
  157. @Override
  158. public void actionPerformed(ActionEvent e) {
  159. TopicBar.this.actionPerformed(e);
  160. }
  161. });
  162. topicText.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"),
  163. "escapeButton");
  164. topicText.getActionMap().put("escapeButton", new AbstractAction(
  165. "escapeButton") {
  166. private static final long serialVersionUID = 1;
  167. /** {@inheritDoc} */
  168. @Override
  169. public void actionPerformed(ActionEvent e) {
  170. e.setSource(topicCancel);
  171. TopicBar.this.actionPerformed(e);
  172. }
  173. });
  174. topicText.addHyperlinkListener(this);
  175. topicText.addMouseListener(this);
  176. topicText.getDocument().addDocumentListener(this);
  177. IdentityManager.getGlobalConfig().addChangeListener(
  178. "ui", "backgroundcolour", this);
  179. IdentityManager.getGlobalConfig().addChangeListener(
  180. "ui", "foregroundcolour", this);
  181. IdentityManager.getGlobalConfig().addChangeListener(
  182. "ui", "inputbackgroundcolour", this);
  183. IdentityManager.getGlobalConfig().addChangeListener(
  184. "ui", "inputforegroundcolour", this);
  185. IdentityManager.getGlobalConfig().addChangeListener(
  186. controller.getDomain(), "showfulltopic", this);
  187. IdentityManager.getGlobalConfig().addChangeListener(
  188. controller.getDomain(), "hideEmptyTopicBar", this);
  189. setColours();
  190. }
  191. /** {@inheritDoc} */
  192. @Override
  193. public void onChannelTopic(final Parser tParser, ChannelInfo cChannel,
  194. boolean bIsJoinTopic) {
  195. topicChanged();
  196. }
  197. /**
  198. * Topic has changed, update topic.
  199. */
  200. private void topicChanged() {
  201. topicText.setText("");
  202. setAttributes();
  203. ((DefaultStyledDocument) topicText.getDocument()).setCharacterAttributes(
  204. 0, Integer.MAX_VALUE, as, true);
  205. if (channel.getCurrentTopic() != null) {
  206. Styliser.addStyledString((StyledDocument) topicText.getDocument(),
  207. new String[]{Styliser.CODE_HEXCOLOUR + ColourManager.getHex(
  208. foregroundColour) + channel.getCurrentTopic().getTopic(),},
  209. as);
  210. }
  211. if (channel.getConfigManager().getOptionBool(controller.getDomain(),
  212. "hideEmptyTopicBar")) {
  213. setVisible(topicText.getDocument().getLength() != 0);
  214. }
  215. topicText.setCaretPosition(0);
  216. validateTopic();
  217. }
  218. /**
  219. * {@inheritDoc}
  220. *
  221. * @param e Action event
  222. */
  223. @Override
  224. public void actionPerformed(final ActionEvent e) {
  225. if (e.getSource() == topicEdit || e.getSource() == topicText) {
  226. if (topicText.isEditable()) {
  227. channel.setTopic(topicText.getText());
  228. ((ChannelFrame) channel.getFrame()).getInputField().
  229. requestFocusInWindow();
  230. topicChanged();
  231. topicText.setFocusable(false);
  232. topicText.setEditable(false);
  233. topicCancel.setVisible(false);
  234. } else {
  235. topicText.setVisible(false);
  236. topicText.setText("");
  237. setAttributes();
  238. ((DefaultStyledDocument) topicText.getDocument()).
  239. setCharacterAttributes(
  240. 0, Integer.MAX_VALUE, as, true);
  241. if (channel.getCurrentTopic() != null) {
  242. topicText.setText(channel.getCurrentTopic().getTopic());
  243. }
  244. topicText.setCaretPosition(0);
  245. topicText.setFocusable(true);
  246. topicText.setEditable(true);
  247. topicText.setVisible(true);
  248. topicText.requestFocusInWindow();
  249. topicCancel.setVisible(true);
  250. }
  251. } else if (e.getSource() == topicCancel) {
  252. topicText.setFocusable(false);
  253. topicText.setEditable(false);
  254. topicCancel.setVisible(false);
  255. ((ChannelFrame) channel.getFrame()).getInputField().
  256. requestFocusInWindow();
  257. topicChanged();
  258. }
  259. }
  260. /** {@inheritDoc} */
  261. @Override
  262. public void hyperlinkUpdate(final HyperlinkEvent e) {
  263. if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
  264. URLHandler.getURLHander().launchApp(e.getURL());
  265. }
  266. }
  267. private void setColours() {
  268. backgroundColour = channel.getConfigManager().getOptionColour(
  269. "ui", "inputbackgroundcolour", "ui", "backgroundcolour");
  270. foregroundColour = channel.getConfigManager().getOptionColour(
  271. "ui", "inputforegroundcolour", "ui", "foregroundcolour");
  272. setBackground(backgroundColour);
  273. setForeground(foregroundColour);
  274. setDisabledTextColour(foregroundColour);
  275. setCaretColor(foregroundColour);
  276. setAttributes();
  277. }
  278. private void setAttributes() {
  279. as = new SimpleAttributeSet();
  280. StyleConstants.setFontFamily(as, topicText.getFont().getFamily());
  281. StyleConstants.setFontSize(as, topicText.getFont().getSize());
  282. StyleConstants.setBackground(as, backgroundColour);
  283. StyleConstants.setForeground(as, foregroundColour);
  284. StyleConstants.setUnderline(as, false);
  285. StyleConstants.setBold(as, false);
  286. StyleConstants.setItalic(as, false);
  287. }
  288. /**
  289. * Sets the caret position in this topic bar.
  290. *
  291. * @param position New position
  292. */
  293. public void setCaretPosition(final int position) {
  294. UIUtilities.invokeLater(new Runnable() {
  295. /** {@inheritDoc} */
  296. @Override
  297. public void run() {
  298. topicText.setCaretPosition(position);
  299. }
  300. });
  301. }
  302. /**
  303. * Sets the caret colour to the specified coloour.
  304. *
  305. * @param optionColour Colour for the caret
  306. */
  307. public void setCaretColor(final Color optionColour) {
  308. UIUtilities.invokeLater(new Runnable() {
  309. /** {@inheritDoc} */
  310. @Override
  311. public void run() {
  312. topicText.setCaretColor(optionColour);
  313. }
  314. });
  315. }
  316. /**
  317. * Sets the foreground colour to the specified coloour.
  318. *
  319. * @param optionColour Colour for the foreground
  320. */
  321. @Override
  322. public void setForeground(final Color optionColour) {
  323. UIUtilities.invokeLater(new Runnable() {
  324. /** {@inheritDoc} */
  325. @Override
  326. public void run() {
  327. topicText.setForeground(optionColour);
  328. }
  329. });
  330. }
  331. /**
  332. * Sets the disabled text colour to the specified coloour.
  333. *
  334. * @param optionColour Colour for the disabled text
  335. */
  336. public void setDisabledTextColour(final Color optionColour) {
  337. UIUtilities.invokeLater(new Runnable() {
  338. /** {@inheritDoc} */
  339. @Override
  340. public void run() {
  341. topicText.setDisabledTextColor(optionColour);
  342. }
  343. });
  344. }
  345. /**
  346. * Sets the background colour to the specified coloour.
  347. *
  348. * @param optionColour Colour for the caret
  349. */
  350. @Override
  351. public void setBackground(final Color optionColour) {
  352. UIUtilities.invokeLater(new Runnable() {
  353. /** {@inheritDoc} */
  354. @Override
  355. public void run() {
  356. topicText.setBackground(optionColour);
  357. }
  358. });
  359. }
  360. /** {@inheritDoc} */
  361. @Override
  362. public void configChanged(String domain, String key) {
  363. //TODO issue 3251
  364. //if ("showfulltopic".equals(key)) {
  365. // if (channel.getConfigManager().getOptionBool(controller.getDomain(),
  366. // "showfulltopic")) {
  367. // topicText.setEditorKit(new StyledEditorKit());
  368. // } else {
  369. // topicText.setEditorKit(new WrapEditorKit());
  370. // }
  371. // ((DefaultStyledDocument) topicText.getDocument()).setDocumentFilter(
  372. // new NewlinesDocumentFilter());
  373. //}
  374. setColours();
  375. if ("hideEmptyTopicBar".equals(key)) {
  376. setVisible(true);
  377. if (channel.getConfigManager().getOptionBool(controller.getDomain(),
  378. "hideEmptyTopicBar")) {
  379. setVisible(topicText.getDocument().getLength() != 0);
  380. }
  381. }
  382. }
  383. /**
  384. * Closes this topic bar.
  385. */
  386. public void close() {
  387. channel.getChannelInfo().getParser().getCallbackManager().delCallback(
  388. ChannelTopicListener.class, this);
  389. }
  390. /**
  391. * Validates the topic text and shows errors as appropriate.
  392. */
  393. public void validateTopic() {
  394. UIUtilities.invokeLater(new Runnable() {
  395. /** {@inheritDoc} */
  396. @Override
  397. public void run() {
  398. if (topicText.isEditable()) {
  399. final int charsLeft = topicLengthMax - topicText.getText().
  400. length();
  401. if (charsLeft < 0) {
  402. errorIcon.setVisible(true);
  403. errorIcon.setToolTipText("Topic too long: " + topicText.
  404. getText().length() + " of " + topicLengthMax);
  405. } else {
  406. errorIcon.setVisible(false);
  407. errorIcon.setToolTipText(null);
  408. }
  409. } else {
  410. errorIcon.setVisible(false);
  411. }
  412. }
  413. });
  414. }
  415. /**
  416. * {@inheritDoc}
  417. *
  418. * @param e Mouse event
  419. */
  420. @Override
  421. public void mouseClicked(MouseEvent e) {
  422. if (e.getClickCount() == 2) {
  423. topicEdit.doClick();
  424. }
  425. }
  426. /**
  427. * {@inheritDoc}
  428. *
  429. * @param e Mouse event
  430. */
  431. @Override
  432. public void mousePressed(final MouseEvent e) {
  433. //Ignore
  434. }
  435. /**
  436. * {@inheritDoc}
  437. *
  438. * @param e Mouse event
  439. */
  440. @Override
  441. public void mouseReleased(final MouseEvent e) {
  442. //Ignore
  443. }
  444. /**
  445. * {@inheritDoc}
  446. *
  447. * @param e Mouse event
  448. */
  449. @Override
  450. public void mouseEntered(final MouseEvent e) {
  451. //Ignore
  452. }
  453. /**
  454. * {@inheritDoc}
  455. *
  456. * @param e Mouse event
  457. */
  458. @Override
  459. public void mouseExited(final MouseEvent e) {
  460. //Ignore
  461. }
  462. /** {@inheritDoc} */
  463. @Override
  464. public void insertUpdate(final DocumentEvent e) {
  465. validateTopic();
  466. }
  467. /** {@inheritDoc} */
  468. @Override
  469. public void removeUpdate(final DocumentEvent e) {
  470. validateTopic();
  471. }
  472. /** {@inheritDoc} */
  473. @Override
  474. public void changedUpdate(final DocumentEvent e) {
  475. validateTopic();
  476. }
  477. }
  478. /**
  479. * @author Stanislav Lapitsky
  480. * @version 1.0
  481. */
  482. class WrapEditorKit extends StyledEditorKit {
  483. private static final long serialVersionUID = 1;
  484. private ViewFactory defaultFactory = new WrapColumnFactory();
  485. /** {@inheritDoc} */
  486. @Override
  487. public ViewFactory getViewFactory() {
  488. return defaultFactory;
  489. }
  490. }
  491. /**
  492. * @author Stanislav Lapitsky
  493. * @version 1.0
  494. */
  495. class WrapColumnFactory implements ViewFactory {
  496. /** {@inheritDoc} */
  497. @Override
  498. public View create(final Element elem) {
  499. String kind = elem.getName();
  500. if (kind != null) {
  501. if (kind.equals(AbstractDocument.ContentElementName)) {
  502. return new WrapLabelView(elem);
  503. } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
  504. return new NoWrapParagraphView(elem);
  505. } else if (kind.equals(AbstractDocument.SectionElementName)) {
  506. return new BoxView(elem, View.Y_AXIS);
  507. } else if (kind.equals(StyleConstants.ComponentElementName)) {
  508. return new ComponentView(elem);
  509. } else if (kind.equals(StyleConstants.IconElementName)) {
  510. return new IconView(elem);
  511. }
  512. }
  513. // default to text display
  514. return new LabelView(elem);
  515. }
  516. }
  517. /**
  518. * @author Stanislav Lapitsky
  519. * @version 1.0
  520. */
  521. class NoWrapParagraphView extends ParagraphView {
  522. /**
  523. * Creates a new no wrap paragraph view.
  524. *
  525. * @param elem Element to view
  526. */
  527. public NoWrapParagraphView(final Element elem) {
  528. super(elem);
  529. }
  530. /** {@inheritDoc} */
  531. @Override
  532. public void layout(final int width, final int height) {
  533. super.layout(Short.MAX_VALUE, height);
  534. }
  535. /** {@inheritDoc} */
  536. @Override
  537. public float getMinimumSpan(final int axis) {
  538. return super.getPreferredSpan(axis);
  539. }
  540. }
  541. /**
  542. * @author Stanislav Lapitsky
  543. * @version 1.0
  544. */
  545. class WrapLabelView extends LabelView {
  546. /**
  547. * Creates a new wrap label view.
  548. *
  549. * @param elem Element to view
  550. */
  551. public WrapLabelView(final Element elem) {
  552. super(elem);
  553. }
  554. /** {@inheritDoc} */
  555. @Override
  556. public int getBreakWeight(final int axis, final float pos, final float len) {
  557. if (axis == View.X_AXIS) {
  558. checkPainter();
  559. int p0 = getStartOffset();
  560. int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
  561. if (p1 == p0) {
  562. // can't even fit a single character
  563. return View.BadBreakWeight;
  564. }
  565. try {
  566. //if the view contains line break char return forced break
  567. if (getDocument().getText(p0, p1 - p0).indexOf("\r") >= 0) {
  568. return View.ForcedBreakWeight;
  569. }
  570. } catch (BadLocationException ex) {
  571. //should never happen
  572. }
  573. }
  574. return super.getBreakWeight(axis, pos, len);
  575. }
  576. /** {@inheritDoc} */
  577. @Override
  578. public View breakView(final int axis, final int p0, final float pos,
  579. final float len) {
  580. if (axis == View.X_AXIS) {
  581. checkPainter();
  582. int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
  583. try {
  584. //if the view contains line break char break the view
  585. int index = getDocument().getText(p0, p1 - p0).indexOf("\r");
  586. if (index >= 0) {
  587. GlyphView v = (GlyphView) createFragment(p0, p0 + index + 1);
  588. return v;
  589. }
  590. } catch (BadLocationException ex) {
  591. //should never happen
  592. }
  593. }
  594. return super.breakView(axis, p0, pos, len);
  595. }
  596. }