improved CLI and handling of attachments
This commit is contained in:
parent
cce87915da
commit
bc9e21950a
|
@ -90,12 +90,16 @@ int main(int argc, char *argv[])
|
|||
encodingArg.setRequiredValueCount(1);
|
||||
encodingArg.setValueNames({"latin1/utf8/utf16le/utf16be"});
|
||||
encodingArg.setCombinable(true);
|
||||
Argument attachmentsArg("attachments", string(), "specifies attachments to be added/updated/removed");
|
||||
attachmentsArg.setRequiredValueCount(-1);
|
||||
attachmentsArg.setValueNames({"path=some/file", "name=Some name", "desc=Some desc", "mime=mime/type", ",", "path=another/file"});
|
||||
attachmentsArg.setCombinable(true);
|
||||
Argument setTagInfoArg("set-tag-info", "set", "sets the values of all specified tag fields");
|
||||
setTagInfoArg.setDenotesOperation(true);
|
||||
setTagInfoArg.setCallback(std::bind(Cli::setTagInfo, _1, std::cref(filesArg), std::cref(removeOtherFieldsArg), std::cref(treatUnknownFilesAsMp3FilesArg), std::cref(id3v1UsageArg), std::cref(id3v2UsageArg), std::cref(mergeMultipleSuccessiveTagsArg), std::cref(id3v2VersionArg), std::cref(encodingArg), std::cref(verboseArg)));
|
||||
setTagInfoArg.setCallback(std::bind(Cli::setTagInfo, _1, std::cref(filesArg), std::cref(removeOtherFieldsArg), std::cref(treatUnknownFilesAsMp3FilesArg), std::cref(id3v1UsageArg), std::cref(id3v2UsageArg), std::cref(mergeMultipleSuccessiveTagsArg), std::cref(id3v2VersionArg), std::cref(encodingArg), std::cref(attachmentsArg), std::cref(verboseArg)));
|
||||
setTagInfoArg.setRequiredValueCount(-1);
|
||||
setTagInfoArg.setValueNames({"title=foo", "album=bar", "cover=/path/to/file"});
|
||||
setTagInfoArg.setSecondaryArguments({&filesArg, &removeOtherFieldsArg, &treatUnknownFilesAsMp3FilesArg, &id3v1UsageArg, &id3v2UsageArg, &mergeMultipleSuccessiveTagsArg, &id3v2VersionArg, &encodingArg, &verboseArg});
|
||||
setTagInfoArg.setSecondaryArguments({&filesArg, &removeOtherFieldsArg, &treatUnknownFilesAsMp3FilesArg, &id3v1UsageArg, &id3v2UsageArg, &mergeMultipleSuccessiveTagsArg, &id3v2VersionArg, &encodingArg, &attachmentsArg, &verboseArg});
|
||||
// extract cover
|
||||
Argument extractFieldArg("extract", "ext", "extracts the specified field from the specified file");
|
||||
extractFieldArg.setRequiredValueCount(1);
|
||||
|
|
|
@ -93,9 +93,8 @@ QString incremented(const QString &str, unsigned int toIncrement = 1)
|
|||
return res;
|
||||
}
|
||||
|
||||
void printNotifications(const MediaFileInfo &fileInfo, const char *head = nullptr, bool beVerbose = false)
|
||||
void printNotifications(NotificationList ¬ifications, const char *head = nullptr, bool beVerbose = false)
|
||||
{
|
||||
auto notifications = fileInfo.gatherRelatedNotifications();
|
||||
if(!beVerbose) {
|
||||
for(const auto ¬ification : notifications) {
|
||||
switch(notification.type()) {
|
||||
|
@ -119,13 +118,17 @@ void printNotifications(const MediaFileInfo &fileInfo, const char *head = nullpt
|
|||
case NotificationType::Debug:
|
||||
if(beVerbose) {
|
||||
cout << "Debug ";
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case NotificationType::Information:
|
||||
if(beVerbose) {
|
||||
cout << "Information ";
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case NotificationType::Warning:
|
||||
cout << "Warning ";
|
||||
break;
|
||||
|
@ -142,6 +145,13 @@ void printNotifications(const MediaFileInfo &fileInfo, const char *head = nullpt
|
|||
}
|
||||
}
|
||||
|
||||
void printNotifications(const MediaFileInfo &fileInfo, const char *head = nullptr, bool beVerbose = false)
|
||||
{
|
||||
NotificationList notifications;
|
||||
fileInfo.gatherRelatedNotifications(notifications);
|
||||
printNotifications(notifications, head, beVerbose);
|
||||
}
|
||||
|
||||
void printFieldNames(const StringVector ¶meterValues)
|
||||
{
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
|
@ -389,6 +399,140 @@ unsigned int parseFieldDenotations(const StringVector &fieldDenotations, bool re
|
|||
return validDenotations;
|
||||
}
|
||||
|
||||
enum class AttachmentAction {
|
||||
Add,
|
||||
UpdateById,
|
||||
UpdateByName,
|
||||
RemoveById,
|
||||
RemoveByName
|
||||
};
|
||||
|
||||
class AttachmentInfo
|
||||
{
|
||||
public:
|
||||
AttachmentInfo();
|
||||
void apply(AbstractContainer *container);
|
||||
void apply(AbstractAttachment *attachment);
|
||||
void reset();
|
||||
bool next(AbstractContainer *container);
|
||||
|
||||
AttachmentAction action;
|
||||
uint64 id;
|
||||
string path;
|
||||
string name;
|
||||
string mime;
|
||||
string desc;
|
||||
};
|
||||
|
||||
AttachmentInfo::AttachmentInfo() :
|
||||
action(AttachmentAction::Add),
|
||||
id(0)
|
||||
{}
|
||||
|
||||
void AttachmentInfo::apply(AbstractContainer *container)
|
||||
{
|
||||
static const string context("applying specified attachments");
|
||||
AbstractAttachment *attachment = nullptr;
|
||||
bool attachmentFound = false;
|
||||
switch(action) {
|
||||
case AttachmentAction::Add:
|
||||
if(path.empty() || name.empty()) {
|
||||
container->addNotification(NotificationType::Critical, "No name or path specified for new attachment to be added.", context);
|
||||
return;
|
||||
}
|
||||
apply(container->createAttachment());
|
||||
break;
|
||||
case AttachmentAction::UpdateById:
|
||||
for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) {
|
||||
attachment = container->attachment(i);
|
||||
if(attachment->id() == id) {
|
||||
apply(attachment);
|
||||
attachmentFound = true;
|
||||
}
|
||||
}
|
||||
if(!attachmentFound == true) {
|
||||
container->addNotification(NotificationType::Critical, "Attachment with the specified ID \"" + numberToString(id) + "\" does not exist and hence can't be updated.", context);
|
||||
}
|
||||
break;
|
||||
case AttachmentAction::UpdateByName:
|
||||
for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) {
|
||||
attachment = container->attachment(i);
|
||||
if(attachment->name() == name) {
|
||||
apply(attachment);
|
||||
attachmentFound = true;
|
||||
}
|
||||
}
|
||||
if(!attachmentFound == true) {
|
||||
container->addNotification(NotificationType::Critical, "Attachment with the specified name \"" + name + "\" does not exist and hence can't be updated.", context);
|
||||
}
|
||||
break;
|
||||
case AttachmentAction::RemoveById:
|
||||
for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) {
|
||||
attachment = container->attachment(i);
|
||||
if(attachment->id() == id) {
|
||||
attachment->setIgnored(true);
|
||||
attachmentFound = true;
|
||||
}
|
||||
}
|
||||
if(!attachmentFound == true) {
|
||||
container->addNotification(NotificationType::Critical, "Attachment with the specified ID \"" + numberToString(id) + "\" does not exist and hence can't be removed.", context);
|
||||
}
|
||||
break;
|
||||
case AttachmentAction::RemoveByName:
|
||||
for(size_t i = 0, count = container->attachmentCount(); i < count; ++i) {
|
||||
attachment = container->attachment(i);
|
||||
if(attachment->name() == name) {
|
||||
attachment->setIgnored(true);
|
||||
attachmentFound = true;
|
||||
}
|
||||
}
|
||||
if(!attachmentFound == true) {
|
||||
container->addNotification(NotificationType::Critical, "Attachment with the specified name \"" + name + "\" does not exist and hence can't be removed.", context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AttachmentInfo::apply(AbstractAttachment *attachment)
|
||||
{
|
||||
if(id) {
|
||||
attachment->setId(id);
|
||||
}
|
||||
if(!path.empty()) {
|
||||
attachment->setFile(path);
|
||||
}
|
||||
if(!name.empty()) {
|
||||
attachment->setName(name);
|
||||
}
|
||||
if(!mime.empty()) {
|
||||
attachment->setMimeType(mime);
|
||||
}
|
||||
if(!desc.empty()) {
|
||||
attachment->setDescription(desc);
|
||||
}
|
||||
}
|
||||
|
||||
void AttachmentInfo::reset()
|
||||
{
|
||||
action = AttachmentAction::Add;
|
||||
id = 0;
|
||||
path.clear();
|
||||
name.clear();
|
||||
mime.clear();
|
||||
desc.clear();
|
||||
}
|
||||
|
||||
bool AttachmentInfo::next(AbstractContainer *container)
|
||||
{
|
||||
if(!id && path.empty() && name.empty() && mime.empty() && desc.empty()) {
|
||||
// skip empty attachment infos
|
||||
return false;
|
||||
}
|
||||
apply(container);
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
void generateFileInfo(const StringVector ¶meterValues, const Argument &inputFileArg, const Argument &outputFileArg, const Argument &validateArg)
|
||||
{
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
|
@ -523,7 +667,7 @@ void displayFileInfo(const StringVector &, const Argument &filesArg, const Argum
|
|||
{ // chapters
|
||||
const auto chapters = fileInfo.chapters();
|
||||
if(!chapters.empty()) {
|
||||
for(const AbstractChapter *chapter : chapters) {
|
||||
for(const auto *chapter : chapters) {
|
||||
printProperty("ID", chapter->id());
|
||||
if(!chapter->names().empty()) {
|
||||
printProperty("Name", static_cast<string>(chapter->names().front()));
|
||||
|
@ -597,7 +741,7 @@ void displayTagInfo(const StringVector ¶meterValues, const Argument &filesAr
|
|||
cout << "none";
|
||||
} else {
|
||||
try {
|
||||
QString textValue = tagValueToQString(value);
|
||||
const auto textValue = tagValueToQString(value);
|
||||
if(textValue.isEmpty()) {
|
||||
cout << "can't display here (see --extract)";
|
||||
} else {
|
||||
|
@ -628,7 +772,7 @@ void displayTagInfo(const StringVector ¶meterValues, const Argument &filesAr
|
|||
void setTagInfo(const StringVector ¶meterValues, const Argument &filesArg, const Argument &removeOtherFieldsArg,
|
||||
const Argument &treatUnknownFilesAsMp3FilesArg, const Argument &id3v1UsageArg, const Argument &id3v2UsageArg,
|
||||
const Argument &mergeMultipleSuccessiveTagsArg, const Argument &id3v2VersionArg, const Argument &encodingArg,
|
||||
const Argument &verboseArg)
|
||||
const Argument &attachmentsArg, const Argument &verboseArg)
|
||||
{
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
if(!filesArg.valueCount()) {
|
||||
|
@ -637,8 +781,8 @@ void setTagInfo(const StringVector ¶meterValues, const Argument &filesArg, c
|
|||
}
|
||||
FieldDenotation fields[knownFieldArraySize];
|
||||
unsigned int validDenotations = parseFieldDenotations(parameterValues, false, fields);
|
||||
if(!validDenotations) {
|
||||
cout << "Error: No fields have been specified." << endl;
|
||||
if(!validDenotations && !attachmentsArg.valueCount()) {
|
||||
cout << "Error: No fields/attachments have been specified." << endl;
|
||||
return;
|
||||
}
|
||||
uint32 id3v2Version = 3;
|
||||
|
@ -653,20 +797,25 @@ void setTagInfo(const StringVector ¶meterValues, const Argument &filesArg, c
|
|||
cout << "Warning: The specified ID3v2 version \"" << id3v2VersionArg.values().front() << "\" is invalid and will be ingored." << endl;
|
||||
}
|
||||
}
|
||||
TagTextEncoding encoding = parseEncodingDenotation(encodingArg, TagTextEncoding::Utf8);
|
||||
TagTextEncoding denotedEncoding = parseEncodingDenotation(encodingArg, TagTextEncoding::Utf8);
|
||||
TagUsage id3v1Usage = parseUsageDenotation(id3v1UsageArg, TagUsage::KeepExisting);
|
||||
TagUsage id3v2Usage = parseUsageDenotation(id3v2UsageArg, TagUsage::Always);
|
||||
unsigned int fileIndex = 0;
|
||||
static const string context("setting tags");
|
||||
MediaFileInfo fileInfo;
|
||||
NotificationList notifications;
|
||||
for(const auto &file : filesArg.values()) {
|
||||
try {
|
||||
// parse tags
|
||||
cout << "Setting tag information for \"" << file << "\" ..." << endl;
|
||||
notifications.clear();
|
||||
fileInfo.setPath(file);
|
||||
fileInfo.parseTags();
|
||||
cout << "Setting tag information for \"" << file << "\" ..." << endl;
|
||||
auto tags = fileInfo.tags();
|
||||
fileInfo.parseTracks();
|
||||
fileInfo.createAppropriateTags(treatUnknownFilesAsMp3FilesArg.isPresent(), id3v1Usage, id3v2Usage, mergeMultipleSuccessiveTagsArg.isPresent(), !id3v2VersionArg.isPresent(), id3v2Version);
|
||||
if(tags.size()) {
|
||||
auto container = fileInfo.container();
|
||||
auto tags = fileInfo.tags();
|
||||
if(!tags.empty()) {
|
||||
// iterate through all tags
|
||||
for(auto *tag : tags) {
|
||||
if(removeOtherFieldsArg.isPresent()) {
|
||||
|
@ -705,14 +854,14 @@ void setTagInfo(const StringVector ¶meterValues, const Argument &filesArg, c
|
|||
value.setMimeType(fileInfo.mimeType());
|
||||
tag->setValue(field, move(value));
|
||||
} catch (ios_base::failure &) {
|
||||
cout << "An IO error occured when parsing the specified cover file." << endl;
|
||||
fileInfo.addNotification(NotificationType::Critical, "An IO error occured when parsing the specified cover file.", context);
|
||||
} catch (Media::Failure &) {
|
||||
cout << "Unable to parse specified cover file." << endl;
|
||||
fileInfo.addNotification(NotificationType::Critical, "Unable to parse specified cover file.", context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TagTextEncoding usedEncoding = encoding;
|
||||
if(!tag->canEncodingBeUsed(encoding)) {
|
||||
TagTextEncoding usedEncoding = denotedEncoding;
|
||||
if(!tag->canEncodingBeUsed(denotedEncoding)) {
|
||||
usedEncoding = tag->proposedTextEncoding();
|
||||
}
|
||||
tag->setValue(field, qstringToTagValue(selectedDenotatedValue->second, usedEncoding));
|
||||
|
@ -725,21 +874,74 @@ void setTagInfo(const StringVector ¶meterValues, const Argument &filesArg, c
|
|||
field = nextKnownField(field);
|
||||
}
|
||||
}
|
||||
try {
|
||||
fileInfo.applyChanges();
|
||||
cout << "Changes have been applied." << endl;
|
||||
} catch(ApplicationUtilities::Failure &) {
|
||||
cout << "A failure occured when applying the changes." << endl;
|
||||
}
|
||||
} else {
|
||||
cout << " Can not create appropriate tags for file." << endl;
|
||||
fileInfo.addNotification(NotificationType::Critical, "Can not create appropriate tags for file.", context);
|
||||
}
|
||||
} catch(ios_base::failure &) {
|
||||
bool attachmentsModified = false;
|
||||
if(attachmentsArg.isPresent()) {
|
||||
static const string context("setting attachments");
|
||||
fileInfo.parseAttachments();
|
||||
if(fileInfo.attachmentsParsingStatus() == ParsingStatus::Ok) {
|
||||
if(container) {
|
||||
AttachmentInfo currentInfo;
|
||||
for(const auto &value : attachmentsArg.values()) {
|
||||
const auto *data = value.data();
|
||||
if(value == ",") {
|
||||
attachmentsModified |= currentInfo.next(container);
|
||||
} else if(value == "add") {
|
||||
currentInfo.action = AttachmentAction::Add;
|
||||
} else if(value == "update-by-id") {
|
||||
currentInfo.action = AttachmentAction::UpdateById;
|
||||
} else if(value == "update-by-name") {
|
||||
currentInfo.action = AttachmentAction::UpdateByName;
|
||||
} else if(value == "remove-by-id") {
|
||||
currentInfo.action = AttachmentAction::RemoveById;
|
||||
} else if(value == "remove-by-name") {
|
||||
currentInfo.action = AttachmentAction::RemoveByName;
|
||||
} else if(!strncmp(data, "id=", 3)) {
|
||||
try {
|
||||
currentInfo.id = stringToNumber<uint64, string>(data + 3);
|
||||
} catch(const ConversionException &) {
|
||||
container->addNotification(NotificationType::Warning, "The specified attachment ID \"" + string(data + 3) + "\" is invalid.", context);
|
||||
}
|
||||
} else if(!strncmp(data, "path=", 5)) {
|
||||
currentInfo.path = data + 5;
|
||||
} else if(!strncmp(data, "name=", 5)) {
|
||||
currentInfo.name = data + 5;
|
||||
} else if(!strncmp(data, "mime=", 5)) {
|
||||
currentInfo.mime = data + 5;
|
||||
} else if(!strncmp(data, "desc=", 5)) {
|
||||
currentInfo.desc = data + 5;
|
||||
} else {
|
||||
container->addNotification(NotificationType::Warning, "The attachment specification \"" + value + "\" is invalid and will be ignored.", context);
|
||||
}
|
||||
}
|
||||
attachmentsModified |= currentInfo.next(container);
|
||||
} else {
|
||||
fileInfo.addNotification(NotificationType::Critical, "Unable to assign attachments because the container object has not been initialized.", context);
|
||||
}
|
||||
} else {
|
||||
// notification will be added by the file info automatically
|
||||
}
|
||||
}
|
||||
if(!tags.empty() || attachmentsModified) {
|
||||
try {
|
||||
// save parsing notifications because notifications of sub objects like tags, tracks, ... will be gone after applying changes
|
||||
fileInfo.gatherRelatedNotifications(notifications);
|
||||
fileInfo.invalidateNotifications();
|
||||
fileInfo.applyChanges();
|
||||
fileInfo.gatherRelatedNotifications(notifications);
|
||||
cout << "Changes have been applied." << endl;
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cout << "Error: Failed to apply changes." << endl;
|
||||
}
|
||||
}
|
||||
} catch(const ios_base::failure &) {
|
||||
cout << "Error: An IO failure occured when reading/writing the file \"" << file << "\"." << endl;
|
||||
} catch(ApplicationUtilities::Failure &) {
|
||||
} catch(const ApplicationUtilities::Failure &) {
|
||||
cout << "Error: A parsing failure occured when reading/writing the file \"" << file << "\"." << endl;
|
||||
}
|
||||
printNotifications(fileInfo, "Notifications:", verboseArg.isPresent());
|
||||
printNotifications(notifications, "Notifications:", verboseArg.isPresent());
|
||||
++fileIndex;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ void printFieldNames(const ApplicationUtilities::StringVector ¶meterValues);
|
|||
void displayFileInfo(const ApplicationUtilities::StringVector &, const ApplicationUtilities::Argument &filesArg, const ApplicationUtilities::Argument &verboseArg);
|
||||
void generateFileInfo(const ApplicationUtilities::StringVector ¶meterValues, const ApplicationUtilities::Argument &inputFileArg, const ApplicationUtilities::Argument &outputFileArg, const ApplicationUtilities::Argument &validateArg);
|
||||
void displayTagInfo(const ApplicationUtilities::StringVector ¶meterValues, const ApplicationUtilities::Argument &filesArg, const ApplicationUtilities::Argument &verboseArg);
|
||||
void setTagInfo(const ApplicationUtilities::StringVector ¶meterValues, const ApplicationUtilities::Argument &filesArg, const ApplicationUtilities::Argument &removeOtherFieldsArg, const ApplicationUtilities::Argument &treatUnknownFilesAsMp3FilesArg, const ApplicationUtilities::Argument &id3v1UsageArg, const ApplicationUtilities::Argument &id3v2UsageArg, const ApplicationUtilities::Argument &mergeMultipleSuccessiveTagsArg, const ApplicationUtilities::Argument &id3v2VersionArg, const ApplicationUtilities::Argument &encodingArg, const ApplicationUtilities::Argument &verboseArg);
|
||||
void setTagInfo(const ApplicationUtilities::StringVector ¶meterValues, const ApplicationUtilities::Argument &filesArg, const ApplicationUtilities::Argument &removeOtherFieldsArg, const ApplicationUtilities::Argument &treatUnknownFilesAsMp3FilesArg, const ApplicationUtilities::Argument &id3v1UsageArg, const ApplicationUtilities::Argument &id3v2UsageArg, const ApplicationUtilities::Argument &mergeMultipleSuccessiveTagsArg, const ApplicationUtilities::Argument &id3v2VersionArg, const ApplicationUtilities::Argument &encodingArg, const ApplicationUtilities::Argument &attachmentsArg, const ApplicationUtilities::Argument &verboseArg);
|
||||
void extractField(const ApplicationUtilities::StringVector ¶meterValues, const ApplicationUtilities::Argument &inputFileArg, const ApplicationUtilities::Argument &outputFileArg, const ApplicationUtilities::Argument &verboseArg);
|
||||
void removeBackupFiles(const ApplicationUtilities::StringVector ¶meterValues, const ApplicationUtilities::Argument &recursiveArg);
|
||||
|
||||
|
|
|
@ -84,18 +84,17 @@ void AttachmentsEdit::invalidate()
|
|||
setupUi();
|
||||
}
|
||||
|
||||
bool AttachmentsEdit::addFile(const QString &path)
|
||||
void AttachmentsEdit::addFile(const QString &path)
|
||||
{
|
||||
if(fileInfo() && fileInfo()->areAttachmentsSupported() && fileInfo()->container()) {
|
||||
if(fileInfo() && fileInfo()->attachmentsParsingStatus() == ParsingStatus::Ok && fileInfo()->container()) {
|
||||
// create and add attachment
|
||||
auto *attachment = fileInfo()->container()->createAttachment();
|
||||
attachment->setFile(path.toLocal8Bit().data());
|
||||
attachment->setIgnored(true);
|
||||
attachment->setFile(path.toLocal8Bit().data());
|
||||
m_addedAttachments << attachment;
|
||||
m_model->addAttachment(-1, attachment, true, path);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
throw Failure();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,8 +102,12 @@ void AttachmentsEdit::showFileSelection()
|
|||
{
|
||||
auto path = QFileDialog::getOpenFileName(this, tr("Select a file to attach"));
|
||||
if(!path.isEmpty()) {
|
||||
if(!addFile(path)) {
|
||||
QMessageBox::warning(this, QApplication::applicationName(), tr("The file couldn't be added."));
|
||||
try {
|
||||
addFile(path);
|
||||
} catch(const Failure &) {
|
||||
QMessageBox::warning(this, QApplication::applicationName(), tr("The file couldn't be added because the attachments of the file could not be parsed successfully."));
|
||||
} catch(const ios_base::failure &) {
|
||||
QMessageBox::warning(this, QApplication::applicationName(), tr("The file couldn't be added because an IO error occured."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ public slots:
|
|||
void restore();
|
||||
void apply();
|
||||
void invalidate();
|
||||
bool addFile(const QString &path);
|
||||
void addFile(const QString &path);
|
||||
|
||||
void showFileSelection();
|
||||
void extractSelected();
|
||||
|
|
Loading…
Reference in New Issue