VLCBookmarksWindowController.m 13.4 KB
Newer Older
1
/*****************************************************************************
2
 * VLCBookmarksWindowController.m: MacOS X Bookmarks window
3
 *****************************************************************************
4
 * Copyright (C) 2005 - 2015 VLC authors and VideoLAN
5 6
 * $Id$
 *
7
 * Authors: Felix Paul Kühne <fkuehne at videolan dot org>
8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 23 24 25
 *****************************************************************************/


/*****************************************************************************
26 27 28 29
 * Note:
 * the code used to bind with VLC's modules is heavily based upon
 * ../wxwidgets/bookmarks.cpp, written by Gildas Bazin.
 * (he is a member of the VideoLAN team)
30 31 32 33 34 35 36
 *****************************************************************************/


/*****************************************************************************
 * Preamble
 *****************************************************************************/

37
#import "VLCBookmarksWindowController.h"
38
#import "CompatibilityFixes.h"
39

40
@interface VLCBookmarksWindowController() <NSTableViewDataSource, NSTableViewDelegate>
41 42 43
{
    input_thread_t *p_old_input;
}
44 45
@end

46
@implementation VLCBookmarksWindowController
47 48 49 50 51

/*****************************************************************************
 * GUI methods
 *****************************************************************************/

52
- (id)init
53
{
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    self = [super initWithWindowNibName:@"Bookmarks"];

    return self;
}

- (void)dealloc
{
    if (p_old_input)
        vlc_object_release(p_old_input);

    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)windowDidLoad
{
    [self.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];
70

71 72 73 74 75 76
    _dataTable.dataSource = self;
    _dataTable.delegate = self;
    _dataTable.action = @selector(goToBookmark:);
    _dataTable.target = self;

    /* main window */
77
    [self.window setTitle: _NS("Bookmarks")];
78 79 80 81 82 83 84 85 86 87 88 89 90 91
    [_addButton setTitle: _NS("Add")];
    [_clearButton setTitle: _NS("Clear")];
    [_editButton setTitle: _NS("Edit")];
    [_removeButton setTitle: _NS("Remove")];
    [[[_dataTable tableColumnWithIdentifier:@"description"] headerCell]
     setStringValue: _NS("Description")];
    [[[_dataTable tableColumnWithIdentifier:@"time_offset"] headerCell]
     setStringValue: _NS("Time")];

    /* edit window */
    [_editOKButton setTitle: _NS("OK")];
    [_editCancelButton setTitle: _NS("Cancel")];
    [_editNameLabel setStringValue: _NS("Name")];
    [_editTimeLabel setStringValue: _NS("Time")];
92 93 94 95 96

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(inputChangedEvent:)
                                                 name:VLCInputChangedNotification
                                               object:nil];
97 98
}

99 100
- (void)updateCocoaWindowLevel:(NSInteger)i_level
{
101
    if (self.isWindowLoaded && [self.window isVisible] && [self.window level] != i_level)
102
        [self.window setLevel: i_level];
103 104
}

105 106 107
- (void)showBookmarks
{
    /* show the window, called from intf.m */
108 109 110
    [self.window displayIfNeeded];
    [self.window setLevel: [[[VLCMain sharedInstance] voutController] currentStatusWindowLevel]];
    [self.window makeKeyAndOrderFront:nil];
111 112
}

113
-(void)inputChangedEvent:(NSNotification *)o_notification
114
{
115
    [_dataTable reloadData];
116 117
}

118 119 120
- (IBAction)add:(id)sender
{
    /* add item to list */
David's avatar
David committed
121
    input_thread_t * p_input = pl_CurrentInput(getIntf());
122

123
    if (!p_input)
124
        return;
125

126
    seekpoint_t bookmark;
127

128
    if (!input_Control(p_input, INPUT_GET_BOOKMARK, &bookmark)) {
129
        bookmark.psz_name = _("Untitled");
130
        input_Control(p_input, INPUT_ADD_BOOKMARK, &bookmark);
131
    }
132

133
    vlc_object_release(p_input);
134

135
    [_dataTable reloadData];
136 137 138 139 140
}

- (IBAction)clear:(id)sender
{
    /* clear table */
David's avatar
David committed
141
    input_thread_t * p_input = pl_CurrentInput(getIntf());
142

143
    if (!p_input)
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
144
        return;
145

146
    input_Control(p_input, INPUT_CLEAR_BOOKMARKS);
147

148
    vlc_object_release(p_input);
149

150
    [_dataTable reloadData];
151 152 153 154 155 156 157
}

- (IBAction)edit:(id)sender
{
    /* put values to the sheet's fields and show sheet */
    /* we take the values from the core and not the table, because we cannot
     * really trust it */
David's avatar
David committed
158
    input_thread_t * p_input = pl_CurrentInput(getIntf());
159 160 161
    seekpoint_t **pp_bookmarks;
    int i_bookmarks;
    int row;
162
    row = [_dataTable selectedRow];
163

164
    if (!p_input)
165
        return;
166

167 168
    if (row < 0) {
        vlc_object_release(p_input);
169 170 171
        return;
    }

172 173
    if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS) {
        vlc_object_release(p_input);
174
        return;
175
    }
176

177
    [_editNameTextField setStringValue: toNSStr(pp_bookmarks[row]->psz_name)];
178
    [_editTimeTextField setStringValue:[self timeStringForBookmark:pp_bookmarks[row]]];
179

180 181 182
    /* Just keep the pointer value to check if it
     * changes. Note, we don't need to keep a reference to the object.
     * so release it now. */
183
    p_old_input = p_input;
184
    vlc_object_release(p_input);
185

186
    [NSApp beginSheet: _editBookmarksWindow modalForWindow: self.window modalDelegate: _editBookmarksWindow didEndSelector: nil contextInfo: nil];
187 188

    // Clear the bookmark list
189 190 191
    for (int i = 0; i < i_bookmarks; i++)
        vlc_seekpoint_Delete(pp_bookmarks[i]);
    free(pp_bookmarks);
192 193 194 195 196
}

- (IBAction)edit_cancel:(id)sender
{
    /* close sheet */
197 198
    [NSApp endSheet:_editBookmarksWindow];
    [_editBookmarksWindow close];
199 200 201 202 203
}

- (IBAction)edit_ok:(id)sender
{
    /* save field contents and close sheet */
204
     seekpoint_t **pp_bookmarks;
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
205
    int i_bookmarks, i;
David's avatar
David committed
206
    input_thread_t * p_input = pl_CurrentInput(getIntf());
207

208
    if (!p_input) {
209
        NSBeginCriticalAlertSheet(_NS("No input"), _NS("OK"), @"", @"", self.window, nil, nil, nil, nil, @"%@",_NS("No input found. A stream must be playing or paused for bookmarks to work."));
210 211
        return;
    }
212
    if (p_old_input != p_input) {
213
        NSBeginCriticalAlertSheet(_NS("Input has changed"), _NS("OK"), @"", @"", self.window, nil, nil, nil, nil, @"%@",_NS("Input has changed, unable to save bookmark. Suspending playback with \"Pause\" while editing bookmarks to ensure to keep the same input."));
214
        vlc_object_release(p_input);
215 216
        return;
    }
217

218 219
    if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS) {
        vlc_object_release(p_input);
220
        return;
221
    }
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
222

223
    i = [_dataTable selectedRow];
224

225
    free(pp_bookmarks[i]->psz_name);
226

227
    pp_bookmarks[i]->psz_name = strdup([[_editNameTextField stringValue] UTF8String]);
228

229
    NSArray * components = [[_editTimeTextField stringValue] componentsSeparatedByString:@":"];
230
    NSUInteger componentCount = [components count];
231
    if (componentCount == 1)
232
        pp_bookmarks[i]->i_time_offset = 1000000LL * ([[components firstObject] floatValue]);
233
    else if (componentCount == 2)
234
        pp_bookmarks[i]->i_time_offset = 1000000LL * ([[components firstObject] longLongValue] * 60 + [[components objectAtIndex:1] longLongValue]);
235
    else if (componentCount == 3)
236
        pp_bookmarks[i]->i_time_offset = 1000000LL * ([[components firstObject] longLongValue] * 3600 + [[components objectAtIndex:1] longLongValue] * 60 + [[components objectAtIndex:2] floatValue]);
237
    else {
David's avatar
David committed
238
        msg_Err(getIntf(), "Invalid string format for time");
239 240 241
        goto clear;
    }

242
    if (input_Control(p_input, INPUT_CHANGE_BOOKMARK, pp_bookmarks[i], i) != VLC_SUCCESS) {
David's avatar
David committed
243
        msg_Warn(getIntf(), "Unable to change the bookmark");
244
        goto clear;
245
    }
246

247
    [_dataTable reloadData];
248
    vlc_object_release(p_input);
249

250 251
    [NSApp endSheet: _editBookmarksWindow];
    [_editBookmarksWindow close];
252 253 254

clear:
    // Clear the bookmark list
255 256 257
    for (int i = 0; i < i_bookmarks; i++)
        vlc_seekpoint_Delete(pp_bookmarks[i]);
    free(pp_bookmarks);
258 259 260 261 262
}


- (IBAction)goToBookmark:(id)sender
{
David's avatar
David committed
263
    input_thread_t * p_input = pl_CurrentInput(getIntf());
264

265
    if (!p_input)
266
        return;
267

268
    input_Control(p_input, INPUT_SET_BOOKMARK, [_dataTable selectedRow]);
269

270
    vlc_object_release(p_input);
271 272 273 274
}

- (IBAction)remove:(id)sender
{
David's avatar
David committed
275
    input_thread_t * p_input = pl_CurrentInput(getIntf());
276

277
    if (!p_input)
278
        return;
279

280
    int i_focused = [_dataTable selectedRow];
281

282 283
    if (i_focused >= 0)
        input_Control(p_input, INPUT_DEL_BOOKMARK, i_focused);
284

285
    vlc_object_release(p_input);
286

287
    [_dataTable reloadData];
288 289
}

290 291 292 293 294
- (NSString *)timeStringForBookmark:(seekpoint_t *)bookmark
{
    assert(bookmark != NULL);

    mtime_t total = bookmark->i_time_offset;
295 296
    uint64_t hour = ( total / ( CLOCK_FREQ * 3600 ) );
    uint64_t min = ( total % ( CLOCK_FREQ * 3600 ) ) / ( CLOCK_FREQ * 60 );
297 298
    float    sec = ( total % ( CLOCK_FREQ * 60 ) ) / ( CLOCK_FREQ * 1. );

299
    return [NSString stringWithFormat:@"%02llu:%02llu:%06.3f", hour, min, sec];
300 301
}

302 303 304 305
/*****************************************************************************
 * data source methods
 *****************************************************************************/

306
- (NSInteger)numberOfRowsInTableView:(NSTableView *)theDataTable
307 308
{
    /* return the number of bookmarks */
David's avatar
David committed
309
    input_thread_t * p_input = pl_CurrentInput(getIntf());
310 311
    seekpoint_t **pp_bookmarks;
    int i_bookmarks;
312

313
    if (!p_input)
314
        return 0;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
315 316 317 318 319

    int returnValue = input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks);
    vlc_object_release(p_input);

    if (returnValue != VLC_SUCCESS)
320
        return 0;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
321 322 323 324 325 326

    for (int i = 0; i < i_bookmarks; i++)
        vlc_seekpoint_Delete(pp_bookmarks[i]);
    free(pp_bookmarks);

    return i_bookmarks;
327 328
}

329
- (id)tableView:(NSTableView *)theDataTable objectValueForTableColumn: (NSTableColumn *)theTableColumn row: (NSInteger)row
330 331
{
    /* return the corresponding data as NSString */
David's avatar
David committed
332
    input_thread_t * p_input = pl_CurrentInput(getIntf());
333 334
    seekpoint_t **pp_bookmarks;
    int i_bookmarks;
335
    id ret = @"";
336

337 338 339
    if (!p_input)
        return @"";
    else if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS)
340
        ret = @"";
341 342
    else if (row >= i_bookmarks)
        ret = @"";
343
    else {
344
        NSString * identifier = [theTableColumn identifier];
345
        if ([identifier isEqualToString: @"description"])
346
            ret = toNSStr(pp_bookmarks[row]->psz_name);
347
		else if ([identifier isEqualToString: @"time_offset"]) {
348
            ret = [self timeStringForBookmark:pp_bookmarks[row]];
349
        }
350 351

        // Clear the bookmark list
352 353 354
        for (int i = 0; i < i_bookmarks; i++)
            vlc_seekpoint_Delete(pp_bookmarks[i]);
        free(pp_bookmarks);
355
    }
356
    vlc_object_release(p_input);
357
    return ret;
358 359 360 361 362 363 364 365 366
}

/*****************************************************************************
 * delegate methods
 *****************************************************************************/

- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
    /* check whether a row is selected and en-/disable the edit/remove buttons */
367
    if ([_dataTable selectedRow] == -1) {
368
        /* no row is selected */
369 370
        [_editButton setEnabled: NO];
        [_removeButton setEnabled: NO];
371
    } else {
372
        /* a row is selected */
373 374
        [_editButton setEnabled: YES];
        [_removeButton setEnabled: YES];
375 376 377
    }
}

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
/* Called when the user hits CMD + C or copy is clicked in the edit menu
 */
- (void) copy:(id)sender {
    NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
    NSIndexSet *selectionIndices = [_dataTable selectedRowIndexes];


    input_thread_t *p_input = pl_CurrentInput(getIntf());
    int i_bookmarks;
    seekpoint_t **pp_bookmarks;

    if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS)
        return;

    [pasteBoard clearContents];
    NSUInteger index = [selectionIndices firstIndex];

    while(index != NSNotFound) {
        /* Get values */
        if (index >= i_bookmarks)
            break;
        NSString *name = toNSStr(pp_bookmarks[index]->psz_name);
        NSString *time = [self timeStringForBookmark:pp_bookmarks[index]];

        NSString *message = [NSString stringWithFormat:@"%@ - %@", name, time];
        [pasteBoard writeObjects:@[message]];

        /* Get next index */
        index = [selectionIndices indexGreaterThanIndex:index];
    }

    // Clear the bookmark list
    for (int i = 0; i < i_bookmarks; i++)
        vlc_seekpoint_Delete(pp_bookmarks[i]);
    free(pp_bookmarks);
}

#pragma mark -
#pragma mark UI validation

/* Validate the copy menu item
 */
- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
    SEL theAction = [anItem action];

    if (theAction == @selector(copy:)) {
        if ([[_dataTable selectedRowIndexes] count] > 0) {
            return YES;
        }
        return NO;
    }
    /* Indicate that we handle the validation method,
     * even if we don’t implement the action
     */
    return YES;
}

436
@end