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.NeverNote;
\r
46 import cx.fbn.nevernote.gui.BrowserWindow;
\r
47 import cx.fbn.nevernote.sql.DatabaseConnection;
\r
49 public class OnlineNoteHistory extends QDialog {
\r
50 public final QPushButton restoreAsNew;
\r
51 public final QPushButton restore;
\r
52 private DatabaseConnection conn;
\r
53 public final QComboBox historyCombo;
\r
54 private final BrowserWindow browser;
\r
57 public OnlineNoteHistory(DatabaseConnection c) {
\r
58 setWindowTitle(tr("Online Note History"));
\r
59 QVBoxLayout main = new QVBoxLayout();
\r
61 historyCombo = new QComboBox(this);
\r
63 QHBoxLayout comboLayout = new QHBoxLayout();
\r
64 comboLayout.addWidget(new QLabel(tr("History Date:")));
\r
65 comboLayout.addWidget(historyCombo);
\r
66 comboLayout.addStretch(100);
\r
68 main.addLayout(comboLayout);
\r
70 browser = new BrowserWindow(conn);
\r
71 main.addWidget(browser);
\r
72 browser.titleLabel.setVisible(false);
\r
73 browser.notebookBox.setVisible(false);
\r
74 browser.hideButtons();
\r
75 browser.tagEdit.setVisible(false);
\r
76 browser.tagLabel.setVisible(false);
\r
78 QHBoxLayout buttonLayout = new QHBoxLayout();
\r
79 buttonLayout.addStretch(100);
\r
80 restore = new QPushButton(tr("Restore Note"));
\r
81 restore.clicked.connect(this, "restorePushed()");
\r
83 restoreAsNew = new QPushButton(tr("Restore As New Note"));
\r
84 restoreAsNew.clicked.connect(this, "restoreAsNewPushed()");
\r
85 QPushButton cancel = new QPushButton(tr("Cancel"));
\r
86 cancel.clicked.connect(this, "cancelPressed()");
\r
88 buttonLayout.addWidget(restore);
\r
89 buttonLayout.addWidget(restoreAsNew);
\r
90 buttonLayout.addWidget(cancel);
\r
91 main.addLayout(buttonLayout);
\r
93 browser.getBrowser().setContextMenuPolicy(ContextMenuPolicy.NoContextMenu);
\r
97 @SuppressWarnings("unused")
\r
98 private void restoreAsNewPushed() {
\r
101 @SuppressWarnings("unused")
\r
102 private void restorePushed() {
\r
105 @SuppressWarnings("unused")
\r
106 private void cancelPressed() {
\r
110 public void setCurrent(boolean isDirty) {
\r
112 historyCombo.addItem(new String("Current (Non Synchronized)"));
\r
114 historyCombo.addItem(new String("Current (Synchronized)"));
\r
118 public void load(List<NoteVersionId> versions) {
\r
119 String fmt = Global.getDateFormat() + " " + Global.getTimeFormat();
\r
120 String dateTimeFormat = new String(fmt);
\r
121 SimpleDateFormat simple = new SimpleDateFormat(dateTimeFormat);
\r
123 for (int i=0; i<versions.size(); i++) {
\r
124 StringBuilder versionDate = new StringBuilder(simple.format(versions.get(i).getServiceUpdated()));
\r
125 historyCombo.addItem(versionDate.toString());
\r
129 public void setContent(Note currentNote) {
\r
130 StringBuffer b = rebuildNoteHTML(currentNote);
\r
131 StringBuffer js = new StringBuffer();
\r
133 // We need to prepend the note with <HEAD></HEAD> or encoded characters are ugly
\r
134 js.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");
\r
135 js.append("<style type=\"text/css\">en-crypt-temp { border-style:solid; border-color:blue; padding:1mm 1mm 1mm 1mm; }</style>");
\r
136 js.append("</head>");
\r
137 js.append(b.toString());
\r
138 js.append("</HTML>");
\r
139 // js.replace("<!DOCTYPE en-note SYSTEM 'http://xml.evernote.com/pub/enml2.dtd'>", "");
\r
140 // js.replace("<?xml version='1.0' encoding='UTF-8'?>", "");
\r
142 browser.setNote(currentNote);
\r
143 browser.getBrowser().page().mainFrame().setHtml(js.toString());
\r
146 //*************************************************
\r
147 //* XML Modifying Methods
\r
148 //*************************************************
\r
149 private StringBuffer rebuildNoteHTML(Note note) {
\r
150 QDomDocument doc = new QDomDocument();
\r
151 QDomDocument.Result result = doc.setContent(note.getContent());
\r
152 if (!result.success) {
\r
153 return new StringBuffer(note.getContent());
\r
156 doc = modifyTags(note, doc);
\r
157 QDomElement docElem = doc.documentElement();
\r
158 docElem.setTagName("Body");
\r
160 // Fix the stupid problem where inserting an <img> tag after an <a> tag (which is done
\r
161 // to get the <en-media> application tag to work properly) causes spaces to be inserted
\r
162 // between the <a> & <img>. This messes things up later. This is an ugly hack.
\r
163 String docString = doc.toString();
\r
164 StringBuffer html = new StringBuffer(docString.substring(docString.toLowerCase().indexOf("<body>")));
\r
166 for (int i=html.indexOf("<a en-tag=\"en-media\" ", 0); i>-1; i=html.indexOf("<a en-tag=\"en-media\" ", i)) {
\r
167 i=html.indexOf(">\n",i+1);
\r
168 int z = html.indexOf("<img",i);
\r
169 for (int j=z-1; j>i; j--)
\r
170 html.deleteCharAt(j);
\r
171 i=html.indexOf("/>", z+1);
\r
172 z = html.indexOf("</a>",i);
\r
173 for (int j=z-1; j>i+1; j--)
\r
174 html.deleteCharAt(j);
\r
181 private QDomDocument modifyTags(Note note, QDomDocument doc) {
\r
182 QDomElement docElem = doc.documentElement();
\r
184 // Modify en-media tags
\r
185 QDomNodeList anchors = docElem.elementsByTagName("en-media");
\r
186 int enMediaCount = anchors.length();
\r
187 for (int i=enMediaCount-1; i>=0; i--) {
\r
188 QDomElement enmedia = anchors.at(i).toElement();
\r
189 if (enmedia.hasAttribute("type")) {
\r
190 QDomAttr attr = enmedia.attributeNode("type");
\r
191 QDomAttr hash = enmedia.attributeNode("hash");
\r
192 String[] type = attr.nodeValue().split("/");
\r
193 String appl = type[1];
\r
195 if (type[0] != null) {
\r
196 if (type[0].equals("image")) {
\r
197 modifyImageTags(note, docElem, enmedia, hash);
\r
199 if (!type[0].equals("image")) {
\r
200 modifyApplicationTags(note, doc, docElem, enmedia, hash, appl);
\r
202 // if (type[0].equals("audio")) {
\r
203 // modifyApplicationTags(doc, docElem, enmedia, hash, appl);
\r
209 // Modify todo tags
\r
210 anchors = docElem.elementsByTagName("en-todo");
\r
211 int enTodoCount = anchors.length();
\r
212 for (int i=enTodoCount-1; i>=0; i--) {
\r
213 QDomElement enmedia = anchors.at(i).toElement();
\r
214 modifyTodoTags(enmedia);
\r
217 // Modify en-crypt tags
\r
218 anchors = docElem.elementsByTagName("en-crypt");
\r
219 int enCryptLen = anchors.length();
\r
220 for (int i=enCryptLen-1; i>=0; i--) {
\r
221 QDomElement enmedia = anchors.at(i).toElement();
\r
222 //enmedia.setAttribute("style","display:none");
\r
223 enmedia.setAttribute("contentEditable","false");
\r
224 enmedia.setAttribute("src", Global.getFileManager().getImageDirPath("encrypt.png"));
\r
225 enmedia.setAttribute("en-tag","en-crypt");
\r
226 enmedia.setAttribute("alt", enmedia.text());
\r
227 Global.cryptCounter++;
\r
228 enmedia.setAttribute("id", "crypt"+Global.cryptCounter.toString());
\r
229 String encryptedText = enmedia.text();
\r
231 // If the encryption string contains crlf at the end, remove them because they mess up the javascript.
\r
232 if (encryptedText.endsWith("\n"))
\r
233 encryptedText = encryptedText.substring(0,encryptedText.length()-1);
\r
234 if (encryptedText.endsWith("\r"))
\r
235 encryptedText = encryptedText.substring(0,encryptedText.length()-1);
\r
237 // Add the commands
\r
238 enmedia.setAttribute("onClick", "window.jambi.decryptText('crypt"+Global.cryptCounter.toString()+"', '"+encryptedText+"', '"+enmedia.attribute("hint")+"');");
\r
239 enmedia.setAttribute("onMouseOver", "style.cursor='hand'");
\r
240 enmedia.setTagName("img");
\r
241 enmedia.removeChild(enmedia.firstChild()); // Remove the actual encrypted text
\r
250 private void modifyApplicationTags(Note n, QDomDocument doc, QDomElement docElem, QDomElement enmedia, QDomAttr hash, String appl) {
\r
253 for (int i=0; i<n.getResourcesSize(); i++) {
\r
254 String hashValue = hash.value();
\r
255 byte res[] = n.getResources().get(i).getData().getBodyHash();
\r
256 String resourceHashValue = new String(Global.byteArrayToHexString(res));
\r
257 if (resourceHashValue.equalsIgnoreCase(hashValue)) {
\r
258 r = n.getResources().get(i);
\r
259 i=n.getResourcesSize();
\r
264 if (r.getData()!=null) {
\r
265 // Did we get a generic applicaiton? Then look at the file name to
\r
266 // try and find a good application type for the icon
\r
267 if (appl.equalsIgnoreCase("octet-stream")) {
\r
268 if (r.getAttributes() != null && r.getAttributes().getFileName() != null) {
\r
269 String fn = r.getAttributes().getFileName();
\r
270 int pos = fn.indexOf(".");
\r
272 appl = fn.substring(pos+1);
\r
277 String fileDetails = null;
\r
278 if (r.getAttributes() != null && r.getAttributes().getFileName() != null && !r.getAttributes().getFileName().equals(""))
\r
279 fileDetails = r.getAttributes().getFileName();
\r
280 if (fileDetails != null && !fileDetails.equals(""))
\r
281 enmedia.setAttribute("href", "nnres://" +r.getGuid()+n.getUpdateSequenceNum() +Global.attachmentNameDelimeter +fileDetails);
\r
283 enmedia.setAttribute("href", "nnres://" +r.getGuid()+n.getUpdateSequenceNum() +Global.attachmentNameDelimeter +appl);
\r
284 if (fileDetails == null || fileDetails.equals(""))
\r
286 enmedia.setAttribute("en-tag", "en-media");
\r
287 enmedia.setAttribute("guid", r.getGuid());
\r
288 enmedia.setTagName("a");
\r
289 QDomElement newText = doc.createElement("img");
\r
290 String icon = findIcon(appl);
\r
291 if (icon.equals("attachment.png"))
\r
292 icon = findIcon(fileDetails.substring(fileDetails.indexOf(".")+1));
\r
293 newText.setAttribute("src", Global.getFileManager().getImageDirPath(icon));
\r
294 newText.setAttribute("title", fileDetails);
\r
295 enmedia.removeChild(enmedia.firstChild());
\r
296 enmedia.appendChild(newText);
\r
301 * find the appropriate icon for an attachment
\r
303 * NFC TODO: duplicate of {@link NeverNote#findIcon(String)}
\r
305 private String findIcon(String appl) {
\r
306 appl = appl.toLowerCase();
\r
307 String relativePath = appl + ".png";
\r
308 File f = Global.getFileManager().getImageDirFile(relativePath);
\r
310 return relativePath;
\r
312 return "attachment.png";
\r
321 private void modifyTodoTags(QDomElement todo) {
\r
322 todo.setAttribute("type", "checkbox");
\r
323 String checked = todo.attribute("checked");
\r
324 todo.removeAttribute("checked");
\r
325 if (checked.equalsIgnoreCase("true"))
\r
326 todo.setAttribute("checked", "");
\r
328 todo.setAttribute("unchecked","");
\r
329 todo.setAttribute("value", checked);
\r
330 todo.setAttribute("onClick", "value=checked;window.jambi.contentChanged(); ");
\r
331 todo.setTagName("input");
\r
335 private void modifyImageTags(Note note, QDomElement docElem, QDomElement enmedia, QDomAttr hash) {
\r
336 String type = enmedia.attribute("type");
\r
337 if (type.startsWith("image/"))
\r
338 type = "."+type.substring(6);
\r
343 for (int i=0; i<note.getResourcesSize(); i++) {
\r
344 String hashValue = hash.value();
\r
345 byte res[] = note.getResources().get(i).getData().getBodyHash();
\r
346 String resourceHashValue = new String(Global.byteArrayToHexString(res));
\r
347 if (hashValue.equalsIgnoreCase(resourceHashValue)) {
\r
348 r = note.getResources().get(i);
\r
349 i=note.getResourcesSize();
\r
356 QFile tfile = new QFile(Global.getFileManager().getResDirPath(r.getGuid() + note.getUpdateSequenceNum() + type));
\r
357 if (!tfile.exists()) {
\r
358 if (r!= null && r.getData() != null && r.getData().getBody().length > 0) {
\r
359 tfile.open(new QIODevice.OpenMode(QIODevice.OpenModeFlag.WriteOnly));
\r
360 QByteArray binData = new QByteArray(r.getData().getBody());
\r
361 tfile.write(binData);
\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
370 enmedia.setAttribute("src", tfile.fileName());
\r
371 enmedia.setAttribute("en-tag", "en-media");
\r
372 enmedia.setNodeValue("");
\r
373 enmedia.setAttribute("guid", r.getGuid());
\r
374 enmedia.setTagName("img");
\r