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.

ButtonBar.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. /*
  2. * Copyright (c) 2006-2014 DMDirc Developers
  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.addons.ui_swing.framemanager.buttonbar;
  23. import com.dmdirc.ClientModule.GlobalConfig;
  24. import com.dmdirc.DMDircMBassador;
  25. import com.dmdirc.FrameContainer;
  26. import com.dmdirc.FrameContainerComparator;
  27. import com.dmdirc.addons.ui_swing.EdtHandlerInvocation;
  28. import com.dmdirc.addons.ui_swing.SwingWindowFactory;
  29. import com.dmdirc.addons.ui_swing.UIUtilities;
  30. import com.dmdirc.addons.ui_swing.actions.CloseFrameContainerAction;
  31. import com.dmdirc.addons.ui_swing.components.frames.TextFrame;
  32. import com.dmdirc.addons.ui_swing.events.SwingWindowAddedEvent;
  33. import com.dmdirc.addons.ui_swing.events.SwingWindowDeletedEvent;
  34. import com.dmdirc.addons.ui_swing.events.SwingWindowSelectedEvent;
  35. import com.dmdirc.addons.ui_swing.framemanager.FrameManager;
  36. import com.dmdirc.addons.ui_swing.framemanager.FramemanagerPosition;
  37. import com.dmdirc.addons.ui_swing.injection.SwingEventBus;
  38. import com.dmdirc.addons.ui_swing.interfaces.ActiveFrameManager;
  39. import com.dmdirc.events.FrameIconChangedEvent;
  40. import com.dmdirc.events.FrameNameChangedEvent;
  41. import com.dmdirc.events.NotificationClearedEvent;
  42. import com.dmdirc.events.NotificationSetEvent;
  43. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  44. import com.dmdirc.interfaces.config.ConfigChangeListener;
  45. import com.dmdirc.interfaces.ui.Window;
  46. import com.dmdirc.ui.WindowManager;
  47. import com.dmdirc.util.colours.Colour;
  48. import com.google.common.base.Optional;
  49. import java.awt.Dimension;
  50. import java.awt.Insets;
  51. import java.awt.event.ActionEvent;
  52. import java.awt.event.ActionListener;
  53. import java.awt.event.ComponentEvent;
  54. import java.awt.event.ComponentListener;
  55. import java.awt.event.MouseEvent;
  56. import java.awt.event.MouseListener;
  57. import java.io.Serializable;
  58. import java.util.ArrayList;
  59. import java.util.Collections;
  60. import java.util.HashMap;
  61. import java.util.List;
  62. import java.util.Map;
  63. import javax.inject.Inject;
  64. import javax.swing.JComponent;
  65. import javax.swing.JMenuItem;
  66. import javax.swing.JPopupMenu;
  67. import javax.swing.JScrollPane;
  68. import javax.swing.ScrollPaneConstants;
  69. import javax.swing.SwingConstants;
  70. import javax.swing.SwingUtilities;
  71. import net.miginfocom.layout.PlatformDefaults;
  72. import net.miginfocom.swing.MigLayout;
  73. import net.engio.mbassy.listener.Handler;
  74. import net.engio.mbassy.listener.Invoke;
  75. /**
  76. * The button bar manager is a grid of buttons that presents a manager similar to that used by mIRC.
  77. */
  78. public final class ButtonBar implements FrameManager, ActionListener, ComponentListener,
  79. Serializable, MouseListener, ConfigChangeListener {
  80. /** Serial version UID. */
  81. private static final long serialVersionUID = 3;
  82. /** The default number of buttons per row or column. */
  83. private static final int NUM_CELLS = 1;
  84. /** The default height of buttons. */
  85. private static final int BUTTON_HEIGHT = 25;
  86. /** A map of windows to the buttons we're using for them. */
  87. private final Map<Window, FrameToggleButton> buttons;
  88. /** The scrolling panel for our ButtonBar. */
  89. private final JScrollPane scrollPane;
  90. /** The panel used for our buttons. */
  91. private final ButtonPanel buttonPanel;
  92. /** The position of this frame manager. */
  93. private final FramemanagerPosition position;
  94. /** The default width of buttons. */
  95. private int buttonWidth = 0;
  96. /** The parent for the manager. */
  97. private JComponent parent;
  98. /** The currently selected frame. */
  99. private transient FrameContainer selected;
  100. /** Selected window. */
  101. private Window activeWindow;
  102. /** Sort root windows prefs setting. */
  103. private boolean sortRootWindows;
  104. /** Sort child windows prefs setting. */
  105. private boolean sortChildWindows;
  106. /** UI Window Factory. */
  107. private final SwingWindowFactory windowFactory;
  108. /** Window management. */
  109. private final WindowManager windowManager;
  110. /** Global configuration to read settings from. */
  111. private final AggregateConfigProvider globalConfig;
  112. /** Active frame manager. */
  113. private final ActiveFrameManager activeFrameManager;
  114. /**
  115. * Creates a new instance of ButtonBar.
  116. *
  117. * @param windowFactory The factory to use to retrieve window information.
  118. * @param windowManager The window manager to use to read window state.
  119. * @param globalConfig Global configuration to read settings from.
  120. * @param activeFrameManager The active window manager
  121. * @param eventBus The event bus to subscribe to events on
  122. */
  123. @Inject
  124. public ButtonBar(
  125. final SwingWindowFactory windowFactory,
  126. @GlobalConfig final AggregateConfigProvider globalConfig,
  127. final WindowManager windowManager,
  128. final ActiveFrameManager activeFrameManager,
  129. final DMDircMBassador eventBus,
  130. @SwingEventBus final DMDircMBassador swingEventBus) {
  131. this.windowFactory = windowFactory;
  132. this.globalConfig = globalConfig;
  133. this.windowManager = windowManager;
  134. this.activeFrameManager = activeFrameManager;
  135. eventBus.subscribe(this);
  136. swingEventBus.subscribe(this);
  137. scrollPane = new JScrollPane();
  138. scrollPane.setBorder(null);
  139. scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
  140. scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
  141. scrollPane.setMinimumSize(new Dimension(0, BUTTON_HEIGHT
  142. + (int) PlatformDefaults.getUnitValueX("related").getValue() * 2));
  143. position = FramemanagerPosition.getPosition(
  144. globalConfig.getOption("ui", "framemanagerPosition"));
  145. if (position.isHorizontal()) {
  146. buttonPanel = new ButtonPanel(activeFrameManager,
  147. new MigLayout("ins rel, fill, flowx"), this);
  148. } else {
  149. buttonPanel = new ButtonPanel(activeFrameManager,
  150. new MigLayout("ins rel, fill, flowy"), this);
  151. }
  152. scrollPane.getViewport().addMouseWheelListener(buttonPanel);
  153. scrollPane.getViewport().add(buttonPanel);
  154. buttons = Collections.synchronizedMap(new HashMap<Window, FrameToggleButton>());
  155. sortChildWindows = globalConfig.getOptionBool("ui", "sortchildwindows");
  156. sortRootWindows = globalConfig.getOptionBool("ui", "sortrootwindows");
  157. globalConfig.addChangeListener("ui", "sortrootwindows", this);
  158. globalConfig.addChangeListener("ui", "sortchildwindows", this);
  159. }
  160. /**
  161. * Retrieves button height.
  162. *
  163. * @return Button height
  164. *
  165. * @since 0.6.4
  166. */
  167. public int getButtonHeight() {
  168. return BUTTON_HEIGHT;
  169. }
  170. /**
  171. * Returns the button object of the current selected window.
  172. *
  173. * @return Button object for the current selected window
  174. */
  175. public FrameToggleButton getSelectedButton() {
  176. return getButton(selected);
  177. }
  178. @Override
  179. public void setParent(final JComponent parent) {
  180. SwingUtilities.invokeLater(new Runnable() {
  181. @Override
  182. public void run() {
  183. ButtonBar.this.parent = parent;
  184. scrollPane.setSize(parent.getWidth(), parent.getHeight());
  185. parent.setVisible(false);
  186. parent.setLayout(new MigLayout("ins 0"));
  187. parent.add(scrollPane);
  188. parent.addComponentListener(ButtonBar.this);
  189. ButtonBar.this.buttonWidth = position.isHorizontal()
  190. ? 150 : parent.getWidth() / NUM_CELLS;
  191. initButtons(windowManager.getRootWindows());
  192. final TextFrame activeFrame = activeFrameManager.getActiveFrame();
  193. if (activeFrame != null) {
  194. selectionChanged(new SwingWindowSelectedEvent(
  195. Optional.fromNullable((Window) activeFrame)));
  196. }
  197. parent.setVisible(true);
  198. }
  199. });
  200. }
  201. /**
  202. * Initialises buttons for the currently available windows. This should only be called once when
  203. * this buttonbar is made active in the client. See {@link #setParent}. This method essentially
  204. * does nothing if the client is started with the buttonbar enabled.
  205. *
  206. * @param windowCollection Collection of windows {@link FrameContainer}
  207. *
  208. * @since 0.6.4
  209. */
  210. private void initButtons(final Iterable<FrameContainer> windowCollection) {
  211. for (FrameContainer frame : windowCollection) {
  212. final TextFrame window = windowFactory.getSwingWindow(frame);
  213. final TextFrame parentWindow = windowFactory.getSwingWindow(frame.getParent().orNull());
  214. if (window != null) {
  215. windowAdded(new SwingWindowAddedEvent(Optional.fromNullable(parentWindow), window));
  216. }
  217. if (!frame.getChildren().isEmpty()) {
  218. initButtons(new ArrayList<>(frame.getChildren()));
  219. }
  220. }
  221. }
  222. /**
  223. * Retreives the button object associated with {@link FrameContainer}.
  224. *
  225. * @param frame FrameContainer to find associated button for
  226. *
  227. * @return {@link FrameToggleButton} object asociated with this FrameContainer. Returns null if
  228. * none exist
  229. */
  230. public FrameToggleButton getButton(final FrameContainer frame) {
  231. final Window window = windowFactory.getSwingWindow(frame);
  232. if (buttons.containsKey(window)) {
  233. return buttons.get(window);
  234. }
  235. return null;
  236. }
  237. /**
  238. * Adds buttons for the collection of windows that is passed to it. This method also iterates
  239. * through any children for each item in the collection.
  240. *
  241. * This method has no boundaries as to how many generations it can iterate through.
  242. *
  243. * @param windowCollection Collection of windows {@link FrameContainer}
  244. *
  245. * @since 0.6.4
  246. */
  247. private void displayButtons(final Iterable<FrameContainer> windowCollection) {
  248. for (FrameContainer window : windowCollection) {
  249. final FrameToggleButton button = getButton(window);
  250. if (button != null) {
  251. button.setPreferredSize(new Dimension(buttonWidth, BUTTON_HEIGHT));
  252. buttonPanel.add(button);
  253. if (!window.getChildren().isEmpty()) {
  254. final List<FrameContainer> childList = new ArrayList<>(window.getChildren());
  255. if (sortChildWindows) {
  256. Collections.sort(childList, new FrameContainerComparator());
  257. }
  258. displayButtons(childList);
  259. }
  260. }
  261. }
  262. }
  263. /**
  264. * Removes all buttons from the bar and readds them.
  265. */
  266. private void relayout() {
  267. buttonPanel.setVisible(false);
  268. buttonPanel.removeAll();
  269. final List<FrameContainer> windowList = new ArrayList<>(windowManager.getRootWindows());
  270. if (sortRootWindows) {
  271. Collections.sort(windowList, new FrameContainerComparator());
  272. }
  273. displayButtons(windowList);
  274. buttonPanel.setVisible(true);
  275. }
  276. /**
  277. * Adds a button to the button array with the details from the specified container.
  278. *
  279. * @param source The Container to get title/icon info from
  280. */
  281. private void addButton(final Window source) {
  282. final FrameToggleButton button = new FrameToggleButton(
  283. source.getContainer().getName(),
  284. source.getContainer().getIconManager().getIcon(source.getContainer().getIcon()),
  285. (TextFrame) source);
  286. button.addActionListener(this);
  287. button.addMouseListener(this);
  288. button.setHorizontalAlignment(SwingConstants.LEFT);
  289. button.setMinimumSize(new Dimension(0, BUTTON_HEIGHT));
  290. button.setMargin(new Insets(0, 0, 0, 0));
  291. buttons.put(source, button);
  292. }
  293. @Override
  294. public boolean canPositionVertically() {
  295. return true;
  296. }
  297. @Override
  298. public boolean canPositionHorizontally() {
  299. return true;
  300. }
  301. @Handler(invocation = EdtHandlerInvocation.class, delivery = Invoke.Asynchronously)
  302. public void windowAdded(final SwingWindowAddedEvent event) {
  303. final TextFrame window = event.getChildWindow();
  304. addButton(window);
  305. relayout();
  306. }
  307. @Handler(invocation = EdtHandlerInvocation.class, delivery = Invoke.Asynchronously)
  308. public void windowDeleted(final SwingWindowDeletedEvent event) {
  309. final TextFrame window = event.getChildWindow();
  310. if (buttons.containsKey(window)) {
  311. buttonPanel.setVisible(false);
  312. buttonPanel.remove(buttons.get(window));
  313. buttons.remove(window);
  314. buttonPanel.setVisible(true);
  315. }
  316. }
  317. @Override
  318. public void actionPerformed(final ActionEvent e) {
  319. final FrameToggleButton button = (FrameToggleButton) e.getSource();
  320. final TextFrame frame = button.getTextFrame();
  321. if (frame != null && frame.equals(activeWindow)) {
  322. button.setSelected(true);
  323. }
  324. activeFrameManager.setActiveFrame(frame);
  325. }
  326. @Override
  327. public void componentResized(final ComponentEvent e) {
  328. buttonWidth = position.isHorizontal() ? 150 : parent.getWidth() / NUM_CELLS;
  329. relayout();
  330. }
  331. @Override
  332. public void componentMoved(final ComponentEvent e) {
  333. // Do nothing
  334. }
  335. @Override
  336. public void componentShown(final ComponentEvent e) {
  337. // Do nothing
  338. }
  339. @Override
  340. public void componentHidden(final ComponentEvent e) {
  341. // Do nothing
  342. }
  343. @Handler(invocation = EdtHandlerInvocation.class, delivery = Invoke.Asynchronously)
  344. public void notificationSet(final NotificationSetEvent event) {
  345. final FrameToggleButton button = getButton(event.getWindow());
  346. if (button != null) {
  347. button.setForeground(UIUtilities.convertColour(event.getColour()));
  348. }
  349. }
  350. @Handler(invocation = EdtHandlerInvocation.class, delivery = Invoke.Asynchronously)
  351. public void notificationCleared(final NotificationClearedEvent event) {
  352. // TODO: Should this colour be configurable?
  353. notificationSet(new NotificationSetEvent(event.getWindow(),
  354. event.getWindow().getNotification().or(Colour.BLACK)));
  355. }
  356. @Handler(invocation = EdtHandlerInvocation.class, delivery = Invoke.Asynchronously)
  357. public void selectionChanged(final SwingWindowSelectedEvent event) {
  358. activeWindow = event.getWindow().orNull();
  359. final FrameToggleButton selectedButton = getButton(selected);
  360. if (selected != null && selectedButton != null) {
  361. selectedButton.setSelected(false);
  362. }
  363. selected = activeWindow == null ? null : activeWindow.getContainer();
  364. final FrameToggleButton button = getButton(event.getWindow().get().getContainer());
  365. if (button != null) {
  366. scrollPane.getViewport().scrollRectToVisible(button.getBounds());
  367. button.setSelected(true);
  368. }
  369. }
  370. @Handler(invocation = EdtHandlerInvocation.class, delivery = Invoke.Asynchronously)
  371. public void iconChanged(final FrameIconChangedEvent event) {
  372. final FrameToggleButton button = getButton(event.getContainer());
  373. if (button != null) {
  374. button.setIcon(event.getContainer().getIconManager().getIcon(event.getIcon()));
  375. }
  376. }
  377. @Handler(invocation = EdtHandlerInvocation.class, delivery = Invoke.Asynchronously)
  378. public void nameChanged(final FrameNameChangedEvent event) {
  379. final FrameToggleButton button = getButton(event.getContainer());
  380. if (button != null) {
  381. button.setText(event.getName());
  382. }
  383. }
  384. /**
  385. * Creates and displays a Popup menu for the button that was clicked.
  386. *
  387. * @param e MouseEvent for this event
  388. */
  389. public void processMouseEvents(final MouseEvent e) {
  390. if (e.isPopupTrigger()) {
  391. final FrameToggleButton button = (FrameToggleButton) e.getSource();
  392. final TextFrame frame = button.getTextFrame();
  393. if (frame == null) {
  394. return;
  395. }
  396. final JPopupMenu popupMenu = frame.getPopupMenu(null,
  397. new Object[][]{new Object[]{""}});
  398. frame.addCustomPopupItems(popupMenu);
  399. popupMenu.add(new JMenuItem(new CloseFrameContainerAction(frame.
  400. getContainer())));
  401. popupMenu.show(button, e.getX(), e.getY());
  402. }
  403. }
  404. @Override
  405. public void mouseClicked(final MouseEvent e) {
  406. processMouseEvents(e);
  407. }
  408. @Override
  409. public void mousePressed(final MouseEvent e) {
  410. processMouseEvents(e);
  411. }
  412. @Override
  413. public void mouseReleased(final MouseEvent e) {
  414. processMouseEvents(e);
  415. }
  416. @Override
  417. public void mouseEntered(final MouseEvent e) {
  418. //Do nothing
  419. }
  420. @Override
  421. public void mouseExited(final MouseEvent e) {
  422. //Do nothing
  423. }
  424. @Override
  425. public void configChanged(final String domain, final String key) {
  426. switch (key) {
  427. case "sortrootwindows":
  428. sortRootWindows = globalConfig.getOptionBool("ui", "sortrootwindows");
  429. break;
  430. case "sortchildwindows":
  431. sortChildWindows = globalConfig.getOptionBool("ui", "sortrootwindows");
  432. break;
  433. }
  434. relayout();
  435. }
  436. }