1 /*****************************************************************************
2 * VLCBookmarksWindowController.m: MacOS X Bookmarks window
3 *****************************************************************************
4 * Copyright (C) 2005 - 2015 VLC authors and VideoLAN
7 * Authors: Felix Paul Kühne <fkuehne at videolan dot org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
25 /*****************************************************************************
27 * the code used to bind with VLC's modules is heavily based upon
28 * ../wxwidgets/bookmarks.cpp, written by Gildas Bazin.
29 * (he is a member of the VideoLAN team)
30 *****************************************************************************/
33 /*****************************************************************************
35 *****************************************************************************/
37 #import "VLCBookmarksWindowController.h"
38 #import "CompatibilityFixes.h"
40 @interface VLCBookmarksWindowController() <NSTableViewDataSource, NSTableViewDelegate>
42 input_thread_t *p_old_input;
46 @implementation VLCBookmarksWindowController
48 /*****************************************************************************
50 *****************************************************************************/
54 self = [super initWithWindowNibName:@"Bookmarks"];
62 vlc_object_release(p_old_input);
64 [[NSNotificationCenter defaultCenter] removeObserver:self];
69 [self.window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenAuxiliary];
71 _dataTable.dataSource = self;
72 _dataTable.delegate = self;
73 _dataTable.action = @selector(goToBookmark:);
74 _dataTable.target = self;
77 [self.window setTitle: _NS("Bookmarks")];
78 [_addButton setTitle: _NS("Add")];
79 [_clearButton setTitle: _NS("Clear")];
80 [_editButton setTitle: _NS("Edit")];
81 [_extractButton setTitle: _NS("Extract")];
82 [_removeButton setTitle: _NS("Remove")];
83 [[[_dataTable tableColumnWithIdentifier:@"description"] headerCell]
84 setStringValue: _NS("Description")];
85 [[[_dataTable tableColumnWithIdentifier:@"time_offset"] headerCell]
86 setStringValue: _NS("Time")];
89 [_editOKButton setTitle: _NS("OK")];
90 [_editCancelButton setTitle: _NS("Cancel")];
91 [_editNameLabel setStringValue: _NS("Name")];
92 [_editTimeLabel setStringValue: _NS("Time")];
94 [[NSNotificationCenter defaultCenter] addObserver:self
95 selector:@selector(inputChangedEvent:)
96 name:VLCInputChangedNotification
100 - (void)updateCocoaWindowLevel:(NSInteger)i_level
102 if (self.isWindowLoaded && [self.window isVisible] && [self.window level] != i_level)
103 [self.window setLevel: i_level];
106 - (void)showBookmarks
108 /* show the window, called from intf.m */
109 [self.window displayIfNeeded];
110 [self.window setLevel: [[[VLCMain sharedInstance] voutController] currentStatusWindowLevel]];
111 [self.window makeKeyAndOrderFront:nil];
114 -(void)inputChangedEvent:(NSNotification *)o_notification
116 [_dataTable reloadData];
119 - (IBAction)add:(id)sender
121 /* add item to list */
122 input_thread_t * p_input = pl_CurrentInput(getIntf());
127 seekpoint_t bookmark;
129 if (!input_Control(p_input, INPUT_GET_BOOKMARK, &bookmark)) {
130 bookmark.psz_name = _("Untitled");
131 input_Control(p_input, INPUT_ADD_BOOKMARK, &bookmark);
134 vlc_object_release(p_input);
136 [_dataTable reloadData];
139 - (IBAction)clear:(id)sender
142 input_thread_t * p_input = pl_CurrentInput(getIntf());
147 input_Control(p_input, INPUT_CLEAR_BOOKMARKS);
149 vlc_object_release(p_input);
151 [_dataTable reloadData];
154 - (IBAction)edit:(id)sender
156 /* put values to the sheet's fields and show sheet */
157 /* we take the values from the core and not the table, because we cannot
159 input_thread_t * p_input = pl_CurrentInput(getIntf());
160 seekpoint_t **pp_bookmarks;
163 row = [_dataTable selectedRow];
169 vlc_object_release(p_input);
173 if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS) {
174 vlc_object_release(p_input);
178 [_editNameTextField setStringValue: toNSStr(pp_bookmarks[row]->psz_name)];
179 [_editTimeTextField setStringValue:[self timeStringForBookmark:pp_bookmarks[row]]];
181 /* Just keep the pointer value to check if it
182 * changes. Note, we don't need to keep a reference to the object.
183 * so release it now. */
184 p_old_input = p_input;
185 vlc_object_release(p_input);
187 [NSApp beginSheet: _editBookmarksWindow modalForWindow: self.window modalDelegate: _editBookmarksWindow didEndSelector: nil contextInfo: nil];
189 // Clear the bookmark list
190 for (int i = 0; i < i_bookmarks; i++)
191 vlc_seekpoint_Delete(pp_bookmarks[i]);
195 - (IBAction)edit_cancel:(id)sender
198 [NSApp endSheet:_editBookmarksWindow];
199 [_editBookmarksWindow close];
202 - (IBAction)edit_ok:(id)sender
204 /* save field contents and close sheet */
205 seekpoint_t **pp_bookmarks;
207 input_thread_t * p_input = pl_CurrentInput(getIntf());
210 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."));
213 if (p_old_input != p_input) {
214 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."));
215 vlc_object_release(p_input);
219 if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS) {
220 vlc_object_release(p_input);
224 i = [_dataTable selectedRow];
226 free(pp_bookmarks[i]->psz_name);
228 pp_bookmarks[i]->psz_name = strdup([[_editNameTextField stringValue] UTF8String]);
230 NSArray * components = [[_editTimeTextField stringValue] componentsSeparatedByString:@":"];
231 NSUInteger componentCount = [components count];
232 if (componentCount == 1)
233 pp_bookmarks[i]->i_time_offset = 1000000LL * ([[components firstObject] floatValue]);
234 else if (componentCount == 2)
235 pp_bookmarks[i]->i_time_offset = 1000000LL * ([[components firstObject] longLongValue] * 60 + [[components objectAtIndex:1] longLongValue]);
236 else if (componentCount == 3)
237 pp_bookmarks[i]->i_time_offset = 1000000LL * ([[components firstObject] longLongValue] * 3600 + [[components objectAtIndex:1] longLongValue] * 60 + [[components objectAtIndex:2] floatValue]);
239 msg_Err(getIntf(), "Invalid string format for time");
243 if (input_Control(p_input, INPUT_CHANGE_BOOKMARK, pp_bookmarks[i], i) != VLC_SUCCESS) {
244 msg_Warn(getIntf(), "Unable to change the bookmark");
248 [_dataTable reloadData];
249 vlc_object_release(p_input);
251 [NSApp endSheet: _editBookmarksWindow];
252 [_editBookmarksWindow close];
255 // Clear the bookmark list
256 for (int i = 0; i < i_bookmarks; i++)
257 vlc_seekpoint_Delete(pp_bookmarks[i]);
261 - (IBAction)extract:(id)sender
263 #warning this does not work anymore
265 if ([_dataTable numberOfSelectedRows] < 2) {
266 NSBeginAlertSheet(_NS("Invalid selection"), _NS("OK"), @"", @"", self.window, nil, nil, nil, nil, @"%@",_NS("Two bookmarks have to be selected."));
269 input_thread_t * p_input = pl_CurrentInput(getIntf());
271 NSBeginCriticalAlertSheet(_NS("No input found"), _NS("OK"), @"", @"", self.window, nil, nil, nil, nil, @"%@",_NS("The stream must be playing or paused for bookmarks to work."));
275 seekpoint_t **pp_bookmarks;
280 for (NSUInteger x = 0; c != 2; x++) {
281 if ([_dataTable isRowSelected:x]) {
285 } else if (i_second == -1) {
292 if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS) {
293 vlc_object_release(p_input);
294 msg_Err(getIntf(), "already defined bookmarks couldn't be retrieved");
298 char *psz_uri = input_item_GetURI(input_GetItem(p_input));
299 [[[VLCMain sharedInstance] wizard] initWithExtractValuesFrom: [NSString stringWithFormat:@"%lli", pp_bookmarks[i_first]->i_time_offset/1000000] to: [NSString stringWithFormat:@"%lli", pp_bookmarks[i_second]->i_time_offset/1000000] ofItem: toNSStr(psz_uri)];
301 vlc_object_release(p_input);
303 // Clear the bookmark list
304 for (int i = 0; i < i_bookmarks; i++)
305 vlc_seekpoint_Delete(pp_bookmarks[i]);
310 - (IBAction)goToBookmark:(id)sender
312 input_thread_t * p_input = pl_CurrentInput(getIntf());
317 input_Control(p_input, INPUT_SET_BOOKMARK, [_dataTable selectedRow]);
319 vlc_object_release(p_input);
322 - (IBAction)remove:(id)sender
324 input_thread_t * p_input = pl_CurrentInput(getIntf());
329 int i_focused = [_dataTable selectedRow];
332 input_Control(p_input, INPUT_DEL_BOOKMARK, i_focused);
334 vlc_object_release(p_input);
336 [_dataTable reloadData];
339 - (NSString *)timeStringForBookmark:(seekpoint_t *)bookmark
341 assert(bookmark != NULL);
343 mtime_t total = bookmark->i_time_offset;
344 unsigned hour = ( total / ( CLOCK_FREQ * 3600 ) );
345 unsigned min = ( total % ( CLOCK_FREQ * 3600 ) ) / ( CLOCK_FREQ * 60 );
346 float sec = ( total % ( CLOCK_FREQ * 60 ) ) / ( CLOCK_FREQ * 1. );
348 return [NSString stringWithFormat:@"%02d:%02d:%06.3f", hour, min, sec];
351 /*****************************************************************************
352 * data source methods
353 *****************************************************************************/
355 - (NSInteger)numberOfRowsInTableView:(NSTableView *)theDataTable
357 /* return the number of bookmarks */
358 input_thread_t * p_input = pl_CurrentInput(getIntf());
359 seekpoint_t **pp_bookmarks;
365 int returnValue = input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks);
366 vlc_object_release(p_input);
368 if (returnValue != VLC_SUCCESS)
371 for (int i = 0; i < i_bookmarks; i++)
372 vlc_seekpoint_Delete(pp_bookmarks[i]);
378 - (id)tableView:(NSTableView *)theDataTable objectValueForTableColumn: (NSTableColumn *)theTableColumn row: (NSInteger)row
380 /* return the corresponding data as NSString */
381 input_thread_t * p_input = pl_CurrentInput(getIntf());
382 seekpoint_t **pp_bookmarks;
388 else if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS)
390 else if (row >= i_bookmarks)
393 NSString * identifier = [theTableColumn identifier];
394 if ([identifier isEqualToString: @"description"])
395 ret = toNSStr(pp_bookmarks[row]->psz_name);
396 else if ([identifier isEqualToString: @"time_offset"]) {
397 ret = [self timeStringForBookmark:pp_bookmarks[row]];
400 // Clear the bookmark list
401 for (int i = 0; i < i_bookmarks; i++)
402 vlc_seekpoint_Delete(pp_bookmarks[i]);
405 vlc_object_release(p_input);
409 /*****************************************************************************
411 *****************************************************************************/
413 - (void)tableViewSelectionDidChange:(NSNotification *)aNotification
415 /* check whether a row is selected and en-/disable the edit/remove buttons */
416 if ([_dataTable selectedRow] == -1) {
417 /* no row is selected */
418 [_editButton setEnabled: NO];
419 [_removeButton setEnabled: NO];
420 [_extractButton setEnabled: NO];
422 /* a row is selected */
423 [_editButton setEnabled: YES];
424 [_removeButton setEnabled: YES];
425 if ([_dataTable numberOfSelectedRows] == 2)
426 [_extractButton setEnabled: YES];
430 /* Called when the user hits CMD + C or copy is clicked in the edit menu
432 - (void) copy:(id)sender {
433 NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
434 NSIndexSet *selectionIndices = [_dataTable selectedRowIndexes];
437 input_thread_t *p_input = pl_CurrentInput(getIntf());
439 seekpoint_t **pp_bookmarks;
441 if (input_Control(p_input, INPUT_GET_BOOKMARKS, &pp_bookmarks, &i_bookmarks) != VLC_SUCCESS)
444 [pasteBoard clearContents];
445 NSUInteger index = [selectionIndices firstIndex];
447 while(index != NSNotFound) {
449 if (index >= i_bookmarks)
451 NSString *name = toNSStr(pp_bookmarks[index]->psz_name);
452 NSString *time = [self timeStringForBookmark:pp_bookmarks[index]];
454 NSString *message = [NSString stringWithFormat:@"%@ - %@", name, time];
455 [pasteBoard writeObjects:@[message]];
458 index = [selectionIndices indexGreaterThanIndex:index];
461 // Clear the bookmark list
462 for (int i = 0; i < i_bookmarks; i++)
463 vlc_seekpoint_Delete(pp_bookmarks[i]);
468 #pragma mark UI validation
470 /* Validate the copy menu item
472 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
474 SEL theAction = [anItem action];
476 if (theAction == @selector(copy:)) {
477 if ([[_dataTable selectedRowIndexes] count] > 0) {
482 /* Indicate that we handle the validation method,
483 * even if we don’t implement the action