filerenamer.h 18.1 KB
Newer Older
1
2
/***************************************************************************
    begin                : Thu Oct 28 2004
3
    copyright            : (C) 2004, 2007 by Michael Pyne
4
5
6
7
8
9
10
11
12
13
14
15
16
                         : (C) 2003 Frerich Raabe <raabe@kde.org>
    email                : michael.pyne@kdemail.net
***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

17
18
#ifndef FILERENAMER_H
#define FILERENAMER_H
19

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

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

class QCheckBox;
class QPushButton;
30
class QSignalMapper;
31

32
33
34
35
36
37
38
39
class ExampleOptionsDialog;
class PlaylistItem;

template<class T>
class QList;

typedef QList<PlaylistItem *> PlaylistItemList;

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

/**
 * 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.
 */
struct Row
{
50
51
    Row() : widget(0), upButton(0), downButton(0), enableButton(0) {}

52
    QWidget *widget;
53
54
55

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

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

62
63
64
65
66
67
68
/**
 * 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).
 */
69
typedef QVector<Row> Rows;
70
71
72
73
74
75
76

/**
 * Holds a list directory separator checkboxes which separate a row.  There
 * should always be 1 less than the number of rows in the GUI.
 *
 * Used for ConfigCategoryReader.
 */
77
typedef QVector<QCheckBox *> DirSeparatorCheckBoxes;
78
79
80
81
82
83
84
85

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

86
87
88
89
90
/**
 * 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.
91
 *
92
 * @author Michael Pyne <michael.pyne@kdemail.net>
93
 */
94
95
96
97
class ConfigCategoryReader : public CategoryReaderInterface
{
public:
    // ConfigCategoryReader specific members
98

99
    ConfigCategoryReader();
100

101
102
    const PlaylistItem *playlistItem() const { return m_currentItem; }
    void setPlaylistItem(const PlaylistItem *item) { m_currentItem = item; }
103

104
105
106
    // CategoryReaderInterface reimplementations

    virtual QString categoryValue(TagType type) const;
107
108
109
110
    virtual QString prefix(const CategoryID &category) const;
    virtual QString suffix(const CategoryID &category) const;
    virtual TagRenamerOptions::EmptyActions emptyAction(const CategoryID &category) const;
    virtual QString emptyText(const CategoryID &category) const;
111
    virtual QList<CategoryID> categoryOrder() const;
112
    virtual QString separator() const;
Scott Wheeler's avatar
Scott Wheeler committed
113
    virtual QString musicFolder() const;
Scott Wheeler's avatar
Scott Wheeler committed
114
115
    virtual int trackWidth(int categoryNum) const;
    virtual bool hasFolderSeparator(int index) const;
116
    virtual bool isDisabled(const CategoryID &category) const;
117
118

private:
119
    const PlaylistItem *m_currentItem;
120
    CategoryOptionsMap m_options;
121
    QList<CategoryID> m_categoryOrder;
122
    QString m_separator;
Scott Wheeler's avatar
Scott Wheeler committed
123
    QString m_musicFolder;
124
    QVector<bool> m_folderSeparators;
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
};

/**
 * 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.
 *
 * @author Michael Pyne <michael.pyne@kdemail.net>
 */
141
class FileRenamerWidget : public QWidget, public CategoryReaderInterface
142
{
143
144
    Q_OBJECT

145
public:
146
147
148
    FileRenamerWidget(QWidget *parent);
    ~FileRenamerWidget();

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

152
153
154
155
156
157
    /**
     * 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.
     */
158
159
160
    void saveConfig();

protected slots:
161
162
163
164
165
    /**
     * 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.
     */
166
167
    virtual void exampleTextChanged();

168
169
170
171
    /**
     * This function shows the example dialog if it is hidden, and hides the
     * example dialog if it is shown.
     */
172
173
    virtual void toggleExampleDialog();

174
175
176
177
178
179
    /**
     * This function inserts the currently selected category, so that the
     * user can use duplicate tags in the file renamer.
     */
    virtual void insertCategory();

180
private:
181
182
183
184
    /**
     * This function initializes the category options by loading the data from
     * the global KConfig object.  This is called automatically in the constructor.
     */
185
186
    void loadConfig();

187
    /**
188
189
     * This function adds a "Insert Folder separator" checkbox to the end of
     * the current layout.  The setting defaults to being unchecked.
190
     */
191
    void addFolderSeparatorCheckbox();
192

193
    /**
194
195
196
     * 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.
197
     *
198
199
     * @param category Type of row to append.
     * @return identifier of newly added row.
200
     */
Scott Wheeler's avatar
Scott Wheeler committed
201
    int addRowCategory(TagType category);
202
203
204
205
206
207
208
209

    /**
     * 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
210
    bool removeRow(int id);
211
212
213
214
215
216
217
218
219

    /**
     * Updates the mappings currently set for the row identified by oldId so
     * that they emit newId instead.  Does not actually delete the row given
     * by oldId.
     *
     * @param oldId The identifier of the row to change mappings for.
     * @param newId The identifier to use instead.
     */
Scott Wheeler's avatar
Scott Wheeler committed
220
    void moveSignalMappings(int oldId, int newId);
221
222
223
224
225
226

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

228
229
230
231
232
233
234
235
    /**
     * 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.
     */
236
237
    QString fileCategoryValue(TagType category) const;

238
239
240
241
242
243
244
245
    /**
     * 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.
     */
246
247
    virtual QString categoryValue(TagType category) const;

248
249
250
251
252
253
    /**
     * 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.
     */
254
    virtual QString prefix(const CategoryID &category) const
255
    {
256
        return m_rows[findIdentifier(category)].options.prefix();
257
258
    }

259
260
261
262
263
264
    /**
     * 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.
     */
265
    virtual QString suffix(const CategoryID &category) const
266
    {
267
        return m_rows[findIdentifier(category)].options.suffix();
268
269
    }

270
271
272
273
274
275
    /**
     * 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.
     */
276
    virtual TagRenamerOptions::EmptyActions emptyAction(const CategoryID &category) const
277
    {
278
        return m_rows[findIdentifier(category)].options.emptyAction();
279
280
    }

281
282
283
284
285
286
287
    /**
     * 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.
     */
288
    virtual QString emptyText(const CategoryID &category) const
289
    {
290
        return m_rows[findIdentifier(category)].options.emptyText();
291
292
    }

293
    /**
294
     * @return list of CategoryIDs corresponding to the user-specified category order.
295
     */
296
    virtual QList<CategoryID> categoryOrder() const;
297

298
299
300
    /**
     * @return string that separates the tag values in the file name.
     */
301
302
    virtual QString separator() const;

303
304
305
    /**
     * @return local path to the music folder used to store renamed files.
     */
Scott Wheeler's avatar
Scott Wheeler committed
306
    virtual QString musicFolder() const;
307

308
    /**
309
     * @param categoryNum Zero-based number of category to get results for (if more than one).
310
311
     * @return the minimum width of the track category.
     */
Scott Wheeler's avatar
Scott Wheeler committed
312
    virtual int trackWidth(int categoryNum) const
313
    {
314
315
        CategoryID id(Track, categoryNum);
        return m_rows[findIdentifier(id)].options.trackWidth();
316
    }
317

318
319
320
321
322
323
324
    /**
     * @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.
     */
Scott Wheeler's avatar
Scott Wheeler committed
325
    virtual bool hasFolderSeparator(int index) const;
326

327
328
329
330
    /**
     * @param category The category to get the status of.
     * @return true if \p category is disabled by the user, and false otherwise.
     */
331
    virtual bool isDisabled(const CategoryID &category) const
332
    {
333
        return m_rows[findIdentifier(category)].options.disabled();
334
335
    }

336
337
338
339
340
341
    /**
     * 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.
     *
342
     * @param id the identifier of the row to move
343
344
     * @param direction the direction to move
     */
Scott Wheeler's avatar
Scott Wheeler committed
345
    void moveItem(int id, MovementDirection direction);
346

347
348
349
350
351
352
    /**
     * This function actually performs the work of showing the options dialog for
     * \p category.
     *
     * @param category the category to show the options dialog for.
     */
353
354
    void showCategoryOptions(TagType category);

355
    /**
356
     * This function enables or disables the widget in the row identified by \p id,
357
358
359
360
     * 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.
     *
361
     * @param id the identifier of the row to change.  This is *not* the category to
362
363
364
     *        change.
     * @param enable enables the category if true, disables if false.
     */
365
    void setCategoryEnabled(int id, bool enable);
366

367
368
369
    /**
     * This function enables all of the up buttons.
     */
370
    void enableAllUpButtons();
371

372
373
374
    /**
     * This function enables all of the down buttons.
     */
375
    void enableAllDownButtons();
376

377
    /**
378
     * This function returns the identifier of the row at \p position.
379
     *
380
381
     * @param position The position to find the identifier of.
     * @return The unique id of the row at \p position.
382
     */
Scott Wheeler's avatar
Scott Wheeler committed
383
    int idOfPosition(int position) const;
384

385
    /**
386
387
     * This function returns the identifier of the row in the m_rows index that
     * contains \p category and matches \p categoryNum.
388
389
     *
     * @param category the category to find.
390
391
     * @return the identifier of the category, or MAX_CATEGORIES if it couldn't
     *         be found.
392
     */
Scott Wheeler's avatar
Scott Wheeler committed
393
    int findIdentifier(const CategoryID &category) const;
394

395
private slots:
396
397
398
399
400
401
402
    /**
     * 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.
     */
403
404
    virtual void fileSelected(const QString &file);

405
406
407
408
409
    /**
     * 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().
     */
410
411
    virtual void dataSelected();

412
413
    /**
     * This function brings up a dialog that allows the user to edit the options
414
     * for \p id.
415
     *
416
     * @param id the unique id to bring up the options for.
417
     */
418
    virtual void showCategoryOption(int id);
419

420
    /**
421
422
423
     * This function removes the row identified by id and updates the internal data to be
     * consistent again, by forwarding the call to removeRow().
     * This roundabout way is done due to QSignalMapper.
424
     *
425
     * @param id The unique id to update
426
     */
427
    virtual void slotRemoveRow(int id);
428

429
430
431
    /**
     * This function moves \p category up in the layout.
     *
432
     * @param id the unique id of the widget to move up.
433
     */
434
    virtual void moveItemUp(int id);
435

436
437
438
    /**
     * This function moves \p category down in the layout.
     *
439
     * @param id the unique id of the widget to move down.
440
     */
441
    virtual void moveItemDown(int id);
442

443
444
445
    /**
     * This slot should be called whenever the example input dialog is shown.
     */
446
447
    virtual void exampleDialogShown();

448
449
450
    /**
     * This slot should be called whever the example input dialog is hidden.
     */
451
452
453
    virtual void exampleDialogHidden();

private:
454
    /// This is the frame that holds all of the category widgets and checkboxes.
455
456
457
    QFrame *m_mainFrame;

    Ui::FileRenamerBase *m_ui;
458

459
    /**
460
461
462
463
464
465
466
467
     * 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
468
     */
469
    Rows m_rows;
470

471
    /**
472
473
474
     * This holds an array of checkboxes that allow the user to insert folder
     * separators in between categories.
     */
475
    DirSeparatorCheckBoxes m_folderSwitches;
476
477
478

    ExampleOptionsDialog *m_exampleDialog;

479
    /// This is true if we're reading example tags from m_exampleFile.
480
    bool m_exampleFromFile;
481
    QString m_exampleFile;
482

483
484
485
486
487
488
    // Used to map signals from rows to the correct widget.
    QSignalMapper *mapper;
    QSignalMapper *toggleMapper;
    QSignalMapper *upMapper;
    QSignalMapper *downMapper;
};
489

490
491
492
493
494
495
496
497
/**
 * 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.
 *
 * @author Michael Pyne <michael.pyne@kdemail.net>
 */
498
499
500
class FileRenamer
{
public:
501
    FileRenamer();
502

503
504
505
506
507
508
    /**
     * 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.
     */
509
    void rename(PlaylistItem *item);
510

511
512
513
514
515
516
517
    /**
     * 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);
518

519
    /**
520
521
522
523
     * 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).
524
     *
525
     * @param interface object to read options/data from.
526
     */
527
    static QString fileName(const CategoryReaderInterface &interface);
528

529
private:
530
531
532
533
534
    /**
     * 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.
     */
Scott Wheeler's avatar
Scott Wheeler committed
535
    void setFolderIcon(const KUrl &dst, const PlaylistItem *item);
536

537
538
539
540
    /**
     * Attempts to rename the file from \a src to \a dest.  Returns true if the
     * operation succeeded.
     */
541
    bool moveFile(const QString &src, const QString &dest);
542
543
};

544
#endif /* FILERENAMER_H */
545

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