/* Copyright (C) Abu Rizal, 2009.
*
* This file is part of QurText.
*
* QurText is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QurText is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QurText. If not, see <http://www.gnu.org/licenses/>.
*/
package qurtext.client;
import java.util.ArrayList;
import java.util.TreeMap;
import java.util.TreeSet;
import com.allen_sauer.gwt.voices.client.Sound;
import com.allen_sauer.gwt.voices.client.SoundController;
import com.allen_sauer.gwt.voices.client.handler.PlaybackCompleteEvent;
import com.allen_sauer.gwt.voices.client.handler.SoundHandler;
import com.allen_sauer.gwt.voices.client.handler.SoundLoadStateChangeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.InlineHTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
public class SectionView extends BaseView {
private static final int targetLinkCount=9; //total navigation links including jumps to ends.
private static final int endLinkCount=2; //extra links to jump to the end
private Hyperlink closeUpdateLink=new Hyperlink("Chapters","");
private MainPresenter mainPresenter;
private final Button initSectionButton=new Button("Initialize Section");
private final Button initLiteralButton=new Button("Initialize Literals");
private final Button reciteAllButton=new Button("Recite Section");
private final Label sectionText=new Label("Click on a button!");
private HorizontalPanel sectionNavigationBar=new HorizontalPanel();
private final HorizontalPanel sectionPanel = new HorizontalPanel();
private final HorizontalPanel sectionAdminBar = new HorizontalPanel();
private VerticalPanel sectionMainPanel=new VerticalPanel();
private SoundController soundController = new SoundController();
private Sound sound;
private String currentToken;
private ArrayList<String> playList=new ArrayList<String>();
private TreeMap<String,ArrayList<HTML>> textHtmlMap = new TreeMap<String, ArrayList<HTML>>();
private TreeMap<String,HTML> translationHtmlMap = new TreeMap<String, HTML>();
private SoundHandler soundHandler = new SoundHandler() {
@Override
public void onSoundLoadStateChange(SoundLoadStateChangeEvent event) {
// do nothing
}
@Override
public void onPlaybackComplete(PlaybackCompleteEvent event) {
if (null==currentToken) return;
ArrayList<HTML> textHtmls = textHtmlMap.get(currentToken);
if (null==textHtmls) return;
for (HTML textHTML:textHtmls) {
textHTML.removeStyleName("selected");
}
HTML translationHtml = translationHtmlMap.get(currentToken);
if (null==translationHtml) return;
translationHtml.removeStyleName("selected");
currentToken=null;
sound=null;
if (playList.size()>0) recite();
}
};
private ClickHandler playAudioHandler=new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
HTML source=(HTML) event.getSource();
String newToken = source.getElement().getAttribute("name").trim();
playList.clear();
playList.add(newToken);
stopReciting();
recite();
}
};
private void stopReciting() {
if (null!=sound) {
sound.stop();
soundHandler.onPlaybackComplete(null);
}
}
private ClickHandler updateClickHandler = new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
Hyperlink sender=(Hyperlink)event.getSource();
String historyToken = sender.getTargetHistoryToken();
mainPresenter.handleHistoryToken(historyToken);
}
};
public MainPresenter getMainPresenter() {
return mainPresenter;
}
public void setMainPresenter(MainPresenter mainPresenter) {
this.mainPresenter = mainPresenter;
}
public void showSection(ClientSection section) {
mainPresenter.showSectionPanel();
sectionNavigationBar.clear();
sectionNavigationBar.add(closeUpdateLink);
sectionNavigationBar.addStyleName("navbar");
if (mainPresenter.getAllSections()==null) return; //not initialized yet
int sectionNo = section.sectionNo;
int start=0;
int end=mainPresenter.getAllSections().size();
if (sectionNo<=start+targetLinkCount/2) { //0 1 2 3 *4* 5 6 | 7 8
end=start+(targetLinkCount-endLinkCount);
} else if (sectionNo>=end-targetLinkCount/2) { //2 3 | 4 5 *6* 7 8 9 10
start=end-(targetLinkCount-endLinkCount);
} else {
start = (sectionNo-(targetLinkCount/2-endLinkCount)); // 0 .. 3 4 *5* 6 7 .. 10
end = (sectionNo+(targetLinkCount/2-endLinkCount)+1);
}
addPreviousSectionLink(section, sectionNo-1);
if (start>0) {
addSectionLink(section, 0);
}
if (start>1) {
sectionNavigationBar.add(spacer());
sectionNavigationBar.add(new HTML("..."));
}
for (int i=start;i<end;i++) {
addSectionLink(section, i);
}
if (end<mainPresenter.getAllSections().size()-1) {
sectionNavigationBar.add(spacer());
sectionNavigationBar.add(new HTML("..."));
}
if (end<mainPresenter.getAllSections().size()) {
addSectionLink(section, mainPresenter.getAllSections().size()-1);
}
addNextSectionLink(section, sectionNo+1);
sectionNavigationBar.add(reciteAllButton);
}
private void addSectionLink(ClientSection currentSection, int i) {
ClientSection section=mainPresenter.getAllSections().get(i);
sectionNavigationBar.add(spacer());
String label="" + section.verse;
if (1==section.verse) label=""+section.chapter+":"+label;
addSectionLink(currentSection, section, label);
}
private void addPreviousSectionLink(ClientSection currentSection, int i) {
if (i<0) i=0;
ClientSection section=mainPresenter.getAllSections().get(i);
sectionNavigationBar.add(spacer());
String label="<<";
addSectionLink(currentSection, section, label);
}
private void addNextSectionLink(ClientSection currentSection, int i) {
if (i>=mainPresenter.getAllSections().size()) i=mainPresenter.getAllSections().size()-1;
ClientSection section=mainPresenter.getAllSections().get(i);
sectionNavigationBar.add(spacer());
String label=">>";
addSectionLink(currentSection, section, label);
}
private Hyperlink addSectionLink(ClientSection currentSection,
ClientSection section, String label) {
Hyperlink sectionLink = null;
if (currentSection.equals(section)) {
sectionNavigationBar.add(new HTML("<b>" + label + "</b>"));
mainPresenter.getSectionPresenter().updateSectionPanel(section);
} else {
sectionLink = new Hyperlink(label,"" + section.chapter + ":" + section.verse);
sectionLink.addClickHandler(updateClickHandler);
sectionNavigationBar.add(sectionLink);
}
return sectionLink;
}
/**
* initialize all widgets for the first time
*/
public void onSectionViewLoad() {
closeUpdateLink.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
mainPresenter.showJumpPanel();
}
});
initSectionButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
mainPresenter.getSectionPresenter().onInitSection();
}
});
initLiteralButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
mainPresenter.getSectionPresenter().onInitLiteral();
}
});
reciteAllButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
playList.clear();
stopReciting();
ClientSection section=mainPresenter.getCurrentSection();
for (ClientVerse verse:section.verses.values()) {
if (verse.verseNo==1 && verse.chapterNo!=1 && verse.chapterNo!=9) {
playList.add("1:1");
}
playList.add("" + verse.chapterNo + ":" + verse.verseNo);
}
recite();
}
});
sectionAdminBar.setVisible(false);
sectionAdminBar.add(initSectionButton);
sectionAdminBar.add(spacer());
sectionAdminBar.add(initLiteralButton);
sectionAdminBar.add(spacer());
sectionAdminBar.add(sectionText);
sectionAdminBar.setWidth("100%");
sectionNavigationBar.setWidth("100%");
sectionPanel.setWidth("100%");
sectionMainPanel.setWidth("100%");
sectionMainPanel.add(sectionAdminBar);
sectionMainPanel.add(sectionNavigationBar);
sectionMainPanel.add(sectionPanel);
}
public void sectionPanelClear() {
sectionPanel.clear();
}
public void showText(TreeMap<Integer, ClientVerse> verses) {
FlowPanel textPanel=new FlowPanel();
textPanel.setWidth("600");
textPanel.addStyleName("arabic");
textPanel.getElement().setAttribute("dir", "rtl");
int chapterNo = verses.values().iterator().next().chapterNo;
showChapterArabicName(chapterNo, textPanel);
for (ClientVerse verse:verses.values()) {
if (verse.verseNo==1 && verse.chapterNo!=1 && verse.chapterNo!=9) {
FlowPanel basmallahPanel=new FlowPanel();
basmallahPanel.setWidth("100%");
basmallahPanel.addStyleName("basmallah");
showText(basmallahPanel, mainPresenter.getSectionPresenter().getBasmallah());
textPanel.add(basmallahPanel);
}
showText(textPanel, verse);
String token = "" + verse.chapterNo + ":" + verse.verseNo;
InlineHTML number = new InlineHTML("<font color='gray'><sup><nobr>{" + arabicNumber(verse.verseNo) + "}</nobr></sup></font> ");
addPlayRecitationHandler(token, number, true);
textPanel.add(number);
}
sectionPanel.add(textPanel);
}
private boolean showChapterArabicName(int chapterNo,
FlowPanel textPanel) {
ClientChapter chapter=mainPresenter.getChapter(chapterNo);
if (null==chapter) return false;
textPanel.add(new HTML("\u0633\u0648\u0631\u0629 " + chapter.title));
return true;
}
private String arabicNumber(int verseNo) {
StringBuffer result=new StringBuffer();
while (verseNo>0) {
char c= (char) (1632+(verseNo % 10));
result.insert(0, c);
verseNo = verseNo / 10;
}
return result.toString();
}
private void addPlayRecitationHandler(String token, HTML widget, boolean showTitle) {
widget.addClickHandler(playAudioHandler);
if (showTitle) widget.setTitle("Click to recite");
widget.getElement().setAttribute("name", token);
}
private static class ArabicWord {
public String text;
public String transliteration;
public String literal;
public String style;
}
private ArrayList<ArabicWord> getArabicWords(ClientVerse verse) {
String[] arabicWords=rearrangeDiacritics(verse.text).split("[ ]");
String[] transliterationWords=verse.transliteration.split("[ ]");
String[] literalWords=htmlEscape(verse.literal).split("[ ]");
ArrayList<ArabicWord> results = new ArrayList<ArabicWord>(arabicWords.length);
int i=0;
for (String word: arabicWords) {
if ("".equals(word)) break; //unexpected empty word
ArabicWord result=new ArabicWord();
result.text=word;
if (word.length()==1) {
if ("\u06D6".compareTo(word)<=0 //ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA
&& "\u06DC".compareTo(word)>=0 //ARABIC ARABIC SMALL HIGH SEEN
) {
result.style="sign";
} else if ("\u06E9".equals(word) //ARABIC PLACE OF SAJDAH
|| "\u06DE".equals(word) //ARABIC START OF RUB EL HIZB
) {
result.style="internal-sign";
}
} else {
result.transliteration=transliterationWords[i];
result.literal=literalWords[i];
i++;
}
results.add(result);
}
return results;
}
/**
* @param literal
* @return
*/
private String htmlEscape(String literal) {
return literal.replaceAll("[<]","<").replaceAll("[&]","&");
}
private void showText(FlowPanel textPanel, ClientVerse verse) {
if (null==verse) return;
String arabicText = verse.text;
if (null==arabicText) return;
String token = "" + verse.chapterNo + ":" + verse.verseNo;
ArrayList<ArabicWord> arabicWords=getArabicWords(verse);
ArrayList<HTML> textHtmls = new ArrayList<HTML>(arabicWords.size());
for (ArabicWord arabicWord:arabicWords) {
InlineHTML text = new InlineHTML(arabicWord.text + " ");
textHtmls.add(text);
if (null==arabicWord.transliteration) {
if (null!=arabicWord) text.addStyleName(arabicWord.style);
addPlayRecitationHandler(token, text, true);
} else {
TooltipHandler handler=new TooltipHandler(arabicWord.transliteration.replaceAll("[<][s][>]","<font color='gray'><s>").replaceAll("[<][/][s][>]","</s></font>") + "<br/>" + arabicWord.literal, 10000, "tooltip");
text.addMouseOverHandler(handler);
text.addMouseOutHandler(handler);
addPlayRecitationHandler(token, text, false);
}
textPanel.add(text);
}
textHtmlMap.put("" + verse.chapterNo + ":" + verse.verseNo,textHtmls);
}
/**
* allow more readable rendering of arabic text, such as:
* - KASRA and KASRATAN should be always under the base, instead of under the SHADDA
* This is on best-effort basis. Some system might keep the KASRA under the SHADDA
*/
private String rearrangeDiacritics(String text) {
text = text.replaceAll("[\u0651][\u0650]", "\u0650\u0651"); // SHADDA KASRA
text = text.replaceAll("[\u0651][\u064D]", "\u064D\u0651"); // SHADDA KASRATAN
return text;
}
public void showTranslation(TreeMap<Integer, ClientVerse> verses) {
FlowPanel textPanel=new FlowPanel();
int chapterNo = verses.values().iterator().next().chapterNo;
showChapterTranslationText(textPanel, chapterNo);
TreeSet<String> topics=new TreeSet<String>();
for (ClientVerse verse:verses.values()) {
if (verse.verseNo==1 && verse.chapterNo!=1 && verse.chapterNo!=9) {
ClientVerse basmallah = mainPresenter.getSectionPresenter().getBasmallah();
if (null==basmallah) break;
String token = "" + basmallah.chapterNo + ":" + basmallah.verseNo;
String text = basmallah.translation + " ";
InlineHTML translationHTML = new InlineHTML(text);
translationHtmlMap.put(token, translationHTML);
addPlayRecitationHandler(token, translationHTML, true);
textPanel.add(translationHTML);
}
String token = "" + verse.chapterNo + ":" + verse.verseNo;
InlineHTML number = new InlineHTML("<font color='gray'><sup>" + token + "</sup></font> ");
addPlayRecitationHandler(token, number, true);
textPanel.add(number);
String text =verse.translation + " ";
InlineHTML translationHTML = new InlineHTML(text);
translationHtmlMap.put(token, translationHTML);
addPlayRecitationHandler(token, translationHTML, true);
textPanel.add(translationHTML);
if (null!=verse.topics) {
for (String topic:verse.topics.split("[,]")) {
if (topic.length()>0) topics.add(topic);
}
}
}
textPanel.addStyleName("translation");
StringBuffer topicString=new StringBuffer();
for (String topic:topics) {
topicString.append(topic);
topicString.append(", ");
}
if (topicString.length()>0) topicString.setLength(topicString.length()-", ".length());
textPanel.add(new HTML("Topics: " + topicString));
sectionPanel.add(textPanel);
}
private boolean showChapterTranslationText(FlowPanel textPanel, int chapterNo) {
ClientChapter chapter=mainPresenter.getChapter(chapterNo);
if (null==chapter) return false;
textPanel.add(new HTML("Chapter " + chapterNo + ": " + chapter.transliteration));
return true;
}
public void setStatusText(String text) {
sectionText.setText(text);
}
public void showAdminTab() {
sectionAdminBar.setVisible(true);
initSectionButton.setVisible(true);
initLiteralButton.setVisible(true);
sectionText.setVisible(true);
}
public Widget getPanel() {
return sectionMainPanel;
}
private void recite() {
if (playList.size()<=0) return;
currentToken = playList.remove(0);
String[] tokens=currentToken.split("[:]");
String filename = "000".substring(tokens[0].length())+tokens[0] + "000".substring(tokens[1].length())+tokens[1] + ".mp3";
sound = soundController.createSound(Sound.MIME_TYPE_AUDIO_MPEG,
"media/"+filename);
sound.addEventHandler(soundHandler);
sound.play();
ArrayList<HTML> textHtmls = textHtmlMap.get(currentToken);
if (null==textHtmls) return;
for (HTML textHTML:textHtmls) {
textHTML.addStyleName("selected");
}
HTML translationHtml = translationHtmlMap.get(currentToken);
if (null==translationHtml) return;
translationHtml.addStyleName("selected");
}
}