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.

TextFrame.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.addons.ui_swing.components.frames;
  18. import com.dmdirc.addons.ui_swing.EdtHandlerInvocation;
  19. import com.dmdirc.addons.ui_swing.SwingController;
  20. import com.dmdirc.addons.ui_swing.UIUtilities;
  21. import com.dmdirc.addons.ui_swing.actions.InputFieldCopyAction;
  22. import com.dmdirc.addons.ui_swing.actions.SearchAction;
  23. import com.dmdirc.addons.ui_swing.components.IconManager;
  24. import com.dmdirc.addons.ui_swing.components.SwingSearchBar;
  25. import com.dmdirc.addons.ui_swing.components.SwingSearchBarFactory;
  26. import com.dmdirc.addons.ui_swing.dialogs.paste.PasteDialogFactory;
  27. import com.dmdirc.addons.ui_swing.events.SwingActiveWindowChangeRequestEvent;
  28. import com.dmdirc.addons.ui_swing.events.SwingEventBus;
  29. import com.dmdirc.addons.ui_swing.interfaces.ActiveFrameManager;
  30. import com.dmdirc.addons.ui_swing.textpane.ClickTypeValue;
  31. import com.dmdirc.addons.ui_swing.textpane.MouseEventType;
  32. import com.dmdirc.addons.ui_swing.textpane.TextPane;
  33. import com.dmdirc.addons.ui_swing.textpane.TextPaneControlCodeCopyAction;
  34. import com.dmdirc.addons.ui_swing.textpane.TextPaneCopyAction;
  35. import com.dmdirc.addons.ui_swing.textpane.TextPaneEndAction;
  36. import com.dmdirc.addons.ui_swing.textpane.TextPaneFactory;
  37. import com.dmdirc.addons.ui_swing.textpane.TextPaneHomeAction;
  38. import com.dmdirc.addons.ui_swing.textpane.TextPaneListener;
  39. import com.dmdirc.addons.ui_swing.textpane.TextPanePageDownAction;
  40. import com.dmdirc.addons.ui_swing.textpane.TextPanePageUpAction;
  41. import com.dmdirc.commandparser.PopupManager;
  42. import com.dmdirc.commandparser.PopupMenu;
  43. import com.dmdirc.commandparser.PopupMenuItem;
  44. import com.dmdirc.commandparser.PopupType;
  45. import com.dmdirc.commandparser.parsers.CommandParser;
  46. import com.dmdirc.config.ConfigBinding;
  47. import com.dmdirc.config.GlobalConfig;
  48. import com.dmdirc.events.FrameClosingEvent;
  49. import com.dmdirc.events.LinkChannelClickedEvent;
  50. import com.dmdirc.events.LinkNicknameClickedEvent;
  51. import com.dmdirc.events.LinkUrlClickedEvent;
  52. import com.dmdirc.interfaces.CommandController;
  53. import com.dmdirc.events.eventbus.EventBus;
  54. import com.dmdirc.interfaces.WindowModel;
  55. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  56. import com.dmdirc.plugins.ServiceManager;
  57. import com.dmdirc.ui.input.TabCompleterUtils;
  58. import com.dmdirc.ui.messages.ColourManager;
  59. import com.dmdirc.ui.messages.ColourManagerFactory;
  60. import java.awt.Point;
  61. import java.awt.datatransfer.Clipboard;
  62. import java.awt.event.KeyEvent;
  63. import java.awt.event.MouseEvent;
  64. import java.awt.event.WindowAdapter;
  65. import java.awt.event.WindowEvent;
  66. import java.util.Optional;
  67. import javax.inject.Inject;
  68. import javax.swing.JComponent;
  69. import javax.swing.JMenu;
  70. import javax.swing.JMenuItem;
  71. import javax.swing.JPanel;
  72. import javax.swing.JPopupMenu;
  73. import javax.swing.JSeparator;
  74. import javax.swing.KeyStroke;
  75. import javax.swing.SwingUtilities;
  76. import net.engio.mbassy.listener.Handler;
  77. import net.miginfocom.swing.MigLayout;
  78. /**
  79. * Implements a generic (internal) frame.
  80. */
  81. public abstract class TextFrame extends JPanel implements TextPaneListener {
  82. /** A version number for this class. */
  83. private static final long serialVersionUID = 5;
  84. /** The channel object that owns this frame. */
  85. protected final WindowModel frameParent;
  86. /** Colour manager. */
  87. protected final ColourManager colourManager;
  88. /** Frame output pane. */
  89. private TextPane textPane;
  90. /** search bar. */
  91. private SwingSearchBar searchBar;
  92. /** Command parser for popup commands. */
  93. private final CommandParser commandParser;
  94. /** Swing event bus to post events to. */
  95. private final SwingEventBus swingEventBus;
  96. /** Manager to use for building popups. */
  97. private final PopupManager popupManager;
  98. /** Bus to despatch events on. */
  99. private final EventBus eventBus;
  100. /** Clipboard to copy and paste from. */
  101. private final Clipboard clipboard;
  102. private final IconManager iconManager;
  103. /** Boolean to determine if this frame should be popped out of main client. */
  104. private boolean popout;
  105. /** DesktopWindowFrame to use for this TextFrame if it is to be popped out of the client. */
  106. private DesktopWindowFrame popoutFrame;
  107. /** Desktop place holder object used if this frame is popped out. */
  108. private DesktopPlaceHolderFrame popoutPlaceholder;
  109. /**
  110. * Creates a new instance of Frame.
  111. *
  112. * @param owner FrameContainer owning this frame.
  113. * @param commandParser The command parser to use for this frame.
  114. * @param deps Collection of TextPane dependencies.
  115. */
  116. protected TextFrame(
  117. final WindowModel owner,
  118. final CommandParser commandParser,
  119. final TextFrameDependencies deps) {
  120. this.swingEventBus = deps.swingEventBus;
  121. this.popupManager = deps.popupManager;
  122. this.frameParent = owner;
  123. this.eventBus = deps.eventBus;
  124. this.commandParser = commandParser;
  125. this.clipboard = deps.clipboard;
  126. this.colourManager = deps.colourManagerFactory.getColourManager(owner.getConfigManager());
  127. this.iconManager = deps.iconManager;
  128. initComponents(deps.textPaneFactory, deps.searchBarFactory);
  129. setFocusable(true);
  130. setLayout(new MigLayout("fill"));
  131. }
  132. /**
  133. * Initialises the instance, adding any required listeners.
  134. */
  135. public void init() {
  136. getContainer().getConfigManager().getBinder().bind(this, TextFrame.class);
  137. getTextPane().addTextPaneListener(this);
  138. }
  139. /**
  140. * Determines if this frame should be popped out of the client or not. Once this is set to true
  141. * it will pop out of the client as a free floating Desktop window.
  142. *
  143. * If this is set to false then the desktop window for this frame is disposed of and this frame
  144. * is returned to the client.
  145. *
  146. * @param popout Should this frame pop out?
  147. */
  148. public void setPopout(final boolean popout) {
  149. this.popout = popout;
  150. if (popout) {
  151. popoutPlaceholder = new DesktopPlaceHolderFrame();
  152. popoutFrame = new DesktopWindowFrame(this, iconManager);
  153. eventBus.subscribe(popoutFrame);
  154. popoutFrame.addWindowListener(new WindowAdapter() {
  155. @Override
  156. public void windowClosing(final WindowEvent e) {
  157. setPopout(false);
  158. }
  159. });
  160. popoutFrame.display();
  161. } else if (popoutFrame != null) {
  162. popoutPlaceholder = null;
  163. popoutFrame.dispose();
  164. eventBus.unsubscribe(popoutFrame);
  165. popoutFrame = null;
  166. }
  167. // TODO: This is a horrible hack really.
  168. swingEventBus.publishAsync(new SwingActiveWindowChangeRequestEvent(Optional.of(this)));
  169. }
  170. /**
  171. * Returns the frame for the free floating desktop window associated with this TextFrame. If one
  172. * does not exist then null is returned.
  173. *
  174. * @return Desktop window frame or null if does not exist
  175. */
  176. public DesktopWindowFrame getPopoutFrame() {
  177. return popoutFrame;
  178. }
  179. /**
  180. * Checks if this frame should be popped out of the client or not. Returns our place holder
  181. * frame if it is to be used or this TextFrame if it is not to be popped out.
  182. *
  183. * @return JPanel to use by the client in the window pane
  184. */
  185. public JPanel getDisplayFrame() {
  186. if (popout) {
  187. return popoutPlaceholder;
  188. } else {
  189. return this;
  190. }
  191. }
  192. /**
  193. * Called when the frame has been selected in the UI.
  194. */
  195. public void activateFrame() {
  196. UIUtilities.invokeLater(() -> frameParent.getUnreadStatusManager().clearStatus());
  197. }
  198. /**
  199. * Initialises the components for this frame.
  200. */
  201. private void initComponents(final TextPaneFactory textPaneFactory,
  202. final SwingSearchBarFactory searchBarFactory) {
  203. setTextPane(textPaneFactory.getTextPane(this));
  204. searchBar = searchBarFactory.create(this);
  205. searchBar.setVisible(false);
  206. getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
  207. put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0),
  208. "pageUpAction");
  209. getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
  210. put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0),
  211. "pageDownAction");
  212. getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
  213. put(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0), "searchAction");
  214. getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
  215. put(KeyStroke.getKeyStroke(KeyEvent.VK_F,
  216. UIUtilities.getCtrlDownMask()), "searchAction");
  217. getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
  218. put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME,
  219. UIUtilities.getCtrlDownMask()), "homeAction");
  220. getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
  221. put(KeyStroke.getKeyStroke(KeyEvent.VK_END,
  222. UIUtilities.getCtrlDownMask()), "endAction");
  223. getSearchBar().getTextField().getInputMap().put(KeyStroke.getKeyStroke(
  224. KeyEvent.VK_C, UIUtilities.getCtrlMask()), "textpaneCopy");
  225. getSearchBar().getTextField().getInputMap().put(KeyStroke.getKeyStroke(
  226. KeyEvent.VK_C, UIUtilities.getCtrlMask()
  227. & KeyEvent.SHIFT_DOWN_MASK), "textpaneCopy");
  228. getSearchBar().getTextField().getActionMap().put("textpaneCopy",
  229. new InputFieldCopyAction(getTextPane(),
  230. getSearchBar().getTextField()));
  231. getActionMap().put("pageUpAction",
  232. new TextPanePageUpAction(getTextPane()));
  233. getActionMap().put("pageDownAction",
  234. new TextPanePageDownAction(getTextPane()));
  235. getActionMap().put("searchAction", new SearchAction(searchBar));
  236. getActionMap().put("homeAction", new TextPaneHomeAction(getTextPane()));
  237. getActionMap().put("endAction", new TextPaneEndAction(getTextPane()));
  238. }
  239. /**
  240. * Gets the core model for this window.
  241. *
  242. * @return This window's model
  243. */
  244. public WindowModel getContainer() {
  245. return frameParent;
  246. }
  247. /**
  248. * Returns the text pane for this frame.
  249. *
  250. * @return Text pane for this frame
  251. */
  252. public final TextPane getTextPane() {
  253. return textPane;
  254. }
  255. /**
  256. * Sets the frames text pane.
  257. *
  258. * @param newTextPane new text pane to use
  259. */
  260. protected final void setTextPane(final TextPane newTextPane) {
  261. textPane = newTextPane;
  262. }
  263. @Override
  264. public void mouseClicked(final ClickTypeValue clicktype,
  265. final MouseEventType eventType, final MouseEvent event) {
  266. if (event.isPopupTrigger()) {
  267. showPopupMenuInternal(clicktype, event.getPoint());
  268. }
  269. if (eventType == MouseEventType.CLICK && event.getButton() == MouseEvent.BUTTON1) {
  270. switch (clicktype.getType()) {
  271. case CHANNEL:
  272. eventBus.publishAsync(new LinkChannelClickedEvent(getContainer(), clicktype.getValue()));
  273. break;
  274. case NICKNAME:
  275. eventBus.publishAsync(new LinkNicknameClickedEvent(getContainer(), clicktype.getValue()));
  276. break;
  277. case HYPERLINK:
  278. eventBus.publishAsync(new LinkUrlClickedEvent(getContainer(), clicktype.getValue()));
  279. break;
  280. case NORMAL:
  281. //Ignore normal clicks
  282. break;
  283. }
  284. }
  285. }
  286. /**
  287. * What popup type should be used for popup menus for nicknames.
  288. *
  289. * @return Appropriate popuptype for this frame
  290. */
  291. public abstract PopupType getNicknamePopupType();
  292. /**
  293. * What popup type should be used for popup menus for channels.
  294. *
  295. * @return Appropriate popuptype for this frame
  296. */
  297. public abstract PopupType getChannelPopupType();
  298. /**
  299. * What popup type should be used for popup menus for hyperlinks.
  300. *
  301. * @return Appropriate popuptype for this frame
  302. */
  303. public abstract PopupType getHyperlinkPopupType();
  304. /**
  305. * What popup type should be used for popup menus for normal clicks.
  306. *
  307. * @return Appropriate popuptype for this frame
  308. */
  309. public abstract PopupType getNormalPopupType();
  310. /**
  311. * A method called to add custom popup items.
  312. *
  313. * @param popupMenu Popup menu to add popup items to
  314. */
  315. public abstract void addCustomPopupItems(final JPopupMenu popupMenu);
  316. /**
  317. * Shows a popup menu at the specified point for the specified click type.
  318. *
  319. * @param type ClickType Click type
  320. * @param point Point Point of the click
  321. */
  322. private void showPopupMenuInternal(final ClickTypeValue type,
  323. final Point point) {
  324. final String[] parts = type.getValue().split("\n");
  325. final Object[][] arguments = new Object[parts.length][1];
  326. int i = 0;
  327. for (final String part : parts) {
  328. arguments[i++][0] = part;
  329. }
  330. final JPopupMenu popupMenu;
  331. switch (type.getType()) {
  332. case CHANNEL:
  333. popupMenu = getPopupMenu(getChannelPopupType(), arguments);
  334. popupMenu.add(new ChannelCopyAction(clipboard, type.getValue()));
  335. if (popupMenu.getComponentCount() > 1) {
  336. popupMenu.addSeparator();
  337. }
  338. break;
  339. case HYPERLINK:
  340. popupMenu = getPopupMenu(getHyperlinkPopupType(), arguments);
  341. popupMenu.add(new HyperlinkCopyAction(clipboard, type.getValue()));
  342. if (popupMenu.getComponentCount() > 1) {
  343. popupMenu.addSeparator();
  344. }
  345. break;
  346. case NICKNAME:
  347. popupMenu = getPopupMenu(getNicknamePopupType(), arguments);
  348. if (popupMenu.getComponentCount() > 0) {
  349. popupMenu.addSeparator();
  350. }
  351. popupMenu.add(new NicknameCopyAction(clipboard, type.getValue()));
  352. break;
  353. default:
  354. popupMenu = getPopupMenu(null, arguments);
  355. break;
  356. }
  357. popupMenu.add(new TextPaneCopyAction(getTextPane()));
  358. popupMenu.add(new TextPaneControlCodeCopyAction(textPane));
  359. addCustomPopupItems(popupMenu);
  360. popupMenu.show(this, (int) point.getX(), (int) point.getY());
  361. }
  362. /**
  363. * Shows a popup menu at the specified point for the specified click type.
  364. *
  365. * @param type ClickType Click type
  366. * @param point Point Point of the click (Must be screen coords)
  367. */
  368. public void showPopupMenu(final ClickTypeValue type, final Point point) {
  369. SwingUtilities.convertPointFromScreen(point, this);
  370. showPopupMenuInternal(type, point);
  371. }
  372. /**
  373. * Builds a popup menu of a specified type.
  374. *
  375. * @param type type of menu to build
  376. * @param arguments Arguments for the command
  377. *
  378. * @return PopupMenu
  379. */
  380. public JPopupMenu getPopupMenu(final PopupType type, final Object[][] arguments) {
  381. JPopupMenu popupMenu = new JPopupMenu();
  382. if (type != null) {
  383. popupMenu = (JPopupMenu) populatePopupMenu(popupMenu,
  384. popupManager.getMenu(type, getContainer().getConfigManager()), arguments);
  385. }
  386. return popupMenu;
  387. }
  388. /**
  389. * Populates the specified popupmenu.
  390. *
  391. * @param menu Menu component
  392. * @param popup Popup to get info from
  393. * @param arguments Arguments for the command
  394. *
  395. * @return Populated popup
  396. */
  397. private JComponent populatePopupMenu(final JComponent menu, final PopupMenu popup,
  398. final Object[][] arguments) {
  399. for (final PopupMenuItem menuItem : popup.getItems()) {
  400. if (menuItem.isDivider()) {
  401. menu.add(new JSeparator());
  402. } else if (menuItem.isSubMenu()) {
  403. menu.add(populatePopupMenu(new JMenu(menuItem.getName()),
  404. menuItem.getSubMenu(), arguments));
  405. } else {
  406. menu.add(new JMenuItem(new CommandAction(commandParser, getContainer(),
  407. menuItem.getName(), menuItem.getCommand(arguments))));
  408. }
  409. }
  410. return menu;
  411. }
  412. /**
  413. * Gets the search bar.
  414. *
  415. * @return the frames search bar
  416. */
  417. public final SwingSearchBar getSearchBar() {
  418. return searchBar;
  419. }
  420. @ConfigBinding(domain = "ui", key = "foregroundcolour")
  421. private void updateForegroundColour(final String value) {
  422. getTextPane().setForeground(UIUtilities.convertColour(
  423. colourManager.getColourFromString(value, null)));
  424. }
  425. @ConfigBinding(domain = "ui", key = "backgroundcolour")
  426. private void updateBackgroundColour(final String value) {
  427. getTextPane().setBackground(UIUtilities.convertColour(
  428. colourManager.getColourFromString(value, null)));
  429. }
  430. @Handler(invocation = EdtHandlerInvocation.class)
  431. public void windowClosing(final FrameClosingEvent event) {
  432. if (event.getSource().equals(getContainer())) {
  433. if (popout) {
  434. setPopout(false);
  435. }
  436. setVisible(false);
  437. getTextPane().close();
  438. }
  439. }
  440. /** Disposes of this window, removing any listeners. */
  441. public void dispose() {
  442. getContainer().getConfigManager().getBinder().unbind(this);
  443. }
  444. /**
  445. * Bundle of dependencies required by {@link TextFrame}.
  446. *
  447. * <p>
  448. * Because of the number of dependencies and the amount of subclassing, collect the dependencies
  449. * together here so they can be easily modified without having to modify all subclasses.
  450. */
  451. public static class TextFrameDependencies {
  452. final TextPaneFactory textPaneFactory;
  453. final SwingController controller;
  454. final PopupManager popupManager;
  455. final EventBus eventBus;
  456. final AggregateConfigProvider globalConfig;
  457. final PasteDialogFactory pasteDialog;
  458. final ServiceManager serviceManager;
  459. final ActiveFrameManager activeFrameManager;
  460. final Clipboard clipboard;
  461. final CommandController commandController;
  462. final ColourManagerFactory colourManagerFactory;
  463. final SwingEventBus swingEventBus;
  464. final TabCompleterUtils tabCompleterUtils;
  465. final SwingSearchBarFactory searchBarFactory;
  466. final IconManager iconManager;
  467. @Inject
  468. public TextFrameDependencies(
  469. final TextPaneFactory textPaneFactory,
  470. final SwingController controller,
  471. final PopupManager popupManager,
  472. final EventBus eventBus,
  473. final PasteDialogFactory pasteDialog,
  474. final ServiceManager serviceManager,
  475. @GlobalConfig final AggregateConfigProvider globalConfig,
  476. final ActiveFrameManager activeFrameManager,
  477. final Clipboard clipboard,
  478. final CommandController commandController,
  479. final ColourManagerFactory colourManagerFactory,
  480. final SwingEventBus swingEventBus,
  481. final TabCompleterUtils tabCompleterUtils,
  482. final SwingSearchBarFactory searchBarFactory,
  483. final IconManager iconManager) {
  484. this.textPaneFactory = textPaneFactory;
  485. this.controller = controller;
  486. this.popupManager = popupManager;
  487. this.eventBus = eventBus;
  488. this.globalConfig = globalConfig;
  489. this.pasteDialog = pasteDialog;
  490. this.serviceManager = serviceManager;
  491. this.activeFrameManager = activeFrameManager;
  492. this.clipboard = clipboard;
  493. this.commandController = commandController;
  494. this.colourManagerFactory = colourManagerFactory;
  495. this.swingEventBus = swingEventBus;
  496. this.tabCompleterUtils = tabCompleterUtils;
  497. this.searchBarFactory = searchBarFactory;
  498. this.iconManager = iconManager;
  499. }
  500. }
  501. }