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 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. /*
  2. * Copyright (c) 2006-2011 Chris Smith, Shane Mc Cormack, Gregory Holmes,
  3. * Simon Mott
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  21. * SOFTWARE.
  22. */
  23. package com.dmdirc.addons.ui_swing.framemanager.buttonbar;
  24. import com.dmdirc.FrameContainer;
  25. import com.dmdirc.FrameContainerComparator;
  26. import com.dmdirc.addons.ui_swing.SwingController;
  27. import com.dmdirc.addons.ui_swing.SwingWindowFactory;
  28. import com.dmdirc.addons.ui_swing.UIUtilities;
  29. import com.dmdirc.addons.ui_swing.actions.CloseFrameContainerAction;
  30. import com.dmdirc.addons.ui_swing.components.frames.TextFrame;
  31. import com.dmdirc.addons.ui_swing.framemanager.FrameManager;
  32. import com.dmdirc.addons.ui_swing.framemanager.FramemanagerPosition;
  33. import com.dmdirc.config.IdentityManager;
  34. import com.dmdirc.interfaces.ConfigChangeListener;
  35. import com.dmdirc.interfaces.FrameInfoListener;
  36. import com.dmdirc.interfaces.NotificationListener;
  37. import com.dmdirc.interfaces.SelectionListener;
  38. import com.dmdirc.ui.IconManager;
  39. import com.dmdirc.ui.WindowManager;
  40. import com.dmdirc.ui.interfaces.UIController;
  41. import com.dmdirc.ui.interfaces.Window;
  42. import java.awt.Color;
  43. import java.awt.Dimension;
  44. import java.awt.Insets;
  45. import java.awt.event.ActionEvent;
  46. import java.awt.event.ActionListener;
  47. import java.awt.event.ComponentEvent;
  48. import java.awt.event.ComponentListener;
  49. import java.awt.event.MouseEvent;
  50. import java.awt.event.MouseListener;
  51. import java.io.Serializable;
  52. import java.util.ArrayList;
  53. import java.util.Collection;
  54. import java.util.Collections;
  55. import java.util.HashMap;
  56. import java.util.Map;
  57. import javax.swing.JComponent;
  58. import javax.swing.JMenuItem;
  59. import javax.swing.JPopupMenu;
  60. import javax.swing.JScrollPane;
  61. import javax.swing.ScrollPaneConstants;
  62. import javax.swing.SwingConstants;
  63. import javax.swing.SwingUtilities;
  64. import net.miginfocom.layout.PlatformDefaults;
  65. import net.miginfocom.swing.MigLayout;
  66. /**
  67. * The button bar manager is a grid of buttons that presents a manager similar
  68. * to that used by mIRC.
  69. */
  70. public final class ButtonBar implements FrameManager, ActionListener,
  71. ComponentListener, Serializable, NotificationListener,
  72. SelectionListener, FrameInfoListener, MouseListener,
  73. ConfigChangeListener {
  74. /**
  75. * A version number for this class. It should be changed whenever the class
  76. * structure is changed (or anything else that would prevent serialized
  77. * objects being unserialized with the new class).
  78. */
  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 number of buttons to render per {cell,row}. */
  83. private static final int MAX_BUTTONS = Integer.MAX_VALUE;
  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 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 = IdentityManager.getGlobalConfig()
  104. .getOptionBool("ui", "sortrootwindows");
  105. /** Sort child windows prefs setting. */
  106. private boolean sortChildWindows = IdentityManager.getGlobalConfig()
  107. .getOptionBool("ui", "sortchildwindows");
  108. /** UI Window Factory. */
  109. private SwingWindowFactory windowFactory;
  110. /** UI Controller. */
  111. private SwingController controller;
  112. /** Creates a new instance of DummyFrameManager. */
  113. public ButtonBar() {
  114. scrollPane = new JScrollPane();
  115. scrollPane.setBorder(null);
  116. scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants
  117. .HORIZONTAL_SCROLLBAR_NEVER);
  118. scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants
  119. .VERTICAL_SCROLLBAR_AS_NEEDED);
  120. scrollPane.setMinimumSize(new Dimension(0, BUTTON_HEIGHT
  121. + ((int) PlatformDefaults.getUnitValueX("related")
  122. .getValue()) * 2));
  123. buttons = Collections.synchronizedMap(
  124. new HashMap<Window, FrameToggleButton>());
  125. position = FramemanagerPosition.getPosition(
  126. IdentityManager.getGlobalConfig().getOption("ui",
  127. "framemanagerPosition"));
  128. IdentityManager.getGlobalConfig().addChangeListener("ui",
  129. "sortrootwindows", this);
  130. IdentityManager.getGlobalConfig().addChangeListener("ui",
  131. "sortchildwindows", this);
  132. if (position.isHorizontal()) {
  133. buttonPanel = new ButtonPanel(new MigLayout("ins rel, fill, flowx"),
  134. this);
  135. } else {
  136. buttonPanel = new ButtonPanel(new MigLayout("ins rel, fill, flowy"),
  137. this);
  138. }
  139. scrollPane.getViewport().addMouseWheelListener(buttonPanel);
  140. scrollPane.getViewport().add(buttonPanel);
  141. }
  142. /**
  143. * Retreives button height.
  144. *
  145. * @return Button height
  146. *
  147. * @since 0.6.4
  148. */
  149. public int getButtonHeight() {
  150. return BUTTON_HEIGHT;
  151. }
  152. /**
  153. * Returns the button object of the current selected window.
  154. *
  155. * @return Button object for the current selected window
  156. */
  157. public FrameToggleButton getSelectedButton() {
  158. return getButton(selected);
  159. }
  160. /** {@inheritDoc} */
  161. @Override
  162. public void setParent(final JComponent parent) {
  163. SwingUtilities.invokeLater(new Runnable() {
  164. /** {inheritDoc} */
  165. @Override
  166. public void run() {
  167. ButtonBar.this.parent = parent;
  168. scrollPane.setSize(parent.getWidth(), parent.getHeight());
  169. parent.setVisible(false);
  170. parent.setLayout(new MigLayout("ins 0"));
  171. parent.add(scrollPane);
  172. parent.addComponentListener(ButtonBar.this);
  173. ButtonBar.this.buttonWidth = position.isHorizontal()
  174. ? 150 : (parent.getWidth() / NUM_CELLS);
  175. initButtons(WindowManager.getRootWindows());
  176. if (controller.getMainFrame().getActiveFrame() != null) {
  177. selectionChanged(controller.getMainFrame()
  178. .getActiveFrame().getContainer());
  179. }
  180. parent.setVisible(true);
  181. }
  182. });
  183. }
  184. /**
  185. * Initialises buttons for the currently available windows. This should only
  186. * be called once when this buttonbar is made active in the client.
  187. * See {@link #setParent}. This method essentially does nothing if the
  188. * client is started with the buttonbar enabled.
  189. *
  190. * @param windowCollection Collection of windows {@link FrameContainer}
  191. * @author Simon Mott
  192. * @since 0.6.4
  193. */
  194. private void initButtons(
  195. final Collection<FrameContainer<?>> windowCollection) {
  196. Window window;
  197. Window parentWindow;
  198. for (FrameContainer<?> frame : windowCollection) {
  199. window = windowFactory.getSwingWindow(frame);
  200. parentWindow = windowFactory.getSwingWindow(frame.getParent());
  201. if (window != null) {
  202. windowAdded(parentWindow, window);
  203. }
  204. if (!frame.getChildren().isEmpty()) {
  205. final ArrayList<FrameContainer<?>> childList = new ArrayList
  206. <FrameContainer<?>>(frame.getChildren());
  207. initButtons(childList);
  208. }
  209. }
  210. }
  211. /**
  212. * Retreives the button object associated with {@link FrameContainer}.
  213. *
  214. * @param frame FrameContainer to find associated button for
  215. * @return {@link FrameToggleButton} object asociated with this
  216. * FrameContainer. Returns null if none exist
  217. */
  218. public FrameToggleButton getButton(final FrameContainer<?> frame) {
  219. final Window window = windowFactory.getSwingWindow(frame);
  220. if (buttons.containsKey(window)) {
  221. return buttons.get(window);
  222. }
  223. return null;
  224. }
  225. /** {@inheritDoc} */
  226. @Override
  227. public void setController(final UIController controller) {
  228. if (!(controller instanceof SwingController)) {
  229. throw new IllegalArgumentException(
  230. "Controller must be an instance of SwingController");
  231. }
  232. this.windowFactory = ((SwingController) controller).getWindowFactory();
  233. this.controller = ((SwingController) controller);
  234. }
  235. /**
  236. * Adds buttons for the collection of windows that is passed to it.
  237. * This method also iterates through any children for each item in the
  238. * collection.
  239. *
  240. * This method has no boundaries as to how many generations it can iterate
  241. * through.
  242. *
  243. * @param windowCollection Collection of windows {@link FrameContainer}
  244. *
  245. * @author Simon Mott
  246. * @since 0.6.4
  247. */
  248. private void displayButtons(
  249. final Collection<FrameContainer<?>> windowCollection) {
  250. FrameToggleButton button;
  251. for (FrameContainer<?> window : windowCollection) {
  252. button = getButton(window);
  253. if (button != null) {
  254. button.setPreferredSize(
  255. new Dimension(buttonWidth, BUTTON_HEIGHT));
  256. buttonPanel.add(button);
  257. if (!window.getChildren().isEmpty()) {
  258. final ArrayList<FrameContainer<?>> childList = new ArrayList
  259. <FrameContainer<?>>(window.getChildren());
  260. if (sortChildWindows) {
  261. Collections.sort(childList,
  262. new FrameContainerComparator());
  263. }
  264. displayButtons(childList);
  265. }
  266. }
  267. }
  268. }
  269. /**
  270. * Removes all buttons from the bar and readds them.
  271. */
  272. private void relayout() {
  273. buttonPanel.setVisible(false);
  274. buttonPanel.removeAll();
  275. final ArrayList<FrameContainer<?>> windowList = new
  276. ArrayList<FrameContainer<?>>(WindowManager.getRootWindows());
  277. if (sortRootWindows) {
  278. Collections.sort(windowList, new FrameContainerComparator());
  279. }
  280. displayButtons(windowList);
  281. buttonPanel.setVisible(true);
  282. }
  283. /**
  284. * Adds a button to the button array with the details from the specified
  285. * container.
  286. *
  287. * @param source The Container to get title/icon info from
  288. */
  289. private void addButton(final Window source) {
  290. final FrameToggleButton button = new FrameToggleButton(
  291. source.getContainer().toString(), IconManager.getIconManager()
  292. .getIcon(source.getContainer().getIcon()), source);
  293. button.addActionListener(this);
  294. button.addMouseListener(this);
  295. button.setHorizontalAlignment(SwingConstants.LEFT);
  296. button.setMinimumSize(new Dimension(0, BUTTON_HEIGHT));
  297. button.setMargin(new Insets(0, 0, 0, 0));
  298. buttons.put(source, button);
  299. }
  300. /** {@inheritDoc} */
  301. @Override
  302. public boolean canPositionVertically() {
  303. return true;
  304. }
  305. /** {@inheritDoc} */
  306. @Override
  307. public boolean canPositionHorizontally() {
  308. return true;
  309. }
  310. /** {@inheritDoc} */
  311. @Override
  312. public void windowAdded(final Window parent, final Window window) {
  313. UIUtilities.invokeLater(new Runnable() {
  314. /** {@inheritDoc} */
  315. @Override
  316. public void run() {
  317. addButton(window);
  318. relayout();
  319. window.getContainer().addNotificationListener(ButtonBar.this);
  320. window.getContainer().addSelectionListener(ButtonBar.this);
  321. window.getContainer().addFrameInfoListener(ButtonBar.this);
  322. }
  323. });
  324. }
  325. /** {@inheritDoc} */
  326. @Override
  327. public void windowDeleted(final Window parent, final Window window) {
  328. UIUtilities.invokeLater(new Runnable() {
  329. /** {@inheritDoc} */
  330. @Override
  331. public void run() {
  332. window.getContainer().removeNotificationListener(
  333. ButtonBar.this);
  334. window.getContainer().removeFrameInfoListener(ButtonBar.this);
  335. window.getContainer().removeSelectionListener(ButtonBar.this);
  336. if (buttons.containsKey(window)) {
  337. buttonPanel.setVisible(false);
  338. buttonPanel.remove(buttons.get(window));
  339. buttons.remove(window);
  340. buttonPanel.setVisible(true);
  341. }
  342. }
  343. });
  344. }
  345. /**
  346. * Called when the user clicks on one of the buttons.
  347. *
  348. * @param e The action event associated with this action
  349. */
  350. @Override
  351. public void actionPerformed(final ActionEvent e) {
  352. final FrameToggleButton button = (FrameToggleButton) e.getSource();
  353. final FrameContainer<?> window = button.getFrameContainer();
  354. final TextFrame frame = (TextFrame) button.getWindow();
  355. if (frame != null && window.equals(activeWindow.getContainer())) {
  356. button.setSelected(true);
  357. }
  358. window.activateFrame();
  359. }
  360. /**
  361. * Called when the parent component is resized.
  362. *
  363. * @param e A ComponentEvent corresponding to this event.
  364. */
  365. @Override
  366. public void componentResized(final ComponentEvent e) {
  367. buttonWidth = position.isHorizontal() ? 150
  368. : (parent.getWidth() / NUM_CELLS);
  369. relayout();
  370. }
  371. /**
  372. * Called when the parent component is moved.
  373. *
  374. * @param e A ComponentEvent corresponding to this event.
  375. */
  376. @Override
  377. public void componentMoved(final ComponentEvent e) {
  378. // Do nothing
  379. }
  380. /**
  381. * Called when the parent component is made visible.
  382. *
  383. * @param e A ComponentEvent corresponding to this event.
  384. */
  385. @Override
  386. public void componentShown(final ComponentEvent e) {
  387. // Do nothing
  388. }
  389. /**
  390. * Called when the parent component is made invisible.
  391. *
  392. * @param e A ComponentEvent corresponding to this event.
  393. */
  394. @Override
  395. public void componentHidden(final ComponentEvent e) {
  396. // Do nothing
  397. }
  398. /** {@inheritDoc} */
  399. @Override
  400. public void notificationSet(final FrameContainer<?> window,
  401. final Color colour) {
  402. UIUtilities.invokeLater(new Runnable() {
  403. /** {@inheritDoc} */
  404. @Override
  405. public void run() {
  406. final FrameToggleButton button = getButton(window);
  407. if (button != null) {
  408. button.setForeground(colour);
  409. }
  410. }
  411. });
  412. }
  413. /** {@inheritDoc} */
  414. @Override
  415. public void notificationCleared(final FrameContainer<?> window) {
  416. UIUtilities.invokeLater(new Runnable() {
  417. /** {@inheritDoc} */
  418. @Override
  419. public void run() {
  420. notificationSet(window, window.getNotification());
  421. }
  422. });
  423. }
  424. /** {@inheritDoc} */
  425. @Override
  426. public void selectionChanged(final FrameContainer<?> window) {
  427. UIUtilities.invokeLater(new Runnable() {
  428. /** {@inheritDoc} */
  429. @Override
  430. public void run() {
  431. activeWindow = (TextFrame) (getButton(window)).getWindow();
  432. FrameToggleButton button;
  433. button = getButton(selected);
  434. if (selected != null && button != null) {
  435. button.setSelected(false);
  436. }
  437. selected = window;
  438. button = getButton(window);
  439. if (button != null) {
  440. scrollPane.getViewport().scrollRectToVisible(
  441. button.getBounds());
  442. button.setSelected(true);
  443. }
  444. }
  445. });
  446. }
  447. /** {@inheritDoc} */
  448. @Override
  449. public void iconChanged(final FrameContainer<?> window, final String icon) {
  450. UIUtilities.invokeLater(new Runnable() {
  451. /** {@inheritDoc} */
  452. @Override
  453. public void run() {
  454. final FrameToggleButton button = getButton(window);
  455. if (button != null) {
  456. button.setIcon(IconManager.getIconManager().getIcon(icon));
  457. }
  458. }
  459. });
  460. }
  461. /** {@inheritDoc} */
  462. @Override
  463. public void nameChanged(final FrameContainer<?> window, final String name) {
  464. UIUtilities.invokeLater(new Runnable() {
  465. /** {@inheritDoc} */
  466. @Override
  467. public void run() {
  468. final FrameToggleButton button = getButton(window);
  469. if (button != null) {
  470. button.setText(name);
  471. }
  472. }
  473. });
  474. }
  475. /** {@inheritDoc} */
  476. @Override
  477. public void titleChanged(final FrameContainer<?> window,
  478. final String title) {
  479. // Do nothing
  480. }
  481. /**
  482. * Creates and displays a Popup menu for the button that was clicked.
  483. *
  484. * @param e MouseEvent for this event
  485. */
  486. public void processMouseEvents(final MouseEvent e) {
  487. if (e.isPopupTrigger()) {
  488. final FrameToggleButton button = (FrameToggleButton) e.getSource();
  489. final TextFrame frame = (TextFrame) button.getWindow();
  490. if (frame == null) {
  491. return;
  492. }
  493. final JPopupMenu popupMenu = frame.getPopupMenu(null,
  494. new Object[][] {new Object[]{""}});
  495. frame.addCustomPopupItems(popupMenu);
  496. popupMenu.add(new JMenuItem(new CloseFrameContainerAction(frame.
  497. getContainer())));
  498. popupMenu.show(button, e.getX(), e.getY());
  499. }
  500. }
  501. /**
  502. * {@inheritDoc}
  503. *
  504. * @param e MouseEvent for this event
  505. */
  506. @Override
  507. public void mouseClicked(final MouseEvent e) {
  508. processMouseEvents(e);
  509. }
  510. /**
  511. * {@inheritDoc}
  512. *
  513. * @param e MouseEvent for this event
  514. */
  515. @Override
  516. public void mousePressed(final MouseEvent e) {
  517. processMouseEvents(e);
  518. }
  519. /**
  520. * {@inheritDoc}
  521. *
  522. * @param e MouseEvent for this event
  523. */
  524. @Override
  525. public void mouseReleased(final MouseEvent e) {
  526. processMouseEvents(e);
  527. }
  528. /**
  529. * {@inheritDoc}
  530. *
  531. * @param e MouseEvent for this event
  532. */
  533. @Override
  534. public void mouseEntered(final MouseEvent e) {
  535. //Do nothing
  536. }
  537. /**
  538. * {@inheritDoc}
  539. *
  540. * @param e MouseEvent for this event
  541. */
  542. @Override
  543. public void mouseExited(final MouseEvent e) {
  544. //Do nothing
  545. }
  546. /** {@inheritDoc} */
  547. @Override
  548. public void configChanged(final String domain, final String key) {
  549. if ("sortrootwindows".equals(key)) {
  550. sortRootWindows = IdentityManager.getGlobalConfig()
  551. .getOptionBool("ui", "sortrootwindows");
  552. } else if ("sortchildwindows".equals(key)) {
  553. sortChildWindows = IdentityManager.getGlobalConfig()
  554. .getOptionBool("ui", "sortrootwindows");
  555. }
  556. relayout();
  557. }
  558. }