File indexing completed on 2021-12-21 13:27:53

0001 /**
0002  * Copyright (C) 2004, 2007 Michael Pyne <mpyne@kde.org>
0003  * Copyright (C) 2003 Frerich Raabe <raabe@kde.org>
0004  *
0005  * This program is free software; you can redistribute it and/or modify it under
0006  * the terms of the GNU General Public License as published by the Free Software
0007  * Foundation; either version 2 of the License, or (at your option) any later
0008  * version.
0009  *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU General Public License along with
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.
0016  */
0017 
0018 #ifndef JUK_FILERENAMER_H
0019 #define JUK_FILERENAMER_H
0020 
0021 #include <QString>
0022 #include <QVector>
0023 #include <QMap>
0024 
0025 #include "ui_filerenamerbase.h"
0026 #include "categoryreaderinterface.h"
0027 #include "tagrenameroptions.h"
0028 
0029 class QCheckBox;
0030 class QPushButton;
0031 class QUrl;
0032 
0033 class ExampleOptionsDialog;
0034 class PlaylistItem;
0035 
0036 typedef QVector<PlaylistItem *> PlaylistItemList;
0037 
0038 // Used to decide what direction the FileRenamerWidget will move rows in.
0039 enum MovementDirection { MoveUp, MoveDown };
0040 
0041 /**
0042  * This is used by FileRenamerWidget to store information about a particular
0043  * tag type, including its position, the QFrame holding the information,
0044  * the up, down, and enable buttons, and the user-selected renaming options.
0045  */
0046 struct Row final
0047 {
0048     Row() : widget(0), upButton(0), downButton(0), enableButton(0) {}
0049 
0050     QWidget *widget;
0051 
0052     QPushButton *upButton, *downButton, *optionsButton, *enableButton;
0053 
0054     TagRenamerOptions options;
0055     CategoryID category; // Includes category and a disambiguation id.
0056     int position; ///< Position in the GUI (0 == top)
0057     QString name;
0058 };
0059 
0060 /**
0061  * A list of rows, each of which may have its own category options and other
0062  * associated data.  There is no relation between the order of rows in the vector and their
0063  * GUI layout.  Instead, each Row has a position member which indicates what GUI position it
0064  * takes up.  The index into the vector is known as the row identifier (which is unique but
0065  * not necessarily constant).
0066  */
0067 typedef QVector<Row> Rows;
0068 
0069 /**
0070  * Associates a CategoryID combination with a set of options.
0071  *
0072  * Used for ConfigCategoryReader
0073  */
0074 typedef QMap<CategoryID, TagRenamerOptions> CategoryOptionsMap;
0075 
0076 /**
0077  * An implementation of CategoryReaderInterface that reads the user's settings
0078  * from the global KConfig configuration object, and reads track information
0079  * from whatever the given PlaylistItem is.  You can assign different
0080  * PlaylistItems in order to change the returned tag category information.
0081  *
0082  * @author Michael Pyne <mpyne@kde.org>
0083  */
0084 class ConfigCategoryReader final : public CategoryReaderInterface
0085 {
0086 public:
0087     // ConfigCategoryReader specific members
0088 
0089     ConfigCategoryReader();
0090 
0091     const PlaylistItem *playlistItem() const { return m_currentItem; }
0092     void setPlaylistItem(const PlaylistItem *item) { m_currentItem = item; }
0093 
0094     // CategoryReaderInterface reimplementations
0095 
0096     virtual QString categoryValue(TagType type) const override;
0097     virtual QString prefix(const CategoryID &category) const override;
0098     virtual QString suffix(const CategoryID &category) const override;
0099     virtual TagRenamerOptions::EmptyActions emptyAction(const CategoryID &category) const override;
0100     virtual QString emptyText(const CategoryID &category) const override;
0101     virtual QList<CategoryID> categoryOrder() const override;
0102     virtual QString separator() const override;
0103     virtual QString musicFolder() const override;
0104     virtual int trackWidth(int categoryNum) const override;
0105     virtual bool hasFolderSeparator(int index) const override;
0106     virtual bool isDisabled(const CategoryID &category) const override;
0107 
0108 private:
0109     const PlaylistItem *m_currentItem;
0110     CategoryOptionsMap m_options;
0111     QList<CategoryID> m_categoryOrder;
0112     QString m_separator;
0113     QString m_musicFolder;
0114     QVector<bool> m_folderSeparators;
0115 };
0116 
0117 /**
0118  * This class implements a dialog that allows the user to alter the behavior
0119  * of the file renamer.  It supports 6 different genre types at this point,
0120  * and it shouldn't be too difficult to extend that in the future if needed.
0121  * It allows the user to open an external dialog, which will let the user see
0122  * an example of what their current options will look like, by either allowing
0123  * the user to type in some sample information, or by loading a file and
0124  * reading tags from there.
0125  *
0126  * It also implements the CategoryReaderInterface in order to implement the
0127  * example filename functionality.
0128  *
0129  * @author Michael Pyne <mpyne@kde.org>
0130  */
0131 class FileRenamerWidget final : public QWidget, public CategoryReaderInterface
0132 {
0133     Q_OBJECT
0134 
0135 public:
0136     explicit FileRenamerWidget(QWidget *parent);
0137     ~FileRenamerWidget();
0138 
0139     /// Maximum number of total categories the widget will allow.
0140     static int const MAX_CATEGORIES = 16;
0141 
0142     /**
0143      * This function saves all of the category options to the global KConfig
0144      * object.  You must call this manually, FileRenamerWidget doesn't call it
0145      * automatically so that situations where the user hits "Cancel" work
0146      * correctly.
0147      */
0148     void saveConfig();
0149 
0150 signals:
0151     void accepted(); // for the QDialogButtonBox
0152     void rejected();
0153 
0154 protected slots:
0155     /**
0156      * This function should be called whenever the example text may need to be
0157      * changed.  For example, when the user selects a different separator or
0158      * changes the example text, this slot should be called.
0159      */
0160     virtual void exampleTextChanged();
0161 
0162     /**
0163      * This function shows the example dialog if it is hidden, and hides the
0164      * example dialog if it is shown.
0165      */
0166     virtual void toggleExampleDialog();
0167 
0168     /**
0169      * This function inserts the currently selected category, so that the
0170      * user can use duplicate tags in the file renamer.
0171      */
0172     virtual void insertCategory();
0173 
0174 private:
0175     /**
0176      * This function initializes the category options by loading the data from
0177      * the global KConfig object.  This is called automatically in the constructor.
0178      */
0179     void loadConfig();
0180 
0181     /**
0182      * This function adds a "Insert Folder separator" checkbox to the end of
0183      * the current layout.  The setting defaults to being unchecked.
0184      */
0185     void addFolderSeparatorCheckbox();
0186 
0187     /**
0188      * This function creates a row in the main view for category, appending it
0189      * to the end.  It handles connecting signals to the mapper and such as
0190      * well.
0191      *
0192      * @param category Type of row to append.
0193      * @return identifier of newly added row.
0194      */
0195     int addRowCategory(TagType category);
0196 
0197     /**
0198      * Removes the given row, updating the other rows to have the correct
0199      * number of categoryNumber.
0200      *
0201      * @param id The identifier of the row to remove.
0202      * @return true if the delete succeeded, false otherwise.
0203      */
0204     bool removeRow(int id);
0205 
0206     /**
0207      * Installs button signal handlers for the buttons in @p row so that they
0208      * are called in response to GUI events, and removes any existing handlers.
0209      */
0210     void assignPositionHandlerForRow(Row &row);
0211 
0212     /**
0213      * This function sets up the internal view by creating the checkboxes and
0214      * the rows for each category.
0215      */
0216     void createTagRows();
0217 
0218     /**
0219      * Returns the value for \p category by retrieving the tag from m_exampleFile.
0220      * If \p category is Track, then an appropriate fixup will be applied if needed
0221      * to match the user's desired minimum width.
0222      *
0223      * @param category the category to retrieve the value for.
0224      * @return the string representation of the value for \p category.
0225      */
0226     QString fileCategoryValue(TagType category) const;
0227 
0228     /**
0229      * Returns the value for \p category by reading the user entry for that
0230      * category. If \p category is Track, then an appropriate fixup will be applied
0231      * if needed to match the user's desired minimum width.
0232      *
0233      * @param category the category to retrieve the value for.
0234      * @return the string representation of the value for \p category.
0235      */
0236     virtual QString categoryValue(TagType category) const override;
0237 
0238     /**
0239      * Returns the user-specified prefix string for \p category.
0240      *
0241      * @param category the category to retrieve the value for.
0242      * @return user-specified prefix string for \p category.
0243      */
0244     virtual QString prefix(const CategoryID &category) const override
0245     {
0246         return m_rows[findIdentifier(category)].options.prefix();
0247     }
0248 
0249     /**
0250      * Returns the user-specified suffix string for \p category.
0251      *
0252      * @param category the category to retrieve the value for.
0253      * @return user-specified suffix string for \p category.
0254      */
0255     virtual QString suffix(const CategoryID &category) const override
0256     {
0257         return m_rows[findIdentifier(category)].options.suffix();
0258     }
0259 
0260     /**
0261      * Returns the user-specified empty action for \p category.
0262      *
0263      * @param category the category to retrieve the value for.
0264      * @return user-specified empty action for \p category.
0265      */
0266     virtual TagRenamerOptions::EmptyActions emptyAction(const CategoryID &category) const override
0267     {
0268         return m_rows[findIdentifier(category)].options.emptyAction();
0269     }
0270 
0271     /**
0272      * Returns the user-specified empty text for \p category.  This text might
0273      * be used to replace an empty value.
0274      *
0275      * @param category the category to retrieve the value for.
0276      * @return the user-specified empty text for \p category.
0277      */
0278     virtual QString emptyText(const CategoryID &category) const override
0279     {
0280         return m_rows[findIdentifier(category)].options.emptyText();
0281     }
0282 
0283     /**
0284      * @return list of CategoryIDs corresponding to the user-specified category order.
0285      */
0286     virtual QList<CategoryID> categoryOrder() const override;
0287 
0288     /**
0289      * @return string that separates the tag values in the file name.
0290      */
0291     virtual QString separator() const override;
0292 
0293     /**
0294      * @return local path to the music folder used to store renamed files.
0295      */
0296     virtual QString musicFolder() const override;
0297 
0298     /**
0299      * @param categoryNum Zero-based number of category to get results for (if more than one).
0300      * @return the minimum width of the track category.
0301      */
0302     virtual int trackWidth(int categoryNum) const override
0303     {
0304         CategoryID id(Track, categoryNum);
0305         return m_rows[findIdentifier(id)].options.trackWidth();
0306     }
0307 
0308     /**
0309      * @param  index, the 0-based index for the folder boundary.
0310      * @return true if there should be a folder separator between category
0311      *         index and index + 1, and false otherwise.  Note that for purposes
0312      *         of this function, only categories that are required or non-empty
0313      *         should count.
0314      */
0315     virtual bool hasFolderSeparator(int index) const override;
0316 
0317     /**
0318      * @param category The category to get the status of.
0319      * @return true if \p category is disabled by the user, and false otherwise.
0320      */
0321     virtual bool isDisabled(const CategoryID &category) const override
0322     {
0323         return m_rows[findIdentifier(category)].options.disabled();
0324     }
0325 
0326     /**
0327      * This moves the widget \p l in the direction given by \p direction, taking
0328      * care to make sure that the checkboxes are not moved, and that they are
0329      * enabled or disabled as appropriate for the new layout, and that the up and
0330      * down buttons are also adjusted as necessary.
0331      *
0332      * @param id the identifier of the row to move
0333      * @param direction the direction to move
0334      */
0335     void moveItem(int id, MovementDirection direction);
0336 
0337     /**
0338      * This function actually performs the work of showing the options dialog for
0339      * \p category.
0340      *
0341      * @param category the category to show the options dialog for.
0342      */
0343     void showCategoryOptions(TagType category);
0344 
0345     /**
0346      * This function enables or disables the widget in the row identified by \p id,
0347      * controlled by \p enable.  This function also makes sure that checkboxes are
0348      * enabled or disabled as appropriate if they no longer make sense due to the
0349      * adjacent category being enabled or disabled.
0350      *
0351      * @param id the identifier of the row to change.  This is *not* the category to
0352      *        change.
0353      * @param enable enables the category if true, disables if false.
0354      */
0355     void setCategoryEnabled(int id, bool enable);
0356 
0357     /**
0358      * This function returns the identifier of the row at \p position.
0359      *
0360      * @param position The position to find the identifier of.
0361      * @return The unique id of the row at \p position.
0362      */
0363     int idOfPosition(int position) const;
0364 
0365     /**
0366      * This function returns the identifier of the row in the m_rows index that
0367      * contains \p category and matches \p categoryNum.
0368      *
0369      * @param category the category to find.
0370      * @return the identifier of the category, or MAX_CATEGORIES if it couldn't
0371      *         be found.
0372      */
0373     int findIdentifier(const CategoryID &category) const;
0374 
0375 private slots:
0376     /**
0377      * This function reads the tags from \p file and ensures that the dialog will
0378      * use those tags until a different file is selected or dataSelected() is
0379      * called.
0380      *
0381      * @param file the path to the local file to read.
0382      */
0383     virtual void fileSelected(const QString &file);
0384 
0385     /**
0386      * This function reads the tags from the user-supplied examples and ensures
0387      * that the dialog will use those tags until a file is selected using
0388      * fileSelected().
0389      */
0390     virtual void dataSelected();
0391 
0392     /**
0393      * This function brings up a dialog that allows the user to edit the options
0394      * for \p id.
0395      *
0396      * @param id the unique id to bring up the options for.
0397      */
0398     virtual void showCategoryOption(int id);
0399 
0400     /**
0401      * This function removes the row identified by id and updates the internal data to be
0402      * consistent again, by forwarding the call to removeRow().
0403      *
0404      * @param id The unique id to update
0405      */
0406     virtual void slotRemoveRow(int id);
0407 
0408     /**
0409      * This function moves \p category up in the layout.
0410      *
0411      * @param id the unique id of the widget to move up.
0412      */
0413     virtual void moveItemUp(int id);
0414 
0415     /**
0416      * This function moves \p category down in the layout.
0417      *
0418      * @param id the unique id of the widget to move down.
0419      */
0420     virtual void moveItemDown(int id);
0421 
0422     /**
0423      * This slot should be called whenever the example input dialog is shown.
0424      */
0425     virtual void exampleDialogShown();
0426 
0427     /**
0428      * This slot should be called whenever the example input dialog is hidden.
0429      */
0430     virtual void exampleDialogHidden();
0431 
0432 private:
0433     /// This is the frame that holds all of the category widgets and checkboxes.
0434     QFrame *m_mainFrame;
0435 
0436     Ui::FileRenamerBase *m_ui;
0437 
0438     /**
0439      * This is the meat of the widget, it holds the rows for the user configuration.  It is
0440      * initially created such that m_rows[0] is the top and row + 1 is the row just below.
0441      * However, this is NOT NECESSARILY true, so don't rely on this.  As soon as the user
0442      * clicks an arrow to move a row then the order will be messed up.  Use row.position to
0443      * determine where the row is in the GUI.
0444      *
0445      * @see idOfPosition
0446      * @see findIdentifier
0447      */
0448     Rows m_rows;
0449 
0450     /**
0451      * This holds an array of checkboxes that allow the user to insert folder
0452      * separators in between categories.
0453      */
0454     QVector<QCheckBox *> m_folderSwitches;
0455 
0456     ExampleOptionsDialog *m_exampleDialog;
0457 
0458     /// This is true if we're reading example tags from m_exampleFile.
0459     bool m_exampleFromFile;
0460     QString m_exampleFile;
0461 };
0462 
0463 /**
0464  * This class contains the backend code to actually implement the file renaming.  It performs
0465  * the function of moving the files from one location to another, constructing the file name
0466  * based off of the user's options (see ConfigCategoryReader) and of setting folder icons
0467  * if appropriate.
0468  *
0469  * @author Michael Pyne <mpyne@kde.org>
0470  */
0471 class FileRenamer final
0472 {
0473 public:
0474     FileRenamer();
0475 
0476     /**
0477      * Renames the filename on disk of the file represented by item according
0478      * to the user configuration stored in KConfig.
0479      *
0480      * @param item The item to rename.
0481      */
0482     void rename(PlaylistItem *item);
0483 
0484     /**
0485      * Renames the filenames on disk of the files given in items according to
0486      * the user configuration stored in KConfig.
0487      *
0488      * @param items The items to rename.
0489      */
0490     void rename(const PlaylistItemList &items);
0491 
0492     /**
0493      * Returns the file name that would be generated based on the options read from
0494      * interface, which must implement CategoryReaderInterface.  (A whole interface is used
0495      * so that we can re-use the code to generate filenames from a in-memory GUI and from
0496      * KConfig).
0497      *
0498      * @param interface object to read options/data from.
0499      */
0500     static QString fileName(const CategoryReaderInterface &interface);
0501 
0502 private:
0503     /**
0504      * Sets the folder icon for elements of the destination path for item (if
0505      * there is not already a folder icon set, and if the folder's name has
0506      * the album name.
0507      */
0508     void setFolderIcon(const QUrl &dst, const PlaylistItem *item);
0509 
0510     /**
0511      * Attempts to rename the file from \a src to \a dest.  Returns true if the
0512      * operation succeeded.
0513      */
0514     bool moveFile(const QString &src, const QString &dest);
0515 };
0516 
0517 #endif /* JUK_FILERENAMER_H */
0518 
0519 // vim: set et sw=4 tw=0 sta: