/*
* FrontlineSMS <http://www.frontlinesms.com>
* Copyright 2007, 2008 kiwanja
*
* This file is part of FrontlineSMS.
*
* FrontlineSMS is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* FrontlineSMS 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 Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FrontlineSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package net.frontlinesms.ui;
import java.awt.Font;
import java.awt.Frame;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import net.frontlinesms.*;
import net.frontlinesms.data.*;
import net.frontlinesms.data.domain.*;
import net.frontlinesms.data.domain.FrontlineMessage.Status;
import net.frontlinesms.data.domain.FrontlineMessage.Type;
import net.frontlinesms.data.events.EntitySavedNotification;
import net.frontlinesms.data.repository.*;
import net.frontlinesms.debug.RandomDataGenerator;
import net.frontlinesms.email.EmailException;
import net.frontlinesms.events.*;
import net.frontlinesms.listener.EmailListener;
import net.frontlinesms.listener.UIListener;
import net.frontlinesms.messaging.mms.email.MmsEmailServiceStatus;
import net.frontlinesms.messaging.mms.events.MmsServiceStatusNotification;
import net.frontlinesms.messaging.sms.SmsService;
import net.frontlinesms.messaging.sms.SmsServiceManager;
import net.frontlinesms.messaging.sms.events.InternetServiceEventNotification;
import net.frontlinesms.messaging.sms.events.NoSmsServicesConnectedNotification;
import net.frontlinesms.messaging.sms.internet.SmsInternetService;
import net.frontlinesms.plugins.*;
import net.frontlinesms.resources.ResourceUtils;
import net.frontlinesms.ui.events.FrontlineUiUpateJob;
import net.frontlinesms.ui.events.TabChangedNotification;
import net.frontlinesms.ui.handler.*;
import net.frontlinesms.ui.handler.contacts.*;
import net.frontlinesms.ui.handler.core.DatabaseSettingsPanel;
import net.frontlinesms.ui.handler.core.DateSelecter;
import net.frontlinesms.ui.handler.email.*;
import net.frontlinesms.ui.handler.help.AboutDialog;
import net.frontlinesms.ui.handler.help.ContributeDialog;
import net.frontlinesms.ui.handler.importexport.ExportDialogHandlerFactory;
import net.frontlinesms.ui.handler.importexport.ImportDialogHandlerFactory;
import net.frontlinesms.ui.handler.keyword.KeywordTabHandler;
import net.frontlinesms.ui.handler.message.*;
import net.frontlinesms.ui.handler.mms.MmsSettingsDialogHandler;
import net.frontlinesms.ui.handler.phones.NoPhonesDetectedDialogHandler;
import net.frontlinesms.ui.handler.phones.PhoneTabHandler;
import net.frontlinesms.ui.handler.settings.SmsInternetServiceSettingsHandler;
import net.frontlinesms.ui.i18n.*;
import net.frontlinesms.ui.settings.FrontlineSettingsHandler;
import org.apache.log4j.Logger;
import org.smslib.CIncomingMessage;
import thinlet.FrameLauncher;
import thinlet.Thinlet;
// FIXME should not be using static imports
import static net.frontlinesms.FrontlineSMSConstants.*;
import static net.frontlinesms.ui.UiGeneratorControllerConstants.*;
/**
* Class containing control methods for the Thinlet-driven GUI.
*
* The public (void) methods in this class are called by reflection via the Thinlet class.
*
* Employed within are a selection of different methods for essentially getting the same
* thing done, e.g. caching of components at class level vs. searching every time they
* are to be used. This is because design methods have changed throughout the development
* of this class. Currently searching for components as and when they are needed is
* favoured, and so this should be done where possible.
*
* We're now in the process of separating this class into smaller classes which control separate,
* modular parts of the UI, e.g. the {@link HomeTabHandler}.
*
* @author Alex Anderson <alex@frontlinesms.com>
* @author Carlos Eduardo Genz kadu(at)masabi(dot)com
* @author Morgan Belkadi <morgan@frontlinesms.com>
*/
@SuppressWarnings("serial")
public class UiGeneratorController extends FrontlineUI implements EmailListener, UIListener, SingleGroupSelecterPanelOwner, EventObserver {
//> CONSTANTS
/** Default height of the Thinlet frame launcher */
public static final int DEFAULT_HEIGHT = 768;
/** Default width of the Thinlet frame launcher */
public static final int DEFAULT_WIDTH = 1024;
private static final String I18N_CONFIRM_EXIT = "message.confirm.exit";
private static final String I18N_ACTIVE_CONNECTIONS = "connections.active.connections.statusbar";
//> INSTANCE PROPERTIES
/** Logging object */
public Logger LOG = FrontlineUtils.getLogger(UiGeneratorController.class);
/** The {@link FrontlineSMS} instance that this UI is attached to. */
private FrontlineSMS frontlineController;
/** The INTERNAL NAME of the tab (a thinlet UI component) currently active */
private String currentTab;
/** The manager of {@link SmsService}s */
private final SmsServiceManager phoneManager;
/** Manager of {@link PluginController}s */
private final PluginManager pluginManager;
/** Data Access Object for {@link Contact}s */
private final ContactDao contactDao;
/** Data Access Object for {@link Group}s */
private final GroupDao groupDao;
/** Data Access Object for {@link GroupMembership}s */
private final GroupMembershipDao groupMembershipDao;
/** Data Access Object for {@link FrontlineMessage}s */
private final MessageDao messageFactory;
/** Data Access Object for {@link SmsModemSettings}s */
private final SmsModemSettingsDao phoneDetailsManager;
/** Controller of the home tab. */
private final HomeTabHandler homeTabController;
/** Controller of the phones tab. */
private final PhoneTabHandler phoneTabController;
/** Controller of the contacts tab. */
private final ContactsTabHandler contactsTabController;
/** Controller of the keywords tab. */
private final KeywordTabHandler keywordTabHandler;
/** Controller of the message tab. */
private final MessageHistoryTabHandler messageTabController;
/** Handler for the email tab. */
private final EmailTabHandler emailTabHandler;
// FIXME these should probably move to the contacts tab controller
/** Fake group: The root group, of which all other top-level groups are children. The name of this group specified in the constructor will not be used due to overridden {@link Group#getName()}. */
final Group rootGroup = new Group(null, null) {
@Override
/** Provide an internationalised version of this group's name */
public String getName() {
return InternationalisationUtils.getI18nString(FrontlineSMSConstants.CONTACTS_ALL);
}
};
/** The number of people the current SMS will be sent to
* TODO this is a very strange variable to have. This should be replaced with context-specific tracking of the number of messages to be sent. */
private int numberToSend = 1;
/** Thinlet UI Component: status bar at the bottom of the window */
private final Object statusBarComponent;
/** Thinlet UI Cialog: device connection dialog handler */
private NoPhonesDetectedDialogHandler deviceConnectionDialogHandler;
/** A Lock object used to synchronise methods accessing the {@link #deviceConnectionDialogHandler} */
private Object deviceConnectionDialogHandlerLock = new Object();
/**
* Creates a new instance of the UI Controller.
* @param frontlineController The {@link FrontlineSMS} instance that this class is tied to.
* @param detectPhones <code>true</code> if phone detection should be started automatically; <code>false</code> otherwise.
* @throws Throwable any unhandled {@link Throwable} from this method
*/
public UiGeneratorController(FrontlineSMS frontlineController, boolean detectPhones) throws Throwable {
this.frontlineController = frontlineController;
// We prepare listening events for device connection
this.frontlineController.getEventBus().registerObserver(this);
// Load the requested language file.
AppProperties appProperties = AppProperties.getInstance();
String currentLanguageFile = appProperties.getLanguageFilePath();
if (currentLanguageFile != null) {
LanguageBundle languageBundle = InternationalisationUtils.getLanguageBundle(new File(currentLanguageFile));
if(languageBundle == null) {
LOG.warn("Could not find language bundle at: " + currentLanguageFile);
} else {
FrontlineUI.currentResourceBundle = languageBundle;
setResourceBundle(languageBundle.getProperties(), languageBundle.isRightToLeft());
Font requestedFont = languageBundle.getFont();
if(requestedFont != null) {
setFont(new Font(requestedFont.getName(), getFont().getStyle(), getFont().getSize()));
}
LOG.debug("Loaded language from file: " + currentLanguageFile);
}
}
this.phoneManager = frontlineController.getSmsServiceManager();
this.contactDao = frontlineController.getContactDao();
this.groupDao = frontlineController.getGroupDao();
this.groupMembershipDao = frontlineController.getGroupMembershipDao();
this.messageFactory = frontlineController.getMessageDao();
this.phoneDetailsManager = frontlineController.getSmsModemSettingsDao();
this.pluginManager = frontlineController.getPluginManager();
// Load the data mode from the ui.properties file
UiProperties uiProperties = UiProperties.getInstance();
LOG.debug("Detect Phones [" + detectPhones + "]");
try {
Object desktopUiComponent = loadComponentFromFile(UI_FILE_HOME);
add(desktopUiComponent);
// Remove the debug menu if this build is a proper release
if(!BuildProperties.getInstance().getVersion().contains("SNAPSHOT")) {
Object debugMenuComponent = find(desktopUiComponent, "mnDebug");
if(debugMenuComponent != null) {
remove(debugMenuComponent);
}
}
statusBarComponent = find(COMPONENT_STATUS_BAR);
setStatus(InternationalisationUtils.getI18nString(MESSAGE_STARTING));
// Find the languages submenu, and add all present language packs to it
addLanguageMenu(find("menu_language"));
// setText(find(COMPONENT_TF_COST_PER_SMS), InternationalisationUtils.formatCurrency(this.getCostPerSms(), false));
// setText(find(COMPONENT_LB_COST_PER_SMS_PREFIX),
// InternationalisationUtils.isCurrencySymbolPrefix()
// ? InternationalisationUtils.getCurrencySymbol()
// : "");
// setText(find(COMPONENT_LB_COST_PER_SMS_SUFFIX),
// InternationalisationUtils.isCurrencySymbolSuffix()
// ? InternationalisationUtils.getCurrencySymbol()
// : "");
Object tabbedPane = find(COMPONENT_TABBED_PANE);
this.phoneTabController = new PhoneTabHandler(this);
this.phoneTabController.init();
this.contactsTabController = new ContactsTabHandler(this);
this.contactsTabController.init();
this.messageTabController = new MessageHistoryTabHandler(this);
this.messageTabController.init();
this.emailTabHandler = new EmailTabHandler(this);
this.emailTabHandler.init();
this.keywordTabHandler = new KeywordTabHandler(this);
this.keywordTabHandler.init();
this.homeTabController = new HomeTabHandler(this);
this.homeTabController.init();
if (uiProperties.isTabVisible("hometab")) {
add(tabbedPane, this.homeTabController.getTab());
setSelected(find(COMPONENT_MI_HOME), true);
}
add(tabbedPane, this.contactsTabController.getTab());
if (uiProperties.isTabVisible("keywordstab")) {
add(tabbedPane, this.keywordTabHandler.getTab());
setSelected(find(COMPONENT_MI_KEYWORD), true);
}
if(uiProperties.isTabVisible("messagetab")) {
add(tabbedPane, this.messageTabController.getTab());
}
if (uiProperties.isTabVisible("emailstab")) {
add(tabbedPane, this.emailTabHandler.getTab());
setSelected(find(COMPONENT_MI_EMAIL), true);
}
add(tabbedPane, phoneTabController.getTab());
// Initialise the plugins menu
Object pluginMenu = find("menu_tabs");
Locale locale = InternationalisationUtils.getCurrentLocale();
for(Class<? extends PluginController> pluginClass : PluginProperties.getInstance().getPluginClasses()) {
// Try to get an icon from the classpath
String pluginName = pluginClass.newInstance().getName(locale);
String iconPath;
if(pluginClass.isAnnotationPresent(PluginControllerProperties.class)) {
PluginControllerProperties properties = pluginClass.getAnnotation(PluginControllerProperties.class);
iconPath = properties.iconPath();
} else {
iconPath = '/' + pluginClass.getPackage().getName().replace('.', '/') + '/' + pluginClass.getSimpleName() + ".png";
}
Object menuItem = createCheckboxMenuitem(iconPath, pluginName, PluginProperties.getInstance().isPluginEnabled(pluginClass));
add(pluginMenu, menuItem);
setAction(menuItem, "updatePluginEnabled('"+pluginClass.getName()+"', this.selected)", pluginMenu, this);
}
// Add plugins tabs
for(PluginController controller : this.pluginManager.getPluginControllers()) {
addPluginTextResources(controller);
add(tabbedPane, controller.getTab(this));
}
currentTab = TAB_HOME;
// Try to add the emulator number to the contacts
try {
Contact testContact = new Contact(TEST_NUMBER_NAME, EMULATOR_MSISDN, "", "", "", true);
contactDao.saveContact(testContact);
} catch(DuplicateKeyException ex) {
LOG.debug("Contact already exists", ex);
}
// Initialise the phone manager, and start auto-detection of connected phones.
setStatus(InternationalisationUtils.getI18nString(MESSAGE_INITIALISING_PHONE_MANAGER));
//Window size
Integer width = uiProperties.getWindowWidth();
if(width == null) width = DEFAULT_WIDTH;
Integer height = uiProperties.getWindowHeight();
if(height == null) height = DEFAULT_HEIGHT;
final String WINDOW_TITLE = "FrontlineSMS " + BuildProperties.getInstance().getVersion();
frameLauncher = new FrameLauncher(WINDOW_TITLE, this, width, height, getIcon(Icon.FRONTLINE_ICON));
if (uiProperties.isWindowStateMaximized()) {
//Is maximised
frameLauncher.setExtendedState(Frame.MAXIMIZED_BOTH);
}
frontlineController.setEmailListener(this);
frontlineController.setUiListener(this);
frontlineController.setSmsDeviceEventListener(this.phoneTabController);
setStatus(InternationalisationUtils.getI18nString(MESSAGE_PHONE_MANAGER_INITIALISED));
// Active connections
this.updateActiveConnections();
if (detectPhones) {
this.autodetectModems();
}
// Statistics
getFrontlineController().handleStatistics(this);
} catch(Throwable t) {
LOG.error("Problem starting User Interface module.", t);
super.destroy();
throw t;
}
}
public void autodetectModems() {
this.phoneTabController.phoneManager_detectModems();
}
/**
*
* @param pluginClassName the fully qualified name of the plugin class
* @param enabled <code>true</code> if the plugin should be enabled by this method, <code>false</code> if it should be disabled
*/
public void updatePluginEnabled(String pluginClassName, boolean enabled) {
if(LOG.isTraceEnabled()) {
LOG.trace("UiGeneratorController.updatePluginEnabled()");
LOG.trace("\tclass : " + pluginClassName);
LOG.trace("\tenabled : " + enabled);
}
// 1st, check if the plugin is already in the state the user is trying to put it in
if(enabled == PluginProperties.getInstance().isPluginEnabled(pluginClassName)) {
// The plugin is already in the expected state, so we should do nothing
return;
} else {
if(enabled) {
try {
// If we are enabling the plugin, we need to load it, add it to the loaded plugins list, and
// finally add its tab to the UI
PluginController controller = this.pluginManager.loadPluginController(pluginClassName);
addPluginTextResources(controller);
this.pluginManager.initPluginController(controller);
this.add(find(COMPONENT_TABBED_PANE), controller.getTab(this));
} catch(Throwable t) {
// There was a problem initialising the plugin. Log this, warn the user, and do
// not enable the plugin in the properties.
LOG.warn("There was a problem initialising the plugin.", t);
// TODO we should probably make this warning for the user slightly more elegant.
throw new RuntimeException(t);
}
} else {
// If we are disabling a plugin, we need to remove its tab from the UI, and then discard it
// Get the instance of the controller
PluginController controller = null;
for(PluginController c : this.pluginManager.getPluginControllers()) {
if(c.getClass().getName().equals(pluginClassName)) {
controller = c;
break;
}
}
if(controller == null) {
LOG.warn("Attempted to disable plugin controller '" + pluginClassName + "', but it could not be found.");
return;
}
this.remove(controller.getTab(this));
controller.deinit();
this.pluginManager.unloadPluginController(controller);
}
// Finally, change the value in the plugin properties file.
PluginProperties.getInstance().setPluginEnabled(pluginClassName, enabled);
PluginProperties.getInstance().saveToDisk();
}
}
/**
* Adds the text resources for a {@link PluginController} to {@link UiGeneratorController}'s text resource manager.
* @param controller the plugin controller whose text resource should be loaded
*/
public void addPluginTextResources(PluginController controller) {
// Add to the default English bundle
InternationalisationUtils.mergeMaps(Thinlet.DEFAULT_ENGLISH_BUNDLE, controller.getDefaultTextResource());
// Add to the current language bundle
LanguageBundle currentResourceBundle = FrontlineUI.currentResourceBundle;
if(currentResourceBundle != null) {
InternationalisationUtils.mergeMaps(currentResourceBundle.getProperties(), controller.getTextResource(currentResourceBundle.getLocale()));
setResourceBundle(currentResourceBundle.getProperties(), currentResourceBundle.isRightToLeft());
}
}
/** Pass through to method in the {@link HomeTabHandler}. */
public void showHomeTabSettings() {
this.homeTabController.showHomeTabSettings();
}
/**
* Shows the email accounts settings dialog.
*/
public void showEmailAccountsSettings() {
EmailAccountDialogHandler emailAccountDialogHandler = new EmailAccountDialogHandler(this, false);
add(emailAccountDialogHandler.getDialog());
}
/**
* Shows the statistics dialog.
*/
public void showStatsDialog() {
final StatisticsDialogHandler statisticsDialogHandler = new StatisticsDialogHandler(this);
new FrontlineUiUpateJob() {
public void run() {
add(statisticsDialogHandler.getDialog());
}
}.execute();
}
/**
* Gets the string to display for the recipient of a message.
* @param message The message whose recipient to get the name of
* @return This will be the name of the contact who received the message, or the recipient's phone number if they are not a contact.
*/
public String getRecipientDisplayValue(FrontlineMessage message) {
Contact recipient = contactDao.getFromMsisdn(message.getRecipientMsisdn());
String recipientDisplayName = recipient != null ? recipient.getDisplayName() : message.getRecipientMsisdn();
return recipientDisplayName;
}
/**
* Gets the string to display for the sender of a message.
* @param message The message whose sender to get the name of
* @return This will be the name of the contact who sent the message, or the sender's phone number if they are not a contact.
* @deprecated should be moved to message tab cont
*/
public String getSenderDisplayValue(FrontlineMessage message) {
Contact sender = contactDao.getFromMsisdn(message.getSenderMsisdn());
String senderDisplayName = sender != null ? sender.getDisplayName() : message.getSenderMsisdn();
return senderDisplayName;
}
/**
* Checks if the supplied group is a real group, or just one of the default groups
* used for visualization.
* @param group the group to check
* @return <code>true</code> if the supplied {@link Group} is one of the synthetic groups; <code>false</code> otherwise.
*/
public boolean isDefaultGroup(Group group) {
return group == this.rootGroup;
}
/**
* Selects the supplied object. If not found, none is selected.
* @param selected
* @param groupListComponent
*/
public void setSelectedGC(Object selected, Object groupListComponent) {
for (Object o : getItems(groupListComponent)) {
if (selected != null) {
if (isAttachment(selected, Group.class) && isAttachment(o, Group.class)) {
Group sel = getGroup(selected);
Group oo = getGroup(o);
if (oo.equals(sel)) {
setSelected(o, true);
return;
}
}
if (isAttachment(selected, Contact.class) && isAttachment(o, Contact.class)) {
Contact sel = getContact(selected);
Contact oo = getContact(o);
if (oo.equals(sel)) {
setSelected(o, true);
return;
}
}
}
setSelectedGC(selected, o);
}
}
public void getGroupsRecursivelyUp(List<Group> groups, Group g) {
groups.add(g);
Group parent = g.getParent();
if (!parent.equals(this.rootGroup)) {
getGroupsRecursivelyUp(groups, parent);
}
}
/**
* Shows the message history for the selected contact or group.
* @param component group list or contact list
*/
public void showMessageHistory(Object component) {
changeTab(TAB_MESSAGE_HISTORY);
this.messageTabController.doShowMessageHistory(component);
}
/** If the confirmation dialog exists, remove it */
public void removeConfirmationDialog() {
Object confirm = find(COMPONENT_CONFIRM_DIALOG);
if (confirm != null) removeDialog(confirm);
}
/**
* Shows a general dialog asking the user to confirm his action.
* @param methodToBeCalled the name of the method to be called
*/
public void showConfirmationDialog(String methodToBeCalled){
showConfirmationDialog(methodToBeCalled, this);
}
/** Shows a general dialog asking the user to confirm his action.
* @param methodToBeCalled The name of the method to be called
* @param handler The event handler to call the method on
*/
public void showConfirmationDialog(String methodToBeCalled, ThinletUiEventHandler handler){
Object conf = loadComponentFromFile(UI_FILE_CONFIRMATION_DIALOG_FORM);
setMethod(find(conf, COMPONENT_BT_CONTINUE), ATTRIBUTE_ACTION, methodToBeCalled, conf, handler);
add(conf);
}
/**
* Shows a general dialog asking the user to confirm his action.
* @return the confirmation dialog
*/
public Object showConfirmationDialog(String methodToBeCalled, String confirmationMessageKey) {
return showConfirmationDialog(methodToBeCalled, this, confirmationMessageKey);
}
/**
* Shows a general dialog asking the user to confirm his action.
* @return the confirmation dialog
*/
public Object showConfirmationDialog(String methodToBeCalled, ThinletUiEventHandler handler, String confirmationMessageKey) {
Object conf = loadComponentFromFile(UI_FILE_CONFIRMATION_DIALOG_FORM);
setMethod(find(conf, COMPONENT_BT_CONTINUE), ATTRIBUTE_ACTION, methodToBeCalled, conf, handler);
setText(find(conf, "lbText"), InternationalisationUtils.getI18nString(confirmationMessageKey));
add(conf);
return conf;
}
/**
* Shows the export wizard dialog, according to the supplied type.
* @param list The list to get selected items from.
* @param typeName The desired type
*/
public void showExportWizard(Object list, String typeName){
ExportDialogHandlerFactory.createHandler(this, typeName).showWizard(list);
}
/**
* Shows the export wizard dialog, according to the supplied type.
* @param type The desired type
*/
public void showExportWizard(String typeName){
ExportDialogHandlerFactory.createHandler(this, typeName).showWizard();
}
/**
* Shows the import wizard dialog, according to the supplied type.
* @param type The desired type (0 for Contacts, 1 for Messages and 2 for Keywords)
*/
public void showImportWizard(String typeName){
ImportDialogHandlerFactory.createHandler(this, typeName).showWizard();
}
/**
* Shows the pending message dialog.
* @param messages thy messages which are pending
*/
private void showPendingMessages(Collection<FrontlineMessage> messages) {
Object pendingMsgForm = loadComponentFromFile(UI_FILE_PENDING_MESSAGES_FORM);
Object list = find(pendingMsgForm, COMPONENT_PENDING_LIST);
for (FrontlineMessage m : messages) {
add(list, getRowForPending(m));
}
add(pendingMsgForm);
}
/**
* Remove the selected items from the supplied list.
* @param recipientList
* @param dialog
*/
public void removeSelectedFromRecipientList(Object recipientList, Object dialog) {
for (Object selected : getSelectedItems(recipientList)) {
numberToSend--;
remove(selected);
}
}
public void show_composeMessageForm(Group group) {
if (group == null) return;
LOG.debug("Getting contacts from Group [" + group.getName() + "]");
HashSet<Object> recipients = new HashSet<Object>();
boolean hasMembers = false;
for (Contact c : groupMembershipDao.getMembers(group)) {
hasMembers = true;
if (c.isActive()) {
LOG.debug("Adding contact [" + c.getName() + "] to the send list.");
recipients.add(c);
}
}
if (recipients.size() == 0) {
LOG.debug("No contacts to send, or selected groups contain only dormants.");
String key = hasMembers ? MESSAGE_ONLY_DORMANTS : MESSAGE_GROUP_NO_MEMBERS;
alert(InternationalisationUtils.getI18nString(key));
LOG.trace("EXIT");
} else {
show_composeMessageForm(recipients);
}
}
public void show_composeMessageForm(Collection<Object> recipients) {
numberToSend = recipients.size();
Object dialog = loadComponentFromFile(UI_FILE_COMPOSE_MESSAGE_FORM);
Object to = find(dialog, COMPONENT_COMPOSE_MESSAGE_RECIPIENT_LIST);
for(Object recipient : recipients) {
if(recipient instanceof Contact) {
add(to, createListItem((Contact)recipient));
} else if(recipient instanceof String) {
// FIXME set a suitable icon for this phone number list item
add(to, createListItem((String)recipient, recipient));
}
}
final boolean shouldDisplayRecipientField = false;
final boolean shouldCheckMaxMessageLength = false;
final int numberOfRecipients = recipients.size();
MessagePanelHandler messagePanelController = MessagePanelHandler.create(this, shouldDisplayRecipientField, shouldCheckMaxMessageLength, numberOfRecipients);
this.setWidth(dialog, 450);
this.setHeight(dialog, 380);
// We need to add the message panel to the dialog before setting the send button method
add(dialog, messagePanelController.getPanel());
messagePanelController.setSendButtonMethod(this, dialog, "sendMessage(composeMessageDialog, composeMessage_to, tfMessage)");
add(dialog);
LOG.trace("EXIT");
}
/**
* Shows the compose message dialog, populating the list with the selection of the
* supplied list.
* @param list
*/
public void show_composeMessageForm(Object list) {
LOG.trace("ENTER");
// Build up a list of selected recipients, and then pass this to
// the message composition form.
for (Object selectedComponent : getSelectedItems(list)) {
Object attachedItem = getAttachedObject(selectedComponent);
if(attachedItem == null) {
/** skip null items TODO is this necessary with instanceof */
} else if (attachedItem instanceof Contact) {
Set<Object> recipients = new HashSet<Object>();
Contact contact = (Contact)attachedItem;
LOG.debug("Adding contact [" + contact.getName() + "] to the send list.");
recipients.add(getContact(selectedComponent));
show_composeMessageForm(recipients);
} else if (attachedItem instanceof Group) {
show_composeMessageForm((Group) attachedItem);
} else if (attachedItem instanceof FrontlineMessage) {
Set<Object> recipients = new HashSet<Object>();
FrontlineMessage message = (FrontlineMessage) attachedItem;
// We should only attempt to reply to messages we have received - otherwise
// we could end up texting ourselves a lot!
if(message.getType() == Type.RECEIVED) {
String senderMsisdn = message.getSenderMsisdn();
Contact contact = contactDao.getFromMsisdn(senderMsisdn);
if(contact != null) {
recipients.add(contact);
} else {
recipients.add(senderMsisdn);
}
}
show_composeMessageForm(recipients);
}
}
}
/**
* This method sends a message for all contacts in the supplied list.
*
* @param composeMessageDialog
* @param recipientList The list with all contacts.
* @param messageContent The desired message.
*/
public void sendMessage(Object composeMessageDialog, Object recipientList, Object messageContent) {
String messageText = getText(messageContent);
for (Object o : getItems(recipientList)) {
Object attachedObject = getAttachedObject(o);
if(attachedObject == null) {
// Do nothing
// TODO check this is necessary
} else if(attachedObject instanceof Contact) {
Contact c = (Contact)attachedObject;
frontlineController.sendTextMessage(c.getPhoneNumber(), messageText);
} else if(attachedObject instanceof String) {
// Attached object is a phone number
frontlineController.sendTextMessage((String)attachedObject, messageText);
}
}
if (composeMessageDialog != null) {
remove(composeMessageDialog);
}
}
/**
* Method called when the user first click on the end date textfield and the value is set to undefined.
*
* @param o
*/
public void removeUndefinedString(Object o) {
if (getText(o).equals(InternationalisationUtils.getI18nString(COMMON_UNDEFINED))) {
setText(o, "");
}
}
/**
* Updates the status bar with the supplied string.
* @param status the new status to display
*/
public void setStatus(final String status) {
new FrontlineUiUpateJob() {
public void run() {
setString(statusBarComponent, TEXT, status);
}
}.execute();
LOG.debug("Status Text [" + status + "]");
}
/**
* Sets the phone number of the selected contact.
*
* @param contactSelecter_contactList
* @param dialog
*/
public void smsHttpSettings_setTextfield(Object contactSelecter_contactList, Object dialog) {
Object obj = find("smsHttpDialog");
Object tf = find(obj, "tfAccountSender");
Object selectedItem = getSelectedItem(contactSelecter_contactList);
if (selectedItem == null) {
alert(InternationalisationUtils.getI18nString(MESSAGE_NO_CONTACT_SELECTED));
return;
}
Contact selectedContact = getContact(selectedItem);
setText(tf, selectedContact.getPhoneNumber());
remove(dialog);
}
/**
* Sets the phone number of the selected contact.
*
* @param contactSelecter_contactList
* @param dialog
*/
public void smsHttpSettingsEdit_setTextfield(Object contactSelecter_contactList, Object dialog) {
Object obj = find("editSmsHttp");
Object tf = find(obj, "tfAccountSender");
Object selectedItem = getSelectedItem(contactSelecter_contactList);
if (selectedItem == null) {
alert(InternationalisationUtils.getI18nString(MESSAGE_NO_CONTACT_SELECTED));
return;
}
Contact selectedContact = getContact(selectedItem);
setText(tf, selectedContact.getPhoneNumber());
remove(dialog);
}
/**
* Shows the email accounts settings dialog.
*/
public void showMmsEmailAccountsSettings() {
MmsSettingsDialogHandler mmsSettingsDialogHandler = new MmsSettingsDialogHandler(this);
this.add(mmsSettingsDialogHandler.getDialog());
}
/**
* Method called when the user changes the task type.
*/
public void taskTypeChanged(Object dialog, Object selected) {
String name = getString(selected, Thinlet.NAME);
Object label = find(dialog, COMPONENT_LB_TEXT);
if (name.equalsIgnoreCase(COMPONENT_RB_HTTP)) {
//HTTP
setText(label, InternationalisationUtils.getI18nString(COMMON_URL));
setIcon(label, Icon.ACTION_HTTP_REQUEST);
} else {
//CMD
setText(label, InternationalisationUtils.getI18nString(COMMON_COMMAND));
setIcon(label, Icon.ACTION_EXTERNAL_COMMAND);
}
}
public void showDateSelecter(Object textField) {
LOG.trace("ENTER");
try {
new DateSelecter(this, textField).showSelecter();
} catch (IOException e) {
LOG.error("Error parsing file for dateSelecter", e);
LOG.trace("EXIT");
throw new RuntimeException(e);
}
LOG.trace("EXIT");
}
public void replyManager_showDateSelecter(Object panel, String type) {
LOG.trace("ENTER");
Object textField = type.equals("s") ? find(panel, COMPONENT_TF_START_DATE) : find(panel, COMPONENT_TF_END_DATE);
try {
new DateSelecter(this, textField).showSelecter();
} catch (IOException e) {
LOG.error("Error parsing file for dateSelecter", e);
LOG.trace("EXIT");
throw new RuntimeException(e);
}
LOG.trace("EXIT");
}
/**
* Method called when an event is fired and should be added to the event list on the home tab.
* @param newEvent New instance of {@link Event} to be added to the list.
*/
public void newEvent(Event newEvent) {
this.homeTabController.newEvent(newEvent);
}
/**
* Method invoked when the status for actions changes.
*
* @param panel
* @param live
*/
public void statusChanged(Object panel, boolean live) {
Object att = getAttachedObject(panel);
Object startTextField = find(panel, COMPONENT_TF_START_DATE);
Object endTextField = find(panel, COMPONENT_TF_END_DATE);
if (att != null) {
if (live) {
setText(startTextField, InternationalisationUtils.getDefaultStartDate());
if (getString(endTextField, Thinlet.TEXT).equals(InternationalisationUtils.getDefaultStartDate())) {
setText(endTextField, "");
}
} else {
setText(endTextField, InternationalisationUtils.getDefaultStartDate());
}
}
}
/**
* Perform actions before exiting.
* TODO this method should be renamed to reflect it's behaviour.
* @return <code>true</code> if the exit should be proceeded with; <code>false</code> otherwise.
*/
public boolean hasSomethingToDoBeforeExit() {
LOG.trace("ENTER");
saveWindowSize();
Collection<FrontlineMessage> pending = messageFactory.getMessages(Type.OUTBOUND, Status.PENDING);
if(LOG.isDebugEnabled()) LOG.debug("Pending Messages size [" + pending.size() + "]");
if (pending.size() > 0) {
showPendingMessages(pending);
} else {
this.showConfirmationDialog("doClose", I18N_CONFIRM_EXIT);
}
return true;
}
public void close() {
this.showConfirmationDialog("doClose", I18N_CONFIRM_EXIT);
}
public void doClose() {
LOG.trace("ENTER doClose");
saveWindowSize();
frameLauncher.dispose();
this.frontlineController.destroy();
this.destroy();
LOG.trace("EXIT");
// throw new AppShutdownException();
}
/**
* Method called when the user make the final decision to close the app.
*/
public void exit() {
for (FrontlineMessage m : messageFactory.getMessages(Type.OUTBOUND, Status.PENDING)) {
m.setStatus(Status.OUTBOX);
}
doClose();
}
/**
* Writes to the property file the current window size.
*/
private void saveWindowSize() {
UiProperties uiProperties = UiProperties.getInstance();
uiProperties.setWindowState(frameLauncher.getExtendedState() == Frame.MAXIMIZED_BOTH,
frameLauncher.getBounds().width, frameLauncher.getBounds().height);
uiProperties.saveToDisk();
}
/**
* Checks if the object attached to a component is of a specific class.
* @param component
* @param clazz
* @return
*/
public boolean isAttachment(Object component, Class<?> clazz) {
Object object = getAttachedObject(component);
return object != null && object.getClass().equals(clazz);
}
/**
* Gets the Message instance attached to the supplied component.
*
* @param component
* @return The Message instance.
*/
public FrontlineMessage getMessage(Object component) {
return (FrontlineMessage) getAttachedObject(component);
}
/**
* Gets the EMail instance attached to the supplied component.
*
* @param component
* @return The Email instance.
*/
public Email getEmail(Object component) {
return (Email) getAttachedObject(component);
}
/**
* Gets the Contact instance attached to the supplied component.
*
* @param component
* @return The Contact instance.
*/
public Contact getContact(Object component) {
return (Contact) getAttachedObject(component);
}
/**
* Gets the Group instance attached to the supplied component.
*
* @param component
* @return The Group instance.
*/
public Group getGroup(Object component) {
return (Group) getAttachedObject(component);
}
/**
* Returns the keyword attached to the supplied component.
*
* @param component
* @return
*/
public Keyword getKeyword(Object component) {
Object obj = getAttachedObject(component);
if (obj instanceof Keyword) return (Keyword)obj;
else if (obj instanceof KeywordAction) return ((KeywordAction)obj).getKeyword();
else if (obj == null) return null;
else throw new RuntimeException();
}
/**
* Returns the keyword action attached to the supplied component.
*
* @param component
* @return
*/
public KeywordAction getKeywordAction(Object component) {
Object obj = getAttachedObject(component);
if (obj == null) return null;
else if (obj instanceof KeywordAction) {
return (KeywordAction)obj;
} else throw new RuntimeException();
}
/**
* Get the status of a {@link FrontlineMessage} as a {@link String}.
* @param message
* @param languageBundle
* @return {@link String} representation of the status.
*/
public static final String getMessageStatusAsString(FrontlineMessage message, LanguageBundle languageBundle) {
return InternationalisationUtils.getI18nString(message.getStatus().getI18nKey(), languageBundle);
}
/**
* Gets a short description of a keyword-action.
* @param action The keyword action to get the description.
* @return The description of the supplied keyword action.
*/
private String getActionDescription(KeywordAction action) {
StringBuilder ret = new StringBuilder("");
switch (action.getType()) {
case FORWARD:
ret.append(InternationalisationUtils.getI18nString(ACTION_FORWARD));
ret.append(": \"");
ret.append(KeywordAction.KeywordUtils.getForwardText(action, DEMO_SENDER, DEMO_SENDER.getPhoneNumber(), action.getKeyword().getKeyword() + DEMO_MESSAGE_TEXT_INCOMING));
ret.append("\" ");
ret.append(InternationalisationUtils.getI18nString(COMMON_TO_GROUP));
ret.append(": \"");
ret.append(action.getGroup().getName());
ret.append("\"");
break;
case JOIN:
ret.append(InternationalisationUtils.getI18nString(COMMON_JOIN));
ret.append(": ");
ret.append(action.getGroup().getName());
break;
case LEAVE:
ret.append(InternationalisationUtils.getI18nString(COMMON_LEAVE));
ret.append(": ");
ret.append(action.getGroup().getName());
break;
case REPLY:
ret.append(InternationalisationUtils.getI18nString(COMMON_REPLY));
ret.append(": ");
ret.append(KeywordAction.KeywordUtils.getReplyText(action, DEMO_SENDER, DEMO_SENDER.getPhoneNumber(), DEMO_MESSAGE_TEXT_INCOMING, DEMO_MESSAGE_KEYWORD));
break;
case EXTERNAL_CMD:
if (action.getExternalCommandType() == KeywordAction.ExternalCommandType.HTTP_REQUEST) {
ret.append(InternationalisationUtils.getI18nString(COMMON_HTTP_REQUEST));
} else {
ret.append(InternationalisationUtils.getI18nString(COMMON_EXTERNAL_COMMAND));
}
break;
case EMAIL:
ret.append(InternationalisationUtils.getI18nString(COMMON_E_MAIL));
ret.append(": ");
ret.append(KeywordAction.KeywordUtils.getReplyText(action, DEMO_SENDER, DEMO_SENDER.getPhoneNumber(), action.getKeyword().getKeyword() + DEMO_MESSAGE_TEXT_INCOMING, DEMO_MESSAGE_KEYWORD));
break;
}
return ret.toString();
}
public void table_addCells(Object tableRow, String[] cellContents) {
for(String s : cellContents) add(tableRow, createTableCell(s));
}
public void table_addCells(Object tableRow, String[] cellContents, boolean bold) {
for(String s : cellContents) add(tableRow, createTableCell(s, bold));
}
/**
* Creates a list item Thinlet UI Component for the supplied keyword. The keyword
* object is attached to the component, and the component's icon is set appropriately.
* @param keyword
* @return
*/
public Object createListItem(Keyword keyword) {
String key = keyword.getKeyword().length() == 0 ? "<" + InternationalisationUtils.getI18nString(COMMON_BLANK) + ">" : keyword.getKeyword();
Object listItem = createListItem(
key,
keyword);
setIcon(listItem, Icon.KEYWORD);
return listItem;
}
/**
* Creates a list item Thinlet UI Component for the supplied contact. The contact
* object is attached to the component, and the component's icon is set appropriately.
* @param contact
* @return
*/
public Object createListItem(Contact contact) {
Object listItem = createListItem(contact.getName() + " (" + contact.getPhoneNumber() + ")", contact);
setIcon(listItem, Icon.CONTACT);
return listItem;
}
/**
* Creates a Thinlet UI table row containing details of a contact.
* @param contact
* @return
*/
public Object getRow(Contact contact) {
Object row = createTableRow(contact);
Object cell = createTableCell("");
if (contact.isActive()) {
setIcon(cell, Icon.CIRLCE_TICK);
} else {
setIcon(cell, Icon.CANCEL);
}
setChoice(cell, ALIGNMENT, CENTER);
add(row, cell);
String name = contact.getName();
add(row, createTableCell(name));
add(row, createTableCell(contact.getPhoneNumber()));
add(row, createTableCell(contact.getEmailAddress()));
// String groups = Utils.contactGroupsAsString(this.groupMembershipDao.getGroups(contact), DEFAULT_GROUPS_DELIMITER);
// add(row, createTableCell(groups));
return row;
}
/**
* Creates a Thinlet UI table row containing details of a keyword action.
* @param contact
* @return
*/
public Object getRow(KeywordAction action) {
Object row = createTableRow(action);
String icon;
switch(action.getType()) {
case FORWARD:
icon = Icon.ACTION_FORWARD;
break;
case JOIN:
icon = Icon.ACTION_JOIN;
break;
case LEAVE:
icon = Icon.ACTION_LEAVE;
break;
case REPLY:
icon = Icon.ACTION_REPLY;
break;
case EXTERNAL_CMD:
if (action.getExternalCommandType() == KeywordAction.ExternalCommandType.COMMAND_LINE)
icon = Icon.ACTION_EXTERNAL_COMMAND;
else
icon = Icon.ACTION_HTTP_REQUEST;
break;
case EMAIL:
icon = Icon.ACTION_EMAIL;
break;
default:
icon = Icon.SURVEY;
break;
}
Object cell = createTableCell("");
setIcon(cell, icon);
add(row, cell);
add(row, createTableCell(getActionDescription(action)));
add(row, createTableCell(InternationalisationUtils.getDateFormat().format(action.getStartDate())));
if (action.getEndDate() != DEFAULT_END_DATE) {
add(row, createTableCell(InternationalisationUtils.getDateFormat().format(action.getEndDate())));
} else {
add(row, createTableCell(InternationalisationUtils.getI18nString(COMMON_UNDEFINED)));
}
cell = createTableCell("");
setIcon(cell, action.isAlive(System.currentTimeMillis()) ? Icon.CIRLCE_TICK : Icon.CANCEL);
setChoice(cell, ALIGNMENT, CENTER);
add(row, cell);
add(row, createTableCell(action.getCounter()));
return row;
}
/**
* Creates a Thinlet UI table row containing details of an e-mail account.
* @param message
* @return
*/
public Object getRow(EmailAccount acc) {
Object row = createTableRow(acc);
Object iconCell = createTableCell(acc.getProtocol());
setIcon(iconCell, acc.useSsl() ? Icon.SSL : Icon.NO_SSL);
add(row, iconCell);
add(row, createTableCell(acc.getAccountServer()));
add(row, createTableCell(acc.getAccountName()));
return row;
}
/**
* Creates a Thinlet UI table row containing details of an SMS message.
* @param message
* @return
*/
private Object getRowForPending(FrontlineMessage message) {
Object row = createTableRow(message);
String senderDisplayName = getSenderDisplayValue(message);
String recipientDisplayName = getRecipientDisplayValue(message);
add(row, createTableCell(senderDisplayName));
add(row, createTableCell(recipientDisplayName));
add(row, createTableCell(message.getTextContent()));
return row;
}
/**
* Called when the current tab is changed; handles the tab-specific refreshments that need to be made.
* @param tabbedPane
*/
public void tabSelectionChanged(Object tabbedPane) {
LOG.trace("ENTER");
Object newTab = getSelectedItem(tabbedPane);
currentTab = getString(newTab, NAME);
this.frontlineController.getEventBus().notifyObservers(new TabChangedNotification(currentTab));
LOG.debug("Current tab [" + currentTab + "]");
if (currentTab == null) return;
LOG.trace("EXIT");
}
public synchronized void outgoingEmailEvent(EmailSender sender, Email email) {
LOG.trace("ENTER");
LOG.debug("E-mail [" + email.getEmailContent() + "], Status [" + email.getStatus() + "]");
if (currentTab.equals(TAB_EMAIL_LOG)) {
this.emailTabHandler.outgoingEmailEvent(sender, email);
}
if (email.getStatus() == Email.Status.SENT) {
String[] recipients = email.getEmailRecipients().split(";");
String strRecipients = recipients[0] + (recipients.length > 1 ? InternationalisationUtils.getI18nString(EVENT_DESCRIPTION_MULTI_RECIPIENTS, recipients.length) : "");
newEvent(new Event(Event.TYPE_OUTGOING_EMAIL, InternationalisationUtils.getI18nString(EVENT_DESCRIPTION, strRecipients, email.getEmailContent())));
}
LOG.trace("EXIT");
}
/**
* Event triggered when a contact is added to a group by a {@link KeywordAction}.
*/
public synchronized void contactAddedToGroup(Contact contact, Group group) {
if(currentTab.equals(TAB_CONTACT_MANAGER)) {
this.contactsTabController.addToContactList(contact, group);
}
}
public synchronized void keywordActionExecuted(KeywordAction action) {
if (currentTab.equals(TAB_KEYWORD_MANAGER)) {
Object keyTab = find(currentTab);
Object pnKeywordActionsAdvanced = find(keyTab, COMPONENT_PN_KEYWORD_ACTIONS_ADVANCED);
if (pnKeywordActionsAdvanced != null) {
Object actionsList = find(pnKeywordActionsAdvanced, COMPONENT_ACTION_LIST);
int selected = getSelectedIndex(actionsList);
int index = -1;
for (Object act : getItems(actionsList)) {
KeywordAction a = getKeywordAction(act);
if (a.equals(action)) {
index = getIndex(actionsList, act);
remove(act);
break;
}
}
if (index != -1) {
add(actionsList, getRow(action), index);
}
if (selected >= 0) {
setSelectedIndex(actionsList, selected);
}
}
}
}
public void changeLanguage(Object menuItem) {
AppProperties appProperties = AppProperties.getInstance();
appProperties.setLanguageFilename(getAttachedObject(menuItem).toString());
appProperties.saveToDisk();
reloadUi();
}
private void addLanguageMenu(Object menu) {
for(FileLanguageBundle languageBundle : InternationalisationUtils.getLanguageBundles()) {
Object menuitem = create(MENUITEM);
setText(menuitem, languageBundle.getLanguageName());
setIcon(menuitem, getFlagIcon(languageBundle));
setMethod(menuitem, ATTRIBUTE_ACTION, "changeLanguage(this)", menu, this);
setAttachedObject(menuitem, languageBundle.getFile().getAbsolutePath());
add(menu, menuitem);
}
}
public void showAboutScreen() {
new AboutDialog(this).show();
}
public void showContributeScreen() {
new ContributeDialog(this).show();
}
public void incomingMessageEvent(FrontlineMessage message) {
LOG.trace("ENTER");
if (currentTab.equals(TAB_MESSAGE_HISTORY)) {
this.messageTabController.incomingMessageEvent(message);
}
Contact sender = contactDao.getFromMsisdn(message.getSenderMsisdn());
String strSender = (sender == null ? message.getSenderMsisdn() : sender.getName());
int eventType = (message instanceof FrontlineMultimediaMessage ? Event.TYPE_INCOMING_MMS : Event.TYPE_INCOMING_MESSAGE);
newEvent(new Event(eventType, InternationalisationUtils.getI18nString(EVENT_DESCRIPTION, strSender, message.getTextContent())));
setStatus(InternationalisationUtils.getI18nString(MESSAGE_MESSAGE_RECEIVED));
LOG.trace("EXIT");
}
public void outgoingMessageEvent(FrontlineMessage message) {
LOG.trace("ENTER");
LOG.debug("Message [" + message.getTextContent() + "], Status [" + message.getStatus() + "]");
if (currentTab.equals(TAB_MESSAGE_HISTORY)) {
this.messageTabController.outgoingMessageEvent(message);
}
Contact recipient = contactDao.getFromMsisdn(message.getRecipientMsisdn());
String strRecipient = (recipient == null ? message.getRecipientMsisdn() : recipient.getName());
if (message.getStatus() == Status.SENT) {
newEvent(new Event(Event.TYPE_OUTGOING_MESSAGE, InternationalisationUtils.getI18nString(EVENT_DESCRIPTION, strRecipient, message.getTextContent())));
} else if (message.getStatus() == Status.FAILED) {
newEvent(new Event(Event.TYPE_OUTGOING_MESSAGE_FAILED, InternationalisationUtils.getI18nString(EVENT_DESCRIPTION, strRecipient, message.getTextContent())));
}
LOG.trace("ENTER");
}
public void switchToPhonesTab() {
changeTab(TAB_ADVANCED_PHONE_MANAGER);
}
public void tabsChanged(Object menuItem) {
Object tabbedPane = find(COMPONENT_TABBED_PANE);
String name = getName(menuItem);
boolean selected = isSelected(menuItem);
String tabName = "";
if (selected) {
// Add a tab
if (name.equals(COMPONENT_MI_HOME)) {
// add the home tab at index ZERO - the tab furthest on the left
add(tabbedPane, this.homeTabController.getTab(), 0);
tabName = "hometab";
} else if (name.equals(COMPONENT_MI_KEYWORD)) {
add(tabbedPane, this.keywordTabHandler.getTab());
tabName = "keywordstab";
} else if (name.equals(COMPONENT_MI_EMAIL)) {
add(tabbedPane, this.emailTabHandler.getTab());
tabName = "emailstab";
}
} else {
Object tab = null;
// Remove a tab
if (name.equals(COMPONENT_MI_HOME)) {
tab = find(tabbedPane, TAB_HOME);
tabName = "hometab";
} else if (name.equals(COMPONENT_MI_KEYWORD)) {
tab = find(tabbedPane, TAB_KEYWORD_MANAGER);
tabName = "keywordstab";
} else if (name.equals(COMPONENT_MI_EMAIL)) {
tab = find(tabbedPane, TAB_EMAIL_LOG);
tabName = "emailstab";
}
if (tab != null) {
remove(tab);
setInteger(tabbedPane, SELECTED, 0);
}
}
// Save tab visibility to disk
UiProperties uiProperties = UiProperties.getInstance();
uiProperties.setTabVisible(tabName, selected);
uiProperties.saveToDisk();
}
private void changeTab(String tabName) {
Object tabbedPane = find(COMPONENT_TABBED_PANE);
Object tab = find(tabName);
// The following method is taken from the inside of Thinlet.handleMouseEvent().
// Had to extend the visibility of a number of methods to make this possible.
int current = getIndex(tabbedPane, tab);
setInteger(tabbedPane, SELECTED, current, 0);
checkOffset(tabbedPane);
tabSelectionChanged(tabbedPane);
repaint(tabbedPane);
}
public void showUserDetailsDialog() {
Object detailsDialog = loadComponentFromFile(UI_FILE_USER_DETAILS_DIALOG);
add(detailsDialog);
}
public void reportError(Object dialog) {
removeDialog(dialog);
final String userName = getText(find(dialog, "tfName"));
final String userEmail = getText(find(dialog, "tfEmail"));
final String reason = getText(find(dialog, "taReason"));
new Thread("ERROR_REPORT") {
public void run() {
try {
ErrorUtils.sendLogs(userName, userEmail, reason, true);
String success = InternationalisationUtils.getI18nString(MESSAGE_LOG_FILES_SENT);
LOG.debug(success);
alert(success);
setStatus(success);
} catch (EmailException e) {
String msg = InternationalisationUtils.getI18nString(MESSAGE_FAILED_TO_SEND_REPORT);
LOG.debug(msg, e);
setStatus(msg);
alert(msg);
// Show user the option to save the logs.zip to a place they know!
String dir = ErrorUtils.showFileChooserForSavingZippedLogs();
if (dir != null) {
try {
ErrorUtils.copyLogsZippedToDir(dir);
} catch (IOException e1) {
LOG.debug("", e1);
String first = InternationalisationUtils.getI18nString(MESSAGE_FAILED_TO_COPY_LOGS);
String second = InternationalisationUtils.getI18nString(MESSAGE_LOGS_LOCATED_IN);
setStatus(first.replace(ARG_VALUE, ZIPPED_LOGS_FILENAME) + "." + second.replace(ARG_VALUE, ResourceUtils.getConfigDirectoryPath() + "logs") + ".");
alert(first.replace(ARG_VALUE, ZIPPED_LOGS_FILENAME) + "." + second.replace(ARG_VALUE, ResourceUtils.getConfigDirectoryPath() + "logs") + ".");
}
msg = InternationalisationUtils.getI18nString(MESSAGE_LOGS_SAVED_PLEASE_REPORT);
setStatus(msg);
alert(msg);
}
} catch (IOException e) {
// Problem writing logs.zip
LOG.debug("", e);
try {
ErrorUtils.sendLogsToFrontlineSupport(userName, userEmail, reason, null);
} catch (EmailException e1) {
LOG.debug("", e1);
}
} finally {
File input = new File(ResourceUtils.getConfigDirectoryPath() + ZIPPED_LOGS_FILENAME);
if (input.exists()) {
input.deleteOnExit();
}
}
}
}.start();
}
/**
* Handles expansion changes to a group list - a group's icon changes
* depending on whether it is expanded or collapsed and whether it
* has subgroups and members or not.
* @param groupList
*/
public void groupList_expansionChanged(Object groupList) {
for (Object o : getItems(groupList)) {
if (isAttachment(o, Group.class)) {
if(getBoolean(o, EXPANDED) && groupDao.hasDescendants(getAttachedObject(o, Group.class))) {
// Set the icon to EXPANDED, and set children icons too!
setIcon(o, Icon.FOLDER_OPEN);
groupList_expansionChanged(o);
} else {
// Set the icon to CLOSED
setIcon(o, Icon.FOLDER_CLOSED);
}
}
}
}
/**
* Show dialog for editing Forms settings.
*/
public void showFormsSettings() {
Object dialog = loadComponentFromFile("/ui/dialog/formsSettings.xml");
add(dialog);
}
/**
* Show the dialog for viewing and editing settings for {@link SmsInternetServiceSettingsHandler}.
*/
public void showSmsInternetServiceSettings() {
new SmsInternetServiceSettingsHandler(this).showSettingsDialog();
}
public void showConfigurationLocationDialog() {
Object dialog = loadComponentFromFile("/ui/dialog/configurationLocation.xml");
setText(find(dialog, "tfConfigurationLocation"), ResourceUtils.getConfigDirectoryPath());
add(dialog);
}
//> ACCESSORS
/** @return {@link UiProperties} associated with this class */
public UiProperties getProperties() {
return UiProperties.getInstance();
}
/** @return {@link #frontlineController} */
public FrontlineSMS getFrontlineController() {
return this.frontlineController;
}
/** @return {@link #phoneManager} */
public SmsServiceManager getPhoneManager() {
return this.phoneManager;
}
/** @return {@link #phoneDetailsManager} */
public SmsModemSettingsDao getPhoneDetailsManager() {
return phoneDetailsManager;
}
/** @return the current tab as an object component */
public Object getCurrentTab() {
return this.find(this.currentTab);
}
/** @return {@link #rootGroup} */
public Group getRootGroup() {
return this.rootGroup;
}
/** @return the {@link Frame} attached to this thinlet window */
public Frame getFrameLauncher() {
return this.frameLauncher;
}
public SmsInternetServiceSettingsDao getSmsInternetServiceSettingsDao() {
return frontlineController.getSmsInternetServiceSettingsDao();
}
public Collection<SmsInternetService> getSmsInternetServices() {
return this.frontlineController.getSmsInternetServices();
}
public void addSmsInternetService(SmsInternetService smsInternetService) {
this.phoneManager.addSmsInternetService(smsInternetService);
}
private void removeSmsInternetService(SmsInternetService service) {
this.phoneManager.removeSmsInternetService(service);
}
public void contactRemovedFromGroup(Contact contact, Group group) {
if(this.currentTab.equals(TAB_CONTACT_MANAGER)) {
// TODO perhaps update the contact manager to remove the contact from the group, if it is currently relevant
}
}
public void showDatabaseConfigDialog() {
DatabaseSettingsPanel databaseSettings = DatabaseSettingsPanel.createNew(this, null);
boolean needToRestartApplication = true;
databaseSettings.showAsDialog(needToRestartApplication);
}
public void showFrontlineSettings() {
FrontlineSettingsHandler settingsDialog = new FrontlineSettingsHandler(this);
add(settingsDialog.getDialog());
}
/** Reloads the ui. */
public final void reloadUi() {
this.frameLauncher.dispose();
this.frameLauncher.setContent(null);
this.frameLauncher = null;
this.destroy();
try {
new UiGeneratorController(frontlineController, false);
} catch(Throwable t) {
log.error("Unable to reload frontlineSMS.", t);
}
}
@Override
public boolean destroy() {
final boolean destroy = super.destroy();
if(destroy) {
EventBus bus = this.frontlineController.getEventBus();
bus.unregisterObserver(this);
bus.notifyObservers(new UiDestroyEvent(this));
}
return destroy;
}
/**
* Updates the number of active connections in the status bar.
*/
public void updateActiveConnections() {
setText(find(COMPONENT_LB_ACTIVE_CONNECTIONS),
InternationalisationUtils.getI18nString(I18N_ACTIVE_CONNECTIONS, getFrontlineController().getNumberOfActiveConnections()));
}
//> DEBUG METHODS
/** UI Event method: Generate test data
* @throws IOException */
public void generateTestData() throws IOException {
RandomDataGenerator randy = new RandomDataGenerator();
randy.initFromClasspath();
randy.setFrontlineController(this.frontlineController);
randy.generate(200);
}
/** UI Event method (debug): Delete all contacts and groups */
public void deleteAllGroupsAndContacts() {
// Delete all groups and their contacts
for (Group group : this.groupDao.getAllGroups()) {
this.groupDao.deleteGroup(group, true);
}
// And then the remaining contacts
for (Contact contact : this.contactDao.getAllContacts()) {
this.contactDao.deleteContact(contact);
}
removeConfirmationDialog();
contactsTabController.refresh();
infoMessage(InternationalisationUtils.getI18nString(MESSAGE_GROUPS_AND_CONTACTS_DELETED));
}
public void showGroupSelecter() {
Object dialog = super.createDialog("Group Selecter Test");
GroupSelecterPanel selecter = new GroupSelecterPanel(this, this);
selecter.init(this.rootGroup);
add(dialog, selecter.getPanelComponent());
add(dialog);
}
public void groupSelectionChanged(Group selectedGroup) {
System.out.println("GROUP SELECTION IS NOW: " + selectedGroup);
}
public void dbgGenerateIncomingSms() {
this.frontlineController.incomingMessageEvent(FrontlineSMS.EMULATOR, new CIncomingMessage("+123456789", "Test incoming SMS"));
}
public void dbgGenerateError() {
throw new RuntimeException("Exception generated from debug menu.");
}
public void dbgGenerateOutgoingSms() {
FrontlineMessage testMessage = FrontlineMessage.createOutgoingMessage(System.currentTimeMillis(), null, "+123456789", "Test outgoing SMS");
this.messageFactory.saveMessage(testMessage);
this.frontlineController.outgoingMessageEvent(FrontlineSMS.EMULATOR, testMessage);
}
/** Handle notifications from the {@link EventBus} */
public void notify(final FrontlineEventNotification notification) {
if(notification instanceof NoSmsServicesConnectedNotification) {
// Unable to connect to SMS devices. If configured so, prompt the help dialog
if (AppProperties.getInstance().shouldPromptDeviceConnectionDialog()) {
synchronized (deviceConnectionDialogHandlerLock) {
// If the dialog is not already created AND not already displayed, create a new one and show it now
if (deviceConnectionDialogHandler == null) {
deviceConnectionDialogHandler = new NoPhonesDetectedDialogHandler(this);
new FrontlineUiUpateJob() {
public void run() {
deviceConnectionDialogHandler.initDialog((NoSmsServicesConnectedNotification) notification);
add(deviceConnectionDialogHandler.getDialog());
}
}.execute();
}
}
}
} else if (notification instanceof MmsServiceStatusNotification) {
// An MMS Service has changed status
MmsServiceStatusNotification mmsServiceStatusNotification = ((MmsServiceStatusNotification) notification);
if (mmsServiceStatusNotification.getStatus().equals(MmsEmailServiceStatus.FAILED_TO_CONNECT)) {
this.newEvent(new Event(Event.TYPE_SMS_INTERNET_SERVICE_RECEIVING_FAILED,
mmsServiceStatusNotification.getMmsService().getServiceName() + " - " + InternationalisationUtils.getI18nString(FrontlineSMSConstants.COMMON_SMS_INTERNET_SERVICE_RECEIVING_FAILED)));
}
} else if (notification instanceof EntitySavedNotification<?>) {
Object entity = ((EntitySavedNotification<?>) notification).getDatabaseEntity();
if (entity instanceof FrontlineMultimediaMessage) {
// A new Multimedia Message has been received
this.incomingMessageEvent((FrontlineMultimediaMessage) entity);
}
} else if (notification instanceof InternetServiceEventNotification) {
// An Internet Service has been added or deleted
InternetServiceEventNotification internetServiceNotification = (InternetServiceEventNotification) notification;
switch (internetServiceNotification.getEventType()) {
case ADD:
this.addSmsInternetService(internetServiceNotification.getService());
break;
case DELETE:
this.removeSmsInternetService(internetServiceNotification.getService());
break;
default:
break;
}
}
}
public void closeDeviceConnectionDialog(Object dialog) {
synchronized (deviceConnectionDialogHandlerLock) {
this.remove(dialog);
this.deviceConnectionDialogHandler = null;
}
}
/**
* Refreshes the contact tab if it is currently visible. If it is not visible,
* it will not be refreshed until it is shown again.
*/
public void refreshContactsTab() {
if (this.currentTab.equals(TAB_CONTACT_MANAGER)) {
this.contactsTabController.refresh();
}
}
/**
* Refreshes the messages tab if it is currently visible. If it is not visible,
* it will not be refreshed until it is shown again.
*/
public void refreshMessagesTab() {
if (this.currentTab.equals(TAB_MESSAGE_HISTORY)) {
this.messageTabController.refresh();
}
}
}