/*
* Copyright (C) 2013 Jonathan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package tumblrviewer;
import com.tumblr.jumblr.types.*;
import java.awt.*;
import java.awt.event.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.Map.Entry;
import javax.swing.*;
import javax.swing.event.*;
import tumblrviewer.TumblrBackend.DisplayModes;
/**
* This class creates the GUI that is the main view that is seen when scrolling
* through a blog's posts, likes or dashboard.
*
* @author jonathan
*/
public class MainViewGUI
{
static final boolean SINGLE_VIEW_MODE = true;
private static final boolean AUTO_LOAD_AT_PAGE_END = true;
private static final ImageIcon loadingImageIcon = new ImageIcon(MainViewGUI.class.getResource("load-avatar-64.gif"), "Loading avatar");
/* End of constants*/
private final TumblrBackend tumblrBackend;
private final JFrame jFrame;
private final Container panel;
private final Container imageDisplay;
private int imagesLoadedCounter = 0;
private JMenu followingOrNotMenu;
LinkedHashMap<DisplayModes, JRadioButtonMenuItem> modeItems;
private JMenu modeSelectMenu;
private JMenuItem followBlogMenuItem;
private JMenuItem unfollowBlogMenuItem;
private JMenu currentUserOptionsMenu;
private JMenuItem currentUserGoToMenu;
private JMenuItem avatarIconViewMenuItem;
private JMenu currentUserFollowingMenu;
private JMenu currentUserFollowersMenu;
/**
* Creates a new GUI for scrolling through a blog. More than one instance
* can be run at the same time. However every instance of this class will
* also make a new TumblrBackend class within it.
*
* @param currentDisplayMode - whether to show posts, likes or dashboard
* @param blogToView the blog name to view - if null will resolve to the
* current signed in user's home blog
*/
public MainViewGUI(DisplayModes currentDisplayMode, String blogToView)
{
jFrame = new JFrame("Tumblr");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
tumblrBackend = new TumblrBackend(this, currentDisplayMode, blogToView);
panel = jFrame.getContentPane();
panel.setLayout(new BorderLayout());
imageDisplay = new Container();
imageDisplay.setLayout(new BoxLayout(imageDisplay, BoxLayout.Y_AXIS));
JScrollPane jsp = new JScrollPane(imageDisplay, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
jsp.setWheelScrollingEnabled(true);
jsp.getVerticalScrollBar().setUnitIncrement(50);
jsp.setBorder(null);
JButton moreButton = new JButton(java.util.ResourceBundle.getBundle("en_gb").getString("LOAD MORE"));
moreButton.setMnemonic('L');
moreButton.addActionListener(new LoadMoreActionListener(tumblrBackend, moreButton));
if (AUTO_LOAD_AT_PAGE_END)
{
jsp.getViewport().addChangeListener(new checkAtBottomChangeListener(moreButton));
}
else
{
panel.add(moreButton, BorderLayout.PAGE_END);
}
panel.add(jsp, BorderLayout.CENTER);
jFrame.setJMenuBar(createMenuBar());
jFrame.setVisible(true);
jFrame.setMinimumSize(new Dimension(TumblrBackend.PHOTO_PREFERRED_SIZE + 25, TumblrBackend.PHOTO_PREFERRED_SIZE));
moreButton.doClick();
}
/**
* Adds an image to the main view by passing a URL
*
* @param previewImageUrl The url with reference to the image
* @param photoPost The photoPost object that we are going to add
* @param photoNumberInPost The number post in the photoPost object that
* previewImageUrl refers to
*/
public void addImage(final String previewImageUrl, final PhotoPost photoPost, final int photoNumberInPost)
{
ImageIcon icon;
boolean isZeroTimed;
try
{
URL url = new URL(previewImageUrl);
icon = new ImageIcon(url);
isZeroTimed = GIFzeroTimedWorkaround.isZeroTimedGif(url);
}
catch (MalformedURLException e)
{
return; //No point in completing rest of method
}
JLabel imageLabel = new JLabel();
imageLabel.setIcon(icon);
if (isZeroTimed)
{
icon.setImageObserver(new GIFzeroTimedWorkaround.ImageObserverWorkaround(imageLabel));
}
/*label.setText(tooltipText);
label.setVerticalTextPosition(JLabel.BOTTOM);
label.setHorizontalTextPosition(JLabel.CENTER);*/
imageLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
final String blogName = photoPost.getBlogName();
if (!blogName.isEmpty())
{
imageLabel.setToolTipText(blogName);
}
imageLabel.addMouseListener(new imageLabelMouseAdapter(this, photoPost, photoNumberInPost));
imageDisplay.add(imageLabel);
imagesLoadedCounter++;
jFrame.setTitle(tumblrBackend.guiTitle + " (" + imagesLoadedCounter + ")");
panel.revalidate();
}
private static void setGUITheme()
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e)
{
/*try
{
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
{
if ("Nimbus".equals(info.getName()))
{
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e2)
{
}
/**/
}
}
/**
* The JFrame that holds this GUI instance
*
* @return the JFrame object
*/
public JFrame getJFrame()
{
return jFrame;
}
private class LoadMoreActionListener implements ActionListener, Runnable
{
private final TumblrBackend tumblrBackend;
private final JButton button;
private Thread loaderThread;
public LoadMoreActionListener(TumblrBackend tumblrBackend, JButton button)
{
this.tumblrBackend = tumblrBackend;
this.button = button;
}
@Override
public void actionPerformed(ActionEvent e)
{
if (loaderThread != null)
{
return;
}
loaderThread = new Thread((Runnable) this, "Load More Posts Thread");
loaderThread.start();
button.setEnabled(false);
button.setText(java.util.ResourceBundle.getBundle("en_gb").getString("LOADING"));
}
@Override
public void run()
{
tumblrBackend.tumblrLoadMore();
button.setEnabled(true);
button.setText(java.util.ResourceBundle.getBundle("en_gb").getString("LOAD MORE"));
loaderThread = null;
}
}
private static class checkAtBottomChangeListener implements ChangeListener
{
private final JButton moreButton;
public checkAtBottomChangeListener(JButton moreButton)
{
this.moreButton = moreButton;
}
@Override
public void stateChanged(ChangeEvent e)
{
JViewport source = (JViewport) e.getSource();
int bottomOfCurrentScrollPosition = (int) source.getViewPosition().getY() + (int) source.getExtentSize().getHeight();
int bottomOfCompleteView = source.getView().getHeight();
if ((bottomOfCurrentScrollPosition - bottomOfCompleteView) >= 0)
{
//We have reached the bottom!
moreButton.doClick();
}
}
}
/**
* Starts off the main program by opening an instance with the current
* signed in user's blog posts.
*
* @param args - no arguments accepted currently
*/
@SuppressWarnings("ResultOfObjectAllocationIgnored")
public static void main(String[] args)
{
setGUITheme();
new MainViewGUI(DisplayModes.POSTS, null);
}
private JMenuBar createMenuBar()
{
JMenuBar jMenuBar = new JMenuBar();
modeSelectMenu = new JMenu("...");
modeSelectMenu.setEnabled(false);
ButtonGroup modeSelectGroup = new ButtonGroup();
modeItems = new LinkedHashMap<>();
modeItems.put(DisplayModes.POSTS, new JRadioButtonMenuItem(java.util.ResourceBundle.getBundle("en_gb").getString("POSTS")));
modeItems.put(DisplayModes.LIKES, new JRadioButtonMenuItem(java.util.ResourceBundle.getBundle("en_gb").getString("LIKES")));
modeItems.put(DisplayModes.DASHBOARD, new JRadioButtonMenuItem(java.util.ResourceBundle.getBundle("en_gb").getString("DASHBOARD")));
for (JRadioButtonMenuItem modeItem : modeItems.values())
{
modeSelectGroup.add(modeItem);
modeSelectMenu.add(modeItem);
modeItem.addActionListener(new ModeSelectActionListener());
modeItem.setEnabled(false);
}
avatarIconViewMenuItem = new JMenuItem();
avatarIconViewMenuItem.setEnabled(false);
avatarIconViewMenuItem.setIcon(loadingImageIcon);
avatarIconViewMenuItem.addActionListener(new avatarIconViewMenuItemActionListener());
modeSelectMenu.add(avatarIconViewMenuItem);
followingOrNotMenu = new JMenu("...");
followingOrNotMenu.setEnabled(false);
followBlogMenuItem = new JMenuItem(java.util.ResourceBundle.getBundle("en_gb").getString("FOLLOW"));
unfollowBlogMenuItem = new JMenuItem(java.util.ResourceBundle.getBundle("en_gb").getString("UNFOLLOW"));
followBlogMenuItem.addActionListener(new FollowOrUnfollowMenuItemActionListener());
unfollowBlogMenuItem.addActionListener(new FollowOrUnfollowMenuItemActionListener());
followingOrNotMenu.add(followBlogMenuItem);
followingOrNotMenu.add(unfollowBlogMenuItem);
currentUserOptionsMenu = new JMenu(java.util.ResourceBundle.getBundle("en_gb").getString("GO TO"));
currentUserOptionsMenu.setEnabled(false);
currentUserGoToMenu = new JMenuItem("..."); //Will be replaced by users home blog
currentUserGoToMenu.addActionListener(new currentUserGoToMenuItemActionListener());
currentUserFollowingMenu = new JMenu(java.util.ResourceBundle.getBundle("en_gb").getString("FOLLOWING"));
currentUserFollowingMenu.setEnabled(false);
currentUserFollowersMenu = new JMenu(java.util.ResourceBundle.getBundle("en_gb").getString("FOLLOWERS"));
currentUserFollowersMenu.setEnabled(false);
JMenuItem currentUserEnterBlogNameMenuItem = new JMenuItem(java.util.ResourceBundle.getBundle("en_gb").getString("ENTER BLOG NAME"));
currentUserEnterBlogNameMenuItem.addActionListener(new EnterBlogNameMenuItemActionListener());
currentUserOptionsMenu.add(currentUserGoToMenu);
currentUserOptionsMenu.add(currentUserEnterBlogNameMenuItem);
currentUserOptionsMenu.add(currentUserFollowingMenu);
currentUserOptionsMenu.add(currentUserFollowersMenu);
jMenuBar.add(modeSelectMenu);
jMenuBar.add(currentUserOptionsMenu);
jMenuBar.add(followingOrNotMenu);
(new Thread(new RefreshControls(), "MainViewGUI Refresh Controls")).start();
return jMenuBar;
}
private class RefreshControls implements Runnable
{
@Override
public void run()
{
(new Thread(new SetModeMenuItems(), "Mode Menu Setup")).start();
(new Thread(new SetUpFollowingOrNotMenu(), "Following or Not Menu Setup")).start();
(new Thread(new SetUpCurrentUserOptionsMenu(), "Current User Options Setup")).start();
(new Thread(new SetUpCurrentUserFollowingMenu(), "Following Menu Setup")).start();
(new Thread(new SetUpCurrentUserFollowersMenu(), "Followers Menu Setup")).start();
}
private class SetModeMenuItems implements Runnable
{
@Override
public void run()
{
//Add avatar blog name to end of menu
avatarIconViewMenuItem.setText(tumblrBackend.getCurrentViewingBlog());
avatarIconViewMenuItem.setEnabled(true);
modeSelectMenu.setText(tumblrBackend.getCurrentViewingBlog());
modeSelectMenu.setEnabled(true);
//Update modeMenuItem to whatever we are currently viewing as
JMenuItem toBeSelectedMenuItem = modeItems.get(tumblrBackend.getCurrentDisplayMode());
toBeSelectedMenuItem.setSelected(true);
modeItems.get(DisplayModes.POSTS).setEnabled(true);
if (tumblrBackend.isCurrentUsersBlog())
{
modeItems.get(DisplayModes.DASHBOARD).setEnabled(true);
}
if (tumblrBackend.canViewLikes()) //Put this last as it involves a backend request
{
modeItems.get(DisplayModes.LIKES).setEnabled(true);
}
//Add avatar to mode selection menu
modeSelectMenu.setIcon(tumblrBackend.getAvatar(tumblrBackend.getCurrentViewingBlog()));
//Add avatar to end of menu
avatarIconViewMenuItem.setIcon(tumblrBackend.getAvatar(tumblrBackend.getCurrentViewingBlog(), 64));
}
}
private class SetUpFollowingOrNotMenu implements Runnable
{
@Override
public void run()
{
if (tumblrBackend.isCurrentUsersBlog())
{
followingOrNotMenu.setText(null);
}
else
{
boolean isCurrentlyFollowing = tumblrBackend.isFollowing(tumblrBackend.getCurrentViewingBlog());
followingOrNotMenu.setEnabled(true);
if (isCurrentlyFollowing)
{
followingOrNotMenu.setText("Following");
followBlogMenuItem.setEnabled(false);
unfollowBlogMenuItem.setEnabled(true);
}
else
{
followingOrNotMenu.setText("Follow");
followBlogMenuItem.setEnabled(true);
unfollowBlogMenuItem.setEnabled(false);
}
}
}
}
private class SetUpCurrentUserOptionsMenu implements Runnable
{
@Override
public void run()
{
if (tumblrBackend.isCurrentUsersBlog())
{
currentUserGoToMenu.setEnabled(false);
currentUserGoToMenu.setVisible(false);
}
String currentUsersName = tumblrBackend.getCurrentUsersName();
currentUserGoToMenu.setText(currentUsersName);
currentUserGoToMenu.setIcon(tumblrBackend.getAvatar(currentUsersName, 64));
currentUserOptionsMenu.setEnabled(true);
}
}
private class SetUpCurrentUserFollowingMenu implements Runnable
{
int menuMax = 25;
@Override
public void run()
{
for (String blogName : tumblrBackend.getAllUserFollowing())
{
AddBlogMenuLink addBlogMenuLinkThread = new AddBlogMenuLink(tumblrBackend, blogName, currentUserFollowingMenu, jFrame);
new Thread(addBlogMenuLinkThread).start();
}
currentUserFollowingMenu.setText(currentUserFollowingMenu.getText() + " (" + tumblrBackend.getAllUserFollowing().size() + ")");
currentUserFollowingMenu.setEnabled(true);
}
}
private class SetUpCurrentUserFollowersMenu implements Runnable
{
@Override
public void run()
{
for (String blogName : tumblrBackend.getAllUserFollowers())
{
AddBlogMenuLink addBlogMenuLinkThread = new AddBlogMenuLink(tumblrBackend, blogName, currentUserFollowersMenu, jFrame);
new Thread(addBlogMenuLinkThread).start();
}
currentUserFollowersMenu.setText(currentUserFollowersMenu.getText() + " (" + tumblrBackend.getAllUserFollowers().size() + ")");
currentUserFollowersMenu.setEnabled(true);
}
}
}
private class ModeSelectActionListener implements ActionListener
{
private DisplayModes justSelectedDisplayMode;
@Override
@SuppressWarnings("ResultOfObjectAllocationIgnored")
public void actionPerformed(ActionEvent e)
{
//Find what DisplayMode was selected
for (Entry<DisplayModes, JRadioButtonMenuItem> modeItem : modeItems.entrySet())
{
if (modeItem.getValue() == e.getSource())
{
justSelectedDisplayMode = modeItem.getKey();
}
}
new MainViewGUI(justSelectedDisplayMode, tumblrBackend.getCurrentViewingBlog());
if (SINGLE_VIEW_MODE)
{
jFrame.dispose();
}
}
}
private class FollowOrUnfollowMenuItemActionListener implements ActionListener, Runnable
{
private Thread loaderThread;
private ActionEvent e;
@Override
public void actionPerformed(ActionEvent e)
{
this.e = e;
if (loaderThread != null)
{
return;
}
loaderThread = new Thread((Runnable) this);
loaderThread.start();
}
@Override
public void run()
{
followingOrNotMenu.setEnabled(false);
if (e.getSource() == unfollowBlogMenuItem)
{
followingOrNotMenu.setText("Unfollowing...");
tumblrBackend.unfollowBlog(tumblrBackend.getCurrentViewingBlog());
}
else
{
followingOrNotMenu.setText("Following...");
tumblrBackend.followBlog(tumblrBackend.getCurrentViewingBlog());
}
(new Thread(new RefreshControls(), "MainViewGUI Refresh Controls")).start();
loaderThread = null;
}
}
private class imageLabelMouseAdapter extends MouseAdapter implements Runnable
{
private final PhotoPost photoPost;
private final int photoNumberInPost;
private final MainViewGUI mainViewGUI;
private Thread loaderThread;
public imageLabelMouseAdapter(MainViewGUI mainViewGUI, PhotoPost photoPost, int photoNumberInPost)
{
this.photoPost = photoPost;
this.photoNumberInPost = photoNumberInPost;
this.mainViewGUI = mainViewGUI;
}
@Override
public void mousePressed(MouseEvent e)
{
if (loaderThread != null)
{
return;
}
loaderThread = new Thread((Runnable) this);
loaderThread.start();
}
@Override
public void run()
{
PhotoPostViewer photoPostViewer = new PhotoPostViewer(mainViewGUI, tumblrBackend, photoPost, photoNumberInPost);
loaderThread = null;
}
}
private class currentUserGoToMenuItemActionListener implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e)
{
JComponent sourceButton = (JComponent) e.getSource();
sourceButton.setEnabled(false);
MainViewGUI mainViewGUI = new MainViewGUI(DisplayModes.POSTS, tumblrBackend.getCurrentUsersName());
if (SINGLE_VIEW_MODE)
{
jFrame.dispose();
}
}
}
private class avatarIconViewMenuItemActionListener implements ActionListener, Runnable
{
private Thread loaderThread;
@Override
public void actionPerformed(ActionEvent e)
{
if (loaderThread != null)
{
return;
}
loaderThread = new Thread((Runnable) this);
loaderThread.start();
}
@Override
public void run()
{
final JFrame frame = new JFrame("Avatar viewer");
JLabel jLabel = new JLabel();
jLabel.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
frame.dispose();
}
});
jLabel.setIcon(tumblrBackend.getAvatar(tumblrBackend.getCurrentViewingBlog(), 512));
frame.add(jLabel);
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
loaderThread = null;
}
}
private class EnterBlogNameMenuItemActionListener implements ActionListener
{
String customBlogName;
@Override
@SuppressWarnings("ResultOfObjectAllocationIgnored")
public void actionPerformed(ActionEvent e)
{
customBlogName = JOptionPane.showInputDialog(jFrame, "Enter the name of the Tumblr blog you wish to view:", java.util.ResourceBundle.getBundle("en_gb").getString("ENTER BLOG NAME"), JOptionPane.QUESTION_MESSAGE);
if (customBlogName == null || customBlogName.isEmpty())
{
return;
}
if (!tumblrBackend.blogExists(customBlogName))
{
JOptionPane.showMessageDialog(jFrame, "Blog \"" + customBlogName + "\" does not exist!", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
new MainViewGUI(DisplayModes.POSTS, customBlogName);
if (SINGLE_VIEW_MODE)
{
jFrame.dispose();
}
}
}
}