filerenamer.h 17.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * Copyright (C) 2004, 2007 Michael Pyne <mpyne@kde.org>
 * Copyright (C) 2003 Frerich Raabe <raabe@kde.org>
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */
17

18 19
#ifndef JUK_FILERENAMER_H
#define JUK_FILERENAMER_H
20

21
#include <QString>
22
#include <QVector>
23
#include <QMap>
24

Laurent Montel's avatar
Laurent Montel committed
25
#include "ui_filerenamerbase.h"
26 27 28 29 30
#include "categoryreaderinterface.h"
#include "tagrenameroptions.h"

class QCheckBox;
class QPushButton;
31
class QUrl;
32

33 34 35
class ExampleOptionsDialog;
class PlaylistItem;

36
typedef QVector<PlaylistItem *> PlaylistItemList;
37

Scott Wheeler's avatar
Scott Wheeler committed
38
// Used to decide what direction the FileRenamerWidget will move rows in.
39
enum MovementDirection { MoveUp, MoveDown };
40 41 42 43 44 45

/**
 * This is used by FileRenamerWidget to store information about a particular
 * tag type, including its position, the QFrame holding the information,
 * the up, down, and enable buttons, and the user-selected renaming options.
 */
46
struct Row final
47
{
48 49
    Row() : widget(0), upButton(0), downButton(0), enableButton(0) {}

50
    QWidget *widget;
51 52 53

    QPushButton *upButton, *downButton, *optionsButton, *enableButton;

54
    TagRenamerOptions options;
55
    CategoryID category; // Includes category and a disambiguation id.
Scott Wheeler's avatar
Scott Wheeler committed
56
    int position; ///< Position in the GUI (0 == top)
57 58 59
    QString name;
};

60 61 62 63 64 65 66
/**
 * A list of rows, each of which may have its own category options and other
 * associated data.  There is no relation between the order of rows in the vector and their
 * GUI layout.  Instead, each Row has a position member which indicates what GUI position it
 * takes up.  The index into the vector is known as the row identifier (which is unique but
 * not necessarily constant).
 */
67
typedef QVector<Row> Rows;
68 69 70 71 72 73 74 75

/**
 * Associates a CategoryID combination with a set of options.
 *
 * Used for ConfigCategoryReader
 */
typedef QMap<CategoryID, TagRenamerOptions> CategoryOptionsMap;

76 77 78 79 80
/**
 * An implementation of CategoryReaderInterface that reads the user's settings
 * from the global KConfig configuration object, and reads track information
 * from whatever the given PlaylistItem is.  You can assign different
 * PlaylistItems in order to change the returned tag category information.
81
 *
82
 * @author Michael Pyne <mpyne@kde.org>
83
 */
84
class ConfigCategoryReader final : public CategoryReaderInterface
85 86 87
{
public:
    // ConfigCategoryReader specific members
88

89
    ConfigCategoryReader();
90

91 92
    const PlaylistItem *playlistItem() const { return m_currentItem; }
    void setPlaylistItem(const PlaylistItem *item) { m_currentItem = item; }
93

94 95
    // CategoryReaderInterface reimplementations

96 97 98 99 100 101 102 103 104 105 106
    virtual QString categoryValue(TagType type) const override;
    virtual QString prefix(const CategoryID &category) const override;
    virtual QString suffix(const CategoryID &category) const override;
    virtual TagRenamerOptions::EmptyActions emptyAction(const CategoryID &category) const override;
    virtual QString emptyText(const CategoryID &category) const override;
    virtual QList<CategoryID> categoryOrder() const override;
    virtual QString separator() const override;
    virtual QString musicFolder() const override;
    virtual int trackWidth(int categoryNum) const override;
    virtual bool hasFolderSeparator(int index) const override;
    virtual bool isDisabled(const CategoryID &category) const override;
107 108

private:
109
    const PlaylistItem *m_currentItem;
110
    CategoryOptionsMap m_options;
111
    QList<CategoryID> m_categoryOrder;
112
    QString m_separator;
Scott Wheeler's avatar
Scott Wheeler committed
113
    QString m_musicFolder;
114
    QVector<bool> m_folderSeparators;
115 116 117 118 119 120 121 122 123 124 125 126 127 128
};

/**
 * This class implements a dialog that allows the user to alter the behavior
 * of the file renamer.  It supports 6 different genre types at this point,
 * and it shouldn't be too difficult to extend that in the future if needed.
 * It allows the user to open an external dialog, which will let the user see
 * an example of what their current options will look like, by either allowing
 * the user to type in some sample information, or by loading a file and
 * reading tags from there.
 *
 * It also implements the CategoryReaderInterface in order to implement the
 * example filename functionality.
 *
129
 * @author Michael Pyne <mpyne@kde.org>
130
 */
131
class FileRenamerWidget final : public QWidget, public CategoryReaderInterface
132
{
133 134
    Q_OBJECT

135
public:
Yuri Chornoivan's avatar
Yuri Chornoivan committed
136
    explicit FileRenamerWidget(QWidget *parent);
137 138
    ~FileRenamerWidget();

139
    /// Maximum number of total categories the widget will allow.
Scott Wheeler's avatar
Scott Wheeler committed
140
    static int const MAX_CATEGORIES = 16;
141

142 143 144 145 146 147
    /**
     * This function saves all of the category options to the global KConfig
     * object.  You must call this manually, FileRenamerWidget doesn't call it
     * automatically so that situations where the user hits "Cancel" work
     * correctly.
     */
148 149
    void saveConfig();

150 151 152 153
signals:
    void accepted(); // for the QDialogButtonBox
    void rejected();

154
protected slots:
155 156 157 158 159
    /**
     * This function should be called whenever the example text may need to be
     * changed.  For example, when the user selects a different separator or
     * changes the example text, this slot should be called.
     */
160 161
    virtual void exampleTextChanged();

162 163 164 165
    /**
     * This function shows the example dialog if it is hidden, and hides the
     * example dialog if it is shown.
     */
166 167
    virtual void toggleExampleDialog();

168 169 170 171 172 173
    /**
     * This function inserts the currently selected category, so that the
     * user can use duplicate tags in the file renamer.
     */
    virtual void insertCategory();

174
private:
175 176 177 178
    /**
     * This function initializes the category options by loading the data from
     * the global KConfig object.  This is called automatically in the constructor.
     */
179 180
    void loadConfig();

181
    /**
182 183
     * This function adds a "Insert Folder separator" checkbox to the end of
     * the current layout.  The setting defaults to being unchecked.
184
     */
185
    void addFolderSeparatorCheckbox();
186

187
    /**
188 189 190
     * This function creates a row in the main view for category, appending it
     * to the end.  It handles connecting signals to the mapper and such as
     * well.
191
     *
192 193
     * @param category Type of row to append.
     * @return identifier of newly added row.
194
     */
Scott Wheeler's avatar
Scott Wheeler committed
195
    int addRowCategory(TagType category);
196 197 198 199 200 201 202 203

    /**
     * Removes the given row, updating the other rows to have the correct
     * number of categoryNumber.
     *
     * @param id The identifier of the row to remove.
     * @return true if the delete succeeded, false otherwise.
     */
Scott Wheeler's avatar
Scott Wheeler committed
204
    bool removeRow(int id);
205 206

    /**
207 208
     * Installs button signal handlers for the buttons in @p row so that they
     * are called in response to GUI events, and removes any existing handlers.
209
     */
210
    void assignPositionHandlerForRow(Row &row);
211 212 213 214 215 216

    /**
     * This function sets up the internal view by creating the checkboxes and
     * the rows for each category.
     */
    void createTagRows();
217

218 219 220 221 222 223 224 225
    /**
     * Returns the value for \p category by retrieving the tag from m_exampleFile.
     * If \p category is Track, then an appropriate fixup will be applied if needed
     * to match the user's desired minimum width.
     *
     * @param category the category to retrieve the value for.
     * @return the string representation of the value for \p category.
     */
226 227
    QString fileCategoryValue(TagType category) const;

228 229 230 231 232 233 234 235
    /**
     * Returns the value for \p category by reading the user entry for that
     * category. If \p category is Track, then an appropriate fixup will be applied
     * if needed to match the user's desired minimum width.
     *
     * @param category the category to retrieve the value for.
     * @return the string representation of the value for \p category.
     */
236
    virtual QString categoryValue(TagType category) const override;
237

238 239 240 241 242 243
    /**
     * Returns the user-specified prefix string for \p category.
     *
     * @param category the category to retrieve the value for.
     * @return user-specified prefix string for \p category.
     */
244
    virtual QString prefix(const CategoryID &category) const override
245
    {
246
        return m_rows[findIdentifier(category)].options.prefix();
247 248
    }

249 250 251 252 253 254
    /**
     * Returns the user-specified suffix string for \p category.
     *
     * @param category the category to retrieve the value for.
     * @return user-specified suffix string for \p category.
     */
255
    virtual QString suffix(const CategoryID &category) const override
256
    {
257
        return m_rows[findIdentifier(category)].options.suffix();
258 259
    }

260 261 262 263 264 265
    /**
     * Returns the user-specified empty action for \p category.
     *
     * @param category the category to retrieve the value for.
     * @return user-specified empty action for \p category.
     */
266
    virtual TagRenamerOptions::EmptyActions emptyAction(const CategoryID &category) const override
267
    {
268
        return m_rows[findIdentifier(category)].options.emptyAction();
269 270
    }

271 272 273 274 275 276 277
    /**
     * Returns the user-specified empty text for \p category.  This text might
     * be used to replace an empty value.
     *
     * @param category the category to retrieve the value for.
     * @return the user-specified empty text for \p category.
     */
278
    virtual QString emptyText(const CategoryID &category) const override
279
    {
280
        return m_rows[findIdentifier(category)].options.emptyText();
281 282
    }

283
    /**
284
     * @return list of CategoryIDs corresponding to the user-specified category order.
285
     */
286
    virtual QList<CategoryID> categoryOrder() const override;
287

288 289 290
    /**
     * @return string that separates the tag values in the file name.
     */
291
    virtual QString separator() const override;
292

293 294 295
    /**
     * @return local path to the music folder used to store renamed files.
     */
296
    virtual QString musicFolder() const override;
297

298
    /**
299
     * @param categoryNum Zero-based number of category to get results for (if more than one).
300 301
     * @return the minimum width of the track category.
     */
302
    virtual int trackWidth(int categoryNum) const override
303
    {
304 305
        CategoryID id(Track, categoryNum);
        return m_rows[findIdentifier(id)].options.trackWidth();
306
    }
307

308 309 310 311 312 313 314
    /**
     * @param  index, the 0-based index for the folder boundary.
     * @return true if there should be a folder separator between category
     *         index and index + 1, and false otherwise.  Note that for purposes
     *         of this function, only categories that are required or non-empty
     *         should count.
     */
315
    virtual bool hasFolderSeparator(int index) const override;
316

317 318 319 320
    /**
     * @param category The category to get the status of.
     * @return true if \p category is disabled by the user, and false otherwise.
     */
321
    virtual bool isDisabled(const CategoryID &category) const override
322
    {
323
        return m_rows[findIdentifier(category)].options.disabled();
324 325
    }

326 327 328 329 330 331
    /**
     * This moves the widget \p l in the direction given by \p direction, taking
     * care to make sure that the checkboxes are not moved, and that they are
     * enabled or disabled as appropriate for the new layout, and that the up and
     * down buttons are also adjusted as necessary.
     *
332
     * @param id the identifier of the row to move
333 334
     * @param direction the direction to move
     */
Scott Wheeler's avatar
Scott Wheeler committed
335
    void moveItem(int id, MovementDirection direction);
336

337 338 339 340 341 342
    /**
     * This function actually performs the work of showing the options dialog for
     * \p category.
     *
     * @param category the category to show the options dialog for.
     */
343 344
    void showCategoryOptions(TagType category);

345
    /**
346
     * This function enables or disables the widget in the row identified by \p id,
347 348 349 350
     * controlled by \p enable.  This function also makes sure that checkboxes are
     * enabled or disabled as appropriate if they no longer make sense due to the
     * adjacent category being enabled or disabled.
     *
351
     * @param id the identifier of the row to change.  This is *not* the category to
352 353 354
     *        change.
     * @param enable enables the category if true, disables if false.
     */
355
    void setCategoryEnabled(int id, bool enable);
356

357
    /**
358
     * This function returns the identifier of the row at \p position.
359
     *
360 361
     * @param position The position to find the identifier of.
     * @return The unique id of the row at \p position.
362
     */
Scott Wheeler's avatar
Scott Wheeler committed
363
    int idOfPosition(int position) const;
364

365
    /**
366 367
     * This function returns the identifier of the row in the m_rows index that
     * contains \p category and matches \p categoryNum.
368 369
     *
     * @param category the category to find.
370 371
     * @return the identifier of the category, or MAX_CATEGORIES if it couldn't
     *         be found.
372
     */
Scott Wheeler's avatar
Scott Wheeler committed
373
    int findIdentifier(const CategoryID &category) const;
374

375
private slots:
376 377 378 379 380 381 382
    /**
     * This function reads the tags from \p file and ensures that the dialog will
     * use those tags until a different file is selected or dataSelected() is
     * called.
     *
     * @param file the path to the local file to read.
     */
383 384
    virtual void fileSelected(const QString &file);

385 386 387 388 389
    /**
     * This function reads the tags from the user-supplied examples and ensures
     * that the dialog will use those tags until a file is selected using
     * fileSelected().
     */
390 391
    virtual void dataSelected();

392 393
    /**
     * This function brings up a dialog that allows the user to edit the options
394
     * for \p id.
395
     *
396
     * @param id the unique id to bring up the options for.
397
     */
398
    virtual void showCategoryOption(int id);
399

400
    /**
401 402
     * This function removes the row identified by id and updates the internal data to be
     * consistent again, by forwarding the call to removeRow().
403
     *
404
     * @param id The unique id to update
405
     */
406
    virtual void slotRemoveRow(int id);
407

408 409 410
    /**
     * This function moves \p category up in the layout.
     *
411
     * @param id the unique id of the widget to move up.
412
     */
413
    virtual void moveItemUp(int id);
414

415 416 417
    /**
     * This function moves \p category down in the layout.
     *
418
     * @param id the unique id of the widget to move down.
419
     */
420
    virtual void moveItemDown(int id);
421

422 423 424
    /**
     * This slot should be called whenever the example input dialog is shown.
     */
425 426
    virtual void exampleDialogShown();

427
    /**
Yuri Chornoivan's avatar
Yuri Chornoivan committed
428
     * This slot should be called whenever the example input dialog is hidden.
429
     */
430 431 432
    virtual void exampleDialogHidden();

private:
433
    /// This is the frame that holds all of the category widgets and checkboxes.
434 435 436
    QFrame *m_mainFrame;

    Ui::FileRenamerBase *m_ui;
437

438
    /**
439 440 441 442 443 444 445 446
     * This is the meat of the widget, it holds the rows for the user configuration.  It is
     * initially created such that m_rows[0] is the top and row + 1 is the row just below.
     * However, this is NOT NECESSARILY true, so don't rely on this.  As soon as the user
     * clicks an arrow to move a row then the order will be messed up.  Use row.position to
     * determine where the row is in the GUI.
     *
     * @see idOfPosition
     * @see findIdentifier
447
     */
448
    Rows m_rows;
449

450
    /**
451 452 453
     * This holds an array of checkboxes that allow the user to insert folder
     * separators in between categories.
     */
454
    QVector<QCheckBox *> m_folderSwitches;
455 456 457

    ExampleOptionsDialog *m_exampleDialog;

458
    /// This is true if we're reading example tags from m_exampleFile.
459
    bool m_exampleFromFile;
460
    QString m_exampleFile;
461
};
462

463 464 465 466 467 468
/**
 * This class contains the backend code to actually implement the file renaming.  It performs
 * the function of moving the files from one location to another, constructing the file name
 * based off of the user's options (see ConfigCategoryReader) and of setting folder icons
 * if appropriate.
 *
469
 * @author Michael Pyne <mpyne@kde.org>
470
 */
471
class FileRenamer final
472 473
{
public:
474
    FileRenamer();
475

476 477 478 479 480 481
    /**
     * Renames the filename on disk of the file represented by item according
     * to the user configuration stored in KConfig.
     *
     * @param item The item to rename.
     */
482
    void rename(PlaylistItem *item);
483

484 485 486 487 488 489 490
    /**
     * Renames the filenames on disk of the files given in items according to
     * the user configuration stored in KConfig.
     *
     * @param items The items to rename.
     */
    void rename(const PlaylistItemList &items);
491

492
    /**
493 494 495 496
     * Returns the file name that would be generated based on the options read from
     * interface, which must implement CategoryReaderInterface.  (A whole interface is used
     * so that we can re-use the code to generate filenames from a in-memory GUI and from
     * KConfig).
497
     *
498
     * @param interface object to read options/data from.
499
     */
500
    static QString fileName(const CategoryReaderInterface &interface);
501

502
private:
503 504 505 506 507
    /**
     * Sets the folder icon for elements of the destination path for item (if
     * there is not already a folder icon set, and if the folder's name has
     * the album name.
     */
508
    void setFolderIcon(const QUrl &dst, const PlaylistItem *item);
509

510 511 512 513
    /**
     * Attempts to rename the file from \a src to \a dest.  Returns true if the
     * operation succeeded.
     */
514
    bool moveFile(const QString &src, const QString &dest);
515 516
};

517
#endif /* JUK_FILERENAMER_H */
518

519
// vim: set et sw=4 tw=0 sta: