diff options
author | Yana Stamcheva <yana@jitsi.org> | 2011-07-27 19:23:47 +0000 |
---|---|---|
committer | Yana Stamcheva <yana@jitsi.org> | 2011-07-27 19:23:47 +0000 |
commit | 801a79ca9dcd4758344cf0d56085a7fd29f34d3f (patch) | |
tree | d8df9465b77233390d8f7e73fb73c8c5491b035d /src/net/java/sip | |
parent | e46f74f442136d50bb98e1f62041d10087b272e1 (diff) | |
download | jitsi-801a79ca9dcd4758344cf0d56085a7fd29f34d3f.zip jitsi-801a79ca9dcd4758344cf0d56085a7fd29f34d3f.tar.gz jitsi-801a79ca9dcd4758344cf0d56085a7fd29f34d3f.tar.bz2 |
Enables spell check plugin and adds some improvements provided by Purvesh Sahoo.
Diffstat (limited to 'src/net/java/sip')
12 files changed, 1410 insertions, 274 deletions
diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatWritePanel.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatWritePanel.java index 3ded2ff..3ea9afb 100755 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatWritePanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatWritePanel.java @@ -720,9 +720,26 @@ public class ChatWritePanel Point p = e.getPoint(); SwingUtilities.convertPointToScreen(p, e.getComponent()); - rightButtonMenu.setInvoker(editorPane); - rightButtonMenu.setLocation(p.x, p.y); - rightButtonMenu.setVisible(true); + //SPELLCHECK + ArrayList <JMenuItem> contributedMenuEntries + = new ArrayList<JMenuItem>(); + + for(ChatMenuListener listener : this.menuListeners) + { + contributedMenuEntries.addAll( + listener.getMenuElements(this.chatPanel, e)); + } + + for(JMenuItem item : contributedMenuEntries) + { + rightButtonMenu.add(item); + } + + JPopupMenu rightMenu + = rightButtonMenu.makeMenu(contributedMenuEntries); + rightMenu.setInvoker(editorPane); + rightMenu.setLocation(p.x, p.y); + rightMenu.setVisible(true); } } diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/menus/WritePanelRightButtonMenu.java b/src/net/java/sip/communicator/impl/gui/main/chat/menus/WritePanelRightButtonMenu.java index 5f96820..e8ba640 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/menus/WritePanelRightButtonMenu.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/menus/WritePanelRightButtonMenu.java @@ -7,6 +7,7 @@ package net.java.sip.communicator.impl.gui.main.chat.menus; import java.awt.event.*; +import java.util.*; import javax.swing.*; @@ -150,4 +151,32 @@ public class WritePanelRightButtonMenu closeMenuItem.setIcon(new ImageIcon( ImageLoader.getImage(ImageLoader.CLOSE_ICON))); } + + /** + * Provides a popup menu with custom entries followed by default + * operation entries ( copy, paste ,close) + * + * @param entries custom menu entries to be added + * @return right click menu + */ + public JPopupMenu makeMenu(List <JMenuItem> entries) { + + JPopupMenu rightMenu = new JPopupMenu(); + + for(JMenuItem entry : entries) { + rightMenu.add(entry); + } + + if(!entries.isEmpty()) rightMenu.addSeparator(); + + rightMenu.add(copyMenuItem); + rightMenu.add(cutMenuItem); + rightMenu.add(pasteMenuItem); + + rightMenu.addSeparator(); + + rightMenu.add(closeMenuItem); + + return rightMenu; + } } diff --git a/src/net/java/sip/communicator/plugin/spellcheck/ChatAttachments.java b/src/net/java/sip/communicator/plugin/spellcheck/ChatAttachments.java index e9f0104..75f23ff 100644 --- a/src/net/java/sip/communicator/plugin/spellcheck/ChatAttachments.java +++ b/src/net/java/sip/communicator/plugin/spellcheck/ChatAttachments.java @@ -1,8 +1,7 @@ /* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. + * + * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.sip.communicator.plugin.spellcheck; @@ -13,33 +12,48 @@ import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; -import net.java.sip.communicator.service.gui.Chat; -import net.java.sip.communicator.service.gui.event.ChatMenuListener; -import net.java.sip.communicator.util.Logger; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.gui.event.*; +import net.java.sip.communicator.service.resources.*; +import net.java.sip.communicator.util.*; import org.dts.spell.dictionary.*; /** * Wrapper for handling the multiple listeners associated with chats for the * spell checker. - * + * * @author Damian Johnson + * @author Purvesh Sahoo */ class ChatAttachments { - private static final Logger logger - = Logger.getLogger(ChatAttachments.class); - private static final ImageIcon ADD_WORD_ICON - = Resources.getImage(Resources.ADD_WORD_ICON); + private static final Logger logger = Logger + .getLogger(ChatAttachments.class); + + private static final ImageIcon ADD_WORD_ICON = Resources + .getImage(Resources.ADD_WORD_ICON); + private final Chat chat; - private final DocUnderliner docListener; //The red-squibble drawing code + + private final DocUnderliner docListener; // The red-squibble drawing code + private final CaretListener caretListener; + private final ChatMenuListener menuListener; + private boolean isEnabled = true; + private SpellDictionary dict; + private boolean isAttached = false; - ChatAttachments(Chat chat, SpellDictionary dict) + private final ResourceManagementService resources = Resources + .getResources(); + + private SpellCheckerConfigDialog dialog; + + ChatAttachments(Chat chat, final SpellDictionary dict) { this.chat = chat; this.dict = dict; @@ -56,8 +70,7 @@ class ChatAttachments { // thrown by spell checker API if problem occurs logger.error( - "Spell checker dictionary failed to be accessed", - exc); + "Spell checker dictionary failed to be accessed", exc); return false; } } @@ -77,21 +90,61 @@ class ChatAttachments this.menuListener = new ChatMenuListener() { - public List <JMenuItem> getMenuElements(Chat chat, MouseEvent event) //Overridden Here + + public List<JMenuItem> getMenuElements(final Chat chat, + MouseEvent event) { + if (isEnabled && event.getSource() instanceof JTextComponent) { JTextComponent comp = (JTextComponent) event.getSource(); int index = comp.viewToModel(event.getPoint()); + try + { + String compText = + comp.getDocument().getText(0, + comp.getDocument().getLength()); + + if (index != -1 && compText.length() != 0) + { + + return getCorrections(Word.getWord( + comp.getDocument().getText(0, + comp.getDocument().getLength()), index, + false)); + + } - if (index != -1 && comp.getText().length() != 0) + } + catch (BadLocationException e) { - return getCorrections(Word.getWord(comp.getText(), - index, false)); + // TODO Auto-generated catch block + e.printStackTrace(); } } - return new ArrayList <JMenuItem>(); + JMenuItem spellCheck = + new JMenuItem( + resources.getI18NString("plugin.spellcheck.MENU")); + + ArrayList<JMenuItem> spellCheckItem = + new ArrayList<JMenuItem>(); + spellCheckItem.add(spellCheck); + spellCheck.addActionListener(new ActionListener() + { + + public void actionPerformed(ActionEvent e) + { + if(dialog != null) { + dialog.dispose(); + } + dialog = + new SpellCheckerConfigDialog(chat, null, dict); + dialog.setVisible(true); + } + }); + + return spellCheckItem; } }; } @@ -106,6 +159,7 @@ class ChatAttachments this.chat.addChatEditorDocumentListener(this.docListener); this.chat.addChatEditorCaretListener(this.caretListener); this.chat.addChatEditorMenuListener(this.menuListener); + } } @@ -119,6 +173,7 @@ class ChatAttachments this.chat.removeChatEditorDocumentListener(this.docListener); this.chat.removeChatEditorCaretListener(this.caretListener); this.chat.removeChatEditorMenuListener(this.menuListener); + } } @@ -146,22 +201,23 @@ class ChatAttachments } // provides popup menu entries (mostly separated for readability) - private ArrayList <JMenuItem> getCorrections(final Word clickedWord) + private ArrayList<JMenuItem> getCorrections(final Word clickedWord) { - ArrayList <JMenuItem> correctionEntries = new ArrayList <JMenuItem>(); + ArrayList<JMenuItem> correctionEntries = new ArrayList<JMenuItem>(); synchronized (this.dict) { if (!this.dict.isCorrect(clickedWord.getText())) { - List <String> corrections = - this.dict.getSuggestions(clickedWord.getText()); + List<String> corrections = + this.dict.getSuggestions(clickedWord.getText()); for (String correction : corrections) { JMenuItem newEntry = new JMenuItem(correction); newEntry.addActionListener(new CorrectionListener( - clickedWord, correction)); + clickedWord, correction)); correctionEntries.add(newEntry); + } // entry to add word @@ -181,16 +237,38 @@ class ChatAttachments catch (SpellDictionaryException exc) { String msg = - "Unable to add word to personal dictionary"; + "Unable to add word to personal dictionary"; logger.error(msg, exc); } } }); correctionEntries.add(addWord); + } - } + + JMenuItem spellCheck = + new JMenuItem( + resources.getI18NString("plugin.spellcheck.MENU")); + correctionEntries.add(spellCheck); + spellCheck.addActionListener(new ActionListener() + { + + public void actionPerformed(ActionEvent e) + { + if(dialog != null) { + dialog.dispose(); + } + + dialog = + new SpellCheckerConfigDialog(chat, clickedWord, + dict); + dialog.setVisible(true); + } + }); + } return correctionEntries; + } // Applies corrections from popup menu to chat @@ -198,6 +276,7 @@ class ChatAttachments implements ActionListener { private Word clickedWord; + private String correction; CorrectionListener(Word clickedWord, String correction) @@ -209,11 +288,12 @@ class ChatAttachments public void actionPerformed(ActionEvent event) { StringBuffer newMessage = new StringBuffer(chat.getMessage()); + int endIndex = - this.clickedWord.getStart() - + this.clickedWord.getText().length(); + this.clickedWord.getStart() + + this.clickedWord.getText().length(); newMessage.replace(this.clickedWord.getStart(), endIndex, - this.correction); + this.correction); chat.setMessage(newMessage.toString()); } } diff --git a/src/net/java/sip/communicator/plugin/spellcheck/DocUnderliner.java b/src/net/java/sip/communicator/plugin/spellcheck/DocUnderliner.java index 6935b68..6ca51c0 100644 --- a/src/net/java/sip/communicator/plugin/spellcheck/DocUnderliner.java +++ b/src/net/java/sip/communicator/plugin/spellcheck/DocUnderliner.java @@ -1,8 +1,7 @@ /* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. + * + * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.sip.communicator.plugin.spellcheck; @@ -18,74 +17,83 @@ import net.java.sip.communicator.util.Logger; * Notifies subclasses when words are changed and lets them decide if text * should be underlined with a red squiggle. Text appended to the end isn't * formatted until the word's completed. + * * @author Damian Johnson */ abstract class DocUnderliner implements DocumentListener { private static final Logger logger = Logger.getLogger(DocUnderliner.class); + private static final Color UNDERLINE_COLOR = new Color(255, 100, 100); + private static final DefaultHighlighter.DefaultHighlightPainter UNDERLINER; + private final Highlighter docHighlighter; + private final CaretListener endChecker; + private boolean isEnabled = true; static { UNDERLINER = - new DefaultHighlighter.DefaultHighlightPainter(UNDERLINE_COLOR) + new DefaultHighlighter.DefaultHighlightPainter(UNDERLINE_COLOR) + { + public Shape paintLayer(Graphics g, int offs0, int offs1, + Shape area, JTextComponent comp, View view) { - public Shape paintLayer(Graphics g, int offs0, int offs1, - Shape area, JTextComponent comp, View view) - { - Color color = getColor(); - if (color == null) g.setColor(comp.getSelectionColor()); - else g.setColor(color); + Color color = getColor(); + if (color == null) + g.setColor(comp.getSelectionColor()); + else + g.setColor(color); - if (offs0 == view.getStartOffset() - && offs1 == view.getEndOffset()) + if (offs0 == view.getStartOffset() + && offs1 == view.getEndOffset()) + { + // contained in view, can just use bounds + drawWavyLine(g, area.getBounds()); + return area; + } + else + { + // should only render part of View + try { - // contained in view, can just use bounds - drawWavyLine(g, area.getBounds()); - return area; + Shape shape = + view.modelToView(offs0, Position.Bias.Forward, + offs1, Position.Bias.Backward, area); + drawWavyLine(g, shape.getBounds()); + return shape.getBounds(); } - else + catch (BadLocationException exc) { - // should only render part of View - try - { - Shape shape = - view.modelToView(offs0, - Position.Bias.Forward, offs1, - Position.Bias.Backward, area); - drawWavyLine(g, shape.getBounds()); - return shape.getBounds(); - } - catch (BadLocationException exc) - { - String msg = - "Bad bounds (programmer error in spell checker)"; - logger.error(msg, exc); - return area; // can't render - } + String msg = + "Bad bounds (programmer error in spell checker)"; + logger.error(msg, exc); + return area; // can't render } } + } - private void drawWavyLine(Graphics g, Rectangle bounds) - { - int y = (int) (bounds.getY() + bounds.getHeight()); - int x1 = (int) bounds.getX(); - int x2 = (int) (bounds.getX() + bounds.getWidth()); + private void drawWavyLine(Graphics g, Rectangle bounds) + { + int y = (int) (bounds.getY() + bounds.getHeight()); + int x1 = (int) bounds.getX(); + int x2 = (int) (bounds.getX() + bounds.getWidth()); - boolean upperCurve = true; - for (int i = x1; i < x2 - 2; i += 3) - { - if (upperCurve) g.drawArc(i, y - 2, 3, 3, 0, 180); - else g.drawArc(i, y - 2, 3, 3, 180, 180); - upperCurve = !upperCurve; - } + boolean upperCurve = true; + for (int i = x1; i < x2 - 2; i += 3) + { + if (upperCurve) + g.drawArc(i, y - 2, 3, 3, 0, 180); + else + g.drawArc(i, y - 2, 3, 3, 180, 180); + upperCurve = !upperCurve; } - }; + } + }; } { @@ -105,7 +113,7 @@ abstract class DocUnderliner { String text = comp.getText(); Word changed = - Word.getWord(text, text.length() - 1, false); + Word.getWord(text, text.length() - 1, false); format(changed); promptRepaint(); } @@ -120,6 +128,7 @@ abstract class DocUnderliner * Queries to see if a word should be underlined. This is called on every * internal change and whenever a word's completed so it should be a * lightweight process. + * * @param word word to be checked * @return true if the word should be underlined, false otherwise */ @@ -127,6 +136,7 @@ abstract class DocUnderliner /** * Provides the index of the character the cursor is in front of. + * * @return index of caret */ abstract int getCaretPosition(); @@ -146,23 +156,23 @@ abstract class DocUnderliner editorPane.setPreferredSize(new Dimension(400, 500)); final DocUnderliner formatter = - new DocUnderliner(editorPane.getHighlighter()) + new DocUnderliner(editorPane.getHighlighter()) + { + boolean getFormatting(String word) { - boolean getFormatting(String word) - { - return word.contains("foo"); - } + return word.contains("foo"); + } - int getCaretPosition() - { - return editorPane.getCaretPosition(); - } + int getCaretPosition() + { + return editorPane.getCaretPosition(); + } - void promptRepaint() - { - editorPane.repaint(); - } - }; + void promptRepaint() + { + editorPane.repaint(); + } + }; editorPane.getDocument().addDocumentListener(formatter); editorPane.addCaretListener(formatter.getEndChecker()); @@ -178,7 +188,8 @@ abstract class DocUnderliner public void insertUpdate(DocumentEvent event) { - if (!this.isEnabled) return; + if (!this.isEnabled) + return; try { @@ -201,7 +212,7 @@ abstract class DocUnderliner // new character at end (ensure it isn't initially // underlined) clearUnderlining(event.getOffset(), - event.getOffset() + 1); + event.getOffset() + 1); } } else @@ -211,9 +222,11 @@ abstract class DocUnderliner // change within word Word changed; int previousIndex = Math.max(0, event.getOffset() - 1); - if (Character.isLetter(text.charAt(previousIndex))) changed = + if (Character.isLetter(text.charAt(previousIndex))) + changed = Word.getWord(text, event.getOffset(), true); - else changed = + else + changed = Word.getWord(text, event.getOffset(), false); format(changed); } @@ -221,11 +234,9 @@ abstract class DocUnderliner { // dividing a word - need to check both sides Word firstWord = - Word.getWord(text, event.getOffset(), true); + Word.getWord(text, event.getOffset(), true); Word secondWord = - Word - .getWord(text, event.getOffset() + 1, - false); + Word.getWord(text, event.getOffset() + 1, false); format(firstWord); format(secondWord); } @@ -241,9 +252,8 @@ abstract class DocUnderliner { format(changed); int end = - Math.min(changed.getStart() - + changed.getText().length() + 1, text - .length()); + Math.min(changed.getStart() + + changed.getText().length() + 1, text.length()); changed = Word.getWord(text, end, false); wordStart = end; } @@ -260,7 +270,8 @@ abstract class DocUnderliner public void removeUpdate(DocumentEvent event) { - if (!this.isEnabled) return; + if (!this.isEnabled) + return; try { @@ -270,8 +281,7 @@ abstract class DocUnderliner { Word changed; if (event.getOffset() == 0 - || !Character.isLetter(text - .charAt(event.getOffset() - 1))) + || !Character.isLetter(text.charAt(event.getOffset() - 1))) { changed = Word.getWord(text, event.getOffset(), false); } @@ -293,11 +303,13 @@ abstract class DocUnderliner } public void changedUpdate(DocumentEvent e) - {} + { + } /** * Provides a listener that prompts the last word to be checked when the * cursor moves away from it. + * * @return listener for caret position that formats last word when * appropriate */ @@ -308,24 +320,26 @@ abstract class DocUnderliner /** * Formats the word with the appropriate underlining (or lack thereof). + * * @param word word to be formatted */ public void format(Word word) { - if (!this.isEnabled) return; + if (!this.isEnabled) + return; String text = word.getText(); if (text.length() > 0) { clearUnderlining(word.getStart(), word.getStart() + text.length()); - if (getFormatting(text)) underlineRange(word.getStart(), word - .getStart() - + text.length()); + if (getFormatting(text)) + underlineRange(word.getStart(), word.getStart() + text.length()); } } /** * Sets a range in the editor to be underlined. + * * @param start start of range to be underlined * @param end end of range to be underlined */ @@ -335,8 +349,8 @@ abstract class DocUnderliner { try { - if (this.isEnabled) this.docHighlighter.addHighlight(start, - end, UNDERLINER); + if (this.isEnabled) + this.docHighlighter.addHighlight(start, end, UNDERLINER); } catch (BadLocationException exc) { @@ -350,6 +364,7 @@ abstract class DocUnderliner * Clears any underlining that spans to include the given range. Since * formatting is defined by ranges this will likely clear more than the * defined range. + * * @param start start of range in which to clear underlining * @param end end of range in which to clear underlining */ @@ -361,12 +376,12 @@ abstract class DocUnderliner if (this.isEnabled) { for (Highlighter.Highlight highlight : this.docHighlighter - .getHighlights()) + .getHighlights()) { if ((highlight.getStartOffset() <= start && highlight - .getEndOffset() > start) - || (highlight.getStartOffset() < end && highlight - .getEndOffset() >= end)) + .getEndOffset() > start) + || (highlight.getStartOffset() < end && highlight + .getEndOffset() >= end)) { this.docHighlighter.removeHighlight(highlight); } @@ -380,18 +395,23 @@ abstract class DocUnderliner if (this.isEnabled != enable) { this.isEnabled = enable; - if (this.isEnabled) reset(message); - else this.docHighlighter.removeAllHighlights(); + if (this.isEnabled) + reset(message); + else + this.docHighlighter.removeAllHighlights(); promptRepaint(); } } /** * Clears underlining and re-evaluates message's contents + * * @param message textual contents of document */ - public void reset(String message) { - if (!this.isEnabled) return; + public void reset(String message) + { + if (!this.isEnabled) + return; // clears previous underlined sections this.docHighlighter.removeAllHighlights(); @@ -405,9 +425,8 @@ abstract class DocUnderliner { format(changed); int end = - Math.min(changed.getStart() - + changed.getText().length() + 1, message - .length()); + Math.min(changed.getStart() + changed.getText().length() + + 1, message.length()); changed = Word.getWord(message, end, false); wordStart = end; } diff --git a/src/net/java/sip/communicator/plugin/spellcheck/LanguageSelectionField.java b/src/net/java/sip/communicator/plugin/spellcheck/LanguageSelectionField.java new file mode 100644 index 0000000..2986ed2 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/spellcheck/LanguageSelectionField.java @@ -0,0 +1,508 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.spellcheck; + +import java.awt.*; +import java.awt.image.*; + +import java.io.*; +import java.util.*; + +import javax.swing.*; +import javax.swing.event.*; + +import net.java.sip.communicator.plugin.spellcheck.Parameters.Default; +import net.java.sip.communicator.service.contactlist.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.gui.Container; +import net.java.sip.communicator.service.protocol.Contact; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.swing.*; +import net.java.sip.communicator.util.swing.SwingWorker; + +/** + * Combo box providing a listing of all available locales with corresponding + * country flags. Selecting a new field causes that locale's dictionary to be + * downloaded, if not available. The spell checker then use the selected + * language for further checking. + * + * @author Damian Johnson + * @author Yana Stamcheva + */ +public class LanguageSelectionField + extends SIPCommMenuBar + implements PluginComponent +{ + private static final HashMap<SpellChecker, LanguageSelectionField> + CLASS_INSTANCES = + new HashMap<SpellChecker, LanguageSelectionField>(); + + // parallel maps containing cached instances of country flags + private static final HashMap<Parameters.Locale, ImageIcon> + AVAILABLE_FLAGS = new HashMap<Parameters.Locale, ImageIcon>(); + + private static final HashMap<Parameters.Locale, ImageIcon> + UNAVAILABLE_FLAGS = new HashMap<Parameters.Locale, ImageIcon>(); + + private static final Logger logger = Logger + .getLogger(LanguageSelectionField.class); + + private static final ImageIcon BLANK_FLAG_ICON = Resources + .getImage("blankFlag"); + + private final ListCellRenderer languageSelectionRenderer; + + private final HashMap<Parameters.Locale, Boolean> localeAvailabilityCache = + new HashMap<Parameters.Locale, Boolean>(); + + private final SpellChecker spellChecker; + + private final SIPCommMenu menu = new SelectorMenu(); + + private final ArrayList<Parameters.Locale> localeList = + new ArrayList<Parameters.Locale>(); + + /** + * Provides instance of this class associated with a spell checker. If ones + * already been created then this instance is used. + * + * @param checker spell checker field is to be associated with + * @return spell checker locale selection field + */ + public synchronized static LanguageSelectionField makeSelectionField( + SpellChecker checker) + { + // singleton constructor to ensure only one combo box is associated with + // each checker + if (CLASS_INSTANCES.containsKey(checker)) + return CLASS_INSTANCES.get(checker); + else + { + LanguageSelectionField instance = + new LanguageSelectionField(checker); + CLASS_INSTANCES.put(checker, instance); + return instance; + } + } + + private LanguageSelectionField(SpellChecker checker) + { + this.spellChecker = checker; + + setPreferredSize(new Dimension(30, 28)); + setMaximumSize(new Dimension(30, 28)); + setMinimumSize(new Dimension(30, 28)); + + this.menu.setPreferredSize(new Dimension(30, 45)); + this.menu.setMaximumSize(new Dimension(30, 45)); + + this.add(menu); + + this.setBorder(null); + this.menu.add(createEnableCheckBox()); + this.menu.addSeparator(); + this.menu.setBorder(null); + this.menu.setOpaque(false); + this.setOpaque(false); + + DefaultListModel model = new DefaultListModel(); + final JList list = new JList(model); + + this.languageSelectionRenderer = new LanguageListRenderer(); + + for (Parameters.Locale locale : Parameters.getLocales()) + { + + if (!localeAvailabilityCache.containsKey(locale)) + { + localeAvailabilityCache.put(locale, + spellChecker.isLocaleAvailable(locale)); + } + + model.addElement(locale); + localeList.add(locale); + } + + JScrollPane scroll = new JScrollPane(list); + scroll.setBorder(null); + + String localeIso = Parameters.getDefault(Default.LOCALE); + Parameters.Locale loc = Parameters.getLocale(localeIso); + + list.setCellRenderer(languageSelectionRenderer); + list.setSelectedIndex(localeList.indexOf(loc) + 1); + + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + list.addListSelectionListener(new ListSelectionListener() + { + + public void valueChanged(ListSelectionEvent e) + { + + if (!e.getValueIsAdjusting()) + { + final JList source = (JList) e.getSource(); + final Parameters.Locale locale = + (Parameters.Locale) source.getSelectedValue(); + + source.setEnabled(false); + + // Indicate to the user that the language is currently + // loading. + locale.setLoading(true); + + new SetSpellChecker(locale, source).start(); + } + } + }); + + menu.add(scroll); + + ImageIcon flagIcon = + getLocaleIcon(checker.getLocale(), + localeAvailabilityCache.get(checker.getLocale())); + SelectedObject selectedObject = + new SelectedObject(flagIcon, checker.getLocale()); + menu.setSelected(selectedObject); + } + + /** + * Clears any cached data used by the field so it reflects the current state + * of its associated spell checker. + */ + // public void revalidate() + // { + // this.localeAvailabilityCache.clear(); + // this.field.setSelectedItem(this.spellChecker.getLocale()); + // } + + public String getConstraints() + { + return Container.RIGHT; + } + + public Container getContainer() + { + return Container.CONTAINER_CHAT_TOOL_BAR; + } + + public String getName() + { + return "Spell Checker Toggle"; + } + + public int getPositionIndex() + { + return -1; + } + + public boolean isNativeComponent() + { + return false; + } + + public void setCurrentContact(MetaContact metaContact) + { + + } + + public void setCurrentContactGroup(MetaContactGroup metaGroup) + { + + } + + private static ImageIcon getLocaleIcon(Parameters.Locale locale, + boolean isAvailable) + { + if (isAvailable && AVAILABLE_FLAGS.containsKey(locale)) + return AVAILABLE_FLAGS.get(locale); + else if (!isAvailable && UNAVAILABLE_FLAGS.containsKey(locale)) + return UNAVAILABLE_FLAGS.get(locale); + else + { + // load resource + ImageIcon localeFlag; + + try + { + int commaIndex = locale.getIsoCode().indexOf(","); + String countryCode = + locale.getIsoCode().substring(commaIndex + 1); + localeFlag = Resources.getFlagImage(countryCode); + + BufferedImage flagBuffer = copy(localeFlag.getImage()); + setFaded(flagBuffer); + ImageIcon unavailableLocaleFlag = new ImageIcon(flagBuffer); + + AVAILABLE_FLAGS.put(locale, localeFlag); + UNAVAILABLE_FLAGS.put(locale, unavailableLocaleFlag); + return isAvailable ? localeFlag : unavailableLocaleFlag; + } + catch (IOException exc) + { + AVAILABLE_FLAGS.put(locale, BLANK_FLAG_ICON); + UNAVAILABLE_FLAGS.put(locale, BLANK_FLAG_ICON); + return BLANK_FLAG_ICON; + } + } + } + + /** + * Creates a deep copy of an image. + * + * @param image picture to be processed + * @return copy of the image + */ + private static BufferedImage copy(Image image) + { + int width = image.getWidth(null); + int height = image.getHeight(null); + + BufferedImage copy; + try + { + PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false); + pg.grabPixels(); + ColorModel cm = pg.getColorModel(); + + WritableRaster raster = + cm.createCompatibleWritableRaster(width, height); + boolean isRasterPremultiplied = cm.isAlphaPremultiplied(); + copy = new BufferedImage(cm, raster, isRasterPremultiplied, null); + } + catch (InterruptedException e) + { + copy = + new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + } + + Graphics2D g2 = copy.createGraphics(); + g2.setComposite(AlphaComposite.Src); // Preserves color of + // transparent pixels + g2.drawImage(image, 0, 0, null); + g2.dispose(); + return copy; + } + + /** + * Removes all color from an image and makes it partly translucent. Original + * grayscale method written by Marty Stepp. + * + * @param image picture to be processed + */ + private static void setFaded(BufferedImage image) + { + int width = image.getWidth(); + int height = image.getHeight(); + + for (int row = 0; row < width; ++row) + { + for (int col = 0; col < height; ++col) + { + int c = image.getRGB(row, col); + + int r = + (((c >> 16) & 0xff) + ((c >> 8) & 0xff) + (c & 0xff)) / 3; + + int newRgb = (0xff << 24) | (r << 16) | (r << 8) | r; + newRgb &= (1 << 24) - 1; // Blanks alpha value + newRgb |= 128 << 24; // Resets it to the alpha of 128 + image.setRGB(row, col, newRgb); + } + } + } + + public void setCurrentContact(Contact contact) + { + // TODO Auto-generated method stub + + } + + private class SelectorMenu + extends SIPCommMenu + { + Image image = Resources.getImage("service.gui.icons.DOWN_ARROW_ICON") + .getImage(); + + public void paintComponent(Graphics g) + { + super.paintComponent(g); + + g.drawImage(image, getWidth() - image.getWidth(this) - 1, + (getHeight() - image.getHeight(this) - 1) / 2, this); + } + } + + /** + * Returns the enable spell check checkbox. + * + * @return the created checkbox + */ + private JCheckBox createEnableCheckBox() + { + final JCheckBox checkBox = new SIPCommCheckBox( + Resources.getString("plugin.spellcheck.ENABLE_SPELL_CHECK")); + + checkBox.setSelected(spellChecker.isEnabled()); + checkBox.setIconTextGap(0); + checkBox.addChangeListener(new ChangeListener() + { + public void stateChanged(ChangeEvent evt) + { + spellChecker.setEnabled(checkBox.isSelected()); + } + }); + + return checkBox; + } + + private class SetSpellChecker extends SwingWorker + { + private final Parameters.Locale locale; + + private final JList sourceList; + + private boolean skipFiring = false; + + public SetSpellChecker( Parameters.Locale locale, + JList sourceList) + { + this.locale = locale; + this.sourceList = sourceList; + } + + /** + * Called on the event dispatching thread (not on the worker thread) + * after the <code>construct</code> method has returned. + */ + public void finished() + { + if (getValue() != null) + { + sourceList.setEnabled(true); + + localeAvailabilityCache.put(locale, true); + + ImageIcon flagIcon = getLocaleIcon(locale, + localeAvailabilityCache.get(locale)); + + SelectedObject selectedObject = + new SelectedObject(flagIcon, locale); + + menu.setSelected(selectedObject); + } + else + { + // reverts selection + skipFiring = true; + + // source.setSelectedItem(spellChecker.getLocale()); + ImageIcon flagIcon = + getLocaleIcon(locale, + localeAvailabilityCache.get(locale)); + + SelectedObject selectedObject = + new SelectedObject(flagIcon, locale); + + menu.setSelected(selectedObject); + + skipFiring = false; + + sourceList.setEnabled(true); + } + + // Indicate to the user that the language is currently + // loading. + locale.setLoading(false); + } + + /** + * Download the dictionary. + */ + public Object construct() throws Exception + { + try + { + // prevents potential infinite loop during errors + if (this.skipFiring) + return null; + + spellChecker.setLocale(locale); + + return locale; + } + catch (Exception exc) + { + logger.warn( + "Unable to retrieve dictionary for " + locale, exc); + + // warns that it didn't work + PopupDialog dialog = + SpellCheckActivator.getUIService() + .getPopupDialog(); + String message + = Resources.getString( + "plugin.spellcheck.DICT_ERROR"); + if (exc instanceof IOException) + { + message = Resources.getString( + "plugin.spellcheck.DICT_RETRIEVE_ERROR") + + ":\n" + locale.getDictUrl(); + } + else if (exc instanceof IllegalArgumentException) + { + message = Resources.getString( + "plugin.spellcheck.DICT_PROCESS_ERROR"); + } + + dialog.showMessagePopupDialog( + message, + Resources + .getString("plugin.spellcheck.DICT_ERROR_TITLE"), + PopupDialog.WARNING_MESSAGE); + } + + return null; + } + } + + /** + * A custom renderer for languages list, transforming a Locale to a row + * with an icon and text. + */ + private class LanguageListRenderer + extends DefaultListCellRenderer + { + public Component getListCellRendererComponent(JList list, + Object value, int index, boolean isSelected, + boolean cellHasFocus) + { + Parameters.Locale locale = (Parameters.Locale) value; + + if (!localeAvailabilityCache.containsKey(locale)) + { + localeAvailabilityCache.put(locale, + spellChecker.isLocaleAvailable(locale)); + } + + ImageIcon flagIcon = + getLocaleIcon(locale, localeAvailabilityCache.get(locale)); + + String localeLabel = locale.getLabel(); + + if (locale.isLoading()) + setText("<html>" + localeLabel + + " <font color='gray'><i>loading...</i></font><html>"); + else + setText(localeLabel); + + setIcon(flagIcon); + + return this; + } + } +} diff --git a/src/net/java/sip/communicator/plugin/spellcheck/Parameters.java b/src/net/java/sip/communicator/plugin/spellcheck/Parameters.java index 7c3db38..2dd458a 100644 --- a/src/net/java/sip/communicator/plugin/spellcheck/Parameters.java +++ b/src/net/java/sip/communicator/plugin/spellcheck/Parameters.java @@ -1,8 +1,7 @@ /* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. + * + * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.sip.communicator.plugin.spellcheck; @@ -20,27 +19,32 @@ import org.xml.sax.SAXException; /** * Information provided via the spellchecer's xml parameters. - * + * * @author Damian Johnson */ class Parameters { private static final Logger logger = Logger.getLogger(Parameters.class); - private static final String RESOURCE_LOC - = "/resources/config/spellcheck/parameters.xml"; + private static final String RESOURCE_LOC = + "resources/config/spellcheck/parameters.xml"; + private static final String NODE_DEFAULTS = "defaults"; + private static final String NODE_LOCALES = "locales"; - private static final HashMap <Default, String> DEFAULTS - = new HashMap <Default, String>(); - private static final ArrayList <Locale> LOCALES = new ArrayList <Locale>(); + + private static final HashMap<Default, String> DEFAULTS = + new HashMap<Default, String>(); + + private static final ArrayList<Locale> LOCALES = new ArrayList<Locale>(); static { try { - URL url = SpellCheckActivator.bundleContext - .getBundle().getResource(RESOURCE_LOC); + URL url = + SpellCheckActivator.bundleContext.getBundle().getResource( + RESOURCE_LOC); InputStream stream = url.openStream(); @@ -48,10 +52,10 @@ class Parameters throw new IOException(); // strict parsing options - DocumentBuilderFactory factory - = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory factory = + DocumentBuilderFactory.newInstance(); - factory.setValidating(true); + factory.setValidating(false); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); @@ -103,7 +107,7 @@ class Parameters /** * Retrieves default values from xml. - * + * * @param list the configuration list */ private static void parseDefaults(NodeList list) @@ -128,6 +132,7 @@ class Parameters /** * Populates LOCALES list with contents of xml. + * * @param list the configuration list */ private static void parseLocales(NodeList list) @@ -138,10 +143,9 @@ class Parameters NamedNodeMap attributes = node.getAttributes(); String label = ((Attr) attributes.getNamedItem("label")).getValue(); String code = - ((Attr) attributes.getNamedItem("isoCode")).getValue(); + ((Attr) attributes.getNamedItem("isoCode")).getValue(); String dictLocation = - ((Attr) attributes.getNamedItem("dictionaryUrl")) - .getValue(); + ((Attr) attributes.getNamedItem("dictionaryUrl")).getValue(); try { LOCALES.add(new Locale(label, code, new URL(dictLocation))); @@ -149,13 +153,14 @@ class Parameters catch (MalformedURLException exc) { logger.warn("Unable to parse dictionary location of " + label - + " (" + dictLocation + ")", exc); + + " (" + dictLocation + ")", exc); } } } /** * Provides the value of a particular default field, null if undefined. + * * @param field default field to retrieve * @return value corresponding to default field */ @@ -166,6 +171,7 @@ class Parameters /** * Provides locale with a given iso code. Null if undefined. + * * @param isoCode iso code of locale to be retrieved * @return locale with corresponding iso code */ @@ -173,7 +179,8 @@ class Parameters { for (Locale locale : LOCALES) { - if (locale.getIsoCode().equals(isoCode)) return locale; + if (locale.getIsoCode().equals(isoCode)) + return locale; } return null; @@ -181,11 +188,12 @@ class Parameters /** * Provides locales in which dictionary resources are available. + * * @return locations with dictionary resources */ - public static ArrayList <Locale> getLocales() + public static ArrayList<Locale> getLocales() { - return new ArrayList <Locale>(LOCALES); + return new ArrayList<Locale>(LOCALES); } /** @@ -194,9 +202,13 @@ class Parameters public static class Locale { private final String label; + private final String isoCode; + private final URL dictLocation; + private boolean isLoading = false; + private Locale(String label, String isoCode, URL dictLocation) { this.label = label; @@ -206,6 +218,7 @@ class Parameters /** * Provides user readable name of language. + * * @return name of language presented to user */ public String getLabel() @@ -216,6 +229,7 @@ class Parameters /** * Provides ISO code as defined by:<br /> * http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + * * @return iso code */ public String getIsoCode() @@ -226,6 +240,7 @@ class Parameters /** * Provides the url where the dictionary resource can be found for this * language. + * * @return url of dictionary resource */ public URL getDictUrl() @@ -233,6 +248,29 @@ class Parameters return this.dictLocation; } + /** + * Sets the loading property. Indicates if this locale is currently + * loaded in the list. + * + * @param loading indicates if this locale is currently loading in the + * locales list + */ + public void setLoading(boolean loading) + { + this.isLoading = loading; + } + + /** + * Indicates if this locale is currenly loading in the list of locales. + * + * @return <tt>true</tt> if the locale is loading, <tt>false</tt> - + * otherwise + */ + public boolean isLoading() + { + return isLoading; + } + @Override public String toString() { @@ -256,6 +294,7 @@ class Parameters /** * Returns the enum representation of a string. This is case sensitive. + * * @param str toString representation of a default field * @return default field associated with a string * @throws IllegalArgumentException if argument is not represented by a diff --git a/src/net/java/sip/communicator/plugin/spellcheck/Resources.java b/src/net/java/sip/communicator/plugin/spellcheck/Resources.java index 61da50e..e7a5cc2 100644 --- a/src/net/java/sip/communicator/plugin/spellcheck/Resources.java +++ b/src/net/java/sip/communicator/plugin/spellcheck/Resources.java @@ -1,8 +1,7 @@ /* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. + * + * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.sip.communicator.plugin.spellcheck; @@ -20,7 +19,7 @@ import org.osgi.framework.*; /** * The <tt>Resources</tt> class manages the access to the internationalization * properties files and the image resources used in this plugin. - * + * * @author Damian Johnson * @author Yana Stamcheva */ @@ -34,40 +33,38 @@ public class Resources * Location of flag resources. */ private static final String FLAG_PATH = - "resources/images/plugin/spellchecker/flags/"; + "resources/images/plugin/spellcheck/flags/"; /** * The spell check plugin icon, shown in the configuration form. */ - public static final String PLUGIN_ICON - = "plugin.spellcheck.PLUGIN_ICON"; + public static final String PLUGIN_ICON = "plugin.spellcheck.PLUGIN_ICON"; /** * The add word icon. */ - public static final String ADD_WORD_ICON - = "plugin.spellcheck.ADD_WORD_ICON"; + public static final String ADD_WORD_ICON = + "plugin.spellcheck.ADD_WORD_ICON"; /** * The personal dictionary icon. */ - public static final String PERSONAL_DICTIONARY - = "plugin.spellcheck.PERSONAL_DIR"; + public static final String PERSONAL_DICTIONARY = + "plugin.spellcheck.PERSONAL_DIR"; /** * The word include icon. */ - public static final String WORD_INCLUDE - = "plugin.spellcheck.WORD_INCLUDE"; + public static final String WORD_INCLUDE = "plugin.spellcheck.WORD_INCLUDE"; /** * The word exclude icon. */ - public static final String WORD_EXCLUDE - = "plugin.spellcheck.WORD_EXCLUDE"; + public static final String WORD_EXCLUDE = "plugin.spellcheck.WORD_EXCLUDE"; /** * Returns an internationalized string corresponding to the given key. + * * @param key The key of the string. * @return An internationalized string corresponding to the given key. */ @@ -78,6 +75,7 @@ public class Resources /** * Loads an image from a given image identifier. + * * @param imageID The identifier of the image. * @return The image for the given identifier. */ @@ -88,7 +86,7 @@ public class Resources /** * Loads a flag image from a given image identifier. - * + * * @param resource iso code for flag to be retrieved. * @return icon reflecting iso code * @throws IOException if no such resource is available @@ -108,6 +106,7 @@ public class Resources /** * Loads an image from a given image identifier. + * * @param imageID The identifier of the image. * @return The image for the given identifier. */ @@ -119,21 +118,21 @@ public class Resources /** * Returns the <tt>ResourceManagementService</tt> through which we obtain * resources like images and localized texts. - * + * * @return the <tt>ResourceManagementService</tt> */ - private static ResourceManagementService getResources() + public static ResourceManagementService getResources() { if (resourceService != null) return resourceService; - ServiceReference configServiceRef - = SpellCheckActivator.bundleContext.getServiceReference( - ResourceManagementService.class.getName()); + ServiceReference configServiceRef = + SpellCheckActivator.bundleContext + .getServiceReference(ResourceManagementService.class.getName()); - resourceService - = (ResourceManagementService) SpellCheckActivator.bundleContext - .getService(configServiceRef); + resourceService = + (ResourceManagementService) SpellCheckActivator.bundleContext + .getService(configServiceRef); return resourceService; } diff --git a/src/net/java/sip/communicator/plugin/spellcheck/SpellCheckActivator.java b/src/net/java/sip/communicator/plugin/spellcheck/SpellCheckActivator.java index dd0b414..f4427b8 100644 --- a/src/net/java/sip/communicator/plugin/spellcheck/SpellCheckActivator.java +++ b/src/net/java/sip/communicator/plugin/spellcheck/SpellCheckActivator.java @@ -1,11 +1,12 @@ /* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. + * + * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.sip.communicator.plugin.spellcheck; +import java.util.*; + import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.fileaccess.*; import net.java.sip.communicator.service.gui.*; @@ -14,7 +15,7 @@ import org.osgi.framework.*; /** * Enabling and disabling osgi functionality for the spell checker. - * + * * @author Damian Johnson */ public class SpellCheckActivator @@ -32,38 +33,30 @@ public class SpellCheckActivator /** * Called when this bundle is started. + * * @param context The execution context of the bundle being started. */ public void start(BundleContext context) throws Exception { bundleContext = context; -// Disables spell checker until we fix also the configuration form in -// order to be able to change the language. -// -// this.checker.start(context); - -// SpellCheckerConfigForm checkerManager = -// new SpellCheckerConfigForm(this.checker); -// context.registerService(ConfigurationForm.class.getName(), -// checkerManager, null); -// -// // adds button to toggle spell checker -// Hashtable <String, String> containerFilter = -// new Hashtable <String, String>(); -// containerFilter.put(Container.CONTAINER_ID, -// Container.CONTAINER_CHAT_TOOL_BAR.getID()); -// context.registerService(PluginComponent.class.getName(), -// new CheckerToggleButton(this.checker), containerFilter); -// -// // adds field to change language -// context.registerService(PluginComponent.class.getName(), -// LanguageSelectionField.makeSelectionField(this.checker), -// containerFilter); + this.checker.start(context); + + // adds button to toggle spell checker + Hashtable<String, String> containerFilter = + new Hashtable<String, String>(); + containerFilter.put(Container.CONTAINER_ID, + Container.CONTAINER_CHAT_TOOL_BAR.getID()); + + // adds field to change language + context.registerService(PluginComponent.class.getName(), + LanguageSelectionField.makeSelectionField(this.checker), + containerFilter); } /** * Returns the <tt>UIService</tt>. + * * @return the <tt>UIService</tt> */ public static UIService getUIService() @@ -72,8 +65,8 @@ public class SpellCheckActivator return uiService; // retrieves needed services - ServiceReference uiServiceRef - = bundleContext.getServiceReference(UIService.class.getName()); + ServiceReference uiServiceRef = + bundleContext.getServiceReference(UIService.class.getName()); uiService = (UIService) bundleContext.getService(uiServiceRef); @@ -82,6 +75,7 @@ public class SpellCheckActivator /** * Returns the <tt>FileAccessService</tt>. + * * @return the <tt>FileAccessService</tt> */ public static FileAccessService getFileAccessService() @@ -89,17 +83,18 @@ public class SpellCheckActivator if (faService != null) return faService; - ServiceReference faServiceReference - = bundleContext.getServiceReference( - FileAccessService.class.getName()); - faService - = (FileAccessService) bundleContext.getService(faServiceReference); + ServiceReference faServiceReference = + bundleContext + .getServiceReference(FileAccessService.class.getName()); + faService = + (FileAccessService) bundleContext.getService(faServiceReference); return faService; } /** * Returns the <tt>ConfigurationService</tt>. + * * @return the <tt>ConfigurationService</tt> */ public static ConfigurationService getConfigService() @@ -107,12 +102,12 @@ public class SpellCheckActivator if (configService != null) return configService; - ServiceReference configServiceRef - = bundleContext.getServiceReference( - ConfigurationService.class.getName()); + ServiceReference configServiceRef = + bundleContext.getServiceReference(ConfigurationService.class + .getName()); - configService = (ConfigurationService) bundleContext - .getService(configServiceRef); + configService = + (ConfigurationService) bundleContext.getService(configServiceRef); return configService; } diff --git a/src/net/java/sip/communicator/plugin/spellcheck/SpellChecker.java b/src/net/java/sip/communicator/plugin/spellcheck/SpellChecker.java index 8138806..24f253e 100644 --- a/src/net/java/sip/communicator/plugin/spellcheck/SpellChecker.java +++ b/src/net/java/sip/communicator/plugin/spellcheck/SpellChecker.java @@ -1,8 +1,7 @@ /* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. + * + * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.sip.communicator.plugin.spellcheck; @@ -22,7 +21,7 @@ import org.osgi.framework.*; * Model for spell checking capabilities. This allows for the on-demand * retrieval of dictionaries in other languages which are cached with the user's * configurations. - * + * * @author Damian Johnson */ class SpellChecker @@ -30,12 +29,12 @@ class SpellChecker { private static final Logger logger = Logger.getLogger(SpellChecker.class); - private static final String LOCALE_CONFIG_PARAM - = "net.java.sip.communicator.plugin.spellchecker.LOCALE"; + private static final String LOCALE_CONFIG_PARAM = + "net.java.sip.communicator.plugin.spellchecker.LOCALE"; // default bundled dictionary - private static final String DEFAULT_DICT_PATH - = "/resources/config/spellcheck/en_US.zip"; + private static final String DEFAULT_DICT_PATH = + "/resources/config/spellcheck/"; // location where dictionaries are stored private static final String DICT_DIR = "spellingDictionaries/"; @@ -50,24 +49,29 @@ class SpellChecker * will cause an internal NullPointerException in the spell checker. */ private File personalDictLocation; + private File dictLocation; + private SpellDictionary dict; + private Parameters.Locale locale; // dictionary locale // chat instances the spell checker is currently attached to - private ArrayList <ChatAttachments> attachedChats = - new ArrayList <ChatAttachments>(); + private ArrayList<ChatAttachments> attachedChats = + new ArrayList<ChatAttachments>(); + private boolean isEnabled = true; /** * Associates spell checking capabilities with all chats. This doesn't do * anything if this is already running. + * * @param bc execution context of the bundle */ synchronized void start(BundleContext bc) throws Exception { - FileAccessService faService - = SpellCheckActivator.getFileAccessService(); + FileAccessService faService = + SpellCheckActivator.getFileAccessService(); // checks if DICT_DIR exists to see if this is the first run File dictionaryDir = faService.getPrivatePersistentFile(DICT_DIR); @@ -76,33 +80,44 @@ class SpellChecker { dictionaryDir.mkdir(); - // copy default dictionary so it doesn't need to be downloaded - URL dictURL = SpellCheckActivator.bundleContext - .getBundle().getResource(DEFAULT_DICT_PATH); + // copy default dictionaries so they don't need to be downloaded + @SuppressWarnings ("unchecked") + Enumeration<URL> dictUrls + = SpellCheckActivator.bundleContext.getBundle() + .findEntries(DEFAULT_DICT_PATH, + "*.zip", + false); - if (dictURL != null) + if (dictUrls != null) { - InputStream source = dictURL.openStream(); + while (dictUrls.hasMoreElements()) + { + URL dictUrl = dictUrls.nextElement(); - int filenameStart = DEFAULT_DICT_PATH.lastIndexOf('/') + 1; - String filename = DEFAULT_DICT_PATH.substring(filenameStart); - File dictLocation - = faService.getPrivatePersistentFile(DICT_DIR + filename); - copyDictionary(source, dictLocation); + InputStream source = dictUrl.openStream(); + + int filenameStart = dictUrl.getPath().lastIndexOf('/') + 1; + String filename = dictUrl.getPath().substring(filenameStart); + + File dictLocation = + faService.getPrivatePersistentFile(DICT_DIR + filename); + + copyDictionary(source, dictLocation); + } } } // gets resource for personal dictionary - this.personalDictLocation - = faService.getPrivatePersistentFile(DICT_DIR + PERSONAL_DICT_NAME); + this.personalDictLocation = + faService.getPrivatePersistentFile(DICT_DIR + PERSONAL_DICT_NAME); if (!personalDictLocation.exists()) personalDictLocation.createNewFile(); // gets dictionary locale - String localeIso - = SpellCheckActivator.getConfigService() - .getString(LOCALE_CONFIG_PARAM); + String localeIso = + SpellCheckActivator.getConfigService().getString( + LOCALE_CONFIG_PARAM); if (localeIso == null) { @@ -114,8 +129,9 @@ class SpellChecker } Parameters.Locale tmp = Parameters.getLocale(localeIso); - if (tmp == null) throw new Exception( - "No dictionary resources defined for locale: " + localeIso); + if (tmp == null) + throw new Exception("No dictionary resources defined for locale: " + + localeIso); this.locale = tmp; // needed for synchronization lock setLocale(tmp); // initializes dictionary and saves locale config @@ -169,18 +185,19 @@ class SpellChecker /** * Provides the user's list of words to be ignored by the spell checker. + * * @return user's word list */ - ArrayList <String> getPersonalWords() + ArrayList<String> getPersonalWords() { synchronized (this.personalDictLocation) { try { // Retrieves contents of the custom dictionary - ArrayList <String> customWords = new ArrayList <String>(); + ArrayList<String> customWords = new ArrayList<String>(); Scanner customDictScanner = - new Scanner(this.personalDictLocation); + new Scanner(this.personalDictLocation); while (customDictScanner.hasNextLine()) { customWords.add(customDictScanner.nextLine()); @@ -191,7 +208,7 @@ class SpellChecker catch (FileNotFoundException exc) { logger.error("Unable to read custom dictionary", exc); - return new ArrayList <String>(); + return new ArrayList<String>(); } } } @@ -199,9 +216,10 @@ class SpellChecker /** * Writes custom dictionary and updates spell checker to utilize new * listing. + * * @param words words to be ignored by the spell checker */ - void setPersonalWords(List <String> words) + void setPersonalWords(List<String> words) { synchronized (this.personalDictLocation) { @@ -209,8 +227,8 @@ class SpellChecker { // writes new word list BufferedWriter writer = - new BufferedWriter(new FileWriter( - this.personalDictLocation)); + new BufferedWriter( + new FileWriter(this.personalDictLocation)); for (String customWord : words) { @@ -224,10 +242,10 @@ class SpellChecker synchronized (this.attachedChats) { InputStream dictInput = - new FileInputStream(this.dictLocation); + new FileInputStream(this.dictLocation); this.dict = - new OpenOfficeSpellDictionary(dictInput, - this.personalDictLocation); + new OpenOfficeSpellDictionary(dictInput, + this.personalDictLocation); // updates chats for (ChatAttachments chat : this.attachedChats) @@ -239,7 +257,7 @@ class SpellChecker catch (IOException exc) { logger.error("Unable to access personal spelling dictionary", - exc); + exc); } } } @@ -247,6 +265,7 @@ class SpellChecker /** * Provides the locale of the dictionary currently being used by the spell * checker. + * * @return locale of current dictionary */ Parameters.Locale getLocale() @@ -261,6 +280,7 @@ class SpellChecker * Resets spell checker to use a different locale's dictionary. This uses * the local copy of the dictionary if available, otherwise it's downloaded * and saved for future use. + * * @param locale locale of dictionary to be used * @throws Exception problem occurring in utilizing locale's dictionary */ @@ -273,31 +293,30 @@ class SpellChecker int filenameStart = path.lastIndexOf('/') + 1; String filename = path.substring(filenameStart); - File dictLocation - = SpellCheckActivator.getFileAccessService() + File dictLocation = + SpellCheckActivator.getFileAccessService() .getPrivatePersistentFile(DICT_DIR + filename); // downloads dictionary if unavailable (not cached) if (!dictLocation.exists()) - copyDictionary(locale.getDictUrl() - .openStream(), dictLocation); + copyDictionary(locale.getDictUrl().openStream(), dictLocation); // resets dictionary being used to include changes synchronized (this.attachedChats) { InputStream dictInput = new FileInputStream(dictLocation); - SpellDictionary dict - = new OpenOfficeSpellDictionary(dictInput, - this.personalDictLocation); + SpellDictionary dict = + new OpenOfficeSpellDictionary(dictInput, + this.personalDictLocation); this.dict = dict; this.dictLocation = dictLocation; this.locale = locale; // saves locale choice to configuration properties - SpellCheckActivator.getConfigService() - .setProperty(LOCALE_CONFIG_PARAM, locale.getIsoCode()); + SpellCheckActivator.getConfigService().setProperty( + LOCALE_CONFIG_PARAM, locale.getIsoCode()); // updates chats for (ChatAttachments chat : this.attachedChats) @@ -310,6 +329,7 @@ class SpellChecker /** * Determines if locale's dictionary is locally available or not. + * * @param locale locale to be checked * @return true if local resources for dictionary are available and * accessible, false otherwise @@ -321,8 +341,8 @@ class SpellChecker String filename = path.substring(filenameStart); try { - File dictLocation - = SpellCheckActivator.getFileAccessService() + File dictLocation = + SpellCheckActivator.getFileAccessService() .getPrivatePersistentFile(DICT_DIR + filename); return dictLocation.exists(); @@ -358,7 +378,8 @@ class SpellChecker // copies dictionary to appropriate location, closing the stream afterward private void copyDictionary(InputStream input, File dest) - throws IOException, FileNotFoundException + throws IOException, + FileNotFoundException { byte[] buf = new byte[1024]; FileOutputStream output = new FileOutputStream(dest); @@ -377,6 +398,7 @@ class SpellChecker * Determines if spell checker dictionary works. Backend API often fails * when used so this tests that the current dictionary is able to process * words. + * * @return true if current dictionary can check words, false otherwise */ private boolean isDictionaryValid(SpellDictionary dict) diff --git a/src/net/java/sip/communicator/plugin/spellcheck/SpellCheckerConfigDialog.java b/src/net/java/sip/communicator/plugin/spellcheck/SpellCheckerConfigDialog.java new file mode 100644 index 0000000..266be20 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/spellcheck/SpellCheckerConfigDialog.java @@ -0,0 +1,411 @@ +/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license. See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.plugin.spellcheck;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+
+import org.dts.spell.dictionary.*;
+
+import net.java.sip.communicator.service.gui.*;
+import net.java.sip.communicator.service.resources.*;
+import net.java.sip.communicator.util.Logger;
+import net.java.sip.communicator.util.swing.*;
+
+/**
+ * The spell check dialog that would be opened from the right click menu in the
+ * chat window.
+ *
+ * @author Purvesh Sahoo
+ */
+public class SpellCheckerConfigDialog
+ extends SIPCommDialog
+ implements ActionListener
+{
+ private static final Logger logger = Logger
+ .getLogger(SpellCheckerConfigDialog.class);
+
+ /**
+ * UI Components
+ */
+ private JTextComponent currentWord;
+
+ private JList suggestionList;
+
+ private JScrollPane suggestionScroll;
+
+ private JButton changeButton;
+
+ private JButton nextButton;
+
+ private JButton addButton;
+
+ private JPanel checkPanel;
+
+ private JPanel buttonsPanel;
+
+ private JPanel topPanel;
+
+ private JPanel suggestionPanel;
+
+ private SpellDictionary dict;
+
+ private Chat chat;
+
+ private final ResourceManagementService resources = Resources
+ .getResources();
+
+ private String word;
+
+ private int index;
+
+ private Word clickedWord;
+
+ public SpellCheckerConfigDialog(Chat chat, Word clickedWord,
+ SpellDictionary dict)
+ {
+
+ super(false);
+
+ this.dict = dict;
+ this.chat = chat;
+
+ initComponents(clickedWord);
+
+ this.setTitle(resources.getI18NString("plugin.spellcheck.TITLE"));
+ this.setMinimumSize(new Dimension(450, 320));
+ this.setPreferredSize(new Dimension(450, 320));
+ this.setResizable(false);
+
+ JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
+ mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+ mainPanel.add(topPanel);
+
+ this.getContentPane().add(mainPanel);
+
+ this.pack();
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ Dimension screenSize = toolkit.getScreenSize();
+
+ int x = (screenSize.width - this.getWidth()) / 2;
+ int y = (screenSize.height - this.getHeight()) / 2;
+
+ this.setLocation(x, y);
+
+ if (!currentWord.getText().equals(" ")
+ && this.dict.isCorrect(currentWord.getText()))
+ {
+ nextButton.doClick();
+ }
+ }
+
+ /**
+ * Initialises the UI components.
+ */
+ private void initComponents(final Word clickWord)
+ {
+
+ clickedWord =
+ (clickWord == null) ? Word.getWord(" ", 1, false) : clickWord;
+
+ topPanel = new TransparentPanel(new BorderLayout());
+ topPanel.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
+ topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
+
+ checkPanel = new TransparentPanel(new BorderLayout(10, 10));
+ checkPanel.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
+ checkPanel.setLayout(new BoxLayout(checkPanel, BoxLayout.X_AXIS));
+
+ currentWord = new JTextField(clickedWord.getText());
+
+ currentWord.setAlignmentX(LEFT_ALIGNMENT);
+ currentWord.setMaximumSize(new Dimension(550, 30));
+
+ currentWord.setText(clickedWord.getText());
+ currentWord.selectAll();
+
+ // JPanel wordPanel = new TransparentPanel(new BorderLayout());
+ // wordPanel.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
+ // wordPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 5));
+ // wordPanel.add(currentWord);
+
+ buttonsPanel =
+ new TransparentPanel(new FlowLayout(FlowLayout.RIGHT, 0, 10));
+ changeButton =
+ new JButton(
+ resources.getI18NString("plugin.spellcheck.dialog.REPLACE"));
+ changeButton.setMnemonic(resources
+ .getI18nMnemonic("plugin.spellcheck.dialog.REPLACE"));
+
+ changeButton.addActionListener(new ActionListener()
+ {
+
+ public void actionPerformed(ActionEvent e)
+ {
+ if (suggestionList.getSelectedValue() != null)
+ {
+
+ StringBuffer newMessage =
+ new StringBuffer(chat.getMessage());
+ int endIndex;
+
+ if (word != null)
+ {
+ endIndex = index + currentWord.getText().length();
+ newMessage.replace(index, endIndex,
+ (String) suggestionList.getSelectedValue());
+ word = (String) suggestionList.getSelectedValue();
+ }
+ else
+ {
+ endIndex =
+ clickedWord.getStart()
+ + clickedWord.getText().length();
+ newMessage.replace(clickedWord.getStart(), endIndex,
+ (String) suggestionList.getSelectedValue());
+ }
+ currentWord.setText((String) suggestionList
+ .getSelectedValue());
+ chat.setMessage(newMessage.toString());
+
+ }
+ }
+ });
+ changeButton.setEnabled(false);
+
+ nextButton =
+ new JButton(
+ resources.getI18NString("plugin.spellcheck.dialog.FIND"));
+ nextButton.setMnemonic(resources
+ .getI18nMnemonic("plugin.spellcheck.dialog.FIND"));
+
+ nextButton.addActionListener(new ActionListener()
+ {
+
+ public Word getNextWord()
+ {
+
+ Word nextWord;
+ int wordIndex;
+
+ if (word == null)
+ {
+ if (currentWord.getText().equals(" "))
+ {
+ String words[] = chat.getMessage().split(" ");
+ currentWord.setText(words[0]);
+
+ }
+
+ wordIndex =
+ chat.getMessage().indexOf(currentWord.getText());
+ if (dict.isCorrect(currentWord.getText()))
+ currentWord.setText("");
+ }
+ else
+ {
+ wordIndex = chat.getMessage().indexOf(word, index);
+ }
+
+ Word presentWord =
+ Word.getWord(chat.getMessage(), wordIndex, false);
+
+ if (presentWord.getEnd() == chat.getMessage().length())
+ {
+ nextWord = Word.getWord(chat.getMessage(), 1, false);
+
+ }
+ else
+ {
+ nextWord =
+ Word.getWord(chat.getMessage(),
+ presentWord.getEnd() + 1, false);
+ }
+
+ index = nextWord.getStart();
+ word = nextWord.getText();
+
+ return nextWord;
+ }
+
+ public void actionPerformed(ActionEvent e)
+ {
+ Word nextWord = getNextWord();
+ int breakIndex = nextWord.getStart();
+
+ while (dict.isCorrect(nextWord.getText())
+ && nextWord.getEnd() + 1 != breakIndex)
+ {
+ nextWord = getNextWord();
+
+ }
+
+ if (!dict.isCorrect(nextWord.getText()))
+ {
+ word = nextWord.getText();
+ currentWord.setText(nextWord.getText());
+
+ String clickedWord = currentWord.getText();
+ setSuggestionModel(clickedWord);
+ }
+
+ }
+ });
+
+ buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS));
+ buttonsPanel.add(changeButton);
+ buttonsPanel.add(nextButton);
+
+ checkPanel.add(currentWord, BorderLayout.NORTH);
+ checkPanel.add(Box.createHorizontalStrut(10));
+ checkPanel.add(buttonsPanel, BorderLayout.EAST);
+
+ topPanel.add(checkPanel, BorderLayout.NORTH);
+ topPanel.add(Box.createVerticalStrut(10));
+
+ DefaultListModel dataModel = new DefaultListModel();
+ suggestionList = new JList(dataModel);
+
+ suggestionScroll = new JScrollPane(suggestionList);
+ suggestionScroll.setAlignmentX(LEFT_ALIGNMENT);
+
+ if (!dict.isCorrect(clickedWord.getText()))
+ setSuggestionModel(clickedWord.getText());
+
+ suggestionList.addListSelectionListener(new ListSelectionListener()
+ {
+
+ public void valueChanged(ListSelectionEvent e)
+ {
+
+ if (!e.getValueIsAdjusting())
+ {
+ changeButton.setEnabled(true);
+ }
+ }
+ });
+
+ MouseListener clickListener = new MouseAdapter()
+ {
+ public void mouseClicked(MouseEvent e)
+ {
+ if (e.getClickCount() == 2)
+ {
+
+ StringBuffer newMessage =
+ new StringBuffer(chat.getMessage());
+ int endIndex;
+
+ if (word != null)
+ {
+ endIndex = index + currentWord.getText().length();
+ newMessage.replace(index, endIndex,
+ (String) suggestionList.getSelectedValue());
+ word = (String) suggestionList.getSelectedValue();
+ }
+ else
+ {
+ endIndex =
+ clickedWord.getStart()
+ + clickedWord.getText().length();
+ newMessage.replace(clickedWord.getStart(), endIndex,
+ (String) suggestionList.getSelectedValue());
+ }
+ currentWord.setText((String) suggestionList
+ .getSelectedValue());
+ chat.setMessage(newMessage.toString());
+
+ }
+ }
+ };
+
+ suggestionList.addMouseListener(clickListener);
+
+ addButton =
+ new JButton(resources.getI18NString("plugin.spellcheck.dialog.ADD"));
+ addButton.setMnemonic(resources
+ .getI18nMnemonic("plugin.spellcheck.dialog.ADD"));
+
+ addButton.addActionListener(new ActionListener()
+ {
+
+ public void actionPerformed(ActionEvent e)
+ {
+
+ try
+ {
+ dict.addWord(currentWord.getText());
+ chat.promptRepaint();
+ }
+ catch (SpellDictionaryException exc)
+ {
+ String msg = "Unable to add word to personal dictionary";
+ logger.error(msg, exc);
+ }
+ }
+ });
+
+ suggestionPanel = new TransparentPanel(new BorderLayout(10, 10));
+ suggestionPanel.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
+ suggestionPanel.setLayout(new BoxLayout(suggestionPanel,
+ BoxLayout.X_AXIS));
+ suggestionPanel.add(suggestionScroll);
+ suggestionPanel.add(Box.createHorizontalStrut(10));
+ suggestionPanel.add(addButton);
+
+ topPanel.add(suggestionPanel, BorderLayout.SOUTH);
+
+ }
+
+ /**
+ * Sets the model for the suggestion list
+ *
+ * @param clickedWord
+ */
+ private void setSuggestionModel(String clickedWord)
+ {
+
+ DefaultListModel dataModel = new DefaultListModel();
+ List<String> corrections = this.dict.getSuggestions(clickedWord);
+ for (String correction : corrections)
+ {
+ dataModel.addElement(correction);
+ }
+
+ suggestionList.setModel(dataModel);
+ }
+
+ /**
+ * Returns the selected correction value
+ *
+ * @return selected value from suggestion list
+ */
+ public Object getCorrection()
+ {
+
+ return suggestionList.getSelectedValue();
+ }
+
+ public void actionPerformed(ActionEvent e)
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ protected void close(boolean escaped)
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+}
diff --git a/src/net/java/sip/communicator/plugin/spellcheck/Word.java b/src/net/java/sip/communicator/plugin/spellcheck/Word.java index e7a882a..b2e2b40 100644 --- a/src/net/java/sip/communicator/plugin/spellcheck/Word.java +++ b/src/net/java/sip/communicator/plugin/spellcheck/Word.java @@ -1,35 +1,40 @@ /* * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. + * + * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.sip.communicator.plugin.spellcheck; import java.text.BreakIterator; /** - * Immutable representation of a word in the context of a document, bundling - * the bounds with the text. + * Immutable representation of a word in the context of a document, bundling the + * bounds with the text. + * * @author Damian Johnson */ class Word { - private static final BreakIterator WORD_ITR - = BreakIterator.getWordInstance(); + private static final BreakIterator WORD_ITR = BreakIterator + .getWordInstance(); + private final int start; + private final String text; + + private final int end; /** * Provides the word before or after a given index. No bounds checking is * performed. + * * @param text text to be checked * @param index index in which to begin search (inclusive) * @param before search is before index if true, after otherwise * @return index of word boundary */ public static synchronized Word getWord(String text, int index, - boolean before) + boolean before) { int start, end; WORD_ITR.setText(text); @@ -38,21 +43,24 @@ class Word { start = WORD_ITR.preceding(index); end = WORD_ITR.next(); - if (start == BreakIterator.DONE) start = 0; + if (start == BreakIterator.DONE) + start = 0; } else { end = WORD_ITR.following(index); start = WORD_ITR.previous(); - if (end == BreakIterator.DONE) end = text.length() - 1; + if (end == BreakIterator.DONE) + end = text.length() - 1; } - return new Word(start, text.substring(start, end)); + return new Word(start, end, text.substring(start, end)); } - private Word(int start, String text) + private Word(int start, int end, String text) { this.start = start; + this.end = end; this.text = text; } @@ -61,6 +69,11 @@ class Word return this.start; } + public int getEnd() + { + return this.end; + } + public String getText() { return this.text; diff --git a/src/net/java/sip/communicator/plugin/spellcheck/spellCheck.manifest.mf b/src/net/java/sip/communicator/plugin/spellcheck/spellCheck.manifest.mf index e06daec..cf41de6 100644 --- a/src/net/java/sip/communicator/plugin/spellcheck/spellCheck.manifest.mf +++ b/src/net/java/sip/communicator/plugin/spellcheck/spellCheck.manifest.mf @@ -10,9 +10,13 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.gui, net.java.sip.communicator.service.gui.event, net.java.sip.communicator.service.resources, + net.java.sip.communicator.util.swing, + net.java.sip.communicator.service.contactlist, + net.java.sip.communicator.service.protocol, javax.swing, javax.swing.event, javax.swing.text, + javax.swing.text.html, javax.imageio, javax.xml.parsers, org.w3c.dom, |