You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  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.components;
  23. import com.dmdirc.BrowserLauncher;
  24. import com.dmdirc.Config;
  25. import com.dmdirc.FrameContainer;
  26. import com.dmdirc.Server;
  27. import com.dmdirc.identities.ConfigManager;
  28. import com.dmdirc.logger.ErrorLevel;
  29. import com.dmdirc.logger.Logger;
  30. import com.dmdirc.ui.InputWindow;
  31. import com.dmdirc.ui.MainFrame;
  32. import com.dmdirc.ui.dialogs.PasteDialog;
  33. import com.dmdirc.ui.input.InputHandler;
  34. import com.dmdirc.ui.input.TabCompleter;
  35. import com.dmdirc.ui.messages.Formatter;
  36. import com.dmdirc.ui.messages.Styliser;
  37. import com.dmdirc.ui.textpane.TextPane;
  38. import com.dmdirc.ui.textpane.TextPaneListener;
  39. import static com.dmdirc.ui.UIUtilities.SMALL_BORDER;
  40. import java.awt.AWTException;
  41. import java.awt.BorderLayout;
  42. import java.awt.Color;
  43. import java.awt.Dimension;
  44. import java.awt.HeadlessException;
  45. import java.awt.Point;
  46. import java.awt.Robot;
  47. import java.awt.Toolkit;
  48. import java.awt.datatransfer.DataFlavor;
  49. import java.awt.datatransfer.UnsupportedFlavorException;
  50. import java.awt.event.ActionEvent;
  51. import java.awt.event.ActionListener;
  52. import java.awt.event.KeyEvent;
  53. import java.awt.event.KeyListener;
  54. import java.awt.event.MouseEvent;
  55. import java.awt.event.MouseListener;
  56. import java.beans.PropertyChangeEvent;
  57. import java.beans.PropertyChangeListener;
  58. import java.beans.PropertyVetoException;
  59. import java.io.IOException;
  60. import java.lang.reflect.Constructor;
  61. import java.lang.reflect.InvocationTargetException;
  62. import java.util.Date;
  63. import javax.swing.AbstractAction;
  64. import javax.swing.BorderFactory;
  65. import javax.swing.JInternalFrame;
  66. import javax.swing.JLabel;
  67. import javax.swing.JMenuItem;
  68. import javax.swing.JOptionPane;
  69. import javax.swing.JPanel;
  70. import javax.swing.JPopupMenu;
  71. import javax.swing.JTextField;
  72. import javax.swing.KeyStroke;
  73. import javax.swing.UIManager;
  74. import javax.swing.event.InternalFrameEvent;
  75. import javax.swing.event.InternalFrameListener;
  76. import javax.swing.event.UndoableEditEvent;
  77. import javax.swing.event.UndoableEditListener;
  78. import javax.swing.plaf.basic.BasicInternalFrameUI;
  79. import javax.swing.plaf.synth.SynthLookAndFeel;
  80. import javax.swing.text.Document;
  81. import javax.swing.undo.CannotRedoException;
  82. import javax.swing.undo.CannotUndoException;
  83. import javax.swing.undo.UndoManager;
  84. /**
  85. * Implements a generic (internal) frame.
  86. */
  87. public abstract class Frame extends JInternalFrame implements InputWindow,
  88. PropertyChangeListener, InternalFrameListener,
  89. MouseListener, ActionListener, KeyListener, TextPaneListener {
  90. /**
  91. * A version number for this class. It should be changed whenever the class
  92. * structure is changed (or anything else that would prevent serialized
  93. * objects being unserialized with the new class).
  94. */
  95. private static final long serialVersionUID = 4;
  96. /** Input field panel. */
  97. private JPanel inputPanel;
  98. /** Frame input field. */
  99. private JTextField inputField;
  100. /** Frame output pane. */
  101. private TextPane textPane;
  102. /** The InputHandler for our input field. */
  103. private InputHandler inputHandler;
  104. /** The channel object that owns this frame. */
  105. private final FrameContainer parent;
  106. /** Popupmenu for this frame. */
  107. private JPopupMenu popup;
  108. /** Popupmenu for this frame. */
  109. private JPopupMenu inputFieldPopup;
  110. /** popup menu item. */
  111. private JMenuItem copyMI;
  112. /** popup menu item. */
  113. private JMenuItem inputPasteMI;
  114. /** popup menu item. */
  115. private JMenuItem inputCopyMI;
  116. /** popup menu item. */
  117. private JMenuItem inputCutMI;
  118. /** search bar. */
  119. private SearchBar searchBar;
  120. /** Robot for the frame. */
  121. private Robot robot;
  122. /** Away label. */
  123. private JLabel awayLabel;
  124. /**
  125. * Creates a new instance of Frame.
  126. *
  127. * @param owner FrameContainer owning this frame.
  128. */
  129. public Frame(final FrameContainer owner) {
  130. super();
  131. parent = owner;
  132. try {
  133. robot = new Robot();
  134. } catch (AWTException ex) {
  135. Logger.error(ErrorLevel.TRIVIAL, "Error creating robot", ex);
  136. }
  137. setFrameIcon(MainFrame.getMainFrame().getIcon());
  138. initComponents();
  139. setMaximizable(true);
  140. setClosable(true);
  141. setResizable(true);
  142. setIconifiable(true);
  143. setPreferredSize(new Dimension(MainFrame.getMainFrame().getWidth() / 2,
  144. MainFrame.getMainFrame().getHeight() / 3));
  145. addPropertyChangeListener("maximum", this);
  146. addInternalFrameListener(this);
  147. final ConfigManager config = owner.getConfigManager();
  148. getTextPane().setBackground(config.getOptionColour("ui", "backgroundcolour", Color.WHITE));
  149. getTextPane().setForeground(config.getOptionColour("ui", "foregroundcolour", Color.BLACK));
  150. getInputField().setBackground(config.getOptionColour("ui", "inputbackgroundcolour",
  151. config.getOptionColour("ui", "backgroundcolour", Color.WHITE)));
  152. getInputField().setForeground(config.getOptionColour("ui", "inputforegroundcolour",
  153. config.getOptionColour("ui", "foregroundcolour", Color.BLACK)));
  154. getInputField().setCaretColor(config.getOptionColour("ui", "inputforegroundcolour",
  155. config.getOptionColour("ui", "foregroundcolour", Color.BLACK)));
  156. final Boolean pref = Config.getOptionBool("ui", "maximisewindows");
  157. if (pref || MainFrame.getMainFrame().getMaximised()) {
  158. hideTitlebar();
  159. }
  160. }
  161. /**
  162. * Makes this frame visible. We don't call this from the constructor
  163. * so that we can register an actionlistener for the open event before
  164. * the frame is opened.
  165. */
  166. public final void open() {
  167. setVisible(true);
  168. if (Config.getOptionBool("ui", "awayindicator")) {
  169. awayLabel.setVisible(getServer().isAway());
  170. }
  171. }
  172. /**
  173. * Adds a line of text to the main text area.
  174. *
  175. * @param line text to add
  176. * @param timestamp Whether to timestamp the line or not
  177. */
  178. public final void addLine(final String line, final boolean timestamp) {
  179. //SwingUtilities.invokeLater(new Runnable() {
  180. // public void run() {
  181. for (String myLine : line.split("\n")) {
  182. if (timestamp) {
  183. Styliser.addStyledString(getTextPane(), new String[]{
  184. Formatter.formatMessage("timestamp", new Date()),
  185. myLine, });
  186. } else {
  187. Styliser.addStyledString(getTextPane(), myLine);
  188. }
  189. textPane.trim(Config.getOptionInt("ui", "frameBufferSize", Integer.MAX_VALUE));
  190. }
  191. // }
  192. // });
  193. }
  194. /** {@inheritDoc} */
  195. public final void addLine(final String messageType, final Object... args) {
  196. if (messageType.length() > 0) {
  197. addLine(Formatter.formatMessage(messageType, args), true);
  198. }
  199. }
  200. /** {@inheritDoc} */
  201. public final void addLine(final StringBuffer messageType, final Object... args) {
  202. if (messageType != null) {
  203. addLine(messageType.toString(), args);
  204. }
  205. }
  206. /** {@inheritDoc} */
  207. public final void clear() {
  208. getTextPane().clear();
  209. }
  210. /**
  211. * Sets the tab completer for this frame's input handler.
  212. *
  213. * @param tabCompleter The tab completer to use
  214. * @deprecated Seems rather pointless to proxy this
  215. */
  216. @Deprecated
  217. public final void setTabCompleter(final TabCompleter tabCompleter) {
  218. getInputHandler().setTabCompleter(tabCompleter);
  219. }
  220. /**
  221. * Initialises the components for this frame.
  222. */
  223. private void initComponents() {
  224. setInputField(new JTextField());
  225. setTextPane(new TextPane(this));
  226. getInputField().setBorder(
  227. BorderFactory.createCompoundBorder(
  228. getInputField().getBorder(),
  229. BorderFactory.createEmptyBorder(2, 2, 2, 2)));
  230. getTextPane().addMouseListener(this);
  231. getTextPane().addKeyListener(this);
  232. getTextPane().addTextPaneListener(this);
  233. getInputField().addKeyListener(this);
  234. getInputField().addMouseListener(this);
  235. popup = new JPopupMenu();
  236. inputFieldPopup = new JPopupMenu();
  237. copyMI = new JMenuItem("Copy");
  238. copyMI.addActionListener(this);
  239. inputPasteMI = new JMenuItem("Paste");
  240. inputPasteMI.addActionListener(this);
  241. inputCopyMI = new JMenuItem("Copy");
  242. inputCopyMI.addActionListener(this);
  243. inputCutMI = new JMenuItem("Cut");
  244. inputCutMI.addActionListener(this);
  245. popup.add(copyMI);
  246. popup.setOpaque(true);
  247. popup.setLightWeightPopupEnabled(true);
  248. inputFieldPopup.add(inputCutMI);
  249. inputFieldPopup.add(inputCopyMI);
  250. inputFieldPopup.add(inputPasteMI);
  251. inputFieldPopup.setOpaque(true);
  252. inputFieldPopup.setLightWeightPopupEnabled(true);
  253. searchBar = new SearchBar(this);
  254. searchBar.setVisible(false);
  255. awayLabel = new JLabel();
  256. awayLabel.setText("(away)");
  257. awayLabel.setVisible(false);
  258. awayLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0,
  259. SMALL_BORDER));
  260. inputPanel = new JPanel(new BorderLayout());
  261. inputPanel.add(awayLabel, BorderLayout.LINE_START);
  262. inputPanel.add(inputField, BorderLayout.CENTER);
  263. initInputField();
  264. }
  265. private void initInputField() {
  266. final UndoManager undo = new UndoManager();
  267. final Document doc = getInputField().getDocument();
  268. // Listen for undo and redo events
  269. doc.addUndoableEditListener(new UndoableEditListener() {
  270. public void undoableEditHappened(UndoableEditEvent evt) {
  271. undo.addEdit(evt.getEdit());
  272. }
  273. });
  274. // Create an undo action and add it to the text component
  275. getInputField().getActionMap().put("Undo",
  276. new AbstractAction("Undo") {
  277. private static final long serialVersionUID = 1;
  278. public void actionPerformed(ActionEvent evt) {
  279. try {
  280. if (undo.canUndo()) {
  281. undo.undo();
  282. }
  283. } catch (CannotUndoException ex) {
  284. Logger.error(ErrorLevel.TRIVIAL, "Unable to undo", ex);
  285. }
  286. }
  287. });
  288. // Bind the undo action to ctl-Z
  289. getInputField().getInputMap().put(KeyStroke.getKeyStroke("control Z"), "Undo");
  290. // Create a redo action and add it to the text component
  291. getInputField().getActionMap().put("Redo",
  292. new AbstractAction("Redo") {
  293. private static final long serialVersionUID = 1;
  294. public void actionPerformed(ActionEvent evt) {
  295. try {
  296. if (undo.canRedo()) {
  297. undo.redo();
  298. }
  299. } catch (CannotRedoException ex) {
  300. Logger.error(ErrorLevel.TRIVIAL, "Unable to redo", ex);
  301. }
  302. }
  303. });
  304. // Bind the redo action to ctl-Y
  305. getInputField().getInputMap().put(KeyStroke.getKeyStroke("control Y"), "Redo");
  306. }
  307. /**
  308. * Removes and reinserts the border of an internal frame on maximising.
  309. * {@inheritDoc}
  310. */
  311. public final void propertyChange(final PropertyChangeEvent event) {
  312. if (event.getNewValue().equals(Boolean.TRUE)) {
  313. hideTitlebar();
  314. MainFrame.getMainFrame().setMaximised(true);
  315. } else {
  316. showTitlebar();
  317. MainFrame.getMainFrame().setMaximised(false);
  318. MainFrame.getMainFrame().setActiveFrame(this);
  319. }
  320. }
  321. /** Hides the titlebar for this frame. */
  322. private void hideTitlebar() {
  323. setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
  324. ((BasicInternalFrameUI) getUI()).setNorthPane(null);
  325. }
  326. /** Shows the titlebar for this frame. */
  327. private void showTitlebar() {
  328. final Class< ? > c;
  329. Object temp = null;
  330. Constructor< ? > constructor;
  331. final String componentUI = (String) UIManager.get("InternalFrameUI");
  332. if ("javax.swing.plaf.synth.SynthLookAndFeel".equals(componentUI)) {
  333. temp = SynthLookAndFeel.createUI(this);
  334. } else {
  335. try {
  336. c = getClass().getClassLoader().loadClass(componentUI);
  337. constructor = c.getConstructor(new Class[] {javax.swing.JInternalFrame.class});
  338. temp = constructor.newInstance(new Object[] {this});
  339. } catch (ClassNotFoundException ex) {
  340. Logger.error(ErrorLevel.WARNING, "Unable to readd titlebar", ex);
  341. } catch (NoSuchMethodException ex) {
  342. Logger.error(ErrorLevel.WARNING, "Unable to readd titlebar", ex);
  343. } catch (InstantiationException ex) {
  344. Logger.error(ErrorLevel.WARNING, "Unable to readd titlebar", ex);
  345. } catch (IllegalAccessException ex) {
  346. Logger.error(ErrorLevel.WARNING, "Unable to readd titlebar", ex);
  347. } catch (InvocationTargetException ex) {
  348. Logger.error(ErrorLevel.WARNING, "Unable to readd titlebar", ex);
  349. }
  350. }
  351. setBorder(UIManager.getBorder("InternalFrame.border"));
  352. if (temp == null) {
  353. temp = new BasicInternalFrameUI(this);
  354. }
  355. this.setUI((BasicInternalFrameUI) temp);
  356. }
  357. /**
  358. * Not needed for this class. {@inheritDoc}
  359. */
  360. public void internalFrameOpened(final InternalFrameEvent event) {
  361. //Ignore.
  362. }
  363. /**
  364. * Not needed for this class. {@inheritDoc}
  365. */
  366. public void internalFrameClosing(final InternalFrameEvent event) {
  367. //Ignore.
  368. }
  369. /**
  370. * Not needed for this class. {@inheritDoc}
  371. */
  372. public void internalFrameClosed(final InternalFrameEvent event) {
  373. //Ignore.
  374. }
  375. /**
  376. * Makes the internal frame invisible. {@inheritDoc}
  377. */
  378. public void internalFrameIconified(final InternalFrameEvent event) {
  379. event.getInternalFrame().setVisible(false);
  380. }
  381. /**
  382. * Not needed for this class. {@inheritDoc}
  383. */
  384. public void internalFrameDeiconified(final InternalFrameEvent event) {
  385. //Ignore.
  386. }
  387. /**
  388. * Activates the input field on frame focus. {@inheritDoc}
  389. */
  390. public void internalFrameActivated(final InternalFrameEvent event) {
  391. getInputField().requestFocus();
  392. }
  393. /**
  394. * Not needed for this class. {@inheritDoc}
  395. */
  396. public void internalFrameDeactivated(final InternalFrameEvent event) {
  397. //Ignore.
  398. }
  399. /**
  400. * Returns the parent Frame container for this frame.
  401. *
  402. * @return FrameContainer parent
  403. * @deprecated Use getContainer() instead
  404. */
  405. @Deprecated
  406. public final FrameContainer getFrameParent() {
  407. return parent;
  408. }
  409. /** {@inheritDoc} */
  410. public FrameContainer getContainer() {
  411. return parent;
  412. }
  413. /** {@inheritDoc} */
  414. public Server getServer() {
  415. return getContainer().getServer();
  416. }
  417. /** {@inheritDoc} */
  418. public ConfigManager getConfigManager() {
  419. return getContainer().getConfigManager();
  420. }
  421. /**
  422. * Returns the input handler associated with this frame.
  423. *
  424. * @return Input handlers for this frame
  425. */
  426. public final InputHandler getInputHandler() {
  427. return inputHandler;
  428. }
  429. /**
  430. * Sets the input handler for this frame.
  431. *
  432. * @param newInputHandler input handler to set for this frame
  433. */
  434. public final void setInputHandler(final InputHandler newInputHandler) {
  435. this.inputHandler = newInputHandler;
  436. }
  437. /**
  438. * Returns the input field for this frame.
  439. *
  440. * @return JTextField input field for the frame.
  441. */
  442. public final JTextField getInputField() {
  443. return inputField;
  444. }
  445. /**
  446. * Returns the input panel for this frame.
  447. *
  448. * @return JPanel input panel
  449. * @deprecated Used? If it's only used by descendents can't we make the
  450. * field protected instead?
  451. */
  452. @Deprecated
  453. public final JPanel getInputPanel() {
  454. return inputPanel;
  455. }
  456. /**
  457. * Returns the text pane for this frame.
  458. *
  459. * @return Text pane for this frame
  460. */
  461. public final TextPane getTextPane() {
  462. return textPane;
  463. }
  464. /** {@inheritDoc} */
  465. @Override
  466. public final String getName() {
  467. return parent.toString();
  468. }
  469. /**
  470. * Sets the frames input field.
  471. *
  472. * @param newInputField new input field to use
  473. */
  474. protected final void setInputField(final JTextField newInputField) {
  475. this.inputField = newInputField;
  476. }
  477. /**
  478. * Sets the frames text pane.
  479. *
  480. * @param newTextPane new text pane to use
  481. */
  482. protected final void setTextPane(final TextPane newTextPane) {
  483. this.textPane = newTextPane;
  484. }
  485. /**
  486. * Returns the away label for this server connection.
  487. *
  488. * @return JLabel away label
  489. */
  490. public JLabel getAwayLabel() {
  491. return awayLabel;
  492. }
  493. /**
  494. * Sets the away indicator on or off.
  495. *
  496. * @param awayState away state
  497. */
  498. public void setAwayIndicator(final boolean awayState) {
  499. if (awayState) {
  500. getInputPanel().add(awayLabel, BorderLayout.LINE_START);
  501. awayLabel.setVisible(true);
  502. } else {
  503. awayLabel.setVisible(false);
  504. }
  505. }
  506. /**
  507. * Checks for url's, channels and nicknames. {@inheritDoc}
  508. */
  509. public void mouseClicked(final MouseEvent mouseEvent) {
  510. if (mouseEvent.getSource() == getTextPane()) {
  511. processMouseEvent(mouseEvent);
  512. }
  513. }
  514. /**
  515. * Not needed for this class. {@inheritDoc}
  516. */
  517. public void mousePressed(final MouseEvent mouseEvent) {
  518. processMouseEvent(mouseEvent);
  519. }
  520. /**
  521. * Not needed for this class. {@inheritDoc}
  522. */
  523. public void mouseReleased(final MouseEvent mouseEvent) {
  524. if (Config.getOptionBool("ui", "quickCopy") && mouseEvent.getSource() == getTextPane()) {
  525. getTextPane().copy();
  526. getTextPane().clearSelection();
  527. }
  528. processMouseEvent(mouseEvent);
  529. }
  530. /**
  531. * Not needed for this class. {@inheritDoc}
  532. */
  533. public void mouseEntered(final MouseEvent mouseEvent) {
  534. //Ignore.
  535. }
  536. /**
  537. * Not needed for this class. {@inheritDoc}
  538. */
  539. public void mouseExited(final MouseEvent mouseEvent) {
  540. //Ignore.
  541. }
  542. /**
  543. * Processes every mouse button event to check for a popup trigger.
  544. *
  545. * @param e mouse event
  546. */
  547. @Override
  548. public void processMouseEvent(final MouseEvent e) {
  549. if (e.isPopupTrigger() && e.getSource() == getTextPane()) {
  550. final Point point = getTextPane().getMousePosition();
  551. if (point != null) {
  552. final int[] selection = textPane.getSelectedRange();
  553. if (selection[0] == selection[2] && selection[1] == selection[3]) {
  554. copyMI.setEnabled(false);
  555. } else {
  556. copyMI.setEnabled(true);
  557. }
  558. getPopup().show(this, (int) point.getX(), (int) point.getY());
  559. }
  560. } else if (e.isPopupTrigger() && e.getSource() == getInputField()) {
  561. final Point point = getInputField().getMousePosition();
  562. if (point != null) {
  563. inputFieldPopup.show(this, (int) point.getX(),
  564. (int) point.getY() + getTextPane().getHeight()
  565. + SMALL_BORDER);
  566. }
  567. } else {
  568. super.processMouseEvent(e);
  569. }
  570. }
  571. /** {@inheritDoc} */
  572. public void actionPerformed(final ActionEvent actionEvent) {
  573. if (actionEvent.getSource() == copyMI) {
  574. getTextPane().copy();
  575. } else if (actionEvent.getSource() == inputCopyMI) {
  576. getInputField().copy();
  577. } else if (actionEvent.getSource() == inputPasteMI) {
  578. getInputField().paste();
  579. } else if (actionEvent.getSource() == inputCutMI) {
  580. getInputField().cut();
  581. }
  582. }
  583. /**
  584. * returns the popup menu for this frame.
  585. *
  586. * @return JPopupMenu for this frame
  587. */
  588. public final JPopupMenu getPopup() {
  589. return popup;
  590. }
  591. /** {@inheritDoc} */
  592. public void keyTyped(final KeyEvent event) {
  593. //Ignore.
  594. }
  595. /** {@inheritDoc} */
  596. public void keyPressed(final KeyEvent event) {
  597. if ((event.getModifiers() & KeyEvent.CTRL_MASK) == 0) {
  598. if (event.getKeyCode() == KeyEvent.VK_PAGE_UP) {
  599. getTextPane().pageUp();
  600. } else if (event.getKeyCode() == KeyEvent.VK_PAGE_DOWN) {
  601. getTextPane().pageDown();
  602. }
  603. } else {
  604. if (event.getKeyCode() == KeyEvent.VK_HOME) {
  605. getTextPane().setScrollBarPosition(0);
  606. } else if (event.getKeyCode() == KeyEvent.VK_END) {
  607. getTextPane().setScrollBarPosition(textPane.getNumLines());
  608. }
  609. }
  610. if (event.getKeyCode() == KeyEvent.VK_F3) {
  611. if (!getSearchBar().isVisible()) {
  612. getSearchBar().open();
  613. }
  614. getSearchBar().search();
  615. }
  616. if (event.getKeyCode() == KeyEvent.VK_F
  617. && (event.getModifiers() & KeyEvent.CTRL_MASK) != 0
  618. && (event.getModifiers() & KeyEvent.SHIFT_MASK) == 0) {
  619. doSearchBar();
  620. }
  621. if (event.getSource() == getTextPane()) {
  622. if ((Config.getOptionBool("ui", "quickCopy")
  623. || (event.getModifiers() & KeyEvent.CTRL_MASK) == 0)) {
  624. event.setSource(getInputField());
  625. getInputField().requestFocus();
  626. if (robot != null && event.getKeyCode() != KeyEvent.VK_UNDEFINED) {
  627. robot.keyPress(event.getKeyCode());
  628. if (event.getKeyCode() == KeyEvent.VK_SHIFT) {
  629. robot.keyRelease(event.getKeyCode());
  630. }
  631. }
  632. } else if (event.getKeyCode() == KeyEvent.VK_C) {
  633. getTextPane().copy();
  634. }
  635. } else if ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0
  636. && event.getKeyCode() == KeyEvent.VK_V) {
  637. doPaste(event);
  638. }
  639. }
  640. /**
  641. * Checks and pastes text.
  642. *
  643. * @param event the event that triggered the paste
  644. */
  645. private void doPaste(final KeyEvent event) {
  646. String clipboard = null;
  647. String[] clipboardLines = new String[]{"", };
  648. try {
  649. clipboard = getInputField().getText()
  650. + (String) Toolkit.getDefaultToolkit().getSystemClipboard()
  651. .getData(DataFlavor.stringFlavor);
  652. clipboardLines = clipboard.split(System.getProperty("line.separator"));
  653. } catch (HeadlessException ex) {
  654. Logger.error(ErrorLevel.WARNING, "Unable to get clipboard contents", ex);
  655. } catch (IOException ex) {
  656. Logger.error(ErrorLevel.WARNING, "Unable to get clipboard contents", ex);
  657. } catch (UnsupportedFlavorException ex) {
  658. Logger.error(ErrorLevel.WARNING, "Unable to get clipboard contents", ex);
  659. }
  660. if (clipboard != null && clipboard.indexOf('\n') >= 0) {
  661. event.consume();
  662. final int pasteTrigger = Config.getOptionInt("ui", "pasteProtectionLimit", 1);
  663. if (getNumLines(clipboard) > pasteTrigger) {
  664. showPasteDialog(clipboard, clipboardLines);
  665. } else {
  666. for (String clipboardLine : clipboardLines) {
  667. this.sendLine(clipboardLine);
  668. }
  669. }
  670. }
  671. }
  672. /**
  673. * Shows the paste dialog.
  674. *
  675. * @param clipboard contents of the clipboard
  676. * @param clipboardLines clipboard contents split per line
  677. */
  678. private void showPasteDialog(final String clipboard,
  679. final String[] clipboardLines) {
  680. final String[] options = {"Send", "Edit", "Cancel", };
  681. final int n = JOptionPane.showOptionDialog(this,
  682. "<html>Paste would be sent as "
  683. + getNumLines(clipboard) + " lines.<br>"
  684. + "Do you want to continue?</html>",
  685. "Multi-line Paste",
  686. JOptionPane.YES_NO_CANCEL_OPTION,
  687. JOptionPane.QUESTION_MESSAGE,
  688. null,
  689. options,
  690. options[0]);
  691. switch (n) {
  692. case 0:
  693. for (String clipboardLine : clipboardLines) {
  694. this.sendLine(clipboardLine);
  695. }
  696. break;
  697. case 1:
  698. new PasteDialog(this, clipboard).setVisible(true);
  699. break;
  700. case 2:
  701. break;
  702. default:
  703. break;
  704. }
  705. }
  706. /** Opens, closes or focuses the search bar as appropriate. */
  707. private void doSearchBar() {
  708. if (getSearchBar().isVisible()) {
  709. getSearchBar().getFocus();
  710. } else {
  711. getSearchBar().open();
  712. }
  713. }
  714. /** {@inheritDoc} */
  715. public void keyReleased(final KeyEvent event) {
  716. //Ignore.
  717. }
  718. /** {@inheritDoc} */
  719. public void hyperlinkClicked(final String url) {
  720. MainFrame.getMainFrame().getStatusBar().setMessage("Opening: " + url);
  721. BrowserLauncher.openURL(url);
  722. }
  723. /** {@inheritDoc} */
  724. public void channelClicked(final String channel) {
  725. if (parent.getServer().getParser().getChannelInfo(channel) == null) {
  726. parent.getServer().getParser().joinChannel(channel);
  727. } else {
  728. parent.getServer().getChannel(channel).activateFrame();
  729. }
  730. }
  731. /**
  732. * Send the line to the frame container.
  733. *
  734. * @param line the line to send
  735. * @deprecated If this is actually needed by something, it'd make more
  736. * sense for it to be implemented as a method of FrameContainer
  737. */
  738. @Deprecated
  739. public abstract void sendLine(final String line);
  740. /**
  741. * Gets the search bar.
  742. *
  743. * @return the frames search bar
  744. */
  745. public final SearchBar getSearchBar() {
  746. return searchBar;
  747. }
  748. /**
  749. * Returns the number of lines the specified string would be sent as.
  750. *
  751. * @param line line to be checked
  752. * @return number of lines that would be sent
  753. * @deprecated This isn't a property of the frame, it's a property of the
  754. * object associated with the frame (the container). We also need to take
  755. * into account that some owners may not allow pasting or may not have
  756. * a maximum line length
  757. */
  758. @Deprecated
  759. public final int getNumLines(final String line) {
  760. int lines;
  761. final String[] splitLines = line.split("\n");
  762. lines = splitLines.length;
  763. for (String splitLine : splitLines) {
  764. lines += (int) Math.ceil(splitLine.length() / getMaxLineLength());
  765. }
  766. return lines;
  767. }
  768. /** Closes this frame. */
  769. public void close() {
  770. try {
  771. setClosed(true);
  772. } catch (PropertyVetoException ex) {
  773. Logger.error(ErrorLevel.WARNING, "Unable to close frame", ex);
  774. }
  775. }
  776. /** Minimises the frame. */
  777. public void minimise() {
  778. try {
  779. setIcon(true);
  780. } catch (PropertyVetoException ex) {
  781. Logger.error(ErrorLevel.WARNING, "Unable to minimise frame", ex);
  782. }
  783. }
  784. /**
  785. * Returns the maximum length a line can be in this frame.
  786. * @return max line length
  787. * @deprecated This isn't a property of the frame, it's a property of the
  788. * object associated with the frame (the container). We also need to take
  789. * into account that some owners may not allow pasting or may not have
  790. * a maximum line length
  791. */
  792. @Deprecated
  793. public abstract int getMaxLineLength();
  794. }