1 /*****************************************************************************
2  * Copyright (C) 2000 Shie Erlich <krusader@users.sourceforge.net>           *
3  * Copyright (C) 2000 Rafi Yanai <krusader@users.sourceforge.net>            *
4  * Copyright (C) 2004-2019 Krusader Krew [https://krusader.org]              *
5  *                                                                           *
6  * This file is part of Krusader [https://krusader.org].                     *
7  *                                                                           *
8  * Krusader 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  * Krusader 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 Krusader.  If not, see [http://www.gnu.org/licenses/].         *
20  *****************************************************************************/
21 
22 #include "packguibase.h"
23 
24 // QtCore
25 #include <QVariant>
26 // QtGui
27 #include <QGuiApplication>
28 #include <QImage>
29 #include <QPixmap>
30 #include <QKeyEvent>
31 // QtWidgets
32 #include <QSlider>
33 #include <QCheckBox>
34 #include <QComboBox>
35 #include <QLabel>
36 #include <QLineEdit>
37 #include <QPushButton>
38 #include <QToolButton>
39 #include <QLayout>
40 #include <QSpinBox>
41 #include <QHBoxLayout>
42 #include <QGridLayout>
43 #include <QFrame>
44 #include <QVBoxLayout>
45 
46 #include <KConfigCore/KSharedConfig>
47 #include <KI18n/KLocalizedString>
48 #include <KCompletion/KComboBox>
49 #include <KWidgetsAddons/KMessageBox>
50 #include <KIO/Global>
51 #include <KCompletion/KHistoryComboBox>
52 #include <KWidgetsAddons/KStandardGuiItem>
53 
54 #include "../defaults.h"
55 #include "../krglobal.h"
56 #include "../icon.h"
57 
58 /*
59  *  Constructs a PackGUIBase which is a child of 'parent', with the
60  *  name 'name' and widget flags set to 'f'
61  *
62  *  The dialog will by default be modeless, unless you set 'modal' to
63  *  TRUE to construct a modal dialog.
64  */
PackGUIBase(QWidget * parent)65 PackGUIBase::PackGUIBase(QWidget* parent)
66         : QDialog(parent), expanded(false)
67 {
68     KConfigGroup group(krConfig, "Archives");
69 
70     setModal(true);
71     resize(430, 140);
72     setWindowTitle(i18n("Pack"));
73     grid = new QGridLayout(this);
74     grid->setSpacing(6);
75     grid->setContentsMargins(11, 11, 11, 11);
76 
77     hbox = new QHBoxLayout;
78     hbox->setSpacing(6);
79     hbox->setContentsMargins(0, 0, 0, 0);
80 
81     TextLabel3 = new QLabel(this);
82     TextLabel3->setText(i18n("To archive"));
83     hbox->addWidget(TextLabel3);
84 
85     nameData = new QLineEdit(this);
86     hbox->addWidget(nameData);
87 
88     typeData = new QComboBox(this);
89     typeData->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
90     connect(typeData, SIGNAL(activated(QString)), this,  SLOT(checkConsistency()));
91     connect(typeData, SIGNAL(highlighted(QString)), this,  SLOT(checkConsistency()));
92     hbox->addWidget(typeData);
93 
94     grid->addLayout(hbox, 1, 0);
95 
96     hbox_2 = new QHBoxLayout;
97     hbox_2->setSpacing(6);
98     hbox_2->setContentsMargins(0, 0, 0, 0);
99 
100     TextLabel5 = new QLabel(this);
101     TextLabel5->setText(i18n("In folder"));
102     hbox_2->addWidget(TextLabel5);
103 
104     dirData = new QLineEdit(this);
105     hbox_2->addWidget(dirData);
106 
107     browseButton = new QToolButton(this);
108     browseButton->setIcon(Icon("document-open"));
109     hbox_2->addWidget(browseButton);
110     QSpacerItem* spacer = new QSpacerItem(48, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
111     hbox_2->addItem(spacer);
112 
113     grid->addLayout(hbox_2, 2, 0);
114 
115     hbox_3 = new QHBoxLayout;
116     hbox_3->setSpacing(6);
117     hbox_3->setContentsMargins(0, 0, 0, 0);
118 
119     PixmapLabel1 = new QLabel(this);
120     PixmapLabel1->setPixmap(Icon("package-x-generic").pixmap(32));
121     PixmapLabel1->setScaledContents(true);
122     PixmapLabel1->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
123     hbox_3->addWidget(PixmapLabel1);
124 
125     TextLabel1 = new QLabel(this);
126     TextLabel1->setText(i18n("Pack"));
127     hbox_3->addWidget(TextLabel1);
128 
129     grid->addLayout(hbox_3, 0, 0);
130 
131 
132     hbox_4 = new QHBoxLayout;
133     hbox_4->setSpacing(6);
134     hbox_4->setContentsMargins(0, 0, 0, 0);
135 
136     QSpacerItem* spacer_3 = new QSpacerItem(20, 26, QSizePolicy::Fixed, QSizePolicy::Expanding);
137     hbox_4->addItem(spacer_3);
138     grid->addLayout(hbox_4, 3, 0);
139 
140     advancedWidget = new QWidget(this);
141 
142     hbox_5 = new QGridLayout(advancedWidget);
143     hbox_5->setSpacing(6);
144     hbox_5->setContentsMargins(0, 0, 0, 0);
145 
146 
147     QVBoxLayout *compressLayout = new QVBoxLayout;
148     compressLayout->setSpacing(6);
149     compressLayout->setContentsMargins(0, 0, 0, 0);
150 
151     multipleVolume = new QCheckBox(i18n("Multiple volume archive"), advancedWidget);
152     connect(multipleVolume, SIGNAL(toggled(bool)), this, SLOT(checkConsistency()));
153     compressLayout->addWidget(multipleVolume, 0, 0);
154 
155     QHBoxLayout * volumeHbox = new QHBoxLayout;
156 
157     QSpacerItem* spacer_5 = new QSpacerItem(20, 26, QSizePolicy::Fixed, QSizePolicy::Fixed);
158     volumeHbox->addItem(spacer_5);
159 
160     TextLabel7 = new QLabel(i18n("Size:"), advancedWidget);
161     volumeHbox->addWidget(TextLabel7);
162 
163     volumeSpinBox = new QSpinBox(advancedWidget);
164     volumeSpinBox->setMinimum(1);
165     volumeSpinBox->setMaximum(9999);
166     volumeSpinBox->setValue(1440);
167     volumeHbox->addWidget(volumeSpinBox);
168 
169     volumeUnitCombo = new QComboBox(advancedWidget);
170     volumeUnitCombo->addItem("B");
171     volumeUnitCombo->addItem("KB");
172     volumeUnitCombo->addItem("MB");
173     volumeUnitCombo->setCurrentIndex(1);
174     volumeHbox->addWidget(volumeUnitCombo);
175 
176     compressLayout->addLayout(volumeHbox);
177 
178     int level = group.readEntry("Compression level", _defaultCompressionLevel);
179     setCompressionLevel = new QCheckBox(i18n("Set compression level"), advancedWidget);
180     if (level != _defaultCompressionLevel)
181         setCompressionLevel->setChecked(true);
182     connect(setCompressionLevel, SIGNAL(toggled(bool)), this, SLOT(checkConsistency()));
183     compressLayout->addWidget(setCompressionLevel, 0, 0);
184 
185     QHBoxLayout * sliderHbox = new QHBoxLayout;
186 
187     QSpacerItem* spacer_6 = new QSpacerItem(20, 26, QSizePolicy::Fixed, QSizePolicy::Fixed);
188     sliderHbox->addItem(spacer_6);
189 
190     QWidget * sliderVBoxWidget = new QWidget(advancedWidget);
191     QVBoxLayout *sliderVBox = new QVBoxLayout(sliderVBoxWidget);
192 
193     compressionSlider = new QSlider(Qt::Horizontal, sliderVBoxWidget);
194     compressionSlider->setMinimum(1);
195     compressionSlider->setMaximum(9);
196     compressionSlider->setPageStep(1);
197     compressionSlider->setValue(level);
198     compressionSlider->setTickPosition(QSlider::TicksBelow);
199     sliderVBox->addWidget(compressionSlider);
200 
201     QWidget * minmaxWidget = new QWidget(sliderVBoxWidget);
202     sliderVBox->addWidget(minmaxWidget);
203 
204     QHBoxLayout * minmaxHbox = new QHBoxLayout(minmaxWidget);
205 
206     minLabel = new QLabel(i18n("MIN"), minmaxWidget);
207     maxLabel = new QLabel(i18n("MAX"), minmaxWidget);
208     maxLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
209 
210     minmaxHbox->addWidget(minLabel);
211     minmaxHbox->addWidget(maxLabel);
212 
213     sliderHbox->addWidget(sliderVBoxWidget);
214 
215     compressLayout->addLayout(sliderHbox);
216 
217     compressLayout->addStretch(0);
218     hbox_5->addLayout(compressLayout, 0, 0);
219 
220     QFrame *vline = new QFrame(advancedWidget);
221     vline->setFrameStyle(QFrame::VLine | QFrame::Sunken);
222     vline->setMinimumWidth(20);
223     hbox_5->addWidget(vline, 0, 1);
224 
225 
226     QGridLayout * passwordGrid = new QGridLayout;
227     passwordGrid->setSpacing(6);
228     passwordGrid->setContentsMargins(0, 0, 0, 0);
229 
230     TextLabel4 = new QLabel(advancedWidget);
231     TextLabel4->setText(i18n("Password"));
232     passwordGrid->addWidget(TextLabel4, 0, 0);
233 
234     password = new QLineEdit(advancedWidget);
235     password->setEchoMode(QLineEdit::Password);
236     connect(password, SIGNAL(textChanged(QString)), this, SLOT(checkConsistency()));
237 
238     passwordGrid->addWidget(password, 0, 1);
239 
240     TextLabel6 = new QLabel(advancedWidget);
241     TextLabel6->setText(i18n("Again"));
242     passwordGrid->addWidget(TextLabel6, 1, 0);
243 
244     passwordAgain = new QLineEdit(advancedWidget);
245     passwordAgain->setEchoMode(QLineEdit::Password);
246     connect(passwordAgain, SIGNAL(textChanged(QString)), this, SLOT(checkConsistency()));
247 
248     passwordGrid->addWidget(passwordAgain, 1, 1);
249 
250     QHBoxLayout *consistencyHbox = new QHBoxLayout;
251 
252     QSpacerItem* spacer_cons = new QSpacerItem(48, 20, QSizePolicy::Expanding, QSizePolicy::Fixed);
253     consistencyHbox->addItem(spacer_cons);
254 
255     passwordConsistencyLabel = new QLabel(advancedWidget);
256     consistencyHbox->addWidget(passwordConsistencyLabel);
257     passwordGrid->addLayout(consistencyHbox, 2, 0, 1, 2);
258 
259     encryptHeaders = new QCheckBox(i18n("Encrypt headers"), advancedWidget);
260     passwordGrid->addWidget(encryptHeaders, 3, 0, 1, 2);
261 
262     QSpacerItem* spacer_psw = new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Expanding);
263     passwordGrid->addItem(spacer_psw, 4, 0);
264 
265     hbox_5->addLayout(passwordGrid, 0, 2);
266 
267     hbox_7 = new QHBoxLayout;
268     hbox_7->setSpacing(6);
269     hbox_7->setContentsMargins(0, 0, 0, 0);
270 
271     TextLabel8 = new QLabel(i18n("Command line switches:"), advancedWidget);
272     TextLabel8->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
273     hbox_7->addWidget(TextLabel8);
274 
275     commandLineSwitches = new KHistoryComboBox(advancedWidget);
276     commandLineSwitches->setMaxCount(25);  // remember 25 items
277     commandLineSwitches->setDuplicatesEnabled(false);
278     commandLineSwitches->setMinimumContentsLength(10);
279 
280     QStringList list = group.readEntry("Command Line Switches", QStringList());
281     commandLineSwitches->setHistoryItems(list);
282 
283     hbox_7->addWidget(commandLineSwitches);
284 
285     hbox_5->addLayout(hbox_7, 1, 0, 1, 3);
286 
287 
288     advancedWidget->hide();
289     checkConsistency();
290 
291     grid->addWidget(advancedWidget, 4, 0);
292 
293     hbox_6 = new QHBoxLayout;
294     hbox_6->setSpacing(6);
295     hbox_6->setContentsMargins(0, 0, 0, 0);
296 
297     advancedButton = new QPushButton(this);
298     advancedButton->setText(i18n("&Advanced >>"));
299     hbox_6->addWidget(advancedButton);
300 
301     QSpacerItem* spacer_2 = new QSpacerItem(140, 20, QSizePolicy::Expanding, QSizePolicy::Fixed);
302     hbox_6->addItem(spacer_2);
303 
304     okButton = new QPushButton(this);
305     KStandardGuiItem::assign(okButton, KStandardGuiItem::Ok);
306     okButton->setDefault(true);
307     hbox_6->addWidget(okButton);
308 
309     cancelButton = new QPushButton(this);
310     KStandardGuiItem::assign(cancelButton, KStandardGuiItem::Cancel);
311     hbox_6->addWidget(cancelButton);
312 
313     grid->addLayout(hbox_6, 6, 0);
314 
315     // signals and slots connections
316     connect(okButton, SIGNAL(clicked()), this, SLOT(accept()));
317     connect(advancedButton, SIGNAL(clicked()), this, SLOT(expand()));
318     connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
319     connect(browseButton, SIGNAL(clicked()), this, SLOT(browse()));
320 }
321 
322 /*
323  *  Destroys the object and frees any allocated resources
324  */
~PackGUIBase()325 PackGUIBase::~PackGUIBase()
326 {
327     // no need to delete child widgets, Qt does it all for us
328 }
329 
browse()330 void PackGUIBase::browse()
331 {
332     qWarning("PackGUIBase::browse(): Not implemented yet!");
333 }
334 
expand()335 void PackGUIBase::expand()
336 {
337     expanded = !expanded;
338 
339     advancedButton->setText(expanded ? i18n("&Advanced <<") : i18n("&Advanced >>"));
340 
341     if (expanded)
342         advancedWidget->show();
343     else {
344         advancedWidget->hide();
345         layout()->activate();
346         QSize minSize = minimumSize();
347         resize(width(), minSize.height());
348     }
349     show();
350 }
351 
checkConsistency()352 void PackGUIBase::checkConsistency()
353 {
354     QPalette p = QGuiApplication::palette();
355     QPalette pal = passwordConsistencyLabel->palette();
356     if (password->text().isEmpty() && passwordAgain->text().isEmpty()) {
357         pal.setColor(passwordConsistencyLabel->foregroundRole(), p.color(QPalette::Active, QPalette::Text));
358         passwordConsistencyLabel->setText(i18n("No password specified"));
359     } else if (password->text() == passwordAgain->text()) {
360         pal.setColor(passwordConsistencyLabel->foregroundRole(), p.color(QPalette::Active, QPalette::Text));
361         passwordConsistencyLabel->setText(i18n("The passwords are equal"));
362     } else {
363         pal.setColor(passwordConsistencyLabel->foregroundRole(), Qt::red);
364         passwordConsistencyLabel->setText(i18n("The passwords are different"));
365     }
366     passwordConsistencyLabel->setPalette(pal);
367 
368     QString packer = typeData->currentText();
369 
370     bool passworded = false;
371     if (packer == "7z" || packer == "rar" || packer == "zip" || packer == "arj")
372         passworded = true;
373 
374     passwordConsistencyLabel->setEnabled(passworded);
375     password->setEnabled(passworded);
376     passwordAgain->setEnabled(passworded);
377     TextLabel4->setEnabled(passworded);
378     TextLabel6->setEnabled(passworded);
379 
380     encryptHeaders->setEnabled(packer == "rar");
381 
382     multipleVolume->setEnabled(packer == "rar" || packer == "arj");
383     bool volumeEnabled = multipleVolume->isEnabled() && multipleVolume->isChecked();
384     volumeSpinBox->setEnabled(volumeEnabled);
385     volumeUnitCombo->setEnabled(volumeEnabled);
386     TextLabel7->setEnabled(volumeEnabled);
387 
388     /* TODO */
389     setCompressionLevel->setEnabled(packer == "rar" || packer == "arj" || packer == "zip" ||
390                                     packer == "7z");
391     bool sliderEnabled = setCompressionLevel->isEnabled() && setCompressionLevel->isChecked();
392     compressionSlider->setEnabled(sliderEnabled);
393     minLabel->setEnabled(sliderEnabled);
394     maxLabel->setEnabled(sliderEnabled);
395 }
396 
extraProperties(QMap<QString,QString> & inMap)397 bool PackGUIBase::extraProperties(QMap<QString, QString> & inMap)
398 {
399     inMap.clear();
400 
401     KConfigGroup group(krConfig, "Archives");
402 
403     if (password->isEnabled() && passwordAgain->isEnabled()) {
404         if (password->text() != passwordAgain->text()) {
405             KMessageBox::error(this, i18n("Cannot pack, the passwords are different."));
406             return false;
407         }
408 
409         if (!password->text().isEmpty()) {
410             inMap[ "Password" ] = password->text();
411 
412             if (encryptHeaders->isEnabled() && encryptHeaders->isChecked())
413                 inMap[ "EncryptHeaders" ] = '1';
414         }
415     }
416 
417     if (multipleVolume->isEnabled() && multipleVolume->isChecked()) {
418         KIO::filesize_t size = volumeSpinBox->value();
419 
420         switch (volumeUnitCombo->currentIndex()) {
421         case 2:
422             size *= 1000;
423 #if __GNUC__ >= 7
424             [[gnu::fallthrough]];
425 #endif
426         case 1:
427             size *= 1000;
428         default:
429             break;
430         }
431 
432         if (size < 10000) {
433             KMessageBox::error(this, i18n("Invalid volume size."));
434             return false;
435         }
436 
437         QString sbuffer;
438         sbuffer.sprintf("%llu", size);
439 
440         inMap[ "VolumeSize" ] = sbuffer;
441     }
442 
443     if (setCompressionLevel->isEnabled() && setCompressionLevel->isChecked()) {
444         inMap[ "CompressionLevel" ] = QString("%1").arg(compressionSlider->value());
445         int level = compressionSlider->value();
446         group.writeEntry("Compression level", level);
447     }
448 
449     QString cmdArgs = commandLineSwitches->currentText().trimmed();
450     if (!cmdArgs.isEmpty()) {
451         bool firstChar = true;
452         QChar quote = QChar::Null;
453 
454         for (int i = 0; i < cmdArgs.length(); i++) {
455             QChar ch(cmdArgs[ i ]);
456             if (ch.isSpace())
457                 continue;
458 
459             if (ch == quote) {
460                 quote = QChar::Null;
461                 continue;
462             }
463 
464             if (firstChar && ch != QLatin1Char('-')) {
465                 KMessageBox::error(this, i18n("Invalid command line switch.\nA switch must start with '-'."));
466                 return false;
467             }
468 
469             firstChar = false;
470 
471             if (quote == QLatin1Char('"'))
472                 continue;
473             if (quote == QChar::Null && (ch == QLatin1Char('\'') || ch == QLatin1Char('"')))
474                 quote = ch;
475             if (ch == QLatin1Char('\\')) {
476                 if (i == cmdArgs.length() - 1) {
477                     KMessageBox::error(this, i18n("Invalid command line switch.\nBackslashes cannot be the last character."));
478                     return false;
479                 }
480                 i++;
481             }
482         }
483 
484         if (quote != QChar::Null) {
485             KMessageBox::error(this, i18n("Invalid command line switch.\nUnclosed quotation mark."));
486             return false;
487         }
488 
489         commandLineSwitches->addToHistory(cmdArgs);
490         QStringList list = commandLineSwitches->historyItems();
491         group.writeEntry("Command Line Switches", list);
492 
493         inMap[ "CommandLineSwitches" ] = cmdArgs;
494     }
495     return true;
496 }
497