2 * This file is part of NeighborNote
3 * Copyright 2013 Yuki Takahashi
5 * This file may be licensed under the terms of of the
6 * GNU General Public License Version 2 (the ``GPL'').
8 * Software distributed under the License is distributed
9 * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
10 * express or implied. See the GPL for the specific language
11 * governing rights and limitations.
13 * You should have received a copy of the GPL along with this
14 * program. If not, go to http://www.gnu.org/licenses/gpl.html
15 * or write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package cx.fbn.nevernote.gui;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
29 import com.evernote.edam.type.Note;
30 import com.trolltech.qt.QThread;
31 import com.trolltech.qt.core.QByteArray;
32 import com.trolltech.qt.core.QFile;
33 import com.trolltech.qt.core.QSize;
34 import com.trolltech.qt.core.Qt.MouseButton;
35 import com.trolltech.qt.gui.QAction;
36 import com.trolltech.qt.gui.QApplication;
37 import com.trolltech.qt.gui.QContextMenuEvent;
38 import com.trolltech.qt.gui.QListWidget;
39 import com.trolltech.qt.gui.QListWidgetItem;
40 import com.trolltech.qt.gui.QMenu;
42 import cx.fbn.nevernote.Global;
43 import cx.fbn.nevernote.NeverNote;
44 import cx.fbn.nevernote.sql.DatabaseConnection;
45 import cx.fbn.nevernote.threads.CounterRunner;
46 import cx.fbn.nevernote.threads.ENRelatedNotesRunner;
47 import cx.fbn.nevernote.threads.ENThumbnailRunner;
48 import cx.fbn.nevernote.threads.SyncRunner;
49 import cx.fbn.nevernote.utilities.ApplicationLogger;
50 import cx.fbn.nevernote.utilities.Pair;
52 public class RensoNoteList extends QListWidget {
53 private final DatabaseConnection conn;
54 private final ApplicationLogger logger;
55 private final HashMap<QListWidgetItem, String> rensoNoteListItems;
56 private final HashMap<String, RensoNoteListItem> rensoNoteListTrueItems;
57 private String rensoNotePressedItemGuid;
58 private final QAction openNewTabAction;
59 private final QAction starAction;
60 private final QAction unstarAction;
61 private final QAction excludeNoteAction;
62 private final NeverNote parent;
63 private final QMenu menu;
64 private HashMap<String, Integer> mergedHistory; // マージされた操作履歴
65 private final SyncRunner syncRunner;
66 private final ENRelatedNotesRunner enRelatedNotesRunner;
67 private final QThread enRelatedNotesThread;
68 private final HashMap<String, List<String>> enRelatedNotesCache; // Evernote関連ノートのキャッシュ<guid, 関連ノートリスト>
69 private final ENThumbnailRunner enThumbnailRunner;
70 private final QThread enThumbnailThread;
72 private int allPointSum;
74 public RensoNoteList(DatabaseConnection c, NeverNote p, SyncRunner syncRunner, ApplicationLogger logger) {
76 this.logger.log(this.logger.HIGH, "Setting up rensoNoteList");
81 this.syncRunner = syncRunner;
83 this.guid = new String();
84 mergedHistory = new HashMap<String, Integer>();
85 enRelatedNotesCache = new HashMap<String, List<String>>();
86 this.enRelatedNotesRunner = new ENRelatedNotesRunner(this.syncRunner, "enRelatedNotesRunner.log");
87 this.enRelatedNotesRunner.enRelatedNotesSignal.getENRelatedNotesFinished.connect(this, "enRelatedNotesComplete()");
88 this.enRelatedNotesRunner.limitSignal.rateLimitReached.connect(parent, "informRateLimit(Integer)");
89 this.enRelatedNotesThread = new QThread(enRelatedNotesRunner, "ENRelatedNotes Thread");
90 this.getEnRelatedNotesThread().start();
92 this.enThumbnailRunner = new ENThumbnailRunner("enThumbnailRunner.log", CounterRunner.NOTEBOOK,
93 Global.getDatabaseUrl(), Global.getIndexDatabaseUrl(), Global.getResourceDatabaseUrl(), Global.getBehaviorDatabaseUrl(),
94 Global.getDatabaseUserid(), Global.getDatabaseUserPassword(), Global.cipherPassword);
95 this.enThumbnailRunner.enThumbnailSignal.getENThumbnailFinished.connect(this, "enThumbnailComplete(String)");
96 this.enThumbnailRunner.limitSignal.rateLimitReached.connect(parent, "informRateLimit(Integer)");
97 this.enThumbnailThread = new QThread(enThumbnailRunner, "ENThumbnail Thread");
98 this.enThumbnailThread.start();
100 rensoNoteListItems = new HashMap<QListWidgetItem, String>();
101 rensoNoteListTrueItems = new HashMap<String, RensoNoteListItem>();
103 this.itemPressed.connect(this, "rensoNoteItemPressed(QListWidgetItem)");
106 menu = new QMenu(this);
108 openNewTabAction = new QAction(tr("Open in New Tab"), this);
109 openNewTabAction.setToolTip(tr("Open this note in new tab"));
110 openNewTabAction.triggered.connect(parent, "openNewTabFromRNL()");
112 starAction = new QAction(tr("Add Star"), this);
113 starAction.setToolTip(tr("Add Star to this item"));
114 starAction.triggered.connect(parent, "starNote()");
116 unstarAction = new QAction(tr("Remove Star"), this);
117 unstarAction.setToolTip(tr("Remove Star from this item"));
118 unstarAction.triggered.connect(parent, "unstarNote()");
120 excludeNoteAction = new QAction(tr("Exclude"), this);
121 excludeNoteAction.setToolTip(tr("Exclude this note from RensoNoteList"));
122 excludeNoteAction.triggered.connect(parent, "excludeNote()");
124 menu.addAction(openNewTabAction);
125 menu.addAction(excludeNoteAction);
126 menu.aboutToHide.connect(this, "contextMenuHidden()");
128 this.logger.log(this.logger.HIGH, "rensoNoteList setup complete");
132 // 現在開いているノートの連想ノートリストをリフレッシュ
133 public void refreshRensoNoteList() {
134 refreshRensoNoteList(guid);
138 public void refreshRensoNoteList(String guid) {
139 logger.log(logger.HIGH, "Entering RensoNoteList.refreshRensoNoteList guid = " + guid);
142 rensoNoteListItems.clear();
143 rensoNoteListTrueItems.clear();
144 mergedHistory = new HashMap<String, Integer>();
146 if (!this.isEnabled()) {
149 if (guid == null || guid.equals("")) {
154 // すでにEvernote関連ノートがキャッシュされているか確認
156 isCached = enRelatedNotesCache.containsKey(guid);
157 if (!isCached) { // キャッシュ無し
158 // Evernoteの関連ノートを別スレッドで取得させる
159 enRelatedNotesRunner.addGuid(guid);
161 List<String> relatedNoteGuids = enRelatedNotesCache.get(guid);
162 addENRelatedNotes(relatedNoteGuids);
165 calculateHistory(guid);
166 repaintRensoNoteList(false);
168 logger.log(logger.HIGH, "Leaving RensoNoteList.refreshRensoNoteList");
171 // 操作履歴をデータベースから取得してノートごとの関連度を算出、その後mergedHistoryに追加
172 private void calculateHistory(String guid) {
173 logger.log(logger.EXTREME, "Entering RensoNoteList.calculateHistory guid = " + guid);
175 // browseHistory<guid, 回数(ポイント)>
176 HashMap<String, Integer> browseHistory = conn.getHistoryTable().getBehaviorHistory("browse", guid);
177 addWeight(browseHistory, Global.getBrowseWeight());
178 mergedHistory = mergeHistory(filterHistory(browseHistory), mergedHistory);
180 // copy&pasteHistory<guid, 回数(ポイント)>
181 HashMap<String, Integer> copyAndPasteHistory = conn.getHistoryTable().getBehaviorHistory("copy & paste", guid);
182 addWeight(copyAndPasteHistory, Global.getCopyPasteWeight());
183 mergedHistory = mergeHistory(filterHistory(copyAndPasteHistory), mergedHistory);
185 // addNewNoteHistory<guid, 回数(ポイント)>
186 HashMap<String, Integer> addNewNoteHistory = conn.getHistoryTable().getBehaviorHistory("addNewNote", guid);
187 addWeight(addNewNoteHistory, Global.getAddNewNoteWeight());
188 mergedHistory = mergeHistory(filterHistory(addNewNoteHistory), mergedHistory);
190 // rensoItemClickHistory<guid, 回数(ポイント)>
191 HashMap<String, Integer> rensoItemClickHistory = conn.getHistoryTable().getBehaviorHistory("rensoItemClick", guid);
192 addWeight(rensoItemClickHistory, Global.getRensoItemClickWeight());
193 mergedHistory = mergeHistory(filterHistory(rensoItemClickHistory), mergedHistory);
195 // sameTagHistory<guid, 回数(ポイント)>
196 HashMap<String, Integer> sameTagHistory = conn.getHistoryTable().getBehaviorHistory("sameTag", guid);
197 addWeight(sameTagHistory, Global.getSameTagWeight());
198 mergedHistory = mergeHistory(filterHistory(sameTagHistory), mergedHistory);
200 // sameNotebookNoteHistory<guid, 回数(ポイント)>
201 HashMap<String, Integer> sameNotebookHistory = conn.getHistoryTable().getBehaviorHistory("sameNotebook", guid);
202 addWeight(sameNotebookHistory, Global.getSameNotebookWeight());
203 mergedHistory = mergeHistory(filterHistory(sameNotebookHistory), mergedHistory);
204 logger.log(logger.EXTREME, "Leaving RensoNoteList.calculateHistory");
208 private void addWeight(HashMap<String, Integer> history, int weight){
209 logger.log(logger.EXTREME, "Entering RensoNoteList.addWeight");
211 Set<String> keySet = history.keySet();
212 Iterator<String> hist_iterator = keySet.iterator();
213 while(hist_iterator.hasNext()){
214 String key = hist_iterator.next();
215 history.put(key, history.get(key) * weight);
218 logger.log(logger.EXTREME, "Leaving RensoNoteList.addWeight");
222 private void repaintRensoNoteList(boolean needClear) {
223 logger.log(logger.EXTREME, "Entering RensoNoteList.repaintRensoNoteList");
227 rensoNoteListItems.clear();
228 rensoNoteListTrueItems.clear();
231 if (!this.isEnabled()) {
235 // すべての関連ポイントの合計を取得(関連度のパーセント算出に利用)
237 for (int p : mergedHistory.values()) {
241 addRensoNoteList(mergedHistory);
243 logger.log(logger.EXTREME, "Leaving RensoNoteList.repaintRensoNoteList");
246 // 引数1と引数2をマージしたハッシュマップを返す
247 private HashMap<String, Integer> mergeHistory(HashMap<String, Integer> History1, HashMap<String, Integer> History2){
248 logger.log(logger.EXTREME, "Entering RensoNoteList.mergeHistory");
250 HashMap<String, Integer> mergedHistory = new HashMap<String, Integer>();
252 mergedHistory.putAll(History1);
254 Set<String> keySet = History2.keySet();
255 Iterator<String> hist2_iterator = keySet.iterator();
256 while(hist2_iterator.hasNext()){
257 String key = hist2_iterator.next();
258 if(mergedHistory.containsKey(key)){
259 mergedHistory.put(key, mergedHistory.get(key) + History2.get(key));
261 mergedHistory.put(key, History2.get(key));
265 logger.log(logger.EXTREME, "Leaving RensoNoteList.mergeHistory");
266 return mergedHistory;
269 // 連想ノートリストにハッシュマップのデータを追加
270 private void addRensoNoteList(HashMap<String, Integer> History){
271 logger.log(logger.EXTREME, "Entering RensoNoteList.addRensoNoteList");
273 enThumbnailRunner.setUser(Global.getUserInformation());
274 enThumbnailRunner.setServerUrl(Global.getServer());
276 String currentNoteGuid = new String(parent.getCurrentNoteGuid());
278 // スター付きノートとスター無しノートを分ける
279 HashMap<String, Integer> staredNotes = new HashMap<String, Integer>(); // スター付きノートのマップ
280 HashMap<String, Integer> normalNotes = new HashMap<String, Integer>(); // スター無しノートのマップ
281 for (String nextGuid: History.keySet()) {
282 int relationPoint = History.get(nextGuid);
283 boolean isStared = conn.getStaredTable().existNote(currentNoteGuid, nextGuid);
285 staredNotes.put(nextGuid, relationPoint);
287 normalNotes.put(nextGuid, relationPoint);
291 // 連想ノートリストアイテムの最大表示数まで繰り返す
292 for (int i = 0; i < Global.getRensoListItemMaximum(); i++) {
293 // スター付きノートがあれば先に処理する
294 HashMap<String, Integer> tmpMap = new HashMap<String, Integer>();
295 if (!staredNotes.isEmpty()) {
296 tmpMap = staredNotes;
297 }else if (!normalNotes.isEmpty()) {
298 tmpMap = normalNotes;
301 // 操作回数が多い順に取り出して連想ノートリストに追加する
302 if (!tmpMap.isEmpty()) {
304 String maxGuid = new String();
306 for (String nextGuid: tmpMap.keySet()) {
307 int relationPoint = tmpMap.get(nextGuid);
310 if (relationPoint > maxNum) {
311 maxNum = relationPoint;
316 // 次の最大値探索で邪魔なので最大値をHashMapから削除
317 tmpMap.remove(maxGuid);
319 // 関連度最大のノートがアクティブか確認
320 Note maxNote = conn.getNoteTable().getNote(maxGuid, true, false, false, false, true);
321 boolean isNoteActive = false;
322 if(maxNote != null) {
323 isNoteActive = maxNote.isActive();
326 // 存在していて、かつ関連度0でなければノート情報を取得して連想ノートリストに追加
327 if (isNoteActive && maxNum > 0) {
328 // Evernoteサムネイルが取得済みか確認。未取得ならサムネイル取得スレッドにキュー
329 if (Global.isConnected) {
330 String thumbnailName = Global.getFileManager().getResDirPath("enThumbnail-" + maxGuid + ".png");
331 QFile thumbnail = new QFile(thumbnailName);
332 if (!thumbnail.exists()) { // Evernoteサムネイルがファイルとして存在しない
333 QByteArray data = conn.getNoteTable().getENThumbnail(maxGuid);
334 if (data == null) { // Evernoteサムネイル未取得
335 enThumbnailRunner.addGuid(maxGuid);
342 isStared = conn.getStaredTable().existNote(currentNoteGuid, maxGuid);
344 QListWidgetItem item = new QListWidgetItem();
345 RensoNoteListItem myItem = new RensoNoteListItem(maxNote, maxNum, isStared, allPointSum, conn, this);
346 item.setSizeHint(new QSize(0, 90));
348 this.setItemWidget(item, myItem);
349 rensoNoteListItems.put(item, maxGuid);
350 rensoNoteListTrueItems.put(maxGuid, myItem);
356 logger.log(logger.EXTREME, "Leaving RensoNoteList.addRensoNoteList");
359 // リストのアイテムから対象ノートのguidを取得
360 public String getNoteGuid(QListWidgetItem item) {
361 return rensoNoteListItems.get(item);
364 // 関連ノートリストの右クリックメニュー
366 public void contextMenuEvent(QContextMenuEvent event){
367 logger.log(logger.EXTREME, "Entering RensoNoteList.contextMenuEvent");
369 if (rensoNotePressedItemGuid == null || rensoNotePressedItemGuid.equals("")) {
373 // STAR, UNSTARがあれば、一度消す
374 List<QAction> menuActions = new ArrayList<QAction>(menu.actions());
375 if (menuActions.contains(starAction)) {
376 menu.removeAction(starAction);
378 if (menuActions.contains(unstarAction)) {
379 menu.removeAction(unstarAction);
382 // 対象アイテムがスター付きなら「UNSTAR」、スター無しなら「STAR」を追加
383 String currentNoteGuid = parent.getCurrentNoteGuid();
384 boolean isExist = conn.getStaredTable().existNote(currentNoteGuid, rensoNotePressedItemGuid);
386 menu.insertAction(excludeNoteAction, unstarAction);
388 menu.insertAction(excludeNoteAction, starAction);
392 menu.exec(event.globalPos());
394 rensoNotePressedItemGuid = null;
396 logger.log(logger.EXTREME, "Leaving RensoNoteList.contextMenuEvent");
399 // コンテキストメニューが表示されているかどうか
400 public boolean isContextMenuVisible() {
401 return menu.isVisible();
405 @SuppressWarnings("unused")
406 private void contextMenuHidden() {
407 for (RensoNoteListItem item : rensoNoteListTrueItems.values()) {
408 item.setDefaultBackground();
412 // ユーザが連想ノートリストのアイテムを選択した時の処理
413 @SuppressWarnings("unused")
414 private void rensoNoteItemPressed(QListWidgetItem current) {
415 logger.log(logger.HIGH, "Entering RensoNoteList.rensoNoteItemPressed");
417 rensoNotePressedItemGuid = null;
419 if (QApplication.mouseButtons().isSet(MouseButton.RightButton)) {
420 rensoNotePressedItemGuid = getNoteGuid(current);
423 logger.log(logger.HIGH, "Leaving RensoNoteList.rensoNoteItemPressed");
426 // Evernoteの関連ノートの取得が完了
427 @SuppressWarnings("unused")
428 private void enRelatedNotesComplete() {
429 logger.log(logger.HIGH, "Entering RensoNoteList.enRelatedNotesComplete");
431 Pair<String, List<String>> enRelatedNoteGuidPair = enRelatedNotesRunner.getENRelatedNoteGuids(); // <元ノートguid, 関連ノートguidリスト>
433 if (enRelatedNoteGuidPair == null) {
437 String sourceGuid = enRelatedNoteGuidPair.getFirst();
438 List<String> enRelatedNoteGuids = enRelatedNoteGuidPair.getSecond();
441 if (sourceGuid != null && !sourceGuid.equals("") && enRelatedNoteGuids != null) { // Evernote関連ノートがnullでなければ
443 enRelatedNotesCache.put(sourceGuid, enRelatedNoteGuids);
445 if (!enRelatedNoteGuids.isEmpty()) { // Evernote関連ノートが存在していて
446 if (sourceGuid.equals(this.guid)) { // 取得したデータが今開いているノートの関連ノートなら
447 // mergedHistoryにEvernote関連ノートを追加してから再描画
448 addENRelatedNotes(enRelatedNoteGuids);
449 repaintRensoNoteList(true);
454 logger.log(logger.HIGH, "Leaving RensoNoteList.enRelatedNotesComplete");
457 // Evernote関連ノートの関連度情報をmergedHistoryに追加
458 private void addENRelatedNotes(List<String> relatedNoteGuids) {
459 logger.log(logger.EXTREME, "Entering RensoNoteList.addENRelatedNotes");
461 // Evernote関連ノート<guid, 関連ポイント>
462 HashMap<String, Integer> enRelatedNotes = new HashMap<String, Integer>();
464 for (String relatedGuid : relatedNoteGuids) {
465 enRelatedNotes.put(relatedGuid, Global.getENRelatedNotesWeight());
468 mergedHistory = mergeHistory(filterHistory(enRelatedNotes), mergedHistory);
470 logger.log(logger.EXTREME, "Leaving RensoNoteList.addENRelatedNotes");
473 // Evernoteの関連ノート取得スレッドを終了させる
474 public boolean stopThread() {
475 logger.log(logger.HIGH, "Entering RensoNoteList.stopThread");
477 if (!enRelatedNotesRunner.addStop()) {
478 logger.log(logger.HIGH, "RensoNoteList.stopThread failed(enRelatedNotesRunner)");
481 if (!enThumbnailRunner.addStop()) {
482 logger.log(logger.HIGH, "RensoNoteList.stopThread failed(enThumbnailRunner)");
486 logger.log(logger.HIGH, "RensoNoteList.stopThread succeeded");
490 public QThread getEnRelatedNotesThread() {
491 return enRelatedNotesThread;
494 public String getGuid() {
498 // ローカルに存在していて、かつアクティブなノートだけを返す
499 private HashMap<String, Integer> filterHistory(HashMap<String, Integer> sourceHistory) {
500 HashMap<String, Integer> dstHistory = new HashMap<String, Integer>();
502 for (String guid : sourceHistory.keySet()) {
503 if (conn.getNoteTable().exists(guid)) {
504 if (conn.getNoteTable().getNote(guid, false, false, false, false, false).isActive()) {
505 dstHistory.put(guid, sourceHistory.get(guid));
514 * Evernoteサムネイルの取得が完了
516 * @param guid 現在開いているノートのguid
518 @SuppressWarnings("unused")
519 private void enThumbnailComplete(String guid) {
520 logger.log(logger.HIGH, "Entering RensoNoteList.enThumbnailComplete");
522 for (Map.Entry<String, RensoNoteListItem> e : rensoNoteListTrueItems.entrySet()) {
523 // サムネイル取得が完了したノートが現在の連想ノートリストに表示されていたら再描画
524 if (guid.equals(e.getKey())) {
525 e.getValue().repaint();
529 logger.log(logger.HIGH, "Leaving RensoNoteList.enThumbnailComplete");