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

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