/*
 * This file is part of NixNote/NeighborNote 
 * Copyright 2009 Randy Baumgarte
 * Copyright 2013 Yuki Takahashi
 * 
 * This file may be licensed under the terms of of the
 * GNU General Public License Version 2 (the ``GPL'').
 *
 * Software distributed under the License is distributed
 * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
 * express or implied. See the GPL for the specific language
 * governing rights and limitations.
 *
 * You should have received a copy of the GPL along with this
 * program. If not, go to http://www.gnu.org/licenses/gpl.html
 * or write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

package cx.fbn.nevernote.gui;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.FileNameMap;
import java.net.URI;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;

import com.evernote.edam.limits.Constants;
import com.evernote.edam.type.Data;
import com.evernote.edam.type.Note;
import com.evernote.edam.type.Notebook;
import com.evernote.edam.type.Resource;
import com.evernote.edam.type.ResourceAttributes;
import com.evernote.edam.type.Tag;
import com.evernote.edam.type.User;
import com.swabunga.spell.engine.Configuration;
import com.swabunga.spell.engine.SpellDictionary;
import com.swabunga.spell.engine.SpellDictionaryHashMap;
import com.swabunga.spell.engine.Word;
import com.swabunga.spell.event.SpellCheckEvent;
import com.swabunga.spell.event.SpellCheckListener;
import com.swabunga.spell.event.SpellChecker;
import com.swabunga.spell.event.StringWordTokenizer;
import com.trolltech.qt.core.QByteArray;
import com.trolltech.qt.core.QCoreApplication;
import com.trolltech.qt.core.QDataStream;
import com.trolltech.qt.core.QDateTime;
import com.trolltech.qt.core.QEvent;
import com.trolltech.qt.core.QEvent.Type;
import com.trolltech.qt.core.QFile;
import com.trolltech.qt.core.QFileSystemWatcher;
import com.trolltech.qt.core.QIODevice;
import com.trolltech.qt.core.QMimeData;
import com.trolltech.qt.core.QSize;
import com.trolltech.qt.core.QTextCodec;
import com.trolltech.qt.core.QTimer;
import com.trolltech.qt.core.QUrl;
import com.trolltech.qt.core.Qt;
import com.trolltech.qt.core.Qt.Key;
import com.trolltech.qt.core.Qt.KeyboardModifier;
import com.trolltech.qt.core.Qt.KeyboardModifiers;
import com.trolltech.qt.gui.QAction;
import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QCalendarWidget;
import com.trolltech.qt.gui.QClipboard;
import com.trolltech.qt.gui.QClipboard.Mode;
import com.trolltech.qt.gui.QColor;
import com.trolltech.qt.gui.QComboBox;
import com.trolltech.qt.gui.QDateEdit;
import com.trolltech.qt.gui.QDesktopServices;
import com.trolltech.qt.gui.QFileDialog;
import com.trolltech.qt.gui.QFileDialog.AcceptMode;
import com.trolltech.qt.gui.QFileDialog.FileMode;
import com.trolltech.qt.gui.QFont;
import com.trolltech.qt.gui.QFontDatabase;
import com.trolltech.qt.gui.QFormLayout;
import com.trolltech.qt.gui.QGridLayout;
import com.trolltech.qt.gui.QHBoxLayout;
import com.trolltech.qt.gui.QIcon;
import com.trolltech.qt.gui.QImage;
import com.trolltech.qt.gui.QKeyEvent;
import com.trolltech.qt.gui.QKeySequence;
import com.trolltech.qt.gui.QLabel;
import com.trolltech.qt.gui.QLineEdit;
import com.trolltech.qt.gui.QListWidgetItem;
import com.trolltech.qt.gui.QMatrix;
import com.trolltech.qt.gui.QMessageBox;
import com.trolltech.qt.gui.QPalette;
import com.trolltech.qt.gui.QPalette.ColorRole;
import com.trolltech.qt.gui.QPushButton;
import com.trolltech.qt.gui.QShortcut;
import com.trolltech.qt.gui.QSizePolicy;
import com.trolltech.qt.gui.QSplitter;
import com.trolltech.qt.gui.QTextEdit;
import com.trolltech.qt.gui.QTextEdit.LineWrapMode;
import com.trolltech.qt.gui.QTimeEdit;
import com.trolltech.qt.gui.QToolButton;
import com.trolltech.qt.gui.QToolButton.ToolButtonPopupMode;
import com.trolltech.qt.gui.QVBoxLayout;
import com.trolltech.qt.gui.QWidget;
import com.trolltech.qt.network.QNetworkAccessManager;
import com.trolltech.qt.network.QNetworkReply;
import com.trolltech.qt.network.QNetworkReply.NetworkError;
import com.trolltech.qt.network.QNetworkRequest;
import com.trolltech.qt.webkit.QWebPage;
import com.trolltech.qt.webkit.QWebPage.WebAction;
import com.trolltech.qt.webkit.QWebSettings;
import com.trolltech.qt.webkit.QWebView;

import cx.fbn.nevernote.Global;
import cx.fbn.nevernote.clipboard.ClipBoardObserver;
import cx.fbn.nevernote.dialog.EnCryptDialog;
import cx.fbn.nevernote.dialog.EnDecryptDialog;
import cx.fbn.nevernote.dialog.GeoDialog;
import cx.fbn.nevernote.dialog.InsertLatexImage;
import cx.fbn.nevernote.dialog.InsertLinkDialog;
import cx.fbn.nevernote.dialog.NoteQuickLinkDialog;
import cx.fbn.nevernote.dialog.SpellCheck;
import cx.fbn.nevernote.dialog.TableDialog;
import cx.fbn.nevernote.dialog.TagAssign;
import cx.fbn.nevernote.evernote.EnCrypt;
import cx.fbn.nevernote.filters.FilterEditorTags;
import cx.fbn.nevernote.signals.NoteResourceSignal;
import cx.fbn.nevernote.signals.NoteSignal;
import cx.fbn.nevernote.sql.DatabaseConnection;
import cx.fbn.nevernote.utilities.ApplicationLogger;
import cx.fbn.nevernote.utilities.FileUtils;
import cx.fbn.nevernote.utilities.Pair;
import cx.fbn.nevernote.xml.HtmlTagModifier;

public class BrowserWindow extends QWidget {

	public final QLineEdit titleLabel;
	private final QLineEdit urlText;
	private final QLabel authorLabel;
	private final QLineEdit authorText;
	private final QComboBox geoBox;
	public final TagLineEdit tagEdit;
	public final QLabel tagLabel;
	private final QPushButton urlLabel;
	private final QLabel alteredLabel;
	private final QDateEdit alteredDate;
	private final QTimeEdit alteredTime;
	private final QDateEdit createdDate;
	private final QTimeEdit createdTime;
	private final QLabel subjectLabel;
	private final QDateEdit subjectDate;
	private final QTimeEdit subjectTime;
	public final QComboBox notebookBox;
	private final QLabel notebookLabel;
	private final QLabel createdLabel;
	public final QComboBox fontSize;
	public final QAction	fontSizeAction;
	private boolean extendedOn;
	public boolean buttonsVisible;
	private final String iconPath;
	public final ContentView browser;
	private final QTextEdit sourceEdit;
	private String sourceEditHeader;
	Highlighter syntaxHighlighter;
	private List<Tag> allTags;
	private List<String> currentTags;
	public NoteSignal noteSignal;
	public Signal2<String,String> evernoteLinkClicked;
	private List<Notebook> notebookList;
	private Note currentNote;
	private String saveNoteTitle;
	private String saveTagList;
	private boolean insideList;
	private final DatabaseConnection conn;
	private final QCalendarWidget createdCalendarWidget;
	private final QCalendarWidget alteredCalendarWidget;
	private final QCalendarWidget subjectCalendarWidget;

	public final QPushButton undoButton;
	public final QAction	undoAction;
	public final QPushButton redoButton;
	public final QAction	redoAction;
	public final QPushButton cutButton;
	public final QAction	cutAction;
	public final QPushButton copyButton;
	public final QAction	copyAction;
	public final QPushButton pasteButton;
	public final QAction	pasteAction;
	public final QPushButton boldButton;
	public final QAction	boldAction;
	public final QPushButton underlineButton;
	public final QAction	underlineAction;
	public final QPushButton italicButton;
	public final QAction	italicAction;
	public final Signal0 focusLost;
	public final NoteResourceSignal resourceSignal;

	public QPushButton rightAlignButton;
	public final QAction	rightAlignAction;
	public QPushButton leftAlignButton;
	public final QAction	leftAlignAction;
	public QPushButton centerAlignButton;
	public final QAction	centerAlignAction;

	public final QPushButton strikethroughButton;
	public final QAction	strikethroughAction;
	public final QPushButton hlineButton;
	public final QAction	hlineAction;
	public final QPushButton indentButton;
	public final QAction	indentAction;
	public final QPushButton outdentButton;
	public final QAction	outdentAction;
	public final QPushButton bulletListButton;
	public final QAction	bulletListAction;
	public final QPushButton numberListButton;
	public final QAction	numberListAction;
	public final QPushButton spellCheckButton;
	public final QAction	spellCheckAction;
	public final QPushButton todoButton;
	public final QAction	todoAction;

	public final QShortcut focusTitleShortcut;
	public final QShortcut focusTagShortcut;
	public final QShortcut focusNoteShortcut;
	public final QShortcut focusUrlShortcut;
	public final QShortcut focusAuthorShortcut;
	
	public EditorButtonBar buttonLayout;
	public final QComboBox fontList;
	public final QAction	fontListAction;
	public final QToolButton fontColor;
	public final QAction	fontColorAction;
	private final ColorMenu fontColorMenu;
	public final QToolButton fontHilight;
	public final QAction	fontHilightAction;
	private final ColorMenu fontHilightColorMenu;
	public final QFileSystemWatcher fileWatcher;
	public int cursorPosition;
	private boolean forceTextPaste;
	private String selectedFile;
	private String currentHyperlink;
	public boolean keepPDFNavigationHidden;
	private final ApplicationLogger logger;
	SpellDictionary dictionary;
    SpellDictionary userDictionary;
    SpellChecker spellChecker;
    SuggestionListener spellListener;
	private final HashMap<String,Integer> previewPageList;  
	boolean insertHyperlink;
	boolean insideTable;
	boolean insideEncryption;
	public Signal1<BrowserWindow> blockApplication;
	public Signal0 unblockApplication;
	public boolean awaitingHttpResponse;
	public long	unblockTime;
	private final QTimer setSourceTimer;
	String latexGuid;  // This is set if we are editing an existing LaTeX formula.  Useful to track guid.

	private final ClipBoardObserver cbObserver;
	
	public static class SuggestionListener implements SpellCheckListener {
		public boolean abortSpellCheck = false;
		public boolean errorsFound = false;
		private final SpellCheck		spellCheckDialog;
		
		
		private final BrowserWindow parent;
		public SuggestionListener(BrowserWindow parent, SpellChecker checker) {
			this.parent = parent;
			spellCheckDialog = new SpellCheck(checker);
		}
		public void spellingError(SpellCheckEvent event) {
			errorsFound = true;
			spellCheckDialog.setWord(event.getInvalidWord());

		    @SuppressWarnings("unchecked")
			List<Word> suggestions = event.getSuggestions();
		    spellCheckDialog.clearSuggestions();
		    if (!suggestions.isEmpty()) {
//		       spellCheckDialog.setCurrentSuggestion(suggestions.get(0).getWord());
		       for (int i=0; i<suggestions.size(); i++) {
		          spellCheckDialog.addSuggestion(suggestions.get(i).getWord());
		       }
		       spellCheckDialog.setSelectedSuggestion(0);
		    }
		    spellCheckDialog.exec();
		    if (spellCheckDialog.cancelPressed()) {
		    	abortSpellCheck = true;
		    	event.cancel();
		    	return;
		    }
		    if (spellCheckDialog.replacePressed()) {
		    	QClipboard clipboard = QApplication.clipboard();
		    	clipboard.setText(spellCheckDialog.getReplacementWord()); 
		    	parent.pasteClicked();
		    }
		    event.cancel();
		 }
	}

	
	// 引数にcbObserverを追加
	public BrowserWindow(DatabaseConnection c, ClipBoardObserver cbObserver) {
		logger = new ApplicationLogger("browser.log");
		logger.log(logger.HIGH, "Setting up browser");
		iconPath = new String("classpath:cx/fbn/nevernote/icons/");
		forceTextPaste = false;
		insertHyperlink = true;
		insideTable = false;
		insideEncryption = false;
		
		fileWatcher = new QFileSystemWatcher();
//		fileWatcher.fileChanged.connect(this, "fileChanged(String)");
		noteSignal = new NoteSignal();
		titleLabel = new QLineEdit();
		evernoteLinkClicked = new Signal2<String,String>();
		titleLabel.setMaxLength(Constants.EDAM_NOTE_TITLE_LEN_MAX);
		urlText = new QLineEdit();
		authorText = new QLineEdit();
		geoBox = new QComboBox();
		urlLabel = new QPushButton();
		urlLabel.clicked.connect(this, "sourceUrlClicked()");
		authorLabel = new QLabel();
		conn = c;
		
		this.cbObserver = cbObserver;
		
		focusLost = new Signal0();

		tagEdit = new TagLineEdit(allTags);
		tagLabel = new QLabel(tr("Tags:"));
		tagEdit.focusLost.connect(this, "modifyTagsTyping()");

		createdCalendarWidget = new QCalendarWidget();
		createdDate = new QDateEdit();
		createdDate.setDisplayFormat(Global.getDateFormat());
		createdDate.setCalendarPopup(true);
		createdDate.setCalendarWidget(createdCalendarWidget);
		createdTime = new QTimeEdit();
		createdDate.dateChanged.connect(this, "createdChanged()");
		createdTime.timeChanged.connect(this, "createdChanged()");

		alteredCalendarWidget = new QCalendarWidget();
		alteredDate = new QDateEdit();
		alteredDate.setDisplayFormat(Global.getDateFormat());
		alteredDate.setCalendarPopup(true);
		alteredDate.setCalendarWidget(alteredCalendarWidget);
		alteredTime = new QTimeEdit();
		alteredLabel = new QLabel(tr("Altered:"));
		alteredDate.dateChanged.connect(this, "alteredChanged()");
		alteredTime.timeChanged.connect(this, "alteredChanged()");

		subjectCalendarWidget = new QCalendarWidget();
		subjectDate = new QDateEdit();
		subjectDate.setDisplayFormat(Global.getDateFormat());
		subjectDate.setCalendarPopup(true);
		subjectDate.setCalendarWidget(subjectCalendarWidget);
		subjectTime = new QTimeEdit();
		subjectLabel = new QLabel(tr("Subject Date:"));
		subjectDate.dateChanged.connect(this, "subjectDateTimeChanged()");
		subjectTime.timeChanged.connect(this, "subjectDateTimeChanged()");
		authorText.textChanged.connect(this, "authorChanged()");
		urlText.textChanged.connect(this, "sourceUrlChanged()");

		notebookBox = new QComboBox();
		notebookLabel = new QLabel(tr("Notebook"));
		createdLabel = new QLabel(tr("Created:"));
		// selectedText = new String();

		urlLabel.setVisible(false);
		urlText.setVisible(false);
		authorLabel.setVisible(false);
		
		geoBox.setVisible(false);
		geoBox.addItem(new QIcon(iconPath+"globe.png"), "");
		geoBox.addItem(new String(tr("Set")));
		geoBox.addItem(new String(tr("Clear")));
		geoBox.addItem(new String(tr("View On Map")));
		geoBox.activated.connect(this, "geoBoxChanged()");
		
		authorText.setVisible(false);
		createdDate.setVisible(false);
		alteredLabel.setVisible(false);
		//notebookBox.setVisible(false);
		notebookLabel.setVisible(false);
		createdLabel.setVisible(false);
		createdTime.setVisible(false);
		alteredDate.setVisible(false);
		alteredTime.setVisible(false);
		subjectLabel.setVisible(false);
		subjectDate.setVisible(false);
		subjectTime.setVisible(false);
		extendedOn = false;
		buttonsVisible = true;
		setAcceptDrops(true);

		browser = new ContentView(this);
				
		browser.page().setLinkDelegationPolicy(
				QWebPage.LinkDelegationPolicy.DelegateAllLinks);
		browser.linkClicked.connect(this, "linkClicked(QUrl)");
		currentHyperlink = "";
		
		//Setup the source editor
		sourceEdit = new QTextEdit(this);
		sourceEdit.setVisible(false);
		sourceEdit.setTabChangesFocus(true);
		sourceEdit.setLineWrapMode(LineWrapMode.NoWrap);
		QFont font = new QFont();
		font.setFamily("Courier");
		font.setFixedPitch(true);
		font.setPointSize(10);
		sourceEdit.setFont(font);
		syntaxHighlighter = new Highlighter(sourceEdit.document());
		sourceEdit.textChanged.connect(this, "sourceEdited()");

		QVBoxLayout v = new QVBoxLayout();
		QFormLayout notebookLayout = new QFormLayout();
		QGridLayout dateLayout = new QGridLayout();
		titleLabel.setReadOnly(false);
		titleLabel.editingFinished.connect(this, "titleEdited()");
		browser.page().contentsChanged.connect(this, "contentChanged()");
		browser.page().selectionChanged.connect(this, "selectionChanged()");
		browser.page().mainFrame().javaScriptWindowObjectCleared.connect(this,
				"exposeToJavascript()");

		notebookBox.activated.connect(this, "notebookChanged()");
		resourceSignal = new NoteResourceSignal();
		
		QHBoxLayout tagLayout = new QHBoxLayout();
		v.addWidget(titleLabel, 0);
		notebookLayout.addRow(notebookLabel, notebookBox);
		tagLayout.addLayout(notebookLayout, 0);
		tagLayout.stretch(4);
		tagLayout.addWidget(tagLabel, 0);
		tagLayout.addWidget(tagEdit, 1);
		v.addLayout(tagLayout);

		QHBoxLayout urlLayout = new QHBoxLayout();
		urlLayout.addWidget(urlLabel, 0);
		urlLayout.addWidget(urlText, 0);
		v.addLayout(urlLayout);

		QHBoxLayout authorLayout = new QHBoxLayout();
		authorLayout.addWidget(authorLabel, 0);
		authorLayout.addWidget(authorText, 0);
		authorLayout.addWidget(geoBox);
		v.addLayout(authorLayout);

		dateLayout.addWidget(createdLabel, 0, 0);
		dateLayout.addWidget(createdDate, 0, 1);
		dateLayout.addWidget(createdTime, 0, 2);
		dateLayout.setColumnStretch(9, 100);
		dateLayout.addWidget(alteredLabel, 0, 3);
		dateLayout.addWidget(alteredDate, 0, 4);
		dateLayout.addWidget(alteredTime, 0, 5);
		dateLayout.addWidget(subjectLabel, 0, 6);
		dateLayout.addWidget(subjectDate, 0, 7);
		dateLayout.addWidget(subjectTime, 0, 8);
		v.addLayout(dateLayout, 0);

		undoButton = newEditorButton("undo", tr("Undo Change"));
		redoButton = newEditorButton("redo", tr("Redo Change"));
		cutButton = newEditorButton("cut", tr("Cut"));
		copyButton = newEditorButton("copy", tr("Copy"));
		pasteButton = newEditorButton("paste", tr("Paste"));
		boldButton = newEditorButton("bold", tr("Bold"));
		underlineButton = newEditorButton("underline", tr("Underline"));
		italicButton = newEditorButton("italic", tr("Italic"));

		rightAlignButton = newEditorButton("justifyRight", tr("Right Align"));
		leftAlignButton = newEditorButton("justifyLeft", tr("Left Align"));
		centerAlignButton = newEditorButton("justifyCenter", tr("Center Align"));

		strikethroughButton = newEditorButton("strikethrough", tr("Strikethrough"));
		hlineButton = newEditorButton("hline", tr("Insert Horizontal Line"));
		indentButton = newEditorButton("indent", tr("Shift Right"));
		outdentButton = newEditorButton("outdent", tr("Shift Left"));
		bulletListButton = newEditorButton("bulletList", tr("Bullet List"));
		numberListButton = newEditorButton("numberList", tr("Number List"));
		spellCheckButton = newEditorButton("spellCheck", tr("Spell Check"));
		todoButton = newEditorButton("todo", tr("To-do"));

		
		buttonLayout = new EditorButtonBar();
		v.addWidget(buttonLayout);
		
		undoAction = buttonLayout.addWidget(undoButton);
		buttonLayout.toggleUndoVisible.triggered.connect(this, "toggleUndoVisible(Boolean)");
		redoAction = buttonLayout.addWidget(redoButton);
		buttonLayout.toggleRedoVisible.triggered.connect(this, "toggleRedoVisible(Boolean)");
		
		buttonLayout.addWidget(newSeparator());
		cutAction = buttonLayout.addWidget(cutButton);
		buttonLayout.toggleCutVisible.triggered.connect(this, "toggleCutVisible(Boolean)");
		copyAction = buttonLayout.addWidget(copyButton);
		buttonLayout.toggleCopyVisible.triggered.connect(this, "toggleCopyVisible(Boolean)");
		pasteAction = buttonLayout.addWidget(pasteButton);
		buttonLayout.togglePasteVisible.triggered.connect(this, "togglePasteVisible(Boolean)");

		buttonLayout.addWidget(newSeparator());
		boldAction = buttonLayout.addWidget(boldButton);
		buttonLayout.toggleBoldVisible.triggered.connect(this, "toggleBoldVisible(Boolean)");
		italicAction = buttonLayout.addWidget(italicButton);
		buttonLayout.toggleItalicVisible.triggered.connect(this, "toggleItalicVisible(Boolean)");
		underlineAction = buttonLayout.addWidget(underlineButton);
		buttonLayout.toggleUnderlineVisible.triggered.connect(this, "toggleUnderlineVisible(Boolean)");
		strikethroughAction = buttonLayout.addWidget(strikethroughButton);
		buttonLayout.toggleStrikethroughVisible.triggered.connect(this, "toggleStrikethroughVisible(Boolean)");

		
		buttonLayout.addWidget(newSeparator());
		leftAlignAction = buttonLayout.addWidget(leftAlignButton);
		buttonLayout.toggleLeftAlignVisible.triggered.connect(this, "toggleLeftAlignVisible(Boolean)");
		centerAlignAction = buttonLayout.addWidget(centerAlignButton);
		buttonLayout.toggleCenterAlignVisible.triggered.connect(this, "toggleCenterAlignVisible(Boolean)");
		rightAlignAction = buttonLayout.addWidget(rightAlignButton);
		buttonLayout.toggleRightAlignVisible.triggered.connect(this, "toggleRightAlignVisible(Boolean)");

		buttonLayout.addWidget(newSeparator());
		hlineAction = buttonLayout.addWidget(hlineButton);
		buttonLayout.toggleHLineVisible.triggered.connect(this, "toggleHLineVisible(Boolean)");

		indentAction = buttonLayout.addWidget(indentButton);
		buttonLayout.toggleIndentVisible.triggered.connect(this, "toggleIndentVisible(Boolean)");
		outdentAction = buttonLayout.addWidget(outdentButton);
		buttonLayout.toggleOutdentVisible.triggered.connect(this, "toggleOutdentVisible(Boolean)");
		bulletListAction = buttonLayout.addWidget(bulletListButton);
		buttonLayout.toggleBulletListVisible.triggered.connect(this, "toggleBulletListVisible(Boolean)");
		numberListAction = buttonLayout.addWidget(numberListButton);
		buttonLayout.toggleNumberListVisible.triggered.connect(this, "toggleNumberListVisible(Boolean)");

		// Setup the font & font size combo boxes
		buttonLayout.addWidget(newSeparator());
		fontList = new QComboBox();
		fontSize = new QComboBox();
		fontList.setMaximumWidth(130);
		fontSize.setMaximumWidth(45);
		fontSize.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed);
		fontList.setToolTip("Font");
		fontSize.setToolTip("Font Size");
		fontList.setStyleSheet("QComboBox {padding: 3px;} ");
		fontSize.setStyleSheet("QComboBox {padding: 3px;} ");
		fontList.activated.connect(this, "fontChanged(String)");
		fontSize.activated.connect(this, "fontSizeChanged(String)");
		fontListAction = buttonLayout.addWidget(fontList);
		buttonLayout.toggleFontVisible.triggered.connect(this, "toggleFontListVisible(Boolean)");
		fontSizeAction = buttonLayout.addWidget(fontSize);
		buttonLayout.toggleFontSizeVisible.triggered.connect(this, "toggleFontSizeVisible(Boolean)");
		QFontDatabase fonts = new QFontDatabase();
		List<String> fontFamilies = fonts.families();
		for (int i = 0; i < fontFamilies.size(); i++) {
			fontList.addItem(fontFamilies.get(i));
			if (i == 0) {
				loadFontSize(fontFamilies.get(i));
			}
		}

//		buttonLayout.addWidget(newSeparator(), 0);
		fontColor = newToolButton("fontColor", tr("Font Color"));
		fontColorMenu = new ColorMenu(this);
		fontColor.setMenu(fontColorMenu.getMenu());
		fontColor.setPopupMode(ToolButtonPopupMode.MenuButtonPopup);
		fontColor.setAutoRaise(false);
		fontColorMenu.getMenu().triggered.connect(this, "fontColorClicked()");
		fontColorAction = buttonLayout.addWidget(fontColor);
		buttonLayout.toggleFontColorVisible.triggered.connect(this, "toggleFontColorVisible(Boolean)");
		fontHilight = newToolButton("fontHilight", tr("Font Hilight Color"));
		fontHilight.setPopupMode(ToolButtonPopupMode.MenuButtonPopup);
		fontHilight.setAutoRaise(false);
		fontHilightColorMenu = new ColorMenu(this);
		fontHilightColorMenu.setDefault(QColor.yellow);
		fontHilight.setMenu(fontHilightColorMenu.getMenu());
		fontHilightColorMenu.getMenu().triggered.connect(this, "fontHilightClicked()");
		fontHilightAction = buttonLayout.addWidget(fontHilight);
		fontHilightColorMenu.setDefault(QColor.yellow);
		buttonLayout.toggleFontHilight.triggered.connect(this, "toggleFontHilightVisible(Boolean)");
		
		spellCheckAction = buttonLayout.addWidget(spellCheckButton);
		buttonLayout.toggleNumberListVisible.triggered.connect(this, "spellCheckClicked()");
		buttonLayout.toggleSpellCheck.triggered.connect(this, "toggleSpellCheckVisible(Boolean)");
		
		todoAction = buttonLayout.addWidget(todoButton);
		buttonLayout.toggleNumberListVisible.triggered.connect(this, "todoClicked()");
		buttonLayout.toggleTodo.triggered.connect(this, "toggleTodoVisible(Boolean)");

		// Setup the source browser);

//		buttonLayout.addWidget(new QLabel(), 1);
		QSplitter editSplitter = new QSplitter(this);
		editSplitter.addWidget(browser);
		editSplitter.setOrientation(Qt.Orientation.Vertical);
		editSplitter.addWidget(sourceEdit);

		

//		v.addWidget(browser, 1);
//		v.addWidget(sourceEdit);
		v.addWidget(editSplitter);
		setLayout(v);

		browser.downloadAttachmentRequested.connect(this,
				"downloadAttachment(QNetworkRequest)");
		browser.downloadImageRequested.connect(this,
				"downloadImage(QNetworkRequest)");
		setTabOrder(notebookBox, tagEdit);
		setTabOrder(tagEdit, browser);
		
		focusNoteShortcut = new QShortcut(this);
		setupShortcut(focusNoteShortcut, "Focus_Note");
		focusNoteShortcut.activated.connect(this, "focusNote()");
		focusTitleShortcut = new QShortcut(this);
		setupShortcut(focusTitleShortcut, "Focus_Title");
		focusTitleShortcut.activated.connect(this, "focusTitle()");
		focusTagShortcut = new QShortcut(this);
		setupShortcut(focusTagShortcut, "Focus_Tag");
		focusTagShortcut.activated.connect(this, "focusTag()");
		focusAuthorShortcut = new QShortcut(this);
		setupShortcut(focusAuthorShortcut, "Focus_Author");
		focusAuthorShortcut.activated.connect(this, "focusAuthor()");
		focusUrlShortcut = new QShortcut(this);
		setupShortcut(focusUrlShortcut, "Focus_Url");
		focusUrlShortcut.activated.connect(this, "focusUrl()");
		
		browser.page().mainFrame().setTextSizeMultiplier(Global.getTextSizeMultiplier());
		browser.page().mainFrame().setZoomFactor(Global.getZoomFactor());
		
		previewPageList = new HashMap<String,Integer>();
		
		browser.page().microFocusChanged.connect(this, "microFocusChanged()");
		
		//Setup colors
		
		QPalette pal = new QPalette();
		pal.setColor(ColorRole.Text, QColor.black);
		titleLabel.setPalette(pal);
		authorText.setPalette(pal);
		authorLabel.setPalette(pal);
		urlLabel.setPalette(pal);
		urlText.setPalette(pal);
		createdDate.setPalette(pal);
		createdTime.setPalette(pal);
		alteredDate.setPalette(pal);
		alteredTime.setPalette(pal);
		subjectDate.setPalette(pal);
		subjectTime.setPalette(pal);
		tagEdit.setPalette(pal);
		notebookBox.setPalette(pal);
		
		blockApplication = new Signal1<BrowserWindow>();
		unblockApplication = new Signal0();
		
		setSourceTimer = new QTimer();
		setSourceTimer.timeout.connect(this, "setSource()");
		
		logger.log(logger.HIGH, "Browser setup complete");
	}

	
	
	private void setupShortcut(QShortcut action, String text) {
		if (!Global.shortcutKeys.containsAction(text))
			return;
		action.setKey(new QKeySequence(Global.shortcutKeys.getShortcut(text)));
	}
	
	

	
	// Getter for the QWebView
	public QWebView getBrowser() {
		return browser;
	}

	// Block signals while loading data or things are flagged as dirty by
	// mistake
	public void loadingData(boolean val) {
		logger.log(logger.EXTREME, "Entering BrowserWindow.loadingData() " +val);
		notebookBox.blockSignals(val);
		browser.page().blockSignals(val);
		browser.page().mainFrame().blockSignals(val);
		titleLabel.blockSignals(val);
		alteredDate.blockSignals(val);
		alteredTime.blockSignals(val);
		createdTime.blockSignals(val);
		createdDate.blockSignals(val);
		subjectDate.blockSignals(val);
		subjectTime.blockSignals(val);
		urlText.blockSignals(val);
		authorText.blockSignals(val);
		if (!val)
			exposeToJavascript();
		logger.log(logger.EXTREME, "Exiting BrowserWindow.loadingData() " +val);
	}

	// Enable/disable
	public void setReadOnly(boolean v) {
		setEnabled(true);
		titleLabel.setEnabled(!v);
		notebookBox.setEnabled(!v);
		tagEdit.setEnabled(!v);
		authorLabel.setEnabled(!v);
		geoBox.setEnabled(!v);
		urlText.setEnabled(!v);
		createdDate.setEnabled(!v);
		subjectDate.setEnabled(!v);
		alteredDate.setEnabled(!v);
		authorText.setEnabled(!v);
		createdTime.setEnabled(!v);
		alteredTime.setEnabled(!v);
		subjectTime.setEnabled(!v);
		getBrowser().setEnabled(true);
		getBrowser().page().setContentEditable(!v);
//		getBrowser().setEnabled(!v);
	}
	
	// expose this class to Javascript on the web page
	private void exposeToJavascript() {
		browser.page().mainFrame().addToJavaScriptWindowObject("jambi", this);
	}

	// Custom event queue
	@Override
	public boolean event(QEvent e) {
		if (e.type().equals(QEvent.Type.FocusOut)) {
			logger.log(logger.EXTREME, "Focus lost");
			focusLost.emit();
		}
		return super.event(e);
	}

	// clear out browser
	public void clear() {
		logger.log(logger.EXTREME, "Entering BrowserWindow.clear()");
		setNote(null);
		setContent(new QByteArray());
		tagEdit.setText("");
		tagEdit.tagCompleter.reset();
		urlLabel.setText(tr("Source URL:"));
		titleLabel.setText("");
		logger.log(logger.EXTREME, "Exiting BrowserWindow.clear()");
	}

	public void setContent(QByteArray data) {
		sourceEdit.blockSignals(true);
		browser.setContent(data);
		setSource();
	}
	// get/set current note
	public void setNote(Note n) {
		currentNote = n;
		if (n == null)
			n = new Note();
		saveNoteTitle = n.getTitle();

	}

	public Note getNote() {
		return currentNote;
	}

	// New Editor Button
	private QPushButton newEditorButton(String name, String toolTip) {
		QPushButton button = new QPushButton();
//		QIcon icon = new QIcon(iconPath + name + ".gif");
		QIcon icon = new QIcon(iconPath + name + ".png");
		button.setIcon(icon);
		button.setIconSize(new QSize(16, 16));
		
		button.setToolTip(toolTip);
		button.clicked.connect(this, name + "Clicked()");
		button.setStyleSheet("QPushButton {padding: 3px;} ");
		return button;
	}
	// New Editor Button
	private QToolButton newToolButton(String name, String toolTip) {
		QToolButton button = new QToolButton();
//		QIcon icon = new QIcon(iconPath + name + ".gif");
		QIcon icon = new QIcon(iconPath + name + ".png");
		button.setIcon(icon);
		button.setIconSize(new QSize(16, 16));
		
		button.setToolTip(toolTip);
		button.clicked.connect(this, name + "Clicked()");
		button.setStyleSheet("QToolButton {padding: 3px;} ");
		button.setMaximumHeight(22);
		return button;
	}

	// New Separator
	private QLabel newSeparator() {
		return new QLabel("");
	}

	// Set the title in the window
	public void setTitle(String t) {
		titleLabel.setText(t);
		saveNoteTitle = t;
		checkNoteTitle();
	}

	// Return the current text title
	public String getTitle() {
		return titleLabel.text();
	}

	// Set the tag name string
	public void setTag(String t) {
		saveTagList = t;
		tagEdit.setText(t);
		tagEdit.tagCompleter.reset();
	}

	// Set the source URL
	public void setUrl(String t) {
		urlLabel.setText(tr("Source URL:\t"));
		urlText.setText(t);
	}

	// The user want's to launch a web browser on the source of the URL
	public void sourceUrlClicked() {
		// Make sure we have a valid URL
		if (urlText.text().trim().equals(""))
			return;
		
		String url = urlText.text();
		if (!url.toLowerCase().startsWith(tr("http://")))
			url = tr("http://") +url;
		
        if (!QDesktopServices.openUrl(new QUrl(url))) {
        	logger.log(logger.LOW, "Error opening file :" +url);
        }
	}
	
	public void setAuthor(String t) {
		authorLabel.setText(tr("Author:\t"));
		authorText.setText(t);
	}

	// Set the creation date
	public void setCreation(long date) {
		QDateTime dt = new QDateTime();
		dt.setTime_t((int) (date / 1000));
		createdDate.setDateTime(dt);
		createdTime.setDateTime(dt);
		createdDate.setDisplayFormat(Global.getDateFormat());
		createdTime.setDisplayFormat(Global.getTimeFormat());
	}

	// Set the creation date
	public void setAltered(long date) {
		QDateTime dt = new QDateTime();
		dt.setTime_t((int) (date / 1000));
		alteredDate.setDateTime(dt);
		alteredTime.setDateTime(dt);
		alteredDate.setDisplayFormat(Global.getDateFormat());
		alteredTime.setDisplayFormat(Global.getTimeFormat());
	}

	// Set the subject date
	public void setSubjectDate(long date) {
		QDateTime dt = new QDateTime();
		dt.setTime_t((int) (date / 1000));
		subjectDate.setDateTime(dt);
		subjectTime.setDateTime(dt);
		subjectDate.setDisplayFormat(Global.getDateFormat());
		subjectTime.setDisplayFormat(Global.getTimeFormat());
	}

	// Toggle the extended attribute information
	public void toggleInformation() {
		if (extendedOn) {
			extendedOn = false;
		} else {
			extendedOn = true;
		}
		urlLabel.setVisible(extendedOn);
		urlText.setVisible(extendedOn);
		authorText.setVisible(extendedOn);
		geoBox.setVisible(extendedOn);
		authorLabel.setVisible(extendedOn);
		createdDate.setVisible(extendedOn);
		createdTime.setVisible(extendedOn);
		createdLabel.setVisible(extendedOn);
		alteredLabel.setVisible(extendedOn);
		alteredDate.setVisible(extendedOn);
		alteredTime.setVisible(extendedOn);
		//notebookBox.setVisible(extendedOn);
		notebookLabel.setVisible(extendedOn);
		subjectLabel.setVisible(extendedOn);
		subjectDate.setVisible(extendedOn);
		subjectTime.setVisible(extendedOn);
	}

	public void hideButtons() {

		undoButton.parentWidget().setVisible(false);
		buttonsVisible = false;
	}


	// Is the extended view on?
	public boolean isExtended() {
		return extendedOn;
	}

	// Listener for when a link is clicked
	@SuppressWarnings("unused")
	private void openFile() {
		logger.log(logger.EXTREME, "Starting openFile()");
		File fileHandle = new File(selectedFile);
		URI fileURL = fileHandle.toURI();
		String localURL = fileURL.toString();
		QUrl url = new QUrl(localURL);
		QFile file = new QFile(selectedFile);
		
		logger.log(logger.EXTREME, "Adding to fileWatcher:"+file.fileName());
		fileWatcher.addPath(file.fileName());
        
        if (!QDesktopServices.openUrl(url)) {
        	logger.log(logger.LOW, "Error opening file :" +url);
        }
	}
	
	
	// Listener for when a link is clicked
	@SuppressWarnings("unused")
	private void linkClicked(QUrl url) {
		logger.log(logger.EXTREME, "URL Clicked: " +url.toString());
		if (url.toString().startsWith("latex:")) {
			int position = url.toString().lastIndexOf(".");
			String guid = url.toString().substring(0,position);
			position = guid.lastIndexOf("/");
			guid = guid.substring(position+1);
			editLatex(guid);
			return;
		}
		if (url.toString().startsWith("evernote:/view/")) {
			StringTokenizer tokens = new StringTokenizer(url.toString().replace("evernote:/view/", ""), "/");
			tokens.nextToken();
			tokens.nextToken();
			String sid = tokens.nextToken();
			String lid = tokens.nextToken();
			
			// Emit that we want to switch to a new note
			evernoteLinkClicked.emit(sid, lid);

			return;
		}
		if (url.toString().startsWith("nnres://")) {
			logger.log(logger.EXTREME, "URL is NN resource");
			if (url.toString().endsWith("/vnd.evernote.ink")) {
				logger.log(logger.EXTREME, "Unable to open ink note");
				QMessageBox.information(this, tr("Unable Open"), tr("This is an ink note.\n"+
					"Ink notes are not supported since Evernote has not\n published any specifications on them\n" +
					"and I'm too lazy to figure them out by myself."));
				return;
			}
			String fullName = url.toString().substring(8);
			int index = fullName.indexOf(".");
			String guid = "";
			String type = "";
			if (index >-1) {
				type = fullName.substring(index+1);
				guid = fullName.substring(0,index);
			}
			index = guid.indexOf(Global.attachmentNameDelimeter);
			if (index > -1) {
				guid = guid.substring(0,index);
			}
			List<Resource> resList = currentNote.getResources();
			Resource res = null;
			for (int i=0; i<resList.size(); i++) {
				if (resList.get(i).getGuid().equals(guid)) {
					res = resList.get(i);
					i=resList.size();
				}
			}
			if (res == null) {
				String resGuid = Global.resourceMap.get(guid);
				if (resGuid != null) 
					res = conn.getNoteTable().noteResourceTable.getNoteResource(resGuid, true);
			}
			if (res != null) {
				String fileName;
				if (res.getAttributes() != null && 
						res.getAttributes().getFileName() != null && 
						!res.getAttributes().getFileName().trim().equals(""))
					fileName = res.getGuid()+Global.attachmentNameDelimeter+res.getAttributes().getFileName();
				else
					fileName = res.getGuid()+"."+type;
				QFile file = new QFile(Global.getFileManager().getResDirPath(fileName));
		        QFile.OpenMode mode = new QFile.OpenMode();
		        mode.set(QFile.OpenModeFlag.WriteOnly);
		        boolean openResult = file.open(mode);
				logger.log(logger.EXTREME, "File opened:" +openResult);
		        QDataStream out = new QDataStream(file);
		        Resource resBinary = conn.getNoteTable().noteResourceTable.getNoteResource(res.getGuid(), true);
				QByteArray binData = new QByteArray(resBinary.getData().getBody());
				resBinary = null;
				logger.log(logger.EXTREME, "Writing resource");
		        out.writeBytes(binData.toByteArray());
		        file.close();
			        
		        String whichOS = System.getProperty("os.name");
				if (whichOS.contains("Windows")) 
		        	url.setUrl("file:///"+file.fileName());
		        else
		        	url.setUrl("file://"+file.fileName());
		 //       fileWatcher.removePath(file.fileName());
				logger.log(logger.EXTREME, "Adding file watcher " +file.fileName());
				fileWatcher.addPath(file.fileName());
		        
		        // If we can't open it, then prompt the user to save it.
		        if (!QDesktopServices.openUrl(url)) {
					logger.log(logger.EXTREME, "We can't handle this.  Where do we put it?");
		        	QFileDialog dialog = new QFileDialog();
		        	dialog.show();
		        	if (dialog.exec()!=0) {
		        		List<String> fileNames = dialog.selectedFiles(); //gets all selected filenames
		        		if (fileNames.size() == 0) 
		        			return;
		        		String sf = fileNames.get(0);
		        		QFile saveFile = new QFile(sf);
				        mode.set(QFile.OpenModeFlag.WriteOnly);
				        saveFile.open(mode);
				        QDataStream saveOut = new QDataStream(saveFile);
				        saveOut.writeBytes(binData.toByteArray());
				        saveFile.close();
				        return;
		        	}
				}
			}
			return;
		}
		logger.log(logger.EXTREME, "Launching URL");
		QDesktopServices.openUrl(url);
	}

	// Listener for when BOLD is clicked
	@SuppressWarnings("unused")
	private void undoClicked() {
		browser.page().triggerAction(WebAction.Undo);
		browser.setFocus();
	}

	// Listener for when BOLD is clicked
	@SuppressWarnings("unused")
	private void redoClicked() {
		browser.page().triggerAction(WebAction.Redo);
		browser.setFocus();
	}

	// Listener for when BOLD is clicked
	@SuppressWarnings("unused")
	private void boldClicked() {
		browser.page().triggerAction(WebAction.ToggleBold);
		microFocusChanged();
		browser.setFocus();
	}

	// Listener for when Italics is clicked
	@SuppressWarnings("unused")
	private void italicClicked() {
		browser.page().triggerAction(WebAction.ToggleItalic);
		microFocusChanged();
		browser.setFocus();
	}

	// Listener for when UNDERLINE is clicked
	@SuppressWarnings("unused")
	private void underlineClicked() {
		browser.page().triggerAction(WebAction.ToggleUnderline);
		microFocusChanged();
		browser.setFocus();
	}

	// Listener for when Strikethrough is clicked
	@SuppressWarnings("unused")
	private void strikethroughClicked() {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('strikeThrough', false, '');");
		browser.setFocus();
	}

	// Listener for when cut is clicked
	@SuppressWarnings("unused")
	private void cutClicked() {
		cbObserver.setCopySourceGuid(currentNote.getGuid(), browser.page().selectedText());
		
		browser.page().triggerAction(WebAction.Cut);
		browser.setFocus();
	}

	// Listener when COPY is clicked
	@SuppressWarnings("unused")
	private void copyClicked() {
		cbObserver.setCopySourceGuid(currentNote.getGuid(), browser.page().selectedText());
		
		browser.page().triggerAction(WebAction.Copy);
		browser.setFocus();
	}

	// Listener when PASTE is clicked
	public void pasteClicked() {
		logger.log(logger.EXTREME, "Paste Clicked");
		if (forceTextPaste) {
			pasteWithoutFormattingClicked();
			return;
		}
		
		// コピー＆ペーストの操作履歴をデータベースに登録
		String srcGuid = cbObserver.getSourceGuid();
		String dstGuid = currentNote.getGuid();
		if(srcGuid != null && dstGuid != null){
			if(!srcGuid.equals(dstGuid)){
				conn.getHistoryTable().addHistory("copy & paste", srcGuid, dstGuid);
			}
		}
		
		QClipboard clipboard = QApplication.clipboard();
		QMimeData mime = clipboard.mimeData();

		if (mime.hasImage()) {
			logger.log(logger.EXTREME, "Image paste found");
			browser.setFocus();
			insertImage(mime);
			browser.setFocus();
			return;
		}

		if (mime.hasUrls()) {
			logger.log(logger.EXTREME, "URL paste found");
			if (mime.text().startsWith("evernote:")) {
				handleNoteLink(mime);
			} else {
				handleUrls(mime);
				browser.setFocus();
			}
			return;
		}
		
		String text = mime.html();
		if (text.contains("en-tag") && mime.hasHtml()) {
			logger.log(logger.EXTREME, "Intra-note paste found");
			text = fixInternotePaste(text);
			mime.setHtml(text);
			clipboard.setMimeData(mime);
		}

		logger.log(logger.EXTREME, "Final paste choice encountered");
		browser.page().triggerAction(WebAction.Paste);
		browser.setFocus();

	}

	// Paste text without formatting
	private void pasteWithoutFormattingClicked() {
		logger.log(logger.EXTREME, "Paste without format clipped");
		QClipboard clipboard = QApplication.clipboard();
		QMimeData mime = clipboard.mimeData();
		if (!mime.hasText())
			return;
		
		// コピー＆ペーストの操作履歴をデータベースに登録
		String srcGuid = cbObserver.getSourceGuid();
		String dstGuid = currentNote.getGuid();
		if(srcGuid != null && dstGuid != null){
			if(!srcGuid.equals(dstGuid)){
				conn.getHistoryTable().addHistory("copy & paste", srcGuid, dstGuid);
			}
		}
		
		String text = mime.text();
		clipboard.clear();
		clipboard.setText(text, Mode.Clipboard);
		browser.page().triggerAction(WebAction.Paste);

		// This is done because pasting into an encryption block
		// can cause multiple cells (which can't happen).  It 
		// just goes through the table, extracts the data, & 
		// puts it back as one table cell.
		if (insideEncryption) {
			String js = new String( "function fixEncryption() { "
					+"   var selObj = window.getSelection();"
					+"   var selRange = selObj.getRangeAt(0);"
					+"   var workingNode = window.getSelection().anchorNode;"
					+"   while(workingNode != null && workingNode.nodeName.toLowerCase() != 'table') { " 
					+"           workingNode = workingNode.parentNode;"
					+"   } "
					+"   workingNode.innerHTML = window.jambi.fixEncryptionPaste(workingNode.innerHTML);"
					+"} fixEncryption();");
			browser.page().mainFrame().evaluateJavaScript(js);
		}
	}
	
	// This basically removes all the table tags and returns just the contents.
	// This is called by JavaScript to fix encryption pastes.
	public String fixEncryptionPaste(String data) {
		data = data.replace("<tbody>", "");
		data = data.replace("</tbody>", "");
		data = data.replace("<tr>", "");
		data = data.replace("</tr>", "");
		data = data.replace("<td>", "");
		data = data.replace("</td>", "<br>");
		data = data.replace("<br><br>", "<br>");

		return "<tbody><tr><td>"+data+"</td></tr></tbody>";
	}
	
	// insert date/time
	@SuppressWarnings("unused")
	private void insertDateTime() {
		String fmt = Global.getDateFormat() + " " + Global.getTimeFormat();
		String dateTimeFormat = new String(fmt);
		SimpleDateFormat simple = new SimpleDateFormat(dateTimeFormat);
		Calendar cal = Calendar.getInstance();
		
		browser.page().mainFrame().evaluateJavaScript(
			"document.execCommand('insertHtml', false, '"+simple.format(cal.getTime())+"');");
		
		browser.setFocus();

	}

	// Listener when Left is clicked
	@SuppressWarnings("unused")
	private void justifyLeftClicked() {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('JustifyLeft', false, '');");
		browser.setFocus();
	}

	// Listener when Center is clicked
	@SuppressWarnings("unused")
	private void justifyCenterClicked() {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('JustifyCenter', false, '');");
		browser.setFocus();
	}

	// Listener when Left is clicked
	@SuppressWarnings("unused")
	private void justifyRightClicked() {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('JustifyRight', false, '');");
		browser.setFocus();
	}

	// Listener when HLINE is clicked
	@SuppressWarnings("unused")
	private void hlineClicked() {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('insertHorizontalRule', false, '');");
		browser.setFocus();
	}

	// Listener when outdent is clicked
	private void outdentClicked() {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('outdent', false, '');");
		browser.setFocus();
	}

	// Listener when a bullet list is clicked
	@SuppressWarnings("unused")
	private void bulletListClicked() {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('InsertUnorderedList', false, '');");
		browser.setFocus();
	}

	// Listener when a bullet list is clicked
	@SuppressWarnings("unused")
	private void numberListClicked() {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('InsertOrderedList', false, '');");
		browser.setFocus();
	}

	// Listener when indent is clicked
	private void indentClicked() {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('indent', false, '');");
		browser.setFocus();
	}

	// Listener when the font name is changed
	@SuppressWarnings("unused")
	private void fontChanged(String font) {
		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('fontName',false,'" + font + "');");
		browser.setFocus();
	}

	// Listener when a font size is changed
	@SuppressWarnings("unused")
	private void fontSizeChanged(String font) {
		String text = browser.selectedText();
		if (text.trim().equalsIgnoreCase(""))
			return;

		String selectedText = browser.selectedText();
		String url = "<span style=\"font-size:" +font +"pt; \">"+selectedText +"</a>";
		String script = "document.execCommand('insertHtml', false, '"+url+"');";
		browser.page().mainFrame().evaluateJavaScript(script);
/*		browser.page().mainFrame().evaluateJavaScript(
				"document.execCommand('fontSize',false,'"
						+ font + "');");
*/
		browser.setFocus();
	}

	// Load the font combo box based upon the font selected
	private void loadFontSize(String name) {	
		QFontDatabase db = new QFontDatabase(); 
		fontSize.clear();
		List<Integer> points = db.pointSizes(name); 
		for (int i=0; i<points.size(); i++) { 
			fontSize.addItem(points.get(i).toString()); 
		}
		/*
		fontSize.addItem("x-small");
		fontSize.addItem("small");
		fontSize.addItem("medium");
		fontSize.addItem("large");
		fontSize.addItem("x-large");
		fontSize.addItem("xx-large");
		fontSize.addItem("xxx-large");
		*/
	}

	// Listener when a font size is changed
	@SuppressWarnings("unused")
	private void fontColorClicked() {
//		QColorDialog dialog = new QColorDialog();
//		QColor color = QColorDialog.getColor();
		QColor color = fontColorMenu.getColor();
		if (color.isValid())
			browser.page().mainFrame().evaluateJavaScript(
					"document.execCommand('foreColor',false,'" + color.name()
							+ "');");
		browser.setFocus();
	}

	// Listener for when a background color change is requested
	@SuppressWarnings("unused")
	private void fontHilightClicked() {
//		QColorDialog dialog = new QColorDialog();
//		QColor color = QColorDialog.getColor();
		QColor color = fontHilightColorMenu.getColor();
		if (color.isValid())
			browser.page().mainFrame().evaluateJavaScript(
					"document.execCommand('backColor',false,'" + color.name()
							+ "');");
		browser.setFocus();
	}
	
	// Listener for when a background color change is requested
	@SuppressWarnings("unused")
	private void superscriptClicked() {
		browser.page().mainFrame().evaluateJavaScript(
					"document.execCommand('superscript');");
		browser.setFocus();
	}
	
	// Listener for when a background color change is requested
	@SuppressWarnings("unused")
	private void subscriptClicked() {
		browser.page().mainFrame().evaluateJavaScript(
					"document.execCommand('subscript');");
		browser.setFocus();
	}
	// Insert a to-do checkbox
	@SuppressWarnings("unused")
	private void todoClicked() {
		FileNameMap fileNameMap = URLConnection.getFileNameMap();
		String script_start = new String(
				"document.execCommand('insertHtml', false, '");
		String script_end = new String("');");
		String todo = new String(
				"<input TYPE=\"CHECKBOX\" value=\"false\" " +
				"onMouseOver=\"style.cursor=\\'hand\\'\" " +
				"onClick=\"value=checked; window.jambi.contentChanged(); \" />");
		browser.page().mainFrame().evaluateJavaScript(
				script_start + todo + script_end);
		browser.setFocus();
	}

	// Encrypt the selected text
	@SuppressWarnings("unused")
	private void encryptText() {
		String text = browser.selectedText();
		if (text.trim().equalsIgnoreCase(""))
			return;
		text = new String(text.replaceAll("\n", "<br/>"));

		EnCryptDialog dialog = new EnCryptDialog();
		dialog.exec();
		if (!dialog.okPressed()) {
			return;
		}

		EnCrypt crypt = new EnCrypt();
		String encrypted = crypt.encrypt(text, dialog.getPassword().trim(), 64);
		String decrypted = crypt.decrypt(encrypted, dialog.getPassword().trim(), 64);

		if (encrypted.trim().equals("")) {
			QMessageBox.information(this, tr("Error"), tr("Error Encrypting String"));
			return;
		}
		StringBuffer buffer = new StringBuffer(encrypted.length() + 100);
		buffer.append("<img en-tag=\"en-crypt\" cipher=\"RC2\" hint=\""
				+ dialog.getHint().replace("'","\\'") + "\" length=\"64\" ");
		buffer.append("contentEditable=\"false\" alt=\"");
		buffer.append(encrypted);
		buffer.append("\" src=\"").append(FileUtils.toForwardSlashedPath(Global.getFileManager().getImageDirPath("encrypt.png") +"\""));
		Global.cryptCounter++;
		buffer.append(" id=\"crypt"+Global.cryptCounter.toString() +"\"");
		buffer.append(" onMouseOver=\"style.cursor=\\'hand\\'\"");
		buffer.append(" onClick=\"window.jambi.decryptText(\\'crypt"+Global.cryptCounter.toString() 
				+"\\', \\'"+encrypted+"\\', \\'"+dialog.getHint().replace("'", "\\&amp;apos;")+"\\');\"");
		buffer.append("style=\"display:block\" />");

		String script_start = new String(
				"document.execCommand('insertHtml', false, '");
		String script_end = new String("');");
		browser.page().mainFrame().evaluateJavaScript(
				script_start + buffer.toString() + script_end);
	}


	// Insert a Quick hyperlink
	public void insertQuickLink() {
		logger.log(logger.EXTREME, "Inserting link");
		String text = browser.selectedText();
		if (text.trim().equalsIgnoreCase(""))
			return;
		
		NoteQuickLinkDialog dialog = new NoteQuickLinkDialog(logger, conn, text, cbObserver);

		if (dialog.getResults().size() == 0) {
			QMessageBox.critical(null, tr("No Matches Found") ,tr("No matching notes found."));
			return;
		}
		if (dialog.getResults().size() > 1) {
			dialog.exec();
			if (!dialog.okPressed) {
				logger.log(logger.EXTREME, "Insert link canceled");
				return;
			}
		}

		User user = Global.getUserInformation();
   		String dUrl = new String("evernote:///view/") + new String(user.getId() + "/" +user.getShardId() +"/"
   				+dialog.getSelectedNote()+"/"+dialog.getSelectedNote() +"/ " +"style=\"color:#69aa35\"");
		
		String url = "<a title=\"" +dUrl
				+"\" href=" +dUrl 
				+" >"+text +"</a>";
		String script = "document.execCommand('insertHtml', false, '"+url+"');";
		browser.page().mainFrame().evaluateJavaScript(script);	
		contentChanged();
	}

	// Insert a hyperlink
	public void insertLink() {
		logger.log(logger.EXTREME, "Inserting link");
		String text = browser.selectedText();
		if (text.trim().equalsIgnoreCase(""))
			return;

		InsertLinkDialog dialog = new InsertLinkDialog(insertHyperlink);
		if (currentHyperlink != null && currentHyperlink != "") {
			dialog.setUrl(currentHyperlink);
		}
		dialog.exec();
		if (!dialog.okPressed()) {
			logger.log(logger.EXTREME, "Insert link canceled");
			return;
		}
		
		// Take care of inserting new links
		if (insertHyperlink) {
			String selectedText = browser.selectedText();
			if (dialog.getUrl().trim().equals(""))
				return;
			logger.log(logger.EXTREME, "Inserting link on text "+selectedText);
			logger.log(logger.EXTREME, "URL Link " +dialog.getUrl().trim());
			String dUrl = StringUtils.replace(dialog.getUrl().trim(), "'", "\\'");
			String url = "<a href=\"" +dUrl
					+"\" title=" +dUrl 
					+" >"+selectedText +"</a>";
			String script = "document.execCommand('insertHtml', false, '"+url+"');";
			browser.page().mainFrame().evaluateJavaScript(script);
			return;
		}
		
		// Edit existing links
		String js = new String( "function getCursorPos() {"
				+"var cursorPos;"
				+"if (window.getSelection) {"
				+"   var selObj = window.getSelection();"
				+"   var selRange = selObj.getRangeAt(0);"
				+"   var workingNode = window.getSelection().anchorNode.parentNode;"
				+"   while(workingNode != null) { " 
				+"      if (workingNode.nodeName.toLowerCase()=='a') workingNode.setAttribute('href','" +dialog.getUrl() +"');"
				+"      workingNode = workingNode.parentNode;"
				+"   }"
				+"}"
				+"} getCursorPos();");
			browser.page().mainFrame().evaluateJavaScript(js);
		
		if (!dialog.getUrl().trim().equals("")) {
			contentChanged();
			return;
		}
		
		// Remove URL
		js = new String( "function getCursorPos() {"
				+"var cursorPos;"
				+"if (window.getSelection) {"
				+"   var selObj = window.getSelection();"
				+"   var selRange = selObj.getRangeAt(0);"
				+"   var workingNode = window.getSelection().anchorNode.parentNode;"
				+"   while(workingNode != null) { " 
				+"      if (workingNode.nodeName.toLowerCase()=='a') { "
				+"         workingNode.removeAttribute('href');"
				+"         workingNode.removeAttribute('title');"
				+"         var text = document.createTextNode(workingNode.innerText);"
				+"         workingNode.parentNode.insertBefore(text, workingNode);"
				+"         workingNode.parentNode.removeChild(workingNode);"
				+"      }"
				+"      workingNode = workingNode.parentNode;"
				+"   }"
				+"}"
				+"} getCursorPos();");
			browser.page().mainFrame().evaluateJavaScript(js);
			
			contentChanged();

		
	}
	
	
	// Insert a hyperlink
	public void insertLatex() {
		editLatex(null);
	}
	public void editLatex(String guid) {
		logger.log(logger.EXTREME, "Inserting latex");
		String text = browser.selectedText();
		if (text.trim().equalsIgnoreCase("\n") || text.trim().equalsIgnoreCase("")) {
			InsertLatexImage dialog = new InsertLatexImage();
			if (guid != null) {
				String formula = conn.getNoteTable().noteResourceTable.getNoteSourceUrl(guid).replace("http://latex.codecogs.com/gif.latex?", "");
				dialog.setFormula(formula);
			}
			dialog.exec();
			if (!dialog.okPressed()) {
				logger.log(logger.EXTREME, "Edit LaTex canceled");
				return;
			}
			text = dialog.getFormula().trim();
		}
		blockApplication.emit(this);
		logger.log(logger.EXTREME, "Inserting LaTeX formula:" +text);
		latexGuid = guid;
		text = StringUtils.replace(text, "'", "\\'");
		String url = "http://latex.codecogs.com/gif.latex?" +text;
		logger.log(logger.EXTREME, "Sending request to codecogs --> " + url);
		QNetworkAccessManager manager = new QNetworkAccessManager(this);
		manager.finished.connect(this, "insertLatexImageReady(QNetworkReply)");
		unblockTime = new GregorianCalendar().getTimeInMillis()+5000;
		awaitingHttpResponse = true;
		manager.get(new QNetworkRequest(new QUrl(url)));
	}
	
	public void insertLatexImageReady(QNetworkReply reply) {
		logger.log(logger.EXTREME, "Response received from CodeCogs");
		if (reply.error() != NetworkError.NoError) 
			return;

		unblockTime = -1;
		if (!awaitingHttpResponse)
			return;
		
		awaitingHttpResponse = false;
		QUrl replyUrl = reply.url();		
		QByteArray image = reply.readAll();
		reply.close();
		logger.log(logger.EXTREME, "New image size: " +image.size());

		Resource newRes = null;
		QFile tfile;
		String path;
		if (latexGuid == null) {
			logger.log(logger.EXTREME, "Creating temporary gif");			
			path = Global.getFileManager().getResDirPath("latex-temp.gif");
			tfile = new QFile(path);
			tfile.open(new QIODevice.OpenMode(QIODevice.OpenModeFlag.WriteOnly));
			logger.log(logger.EXTREME, "File Open: " +tfile.errorString());
			tfile.write(image);
			logger.log(logger.EXTREME, "Bytes writtes: "+tfile.size());
			tfile.close();
			logger.log(logger.EXTREME, "Creating resource");
			int sequence = 0;
			if (currentNote.getResources() != null || currentNote.getResources().size() > 0)
				sequence = currentNote.getResources().size();
			newRes = createResource(path,sequence ,"image/gif", false);
			QImage pix = new QImage();
			pix.loadFromData(image);
			newRes.setHeight(new Integer(pix.height()).shortValue());
			newRes.setWidth(new Integer(pix.width()).shortValue());
			logger.log(logger.EXTREME, "Renaming temporary file to " +newRes.getGuid()+".gif");
			path = Global.getFileManager().getResDirPath(newRes.getGuid()+".gif");
			tfile.rename(path);
		} else {
			newRes = conn.getNoteTable().noteResourceTable.getNoteResource(latexGuid, false);
			path = Global.getFileManager().getResDirPath(newRes.getGuid()+".gif");
			tfile = new QFile(path);
			tfile.open(new QIODevice.OpenMode(QIODevice.OpenModeFlag.WriteOnly));
			tfile.write(image);
			tfile.close();
			newRes.getData().setBody(image.toByteArray());
			// Calculate the new hash value
	    	MessageDigest md;

	    	logger.log(logger.EXTREME, "Generating MD5");
	    	try {
				md = MessageDigest.getInstance("MD5");
		    	md.update(image.toByteArray());
		    	byte[] hash = md.digest();
		    	newRes.getData().setBodyHash(hash);
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			}
			QImage pix = new QImage();
			pix.loadFromData(image);
			newRes.setHeight(new Integer(pix.height()).shortValue());
			newRes.setWidth(new Integer(pix.width()).shortValue());
			conn.getNoteTable().noteResourceTable.updateNoteResource(newRes, true);
		}

		logger.log(logger.EXTREME, "Setting source: " +replyUrl.toString());
		newRes.getAttributes().setSourceURL(replyUrl.toString());
		conn.getNoteTable().noteResourceTable.updateNoteSourceUrl(newRes.getGuid(), replyUrl.toString(), true);
		
		for(int i=0; i<currentNote.getResourcesSize(); i++) {
			if (currentNote.getResources().get(i).getGuid().equals(newRes.getGuid())) {
				currentNote.getResources().remove(i);
				i=currentNote.getResourcesSize();
			}
		}
		currentNote.getResources().add(newRes);
		

		// do the actual insert into the note.  We only do this on new formulas.  
		if (latexGuid == null) {
			StringBuffer buffer = new StringBuffer(100);
			String formula = replyUrl.toString().toLowerCase().replace("http://latex.codecogs.com/gif.latex?", "");
			buffer.append("<a href=\"latex://"+path.replace("\\", "/")+"\" title=\""+formula+"\""
					+"><img src=\"");
			buffer.append(path.replace("\\", "/"));
			buffer.append("\" en-tag=\"en-latex\" type=\"image/gif\""
				+" hash=\""+Global.byteArrayToHexString(newRes.getData().getBodyHash()) +"\""
				+" guid=\"" +newRes.getGuid() +"\""
				+ " /></a>");
		
			String script_start = new String("document.execCommand('insertHTML', false, '");
			String script_end = new String("');");
			browser.page().mainFrame().evaluateJavaScript(
					script_start + buffer + script_end);
		} else {
			HtmlTagModifier modifier = new HtmlTagModifier(getContent());
			modifier.modifyLatexTagHash(newRes);
			String newContent = modifier.getHtml();
			setContent(new QByteArray(newContent));
		}

		logger.log(logger.EXTREME, "New HTML set\n" +browser.page().currentFrame().toHtml());
		QWebSettings.setMaximumPagesInCache(0);
		QWebSettings.setObjectCacheCapacities(0, 0, 0);
		
		browser.page().mainFrame().setHtml(browser.page().mainFrame().toHtml());
		browser.reload();
		contentChanged();
//		resourceSignal.contentChanged.emit(path);
		unblockTime = -1;
    	unblockApplication.emit();
		return;
		
	}

	
	
	// Insert a table
	public void insertTable() {
		TableDialog dialog = new TableDialog();
		dialog.exec();
		if (!dialog.okPressed()) {
			return;
		}
		
		int cols = dialog.getCols();
		int rows = dialog.getRows();
		int width = dialog.getWidth();
		boolean percent = dialog.isPercent();
		
		String newHTML = "<table border=\"1\" width=\"" +new Integer(width).toString();
		if (percent)
			newHTML = newHTML +"%";
		newHTML = newHTML + "\"><tbody>";

		for (int i=0; i<rows; i++) {
			newHTML = newHTML +"<tr>";
			for (int j=0; j<cols; j++) {
				newHTML = newHTML +"<td>&nbsp;</td>";
			}
			newHTML = newHTML +"</tr>";
		}
		newHTML = newHTML+"</tbody></table>";	
	
		String script = "document.execCommand('insertHtml', false, '"+newHTML+"');";
		browser.page().mainFrame().evaluateJavaScript(script);
	}
	
	
	// Text content changed
	@SuppressWarnings("unused")
	private void selectionChanged() {
		browser.encryptAction.setEnabled(true);
		browser.insertLinkAction.setEnabled(true);
		browser.insertQuickLinkAction.setEnabled(true);
		String scriptStart = "var selection_text = (window.getSelection()).toString();"
				+ "var range = (window.getSelection()).getRangeAt(0);"
				+ "var parent_html = range.commonAncestorContainer.innerHTML;"
				+ "if (parent_html == undefined) {window.jambi.saveSelectedText(selection_text); return;}"
				+ "var first_text = range.startContainer.nodeValue.substr(range.startOffset);"
				+ "var last_text = (range.endContainer.nodeValue).substring(0,range.endOffset);"
				+ "var start = parent_html.indexOf(first_text);"
				+ "var end = parent_html.indexOf(last_text,start+1)+last_text.length;"
				+ "var value = parent_html.substring(start,end);"
				+ "window.jambi.saveSelectedText(value);" ;
		browser.page().mainFrame().evaluateJavaScript(scriptStart);

	}

	public void saveSelectedText(String text) {
		boolean enabled = true;
		if (text.trim().length() == 0)
			enabled=false;
		if (text.indexOf("en-tag=\"en-crypt\"") >= 0)
			enabled=false;
		if (text.indexOf("<img en-tag=\"en-media\"") >= 0)
			enabled=false;
		if (text.indexOf("<a en-tag=\"en-media\"") >= 0)
			enabled=false;
		if (text.indexOf("<input ") >= 0)
			enabled=false;
		
		browser.encryptAction.setEnabled(enabled);
		browser.insertLinkAction.setEnabled(enabled);
		browser.insertQuickLinkAction.setEnabled(enabled);
//		selectedText = text;
	}

	// Decrypt clicked text
	public void decryptText(String id, String text, String hint) {
		EnCrypt crypt = new EnCrypt();
		String plainText = null;
		Calendar currentTime = new GregorianCalendar();
		Long l = new Long(currentTime.getTimeInMillis());
		String slot = new String(Long.toString(l));
		
		// First, try to decrypt with any keys we already have
		for (int i=0; i<Global.passwordRemember.size(); i++) {
			plainText = crypt.decrypt(text, Global.passwordRemember.get(i).getFirst(), 64);
			if (plainText != null) {
				slot = new String(Long.toString(l));
				Global.passwordSafe.put(slot, Global.passwordRemember.get(i));
				removeEncryption(id, plainText, false, slot);	
				return;
			}
		}
		
		
		EnDecryptDialog dialog = new EnDecryptDialog();
		dialog.setHint(hint);
		while (plainText == null || !dialog.okPressed()) {
			dialog.exec();
			if (!dialog.okPressed()) {
				return;
			}
			plainText = crypt.decrypt(text, dialog.getPassword().trim(), 64);
			if (plainText == null) {
				QMessageBox.warning(this, tr("Incorrect Password"), tr("The password entered is not correct"));
			}
		}
		Pair<String,String> passwordPair = new Pair<String,String>();
		passwordPair.setFirst(dialog.getPassword());
		passwordPair.setSecond(dialog.getHint());
		Global.passwordSafe.put(slot, passwordPair);
//		removeEncryption(id, plainText.replaceAll("\n", "<br/>"), dialog.permanentlyDecrypt(), slot);
		removeEncryption(id, plainText, dialog.permanentlyDecrypt(), slot);
		if (dialog.rememberPassword()) {
			Pair<String, String> pair = new Pair<String,String>();
			pair.setFirst(dialog.getPassword());
			pair.setSecond(dialog.getHint());
			Global.passwordRemember.add(pair);
		}

	}

	// Get the editor tag line
	public TagLineEdit getTagLine() {
		return tagEdit;
	}

	// Modify a note's tags
	@SuppressWarnings("unused")
	private void modifyTags() {
		TagAssign tagWindow = new TagAssign(allTags, currentTags, !conn.getNotebookTable().isLinked(currentNote.getNotebookGuid()));
		tagWindow.exec();
		if (tagWindow.okClicked()) {
			currentTags.clear();
			StringBuffer tagDisplay = new StringBuffer();

			List<QListWidgetItem> newTags = tagWindow.getTagList()
					.selectedItems();
			for (int i = 0; i < newTags.size(); i++) {
				currentTags.add(newTags.get(i).text());
				tagDisplay.append(newTags.get(i).text());
				if (i < newTags.size() - 1) {
					tagDisplay.append(Global.tagDelimeter + " ");
				}
			}
			tagEdit.setText(tagDisplay.toString());
			noteSignal.tagsChanged.emit(currentNote.getGuid(), currentTags);
		}
	}

	// Tag line has been modified by typing text
	@SuppressWarnings("unused")
	private void modifyTagsTyping() {
		String completionText = "";
		if (tagEdit.currentCompleterSelection != null && !tagEdit.currentCompleterSelection.equals("")) {
			completionText = tagEdit.currentCompleterSelection;
			tagEdit.currentCompleterSelection = "";
		}
		
		if (tagEdit.text().equalsIgnoreCase(saveTagList))
			return;

		if (saveTagList == null) {
			return;
		}
		
		// We know something has changed...
		String oldTagArray[] = saveTagList.split(Global.tagDelimeter);
		String newTagArray[];
		if (!completionText.equals("")) {
			String before = tagEdit.text().substring(0,tagEdit.cursorPosition());
			int lastDelimiter = before.lastIndexOf(Global.tagDelimeter);
			if (lastDelimiter > 0)
				before = before.substring(0,before.lastIndexOf(Global.tagDelimeter));
			else 
				before = "";
			String after = tagEdit.text().substring(tagEdit.cursorPosition());
			newTagArray = (before+Global.tagDelimeter+completionText+Global.tagDelimeter+after).split(Global.tagDelimeter);
		}
		else {
			newTagArray = tagEdit.text().split(Global.tagDelimeter);
		}
		
		// Remove any traling or leading blanks
		for (int i=0; i<newTagArray.length; i++)
			newTagArray[i] = newTagArray[i].trim().replaceAll("^\\s+", "");;
		
		// Remove any potential duplicates from the new list
		for (int i=0; i<newTagArray.length; i++) {
			boolean foundOnce = false;
			for (int j=0; j<newTagArray.length; j++) {
				if (newTagArray[j].equalsIgnoreCase(newTagArray[i])) {
					if (!foundOnce) {
						foundOnce = true;
					} else
						newTagArray[j] = "";
				}
			}
		}

		List<String> newTagList = new ArrayList<String>();
		List<String> oldTagList = new ArrayList<String>();

		for (int i = 0; i < oldTagArray.length; i++)
			if (!oldTagArray[i].trim().equals(""))
				oldTagList.add(oldTagArray[i]);
		for (int i = 0; i < newTagArray.length; i++)
			if (!newTagArray[i].trim().equals(""))
				newTagList.add(newTagArray[i]);

		if (conn.getNotebookTable().isLinked(currentNote.getNotebookGuid())) {
			for (int i=newTagList.size()-1; i>=0; i--) {
				boolean found = false;
				for (int j=0; j<allTags.size(); j++) {
					if (allTags.get(j).getName().equalsIgnoreCase(newTagList.get(i))) {
						found = true;
						j=allTags.size();
					}
				}
				if (!found)
					newTagList.remove(i);
			}
		}

		// Let's cleanup the appearance of the tag list
		Collections.sort(newTagList);
		String newDisplay = "";
		for (int i=0; i<newTagList.size(); i++) {
			newDisplay = newDisplay+newTagList.get(i);
			if (i<newTagList.size()-1)
				newDisplay = newDisplay+Global.tagDelimeter +" ";
		}
		tagEdit.blockSignals(true);
		tagEdit.setText(newDisplay);
		tagEdit.blockSignals(false);
		
		// We now have lists of the new & old. Remove duplicates. If all
		// are removed from both then nothing has really changed
		for (int i = newTagList.size() - 1; i >= 0; i--) {
			String nTag = newTagList.get(i);
			for (int j = oldTagList.size() - 1; j >= 0; j--) {
				String oTag = oldTagList.get(j);
				if (oTag.equalsIgnoreCase(nTag)) {
					oldTagList.remove(j);
					newTagList.remove(i);
					j = -1;
				}
			}
		}

		if (oldTagList.size() != 0 || newTagList.size() != 0) {
			currentTags.clear();
			newTagArray = tagEdit.text().split(Global.tagDelimeter);
			for (int i = 0; i < newTagArray.length; i++)
				if (!newTagArray[i].trim().equals(""))
					currentTags.add(newTagArray[i].trim());

			noteSignal.tagsChanged.emit(currentNote.getGuid(), currentTags);
		}
		
	}

	// Tab button was pressed
	public void tabPressed() {
		if (insideEncryption)
			return;
		if (!insideList && !insideTable) {
			String script_start = new String(
			"document.execCommand('insertHtml', false, '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;');");
			browser.page().mainFrame().evaluateJavaScript(script_start);
			return;
		}
		if (insideList) {
			indentClicked();
		}
		if (insideTable) {
			String js = new String( "function getCursorPosition() { "
					+"   var selObj = window.getSelection();"
					+"   var selRange = selObj.getRangeAt(0);"
					+"   var workingNode = window.getSelection().anchorNode;"
					+"   var rowCount = 0;"
					+"   var colCount = 0;"
					+"   while(workingNode != null && workingNode.nodeName.toLowerCase() != 'table') { " 
					+"      if (workingNode.nodeName.toLowerCase()=='tr') {"
					+"         rowCount = rowCount+1;"
					+"      }"
					+"      if (workingNode.nodeName.toLowerCase() == 'td') {"
					+"         colCount = colCount+1;"
					+"      }"
					+"      if (workingNode.previousSibling != null)"
					+"          workingNode = workingNode.previousSibling;"
					+"      else "
					+"           workingNode = workingNode.parentNode;"
					+"   }"
					+"   var nodes = workingNode.getElementsByTagName('tr');"
					+"   var tableRows = nodes.length;"
					+"   nodes = nodes[0].getElementsByTagName('td');"
					+"   var tableColumns = nodes.length;"
					+"   window.jambi.setTableCursorPositionTab(rowCount, colCount, tableRows, tableColumns);"
					+"} getCursorPosition();");
			browser.page().mainFrame().evaluateJavaScript(js);
		}
	}
	
	// If a user presses tab from within a table
	public void setTableCursorPositionTab(int currentRow, int currentCol, int tableRows, int tableColumns) {
		if (tableRows == currentRow && currentCol == tableColumns) {
			insertTableRow();
		}
		KeyboardModifiers modifiers = new KeyboardModifiers(KeyboardModifier.NoModifier);
		QKeyEvent right = new QKeyEvent(Type.KeyPress, Qt.Key.Key_Right.value(), modifiers);
		QKeyEvent end = new QKeyEvent(Type.KeyPress, Qt.Key.Key_End.value(), modifiers);
		QKeyEvent end2 = new QKeyEvent(Type.KeyPress, Qt.Key.Key_End.value(), modifiers);
		getBrowser().focusWidget();
		QCoreApplication.postEvent(getBrowser(), end);
		QCoreApplication.postEvent(getBrowser(), right);
		QCoreApplication.postEvent(getBrowser(), end2);
	}
		
	public void backtabPressed() {
		if (insideEncryption) 
			return;
		if (insideList)
			outdentClicked();
		if (insideTable) {
			String js = new String( "function getCursorPosition() { "
					+"   var selObj = window.getSelection();"
					+"   var selRange = selObj.getRangeAt(0);"
					+"   var workingNode = window.getSelection().anchorNode;"
					+"   var rowCount = 0;"
					+"   var colCount = 0;"
					+"   while(workingNode != null && workingNode.nodeName.toLowerCase() != 'table') { " 
					+"      if (workingNode.nodeName.toLowerCase()=='tr') {"
					+"         rowCount = rowCount+1;"
					+"      }"
					+"      if (workingNode.nodeName.toLowerCase() == 'td') {"
					+"         colCount = colCount+1;"
					+"      }"
					+"      if (workingNode.previousSibling != null)"
					+"          workingNode = workingNode.previousSibling;"
					+"      else "
					+"           workingNode = workingNode.parentNode;"
					+"   }"
					+"   var nodes = workingNode.getElementsByTagName('tr');"
					+"   var tableRows = nodes.length;"
					+"   nodes = nodes[0].getElementsByTagName('td');"
					+"   var tableColumns = nodes.length;"
					+"   window.jambi.setTableCursorPositionBackTab(rowCount, colCount, tableRows, tableColumns);"
					+"} getCursorPosition();");
			browser.page().mainFrame().evaluateJavaScript(js);
			
		}
	}
	
	// If a user presses backtab from within a table
	public void setTableCursorPositionBackTab(int currentRow, int currentCol, int tableRows, int tableColumns) {
		if (currentRow  == 1 && currentCol == 1) {
			return;
		}
		KeyboardModifiers modifiers = new KeyboardModifiers(KeyboardModifier.NoModifier);
		QKeyEvent left = new QKeyEvent(Type.KeyPress, Qt.Key.Key_Left.value(), modifiers);
		QKeyEvent home = new QKeyEvent(Type.KeyPress, Qt.Key.Key_Home.value(), modifiers);
		getBrowser().focusWidget();
		QCoreApplication.postEvent(getBrowser(), home);
		QCoreApplication.postEvent(getBrowser(), left);
	}
	
	
	public void setInsideList() {
		insideList = true;
	}
	
	// The title has been edited
	@SuppressWarnings("unused")
	private void titleEdited() {
		// If we don't have a good note, or if the current title
		// matches the old title then we don't need to do anything
		if (currentNote == null)
			return;
		if (currentNote.getTitle().trim().equals(titleLabel.text().trim()))
			return;
		
		// If we have a real change, we need to save it.
		String text = titleLabel.text().trim();
		if (text.equals("")) 
			text = tr("Untitled Note");
		noteSignal.titleChanged.emit(currentNote.getGuid(), text);
		currentNote.setTitle(text);
		saveNoteTitle = text;
		checkNoteTitle();
	}

	// Set the list of note tags
	public void setAllTags(List<Tag> l) {
		allTags = l;
		tagEdit.setTagList(l);
	}

	// Setter for the current tags
	public void setCurrentTags(List<String> s) {
		currentTags = s;
	}

	// Save the list of notebooks
	public void setNotebookList(List<Notebook> n) {
		notebookList = n;
		loadNotebookList();
	}

	// Load the notebook list and select the current notebook
	private void loadNotebookList() {
		if (notebookBox.count() != 0)
			notebookBox.clear();
		if (notebookList == null)
			return;

		for (int i = 0; i < notebookList.size(); i++) {
			notebookBox.addItem(notebookList.get(i).getName());
			if (currentNote != null) {
				if (currentNote.getNotebookGuid().equals(
						notebookList.get(i).getGuid())) {
					notebookBox.setCurrentIndex(i);
				}
			}
		}
	}
	
	
	// Set the notebook for a note
	public void setNotebook(String notebook) {
		currentNote.setNotebookGuid(notebook);
		loadNotebookList();
	}

	// Get the contents of the editor
	public String getContent() {
		return browser.page().currentFrame().toHtml();
	}

	// The note contents have changed
	public void contentChanged() {
		String content = getContent();
		
		// This puts in a 1/2 second delay
		// before updating the source editor.
		// It improves response when someone is doing
		// frequent updates on a large note.
		// If the source editor isn't visible, then there
		// is no point to doing any of this.
		if (sourceEdit.isVisible()) {
			setSourceTimer.stop();
			setSourceTimer.setInterval(500);
			setSourceTimer.setSingleShot(true);
			setSourceTimer.start();
		}
		
		checkNoteTitle();
		noteSignal.noteChanged.emit(currentNote.getGuid(), content); 
	}

	// The notebook selection has changed
	@SuppressWarnings("unused")
	private void notebookChanged() {
		boolean changed = false;
		String n = notebookBox.currentText();
		for (int i = 0; i < notebookList.size(); i++) {
			if (n.equals(notebookList.get(i).getName())) {
				if (!notebookList.get(i).getGuid().equals(currentNote.getNotebookGuid())) {
					String guid = conn.getNotebookTable().findNotebookByName(n);
					if (conn.getNotebookTable().isLinked(guid)) {
						tagEdit.setText("");
						noteSignal.tagsChanged.emit(currentNote.getGuid(), new ArrayList<String>());
						FilterEditorTags t = new FilterEditorTags(conn, logger);
						setAllTags(t.getValidTags(currentNote));
					}
					currentNote.setNotebookGuid(notebookList.get(i).getGuid());
					changed = true;
				}
				i = notebookList.size();
			}
		}
		
		// If the notebook changed, signal the update
		if (changed)
			noteSignal.notebookChanged.emit(currentNote.getGuid(), currentNote
					.getNotebookGuid());
	}

	// Check the note title
	private void checkNoteTitle() {
		String text = browser.page().currentFrame().toPlainText();
		if (saveNoteTitle == null)
			saveNoteTitle = new String();
		text = text.trim();
		if (!saveNoteTitle.trim().equals("") && !saveNoteTitle.trim().equals("Untitled Note"))
			text = saveNoteTitle.trim();
		int newLine = text.indexOf("\n");
		if (newLine > 0)
			text = text.substring(0,newLine);
		if (saveNoteTitle.trim().equals("") || saveNoteTitle.trim().equals("Untitled Note")) {
			if (text.trim().equals(""))
				text = tr("Untitled Note");
				titleLabel.setText(text);
		} else {
			if (text.length() > Constants.EDAM_NOTE_TITLE_LEN_MAX)
				titleLabel.setText(text.substring(0, Constants.EDAM_NOTE_TITLE_LEN_MAX));
			else {
				titleLabel.blockSignals(true);
				if (text.trim().equals(""))
					titleLabel.setText(tr("Untitled Note"));
				else
					titleLabel.setText(text);
				titleLabel.blockSignals(false);
			}
		}
		if (currentNote != null && titleLabel != null && !currentNote.getTitle().equals(text))
			noteSignal.titleChanged.emit(currentNote.getGuid(), text);
	}

	// Return the note contents so we can email them
	public String getContentsToEmail() {
		return browser.page().currentFrame().toPlainText().trim();
		/*
		 * int body = browser.page().currentFrame().toHtml().indexOf("<body>");
		 * String temp = browser.page().currentFrame().toHtml(); if (body == -1)
		 * temp = "<html><body><b>Test</b></body></html>"; else temp =
		 * "<html>"+temp.substring(body); return temp; // return
		 * urlEncode(browser.page().currentFrame().toHtml());
		 */
	}

	// Insert an image into the editor
	private void insertImage(QMimeData mime) {
		logger.log(logger.EXTREME, "Entering insertImage");
		QImage img = (QImage) mime.imageData();
		String script_start = new String(
				"document.execCommand('insertHTML', false, '");
		String script_end = new String("');");

		long now = new Date().getTime();
		String path = Global.getFileManager().getResDirPath(
				(new Long(now).toString()) + ".jpg");

		// This block is just a hack to make sure we wait at least 1ms so we
		// don't
		// have collisions on image names
		long i = new Date().getTime();
		while (now == i)
			i = new Date().getTime();

		// Open the file & write the data
		QFile tfile = new QFile(path);
		tfile.open(new QIODevice.OpenMode(QIODevice.OpenModeFlag.WriteOnly));
		if (!img.save(tfile)) {
			tfile.close();
			return;
		}
		tfile.close();
		
		Resource newRes = createResource(QUrl.fromLocalFile(path).toString(), 0, "image/jpeg", false);
		if (newRes == null)
			return;
		currentNote.getResources().add(newRes);

		// do the actual insert into the note
		StringBuffer buffer = new StringBuffer(100);
		buffer.append("<img src=\"");
		buffer.append(tfile.fileName());
		buffer.append("\" en-tag=en-media type=\"image/jpeg\""
				+" hash=\""+Global.byteArrayToHexString(newRes.getData().getBodyHash()) +"\""
				+" guid=\"" +newRes.getGuid() +"\""
				+" onContextMenu=\"window.jambi.imageContextMenu(&amp." +tfile.fileName() +"&amp.);\""
				+ " />");
		
		browser.page().mainFrame().evaluateJavaScript(
				script_start + buffer + script_end);

		return;
	}

	// Handle pasting of a note-to-note link
	private void handleNoteLink(QMimeData mime) {
		for (int i=0; i<mime.urls().size(); i++) {
			StringTokenizer tokens = new StringTokenizer(mime.urls().get(i).toString().replace("evernote:///view/", ""), "/");
			tokens.nextToken();
			tokens.nextToken();
			String sid = tokens.nextToken();
			String lid = tokens.nextToken();
			
			if (!sid.equals(currentNote.getGuid()) && !lid.equals(currentNote.getGuid())) {
				
				Note note = conn.getNoteTable().getNote(sid, false, false, false, false, false);
				if (note == null)
					note = conn.getNoteTable().getNote(lid, false, false, false, false, false);
		
				if (note == null)
					return;

				// If we've gotten this far, we have a bunch of values.  We need to build the link.
				StringBuffer url = new StringBuffer(100);
				String script_start = new String(
					"document.execCommand('insertHtml', false, '");
				String script_end = new String("');");
	
				url.append("<a href=\""+mime.urls().get(i).toString() +"\" style=\"color:#69aa35\">");
				url.append(note.getTitle());
				url.append("</a>");
				if (mime.urls().size() > 1)
					url.append("&nbsp;");
				browser.page().mainFrame().evaluateJavaScript(
						script_start + url + script_end);
			}
		}
	}
	
	// Handle URLs that are trying to be pasted
	public void handleUrls(QMimeData mime) {
		logger.log(logger.EXTREME, "Starting handleUrls");
		FileNameMap fileNameMap = URLConnection.getFileNameMap();

		List<QUrl> urlList = mime.urls();
		String url = new String();
		String script_start = new String(
				"document.execCommand('createLink', false, '");
		String script_end = new String("');");

		for (int i = 0; i < urlList.size(); i++) {
			url = urlList.get(i).toString();
			// Find out what type of file we have
			String mimeType = fileNameMap.getContentTypeFor(url);

			// If null returned, we need to guess at the file type
			if (mimeType == null)
				mimeType = "application/"
						+ url.substring(url.lastIndexOf(".") + 1);

			// Check if we have an image or some other type of file
			if (url.substring(0, 5).equalsIgnoreCase("file:")
					&& mimeType.substring(0, 5).equalsIgnoreCase("image")) {
				handleLocalImageURLPaste(mime, mimeType);
				return;
			}

			boolean smallEnough = checkFileAttachmentSize(url);
			if (smallEnough 
					&& url.substring(0, 5).equalsIgnoreCase("file:")
					&& !mimeType.substring(0, 5).equalsIgnoreCase("image")) {
				handleLocalAttachment(mime, mimeType);
				return;
			}
			browser.page().mainFrame().evaluateJavaScript(
					script_start + url + script_end);
		}
		return;
	}

	// If a URL being pasted is an image URL, then attach the image
	private void handleLocalImageURLPaste(QMimeData mime, String mimeType) {
		List<QUrl> urlList = mime.urls();
		String url = new String();
		String script_start_image = new String(
				"document.execCommand('insertHtml', false, '");
		String script_end = new String("');");
		StringBuffer buffer;

		// Copy the image over into the resource directory and create a new resource 
		// record for each url pasted
		for (int i = 0; i < urlList.size(); i++) {
			url = urlList.get(i).toString();

			Resource newRes = createResource(url, i, mimeType, false);
			if (newRes == null)
				return;
			currentNote.getResources().add(newRes);
			buffer = new StringBuffer(100);
			
			// Open the file & write the data
			String fileName = Global.getFileManager().getResDirPath(newRes.getGuid());
			QFile tfile = new QFile(fileName);
			tfile.open(new QIODevice.OpenMode(QIODevice.OpenModeFlag.WriteOnly));
			tfile.write(newRes.getData().getBody());
			tfile.close();
			buffer.append(script_start_image);
			buffer.append("<img src=\"" + FileUtils.toForwardSlashedPath(fileName));
//			if (mimeType.equalsIgnoreCase("image/jpg"))
//				mimeType = "image/jpeg";
			buffer.append("\" en-tag=\"en-media\" type=\"" + mimeType +"\""
					+" hash=\""+Global.byteArrayToHexString(newRes.getData().getBodyHash()) +"\""
					+" guid=\"" +newRes.getGuid() +"\""
					+" onContextMenu=\"window.jambi.imageContextMenu(&apos;" +tfile.fileName() +"&apos;);\""
					+ " />");
			buffer.append(script_end);
			browser.page().mainFrame().evaluateJavaScript(buffer.toString());
		}
		return;
	}
	

	// If a URL being pasted is a local file URL, then attach the file
	private void handleLocalAttachment(QMimeData mime, String mimeType) {
		logger.log(logger.EXTREME, "Attaching local file");
		List<QUrl> urlList = mime.urls();
		String script_start = new String(
				"document.execCommand('insertHtml', false, '");
		String script_end = new String("');");
		StringBuffer buffer;

			String[] type = mimeType.split("/");
			String icon = findIcon(type[1]);
			if (icon.equals("attachment.png"))
				icon = findIcon(type[0]);
			buffer = new StringBuffer(100);

		for (int i = 0; i < urlList.size(); i++) {
			String url = urlList.get(i).toString();

			// Start building the HTML
			if (icon.equals("attachment.png"))
				icon = findIcon(url.substring(url.lastIndexOf(".")+1));
			String imageURL = FileUtils.toFileURLString(Global.getFileManager().getImageDirFile(icon));

			logger.log(logger.EXTREME, "Creating resource ");
			Resource newRes = createResource(url, i, mimeType, true);
			if (newRes == null)
				return;
			logger.log(logger.EXTREME, "New resource size: " +newRes.getData().getSize());
			currentNote.getResources().add(newRes);
			
			String fileName = newRes.getGuid() + Global.attachmentNameDelimeter+newRes.getAttributes().getFileName();
			// If we have a PDF, we need to setup the preview.
			if (icon.equalsIgnoreCase("pdf.png") && Global.pdfPreview()) {
				logger.log(logger.EXTREME, "Setting up PDF preview");
				if (newRes.getAttributes() != null && 
						newRes.getAttributes().getFileName() != null && 
						!newRes.getAttributes().getFileName().trim().equals(""))
					fileName = newRes.getGuid()+Global.attachmentNameDelimeter+
						newRes.getAttributes().getFileName();
				else
					fileName = newRes.getGuid()+".pdf";
				QFile file = new QFile(Global.getFileManager().getResDirPath(fileName));
		        QFile.OpenMode mode = new QFile.OpenMode();
		        mode.set(QFile.OpenModeFlag.WriteOnly);
		        file.open(mode);
		        QDataStream out = new QDataStream(file);
//		        Resource resBinary = conn.getNoteTable().noteResourceTable.getNoteResource(newRes.getGuid(), true);
				QByteArray binData = new QByteArray(newRes.getData().getBody());
//				resBinary = null;
		        out.writeBytes(binData.toByteArray());
		        file.close();

				PDFPreview pdfPreview = new PDFPreview();
				if (pdfPreview.setupPreview(Global.getFileManager().getResDirPath(fileName), "pdf",0)) {
			        imageURL = file.fileName() + ".png";
				}
			}
			
			logger.log(logger.EXTREME, "Generating link tags");
			buffer.delete(0, buffer.length());
			buffer.append("<a en-tag=\"en-media\" guid=\"" +newRes.getGuid()+"\" ");
			buffer.append(" onContextMenu=\"window.jambi.imageContextMenu(&apos;")
		      .append(Global.getFileManager().getResDirPath(fileName))
		      .append("&apos;);\" ");			
			buffer.append("type=\"" + mimeType + "\" href=\"nnres://" + fileName +"\" hash=\""+Global.byteArrayToHexString(newRes.getData().getBodyHash()) +"\" >");
			buffer.append("<img src=\"" + imageURL + "\" title=\"" +newRes.getAttributes().getFileName());
			buffer.append("\"></img>");
			buffer.append("</a>");
			browser.page().mainFrame().evaluateJavaScript(
					script_start + buffer.toString() + script_end);
		}
		return;
	}

	private Resource createResource(String url, int sequence, String mime, boolean attachment) {
		logger.log(logger.EXTREME, "Inside create resource");
		QFile resourceFile; 
		//These two lines are added to handle odd characters in the name like #.  Without it
		// toLocalFile() chokes and returns the wrong name.
		logger.log(logger.EXTREME, "File URL:" +url);
		String whichOS = System.getProperty("os.name");
		if (whichOS.contains("Windows")) 
			url = url.replace("file:///", "");
		else
			url = url.replace("file://", "");
		String urlTest = new QUrl(url).toLocalFile();
		logger.log(logger.EXTREME, "File URL toLocalFile():" +urlTest);
		urlTest = url;
		if (!urlTest.equals(""))
			url = urlTest;
//		url = url.replace("/", File.separator);
		logger.log(logger.EXTREME, "Reading from file to create resource:" +url);
		resourceFile = new QFile(url); 
    	resourceFile.open(new QIODevice.OpenMode(QIODevice.OpenModeFlag.ReadOnly));
    	logger.log(logger.EXTREME, "Error opening file "+url.toString()  +": "+resourceFile.errorString());
    	byte[] fileData = resourceFile.readAll().toByteArray();
    	resourceFile.close();
    	logger.log(logger.EXTREME, "File Length: " +fileData.length);
    	if (fileData.length == 0)
    		return null;
    	MessageDigest md;
    	try {
    		logger.log(logger.EXTREME, "Generating MD5");
    		md = MessageDigest.getInstance("MD5");
    		md.update(fileData);
    		byte[] hash = md.digest();
  
    		Resource r = new Resource();
    		Calendar time = new GregorianCalendar();
    		long prevTime = time.getTimeInMillis();
    		while (prevTime == time.getTimeInMillis()) {
    			time = new GregorianCalendar();
    		}
    		r.setGuid(time.getTimeInMillis()+new Integer(sequence).toString());
    		r.setNoteGuid(currentNote.getGuid());
    		r.setMime(mime);
    		r.setActive(true);
    		r.setUpdateSequenceNum(0);
    		r.setWidth((short) 0);
    		r.setHeight((short) 0);
    		r.setDuration((short) 0);
    		  		
    		Data d = new Data();
    		d.setBody(fileData);
    		d.setBodyIsSet(true);
    		d.setBodyHash(hash);
    		d.setBodyHashIsSet(true);
    		r.setData(d);
    		d.setSize(fileData.length);
    		
    		int fileNamePos = url.lastIndexOf(File.separator);
    		if (fileNamePos == -1)
    			fileNamePos = url.lastIndexOf("/");
   			String fileName = url.substring(fileNamePos+1);
    		ResourceAttributes a = new ResourceAttributes();
    		a.setAltitude(0);
    		a.setAltitudeIsSet(false);
    		a.setLongitude(0);
    		a.setLongitudeIsSet(false);
    		a.setLatitude(0);
    		a.setLatitudeIsSet(false);
    		a.setCameraMake("");
    		a.setCameraMakeIsSet(false);
    		a.setCameraModel("");
    		a.setCameraModelIsSet(false);
    		a.setAttachment(attachment);
    		a.setAttachmentIsSet(true);
    		a.setClientWillIndex(false);
    		a.setClientWillIndexIsSet(true);
    		a.setRecoType("");
    		a.setRecoTypeIsSet(false);
    		a.setSourceURL(url);
    		a.setSourceURLIsSet(true);
    		a.setTimestamp(0);
    		a.setTimestampIsSet(false);
    		a.setFileName(fileName);
    		a.setFileNameIsSet(true);
    		r.setAttributes(a);
    		
    		conn.getNoteTable().noteResourceTable.saveNoteResource(r, true);
    		logger.log(logger.EXTREME, "Resource created");
    		return r;
    	} catch (NoSuchAlgorithmException e1) {
    		e1.printStackTrace();
		}
    	return null;
	}
	

    // find the appropriate icon for an attachment
    private String findIcon(String appl) {
    	appl = appl.toLowerCase();
        File f = Global.getFileManager().getImageDirFile(appl + ".png");
    	if (f.exists())
    		return appl+".png";
    	return "attachment.png";
    }



	// Check the file attachment to be sure it isn't over 25 mb
	private boolean checkFileAttachmentSize(String url) {
		String fileName = url.substring(8);
		QFile resourceFile = new QFile(fileName);
		resourceFile.open(new QIODevice.OpenMode(
				QIODevice.OpenModeFlag.ReadOnly));
		long size = resourceFile.size();
		resourceFile.close();
		size = size / 1024 / 1024;
		if (size < 50 && Global.isPremium())
			return true;
		if (size < 25)
			return true;

		String error = tr("A file attachment may not exceed 25MB.");
		QMessageBox.information(this, tr("Attachment Size"), error);
		return false;
	}


	@SuppressWarnings("unused")
	private void createdChanged() {
		QDateTime dt = new QDateTime();
		dt.setDate(createdDate.date());
		dt.setTime(createdTime.time());
		noteSignal.createdDateChanged.emit(currentNote.getGuid(), dt);

	}

	@SuppressWarnings("unused")
	private void alteredChanged() {
		QDateTime dt = new QDateTime();
		dt.setDate(alteredDate.date());
		dt.setTime(alteredTime.time());
		noteSignal.alteredDateChanged.emit(currentNote.getGuid(), dt);
	}

	@SuppressWarnings("unused")
	private void subjectDateTimeChanged() {
		QDateTime dt = new QDateTime();
		dt.setDate(subjectDate.date());
		dt.setTime(subjectTime.time());
		noteSignal.subjectDateChanged.emit(currentNote.getGuid(), dt);

	}

	@SuppressWarnings("unused")
	private void sourceUrlChanged() {
		noteSignal.sourceUrlChanged.emit(currentNote.getGuid(), urlText.text());
	}

	@SuppressWarnings("unused")
	private void authorChanged() {
		noteSignal.authorChanged.emit(currentNote.getGuid(), authorText.text());
	}
	
	@SuppressWarnings("unused")
	private void geoBoxChanged() {
		int index = geoBox.currentIndex();
		geoBox.setCurrentIndex(0);
		if (index == 1) {
			GeoDialog box = new GeoDialog();
			box.setLongitude(currentNote.getAttributes().getLongitude());
			box.setLatitude(currentNote.getAttributes().getLatitude());
			box.setAltitude(currentNote.getAttributes().getAltitude());
			box.exec();
			if (!box.okPressed())
				return;
			double alt = box.getAltitude();
			double lat = box.getLatitude();
			double lon = box.getLongitude();
			if (alt != currentNote.getAttributes().getAltitude() ||
				lon != currentNote.getAttributes().getLongitude() ||
				lat != currentNote.getAttributes().getLatitude()) {
					noteSignal.geoChanged.emit(currentNote.getGuid(), lon, lat, alt);
					currentNote.getAttributes().setAltitude(alt);
					currentNote.getAttributes().setLongitude(lon);
					currentNote.getAttributes().setLatitude(lat);
			}
		}
		
		if (index == 2) {
			noteSignal.geoChanged.emit(currentNote.getGuid(), 0.0, 0.0, 0.0);
			currentNote.getAttributes().setAltitude(0.0);
			currentNote.getAttributes().setLongitude(0.0);
			currentNote.getAttributes().setLatitude(0.0);
		}
		
		if (index == 3 || index == 0) {
			QDesktopServices.openUrl(new QUrl("http://maps.google.com/maps?z=6&q="+currentNote.getAttributes().getLatitude() +"," +currentNote.getAttributes().getLongitude()));
		}
	}

	// ************************************************************
	// * User chose to save an attachment. Pares out the request *
	// * into a guid & file. Save the result. *
	// ************************************************************
	public void downloadAttachment(QNetworkRequest request) {
		String guid;
		QFileDialog fd = new QFileDialog(this);
		fd.setFileMode(FileMode.AnyFile);
		fd.setConfirmOverwrite(true);
		fd.setWindowTitle(tr("Save File"));
		fd.setAcceptMode(AcceptMode.AcceptSave);
		fd.setDirectory(System.getProperty("user.home"));
		String name = request.url().toString();

		int pos = name.lastIndexOf(Global.attachmentNameDelimeter);
		if (pos > -1) {
			guid = name.substring(0, pos).replace("nnres://", "");
			name = name.substring(pos +Global.attachmentNameDelimeter.length());
			fd.selectFile(name);
			pos = name.lastIndexOf('.');
			if (pos > -1) {
				String mimeType = "(*." + name.substring(pos + 1)
						+ ");; All Files (*)";
				fd.setFilter(tr(mimeType));
			}
		} else {
			guid = name;
		}

		// Strip URL prefix and base dir
		guid = guid.replace("nnres://", "")
		        .replace(FileUtils.toForwardSlashedPath(Global.getFileManager().getResDirPath()), "");
		guid = guid.replace("file://", "").replace("/", "")
        	.replace(FileUtils.toForwardSlashedPath(Global.getFileManager().getResDirPath()), "");

		pos = guid.lastIndexOf('.');
		if (pos > 0)
			guid = guid.substring(0,pos);
		if (fd.exec() != 0 && fd.selectedFiles().size() > 0) {
			name = name.replace('\\', '/');
			Resource resBinary = conn.getNoteTable().noteResourceTable.getNoteResource(guid, true);
			QFile saveFile = new QFile(fd.selectedFiles().get(0));
			QFile.OpenMode mode = new QFile.OpenMode();
			mode.set(QFile.OpenModeFlag.WriteOnly);
			saveFile.open(mode);
			QDataStream saveOut = new QDataStream(saveFile);
			QByteArray binData = new QByteArray(resBinary.getData().getBody());
			saveOut.writeBytes(binData.toByteArray());
			saveFile.close();

		}
	}

	
	// ************************************************************
	// * User chose to save an attachment. Pares out the request *
	// * into a guid & file. Save the result. --- DONE FROM downloadAttachment now!!!!!   
	// ************************************************************
	public void downloadImage(QNetworkRequest request) {
		QFileDialog fd = new QFileDialog(this);
		fd.setFileMode(FileMode.AnyFile);
		fd.setConfirmOverwrite(true);
		fd.setWindowTitle(tr("Save File"));
		fd.setAcceptMode(AcceptMode.AcceptSave);
		fd.setDirectory(System.getProperty("user.home"));
		String name = request.url().toString();
		name = name.replace("nnres://", "");
		String dPath = FileUtils.toForwardSlashedPath(Global.getFileManager().getResDirPath());
		name = name.replace(dPath, "");
		int pos = name.lastIndexOf('.');
		String guid = name;
		if (pos > -1) {
			String mimeType = "(*." + name.substring(pos + 1)
			+ ");; All Files (*)";
				fd.setFilter(tr(mimeType));
			guid = guid.substring(0,pos);
		}
		pos = name.lastIndexOf(Global.attachmentNameDelimeter);
		if (pos > -1) {
			guid = name.substring(0, pos);
			fd.selectFile(name.substring(pos+Global.attachmentNameDelimeter.length()));		
		}
		if (fd.exec() != 0 && fd.selectedFiles().size() > 0) {
			Resource resBinary = conn.getNoteTable().noteResourceTable.getNoteResource(guid, true);
			String fileName = fd.selectedFiles().get(0);
			QFile saveFile = new QFile(fileName);
			QFile.OpenMode mode = new QFile.OpenMode();
			mode.set(QFile.OpenModeFlag.WriteOnly);
			saveFile.open(mode);
			QDataStream saveOut = new QDataStream(saveFile);
			QByteArray binData = new QByteArray(resBinary.getData().getBody());
			saveOut.writeBytes(binData.toByteArray());
			saveFile.close();
		}
	}

	
	// *************************************************************
	// * decrypt any hidden text.  We could do an XML parse, but 
	// * it is quicker here just to scan for an <img tag & do the fix
	// * the manual way
	// *************************************************************
	private void removeEncryption(String id, String plainText, boolean permanent, String slot) {
		if (!permanent) {
			plainText = " <table class=\"en-crypt-temp\" slot=\""
					+slot 
					+"\""
					+"border=1 width=100%><tbody><tr><td>"
					+plainText+"</td></tr></tbody></table>";
		}
		
		String html = browser.page().mainFrame().toHtml();
		String text = html;
		int imagePos = html.indexOf("<img");
		int endPos;
		for ( ;imagePos>0; ) {
			// Find the end tag
			endPos = text.indexOf(">", imagePos);
			String tag = text.substring(imagePos-1,endPos);
			if (tag.indexOf("id=\""+id+"\"") > -1) {
					text = text.substring(0,imagePos) +plainText+text.substring(endPos+1);	
					QTextCodec codec = QTextCodec.codecForName("UTF-8");
			        QByteArray unicode =  codec.fromUnicode(text);
					setContent(unicode);
					if (permanent)
						contentChanged();
			}
			imagePos = text.indexOf("<img", imagePos+1);
		}
	}
	
	
	//****************************************************************
	//* Focus shortcuts
	//****************************************************************
	@SuppressWarnings("unused")
	private void focusTitle() {
		titleLabel.setFocus();
	}
	@SuppressWarnings("unused")
	private void focusTag() {
		tagEdit.setFocus();
	}
	@SuppressWarnings("unused")
	private void focusNote() {
		browser.setFocus();
	}
	@SuppressWarnings("unused")
	private void focusAuthor() {
		authorLabel.setFocus();
	}
	@SuppressWarnings("unused")
	private void focusUrl() {
		urlLabel.setFocus();
	}
	

	//*****************************************************************
	//* Set the document background color
	//*****************************************************************
	public void setBackgroundColor(String color) {
		String js = "function changeBackground(color) {"
			+"document.body.style.background = color;"
			+"}" 
			+"changeBackground('" +color+"');";
		browser.page().mainFrame().evaluateJavaScript(js);
		contentChanged();
	}
	
	
	//****************************************************************
	//* MicroFocus changed
	//****************************************************************
	private void microFocusChanged() {
		boldButton.setDown(false);
		italicButton.setDown(false);
		underlineButton.setDown(false);
		browser.openAction.setEnabled(false);
		browser.downloadAttachment.setEnabled(false);
		browser.downloadImage.setEnabled(false);
		browser.rotateImageLeft.setEnabled(false);
		browser.rotateImageRight.setEnabled(false);
		browser.insertTableAction.setEnabled(true);
		browser.deleteTableColumnAction.setEnabled(false);
		browser.insertTableRowAction.setEnabled(false);
		browser.insertTableColumnAction.setEnabled(false);
		browser.deleteTableRowAction.setEnabled(false);
		browser.insertLinkAction.setText(tr("Insert Hyperlink"));
		insertHyperlink = true;
		browser.insertQuickLinkAction.setEnabled(true);
		currentHyperlink ="";
		insideList = false;
		insideTable = false;
		insideEncryption = false;
		forceTextPaste = false;
		
		String js = new String( "function getCursorPos() {"
			+"var cursorPos;"
			+"if (window.getSelection) {"
			+"   var selObj = window.getSelection();"
			+"   var selRange = selObj.getRangeAt(0);"
			+"   var workingNode = window.getSelection().anchorNode.parentNode;"
			+"   while(workingNode != null) { " 
//			+"      window.jambi.printNode(workingNode.nodeName);"
			+"      if (workingNode.nodeName=='TABLE') { if (workingNode.getAttribute('class').toLowerCase() == 'en-crypt-temp') window.jambi.insideEncryption(); }"
			+"      if (workingNode.nodeName=='B') window.jambi.boldActive();"
			+"      if (workingNode.nodeName=='I') window.jambi.italicActive();"
			+"      if (workingNode.nodeName=='U') window.jambi.underlineActive();"
			+"      if (workingNode.nodeName=='UL') window.jambi.setInsideList();"
			+"      if (workingNode.nodeName=='OL') window.jambi.setInsideList();"
			+"      if (workingNode.nodeName=='LI') window.jambi.setInsideList();"
			+"      if (workingNode.nodeName=='TBODY') window.jambi.setInsideTable();"
			+"      if (workingNode.nodeName=='A') {for(var x = 0; x < workingNode.attributes.length; x++ ) {if (workingNode.attributes[x].nodeName.toLowerCase() == 'href') window.jambi.setInsideLink(workingNode.attributes[x].nodeValue);}}"
			+"      if (workingNode.nodeName=='SPAN') {"
			+"         if (workingNode.getAttribute('style') == 'text-decoration: underline;') window.jambi.underlineActive();"
			+"      }"
			+"      workingNode = workingNode.parentNode;"
			+"   }"
			+"}"
			+"} getCursorPos();");
		browser.page().mainFrame().evaluateJavaScript(js);
	}
	
	public void printNode(String n) {
		System.out.println("Node Vaule: " +n);
	}
	
	public void insideEncryption() {
		insideEncryption = true;
		forceTextPaste();
	}
	
	//****************************************************************
	//* Insert a table row
	//****************************************************************
	public void insertTableRow() {
		
		String js = new String( "function insertTableRow() {"
			+"   var selObj = window.getSelection();"
			+"   var selRange = selObj.getRangeAt(0);"
			+"   var workingNode = window.getSelection().anchorNode.parentNode;"
			+"   var cellCount = 0;"
			+"   while(workingNode != null) { " 
			+"      if (workingNode.nodeName.toLowerCase()=='tr') {"
			+"           row = document.createElement('TR');"
			+"           var nodes = workingNode.getElementsByTagName('td');"
			+"           for (j=0; j<nodes.length; j=j+1) {"
			+"              cell = document.createElement('TD');"
			+"              cell.innerHTML='&nbsp;';"
			+"              row.appendChild(cell);"
			+"           }"			
			+"           workingNode.parentNode.insertBefore(row,workingNode.nextSibling);"
			+"           return;"
			+"      }"
			+"      workingNode = workingNode.parentNode;"
			+"   }"
			+"} insertTableRow();");
		browser.page().mainFrame().evaluateJavaScript(js);
		contentChanged();
	}
	
	public void insertTableColumn() {
		String js = new String( "function insertTableColumn() {"
				+"   var selObj = window.getSelection();"
				+"   var selRange = selObj.getRangeAt(0);"
				+"   var workingNode = window.getSelection().anchorNode.parentNode;"
				+"   var current = 0;"
				+"   while (workingNode.nodeName.toLowerCase() != 'table' && workingNode != null) {"
				+"       if (workingNode.nodeName.toLowerCase() == 'td') {"
				+"          var td = workingNode;"
				+"          while (td.previousSibling != null) { " 
				+"             current = current+1; td = td.previousSibling;"
				+"          }"
				+"       }"
				+"       workingNode = workingNode.parentNode; "
				+"   }"
				+"   if (workingNode == null) return;"
				+"   for (var i=0; i<workingNode.rows.length; i++) { " 
				+"      var cell = workingNode.rows[i].insertCell(current+1); "			
				+"      cell.innerHTML = '&nbsp'; "
				+"   }"
				+"} insertTableColumn();");
			browser.page().mainFrame().evaluateJavaScript(js);
			contentChanged();
	}
	
	//****************************************************************
	//* Delete a table row
	//****************************************************************
	public void deleteTableRow() {
		
		String js = new String( "function deleteTableRow() {"
			+"   var selObj = window.getSelection();"
			+"   var selRange = selObj.getRangeAt(0);"
			+"   var workingNode = window.getSelection().anchorNode.parentNode;"
			+"   var cellCount = 0;"
			+"   while(workingNode != null) { " 
			+"      if (workingNode.nodeName.toLowerCase()=='tr') {"
			+"           workingNode.parentNode.removeChild(workingNode);"
			+"           return;"
			+"      }"
			+"      workingNode = workingNode.parentNode;"
			+"   }"
			+"} deleteTableRow();");
		browser.page().mainFrame().evaluateJavaScript(js);
		contentChanged();
	}

	public void deleteTableColumn() {
		String js = new String( "function deleteTableColumn() {"
				+"   var selObj = window.getSelection();"
				+"   var selRange = selObj.getRangeAt(0);"
				+"   var workingNode = window.getSelection().anchorNode.parentNode;"
				+"   var current = 0;"
				+"   while (workingNode.nodeName.toLowerCase() != 'table' && workingNode != null) {"
				+"       if (workingNode.nodeName.toLowerCase() == 'td') {"
				+"          var td = workingNode;"
				+"          while (td.previousSibling != null) { " 
				+"             current = current+1; td = td.previousSibling;"
				+"          }"
				+"       }"
				+"       workingNode = workingNode.parentNode; "
				+"   }"
				+"   if (workingNode == null) return;"
				+"   for (var i=0; i<workingNode.rows.length; i++) { " 
				+"      workingNode.rows[i].deleteCell(current); "			
				+"   }"
				+"} deleteTableColumn();");
			browser.page().mainFrame().evaluateJavaScript(js);
			contentChanged();
	}
	
	
	public void setInsideTable() {
		browser.insertTableRowAction.setEnabled(true);
		browser.insertTableColumnAction.setEnabled(true);
		browser.deleteTableRowAction.setEnabled(true);
		browser.deleteTableColumnAction.setEnabled(true);
		browser.insertTableAction.setEnabled(false);
		browser.encryptAction.setEnabled(false);
		insideTable = true;
	}
	
	public void setInsideLink(String link) {
		browser.insertLinkAction.setText(tr("Edit Hyperlink"));
		currentHyperlink = link;
		insertHyperlink = false;
	}
	
	public void italicActive() {
		italicButton.setDown(true);
	}
	public void boldActive() {
		boldButton.setDown(true);
	}
	public void underlineActive() {
		underlineButton.setDown(true);
	}
	public void forceTextPaste() {
		forceTextPaste = true;
	}
	public void imageContextMenu(String f) {
		browser.downloadImage.setEnabled(true);
		browser.rotateImageRight.setEnabled(true);
		browser.rotateImageLeft.setEnabled(true);
		browser.openAction.setEnabled(true);
		selectedFile = f;
	}
	public void rotateImageRight() {
		QWebSettings.setMaximumPagesInCache(0);
		QWebSettings.setObjectCacheCapacities(0, 0, 0);
		QImage image = new QImage(selectedFile);
		QMatrix matrix = new QMatrix();
		matrix.rotate( 90.0 );
		image = image.transformed(matrix);
		image.save(selectedFile);
		QWebSettings.setMaximumPagesInCache(0);
		QWebSettings.setObjectCacheCapacities(0, 0, 0);
		browser.setHtml(browser.page().mainFrame().toHtml());
		browser.reload();
		contentChanged();
		resourceSignal.contentChanged.emit(selectedFile);

	}
	public void rotateImageLeft() {
		QImage image = new QImage(selectedFile);
		QMatrix matrix = new QMatrix();
		matrix.rotate( -90.0 );
		image = image.transformed(matrix);
		image.save(selectedFile);
		browser.setHtml(browser.page().mainFrame().toHtml());
		browser.reload();
		contentChanged();
		resourceSignal.contentChanged.emit(selectedFile);
	}
	public void resourceContextMenu(String f) {
		browser.downloadAttachment.setEnabled(true);
		browser.openAction.setEnabled(true);
		selectedFile = f;
	}
	public void latexContextMenu(String f) {
		browser.downloadImage.setEnabled(true);
		browser.rotateImageRight.setEnabled(true);
		browser.rotateImageLeft.setEnabled(true);
		browser.openAction.setEnabled(true);
		selectedFile = f;
	}

	//****************************************************************
	//* Apply CSS style to specified word
	//****************************************************************
/*	public void applyStyleToWords(String word, String style) {
		QFile script = new QFile("D:\\NeverNote\\js\\hilight1.js");
		script.open(OpenModeFlag.ReadOnly);
		String s = script.readAll().toString();
		String js = new String(s +" findit('"+word+"', '"+style+"');");
		browser.page().mainFrame().evaluateJavaScript(js);
		System.out.println(getContent());
	}
*/	
	//****************************************************************
	//* Someone tried to paste a resource between notes, so we need  *
	//* to do some special handling.                                 *
	//****************************************************************
	private String fixInternotePaste(String text) {
		logger.log(logger.EXTREME, "Fixing internote paste");
		String returnValue = fixInternotePasteSearch(text, "<img", "src=\"");
		return fixInternotePasteSearch(returnValue, "<a", "href=\"nnres://");
	}
	private String fixInternotePasteSearch(String text, String type, String locTag) {
		
		// First, let's fix the images.
		int startPos = text.indexOf(type);
		int endPos;
		for (; startPos>=0;) {
			endPos = text.indexOf(">", startPos+1);
			String segment = text.substring(startPos, endPos);
			if (segment.indexOf("en-tag") > -1) {
				String newSegment = segment;
				
				int guidStartPos = segment.indexOf("guid=\"");
				int guidEndPos = segment.indexOf("\"", guidStartPos+7);
				String guid = segment.substring(guidStartPos+6,guidEndPos);
				
				int mimeStartPos = segment.indexOf("type");
				int mimeEndPos = segment.indexOf("\"", mimeStartPos+7);
				String mime = segment.substring(mimeStartPos+6,mimeEndPos);

				int srcStartPos = segment.indexOf("src");
				int srcEndPos = segment.indexOf("\"", srcStartPos+6);
				String src = segment.substring(srcStartPos+5,srcEndPos);
				
				Calendar currentTime = new GregorianCalendar();
				Long l = new Long(currentTime.getTimeInMillis());
				long prevTime = l;
				while (l==prevTime) {
					currentTime = new GregorianCalendar();
					l= new Long(currentTime.getTimeInMillis());
				}
				
				Resource r = conn.getNoteTable().noteResourceTable.getNoteResource(guid, true);
				// if r==null, then the image doesn't exist (it was probably cut out of another note, so 
				// we need to recereate it
				if (r==null) {
					r = createResource(src, 1, mime, false);
					if (r==null)
						return "";
				}
		    	String randint = new String(Long.toString(l));
		    	String extension = null;
		    	if (r.getMime()!= null) {
		    		extension = r.getMime().toLowerCase();
		    		if (extension.indexOf("/")>-1)
		    			extension = extension.substring(extension.indexOf("/")+1);
		    	}
		    	String newFile = randint;
		    	if (r.getAttributes().getFileName() != null && r.getAttributes().getFileName() != "")
		    		if (!locTag.startsWith("src"))
		    			newFile = newFile+Global.attachmentNameDelimeter+r.getAttributes().getFileName();
		    	r.setNoteGuid(currentNote.getGuid());
		    	
		    	r.setGuid(randint);
		    	conn.getNoteTable().noteResourceTable.saveNoteResource(r, true);
				QFile f = new QFile(Global.getFileManager().getResDirPath(newFile));
				QByteArray bin = new QByteArray(r.getData().getBody());
				f.open(QFile.OpenModeFlag.WriteOnly);
				f.write(bin);
				f.close();
				newSegment = newSegment.replace("guid=\""+guid, "guid=\""+randint);
				currentNote.getResources().add(r);
				
				int startSrcPos = newSegment.indexOf(locTag);
				int endSrcPos = newSegment.indexOf("\"",startSrcPos+locTag.length()+1);
				String source; 
				if (locTag.startsWith("src")) {
					 source = newSegment.substring(startSrcPos+locTag.length(),endSrcPos);
						newSegment = newSegment.replace(source,
						        FileUtils.toForwardSlashedPath(Global.getFileManager().getResDirPath(newFile)));
				} else {
					source = newSegment.substring(startSrcPos+locTag.length(),endSrcPos);
					newSegment = newSegment.replace(source, newFile);
				}
				
				text = text.substring(0,startPos) + newSegment + text.substring(endPos);
			}
			startPos = text.indexOf(type, startPos+1);
		}
		return text;
	}


	public void nextPage(String file) {
		logger.log(logger.EXTREME, "Starting nextPage()");
		
		Integer pageNumber;
		if (previewPageList.containsKey(file))
			pageNumber = previewPageList.get(file)+1;
		else
			pageNumber = 2;
		previewPageList.remove(file);
		previewPageList.put(file, pageNumber);
		PDFPreview pdfPreview = new PDFPreview();
		boolean goodPreview = pdfPreview.setupPreview(file, "pdf", pageNumber);
		if (goodPreview) {

//			String html = getContent();
			QWebSettings.setMaximumPagesInCache(0);
			QWebSettings.setObjectCacheCapacities(0, 0, 0);
//			browser.setContent(new QByteArray());
			browser.setHtml(browser.page().mainFrame().toHtml());
			browser.reload();
//			browser.setContent(new QByteArray(html));
//			browser.triggerPageAction(WebAction.Reload);
//			pdfMouseOver(selectedFile);
		}
	}

	public void previousPage(String file) {
		logger.log(logger.EXTREME, "Starting previousPage()");
		
		Integer pageNumber;
		if (previewPageList.containsKey(file))
			pageNumber = previewPageList.get(file)-1;
		else
			pageNumber = 1;
		previewPageList.remove(file);
		previewPageList.put(file, pageNumber);
		PDFPreview pdfPreview = new PDFPreview();
		boolean goodPreview = pdfPreview.setupPreview(file, "pdf", pageNumber);
		if (goodPreview) {

//			String html = getContent();
			QWebSettings.setMaximumPagesInCache(0);
			QWebSettings.setObjectCacheCapacities(0, 0, 0);
			browser.setHtml(browser.page().mainFrame().toHtml());
			browser.reload();
//			browser.setContent(new QByteArray(html));
//			browser.triggerPageAction(WebAction.Reload);
		}
	}
	
/*	public void pdfMouseOver(String name) { 
		int pageNumber;
		if (previewPageList.containsKey(selectedFile))
			pageNumber = previewPageList.get(selectedFile)+1;
		else
			pageNumber = 1;
		
		if (pageNumber <= 1)
			browser.previousPageAction.setEnabled(false);
		else
			browser.previousPageAction.setEnabled(true);
		
		PDFPreview pdf = new PDFPreview();
		int totalPages = pdf.getPageCount(name);
		if (previewPageList.containsKey(selectedFile))
			pageNumber = previewPageList.get(selectedFile)+1;
		else
			pageNumber = 1;
		if (totalPages > pageNumber)
			browser.nextPageAction.setEnabled(true);
		else
			browser.nextPageAction.setEnabled(false);
	}
	

	public void pdfMouseOut() { 
//		browser.nextPageAction.setVisible(false);
//		browser.previousPageAction.setVisible(false);
	}
*/
	
	@SuppressWarnings("unused")
	private void toggleUndoVisible(Boolean toggle) {
		undoAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("undo", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleRedoVisible(Boolean toggle) {
		redoAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("redo", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleCutVisible(Boolean toggle) {
		cutAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("cut", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleCopyVisible(Boolean toggle) {
		copyAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("copy", toggle);
	}
	@SuppressWarnings("unused")
	private void togglePasteVisible(Boolean toggle) {
		pasteAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("paste", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleBoldVisible(Boolean toggle) {
		boldAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("bold", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleItalicVisible(Boolean toggle) {
		italicAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("italic", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleUnderlineVisible(Boolean toggle) {
		underlineAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("underline", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleStrikethroughVisible(Boolean toggle) {
		strikethroughAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("strikethrough", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleLeftAlignVisible(Boolean toggle) {
		leftAlignAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("alignLeft", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleRightAlignVisible(Boolean toggle) {
		rightAlignAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("alignRight", toggle);
	}	
	@SuppressWarnings("unused")
	private void toggleCenterAlignVisible(Boolean toggle) {
		centerAlignAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("alignCenter", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleHLineVisible(Boolean toggle) {
		hlineAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("hline", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleIndentVisible(Boolean toggle) {
		indentAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("indent", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleTodoVisible(Boolean toggle) {
		todoAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("todo", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleOutdentVisible(Boolean toggle) {
		outdentAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("outdent", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleBulletListVisible(Boolean toggle) {
		bulletListAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("bulletList", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleNumberListVisible(Boolean toggle) {
		numberListAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("numberList", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleFontListVisible(Boolean toggle) {
		fontListAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("font", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleFontColorVisible(Boolean toggle) {
		fontColorAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("fontColor", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleFontSizeVisible(Boolean toggle) {
		fontSizeAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("fontSize", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleFontHilightVisible(Boolean toggle) {
		fontHilightAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("fontHilight", toggle);
	}
	@SuppressWarnings("unused")
	private void toggleSpellCheckVisible(Boolean toggle) {
		spellCheckAction.setVisible(toggle);
		Global.saveEditorButtonsVisible("spellCheck", toggle);
	}


	private void setupDictionary() {
		File wordList = new File(Global.getFileManager().getSpellDirPath()+Locale.getDefault()+".dic");
		try {
			dictionary = new SpellDictionaryHashMap(wordList);
			spellChecker = new SpellChecker(dictionary);
			
			File userWordList;
			userWordList = new File(Global.getFileManager().getSpellDirPathUser()+"user.dic");
			
			// Get the local user spell dictionary
			try {
				userDictionary = new SpellDictionaryHashMap(userWordList);
			} catch (FileNotFoundException e) {
				userWordList.createNewFile();
				userDictionary = new SpellDictionaryHashMap(userWordList);
			} catch (IOException e) {
				userWordList.createNewFile();
				userDictionary = new SpellDictionaryHashMap(userWordList);
			}
			
			spellListener = new SuggestionListener(this, spellChecker);
			
			// Add the user dictionary
			spellChecker.addSpellCheckListener(spellListener);
			spellChecker.setUserDictionary(userDictionary);

		} catch (FileNotFoundException e) {
			QMessageBox.critical(this, tr("Spell Check Error"), 
					tr("Dictionary ")+ Global.getFileManager().getSpellDirPath()+Locale.getDefault()+
						tr(".dic was not found."));
		} catch (IOException e) {
			QMessageBox.critical(this, tr("Spell Check Error"), 
					tr("Dictionary ")+ Global.getFileManager().getSpellDirPath()+Locale.getDefault()+
						tr(".dic is invalid."));
		}

	}
	
	// Invoke spell checker dialog
	@SuppressWarnings("unused")
	private void spellCheckClicked() {

		if (spellChecker == null) {
			setupDictionary();	
		}
		
		// Read user settings
		spellChecker.getConfiguration().setBoolean(Configuration.SPELL_IGNOREDIGITWORDS, 
				Global.getSpellSetting(Configuration.SPELL_IGNOREDIGITWORDS));
		spellChecker.getConfiguration().setBoolean(Configuration.SPELL_IGNOREINTERNETADDRESSES, 
				Global.getSpellSetting(Configuration.SPELL_IGNOREINTERNETADDRESSES));
		spellChecker.getConfiguration().setBoolean(Configuration.SPELL_IGNOREMIXEDCASE, 
				Global.getSpellSetting(Configuration.SPELL_IGNOREMIXEDCASE));
		spellChecker.getConfiguration().setBoolean(Configuration.SPELL_IGNOREUPPERCASE, 
				Global.getSpellSetting(Configuration.SPELL_IGNOREUPPERCASE));
		spellChecker.getConfiguration().setBoolean(Configuration.SPELL_IGNORESENTENCECAPITALIZATION, 
				Global.getSpellSetting(Configuration.SPELL_IGNORESENTENCECAPITALIZATION));

		spellListener.abortSpellCheck = false;
		spellListener.errorsFound = false;
		String content = getBrowser().page().mainFrame().toPlainText();
		StringWordTokenizer tokenizer = new StringWordTokenizer(content);
		if (!tokenizer.hasMoreWords())
			return;
		getBrowser().page().action(WebAction.MoveToStartOfDocument);

		getBrowser().setFocus();
		boolean found;
			
		// Move to the start of page
		KeyboardModifiers ctrl = new KeyboardModifiers(KeyboardModifier.ControlModifier.value());
		QKeyEvent home = new QKeyEvent(Type.KeyPress, Key.Key_Home.value(), ctrl);  
		browser.keyPressEvent(home);
		getBrowser().setFocus();
			
		tokenizer = new StringWordTokenizer(content);
		String word;
			
		while(tokenizer.hasMoreWords()) {
			word = tokenizer.nextWord();
			found = getBrowser().page().findText(word);
			if (found && !spellListener.abortSpellCheck) {
				spellChecker.checkSpelling(new StringWordTokenizer(word));
				getBrowser().setFocus();
			}
		}

		// Go to the end of the document & finish up.
		home = new QKeyEvent(Type.KeyPress, Key.Key_End.value(), ctrl);  
		browser.keyPressEvent(home);
		if (!spellListener.errorsFound)
			QMessageBox.information(this, tr("Spell Check Complete"), 
					tr("No Errors Found"));

    }
	
	// Source edited
	@SuppressWarnings("unused")
	private void sourceEdited() {
		QTextCodec codec = QTextCodec.codecForLocale();
		codec = QTextCodec.codecForName("UTF-8");
        String content =  codec.fromUnicode(sourceEdit.toHtml()).toString();
		content = StringEscapeUtils.unescapeHtml4(removeTags(content));
		QByteArray data = new QByteArray(sourceEditHeader+content+"</body></html>");
		getBrowser().setContent(data);
		checkNoteTitle();
		if (currentNote != null && sourceEdit != null)
			noteSignal.noteChanged.emit(currentNote.getGuid(), sourceEdit.toPlainText()); 
	}
	
	private void setSource() {
		String text = getContent();
		sourceEdit.blockSignals(true);
		int body = text.indexOf("<body");
		if (body > 0) {
			body = text.indexOf(">",body);
			if (body > 0) {
				sourceEditHeader =text.substring(0, body+1);
				text = text.substring(body+1);
			}
		}
		text = text.replace("</body></html>", "");
		sourceEdit.setPlainText(text);
		sourceEdit.setReadOnly(!getBrowser().page().isContentEditable());
		//syntaxHighlighter.rehighlight();
		sourceEdit.blockSignals(false);
	}

	// show/hide view source window
	public void showSource(boolean value) {
		setSource();
		sourceEdit.setVisible(value);
	}

	// Remove HTML tags
	private String removeTags(String text) {
		StringBuffer buffer = new StringBuffer(text);
		boolean inTag = false;
		int bodyPosition = text.indexOf("<body");
		for (int i=buffer.length()-1; i>=0; i--) {
			if (buffer.charAt(i) == '>')
				inTag = true;
			if (buffer.charAt(i) == '<')
				inTag = false;
			if (inTag || buffer.charAt(i) == '<' || i<bodyPosition)
				buffer.deleteCharAt(i);
		}
		
		return buffer.toString();
	}
}
