1 /**
2  * \file basemainwindow.h
3  * Base class for main window.
4  *
5  * \b Project: Kid3
6  * \author Urs Fleisch
7  * \date 9 Jan 2003
8  *
9  * Copyright (C) 2003-2018  Urs Fleisch
10  *
11  * This file is part of Kid3.
12  *
13  * Kid3 is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * Kid3 is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26 
27 #pragma once
28 
29 #include <QMainWindow>
30 #include <QDateTime>
31 #include <QScopedPointer>
32 #include "iframeeditor.h"
33 #include "frame.h"
34 #include "kid3api.h"
35 
36 class QLabel;
37 class QProgressBar;
38 class QToolButton;
39 class QItemSelection;
40 class QTimer;
41 class ProgressWidget;
42 class Kid3Form;
43 class Kid3Application;
44 class TaggedFile;
45 class ImportDialog;
46 class TagImportDialog;
47 class BatchImportDialog;
48 class ExportDialog;
49 class FindReplaceDialog;
50 class BrowseCoverArtDialog;
51 class RenDirDialog;
52 class NumberTracksDialog;
53 class RenDirDialog;
54 class FilterDialog;
55 class FileFilter;
56 class DownloadDialog;
57 class PlaylistDialog;
58 class PlaylistEditDialog;
59 class PlaylistConfig;
60 class EditFrameFieldsDialog;
61 class PlayToolBar;
62 class DirContents;
63 class FileProxyModel;
64 class DirProxyModel;
65 class TrackDataModel;
66 class IPlatformTools;
67 class BaseMainWindow;
68 
69 /**
70  * Implementation class for BaseMainWindow.
71  * The reason for this implementation class is that a QObject is needed to
72  * have slots. However, BaseMainWindow cannot inherit from QObject because it
73  * is used with multiple inheritance together with another class which is a
74  * QObject (actually a QMainWindow). Therefore the functionality of the main
75  * put into this class which is then used as an implementation class by
76  * BaseMainWindow.
77  */
78 class KID3_GUI_EXPORT BaseMainWindowImpl : public QObject, public IFrameEditor {
79   Q_OBJECT
80 public:
81   /**
82    * Constructor.
83    *
84    * @param mainWin main window widget
85    * @param platformTools platform specific tools
86    */
87   BaseMainWindowImpl(QMainWindow* mainWin, IPlatformTools* platformTools, Kid3Application *app);
88 
89   /**
90    * Destructor.
91    */
92   virtual ~BaseMainWindowImpl() override;
93 
94   /**
95    * Create dialog to edit a frame and update the fields
96    * if Ok is returned.
97    * frameEdited() is emitted when the edit dialog is closed with the edited
98    * frame as a parameter if it was accepted.
99    *
100    * @param frame frame to edit
101    * @param taggedFile tagged file where frame has to be set
102    */
103   virtual void editFrameOfTaggedFile(const Frame* frame, TaggedFile* taggedFile) override;
104 
105   /**
106    * Let user select a frame type.
107    * frameSelected() is emitted when the edit dialog is closed with the selected
108    * frame as a parameter if a frame is selected.
109    *
110    * @param frame is filled with the selected frame
111    * @param taggedFile tagged file for which frame has to be selected
112    */
113   virtual void selectFrame(Frame* frame, const TaggedFile* taggedFile) override;
114 
115   /**
116    * Return object which emits frameSelected(), frameEdited() signals.
117    *
118    * @return object which emits signals.
119    */
120   virtual QObject* qobject() override;
121 
122   /**
123    * Get the tag number of the edited frame.
124    * @return tag number.
125    */
126   virtual Frame::TagNumber tagNumber() const override;
127 
128   /**
129    * Set the tag number of the edited frame.
130    * @param tagNr tag number
131    */
132   virtual void setTagNumber(Frame::TagNumber tagNr) override;
133 
134   /**
135    * Set back pointer for implementation class.
136    *
137    * @param self back pointer
138    */
setBackPointer(BaseMainWindow * self)139   void setBackPointer(BaseMainWindow* self) { m_self = self; }
140 
141   /**
142    * Initialize main window.
143    * Shall be called at end of constructor body.
144    */
145   void init();
146 
147   /**
148    * Change visibility of status bar.
149    * @param visible true to show status bar
150    */
151   void setStatusBarVisible(bool visible);
152 
153   /**
154    * Update modification state before closing.
155    * If anything was modified, save after asking user.
156    * Save options before closing.
157    * This method shall be called by closeEvent() (Qt) or
158    * queryClose() (KDE).
159    *
160    * @return false if user canceled,
161    *         true will quit the application.
162    */
163   bool queryBeforeClosing();
164 
165   /**
166    * Open recent directory.
167    *
168    * @param dir directory to open
169    */
170   void openRecentDirectory(const QString& dir);
171 
172   /**
173    * Apply configuration changes.
174    */
175   void applyChangedConfiguration();
176 
177   /**
178    * Apply keyboard shortcut changes.
179    */
180   void applyChangedShortcuts();
181 
182   /**
183    * Access to application.
184    * @return application.
185    */
app()186   Kid3Application* app() { return m_app; }
187 
188   /**
189    * Access to main form.
190    * @return main form.
191    */
form()192   Kid3Form* form() { return m_form; }
193 
194 public slots:
195   /**
196    * Set window title with information from directory, filter and modification
197    * state.
198    */
199   void updateWindowCaption();
200 
201   /**
202    * Open directory, user has to confirm if current directory modified.
203    *
204    * @param paths directory or file paths
205    */
206   void confirmedOpenDirectory(const QStringList& paths);
207 
208   /**
209    * Update the recent file list and the caption when a new directory
210    * is opened.
211    */
212   void onDirectoryOpened();
213 
214   /**
215    * Request new directory and open it.
216    */
217   void slotFileOpen();
218 
219   /**
220    * Request new directory and open it.
221    */
222   void slotFileOpenDirectory();
223 
224   /**
225    * Reload the current directory.
226    */
227   void slotFileReload();
228 
229   /**
230    * Save modified files.
231    */
232   void slotFileSave();
233 
234   /**
235    * Quit application.
236    */
237   void slotFileQuit();
238 
239   /**
240    * Change status message.
241    *
242    * @param text message
243    */
244   void slotStatusMsg(const QString& text);
245 
246   /**
247    * Clear status message.
248    * To be called when a message set with slotStatusMsg() is no longer valid.
249    */
250   void slotClearStatusMsg();
251 
252   /**
253    * Show playlist dialog.
254    */
255   void slotPlaylistDialog();
256 
257   /**
258    * Create playlist.
259    *
260    * @return true if ok.
261    */
262   bool slotCreatePlaylist();
263 
264   /**
265    * Open dialog to edit playlist.
266    * @param playlistPath path to playlist file
267    */
268   void showPlaylistEditDialog(const QString& playlistPath);
269 
270   /**
271    * Import.
272    */
273   void slotImport();
274 
275   /**
276    * Tag import.
277    */
278   void slotTagImport();
279 
280   /**
281    * Batch import.
282    */
283   void slotBatchImport();
284 
285   /**
286    * Browse album cover artwork.
287    */
288   void slotBrowseCoverArt();
289 
290   /**
291    * Export.
292    */
293   void slotExport();
294 
295   /**
296    * Toggle auto hiding of tags.
297    */
298   void slotSettingsAutoHideTags();
299 
300   /**
301    * Show or hide picture.
302    */
303   void slotSettingsShowHidePicture();
304 
305   /**
306    * Find in tags of files.
307    */
find()308   void find() { findReplace(true); }
309 
310   /**
311    * Find and replace in tags of files.
312    * @param findOnly true to display only find part of dialog
313    */
314   void findReplace(bool findOnly = false);
315 
316   /**
317    * Rename directory.
318    */
319   void slotRenameDirectory();
320 
321   /**
322    * Number tracks.
323    */
324   void slotNumberTracks();
325 
326   /**
327    * Filter.
328    */
329   void slotFilter();
330 
331   /**
332    * Play audio file.
333    */
334   void slotPlayAudio();
335 
336   /**
337    * Update files of current selection.
338    */
339   void updateCurrentSelection();
340 
341   /**
342    * Apply selection change and update GUI controls.
343    * The new selection is stored and the GUI controls and frame list
344    * updated accordingly (filtered for multiple selection).
345    * @param selected selected items
346    * @param deselected deselected items
347    */
348   void applySelectionChange(const QItemSelection& selected,
349                             const QItemSelection& deselected);
350 
351   /**
352    * Update GUI controls from the tags in the files.
353    * The new selection is stored and the GUI controls and frame list
354    * updated accordingly (filtered for multiple selection).
355    */
356   void updateGuiControls();
357 
358   /**
359    * Rename the selected file(s).
360    */
361   void renameFile();
362 
363   /**
364    * Delete the selected file(s).
365    */
366   void deleteFile();
367 
368   /**
369    * Expand the file list.
370    */
371   void expandFileList();
372 
373 signals:
374   /**
375    * Emitted when the dialog to add and edit a frame is closed.
376    * @param tagNr tag number
377    * @param frame edited frame if dialog was accepted, else 0
378    */
379   void frameEdited(Frame::TagNumber tagNr, const Frame* frame);
380 
381   /**
382    * Emitted when the dialog to select a frame is closed.
383    * @param tagNr tag number
384    * @param frame selected frame if dialog was accepted, else 0
385    */
386   void frameSelected(Frame::TagNumber tagNr, const Frame* frame);
387 
388 private slots:
389   /**
390    * Update ID3v2 tags in GUI controls from file displayed in frame list.
391    *
392    * @param taggedFile the selected file
393    * @param tagNr tag number
394    */
395   void updateAfterFrameModification(TaggedFile* taggedFile,
396                                     Frame::TagNumber tagNr);
397 
398   /**
399    * Show play tool bar.
400    */
401   void showPlayToolBar();
402 
403   /**
404    * Expand item if it is a directory.
405    *
406    * @param index index of file in file proxy model
407    */
408   void expandNextDirectory(const QPersistentModelIndex& index);
409 
410   /**
411    * Show filter operation progress.
412    * @param type filter event type
413    * @param fileName name of file processed
414    * @param passed number of files which passed the filter
415    * @param total total number of files checked
416    */
417   void filterProgress(int type, const QString& fileName, int passed, int total);
418 
419   /**
420    * Set tagged files of directory from imported track data model.
421    */
422   void applyImportedTrackData();
423 
424   /**
425    * Called when the edit frame dialog is finished.
426    * @param result dialog result
427    */
428   void onEditFrameDialogFinished(int result);
429 
430   /**
431    * Called when a playlist edit dialog is closed.
432    */
433   void onPlaylistEditDialogFinished();
434 
435   /**
436    * Toggle expanded state of directory in the file list.
437    * @param index index of directory
438    */
439   void toggleExpanded(const QModelIndex& index);
440 
441   /**
442    * Deactivate showing of find replace results.
443    */
444   void deactivateFindReplace();
445 
446   /**
447    * Ensure that found text is made visible in the GUI.
448    */
449   void showFoundText();
450 
451   /**
452    * Update GUI controls after text has been replaced.
453    */
454   void updateReplacedText();
455 
456   /**
457    * Show progress of long running operation in status bar.
458    * @param name name of operation
459    * @param done amount of work done
460    * @param total total amount of work
461    * @param abort if not 0, can be set to true to abort operation
462    */
463   void showOperationProgress(const QString& name, int done, int total,
464                              bool* abort);
465 
466   /**
467    * Called when the item count of the file proxy model changed.
468    */
469   void onItemCountChanged();
470 
471   /**
472    * Called when the item count of the file selection model changed.
473    */
474   void onSelectionCountChanged();
475 
476 private:
477   /**
478    * Free allocated resources.
479    * Our destructor may not be called, so cleanup is done here.
480    */
481   void cleanup();
482 
483   /**
484    * Save application options.
485    */
486   void saveOptions();
487 
488   /**
489    * Load application options.
490    */
491   void readOptions();
492 
493   /**
494    * Save all changed files.
495    *
496    * @param updateGui true to update GUI (controls, status, cursor)
497    */
498   void saveDirectory(bool updateGui = false);
499 
500   /**
501    * If anything was modified, save after asking user.
502    *
503    * @param doNotRevert if true, modifications are not reverted, this can be
504    * used to skip the possibly long process if the application is not be closed
505    *
506    * @return false if user canceled.
507    */
508   bool saveModified(bool doNotRevert = false);
509 
510   /**
511    * If a playlist was modified, save after asking user.
512    * @return false if user canceled.
513    */
514   bool saveModifiedPlaylists();
515 
516   /**
517    * Update track data and create import dialog.
518    */
519   void setupImportDialog();
520 
521   /**
522    * Write playlist according to playlist configuration.
523    *
524    * @param cfg playlist configuration to use
525    *
526    * @return true if ok.
527    */
528   bool writePlaylist(const PlaylistConfig& cfg);
529 
530   /**
531    * Terminate expanding the file list.
532    */
533   void terminateExpandFileList();
534 
535   /**
536    * Terminate filtering the file list.
537    */
538   void terminateFilter();
539 
540   /**
541    * Update GUI controls from the current selection.
542    */
543   void updateGuiControlsFromSelection();
544 
545   /**
546    * Start monitoring the progress of a possibly long operation.
547    *
548    * If the operation takes longer than 3 seconds, a progress widget is shown.
549    *
550    * @param title title to be displayed in progress widget
551    * @param terminationHandler method to be called to terminate operation
552    * @param disconnectModel true to disconnect the file list models while the
553    * progress widget is shown
554    */
555   void startProgressMonitoring(const QString& title,
556                                void (BaseMainWindowImpl::*terminationHandler)(),
557                                bool disconnectModel);
558 
559   /**
560    * Stop monitoring the progress started with startProgressMonitoring().
561    */
562   void stopProgressMonitoring();
563 
564   /**
565    * Check progress of a possibly long operation.
566    *
567    * Progress monitoring is started with startProgressMonitoring(). This method
568    * will check if the opeation is running long enough to show a progress widget
569    * and update the progress information. It will call stopProgressMonitoring()
570    * when the operation is aborted.
571    *
572    * @param done amount of work done
573    * @param total total amount of work
574    * @param text text for progress label
575    */
576   void checkProgressMonitoring(int done, int total, const QString& text);
577 
578   /**
579    * Update label of status bar with information about the number of files.
580    */
581   void updateStatusLabel();
582 
583   IPlatformTools* m_platformTools;
584   QMainWindow* m_w;
585   BaseMainWindow* m_self;
586 
587   QTimer* m_deferredItemCountTimer;
588   QTimer* m_deferredSelectionCountTimer;
589   /** Label with normal status message */
590   QLabel* m_statusLabel;
591   /** GUI with controls */
592   Kid3Form* m_form;
593   /** Application logic */
594   Kid3Application* m_app;
595   /** Import dialog */
596   QScopedPointer<ImportDialog> m_importDialog;
597   /** Import from Tags dialog */
598   QScopedPointer<TagImportDialog> m_tagImportDialog;
599   /** Batch import dialog */
600   QScopedPointer<BatchImportDialog> m_batchImportDialog;
601   /** Browse cover art dialog */
602   QScopedPointer<BrowseCoverArtDialog> m_browseCoverArtDialog;
603   /** Export dialog */
604   ExportDialog* m_exportDialog;
605   /** Find and replace dialog */
606   FindReplaceDialog* m_findReplaceDialog;
607   /** Rename directory dialog */
608   QScopedPointer<RenDirDialog> m_renDirDialog;
609   /** Number tracks dialog */
610   QScopedPointer<NumberTracksDialog> m_numberTracksDialog;
611   /** Filter dialog */
612   QScopedPointer<FilterDialog> m_filterDialog;
613   /** Download dialog */
614   DownloadDialog* m_downloadDialog;
615   /** Playlist dialog */
616   QScopedPointer<PlaylistDialog> m_playlistDialog;
617   /** Playlist edit dialogs */
618   QMap<QString, PlaylistEditDialog*> m_playlistEditDialogs;
619   /** Progress dialog */
620   ProgressWidget* m_progressWidget;
621   QLabel* m_progressLabel;
622   QProgressBar* m_progressBar;
623   QToolButton* m_progressAbortButton;
624   /** Edit frame dialog */
625   EditFrameFieldsDialog* m_editFrameDialog;
626   /** Play toolbar */
627   PlayToolBar* m_playToolBar;
628   Frame m_editFrame;
629   TaggedFile* m_editFrameTaggedFile;
630   Frame::TagNumber m_editFrameTagNr;
631   QDateTime m_progressStartTime;
632   QString m_progressTitle;
633   void (BaseMainWindowImpl::*m_progressTerminationHandler)();
634   int m_folderCount;
635   int m_fileCount;
636   int m_selectionCount;
637   bool m_progressDisconnected;
638   bool m_findReplaceActive;
639   bool m_expandNotificationNeeded;
640 };
641 
642 
643 /**
644  * Base class for the main window.
645  * The main window classes for Qt (QMainWindow) and KDE (KXmlGuiWindow)
646  * have common functionality. The actual Kid3 main window can inherit from both
647  * the platform dependent main window class and this base class. Differences
648  * between the platforms can be handled by implementing the pure virtual methods
649  * of this class. Because this class cannot be a QObject (QMainWindow is
650  * already a QObject), most of its functionality is delegated to a QObject
651  * implementation class.
652  */
653 class KID3_GUI_EXPORT BaseMainWindow {
654 public:
655   /**
656    * Constructor.
657    *
658    * @param mainWin main window
659    * @param platformTools platform specific tools
660    * @param app application context
661    */
662   BaseMainWindow(QMainWindow* mainWin, IPlatformTools* platformTools,
663                  Kid3Application *app);
664 
665   /**
666    * Destructor.
667    */
668   virtual ~BaseMainWindow();
669 
670   /**
671    * Init menu and toolbar actions.
672    */
673   virtual void initActions() = 0;
674 
675   /**
676    * Get keyboard shortcuts.
677    * @return mapping of action names to key sequences.
678    */
679   virtual QMap<QString, QKeySequence> shortcutsMap() const = 0;
680 
681   /**
682    * Add directory to recent files list.
683    *
684    * @param dirName path to directory
685    */
686   virtual void addDirectoryToRecentFiles(const QString& dirName) = 0;
687 
688   /**
689    * Read settings from the configuration.
690    */
691   virtual void readConfig() = 0;
692 
693   /**
694    * Store geometry and recent files in settings.
695    */
696   virtual void saveConfig() = 0;
697 
698   /**
699    * Get action for Settings/Auto Hide Tags.
700    * @return action.
701    */
702   virtual QAction* autoHideTagsAction() = 0;
703 
704   /**
705    * Get action for Settings/Hide Picture.
706    * @return action.
707    */
708   virtual QAction* showHidePictureAction() = 0;
709 
710   /**
711    * Set main window caption.
712    *
713    * @param caption caption without application name
714    * @param modified true if any file is modified
715    */
716   virtual void setWindowCaption(const QString& caption, bool modified) = 0;
717 
718   /**
719    * Play audio file.
720    */
721   void slotPlayAudio();
722 
723   /**
724    * Update files of current selection.
725    */
726   void updateCurrentSelection();
727 
728   /**
729    * Open directory, user has to confirm if current directory modified.
730    *
731    * @param paths directory or file paths
732    */
733   void confirmedOpenDirectory(const QStringList& paths);
734 
735   /**
736    * Access to implementation object.
737    * @return implementation object.
738    */
impl()739   BaseMainWindowImpl* impl() { return m_impl.data(); }
740 
741 protected:
742   /**
743    * Initialize main window.
744    * Shall be called at end of constructor body in derived classes.
745    */
746   void init();
747 
748   /**
749    * Change visibility of status bar.
750    * @param visible true to show status bar
751    */
752   void setStatusBarVisible(bool visible);
753 
754   /**
755    * Update modification state before closing.
756    * If anything was modified, save after asking user.
757    * Save options before closing.
758    * This method shall be called by closeEvent() (Qt) or
759    * queryClose() (KDE).
760    *
761    * @return false if user canceled,
762    *         true will quit the application.
763    */
764   bool queryBeforeClosing();
765 
766   /**
767    * Open recent directory.
768    *
769    * @param dir directory to open
770    */
771   void openRecentDirectory(const QString& dir);
772 
773   /**
774    * Set window title with information from directory, filter and modification
775    * state.
776    */
777   void updateWindowCaption();
778 
779   /**
780    * Access to application.
781    * @return application.
782    */
783   Kid3Application* app();
784 
785   /**
786    * Access to main form.
787    * @return main form.
788    */
789   Kid3Form* form();
790 
791 private:
792   QScopedPointer<BaseMainWindowImpl> m_impl;
793 };
794