001package jmri.jmrit.display;
002
003import java.awt.*;
004import java.awt.event.*;
005
006import javax.swing.*;
007
008import jmri.InstanceManager;
009import jmri.PermissionManager;
010import jmri.util.JmriJFrame;
011import jmri.util.swing.*;
012
013/**
014 * A JmriJFrame with permissions.
015 *
016 * @author Daniel Bergqvist (C) 2024
017 */
018public class JmriJFrameWithPermissions extends JmriJFrame {
019
020    private final JPanel _hiddenPane = new JPanel();
021    private Container _contentPane = new JPanel();
022    private final JMenuBar _hiddenMenuBar = new JMenuBar();
023    private JMenuBar _menuBar = super.getJMenuBar();
024    private boolean _keepSize = true;
025
026    public JmriJFrameWithPermissions() {
027        setupContentPaneAndMenu();
028    }
029
030    public JmriJFrameWithPermissions(String name) {
031        super(name);
032        setupContentPaneAndMenu();
033    }
034
035    public JmriJFrameWithPermissions(String name, boolean saveSize, boolean savePosition) {
036        super(name, saveSize, savePosition);
037        setupContentPaneAndMenu();
038    }
039
040    /**
041     * Setup a fake content pane and a fake menu to be used if
042     * the user doesn't have permission to view the panel.
043     */
044    private void setupContentPaneAndMenu() {
045        _contentPane.setLayout(new BorderLayout() {
046            /* This BorderLayout subclass maps a null constraint to CENTER.
047             * Although the reference BorderLayout also does this, some VMs
048             * throw an IllegalArgumentException.
049             */
050            @Override
051            public void addLayoutComponent(Component comp, Object constraints) {
052                if (constraints == null) {
053                    constraints = BorderLayout.CENTER;
054                }
055                super.addLayoutComponent(comp, constraints);
056            }
057        });
058
059        if (!InstanceManager.getDefault(PermissionManager.class).isEnabled()) {
060            return;
061        }
062        setGlassPane(new MyGlassPane().init());
063        _hiddenPane.setLayout(new GridBagLayout());  // Center innerPanel
064        JPanel innerPanel = new JPanel();
065        innerPanel.setLayout(new BoxLayout(innerPanel, BoxLayout.Y_AXIS));
066        innerPanel.add(new JLabel(Bundle.getMessage("Editor_PermissionDenied")));
067        innerPanel.add(Box.createVerticalStrut(5));
068        innerPanel.add(new JLabel(Bundle.getMessage("Editor_LoginToViewPanel")));
069        _hiddenPane.add(innerPanel);
070        switchContentPaneAndMenu();
071        InstanceManager.getDefault(PermissionManager.class).addLoginListener((isLogin) -> {
072            switchContentPaneAndMenu();
073        });
074    }
075
076    @Override
077    public void setContentPane(Container contentPane) {
078        this._contentPane = contentPane;
079    }
080
081    /**
082     * Switch contentPane and menu depending on whenether
083     * the user has read access to the panel or not.
084     */
085    private void switchContentPaneAndMenu() {
086        if (! InstanceManager.getDefault(PermissionManager.class)
087                .hasAtLeastPermission(EditorPermissions.EDITOR_PERMISSION,
088                        EditorPermissions.EditorPermissionEnum.View)) {
089            super.getGlassPane().setVisible(false);
090            super.setContentPane(_hiddenPane);
091            super.setJMenuBar(_hiddenMenuBar);
092        } else {
093            super.setContentPane(_contentPane);
094            super.setJMenuBar(_menuBar);
095            super.getGlassPane().setVisible(
096                    ! InstanceManager.getDefault(PermissionManager.class)
097                            .hasAtLeastPermission(EditorPermissions.EDITOR_PERMISSION,
098                                    EditorPermissions.EditorPermissionEnum.ViewControl)
099            );
100        }
101        // Save the bounds before pack() since pack() might resize the panel
102        Rectangle bounds = getBounds();
103        pack();
104        if (_keepSize) {
105            setBounds(bounds);
106        } else {
107            setLocation(bounds.x, bounds.y);
108        }
109        revalidate();
110    }
111
112    @Override
113    public void revalidate() {
114        if (_contentPane != super.getContentPane()) {
115            // Ensure the contentPane is validated as well
116            super.setContentPane(_contentPane);
117            _contentPane.revalidate();
118            super.setContentPane(_hiddenPane);
119        }
120        super.revalidate();
121    }
122
123    @Override
124    public void setVisible(boolean b) {
125        if (b && _contentPane != super.getContentPane()) {
126            // Ensure the contentPane is validated as well
127            super.setContentPane(_contentPane);
128            super.setVisible(b);
129            super.setContentPane(_hiddenPane);
130        }
131        super.setVisible(b);
132    }
133
134    @Override
135    public Container getContentPane() {
136        // We have our own content pane which may or may not be the content
137        // pane that's actually in use. If the user doesn't have permission
138        // to view the panel, we show another content pane instead which only
139        // has a message: "Permission denied. Login to view the panel".
140        return _contentPane;
141    }
142
143    @Override
144    public JMenuBar getJMenuBar() {
145        // We have our own menu which may or may not be the menu
146        // that's actually in use. If the user doesn't have permission
147        // to view the panel, we show another menu instead which is
148        // empty.
149        return _menuBar;
150    }
151
152    @Override
153    public void setJMenuBar(JMenuBar menuBar) {
154        this._menuBar = menuBar;
155        switchContentPaneAndMenu();
156    }
157
158    /**
159     * Should the panel keep its size when switching between normal and
160     * hidden panel?
161     * @param keepSize true if to keep size, false if only keep location
162     */
163    public final void setKeepSize(boolean keepSize) {
164        this._keepSize = keepSize;
165    }
166
167
168    /**
169     * This pane consumes all the mouse events and key events when visible.
170     * It's used when the panel is read only.
171     */
172    private static class MyGlassPane extends JPanel
173            implements JmriMouseListener, KeyListener {
174
175        private MyGlassPane init() {
176            setOpaque(false);
177            addMouseListener(JmriMouseListener.adapt(this));
178            addKeyListener(this);
179            return this;
180        }
181
182        private void showReadOnly() {
183            JmriJOptionPane.showMessageDialog(this,
184                    Bundle.getMessage("JmriJFrameWithPermissions_PanelReadOnly"),
185                    Bundle.getMessage("JmriJFrameWithPermissions_TitleError"),
186                    JmriJOptionPane.ERROR_MESSAGE);
187        }
188
189        @Override
190        public void mouseClicked(JmriMouseEvent e) {
191            // Do nothing
192        }
193
194        @Override
195        public void mousePressed(JmriMouseEvent e) {
196            e.consume();
197            requestFocusInWindow();
198            showReadOnly();
199        }
200
201        @Override
202        public void mouseReleased(JmriMouseEvent e) {
203            // Do nothing
204        }
205
206        @Override
207        public void mouseEntered(JmriMouseEvent e) {
208            // Do nothing
209        }
210
211        @Override
212        public void mouseExited(JmriMouseEvent e) {
213            // Do nothing
214        }
215
216        @Override
217        public void keyTyped(KeyEvent e) {
218            e.consume();
219            showReadOnly();
220        }
221
222        @Override
223        public void keyPressed(KeyEvent e) {
224            // Do nothing
225        }
226
227        @Override
228        public void keyReleased(KeyEvent e) {
229            // Do nothing
230        }
231
232    }
233
234}