2 * This file is part of NeverNote
\r
3 * Copyright 2009 Randy Baumgarte
\r
5 * This file may be licensed under the terms of of the
\r
6 * GNU General Public License Version 2 (the ``GPL'').
\r
8 * Software distributed under the License is distributed
\r
9 * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
\r
10 * express or implied. See the GPL for the specific language
\r
11 * governing rights and limitations.
\r
13 * You should have received a copy of the GPL along with this
\r
14 * program. If not, go to http://www.gnu.org/licenses/gpl.html
\r
15 * or write to the Free Software Foundation, Inc.,
\r
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
\r
20 package cx.fbn.nevernote.dialog;
\r
22 import java.io.File;
\r
23 import java.text.SimpleDateFormat;
\r
24 import java.util.List;
\r
26 import com.evernote.edam.notestore.NoteVersionId;
\r
27 import com.evernote.edam.type.Note;
\r
28 import com.evernote.edam.type.Resource;
\r
29 import com.trolltech.qt.core.QByteArray;
\r
30 import com.trolltech.qt.core.QFile;
\r
31 import com.trolltech.qt.core.QIODevice;
\r
32 import com.trolltech.qt.core.Qt.ContextMenuPolicy;
\r
33 import com.trolltech.qt.gui.QComboBox;
\r
34 import com.trolltech.qt.gui.QDialog;
\r
35 import com.trolltech.qt.gui.QHBoxLayout;
\r
36 import com.trolltech.qt.gui.QLabel;
\r
37 import com.trolltech.qt.gui.QPushButton;
\r
38 import com.trolltech.qt.gui.QVBoxLayout;
\r
39 import com.trolltech.qt.xml.QDomAttr;
\r
40 import com.trolltech.qt.xml.QDomDocument;
\r
41 import com.trolltech.qt.xml.QDomElement;
\r
42 import com.trolltech.qt.xml.QDomNodeList;
\r
44 import cx.fbn.nevernote.Global;
\r
45 import cx.fbn.nevernote.gui.BrowserWindow;
\r
46 import cx.fbn.nevernote.sql.DatabaseConnection;
\r
48 public class OnlineNoteHistory extends QDialog {
\r
49 public final QPushButton restoreAsNew;
\r
50 public final QPushButton restore;
\r
51 private DatabaseConnection conn;
\r
52 public final QComboBox historyCombo;
\r
53 private final BrowserWindow browser;
\r
56 public OnlineNoteHistory(DatabaseConnection c) {
\r
57 setWindowTitle("Online Note History");
\r
58 QVBoxLayout main = new QVBoxLayout();
\r
60 historyCombo = new QComboBox(this);
\r
62 QHBoxLayout comboLayout = new QHBoxLayout();
\r
63 comboLayout.addWidget(new QLabel("History Date:"));
\r
64 comboLayout.addWidget(historyCombo);
\r
65 comboLayout.addStretch(100);
\r
67 main.addLayout(comboLayout);
\r
69 browser = new BrowserWindow(conn);
\r
70 main.addWidget(browser);
\r
71 browser.titleLabel.setVisible(false);
\r
72 browser.notebookBox.setVisible(false);
\r
73 browser.hideButtons();
\r
74 browser.tagEdit.setVisible(false);
\r
75 browser.tagLabel.setVisible(false);
\r
77 QHBoxLayout buttonLayout = new QHBoxLayout();
\r
78 buttonLayout.addStretch(100);
\r
79 restore = new QPushButton("Restore Note");
\r
80 restore.clicked.connect(this, "restorePushed()");
\r
82 restoreAsNew = new QPushButton("Restore As New Note");
\r
83 restoreAsNew.clicked.connect(this, "restoreAsNewPushed()");
\r
84 QPushButton cancel = new QPushButton("Cancel");
\r
85 cancel.clicked.connect(this, "cancelPressed()");
\r
87 buttonLayout.addWidget(restore);
\r
88 buttonLayout.addWidget(restoreAsNew);
\r
89 buttonLayout.addWidget(cancel);
\r
90 main.addLayout(buttonLayout);
\r
92 browser.getBrowser().setContextMenuPolicy(ContextMenuPolicy.NoContextMenu);
\r
96 @SuppressWarnings("unused")
\r
97 private void restoreAsNewPushed() {
\r
100 @SuppressWarnings("unused")
\r
101 private void restorePushed() {
\r
104 @SuppressWarnings("unused")
\r
105 private void cancelPressed() {
\r
109 public void setCurrent(boolean isDirty) {
\r
111 historyCombo.addItem(new String("Current (Non Synchronized)"));
\r
113 historyCombo.addItem(new String("Current (Synchronized)"));
\r
117 public void load(List<NoteVersionId> versions) {
\r
118 String fmt = Global.getDateFormat() + " " + Global.getTimeFormat();
\r
119 String dateTimeFormat = new String(fmt);
\r
120 SimpleDateFormat simple = new SimpleDateFormat(dateTimeFormat);
\r
122 for (int i=0; i<versions.size(); i++) {
\r
123 StringBuilder versionDate = new StringBuilder(simple.format(versions.get(i).getServiceUpdated()));
\r
124 historyCombo.addItem(versionDate.toString());
\r
128 public void setContent(Note currentNote) {
\r
129 StringBuffer b = rebuildNoteHTML(currentNote);
\r
130 StringBuffer js = new StringBuffer();
\r
132 // We need to prepend the note with <HEAD></HEAD> or encoded characters are ugly
\r
133 js.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");
\r
134 js.append("<style type=\"text/css\">en-crypt-temp { border-style:solid; border-color:blue; padding:1mm 1mm 1mm 1mm; }</style>");
\r
135 js.append("</head>");
\r
136 js.append(b.toString());
\r
137 js.append("</HTML>");
\r
138 // js.replace("<!DOCTYPE en-note SYSTEM 'http://xml.evernote.com/pub/enml2.dtd'>", "");
\r
139 // js.replace("<?xml version='1.0' encoding='UTF-8'?>", "");
\r
141 browser.setNote(currentNote);
\r
142 browser.getBrowser().page().mainFrame().setHtml(js.toString());
\r
145 //*************************************************
\r
146 //* XML Modifying Methods
\r
147 //*************************************************
\r
148 private StringBuffer rebuildNoteHTML(Note note) {
\r
149 QDomDocument doc = new QDomDocument();
\r
150 QDomDocument.Result result = doc.setContent(note.getContent());
\r
151 if (!result.success) {
\r
152 return new StringBuffer(note.getContent());
\r
155 doc = modifyTags(note, doc);
\r
156 QDomElement docElem = doc.documentElement();
\r
157 docElem.setTagName("Body");
\r
159 // Fix the stupid problem where inserting an <img> tag after an <a> tag (which is done
\r
160 // to get the <en-media> application tag to work properly) causes spaces to be inserted
\r
161 // between the <a> & <img>. This messes things up later. This is an ugly hack.
\r
162 String docString = doc.toString();
\r
163 StringBuffer html = new StringBuffer(docString.substring(docString.toLowerCase().indexOf("<body>")));
\r
165 for (int i=html.indexOf("<a en-tag=\"en-media\" ", 0); i>-1; i=html.indexOf("<a en-tag=\"en-media\" ", i)) {
\r
166 i=html.indexOf(">\n",i+1);
\r
167 int z = html.indexOf("<img",i);
\r
168 for (int j=z-1; j>i; j--)
\r
169 html.deleteCharAt(j);
\r
170 i=html.indexOf("/>", z+1);
\r
171 z = html.indexOf("</a>",i);
\r
172 for (int j=z-1; j>i+1; j--)
\r
173 html.deleteCharAt(j);
\r
180 private QDomDocument modifyTags(Note note, QDomDocument doc) {
\r
181 QDomElement docElem = doc.documentElement();
\r
183 // Modify en-media tags
\r
184 QDomNodeList anchors = docElem.elementsByTagName("en-media");
\r
185 int enMediaCount = anchors.length();
\r
186 for (int i=enMediaCount-1; i>=0; i--) {
\r
187 QDomElement enmedia = anchors.at(i).toElement();
\r
188 if (enmedia.hasAttribute("type")) {
\r
189 QDomAttr attr = enmedia.attributeNode("type");
\r
190 QDomAttr hash = enmedia.attributeNode("hash");
\r
191 String[] type = attr.nodeValue().split("/");
\r
192 String appl = type[1];
\r
194 if (type[0] != null) {
\r
195 if (type[0].equals("image")) {
\r
196 modifyImageTags(note, docElem, enmedia, hash);
\r
198 if (!type[0].equals("image")) {
\r
199 modifyApplicationTags(note, doc, docElem, enmedia, hash, appl);
\r
201 // if (type[0].equals("audio")) {
\r
202 // modifyApplicationTags(doc, docElem, enmedia, hash, appl);
\r
208 // Modify todo tags
\r
209 anchors = docElem.elementsByTagName("en-todo");
\r
210 int enTodoCount = anchors.length();
\r
211 for (int i=enTodoCount-1; i>=0; i--) {
\r
212 QDomElement enmedia = anchors.at(i).toElement();
\r
213 modifyTodoTags(enmedia);
\r
216 // Modify en-crypt tags
\r
217 anchors = docElem.elementsByTagName("en-crypt");
\r
218 int enCryptLen = anchors.length();
\r
219 for (int i=enCryptLen-1; i>=0; i--) {
\r
220 QDomElement enmedia = anchors.at(i).toElement();
\r
221 //enmedia.setAttribute("style","display:none");
\r
222 enmedia.setAttribute("contentEditable","false");
\r
223 enmedia.setAttribute("src", Global.getDirectoryPath()+"images/encrypt.png");
\r
224 enmedia.setAttribute("en-tag","en-crypt");
\r
225 enmedia.setAttribute("alt", enmedia.text());
\r
226 Global.cryptCounter++;
\r
227 enmedia.setAttribute("id", "crypt"+Global.cryptCounter.toString());
\r
228 String encryptedText = enmedia.text();
\r
230 // If the encryption string contains crlf at the end, remove them because they mess up the javascript.
\r
231 if (encryptedText.endsWith("\n"))
\r
232 encryptedText = encryptedText.substring(0,encryptedText.length()-1);
\r
233 if (encryptedText.endsWith("\r"))
\r
234 encryptedText = encryptedText.substring(0,encryptedText.length()-1);
\r
236 // Add the commands
\r
237 enmedia.setAttribute("onClick", "window.jambi.decryptText('crypt"+Global.cryptCounter.toString()+"', '"+encryptedText+"', '"+enmedia.attribute("hint")+"');");
\r
238 enmedia.setAttribute("onMouseOver", "style.cursor='hand'");
\r
239 enmedia.setTagName("img");
\r
240 enmedia.removeChild(enmedia.firstChild()); // Remove the actual encrypted text
\r
249 private void modifyApplicationTags(Note n, QDomDocument doc, QDomElement docElem, QDomElement enmedia, QDomAttr hash, String appl) {
\r
252 for (int i=0; i<n.getResourcesSize(); i++) {
\r
253 String hashValue = hash.value();
\r
254 byte res[] = n.getResources().get(i).getData().getBodyHash();
\r
255 String resourceHashValue = new String(Global.byteArrayToHexString(res));
\r
256 if (resourceHashValue.equalsIgnoreCase(hashValue)) {
\r
257 r = n.getResources().get(i);
\r
258 i=n.getResourcesSize();
\r
263 if (r.getData()!=null) {
\r
264 // Did we get a generic applicaiton? Then look at the file name to
\r
265 // try and find a good application type for the icon
\r
266 if (appl.equalsIgnoreCase("octet-stream")) {
\r
267 if (r.getAttributes() != null && r.getAttributes().getFileName() != null) {
\r
268 String fn = r.getAttributes().getFileName();
\r
269 int pos = fn.indexOf(".");
\r
271 appl = fn.substring(pos+1);
\r
276 String fileDetails = null;
\r
277 if (r.getAttributes() != null && r.getAttributes().getFileName() != null && !r.getAttributes().getFileName().equals(""))
\r
278 fileDetails = r.getAttributes().getFileName();
\r
279 if (fileDetails != null && !fileDetails.equals(""))
\r
280 enmedia.setAttribute("href", "nnres://" +r.getGuid()+n.getUpdateSequenceNum() +Global.attachmentNameDelimeter +fileDetails);
\r
282 enmedia.setAttribute("href", "nnres://" +r.getGuid()+n.getUpdateSequenceNum() +Global.attachmentNameDelimeter +appl);
\r
283 if (fileDetails == null || fileDetails.equals(""))
\r
285 enmedia.setAttribute("en-tag", "en-media");
\r
286 enmedia.setAttribute("guid", r.getGuid());
\r
287 enmedia.setTagName("a");
\r
288 QDomElement newText = doc.createElement("img");
\r
289 String icon = findIcon(appl);
\r
290 if (icon.equals("attachment.png"))
\r
291 icon = findIcon(fileDetails.substring(fileDetails.indexOf(".")+1));
\r
292 newText.setAttribute("src", Global.getDirectoryPath()+"images"+File.separator +icon);
\r
293 newText.setAttribute("title", fileDetails);
\r
294 enmedia.removeChild(enmedia.firstChild());
\r
295 enmedia.appendChild(newText);
\r
299 // find the appropriate icon for an attachment
\r
300 private String findIcon(String appl) {
\r
301 appl = appl.toLowerCase();
\r
302 File f = new File(Global.getDirectoryPath()+"images"+File.separator +appl +".png");
\r
304 return appl+".png";
\r
305 return "attachment.png";
\r
314 private void modifyTodoTags(QDomElement todo) {
\r
315 todo.setAttribute("type", "checkbox");
\r
316 String checked = todo.attribute("checked");
\r
317 todo.removeAttribute("checked");
\r
318 if (checked.equalsIgnoreCase("true"))
\r
319 todo.setAttribute("checked", "");
\r
321 todo.setAttribute("unchecked","");
\r
322 todo.setAttribute("value", checked);
\r
323 todo.setAttribute("onClick", "value=checked;window.jambi.contentChanged(); ");
\r
324 todo.setTagName("input");
\r
328 private void modifyImageTags(Note note, QDomElement docElem, QDomElement enmedia, QDomAttr hash) {
\r
329 String type = enmedia.attribute("type");
\r
330 if (type.startsWith("image/"))
\r
331 type = "."+type.substring(6);
\r
336 for (int i=0; i<note.getResourcesSize(); i++) {
\r
337 String hashValue = hash.value();
\r
338 byte res[] = note.getResources().get(i).getData().getBodyHash();
\r
339 String resourceHashValue = new String(Global.byteArrayToHexString(res));
\r
340 if (hashValue.equalsIgnoreCase(resourceHashValue)) {
\r
341 r = note.getResources().get(i);
\r
342 i=note.getResourcesSize();
\r
349 QFile tfile = new QFile(Global.getDirectoryPath()+"res"+File.separator +r.getGuid()+note.getUpdateSequenceNum()+type);
\r
350 if (!tfile.exists()) {
\r
351 if (r!= null && r.getData() != null && r.getData().getBody().length > 0) {
\r
352 tfile.open(new QIODevice.OpenMode(QIODevice.OpenModeFlag.WriteOnly));
\r
353 QByteArray binData = new QByteArray(r.getData().getBody());
\r
354 tfile.write(binData);
\r
356 enmedia.setAttribute("src", tfile.fileName());
\r
357 enmedia.setAttribute("en-tag", "en-media");
\r
358 enmedia.setNodeValue("");
\r
359 enmedia.setAttribute("guid", r.getGuid());
\r
360 enmedia.setTagName("img");
\r
363 enmedia.setAttribute("src", tfile.fileName());
\r
364 enmedia.setAttribute("en-tag", "en-media");
\r
365 enmedia.setNodeValue("");
\r
366 enmedia.setAttribute("guid", r.getGuid());
\r
367 enmedia.setTagName("img");
\r