blob: b62dc8a44f23848c4be009794da2c0115f3ab5ce [file] [log] [blame]
Alexandre Lision8521baa2015-03-13 11:08:00 -04001/*
Alexandre Lision4dfcafc2015-08-20 12:43:23 -04002 * Copyright (C) 2015 Savoir-faire Linux Inc.
Alexandre Lision8521baa2015-03-13 11:08:00 -04003 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Alexandre Lision8521baa2015-03-13 11:08:00 -040018 */
Alexandre Lision4ba18022015-04-23 12:17:40 -040019#import "HistoryVC.h"
Alexandre Lision5855b6a2015-02-03 11:31:05 -050020
Alexandre Lisione33b5ac2015-03-19 20:13:45 -040021#import <categorizedhistorymodel.h>
Alexandre Lision7837e4f2015-03-23 09:58:12 -040022#import <QSortFilterProxyModel>
Alexandre Lisionc5148052015-03-04 15:10:35 -050023#import <callmodel.h>
24#import <call.h>
Alexandre Lision2db8f472015-07-22 15:05:46 -040025#import <person.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050026#import <contactmethod.h>
Alexandre Lision66643432015-06-04 11:59:36 -040027#import <localhistorycollection.h>
Alexandre Lision5855b6a2015-02-03 11:31:05 -050028
Alexandre Lisionc5148052015-03-04 15:10:35 -050029#import "QNSTreeController.h"
Alexandre Lision2db8f472015-07-22 15:05:46 -040030#import "PersonLinkerVC.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040031#import "views/HoverTableRowView.h"
Alexandre Lisionc5148052015-03-04 15:10:35 -050032
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040033@interface HistoryVC() <NSPopoverDelegate, KeyboardShortcutDelegate, ContactLinkedDelegate> {
Alexandre Lision5855b6a2015-02-03 11:31:05 -050034
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040035 QNSTreeController *treeController;
36 IBOutlet RingOutlineView *historyView;
37 QSortFilterProxyModel *historyProxyModel;
38 NSPopover* addToContactPopover;
39}
Alexandre Lision2db8f472015-07-22 15:05:46 -040040
Alexandre Lisionc5148052015-03-04 15:10:35 -050041@end
Alexandre Lision5855b6a2015-02-03 11:31:05 -050042
Alexandre Lision4ba18022015-04-23 12:17:40 -040043@implementation HistoryVC
Alexandre Lision5855b6a2015-02-03 11:31:05 -050044
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040045// Tags for Views
46NSInteger const IMAGE_TAG = 100;
47NSInteger const DISPLAYNAME_TAG = 200;
48NSInteger const DETAILS_TAG = 300;
Alexandre Lision5855b6a2015-02-03 11:31:05 -050049
Alexandre Lision5855b6a2015-02-03 11:31:05 -050050- (void)awakeFromNib
51{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040052 NSLog(@"INIT HVC");
Alexandre Lision7837e4f2015-03-23 09:58:12 -040053 historyProxyModel = new QSortFilterProxyModel(CategorizedHistoryModel::instance());
54 historyProxyModel->setSourceModel(CategorizedHistoryModel::instance());
55 historyProxyModel->setSortRole(static_cast<int>(Call::Role::Date));
56 historyProxyModel->sort(0,Qt::DescendingOrder);
57 treeController = [[QNSTreeController alloc] initWithQModel:historyProxyModel];
Alexandre Lision5855b6a2015-02-03 11:31:05 -050058
59 [treeController setAvoidsEmptySelection:NO];
60 [treeController setChildrenKeyPath:@"children"];
61
Alexandre Lisionc5148052015-03-04 15:10:35 -050062 [historyView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
63 [historyView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
64 [historyView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
65 [historyView setTarget:self];
Alexandre Lision3b0bd332015-03-15 18:43:07 -040066 [historyView setDoubleAction:@selector(placeHistoryCall:)];
Alexandre Lision2db8f472015-07-22 15:05:46 -040067 [historyView setContextMenuDelegate:self];
68 [historyView setShortcutsDelegate:self];
Alexandre Lision5855b6a2015-02-03 11:31:05 -050069
Alexandre Lision66643432015-06-04 11:59:36 -040070 CategorizedHistoryModel::instance()->addCollection<LocalHistoryCollection>(LoadOptions::FORCE_ENABLED);
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040071
72 QObject::connect(CallModel::instance(),
73 &CategorizedHistoryModel::dataChanged,
74 [=](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
75 [historyView reloadDataForRowIndexes:
76 [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(topLeft.row(), bottomRight.row() + 1)]
77 columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, historyView.tableColumns.count)]];
78 });
Alexandre Lision5855b6a2015-02-03 11:31:05 -050079}
80
Alexandre Lision54b0fae2015-08-04 15:19:01 -040081- (void) dealloc
82{
83 delete historyProxyModel;
84}
85
Alexandre Lision3b0bd332015-03-15 18:43:07 -040086- (void)placeHistoryCall:(id)sender
Alexandre Lisionc5148052015-03-04 15:10:35 -050087{
88 if([[treeController selectedNodes] count] > 0) {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040089 auto item = [treeController selectedNodes][0];
90 QModelIndex qIdx = [treeController toQIdx:item];
Alexandre Lision2db8f472015-07-22 15:05:46 -040091 if (!qIdx.parent().isValid()) {
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040092 if ([historyView isItemExpanded:item]) {
93 [[historyView animator] collapseItem:item];
94 } else
95 [[historyView animator] expandItem:item];
Alexandre Lision2db8f472015-07-22 15:05:46 -040096 return;
97 }
Alexandre Lision7837e4f2015-03-23 09:58:12 -040098 QVariant var = historyProxyModel->data(qIdx, (int)Call::Role::ContactMethod);
Alexandre Lisionc5148052015-03-04 15:10:35 -050099 ContactMethod* m = qvariant_cast<ContactMethod*>(var);
100 if(m){
101 Call* c = CallModel::instance()->dialingCall();
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400102 c->setPeerContactMethod(m);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500103 c << Call::Action::ACCEPT;
104 }
105 }
106}
107
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500108#pragma mark - NSOutlineViewDelegate methods
109
110// -------------------------------------------------------------------------------
111// shouldSelectItem:item
112// -------------------------------------------------------------------------------
113- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
114{
115 return YES;
116}
117
118// -------------------------------------------------------------------------------
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500119// shouldEditTableColumn:tableColumn:item
120//
121// Decide to allow the edit of the given outline view "item".
122// -------------------------------------------------------------------------------
123- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
124{
125 return NO;
126}
127
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400128- (NSImage*) image:(NSImage*) img withTintedWithColor:(NSColor *)tint
129{
130 if (tint) {
131 [img lockFocus];
132 [tint set];
133 NSRect imageRect = {NSZeroPoint, [img size]};
134 NSRectFillUsingOperation(imageRect, NSCompositeSourceAtop);
135 [img unlockFocus];
136 }
137 return img;
138}
139
140/* View Based OutlineView: See the delegate method -tableView:viewForTableColumn:row: in NSTableView.
141 */
142- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500143{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500144 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500145
Alexandre Lision4e280d62015-09-09 15:56:30 -0400146 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400147 if(!qIdx.parent().isValid()) {
148 result = [outlineView makeViewWithIdentifier:@"CategoryCell" owner:outlineView];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400149
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400150 } else {
151 result = [outlineView makeViewWithIdentifier:@"HistoryCell" owner:outlineView];
152 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
153
154 if (qvariant_cast<Call::Direction>(qIdx.data((int)Call::Role::Direction)) == Call::Direction::INCOMING) {
155 if (qvariant_cast<Boolean>(qIdx.data((int) Call::Role::Missed))) {
156 [photoView setImage:[self image:[NSImage imageNamed:@"ic_call_missed"] withTintedWithColor:[NSColor redColor]]];
157 } else {
158 [photoView setImage:[self image:[NSImage imageNamed:@"ic_call_received"]
159 withTintedWithColor:[NSColor colorWithCalibratedRed:116/255.0 green:179/255.0 blue:93/255.0 alpha:1.0]]];
160 }
161 } else {
162 if (qvariant_cast<Boolean>(qIdx.data((int) Call::Role::Missed))) {
163 [photoView setImage:[self image:[NSImage imageNamed:@"ic_call_missed"] withTintedWithColor:[NSColor redColor]]];
164 } else {
165 [photoView setImage:[self image:[NSImage imageNamed:@"ic_call_made"]
166 withTintedWithColor:[NSColor colorWithCalibratedRed:116/255.0 green:179/255.0 blue:93/255.0 alpha:1.0]]];
167 }
168 }
169
170 NSTextField* details = [result viewWithTag:DETAILS_TAG];
171 [details setStringValue:qIdx.data((int)Call::Role::FormattedDate).toString().toNSString()];
172 }
173
174 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
175 [displayName setStringValue:qIdx.data(Qt::DisplayRole).toString().toNSString()];
176
177 return result;
178}
179
180- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
181{
182 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
183 if(!qIdx.parent().isValid()) {
184 return 35.0;
185 } else {
186 return 48.0;
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500187 }
188}
189
Alexandre Lision4e280d62015-09-09 15:56:30 -0400190/* View Based OutlineView: See the delegate method -tableView:rowViewForRow: in NSTableView.
191 */
192- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500193{
Alexandre Lision4e280d62015-09-09 15:56:30 -0400194 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
195
196 HoverTableRowView* result = [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
197 if(!qIdx.parent().isValid()) {
198 [result setHighlightable:NO];
199 } else
200 [result setHighlightable:YES];
201
202 return result;
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500203}
204
Alexandre Lision2db8f472015-07-22 15:05:46 -0400205#pragma mark - ContextMenuDelegate
206
207- (NSMenu*) contextualMenuForIndex:(NSIndexPath*) path
208{
209 if([[treeController selectedNodes] count] > 0) {
210 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
211 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
212 if (qIdx.parent().isValid() && var.isValid()) {
213 if (auto call = var.value<Call *>()) {
214 auto contactmethod = call->peerContactMethod();
215 if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
216 NSMenu *theMenu = [[NSMenu alloc]
217 initWithTitle:@""];
Alexandre Lision922380d2015-09-15 10:25:17 -0400218 [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
Alexandre Lision2db8f472015-07-22 15:05:46 -0400219 action:@selector(addToContact)
220 keyEquivalent:@"a"
221 atIndex:0];
222 return theMenu;
223 }
224 }
225 }
226 }
227 return nil;
228}
229
230- (void) addToContact
231{
232 ContactMethod* contactmethod = nullptr;
233 if([[treeController selectedNodes] count] > 0) {
234 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
235 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
236 if (qIdx.parent().isValid() && var.isValid()) {
237 if (auto call = var.value<Call *>()) {
238 contactmethod = call->peerContactMethod();
239 }
240 }
241 }
242
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400243 if (addToContactPopover != nullptr) {
244 [addToContactPopover performClose:self];
245 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400246 } else if (contactmethod) {
Alexandre Lisionae840f22015-08-04 15:02:16 -0400247 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400248 [editorVC setMethodToLink:contactmethod];
249 [editorVC setContactLinkedDelegate:self];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400250 addToContactPopover = [[NSPopover alloc] init];
251 [addToContactPopover setContentSize:editorVC.view.frame.size];
252 [addToContactPopover setContentViewController:editorVC];
253 [addToContactPopover setAnimates:YES];
254 [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
255 [addToContactPopover setDelegate:self];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400256
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400257 [addToContactPopover showRelativeToRect:[historyView frameOfOutlineCellAtRow:[historyView selectedRow]] ofView:historyView preferredEdge:NSMaxXEdge];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400258 }
259}
260
261#pragma mark - NSPopOverDelegate
262
263- (void)popoverDidClose:(NSNotification *)notification
264{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400265 if (addToContactPopover != nullptr) {
266 [addToContactPopover performClose:self];
267 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400268 }
269}
270
271#pragma mark - ContactLinkedDelegate
272
273- (void)contactLinked
274{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400275 if (addToContactPopover != nullptr) {
276 [addToContactPopover performClose:self];
277 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400278 }
279}
280
281#pragma mark - KeyboardShortcutDelegate
282
283- (void) onAddShortcut
284{
285 if([[treeController selectedNodes] count] > 0) {
286 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
287 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
288 if (qIdx.parent().isValid() && var.isValid()) {
289 if (auto call = var.value<Call *>()) {
290 auto contactmethod = call->peerContactMethod();
291 if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
292 [self addToContact];
293 }
294 }
295 }
296 }
297}
298
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500299@end