blob: 3b17422be8edc55c7a7600f0bb40f0151327fc18 [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 Lision34e738e2015-09-24 14:13:23 -040021//Qt
Alexandre Lision7837e4f2015-03-23 09:58:12 -040022#import <QSortFilterProxyModel>
Alexandre Lision34e738e2015-09-24 14:13:23 -040023
24//LRC
25#import <categorizedhistorymodel.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050026#import <callmodel.h>
27#import <call.h>
Alexandre Lision2db8f472015-07-22 15:05:46 -040028#import <person.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050029#import <contactmethod.h>
Alexandre Lision5855b6a2015-02-03 11:31:05 -050030
Alexandre Lisionc5148052015-03-04 15:10:35 -050031#import "QNSTreeController.h"
Alexandre Lision2db8f472015-07-22 15:05:46 -040032#import "PersonLinkerVC.h"
Alexandre Lision4e280d62015-09-09 15:56:30 -040033#import "views/HoverTableRowView.h"
Alexandre Lisionc5148052015-03-04 15:10:35 -050034
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040035@interface HistoryVC() <NSPopoverDelegate, KeyboardShortcutDelegate, ContactLinkedDelegate> {
Alexandre Lision5855b6a2015-02-03 11:31:05 -050036
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040037 QNSTreeController *treeController;
38 IBOutlet RingOutlineView *historyView;
39 QSortFilterProxyModel *historyProxyModel;
40 NSPopover* addToContactPopover;
41}
Alexandre Lision2db8f472015-07-22 15:05:46 -040042
Alexandre Lisionc5148052015-03-04 15:10:35 -050043@end
Alexandre Lision5855b6a2015-02-03 11:31:05 -050044
Alexandre Lision4ba18022015-04-23 12:17:40 -040045@implementation HistoryVC
Alexandre Lision5855b6a2015-02-03 11:31:05 -050046
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040047// Tags for Views
48NSInteger const IMAGE_TAG = 100;
49NSInteger const DISPLAYNAME_TAG = 200;
50NSInteger const DETAILS_TAG = 300;
Alexandre Lision5855b6a2015-02-03 11:31:05 -050051
Alexandre Lision5855b6a2015-02-03 11:31:05 -050052- (void)awakeFromNib
53{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040054 NSLog(@"INIT HVC");
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040055 historyProxyModel = new QSortFilterProxyModel(&CategorizedHistoryModel::instance());
56 historyProxyModel->setSourceModel(&CategorizedHistoryModel::instance());
Alexandre Lision7837e4f2015-03-23 09:58:12 -040057 historyProxyModel->setSortRole(static_cast<int>(Call::Role::Date));
58 historyProxyModel->sort(0,Qt::DescendingOrder);
59 treeController = [[QNSTreeController alloc] initWithQModel:historyProxyModel];
Alexandre Lision5855b6a2015-02-03 11:31:05 -050060
61 [treeController setAvoidsEmptySelection:NO];
62 [treeController setChildrenKeyPath:@"children"];
63
Alexandre Lisionc5148052015-03-04 15:10:35 -050064 [historyView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
65 [historyView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
66 [historyView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];
67 [historyView setTarget:self];
Alexandre Lision3b0bd332015-03-15 18:43:07 -040068 [historyView setDoubleAction:@selector(placeHistoryCall:)];
Alexandre Lision2db8f472015-07-22 15:05:46 -040069 [historyView setContextMenuDelegate:self];
70 [historyView setShortcutsDelegate:self];
Alexandre Lision5855b6a2015-02-03 11:31:05 -050071
Alexandre Lisiond3aa3ad2015-10-23 14:28:41 -040072 QObject::connect(&CallModel::instance(),
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040073 &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){
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500101 auto 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;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500104 CallModel::instance().selectCall(c);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500105 }
106 }
107}
108
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500109#pragma mark - NSOutlineViewDelegate methods
110
111// -------------------------------------------------------------------------------
112// shouldSelectItem:item
113// -------------------------------------------------------------------------------
114- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item;
115{
116 return YES;
117}
118
119// -------------------------------------------------------------------------------
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500120// shouldEditTableColumn:tableColumn:item
121//
122// Decide to allow the edit of the given outline view "item".
123// -------------------------------------------------------------------------------
124- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
125{
126 return NO;
127}
128
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400129- (NSImage*) image:(NSImage*) img withTintedWithColor:(NSColor *)tint
130{
131 if (tint) {
132 [img lockFocus];
133 [tint set];
134 NSRect imageRect = {NSZeroPoint, [img size]};
135 NSRectFillUsingOperation(imageRect, NSCompositeSourceAtop);
136 [img unlockFocus];
137 }
138 return img;
139}
140
141/* View Based OutlineView: See the delegate method -tableView:viewForTableColumn:row: in NSTableView.
142 */
143- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500144{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500145 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500146
Alexandre Lision4e280d62015-09-09 15:56:30 -0400147 NSTableCellView* result;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400148 if(!qIdx.parent().isValid()) {
149 result = [outlineView makeViewWithIdentifier:@"CategoryCell" owner:outlineView];
Alexandre Lision4e280d62015-09-09 15:56:30 -0400150
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400151 } else {
152 result = [outlineView makeViewWithIdentifier:@"HistoryCell" owner:outlineView];
153 NSImageView* photoView = [result viewWithTag:IMAGE_TAG];
154
155 if (qvariant_cast<Call::Direction>(qIdx.data((int)Call::Role::Direction)) == Call::Direction::INCOMING) {
156 if (qvariant_cast<Boolean>(qIdx.data((int) Call::Role::Missed))) {
157 [photoView setImage:[self image:[NSImage imageNamed:@"ic_call_missed"] withTintedWithColor:[NSColor redColor]]];
158 } else {
159 [photoView setImage:[self image:[NSImage imageNamed:@"ic_call_received"]
160 withTintedWithColor:[NSColor colorWithCalibratedRed:116/255.0 green:179/255.0 blue:93/255.0 alpha:1.0]]];
161 }
162 } else {
163 if (qvariant_cast<Boolean>(qIdx.data((int) Call::Role::Missed))) {
164 [photoView setImage:[self image:[NSImage imageNamed:@"ic_call_missed"] withTintedWithColor:[NSColor redColor]]];
165 } else {
166 [photoView setImage:[self image:[NSImage imageNamed:@"ic_call_made"]
167 withTintedWithColor:[NSColor colorWithCalibratedRed:116/255.0 green:179/255.0 blue:93/255.0 alpha:1.0]]];
168 }
169 }
170
171 NSTextField* details = [result viewWithTag:DETAILS_TAG];
172 [details setStringValue:qIdx.data((int)Call::Role::FormattedDate).toString().toNSString()];
173 }
174
175 NSTextField* displayName = [result viewWithTag:DISPLAYNAME_TAG];
176 [displayName setStringValue:qIdx.data(Qt::DisplayRole).toString().toNSString()];
177
178 return result;
179}
180
181- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item
182{
183 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
184 if(!qIdx.parent().isValid()) {
185 return 35.0;
186 } else {
187 return 48.0;
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500188 }
189}
190
Alexandre Lision4e280d62015-09-09 15:56:30 -0400191/* View Based OutlineView: See the delegate method -tableView:rowViewForRow: in NSTableView.
192 */
193- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView rowViewForItem:(id)item
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500194{
Alexandre Lision4e280d62015-09-09 15:56:30 -0400195 QModelIndex qIdx = [treeController toQIdx:((NSTreeNode*)item)];
196
197 HoverTableRowView* result = [outlineView makeViewWithIdentifier:@"HoverRowView" owner:nil];
198 if(!qIdx.parent().isValid()) {
199 [result setHighlightable:NO];
200 } else
201 [result setHighlightable:YES];
202
203 return result;
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500204}
205
Alexandre Lision2db8f472015-07-22 15:05:46 -0400206#pragma mark - ContextMenuDelegate
207
208- (NSMenu*) contextualMenuForIndex:(NSIndexPath*) path
209{
210 if([[treeController selectedNodes] count] > 0) {
211 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
212 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
213 if (qIdx.parent().isValid() && var.isValid()) {
214 if (auto call = var.value<Call *>()) {
215 auto contactmethod = call->peerContactMethod();
216 if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
217 NSMenu *theMenu = [[NSMenu alloc]
218 initWithTitle:@""];
Alexandre Lision922380d2015-09-15 10:25:17 -0400219 [theMenu insertItemWithTitle:NSLocalizedString(@"Add to contacts", @"Contextual menu action")
Alexandre Lision2db8f472015-07-22 15:05:46 -0400220 action:@selector(addToContact)
221 keyEquivalent:@"a"
222 atIndex:0];
223 return theMenu;
224 }
225 }
226 }
227 }
228 return nil;
229}
230
231- (void) addToContact
232{
233 ContactMethod* contactmethod = nullptr;
234 if([[treeController selectedNodes] count] > 0) {
235 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
236 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
237 if (qIdx.parent().isValid() && var.isValid()) {
238 if (auto call = var.value<Call *>()) {
239 contactmethod = call->peerContactMethod();
240 }
241 }
242 }
243
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400244 if (addToContactPopover != nullptr) {
245 [addToContactPopover performClose:self];
246 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400247 } else if (contactmethod) {
Alexandre Lisionae840f22015-08-04 15:02:16 -0400248 auto* editorVC = [[PersonLinkerVC alloc] initWithNibName:@"PersonLinker" bundle:nil];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400249 [editorVC setMethodToLink:contactmethod];
250 [editorVC setContactLinkedDelegate:self];
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400251 addToContactPopover = [[NSPopover alloc] init];
252 [addToContactPopover setContentSize:editorVC.view.frame.size];
253 [addToContactPopover setContentViewController:editorVC];
254 [addToContactPopover setAnimates:YES];
255 [addToContactPopover setBehavior:NSPopoverBehaviorTransient];
256 [addToContactPopover setDelegate:self];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400257
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400258 [addToContactPopover showRelativeToRect:[historyView frameOfOutlineCellAtRow:[historyView selectedRow]] ofView:historyView preferredEdge:NSMaxXEdge];
Alexandre Lision2db8f472015-07-22 15:05:46 -0400259 }
260}
261
262#pragma mark - NSPopOverDelegate
263
264- (void)popoverDidClose:(NSNotification *)notification
265{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400266 if (addToContactPopover != nullptr) {
267 [addToContactPopover performClose:self];
268 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400269 }
270}
271
272#pragma mark - ContactLinkedDelegate
273
274- (void)contactLinked
275{
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400276 if (addToContactPopover != nullptr) {
277 [addToContactPopover performClose:self];
278 addToContactPopover = NULL;
Alexandre Lision2db8f472015-07-22 15:05:46 -0400279 }
280}
281
282#pragma mark - KeyboardShortcutDelegate
283
284- (void) onAddShortcut
285{
286 if([[treeController selectedNodes] count] > 0) {
287 QModelIndex qIdx = [treeController toQIdx:[treeController selectedNodes][0]];
288 const auto& var = qIdx.data(static_cast<int>(Call::Role::Object));
289 if (qIdx.parent().isValid() && var.isValid()) {
290 if (auto call = var.value<Call *>()) {
291 auto contactmethod = call->peerContactMethod();
292 if (!contactmethod->contact() || contactmethod->contact()->isPlaceHolder()) {
293 [self addToContact];
294 }
295 }
296 }
297 }
298}
299
Alexandre Lision5855b6a2015-02-03 11:31:05 -0500300@end