You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

415 lines
12 KiB

  1. /*
  2. Copyright 2017 Virgil Dupras
  3. This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
  4. which should be included with this package. The terms are also available at
  5. http://www.gnu.org/licenses/gpl-3.0.html
  6. */
  7. #import "ResultWindow.h"
  8. #import "Dialogs.h"
  9. #import "ProgressController.h"
  10. #import "Utils.h"
  11. #import "AppDelegate.h"
  12. #import "Consts.h"
  13. #import "PrioritizeDialog.h"
  14. @implementation ResultWindow
  15. @synthesize optionsSwitch;
  16. @synthesize optionsToolbarItem;
  17. @synthesize matches;
  18. @synthesize stats;
  19. @synthesize filterField;
  20. - (id)initWithParentApp:(AppDelegate *)aApp;
  21. {
  22. self = [super initWithWindowNibName:@"ResultWindow"];
  23. [self window];
  24. app = aApp;
  25. model = [app model];
  26. /* Put a cute iTunes-like bottom bar */
  27. [[self window] setContentBorderThickness:28 forEdge:NSMinYEdge];
  28. table = [[ResultTable alloc] initWithPyRef:[model resultTable] view:matches];
  29. statsLabel = [[StatsLabel alloc] initWithPyRef:[model statsLabel] view:stats];
  30. [self initResultColumns:table];
  31. [[table columns] setColumnsAsReadOnly];
  32. [self fillColumnsMenu];
  33. [matches setTarget:self];
  34. [matches setDoubleAction:@selector(openClicked:)];
  35. [self adjustUIToLocalization];
  36. return self;
  37. }
  38. - (void)dealloc
  39. {
  40. [table release];
  41. [statsLabel release];
  42. [super dealloc];
  43. }
  44. /* Helpers */
  45. - (void)fillColumnsMenu
  46. {
  47. [[app columnsMenu] removeAllItems];
  48. NSArray *menuItems = [[[table columns] model] menuItems];
  49. for (NSInteger i=0; i < [menuItems count]; i++) {
  50. NSArray *pair = [menuItems objectAtIndex:i];
  51. NSString *display = [pair objectAtIndex:0];
  52. BOOL marked = n2b([pair objectAtIndex:1]);
  53. NSMenuItem *mi = [[app columnsMenu] addItemWithTitle:display action:@selector(toggleColumn:) keyEquivalent:@""];
  54. [mi setTarget:self];
  55. [mi setState:marked ? NSControlStateValueOn : NSControlStateValueOff];
  56. [mi setTag:i];
  57. }
  58. [[app columnsMenu] addItem:[NSMenuItem separatorItem]];
  59. NSMenuItem *mi = [[app columnsMenu] addItemWithTitle:NSLocalizedString(@"Reset to Default", @"")
  60. action:@selector(resetColumnsToDefault:) keyEquivalent:@""];
  61. [mi setTarget:self];
  62. }
  63. - (void)updateOptionSegments
  64. {
  65. [optionsSwitch setSelected:[[app detailsPanel] isVisible] forSegment:0];
  66. [optionsSwitch setSelected:[table powerMarkerMode] forSegment:1];
  67. [optionsSwitch setSelected:[table deltaValuesMode] forSegment:2];
  68. }
  69. - (void)adjustUIToLocalization
  70. {
  71. NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
  72. NSInteger seg1delta = 0;
  73. NSInteger seg2delta = 0;
  74. if ([lang isEqual:@"ru"]) {
  75. seg2delta = 20;
  76. }
  77. else if ([lang isEqual:@"uk"]) {
  78. seg2delta = 20;
  79. }
  80. else if ([lang isEqual:@"hy"]) {
  81. seg1delta = 20;
  82. }
  83. if (seg1delta || seg2delta) {
  84. [optionsSwitch setWidth:[optionsSwitch widthForSegment:0]+seg1delta forSegment:0];
  85. [optionsSwitch setWidth:[optionsSwitch widthForSegment:1]+seg2delta forSegment:1];
  86. NSSize s = [optionsToolbarItem maxSize];
  87. s.width += seg1delta + seg2delta;
  88. [optionsToolbarItem setMaxSize:s];
  89. [optionsToolbarItem setMinSize:s];
  90. }
  91. }
  92. - (void)initResultColumns:(ResultTable *)aTable
  93. {
  94. NSInteger appMode = [app getAppMode];
  95. if (appMode == AppModePicture) {
  96. HSColumnDef defs[] = {
  97. {@"marked", 26, 26, 26, YES, [NSButtonCell class]},
  98. {@"name", 162, 16, 0, YES, nil},
  99. {@"folder_path", 142, 16, 0, YES, nil},
  100. {@"size", 63, 16, 0, YES, nil},
  101. {@"extension", 40, 16, 0, YES, nil},
  102. {@"dimensions", 73, 16, 0, YES, nil},
  103. {@"exif_timestamp", 120, 16, 0, YES, nil},
  104. {@"mtime", 120, 16, 0, YES, nil},
  105. {@"percentage", 58, 16, 0, YES, nil},
  106. {@"dupe_count", 80, 16, 0, YES, nil},
  107. nil
  108. };
  109. [[aTable columns] initializeColumns:defs];
  110. NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"];
  111. [[c dataCell] setButtonType:NSButtonTypeSwitch];
  112. [[c dataCell] setControlSize:NSControlSizeSmall];
  113. c = [[aTable view] tableColumnWithIdentifier:@"size"];
  114. [[c dataCell] setAlignment:NSTextAlignmentRight];
  115. }
  116. else if (appMode == AppModeMusic) {
  117. HSColumnDef defs[] = {
  118. {@"marked", 26, 26, 26, YES, [NSButtonCell class]},
  119. {@"name", 235, 16, 0, YES, nil},
  120. {@"folder_path", 120, 16, 0, YES, nil},
  121. {@"size", 63, 16, 0, YES, nil},
  122. {@"duration", 50, 16, 0, YES, nil},
  123. {@"bitrate", 50, 16, 0, YES, nil},
  124. {@"samplerate", 60, 16, 0, YES, nil},
  125. {@"extension", 40, 16, 0, YES, nil},
  126. {@"mtime", 120, 16, 0, YES, nil},
  127. {@"title", 120, 16, 0, YES, nil},
  128. {@"artist", 120, 16, 0, YES, nil},
  129. {@"album", 120, 16, 0, YES, nil},
  130. {@"genre", 80, 16, 0, YES, nil},
  131. {@"year", 40, 16, 0, YES, nil},
  132. {@"track", 40, 16, 0, YES, nil},
  133. {@"comment", 120, 16, 0, YES, nil},
  134. {@"percentage", 57, 16, 0, YES, nil},
  135. {@"words", 120, 16, 0, YES, nil},
  136. {@"dupe_count", 80, 16, 0, YES, nil},
  137. nil
  138. };
  139. [[aTable columns] initializeColumns:defs];
  140. NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"];
  141. [[c dataCell] setButtonType:NSButtonTypeSwitch];
  142. [[c dataCell] setControlSize:NSControlSizeSmall];
  143. c = [[aTable view] tableColumnWithIdentifier:@"size"];
  144. [[c dataCell] setAlignment:NSTextAlignmentRight];
  145. c = [[aTable view] tableColumnWithIdentifier:@"duration"];
  146. [[c dataCell] setAlignment:NSTextAlignmentRight];
  147. c = [[aTable view] tableColumnWithIdentifier:@"bitrate"];
  148. [[c dataCell] setAlignment:NSTextAlignmentRight];
  149. }
  150. else {
  151. HSColumnDef defs[] = {
  152. {@"marked", 26, 26, 26, YES, [NSButtonCell class]},
  153. {@"name", 195, 16, 0, YES, nil},
  154. {@"folder_path", 183, 16, 0, YES, nil},
  155. {@"size", 63, 16, 0, YES, nil},
  156. {@"extension", 40, 16, 0, YES, nil},
  157. {@"mtime", 120, 16, 0, YES, nil},
  158. {@"percentage", 60, 16, 0, YES, nil},
  159. {@"words", 120, 16, 0, YES, nil},
  160. {@"dupe_count", 80, 16, 0, YES, nil},
  161. nil
  162. };
  163. [[aTable columns] initializeColumns:defs];
  164. NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"];
  165. [[c dataCell] setButtonType:NSButtonTypeSwitch];
  166. [[c dataCell] setControlSize:NSControlSizeSmall];
  167. c = [[aTable view] tableColumnWithIdentifier:@"size"];
  168. [[c dataCell] setAlignment:NSTextAlignmentRight];
  169. }
  170. [[aTable columns] restoreColumns];
  171. }
  172. /* Actions */
  173. - (IBAction)changeOptions:(id)sender
  174. {
  175. NSInteger seg = [optionsSwitch selectedSegment];
  176. if (seg == 0) {
  177. [self toggleDetailsPanel:sender];
  178. }
  179. else if (seg == 1) {
  180. [self togglePowerMarker:sender];
  181. }
  182. else if (seg == 2) {
  183. [self toggleDelta:sender];
  184. }
  185. }
  186. - (IBAction)copyMarked:(id)sender
  187. {
  188. NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  189. [model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
  190. [model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])];
  191. [model copyMarked];
  192. }
  193. - (IBAction)trashMarked:(id)sender
  194. {
  195. NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  196. [model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
  197. [model deleteMarked];
  198. }
  199. - (IBAction)exportToXHTML:(id)sender
  200. {
  201. [model exportToXHTML];
  202. }
  203. - (IBAction)exportToCSV:(id)sender
  204. {
  205. [model exportToCSV];
  206. }
  207. - (IBAction)filter:(id)sender
  208. {
  209. NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  210. [model setEscapeFilterRegexp:!n2b([ud objectForKey:@"useRegexpFilter"])];
  211. [model applyFilter:[filterField stringValue]];
  212. }
  213. - (IBAction)focusOnFilterField:(id)sender
  214. {
  215. [[self window] makeFirstResponder:filterField];
  216. }
  217. - (IBAction)ignoreSelected:(id)sender
  218. {
  219. [model addSelectedToIgnoreList];
  220. }
  221. - (IBAction)invokeCustomCommand:(id)sender
  222. {
  223. [model invokeCustomCommand];
  224. }
  225. - (IBAction)markAll:(id)sender
  226. {
  227. [model markAll];
  228. }
  229. - (IBAction)markInvert:(id)sender
  230. {
  231. [model markInvert];
  232. }
  233. - (IBAction)markNone:(id)sender
  234. {
  235. [model markNone];
  236. }
  237. - (IBAction)markSelected:(id)sender
  238. {
  239. [model toggleSelectedMark];
  240. }
  241. - (IBAction)moveMarked:(id)sender
  242. {
  243. NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  244. [model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
  245. [model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])];
  246. [model moveMarked];
  247. }
  248. - (IBAction)openClicked:(id)sender
  249. {
  250. if ([matches clickedRow] < 0) {
  251. return;
  252. }
  253. [matches selectRowIndexes:[NSIndexSet indexSetWithIndex:[matches clickedRow]] byExtendingSelection:NO];
  254. [model openSelected];
  255. }
  256. - (IBAction)openSelected:(id)sender
  257. {
  258. [model openSelected];
  259. }
  260. - (IBAction)removeMarked:(id)sender
  261. {
  262. [model removeMarked];
  263. }
  264. - (IBAction)removeSelected:(id)sender
  265. {
  266. [model removeSelected];
  267. }
  268. - (IBAction)renameSelected:(id)sender
  269. {
  270. NSInteger col = [matches columnWithIdentifier:@"name"];
  271. NSInteger row = [matches selectedRow];
  272. [matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
  273. }
  274. - (IBAction)reprioritizeResults:(id)sender
  275. {
  276. PrioritizeDialog *dlg = [[PrioritizeDialog alloc] initWithApp:model];
  277. NSInteger result = [NSApp runModalForWindow:[dlg window]];
  278. if (result == NSModalResponseStop ) {
  279. [[dlg model] performReprioritization];
  280. }
  281. [dlg release];
  282. [[self window] makeKeyAndOrderFront:nil];
  283. }
  284. - (IBAction)resetColumnsToDefault:(id)sender
  285. {
  286. [[[table columns] model] resetToDefaults];
  287. [self fillColumnsMenu];
  288. }
  289. - (IBAction)revealSelected:(id)sender
  290. {
  291. [model revealSelected];
  292. }
  293. - (IBAction)saveResults:(id)sender
  294. {
  295. NSSavePanel *sp = [NSSavePanel savePanel];
  296. [sp setCanCreateDirectories:YES];
  297. [sp setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
  298. [sp setTitle:NSLocalizedString(@"Select a file to save your results to", @"")];
  299. if ([sp runModal] == NSModalResponseOK) {
  300. [model saveResultsAs:[[sp URL] path]];
  301. [[app recentResults] addFile:[[sp URL] path]];
  302. }
  303. }
  304. - (IBAction)switchSelected:(id)sender
  305. {
  306. [model makeSelectedReference];
  307. }
  308. - (IBAction)toggleColumn:(id)sender
  309. {
  310. NSMenuItem *mi = sender;
  311. BOOL checked = [[[table columns] model] toggleMenuItem:[mi tag]];
  312. [mi setState:checked ? NSControlStateValueOn : NSControlStateValueOff];
  313. }
  314. - (IBAction)toggleDetailsPanel:(id)sender
  315. {
  316. [[app detailsPanel] toggleVisibility];
  317. [self updateOptionSegments];
  318. }
  319. - (IBAction)toggleDelta:(id)sender
  320. {
  321. [table setDeltaValuesMode:![table deltaValuesMode]];
  322. [self updateOptionSegments];
  323. }
  324. - (IBAction)togglePowerMarker:(id)sender
  325. {
  326. [table setPowerMarkerMode:![table powerMarkerMode]];
  327. [self updateOptionSegments];
  328. }
  329. - (IBAction)toggleQuicklookPanel:(id)sender
  330. {
  331. if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
  332. [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
  333. }
  334. else {
  335. [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
  336. }
  337. }
  338. /* Quicklook */
  339. - (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel;
  340. {
  341. return YES;
  342. }
  343. - (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
  344. {
  345. // This document is now responsible of the preview panel
  346. // It is allowed to set the delegate, data source and refresh panel.
  347. previewPanel = [panel retain];
  348. panel.delegate = table;
  349. panel.dataSource = table;
  350. }
  351. - (void)endPreviewPanelControl:(QLPreviewPanel *)panel
  352. {
  353. // This document loses its responsisibility on the preview panel
  354. // Until the next call to -beginPreviewPanelControl: it must not
  355. // change the panel's delegate, data source or refresh it.
  356. [previewPanel release];
  357. previewPanel = nil;
  358. }
  359. - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
  360. {
  361. return ![[ProgressController mainProgressController] isShown];
  362. }
  363. - (BOOL)validateMenuItem:(NSMenuItem *)item
  364. {
  365. if ([item action] == @selector(markAll)) {
  366. [item setTitle:NSLocalizedString(@"Mark All", @"")];
  367. }
  368. return ![[ProgressController mainProgressController] isShown];
  369. }
  370. @end