/* GanttProject is an opensource project management tool. License: GPL3 Copyright (C) 2011 Dmitry Barashev, GanttProject team 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.sourceforge.ganttproject; import biz.ganttproject.core.option.*; import biz.ganttproject.core.option.FontSpec.Size; import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import net.sourceforge.ganttproject.action.zoom.ZoomActionSet; import net.sourceforge.ganttproject.chart.Chart; import net.sourceforge.ganttproject.chart.GanttChart; import net.sourceforge.ganttproject.chart.TimelineChart; import net.sourceforge.ganttproject.document.Document.DocumentException; import net.sourceforge.ganttproject.gui.*; import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder; import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder.I18N; import net.sourceforge.ganttproject.gui.options.SettingsDialog2; import net.sourceforge.ganttproject.gui.options.model.GP1XOptionConverter; import net.sourceforge.ganttproject.gui.scrolling.ScrollingManager; import net.sourceforge.ganttproject.gui.scrolling.ScrollingManagerImpl; import net.sourceforge.ganttproject.gui.zoom.ZoomManager; import net.sourceforge.ganttproject.language.GanttLanguage; import net.sourceforge.ganttproject.language.LanguageOption; import net.sourceforge.ganttproject.language.ShortDateFormatOption; import net.sourceforge.ganttproject.task.TaskManager; import net.sourceforge.ganttproject.task.TaskSelectionManager; import net.sourceforge.ganttproject.task.TaskView; import net.sourceforge.ganttproject.undo.GPUndoManager; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.ProgressProvider; import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkEvent.EventType; import javax.swing.event.HyperlinkListener; import javax.swing.plaf.FontUIResource; import java.awt.*; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; import java.util.List; import java.util.logging.Level; class UIFacadeImpl extends ProgressProvider implements UIFacade { private final JFrame myMainFrame; private final ScrollingManager myScrollingManager; private final ZoomManager myZoomManager; private final GanttStatusBar myStatusBar; private final UIFacade myFallbackDelegate; private final TaskSelectionManager myTaskSelectionManager; private final List<GPOptionGroup> myOptionGroups = Lists.newArrayList(); private final GPOptionGroup myOptions; private final LafOption myLafOption; private final GPOptionGroup myLogoOptions; private final DefaultFileOption myLogoOption; private final NotificationManagerImpl myNotificationManager; private final TaskView myTaskView = new TaskView(); private final DialogBuilder myDialogBuilder; private final Map<String, Font> myOriginalFonts = Maps.newHashMap(); private final List<Runnable> myOnUpdateComponentTreeUiCallbacks = Lists.newArrayList(); private float myLastScale = 0; private static Map<FontSpec.Size, String> getSizeLabels() { Map<FontSpec.Size, String> result = Maps.newHashMap(); for (FontSpec.Size size : FontSpec.Size.values()) { result.put(size, GanttLanguage.getInstance().getText("optionValue.ui.appFontSpec." + size.toString() + ".label")); } return result; } private final DefaultFontOption myAppFontOption = new DefaultFontOption( "appFontSpec", null, Arrays.asList(getFontFamilies())) { @Override public Map<FontSpec.Size, String> getSizeLabels() { return UIFacadeImpl.getSizeLabels(); } }; private final DefaultFontOption myChartFontOption = new DefaultFontOption( "chartFontSpec", new FontSpec("Dialog", FontSpec.Size.NORMAL), Arrays.asList(getFontFamilies())) { @Override public Map<Size, String> getSizeLabels() { return UIFacadeImpl.getSizeLabels(); } }; private final DefaultIntegerOption myDpiOption = new DefaultIntegerOption("screenDpi", DEFAULT_DPI); @Override public IntegerOption getDpiOption() { return myDpiOption; } public GPOption<String> getLafOption() { return myLafOption; } private ChangeValueListener myAppFontValueListener; private final LanguageOption myLanguageOption; private final IGanttProject myProject; private FontSpec myLastFontSpec; UIFacadeImpl(JFrame mainFrame, GanttStatusBar statusBar, NotificationManagerImpl notificationManager, final IGanttProject project, UIFacade fallbackDelegate) { myMainFrame = mainFrame; myProject = project; myDialogBuilder = new DialogBuilder(mainFrame); myScrollingManager = new ScrollingManagerImpl(); myZoomManager = new ZoomManager(project.getTimeUnitStack()); myStatusBar = statusBar; myStatusBar.setNotificationManager(notificationManager); myFallbackDelegate = fallbackDelegate; Job.getJobManager().setProgressProvider(this); myTaskSelectionManager = new TaskSelectionManager(Suppliers.memoize(new Supplier<TaskManager>() { public TaskManager get() { return project.getTaskManager(); } })); myNotificationManager = notificationManager; myLafOption = new LafOption(this); final ShortDateFormatOption shortDateFormatOption = new ShortDateFormatOption(); final DefaultStringOption dateSampleOption = new DefaultStringOption("ui.dateFormat.sample"); dateSampleOption.setWritable(false); final DefaultBooleanOption dateFormatSwitchOption = new DefaultBooleanOption("ui.dateFormat.switch", true); myLanguageOption = new LanguageOption() { { GanttLanguage.getInstance().addListener(new GanttLanguage.Listener() { @Override public void languageChanged(GanttLanguage.Event event) { Locale selected = getSelectedValue(); reloadValues(GanttLanguage.getInstance().getAvailableLocales()); setSelectedValue(selected); } }); } @Override protected void applyLocale(Locale locale) { if (locale == null) { // Selected Locale was not available, so use default Locale locale = Locale.getDefault(); } GanttLanguage.getInstance().setLocale(locale); } }; myLanguageOption.addChangeValueListener(new ChangeValueListener() { @Override public void changeValue(ChangeValueEvent event) { // Language changed... if (dateFormatSwitchOption.isChecked()) { // ... update default date format option Locale selected = myLanguageOption.getSelectedValue(); shortDateFormatOption.setSelectedLocale(selected); } } }); dateFormatSwitchOption.addChangeValueListener(new ChangeValueListener() { private String customFormat; @Override public void changeValue(ChangeValueEvent event) { shortDateFormatOption.setWritable(!dateFormatSwitchOption.isChecked()); if (dateFormatSwitchOption.isChecked()) { customFormat = shortDateFormatOption.getValue(); // Update to default date format Locale selected = myLanguageOption.getSelectedValue(); shortDateFormatOption.setSelectedLocale(selected); dateSampleOption.setValue(shortDateFormatOption.formatDate(new Date())); } else if (customFormat != null) { shortDateFormatOption.setValue(customFormat); } } }); shortDateFormatOption.addChangeValueListener(new ChangeValueListener() { @Override public void changeValue(ChangeValueEvent event) { // Update date sample dateSampleOption.setValue(shortDateFormatOption.formatDate(new Date())); } }); // myFontSizeOption = new DefaultIntegerOption("ui.appFontSize"); // myFontSizeOption.setHasUi(false); GPOption[] options = new GPOption[] { myLafOption, myAppFontOption, myChartFontOption, myDpiOption, myLanguageOption, dateFormatSwitchOption, shortDateFormatOption, dateSampleOption }; myOptions = new GPOptionGroup("ui", options); I18N i18n = new OptionsPageBuilder.I18N(); myOptions.setI18Nkey(i18n.getCanonicalOptionLabelKey(myLafOption), "looknfeel"); myOptions.setI18Nkey(i18n.getCanonicalOptionLabelKey(myLanguageOption), "language"); myOptions.setTitled(false); myLogoOption = new DefaultFileOption("ui.logo"); myLogoOptions = new GPOptionGroup("ui2", myLogoOption); myLogoOptions.setTitled(false); addOptions(myOptions); addOptions(myLogoOptions); } private String[] getFontFamilies() { return GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); } @Override public ScrollingManager getScrollingManager() { return myScrollingManager; } @Override public ZoomManager getZoomManager() { return myZoomManager; } @Override public GPUndoManager getUndoManager() { return myFallbackDelegate.getUndoManager(); } @Override public ZoomActionSet getZoomActionSet() { return myFallbackDelegate.getZoomActionSet(); } @Override public Choice showConfirmationDialog(String message, String title) { String yes = GanttLanguage.getInstance().getText("yes"); String no = GanttLanguage.getInstance().getText("no"); String cancel = GanttLanguage.getInstance().getText("cancel"); int result = JOptionPane.showOptionDialog(myMainFrame, message, title, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[] { yes, no, cancel }, yes); switch (result) { case JOptionPane.YES_OPTION: return Choice.YES; case JOptionPane.NO_OPTION: return Choice.NO; case JOptionPane.CANCEL_OPTION: return Choice.CANCEL; case JOptionPane.CLOSED_OPTION: return Choice.CANCEL; default: return Choice.CANCEL; } } @Override public void showPopupMenu(Component invoker, Action[] actions, int x, int y) { showPopupMenu(invoker, Arrays.asList(actions), x, y); } @Override public void showPopupMenu(Component invoker, Collection<Action> actions, int x, int y) { JPopupMenu menu = new JPopupMenu(); for (Action action : actions) { if (action == null) { menu.addSeparator(); } else { Boolean isSelected = (Boolean)action.getValue(Action.SELECTED_KEY); if (isSelected == null) { menu.add(action); } else { menu.add(new JCheckBoxMenuItem(action)); } } } menu.applyComponentOrientation(getLanguage().getComponentOrientation()); menu.show(invoker, x, y); } @Override public Dialog createDialog(Component content, Action[] buttonActions, String title) { return myDialogBuilder.createDialog(content, buttonActions, title, myNotificationManager); } @Override public void setStatusText(String text) { myStatusBar.setFirstText(text, 2000); } @Override public void showOptionDialog(int messageType, String message, Action[] actions) { JOptionPane optionPane = new JOptionPane(message, messageType); Object[] options = new Object[actions.length]; Object defaultOption = null; for (int i = 0; i < actions.length; i++) { options[i] = actions[i].getValue(Action.NAME); if (actions[i].getValue(Action.DEFAULT) != null) { defaultOption = options[i]; } } optionPane.setOptions(options); if (defaultOption != null) { optionPane.setInitialValue(defaultOption); } JDialog dialog = optionPane.createDialog(myMainFrame, ""); dialog.setVisible(true); Object choice = optionPane.getValue(); for (Action a : actions) { if (a.getValue(Action.NAME).equals(choice)) { a.actionPerformed(null); break; } } } @Override public NotificationManager getNotificationManager() { return myNotificationManager; } /** Show and log the exception */ @Override public void showErrorDialog(Throwable e) { GPLogger.logToLogger(e); showNotificationDialog(NotificationChannel.ERROR, buildMessage(e)); } private static String buildMessage(Throwable e) { StringBuilder result = new StringBuilder(); String lastMessage = null; while (e != null) { if (e.getMessage() != null && !Objects.equal(lastMessage, e.getMessage())) { result.append(e.getMessage()).append("<br>"); lastMessage = e.getMessage(); } e = e.getCause(); } return result.toString(); } @Override public void showErrorDialog(String errorMessage) { GPLogger.log(errorMessage); showNotificationDialog(NotificationChannel.ERROR, errorMessage); } @Override public void showNotificationDialog(NotificationChannel channel, String message) { String i18nPrefix = channel.name().toLowerCase() + ".channel."; getNotificationManager().addNotifications( channel, Collections.singletonList(new NotificationItem(i18n(i18nPrefix + "itemTitle"), GanttLanguage.getInstance().formatText(i18nPrefix + "itemBody", message), new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() != EventType.ACTIVATED) { return; } if ("localhost".equals(e.getURL().getHost()) && "/log".equals(e.getURL().getPath())) { onViewLog(); } else { NotificationManager.DEFAULT_HYPERLINK_LISTENER.hyperlinkUpdate(e); } } }))); } @Override public void showSettingsDialog(String pageID) { SettingsDialog2 dialog = new SettingsDialog2(myProject, this, "settings.app.pageOrder"); dialog.show(pageID); } protected void onViewLog() { ViewLogDialog.show(this); } private static String i18n(String key) { return GanttLanguage.getInstance().getText(key); } void resetErrorLog() { } @Override public GanttChart getGanttChart() { return myFallbackDelegate.getGanttChart(); } @Override public TimelineChart getResourceChart() { return myFallbackDelegate.getResourceChart(); } @Override public Chart getActiveChart() { return myFallbackDelegate.getActiveChart(); } @Override public int getViewIndex() { return myFallbackDelegate.getViewIndex(); } @Override public void setViewIndex(int viewIndex) { myFallbackDelegate.setViewIndex(viewIndex); } @Override public int getGanttDividerLocation() { return myFallbackDelegate.getGanttDividerLocation(); } @Override public void setGanttDividerLocation(int location) { myFallbackDelegate.setGanttDividerLocation(location); } @Override public int getResourceDividerLocation() { return myFallbackDelegate.getResourceDividerLocation(); } @Override public void setResourceDividerLocation(int location) { myFallbackDelegate.setResourceDividerLocation(location); } @Override public void refresh() { myFallbackDelegate.refresh(); } @Override public Frame getMainFrame() { return myMainFrame; } private static GanttLanguage getLanguage() { return GanttLanguage.getInstance(); } static String getExceptionReport(Throwable e) { StringBuffer result = new StringBuffer(); result.append(e.getMessage()); if (e instanceof DocumentException == false) { result.append("\n\n"); StringWriter stringWriter = new StringWriter(); PrintWriter writer = new PrintWriter(stringWriter); e.printStackTrace(writer); writer.close(); result.append(stringWriter.getBuffer().toString()); } return result.toString(); } @Override public void setWorkbenchTitle(String title) { myMainFrame.setTitle(title); } @Override public IProgressMonitor createMonitor(Job job) { return myStatusBar.createProgressMonitor(); } @Override public IProgressMonitor createProgressGroup() { return myStatusBar.createProgressMonitor(); } @Override public IProgressMonitor createMonitor(Job job, IProgressMonitor group, int ticks) { return group; } @Override public IProgressMonitor getDefaultMonitor() { return null; } @Override public TaskView getCurrentTaskView() { return myTaskView; } @Override public TaskTreeUIFacade getTaskTree() { return myFallbackDelegate.getTaskTree(); } @Override public ResourceTreeUIFacade getResourceTree() { return myFallbackDelegate.getResourceTree(); } @Override public TaskSelectionContext getTaskSelectionContext() { return myTaskSelectionManager; } @Override public TaskSelectionManager getTaskSelectionManager() { return myTaskSelectionManager; } @Override public GanttLookAndFeelInfo getLookAndFeel() { return myLafOption.getLookAndFeel(); } @Override public void setLookAndFeel(final GanttLookAndFeelInfo laf) { if (laf == null) { return; } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (!doSetLookAndFeel(laf)) { doSetLookAndFeel(GanttLookAndFeels.getGanttLookAndFeels().getDefaultInfo()); } if (myAppFontValueListener == null) { myAppFontValueListener = new ChangeValueListener() { @Override public void changeValue(ChangeValueEvent event) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateFonts(); updateComponentTreeUI(); } }); } }; myAppFontOption.addChangeValueListener(myAppFontValueListener); myDpiOption.addChangeValueListener(new ChangeValueListener() { @Override public void changeValue(ChangeValueEvent event) { if (myDpiOption.getValue() >= UIFacade.DEFAULT_DPI) { updateFonts(); } } }); } } }); } private boolean doSetLookAndFeel(GanttLookAndFeelInfo laf) { try { UIManager.setLookAndFeel(laf.getClassName()); updateFonts(); updateComponentTreeUI(); return true; } catch (Exception e) { GPLogger.getLogger(UIFacade.class).log(Level.SEVERE, "Can't find the LookAndFeel\n" + laf.getClassName() + "\n" + laf.getName(), e); return false; } } private void updateComponentTreeUI() { SwingUtilities.updateComponentTreeUI(myMainFrame); //myMainFrame.pack(); SwingUtilities.invokeLater(new Runnable() { public void run() { for (Runnable r : myOnUpdateComponentTreeUiCallbacks) { r.run(); } getGanttChart().reset(); getResourceChart().reset(); } }); } private void updateFonts() { if (myOriginalFonts.isEmpty()) { UIDefaults defaults = UIManager.getDefaults(); for (Enumeration<Object> keys = defaults.keys(); keys.hasMoreElements();) { String key = String.valueOf(keys.nextElement()); Object obj = UIManager.get(key); if (obj instanceof Font) { Font f = (Font) obj; myOriginalFonts.put(key, f); } } } FontSpec currentSpec = myAppFontOption.getValue(); float dpiScale = myDpiOption.getValue().floatValue() / DEFAULT_DPI; if (currentSpec != null && (!currentSpec.equals(myLastFontSpec) || dpiScale != myLastScale)) { for (Map.Entry<String, Font> font : myOriginalFonts.entrySet()) { float newSize = (font.getValue().getSize() * currentSpec.getSize().getFactor() * dpiScale); Font newFont; if (Strings.isNullOrEmpty(currentSpec.getFamily())) { newFont = font.getValue().deriveFont(newSize); } else { newFont = new FontUIResource(currentSpec.getFamily(), font.getValue().getStyle(), (int)newSize); } UIManager.put(font.getKey(), newFont); } myLastFontSpec = currentSpec; myLastScale = dpiScale; } } static class LafOption extends DefaultEnumerationOption<GanttLookAndFeelInfo> implements GP1XOptionConverter { private final UIFacade myUiFacade; LafOption(UIFacade uiFacade) { super("laf", GanttLookAndFeels.getGanttLookAndFeels().getInstalledLookAndFeels()); myUiFacade = uiFacade; if (!System.getProperty("os.name").toLowerCase().contains("os x")) { setValue("Plastic"); } } public GanttLookAndFeelInfo getLookAndFeel() { return GanttLookAndFeels.getGanttLookAndFeels().getInfoByName(getValue()); } @Override protected String objectToString(GanttLookAndFeelInfo laf) { return laf.getName(); } @Override public void commit() { super.commit(); myUiFacade.setLookAndFeel(GanttLookAndFeels.getGanttLookAndFeels().getInfoByName(getValue())); } @Override public String getTagName() { return "looknfeel"; } @Override public String getAttributeName() { return "name"; } @Override public void loadValue(String legacyValue) { resetValue(legacyValue, true); myUiFacade.setLookAndFeel(GanttLookAndFeels.getGanttLookAndFeels().getInfoByName(legacyValue)); } } public DefaultEnumerationOption<Locale> getLanguageOption() { return myLanguageOption; } @Override public GPOptionGroup[] getOptions() { return myOptionGroups.toArray(new GPOptionGroup[myOptionGroups.size()]); } @Override public void addOnUpdateComponentTreeUi(Runnable callback) { myOnUpdateComponentTreeUiCallbacks.add(callback); } @Override public Image getLogo() { if (myLogoOption.getValue() == null) { return DEFAULT_LOGO.getImage(); } try { File imageFile = new File(myLogoOption.getValue()); if (imageFile.exists() && imageFile.canRead()) { return Objects.firstNonNull(ImageIO.read(imageFile), DEFAULT_LOGO.getImage()); } GPLogger.logToLogger("File=" + myLogoOption.getValue() + " does not exist or is not readable"); } catch (IOException e) { GPLogger.logToLogger(e); } return DEFAULT_LOGO.getImage(); } void addOptions(GPOptionGroup options) { myOptionGroups.add(options); } FontOption getChartFontOption() { return myChartFontOption; } FontOption getAppFontOption() { return myAppFontOption; } }