package net.frontlinesms.ui.settings;
import java.util.ArrayList;
import java.util.List;
import net.frontlinesms.FrontlineSMSConstants;
import net.frontlinesms.FrontlineUtils;
import net.frontlinesms.data.domain.SmsInternetServiceSettings;
import net.frontlinesms.events.EventBus;
import net.frontlinesms.events.EventObserver;
import net.frontlinesms.events.FrontlineEventNotification;
import net.frontlinesms.messaging.sms.internet.SmsInternetService;
import net.frontlinesms.plugins.PluginController;
import net.frontlinesms.plugins.PluginProperties;
import net.frontlinesms.plugins.PluginSettingsController;
import net.frontlinesms.settings.FrontlineValidationMessage;
import net.frontlinesms.ui.ThinletUiEventHandler;
import net.frontlinesms.ui.UiDestroyEvent;
import net.frontlinesms.ui.UiGeneratorController;
import net.frontlinesms.ui.handler.settings.SettingsAppearanceSectionHandler;
import net.frontlinesms.ui.handler.settings.SettingsGeneralSectionHandler;
import net.frontlinesms.ui.handler.settings.SettingsServicesSectionHandler;
import net.frontlinesms.ui.i18n.InternationalisationUtils;
import org.apache.log4j.Logger;
/**
* Ui Handler for {@link FrontlineSettingsHandler} settings.
* The whole settings dialog system is handled by this class.
*
* @author Morgan Belkadi <morgan@frontlinesms.com>
*/
public class FrontlineSettingsHandler implements ThinletUiEventHandler, EventObserver {
//> CONSTANTS
/** Path to XML for UI layout for settings screen, {@link #settingsDialog} */
private static final String UI_SETTINGS = "/ui/core/settings/dgFrontlineSettings.xml";
/** Logging object */
private static final Logger LOG = FrontlineUtils.getLogger(FrontlineSettingsHandler.class);
private static final String UI_COMPONENT_CORE_TREE = "generalTree";
private static final String UI_COMPONENT_PLUGIN_TREE = "pluginTree";
private static final String UI_COMPONENT_PN_DISPLAY_SETTINGS = "pnDisplaySettings";
private static final String UI_COMPONENT_BT_SAVE = "btSave";
private static final String UI_COMPONENT_BT_CLOSE = "btClose";
private static final String I18N_MESSAGE_CONFIRM_CLOSE_SETTINGS = "message.confirm.close.settings";
private static final String I18N_SETTINGS_SAVED = "settings.saved";
private static final String I18N_TOOLTIP_SETTINGS_BTSAVE_DISABLED = "tooltip.settings.btsave.disabled";
private static final String I18N_TOOLTIP_SETTINGS_SAVES_ALL = "tooltip.settings.saves.all";
//> INSTANCE PROPERTIES
/** Thinlet instance that owns this handler */
private final UiGeneratorController uiController;
/** dialog for editing {@link SmsInternetService} settings, {@link SmsInternetServiceSettings} instances */
private Object settingsDialog;
private EventBus eventBus;
private List<UiSettingsSectionHandler> handlersList;
private List<String> changesList;
//> CONSTRUCTORS
/**
* Creates a new instance of this UI.
* @param controller thinlet controller that owns this {@link FrontlineSettingsHandler}.
*/
public FrontlineSettingsHandler(UiGeneratorController controller) {
this.uiController = controller;
this.eventBus = controller.getFrontlineController().getEventBus();
this.handlersList = new ArrayList<UiSettingsSectionHandler>();
this.changesList = new ArrayList<String>();
this.init();
}
/**
* Shows the general confirmation dialog (for removal).
* @param methodToBeCalled the method to be called if the confirmation is affirmative
*/
public void showConfirmationDialog(String methodToBeCalled){
uiController.showConfirmationDialog(methodToBeCalled, this);
}
private void init() {
LOG.trace("Initializing Frontline Settings");
this.eventBus.registerObserver(this);
settingsDialog = uiController.loadComponentFromFile(UI_SETTINGS, this);
this.loadCoreSettings();
this.loadPluginSettings();
}
private void loadCoreSettings() {
Object coreTree = find(UI_COMPONENT_CORE_TREE);
/** APPEARANCE **/
this.loadCoreSection(coreTree, new SettingsAppearanceSectionHandler(this.uiController));
/** GENERAL **/
this.loadCoreSection(coreTree, new SettingsGeneralSectionHandler(this.uiController));
/** SERVICES **/
this.loadCoreSection(coreTree, new SettingsServicesSectionHandler(this.uiController));
}
/**
* Loads a section for the core settings.
* @param coreTree
* @param handler
*/
private void loadCoreSection(Object coreTree, UiSettingsSectionHandler handler) {
Object rootNode = handler.getSectionNode();
this.uiController.add(coreTree, rootNode);
if (handler instanceof SettingsAppearanceSectionHandler) {
this.uiController.setSelectedItem(coreTree, rootNode);
this.selectionChanged(coreTree);
}
}
/**
* Loads the different plugins into the plugins tree
*/
private void loadPluginSettings() {
for(Class<? extends PluginController> pluginClass : PluginProperties.getInstance().getPluginClasses()) {
PluginSettingsController pluginSettingsController = null;
try {
PluginController pluginController = this.uiController.getFrontlineController().getPluginManager().loadPluginController(pluginClass.getName());
this.uiController.addPluginTextResources(pluginController);
pluginSettingsController = pluginController.getSettingsController(this.uiController);
if (pluginSettingsController != null) { // Then the Plugin has settings
Object pluginRootNode = pluginSettingsController.getRootNode();
// Collapse all root nodes by default
this.uiController.setExpanded(pluginRootNode, false);
this.uiController.add(find(UI_COMPONENT_PLUGIN_TREE), pluginRootNode);
}
} catch (Throwable t) {
LOG.warn("Error when trying to load settings for Plugin " + pluginClass.getSimpleName(), t);
}
}
}
/**
* Called when the selection changed in one of the two trees
* @param tree
*/
public void selectionChanged(Object tree) {
Object selected = this.uiController.getSelectedItem(tree);
Object attachedObject = this.uiController.getAttachedObject(selected);
this.displayPanel((UiSettingsSectionHandler) attachedObject);
}
/**
* Handles the display in the dialog
* @param handler The {@link UiSettingsSectionHandler} responsible of the panel which should be displayed.
*/
private void displayPanel(UiSettingsSectionHandler handler) {
Object pnDisplaySettings = find(UI_COMPONENT_PN_DISPLAY_SETTINGS);
this.uiController.removeAll(pnDisplaySettings);
this.uiController.add(pnDisplaySettings, handler.getPanel());
if (!this.handlersList.contains(handler)) {
this.handlersList.add(handler);
}
}
public void closeDialog() {
if (this.changesList.isEmpty()) {
removeDialog();
} else {
this.uiController.showConfirmationDialog("removeDialog", this, I18N_MESSAGE_CONFIRM_CLOSE_SETTINGS);
}
}
/** Shows this dialog to the user. */
public Object getDialog() {
return settingsDialog;
}
/**
* Removes the provided component from the view.
*/
public void removeDialog() {
this.uiController.remove(this.settingsDialog);
this.uiController.removeConfirmationDialog();
}
private Object find (String componentName) {
return this.uiController.find(settingsDialog, componentName);
}
public void save () {
List<Object> validationMessages = new ArrayList<Object>();
for (UiSettingsSectionHandler settingsSectionHandler : this.handlersList) {
List<FrontlineValidationMessage> validation = settingsSectionHandler.validateFields();
if (validation != null && !validation.isEmpty()) {
for (FrontlineValidationMessage validationMessage : validation) {
//validationMessages.add("[" + settingsSectionHandler.getTitle() + "] " + validationMessage.getLocalisedMessage());
validationMessages.add(createValidationPanel(settingsSectionHandler, validationMessage));
}
}
}
if (validationMessages.isEmpty()) {
this.doSave();
} else {
this.uiController.alert(validationMessages.toArray(new Object[0]));
}
}
private Object createValidationPanel(UiSettingsSectionHandler settingsSectionHandler, FrontlineValidationMessage validationMessage) {
Object panel = this.uiController.createPanel("Validation message");
this.uiController.setGap(panel, 5);
Object sectionNameLabel = this.uiController.createLabel(settingsSectionHandler.getTitle(), validationMessage.getIcon());
this.uiController.setBold(sectionNameLabel);
this.uiController.add(panel, sectionNameLabel);
this.uiController.add(panel, this.uiController.createLabel(validationMessage.getLocalisedMessage()));
return panel;
}
private void doSave() {
for (UiSettingsSectionHandler settingsSectionHandler : this.handlersList) {
settingsSectionHandler.save();
}
this.uiController.removeDialog(settingsDialog);
this.uiController.infoMessage(InternationalisationUtils.getI18nString(I18N_SETTINGS_SAVED));
}
//> INSTANCE HELPER METHODS
//> STATIC FACTORIES
//> STATIC HELPER METHODS
public void notify(FrontlineEventNotification notification) {
if (notification instanceof SettingsChangedEventNotification) {
SettingsChangedEventNotification settingsNotification = (SettingsChangedEventNotification) notification;
String sectionItem = settingsNotification.getSectionItem();
if (settingsNotification.isUnchange()) {
// A previous change has been cancelled, let's remove it from our list
this.changesList.remove(sectionItem);
} else {
// This is an actual change, let's add it to our list if it's a new change
if (!this.changesList.contains(sectionItem)) {
this.changesList.add(sectionItem);
}
}
boolean changesToSave = !this.changesList.isEmpty();
this.handleCloseButton(changesToSave);
this.handleSaveButton(changesToSave);
} else if (notification instanceof UiDestroyEvent) {
if(((UiDestroyEvent) notification).isFor(this.uiController)) {
this.uiController.getFrontlineController().getEventBus().unregisterObserver(this);
}
}
}
private void handleCloseButton(boolean changesToSave) {
Object btClose = find(UI_COMPONENT_BT_CLOSE);
uiController.setText(btClose, InternationalisationUtils.getI18nString(
changesToSave ? FrontlineSMSConstants.ACTION_CANCEL : FrontlineSMSConstants.ACTION_CLOSE));
}
private void handleSaveButton(boolean shouldEnableSaveButton) {
// If our list of changes is empty, this means we went back to the original configuration
Object btSave = find(UI_COMPONENT_BT_SAVE);
this.uiController.setEnabled(btSave, shouldEnableSaveButton);
String tooltip;
if (shouldEnableSaveButton) {
tooltip = InternationalisationUtils.getI18nString(I18N_TOOLTIP_SETTINGS_SAVES_ALL);
} else {
tooltip = InternationalisationUtils.getI18nString(I18N_TOOLTIP_SETTINGS_BTSAVE_DISABLED);
}
this.uiController.setTooltip(btSave, tooltip);
}
//> CONSTANT HANDLERS
}