1 /************************************************************************** 2 ** ** 3 ** Copyright (C) 2018 Lukas Spies ** 4 ** Contact: http://photoqt.org ** 5 ** ** 6 ** This file is part of PhotoQt. ** 7 ** ** 8 ** PhotoQt is free software: you can redistribute it and/or modify ** 9 ** it under the terms of the GNU General Public License as published by ** 10 ** the Free Software Foundation, either version 2 of the License, or ** 11 ** (at your option) any later version. ** 12 ** ** 13 ** PhotoQt is distributed in the hope that it will be useful, ** 14 ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** 15 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** 16 ** GNU General Public License for more details. ** 17 ** ** 18 ** You should have received a copy of the GNU General Public License ** 19 ** along with PhotoQt. If not, see <http://www.gnu.org/licenses/>. ** 20 ** ** 21 **************************************************************************/ 22 23 #ifndef COMMANDLINEPARSER_H 24 #define COMMANDLINEPARSER_H 25 26 #include <QCoreApplication> 27 #include <iomanip> 28 #include "../logger.h" 29 30 // A custom command line parser 31 // Looks very much like QCommandLineParser, but allows grouping entries 32 class CommandLineParser : public QObject { 33 34 Q_OBJECT 35 36 public: 37 CommandLineParser(QCoreApplication * app)38 explicit CommandLineParser(QCoreApplication *app) : QObject() { 39 40 // This will hold the maximum width of the options to align everything nicely 41 maxEntriesWidth = 0; 42 43 // These 4 options should always be the first four! 44 validOptions << "h" << "help" << "v" << "version"; 45 46 // First category 47 QString cat = "Interaction with PhotoQt"; 48 addOption(cat, {"o", "open"}, "Make PhotoQt ask for a new File."); 49 addOption(cat, {"s", "show"}, "Shows PhotoQt (does nothing if already shown)."); 50 addOption(cat, "hide", "Hides PhotoQt to system tray (does nothing if already hidden)."); 51 addOption(cat, {"t", "toggle"}, "Toggle PhotoQt - hides PhotoQt if visible, shows if hidden."); 52 addOption(cat, {"thumbs", "no-thumbs"}, "Enable/Disable thumbnails."); 53 54 // Second category 55 cat = "Start-up-only options"; 56 addOption(cat, "start-in-tray", "Start PhotoQt hidden to the system tray."); 57 addOption(cat, "standalone", "Create standalone PhotoQt, multiple instances but no remote interaction possible."); 58 59 // Third category 60 cat = "General Options"; 61 addOption(cat, {"debug", "no-debug"}, "Switch on/off debug messages."); 62 addOption(cat, "export", "Export configuration to given filename.", "filename"); 63 addOption(cat, "import", "Import configuration from given filename.", "filename"); 64 65 // Process the command line 66 process(app); 67 68 } 69 70 // Convenience function, allows to pass a single option as string 71 void addOption(QString cat, QString option, QString description, QString valueName = "") { 72 addOption(cat, QStringList() << option, description, valueName); 73 } 74 75 // Add a command line option 76 void addOption(QString cat, QStringList option, QString description, QString valueName = "") { 77 78 // Store a new category 79 if(!categories.contains(cat)) 80 categories.append(cat); 81 82 // Compose the option string that is to be displayed 83 QString optionString = ""; 84 // Loop over all options 85 for(QString o : option) { 86 87 // Store this as a valid options the user can use 88 validOptions << o; 89 90 // If this flag comes with a value, store that 91 if(valueName != "") 92 optionsWithValue << o; 93 94 // Multiple options are displayed separated by comma 95 if(optionString != "") 96 optionString += ", "; 97 98 // Add on the option to the string. 99 // 1-character flags are called with a single '-' 100 // 2+-character flags are called with a double '-' 101 optionString += (o.length()==1 ? "-" : "--") + o; 102 103 } 104 105 // If option comes with a value, displayed after options in angled brackets 106 if(valueName.length() > 0) 107 optionString += " <" + valueName + ">"; 108 109 // Find largets width necessary 110 if(optionString.length() > maxEntriesWidth) 111 maxEntriesWidth = optionString.length(); 112 113 // Store entry 114 allEntries << (QStringList() << cat << optionString << description); 115 116 } 117 118 private: 119 120 // Show the help message and quit showHelp()121 void showHelp() { 122 123 // the max width gets two added on the left (indented by 2) and on the right (spacing between option and description) 124 int entryWidth = 2+maxEntriesWidth+2; 125 126 int maxWidth = 78; 127 128 // Header 129 std::cout << "Usage: photoqt [options] [filename]" << std::endl; 130 std::cout << "PhotoQt Image Viewer" << std::endl << std::endl; 131 132 // help and version option 133 std::cout << std::setfill(' ') << std::setw(entryWidth) << std::left << " -h, --help" << "Displays this help." << std::endl; 134 std::cout << std::setfill(' ') << std::setw(entryWidth) << std::left << " -v, --version" << "Displays version information." << std::endl; 135 136 // Loop over all categories 137 for(QString cat : categories) { 138 139 // Display category followed by colon 140 std::cout << std::endl << cat.toStdString() << ":" << std::endl; 141 142 // Loop over all entries to find the ones matching this category 143 for(QStringList entry : allEntries) { 144 145 // If categories match, output option 146 if(entry.at(0) == cat) { 147 148 // Output category 149 std::cout << std::setfill(' ') << std::setw(entryWidth) << std::left << (" " + entry.at(1).toStdString()); 150 151 // The description. This will be reduced by whatever is outputted 152 QString desc = entry.at(2); 153 154 // Keep as going as long as there is something to output 155 while(desc.length() > 0) { 156 157 // The maximum width that is available for the description 158 int maxDescLength = std::min(maxWidth-entryWidth, desc.length()-1); 159 160 // Make sure that, if we are not at the end of the string, we break the string at a space 161 while(desc.at(maxDescLength) != ' ' && maxDescLength < desc.length()-1) 162 --maxDescLength; 163 164 // Error checking, this should never really happen, but just to be safe 165 if(maxDescLength < 1) 166 break; 167 168 // If this is the second line, we need to pad the output on the left for proper indentation 169 if(desc.length() < entry.at(2).length()) 170 std::cout << std::setfill(' ') << std::setw(entryWidth) << " "; 171 172 // Output the description (sub-)string 173 std::cout << desc.left(maxDescLength+1).trimmed().toStdString() << std::endl; 174 175 // Remove whatever part of the description is displayed 176 desc = desc.remove(0, maxDescLength+1); 177 178 } 179 180 } 181 182 } 183 184 } 185 186 // Output the positional argument at end 187 std::cout << std::endl; 188 std::cout << "Arguments:" << std::endl; 189 std::cout << std::setfill(' ') << std::setw(entryWidth) << std::left << " filename" << "File to open with PhotoQt." << std::endl; 190 191 // And quit application 192 qApp->quit(); 193 exit(0); 194 195 } 196 197 // When an error was detected, display error and quit showError(QString err)198 void showError(QString err) { 199 std::cout << err.toStdString() << std::endl; 200 qApp->quit(); 201 exit(0); 202 } 203 204 // Process the command line process(QCoreApplication * app)205 void process(QCoreApplication *app) { 206 207 // Get all arguments 208 QStringList args = app->arguments(); 209 210 // If help flag is set, show help and quit 211 if(args.contains("-h") || args.contains("--help")) { 212 showHelp(); 213 return; 214 } 215 216 // Set the found filename to empty 217 foundFilename = ""; 218 219 // Loop over all arguments 220 for(int i = 1; i < args.length(); ++i) { 221 222 // quick access for current flag 223 QString a = args.at(i); 224 225 // If flag doesn't start with a '-', then this is the positional flag, i.e., the filename 226 if(!a.startsWith("-")) { 227 228 // If this is the first filename, store that 229 if(foundFilename.trimmed() == "") 230 foundFilename = a; 231 // Else show error as only one filename is allowed 232 else { 233 showError("Only one filename can be passed on!"); 234 return; 235 } 236 237 // 'Normal' flags 238 } else { 239 240 // Remove any '-' from the beginning of the flag 241 while(a.startsWith("-")) 242 a = a.remove(0,1); 243 244 // If option is not a valid one, show error and quit 245 if(!validOptions.contains(a)) { 246 showError("Unknown option '" + a + "'."); 247 return; 248 } 249 250 // If option comes with a value 251 if(optionsWithValue.contains(a)) { 252 253 // If no value was provided, show error and quit 254 if(i == args.length()-1 || args.at(i+1).startsWith("-")) { 255 showError("Filename required for option '" + a + "'."); 256 return; 257 // Store value 258 } else { 259 foundValues.insert(a, args.at(++i)); 260 // This is a simple list holding all passed on flags 261 // This makes it much easier to find out what has been set or not 262 foundOptions.append(a); 263 } 264 265 // A normal flag without value 266 } else 267 foundOptions.append(a); 268 269 } 270 271 } 272 273 } 274 275 public: 276 // All options that have been found 277 QStringList foundOptions; 278 // All option/value pairs that have been found 279 QMap<QString,QString> foundValues; 280 // The found filename (if any) 281 QString foundFilename; 282 283 private: 284 // Hold max width required for options 285 int maxEntriesWidth; 286 // All categories that have been set 287 QStringList categories; 288 // All entries 289 QList<QStringList> allEntries; 290 // All options that are set as one list 291 QStringList validOptions; 292 // All options that are set with values as one list 293 QStringList optionsWithValue; 294 295 }; 296 297 298 #endif 299