OSDN Git Service

クリーンアップ
[neighbornote/NeighborNote.git] / src / cx / fbn / nevernote / gui / RensoNoteList.java
1 /*
2  * This file is part of NeighborNote
3  * Copyright 2013 Yuki Takahashi
4  * 
5  * This file may be licensed under the terms of of the
6  * GNU General Public License Version 2 (the ``GPL'').
7  *
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.
12  *
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.
17  *
18 */
19
20 package cx.fbn.nevernote.gui;
21
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Set;
27
28 import com.evernote.edam.type.Note;
29 import com.trolltech.qt.QThread;
30 import com.trolltech.qt.core.QSize;
31 import com.trolltech.qt.core.Qt.MouseButton;
32 import com.trolltech.qt.gui.QAction;
33 import com.trolltech.qt.gui.QApplication;
34 import com.trolltech.qt.gui.QContextMenuEvent;
35 import com.trolltech.qt.gui.QListWidget;
36 import com.trolltech.qt.gui.QListWidgetItem;
37 import com.trolltech.qt.gui.QMenu;
38
39 import cx.fbn.nevernote.Global;
40 import cx.fbn.nevernote.NeverNote;
41 import cx.fbn.nevernote.sql.DatabaseConnection;
42 import cx.fbn.nevernote.threads.ENRelatedNotesRunner;
43 import cx.fbn.nevernote.threads.SyncRunner;
44 import cx.fbn.nevernote.utilities.ApplicationLogger;
45 import cx.fbn.nevernote.utilities.Pair;
46
47 public class RensoNoteList extends QListWidget {
48         private final DatabaseConnection conn;
49         private final ApplicationLogger logger;
50         private final HashMap<QListWidgetItem, String> rensoNoteListItems;
51         private final List<RensoNoteListItem> rensoNoteListTrueItems;
52         private String rensoNotePressedItemGuid;
53         private final QAction openNewTabAction;
54         private final QAction starAction;
55         private final QAction unstarAction;
56         private final QAction excludeNoteAction;
57         private final NeverNote parent;
58         private final QMenu menu;
59         private HashMap<String, Integer> mergedHistory;                         // マージされた操作履歴
60         private final SyncRunner syncRunner;
61         private final ENRelatedNotesRunner enRelatedNotesRunner;
62         private final QThread enRelatedNotesThread;
63         private final HashMap<String, List<String>> enRelatedNotesCache;        // Evernote関連ノートのキャッシュ<guid, 関連ノートリスト>
64         private String guid;
65         private int allPointSum;
66
67         public RensoNoteList(DatabaseConnection c, NeverNote p, SyncRunner syncRunner, ApplicationLogger logger) {
68                 this.logger = logger;
69                 this.logger.log(this.logger.HIGH, "Setting up rensoNoteList");
70                 allPointSum = 0;
71
72                 this.conn = c;
73                 this.parent = p;
74                 this.syncRunner = syncRunner;
75                 
76                 this.guid = new String();
77                 mergedHistory = new HashMap<String, Integer>();
78                 enRelatedNotesCache = new HashMap<String, List<String>>();
79                 this.enRelatedNotesRunner = new ENRelatedNotesRunner(this.syncRunner, this.logger);
80                 this.enRelatedNotesRunner.enRelatedNotesSignal.getENRelatedNotesFinished.connect(this, "enRelatedNotesComplete()");
81                 this.enRelatedNotesRunner.limitSignal.rateLimitReached.connect(parent, "informRateLimit(Integer)");
82                 this.enRelatedNotesThread = new QThread(enRelatedNotesRunner, "ENRelatedNotes Thread");
83                 this.getEnRelatedNotesThread().start();
84                 
85                 rensoNoteListItems = new HashMap<QListWidgetItem, String>();
86                 rensoNoteListTrueItems = new ArrayList<RensoNoteListItem>();
87                 
88                 this.itemPressed.connect(this, "rensoNoteItemPressed(QListWidgetItem)");
89                 
90                 // コンテキストメニュー作成
91                 menu = new QMenu(this);
92                 // 新しいタブで開くアクション生成
93                 openNewTabAction = new QAction(tr("Open in New Tab"), this);
94                 openNewTabAction.setToolTip(tr("Open this note in new tab"));
95                 openNewTabAction.triggered.connect(parent, "openNewTabFromRNL()");
96                 // スターをつけるアクション生成
97                 starAction = new QAction(tr("Add Star"), this);
98                 starAction.setToolTip(tr("Add Star to this item"));
99                 starAction.triggered.connect(parent, "starNote()");
100                 // スターを外すアクション生成
101                 unstarAction = new QAction(tr("Remove Star"), this);
102                 unstarAction.setToolTip(tr("Remove Star from this item"));
103                 unstarAction.triggered.connect(parent, "unstarNote()");
104                 // このノートを除外するアクション生成
105                 excludeNoteAction = new QAction(tr("Exclude"), this);
106                 excludeNoteAction.setToolTip(tr("Exclude this note from RensoNoteList"));
107                 excludeNoteAction.triggered.connect(parent, "excludeNote()");
108                 // コンテキストメニューに登録
109                 menu.addAction(openNewTabAction);
110                 menu.addAction(excludeNoteAction);
111                 menu.aboutToHide.connect(this, "contextMenuHidden()");
112                 
113                 this.logger.log(this.logger.HIGH, "rensoNoteList setup complete");
114         }
115         
116         // オーバーロード
117         // 現在開いているノートの連想ノートリストをリフレッシュ
118         public void refreshRensoNoteList() {
119                 refreshRensoNoteList(guid);
120         }
121
122         // 連想ノートリストをリフレッシュ
123         public void refreshRensoNoteList(String guid) {
124                 logger.log(logger.HIGH, "Entering RensoNoteList.refreshRensoNoteList guid = " + guid);
125
126                 this.clear();
127                 rensoNoteListItems.clear();
128                 rensoNoteListTrueItems.clear();
129                 mergedHistory = new HashMap<String, Integer>();
130
131                 if (!this.isEnabled()) {
132                         return;
133                 }
134                 if (guid == null || guid.equals("")) {
135                         return;
136                 }
137                 
138                 this.guid = guid;
139                 // すでにEvernote関連ノートがキャッシュされているか確認
140                 boolean isCached;
141                 isCached = enRelatedNotesCache.containsKey(guid);
142                 if (!isCached) {        // キャッシュ無し
143                         // Evernoteの関連ノートを別スレッドで取得させる
144                         enRelatedNotesRunner.addGuid(guid);
145                 } else {                        // キャッシュ有り
146                         List<String> relatedNoteGuids = enRelatedNotesCache.get(guid);
147                         addENRelatedNotes(relatedNoteGuids);
148                 }
149                 
150                 calculateHistory(guid);
151                 repaintRensoNoteList(false);
152
153                 logger.log(logger.HIGH, "Leaving RensoNoteList.refreshRensoNoteList");
154         }
155         
156         // 操作履歴をデータベースから取得してノートごとの関連度を算出、その後mergedHistoryに追加
157         private void calculateHistory(String guid) {
158                 logger.log(logger.EXTREME, "Entering RensoNoteList.calculateHistory guid = " + guid);
159                 
160                 // browseHistory<guid, 回数(ポイント)>
161                 HashMap<String, Integer> browseHistory = conn.getHistoryTable().getBehaviorHistory("browse", guid);
162                 addWeight(browseHistory, Global.getBrowseWeight());
163                 mergedHistory = mergeHistory(browseHistory, mergedHistory);
164                 
165                 // copy&pasteHistory<guid, 回数(ポイント)>
166                 HashMap<String, Integer> copyAndPasteHistory = conn.getHistoryTable().getBehaviorHistory("copy & paste", guid);
167                 addWeight(copyAndPasteHistory, Global.getCopyPasteWeight());
168                 mergedHistory = mergeHistory(copyAndPasteHistory, mergedHistory);
169                 
170                 // addNewNoteHistory<guid, 回数(ポイント)>
171                 HashMap<String, Integer> addNewNoteHistory = conn.getHistoryTable().getBehaviorHistory("addNewNote", guid);
172                 addWeight(addNewNoteHistory, Global.getAddNewNoteWeight());
173                 mergedHistory = mergeHistory(addNewNoteHistory, mergedHistory);
174                 
175                 // rensoItemClickHistory<guid, 回数(ポイント)>
176                 HashMap<String, Integer> rensoItemClickHistory = conn.getHistoryTable().getBehaviorHistory("rensoItemClick", guid);
177                 addWeight(rensoItemClickHistory, Global.getRensoItemClickWeight());
178                 mergedHistory = mergeHistory(rensoItemClickHistory, mergedHistory);
179                 
180                 // sameTagHistory<guid, 回数(ポイント)>
181                 HashMap<String, Integer> sameTagHistory = conn.getHistoryTable().getBehaviorHistory("sameTag", guid);
182                 addWeight(sameTagHistory, Global.getSameTagWeight());
183                 mergedHistory = mergeHistory(sameTagHistory, mergedHistory);
184                 
185                 // sameNotebookNoteHistory<guid, 回数(ポイント)>
186                 HashMap<String, Integer> sameNotebookHistory = conn.getHistoryTable().getBehaviorHistory("sameNotebook", guid);
187                 addWeight(sameNotebookHistory, Global.getSameNotebookWeight());
188                 mergedHistory = mergeHistory(sameNotebookHistory, mergedHistory);
189                 logger.log(logger.EXTREME, "Leaving RensoNoteList.calculateHistory");
190         }
191         
192         // 操作回数に重み付けする
193         private void addWeight(HashMap<String, Integer> history, int weight){
194                 logger.log(logger.EXTREME, "Entering RensoNoteList.addWeight");
195                 
196                 Set<String> keySet = history.keySet();
197                 Iterator<String> hist_iterator = keySet.iterator();
198                 while(hist_iterator.hasNext()){
199                         String key = hist_iterator.next();
200                         history.put(key, history.get(key) * weight);
201                 }
202                 
203                 logger.log(logger.EXTREME, "Leaving RensoNoteList.addWeight");
204         }
205         
206         // 連想ノートリストを再描画
207         private void repaintRensoNoteList(boolean needClear) {
208                 logger.log(logger.EXTREME, "Entering RensoNoteList.repaintRensoNoteList");
209                 
210                 if (needClear) {
211                         this.clear();
212                         rensoNoteListItems.clear();
213                         rensoNoteListTrueItems.clear();
214                 }
215                 
216                 if (!this.isEnabled()) {
217                         return;
218                 }
219                 
220                 // すべての関連ポイントの合計を取得(関連度のパーセント算出に利用)
221                 allPointSum = 0;
222                 for (int p : mergedHistory.values()) {
223                         allPointSum += p;
224                 }
225                 
226                 addRensoNoteList(mergedHistory);
227                 
228                 logger.log(logger.EXTREME, "Leaving RensoNoteList.repaintRensoNoteList");
229         }
230         
231         // 引数1と引数2をマージしたハッシュマップを返す
232         private HashMap<String, Integer> mergeHistory(HashMap<String, Integer> History1, HashMap<String, Integer> History2){
233                 logger.log(logger.EXTREME, "Entering RensoNoteList.mergeHistory");
234                 
235                 HashMap<String, Integer> mergedHistory = new HashMap<String, Integer>();
236                 
237                 mergedHistory.putAll(History1);
238                 
239                 Set<String> keySet = History2.keySet();
240                 Iterator<String> hist2_iterator = keySet.iterator();
241                 while(hist2_iterator.hasNext()){
242                         String key = hist2_iterator.next();
243                         if(mergedHistory.containsKey(key)){
244                                 mergedHistory.put(key, mergedHistory.get(key) + History2.get(key));
245                         }else {
246                                 mergedHistory.put(key, History2.get(key));
247                         }
248                 }
249                 
250                 logger.log(logger.EXTREME, "Leaving RensoNoteList.mergeHistory");
251                 return mergedHistory;
252         }
253         
254         // 連想ノートリストにハッシュマップのデータを追加
255         private void addRensoNoteList(HashMap<String, Integer> History){
256                 logger.log(logger.EXTREME, "Entering RensoNoteList.addRensoNoteList");
257                 
258                 String currentNoteGuid = new String(parent.getCurrentNoteGuid());
259                 
260                 // スター付きノートとスター無しノートを分ける
261                 HashMap<String, Integer> staredNotes = new HashMap<String, Integer>();  // スター付きノートのマップ
262                 HashMap<String, Integer> normalNotes = new HashMap<String, Integer>();  // スター無しノートのマップ
263                 for (String nextGuid: History.keySet()) {
264                         int relationPoint = History.get(nextGuid);
265                         boolean isStared = conn.getStaredTable().existNote(currentNoteGuid, nextGuid);
266                         if (isStared) {
267                                 staredNotes.put(nextGuid, relationPoint);
268                         } else {
269                                 normalNotes.put(nextGuid, relationPoint);
270                         }
271                 }
272                 
273                 // 連想ノートリストアイテムの最大表示数まで繰り返す
274                 for (int i = 0; i < Global.getRensoListItemMaximum(); i++) {
275                         // スター付きノートがあれば先に処理する
276                         HashMap<String, Integer> tmpMap = new HashMap<String, Integer>();
277                         if (!staredNotes.isEmpty()) {
278                                 tmpMap = staredNotes;
279                         }else if (!normalNotes.isEmpty()) {
280                                 tmpMap = normalNotes;
281                         }
282                         
283                         // 操作回数が多い順に取り出して連想ノートリストに追加する
284                         if (!tmpMap.isEmpty()) {
285                                 int maxNum = -1;
286                                 String maxGuid = new String();
287                                 
288                                 for (String nextGuid: tmpMap.keySet()) {
289                                         int relationPoint = tmpMap.get(nextGuid);
290                                         
291                                         // 最大ノート探索する
292                                         if (relationPoint > maxNum) {
293                                                 maxNum = relationPoint;
294                                                 maxGuid = nextGuid;
295                                         }
296                                 }
297                                 
298                                 // 次の最大値探索で邪魔なので最大値をHashMapから削除
299                                 tmpMap.remove(maxGuid);
300         
301                                 // 関連度最大のノートがアクティブか確認
302                                 Note maxNote = conn.getNoteTable().getNote(maxGuid, true, false, false, false, true);
303                                 boolean isNoteActive = false;
304                                 if(maxNote != null) {
305                                         isNoteActive = maxNote.isActive();
306                                 }
307                                 
308                                 // 存在していて、かつ関連度0でなければノート情報を取得して連想ノートリストに追加
309                                 if (isNoteActive && maxNum > 0) {
310                                         // スター付きか確認
311                                         boolean isStared;
312                                         isStared = conn.getStaredTable().existNote(currentNoteGuid, maxGuid);
313                                         
314                                         QListWidgetItem item = new QListWidgetItem();
315                                         RensoNoteListItem myItem = new RensoNoteListItem(maxNote, maxNum, isStared, allPointSum, conn, this);
316                                         item.setSizeHint(new QSize(0, 90));
317                                         this.addItem(item);
318                                         this.setItemWidget(item, myItem);
319                                         rensoNoteListItems.put(item, maxGuid);
320                                         rensoNoteListTrueItems.add(myItem);
321                                 } else {
322                                         break;
323                                 }
324                         }
325                 }
326                 logger.log(logger.EXTREME, "Leaving RensoNoteList.addRensoNoteList");
327         }
328
329         // リストのアイテムから対象ノートのguidを取得
330         public String getNoteGuid(QListWidgetItem item) {
331                 return rensoNoteListItems.get(item);
332         }
333         
334         // 関連ノートリストの右クリックメニュー
335         @Override
336         public void contextMenuEvent(QContextMenuEvent event){
337                 logger.log(logger.EXTREME, "Entering RensoNoteList.contextMenuEvent");
338                 
339                 if (rensoNotePressedItemGuid == null || rensoNotePressedItemGuid.equals("")) {
340                         return;
341                 }
342                 
343                 // STAR, UNSTARがあれば、一度消す
344                 List<QAction> menuActions = new ArrayList<QAction>(menu.actions());
345                 if (menuActions.contains(starAction)) {
346                         menu.removeAction(starAction);
347                 }
348                 if (menuActions.contains(unstarAction)) {
349                         menu.removeAction(unstarAction);
350                 }
351                 
352                 // 対象アイテムがスター付きなら「UNSTAR」、スター無しなら「STAR」を追加
353                 String currentNoteGuid = parent.getCurrentNoteGuid();
354                 boolean isExist = conn.getStaredTable().existNote(currentNoteGuid, rensoNotePressedItemGuid);
355                 if (isExist) {
356                         menu.insertAction(excludeNoteAction, unstarAction);
357                 } else {
358                         menu.insertAction(excludeNoteAction, starAction);
359                 }
360                 
361                 // コンテキストメニューを表示
362                 menu.exec(event.globalPos());
363                 
364                 rensoNotePressedItemGuid = null;
365                 
366                 logger.log(logger.EXTREME, "Leaving RensoNoteList.contextMenuEvent");
367         }
368         
369         // コンテキストメニューが表示されているかどうか
370         public boolean isContextMenuVisible() {
371                 return menu.isVisible();
372         }
373         
374         // コンテキストメニューが閉じられた時
375         @SuppressWarnings("unused")
376         private void contextMenuHidden() {
377                 for (int i = 0; i < rensoNoteListTrueItems.size(); i++) {
378                         RensoNoteListItem item = rensoNoteListTrueItems.get(i);
379                         item.setDefaultBackground();
380                 }
381         }
382         
383         // ユーザが連想ノートリストのアイテムを選択した時の処理
384         @SuppressWarnings("unused")
385         private void rensoNoteItemPressed(QListWidgetItem current) {
386                 logger.log(logger.HIGH, "Entering RensoNoteList.rensoNoteItemPressed");
387                 
388                 rensoNotePressedItemGuid = null;
389                 // 右クリックだったときの処理
390                 if (QApplication.mouseButtons().isSet(MouseButton.RightButton)) {
391                         rensoNotePressedItemGuid = getNoteGuid(current);
392                 }
393                 
394                 logger.log(logger.HIGH, "Leaving RensoNoteList.rensoNoteItemPressed");
395         }
396         
397         // Evernoteの関連ノートの取得が完了
398         @SuppressWarnings("unused")
399         private void enRelatedNotesComplete() {
400                 logger.log(logger.HIGH, "Entering RensoNoteList.enRelatedNotesComplete");
401                 
402                 Pair<String, List<String>> enRelatedNoteGuidPair = enRelatedNotesRunner.getENRelatedNoteGuids();        // <元ノートguid, 関連ノートguidリスト>
403                 
404                 if (enRelatedNoteGuidPair == null) {
405                         return;
406                 }
407                 
408                 String sourceGuid = enRelatedNoteGuidPair.getFirst();
409                 List<String> enRelatedNoteGuids = enRelatedNoteGuidPair.getSecond();
410                 
411                 
412                 if (sourceGuid != null && !sourceGuid.equals("") && enRelatedNoteGuids != null) {       // Evernote関連ノートがnullでなければ
413                         // まずキャッシュに追加
414                         enRelatedNotesCache.put(sourceGuid, enRelatedNoteGuids);
415                         
416                         if (!enRelatedNoteGuids.isEmpty()) {    // Evernote関連ノートが存在していて
417                                 if (sourceGuid.equals(this.guid)) {     // 取得したデータが今開いているノートの関連ノートなら
418                                         // mergedHistoryにEvernote関連ノートを追加してから再描画
419                                         addENRelatedNotes(enRelatedNoteGuids);
420                                         repaintRensoNoteList(true);
421                                 }
422                         }
423                 }
424                 
425                 logger.log(logger.HIGH, "Leaving RensoNoteList.enRelatedNotesComplete");
426         }
427         
428         // Evernote関連ノートの関連度情報をmergedHistoryに追加
429         private void addENRelatedNotes(List<String> relatedNoteGuids) {
430                 logger.log(logger.EXTREME, "Entering RensoNoteList.addENRelatedNotes");
431                 
432                 // Evernote関連ノート<guid, 関連ポイント>
433                 HashMap<String, Integer> enRelatedNotes = new HashMap<String, Integer>();
434                 
435                 for (String relatedGuid : relatedNoteGuids) {
436                         enRelatedNotes.put(relatedGuid, Global.getENRelatedNotesWeight());
437                 }
438                 
439                 mergedHistory = mergeHistory(enRelatedNotes, mergedHistory);
440                 
441                 logger.log(logger.EXTREME, "Leaving RensoNoteList.addENRelatedNotes");
442         }
443         
444         // Evernoteの関連ノート取得スレッドを終了させる
445         public boolean stopThread() {
446                 logger.log(logger.HIGH, "Entering RensoNoteList.stopThread");
447                 
448                 if (enRelatedNotesRunner.addStop()) {
449                         logger.log(logger.HIGH, "RensoNoteList.stopThread succeeded");
450                         return true;
451                 }
452                 logger.log(logger.HIGH, "RensoNoteList.stopThread failed");
453                 return false;
454         }
455
456         public QThread getEnRelatedNotesThread() {
457                 return enRelatedNotesThread;
458         }
459         
460         public String getGuid() {
461                 return guid;
462         }
463 }