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.
21 package cx.fbn.nevernote.gui;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
29 import com.evernote.edam.type.Note;
30 import com.trolltech.qt.QThread;
31 import com.trolltech.qt.core.QSize;
32 import com.trolltech.qt.core.Qt.MouseButton;
33 import com.trolltech.qt.gui.QAction;
34 import com.trolltech.qt.gui.QApplication;
35 import com.trolltech.qt.gui.QContextMenuEvent;
36 import com.trolltech.qt.gui.QListWidget;
37 import com.trolltech.qt.gui.QListWidgetItem;
38 import com.trolltech.qt.gui.QMenu;
40 import cx.fbn.nevernote.Global;
41 import cx.fbn.nevernote.NeverNote;
42 import cx.fbn.nevernote.sql.DatabaseConnection;
43 import cx.fbn.nevernote.threads.ENRelatedNotesRunner;
44 import cx.fbn.nevernote.threads.SyncRunner;
45 import cx.fbn.nevernote.utilities.ApplicationLogger;
46 import cx.fbn.nevernote.utilities.Pair;
48 public class RensoNoteList extends QListWidget {
49 private final DatabaseConnection conn;
50 private final ApplicationLogger logger;
51 private final HashMap<QListWidgetItem, String> rensoNoteListItems;
52 private final List<RensoNoteListItem> rensoNoteListTrueItems;
53 private String rensoNotePressedItemGuid;
54 private final QAction openNewTabAction;
55 private final QAction starAction;
56 private final QAction unstarAction;
57 private final QAction excludeNoteAction;
58 private final NeverNote parent;
59 private final QMenu menu;
60 private HashMap<String, Integer> mergedHistory; // マージされた操作履歴
61 private final SyncRunner syncRunner;
62 private final ENRelatedNotesRunner enRelatedNotesRunner;
63 private final QThread enRelatedNotesThread;
64 private final HashMap<String, List<String>> enRelatedNotesCache; // Evernote関連ノートのキャッシュ<guid, 関連ノートリスト>
66 private int allPointSum;
68 public RensoNoteList(DatabaseConnection c, NeverNote p, SyncRunner syncRunner, ApplicationLogger logger) {
70 this.logger.log(this.logger.HIGH, "Setting up rensoNoteList");
75 this.syncRunner = syncRunner;
77 this.guid = new String();
78 mergedHistory = new HashMap<String, Integer>();
79 enRelatedNotesCache = new HashMap<String, List<String>>();
80 this.enRelatedNotesRunner = new ENRelatedNotesRunner(this.syncRunner, this.logger);
81 this.enRelatedNotesRunner.enRelatedNotesSignal.getENRelatedNotesFinished.connect(this, "enRelatedNotesComplete()");
82 this.enRelatedNotesRunner.limitSignal.rateLimitReached.connect(parent, "informRateLimit(Integer)");
83 this.enRelatedNotesThread = new QThread(enRelatedNotesRunner, "ENRelatedNotes Thread");
84 this.getEnRelatedNotesThread().start();
86 rensoNoteListItems = new HashMap<QListWidgetItem, String>();
87 rensoNoteListTrueItems = new ArrayList<RensoNoteListItem>();
89 this.itemPressed.connect(this, "rensoNoteItemPressed(QListWidgetItem)");
92 menu = new QMenu(this);
94 openNewTabAction = new QAction(tr("Open in New Tab"), this);
95 openNewTabAction.setToolTip(tr("Open this note in new tab"));
96 openNewTabAction.triggered.connect(parent, "openNewTabFromRNL()");
98 starAction = new QAction(tr("Add Star"), this);
99 starAction.setToolTip(tr("Add Star to this item"));
100 starAction.triggered.connect(parent, "starNote()");
102 unstarAction = new QAction(tr("Remove Star"), this);
103 unstarAction.setToolTip(tr("Remove Star from this item"));
104 unstarAction.triggered.connect(parent, "unstarNote()");
106 excludeNoteAction = new QAction(tr("Exclude"), this);
107 excludeNoteAction.setToolTip(tr("Exclude this note from RensoNoteList"));
108 excludeNoteAction.triggered.connect(parent, "excludeNote()");
110 menu.addAction(openNewTabAction);
111 menu.addAction(excludeNoteAction);
112 menu.aboutToHide.connect(this, "contextMenuHidden()");
114 this.logger.log(this.logger.HIGH, "rensoNoteList setup complete");
118 // 現在開いているノートの連想ノートリストをリフレッシュ
119 public void refreshRensoNoteList() {
120 refreshRensoNoteList(guid);
124 public void refreshRensoNoteList(String guid) {
125 logger.log(logger.HIGH, "Entering RensoNoteList.refreshRensoNoteList guid = " + guid);
128 rensoNoteListItems.clear();
129 rensoNoteListTrueItems.clear();
130 mergedHistory = new HashMap<String, Integer>();
132 if (!this.isEnabled()) {
135 if (guid == null || guid.equals("")) {
140 // すでにEvernote関連ノートがキャッシュされているか確認
142 isCached = enRelatedNotesCache.containsKey(guid);
143 if (!isCached) { // キャッシュ無し
144 // Evernoteの関連ノートを別スレッドで取得させる
145 enRelatedNotesRunner.addGuid(guid);
147 List<String> relatedNoteGuids = enRelatedNotesCache.get(guid);
148 addENRelatedNotes(relatedNoteGuids);
151 calculateHistory(guid);
152 repaintRensoNoteList(false);
154 logger.log(logger.HIGH, "Leaving RensoNoteList.refreshRensoNoteList");
157 // 操作履歴をデータベースから取得してノートごとの関連度を算出、その後mergedHistoryに追加
158 private void calculateHistory(String guid) {
159 logger.log(logger.EXTREME, "Entering RensoNoteList.calculateHistory guid = " + guid);
161 // browseHistory<guid, 回数(ポイント)>
162 HashMap<String, Integer> browseHistory = conn.getHistoryTable().getBehaviorHistory("browse", guid);
163 addWeight(browseHistory, Global.getBrowseWeight());
164 mergedHistory = mergeHistory(browseHistory, mergedHistory);
166 // copy&pasteHistory<guid, 回数(ポイント)>
167 HashMap<String, Integer> copyAndPasteHistory = conn.getHistoryTable().getBehaviorHistory("copy & paste", guid);
168 addWeight(copyAndPasteHistory, Global.getCopyPasteWeight());
169 mergedHistory = mergeHistory(copyAndPasteHistory, mergedHistory);
171 // addNewNoteHistory<guid, 回数(ポイント)>
172 HashMap<String, Integer> addNewNoteHistory = conn.getHistoryTable().getBehaviorHistory("addNewNote", guid);
173 addWeight(addNewNoteHistory, Global.getAddNewNoteWeight());
174 mergedHistory = mergeHistory(addNewNoteHistory, mergedHistory);
176 // rensoItemClickHistory<guid, 回数(ポイント)>
177 HashMap<String, Integer> rensoItemClickHistory = conn.getHistoryTable().getBehaviorHistory("rensoItemClick", guid);
178 addWeight(rensoItemClickHistory, Global.getRensoItemClickWeight());
179 mergedHistory = mergeHistory(rensoItemClickHistory, mergedHistory);
181 // sameTagHistory<guid, 回数(ポイント)>
182 HashMap<String, Integer> sameTagHistory = conn.getHistoryTable().getBehaviorHistory("sameTag", guid);
183 addWeight(sameTagHistory, Global.getSameTagWeight());
184 mergedHistory = mergeHistory(sameTagHistory, mergedHistory);
186 // sameNotebookNoteHistory<guid, 回数(ポイント)>
187 HashMap<String, Integer> sameNotebookHistory = conn.getHistoryTable().getBehaviorHistory("sameNotebook", guid);
188 addWeight(sameNotebookHistory, Global.getSameNotebookWeight());
189 mergedHistory = mergeHistory(sameNotebookHistory, mergedHistory);
190 logger.log(logger.EXTREME, "Leaving RensoNoteList.calculateHistory");
194 private void addWeight(HashMap<String, Integer> history, int weight){
195 logger.log(logger.EXTREME, "Entering RensoNoteList.addWeight");
197 Set<String> keySet = history.keySet();
198 Iterator<String> hist_iterator = keySet.iterator();
199 while(hist_iterator.hasNext()){
200 String key = hist_iterator.next();
201 history.put(key, history.get(key) * weight);
204 logger.log(logger.EXTREME, "Leaving RensoNoteList.addWeight");
208 private void repaintRensoNoteList(boolean needClear) {
209 logger.log(logger.EXTREME, "Entering RensoNoteList.repaintRensoNoteList");
213 rensoNoteListItems.clear();
214 rensoNoteListTrueItems.clear();
217 if (!this.isEnabled()) {
221 // すべての関連ポイントの合計を取得(関連度のパーセント算出に利用)
223 for (int p : mergedHistory.values()) {
227 addRensoNoteList(mergedHistory);
229 logger.log(logger.EXTREME, "Leaving RensoNoteList.repaintRensoNoteList");
232 // 引数1と引数2をマージしたハッシュマップを返す
233 private HashMap<String, Integer> mergeHistory(HashMap<String, Integer> History1, HashMap<String, Integer> History2){
234 logger.log(logger.EXTREME, "Entering RensoNoteList.mergeHistory");
236 HashMap<String, Integer> mergedHistory = new HashMap<String, Integer>();
238 mergedHistory.putAll(History1);
240 Set<String> keySet = History2.keySet();
241 Iterator<String> hist2_iterator = keySet.iterator();
242 while(hist2_iterator.hasNext()){
243 String key = hist2_iterator.next();
244 if(mergedHistory.containsKey(key)){
245 mergedHistory.put(key, mergedHistory.get(key) + History2.get(key));
247 mergedHistory.put(key, History2.get(key));
251 logger.log(logger.EXTREME, "Leaving RensoNoteList.mergeHistory");
252 return mergedHistory;
255 // 連想ノートリストにハッシュマップのデータを追加
256 private void addRensoNoteList(HashMap<String, Integer> History){
257 logger.log(logger.EXTREME, "Entering RensoNoteList.addRensoNoteList");
259 String currentNoteGuid = new String(parent.getCurrentNoteGuid());
261 // スター付きノートとスター無しノートを分ける
262 HashMap<String, Integer> staredNotes = new HashMap<String, Integer>(); // スター付きノートのマップ
263 HashMap<String, Integer> normalNotes = new HashMap<String, Integer>(); // スター無しノートのマップ
264 for (String nextGuid: History.keySet()) {
265 int relationPoint = History.get(nextGuid);
266 boolean isStared = conn.getStaredTable().existNote(currentNoteGuid, nextGuid);
268 staredNotes.put(nextGuid, relationPoint);
270 normalNotes.put(nextGuid, relationPoint);
274 // 連想ノートリストアイテムの最大表示数まで繰り返す
275 for (int i = 0; i < Global.getRensoListItemMaximum(); i++) {
276 // スター付きノートがあれば先に処理する
277 HashMap<String, Integer> tmpMap = new HashMap<String, Integer>();
278 if (!staredNotes.isEmpty()) {
279 tmpMap = staredNotes;
280 }else if (!normalNotes.isEmpty()) {
281 tmpMap = normalNotes;
284 // 操作回数が多い順に取り出して連想ノートリストに追加する
285 if (!tmpMap.isEmpty()) {
287 String maxGuid = new String();
289 for (String nextGuid: tmpMap.keySet()) {
290 int relationPoint = tmpMap.get(nextGuid);
293 if (relationPoint > maxNum) {
294 maxNum = relationPoint;
299 // 次の最大値探索で邪魔なので最大値をHashMapから削除
300 tmpMap.remove(maxGuid);
302 // 関連度最大のノートがアクティブか確認
303 Note maxNote = conn.getNoteTable().getNote(maxGuid, true, false, false, false, true);
304 boolean isNoteActive = false;
305 if(maxNote != null) {
306 isNoteActive = maxNote.isActive();
309 // 存在していて、かつ関連度0でなければノート情報を取得して連想ノートリストに追加
310 if (isNoteActive && maxNum > 0) {
313 isStared = conn.getStaredTable().existNote(currentNoteGuid, maxGuid);
315 QListWidgetItem item = new QListWidgetItem();
316 RensoNoteListItem myItem = new RensoNoteListItem(maxNote, maxNum, isStared, allPointSum, conn, this);
317 item.setSizeHint(new QSize(0, 90));
319 this.setItemWidget(item, myItem);
320 rensoNoteListItems.put(item, maxGuid);
321 rensoNoteListTrueItems.add(myItem);
327 logger.log(logger.EXTREME, "Leaving RensoNoteList.addRensoNoteList");
330 // リストのアイテムから対象ノートのguidを取得
331 public String getNoteGuid(QListWidgetItem item) {
332 return rensoNoteListItems.get(item);
335 // 関連ノートリストの右クリックメニュー
337 public void contextMenuEvent(QContextMenuEvent event){
338 logger.log(logger.EXTREME, "Entering RensoNoteList.contextMenuEvent");
340 if (rensoNotePressedItemGuid == null || rensoNotePressedItemGuid.equals("")) {
344 // STAR, UNSTARがあれば、一度消す
345 List<QAction> menuActions = new ArrayList<QAction>(menu.actions());
346 if (menuActions.contains(starAction)) {
347 menu.removeAction(starAction);
349 if (menuActions.contains(unstarAction)) {
350 menu.removeAction(unstarAction);
353 // 対象アイテムがスター付きなら「UNSTAR」、スター無しなら「STAR」を追加
354 String currentNoteGuid = parent.getCurrentNoteGuid();
355 boolean isExist = conn.getStaredTable().existNote(currentNoteGuid, rensoNotePressedItemGuid);
357 menu.insertAction(excludeNoteAction, unstarAction);
359 menu.insertAction(excludeNoteAction, starAction);
363 menu.exec(event.globalPos());
365 rensoNotePressedItemGuid = null;
367 logger.log(logger.EXTREME, "Leaving RensoNoteList.contextMenuEvent");
370 // コンテキストメニューが表示されているかどうか
371 public boolean isContextMenuVisible() {
372 return menu.isVisible();
376 @SuppressWarnings("unused")
377 private void contextMenuHidden() {
378 for (int i = 0; i < rensoNoteListTrueItems.size(); i++) {
379 RensoNoteListItem item = rensoNoteListTrueItems.get(i);
380 item.setDefaultBackground();
384 // ユーザが連想ノートリストのアイテムを選択した時の処理
385 @SuppressWarnings("unused")
386 private void rensoNoteItemPressed(QListWidgetItem current) {
387 logger.log(logger.HIGH, "Entering RensoNoteList.rensoNoteItemPressed");
389 rensoNotePressedItemGuid = null;
391 if (QApplication.mouseButtons().isSet(MouseButton.RightButton)) {
392 rensoNotePressedItemGuid = getNoteGuid(current);
395 logger.log(logger.HIGH, "Leaving RensoNoteList.rensoNoteItemPressed");
398 // Evernoteの関連ノートの取得が完了
399 @SuppressWarnings("unused")
400 private void enRelatedNotesComplete() {
401 logger.log(logger.HIGH, "Entering RensoNoteList.enRelatedNotesComplete");
403 Pair<String, List<String>> enRelatedNoteGuidPair = enRelatedNotesRunner.getENRelatedNoteGuids(); // <元ノートguid, 関連ノートguidリスト>
405 if (enRelatedNoteGuidPair == null) {
409 String sourceGuid = enRelatedNoteGuidPair.getFirst();
410 List<String> enRelatedNoteGuids = enRelatedNoteGuidPair.getSecond();
413 if (sourceGuid != null && !sourceGuid.equals("") && enRelatedNoteGuids != null) { // Evernote関連ノートがnullでなければ
415 enRelatedNotesCache.put(sourceGuid, enRelatedNoteGuids);
417 if (!enRelatedNoteGuids.isEmpty()) { // Evernote関連ノートが存在していて
418 if (sourceGuid.equals(this.guid)) { // 取得したデータが今開いているノートの関連ノートなら
419 // mergedHistoryにEvernote関連ノートを追加してから再描画
420 addENRelatedNotes(enRelatedNoteGuids);
421 repaintRensoNoteList(true);
426 logger.log(logger.HIGH, "Leaving RensoNoteList.enRelatedNotesComplete");
429 // Evernote関連ノートの関連度情報をmergedHistoryに追加
430 private void addENRelatedNotes(List<String> relatedNoteGuids) {
431 logger.log(logger.EXTREME, "Entering RensoNoteList.addENRelatedNotes");
433 // Evernote関連ノート<guid, 関連ポイント>
434 HashMap<String, Integer> enRelatedNotes = new HashMap<String, Integer>();
436 for (String relatedGuid : relatedNoteGuids) {
437 enRelatedNotes.put(relatedGuid, Global.getENRelatedNotesWeight());
440 mergedHistory = mergeHistory(enRelatedNotes, mergedHistory);
442 logger.log(logger.EXTREME, "Leaving RensoNoteList.addENRelatedNotes");
445 // Evernoteの関連ノート取得スレッドを終了させる
446 public boolean stopThread() {
447 logger.log(logger.HIGH, "Entering RensoNoteList.stopThread");
449 if (enRelatedNotesRunner.addStop()) {
450 logger.log(logger.HIGH, "RensoNoteList.stopThread succeeded");
453 logger.log(logger.HIGH, "RensoNoteList.stopThread failed");
457 public QThread getEnRelatedNotesThread() {
458 return enRelatedNotesThread;
461 public String getGuid() {