1 /* profile_dialog.cpp 2 * 3 * Wireshark - Network traffic analyzer 4 * By Gerald Combs <gerald@wireshark.org> 5 * Copyright 1998 Gerald Combs 6 * 7 * SPDX-License-Identifier: GPL-2.0-or-later 8 */ 9 10 #include "config.h" 11 #include <glib.h> 12 13 #include "wsutil/filesystem.h" 14 #include "wsutil/utf8_entities.h" 15 #include "epan/prefs.h" 16 17 #include <ui/qt/utils/qt_ui_utils.h> 18 19 #include "ui/profile.h" 20 #include "ui/recent.h" 21 #include "ui/last_open_dir.h" 22 23 #include <ui/qt/utils/variant_pointer.h> 24 #include <ui/qt/models/profile_model.h> 25 26 #include "profile_dialog.h" 27 #include <ui_profile_dialog.h> 28 #include "wireshark_application.h" 29 #include <ui/qt/utils/color_utils.h> 30 #include <ui/qt/simple_dialog.h> 31 32 #include <QBrush> 33 #include <QDir> 34 #include <QFont> 35 #include <QMessageBox> 36 #include <QPushButton> 37 #include <QTreeWidgetItem> 38 #include <QUrl> 39 #include <QComboBox> 40 #include <QLineEdit> 41 #include <QFileDialog> 42 #include <QStandardPaths> 43 #include <QKeyEvent> 44 #include <QMenu> 45 #include <QMessageBox> 46 47 #define PROFILE_EXPORT_PROPERTY "export" 48 #define PROFILE_EXPORT_ALL "all" 49 #define PROFILE_EXPORT_SELECTED "selected" 50 51 ProfileDialog::ProfileDialog(QWidget *parent) : 52 GeometryStateDialog(parent), 53 pd_ui_(new Ui::ProfileDialog), 54 ok_button_(Q_NULLPTR), 55 import_button_(Q_NULLPTR), 56 model_(Q_NULLPTR), 57 sort_model_(Q_NULLPTR) 58 { 59 pd_ui_->setupUi(this); 60 loadGeometry(); 61 setWindowTitle(wsApp->windowTitleString(tr("Configuration Profiles"))); 62 63 ok_button_ = pd_ui_->buttonBox->button(QDialogButtonBox::Ok); 64 65 // XXX - Use NSImageNameAddTemplate and NSImageNameRemoveTemplate to set stock 66 // icons on macOS. 67 // Are there equivalent stock icons on Windows? 68 pd_ui_->newToolButton->setStockIcon("list-add"); 69 pd_ui_->deleteToolButton->setStockIcon("list-remove"); 70 pd_ui_->copyToolButton->setStockIcon("list-copy"); 71 #ifdef Q_OS_MAC 72 pd_ui_->newToolButton->setAttribute(Qt::WA_MacSmallSize, true); 73 pd_ui_->deleteToolButton->setAttribute(Qt::WA_MacSmallSize, true); 74 pd_ui_->copyToolButton->setAttribute(Qt::WA_MacSmallSize, true); 75 pd_ui_->hintLabel->setAttribute(Qt::WA_MacSmallSize, true); 76 #endif 77 78 import_button_ = pd_ui_->buttonBox->addButton(tr("Import", "noun"), QDialogButtonBox::ActionRole); 79 80 #ifdef HAVE_MINIZIP 81 export_button_ = pd_ui_->buttonBox->addButton(tr("Export", "noun"), QDialogButtonBox::ActionRole); 82 83 QMenu * importMenu = new QMenu(import_button_); 84 QAction * entry = importMenu->addAction(tr("from zip file")); 85 connect(entry, &QAction::triggered, this, &ProfileDialog::importFromZip); 86 entry = importMenu->addAction(tr("from directory")); 87 connect(entry, &QAction::triggered, this, &ProfileDialog::importFromDirectory); 88 import_button_->setMenu(importMenu); 89 90 QMenu * exportMenu = new QMenu(export_button_); 91 export_selected_entry_ = exportMenu->addAction(tr("%Ln selected personal profile(s)", "", 0)); 92 export_selected_entry_->setProperty(PROFILE_EXPORT_PROPERTY, PROFILE_EXPORT_SELECTED); 93 connect(export_selected_entry_, &QAction::triggered, this, &ProfileDialog::exportProfiles); 94 entry = exportMenu->addAction(tr("all personal profiles")); 95 entry->setProperty(PROFILE_EXPORT_PROPERTY, PROFILE_EXPORT_ALL); 96 connect(entry, &QAction::triggered, this, &ProfileDialog::exportProfiles); 97 export_button_->setMenu(exportMenu); 98 #else 99 connect(import_button_, &QPushButton::clicked, this, &ProfileDialog::importFromDirectory); 100 #endif 101 102 resetTreeView(); 103 104 /* Select the row for the currently selected profile or the first row if non is selected*/ 105 selectProfile(); 106 107 pd_ui_->cmbProfileTypes->addItems(ProfileSortModel::filterTypes()); 108 109 connect (pd_ui_->cmbProfileTypes, SIGNAL(currentTextChanged(const QString &)), 110 this, SLOT(filterChanged(const QString &))); 111 connect (pd_ui_->lineProfileFilter, SIGNAL(textChanged(const QString &)), 112 this, SLOT(filterChanged(const QString &))); 113 114 currentItemChanged(); 115 116 pd_ui_->profileTreeView->setFocus(); 117 } 118 119 ProfileDialog::~ProfileDialog() 120 { 121 delete pd_ui_; 122 empty_profile_list (TRUE); 123 } 124 125 void ProfileDialog::keyPressEvent(QKeyEvent *evt) 126 { 127 if (pd_ui_->lineProfileFilter->hasFocus() && (evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return)) 128 return; 129 QDialog::keyPressEvent(evt); 130 } 131 132 void ProfileDialog::selectProfile(QString profile) 133 { 134 if (profile.isEmpty()) 135 profile = QString(get_profile_name()); 136 137 int row = model_->findByName(profile); 138 QModelIndex idx = sort_model_->mapFromSource(model_->index(row, ProfileModel::COL_NAME)); 139 if (idx.isValid()) 140 pd_ui_->profileTreeView->selectRow(idx.row()); 141 } 142 143 int ProfileDialog::execAction(ProfileDialog::ProfileAction profile_action) 144 { 145 int ret = QDialog::Accepted; 146 QModelIndex item; 147 148 switch (profile_action) { 149 case ShowProfiles: 150 ret = exec(); 151 break; 152 case NewProfile: 153 on_newToolButton_clicked(); 154 ret = exec(); 155 break; 156 case ImportZipProfile: 157 #ifdef HAVE_MINIZIP 158 importFromZip(); 159 #endif 160 break; 161 case ImportDirProfile: 162 importFromDirectory(); 163 break; 164 case ExportSingleProfile: 165 #ifdef HAVE_MINIZIP 166 exportProfiles(); 167 #endif 168 break; 169 case ExportAllProfiles: 170 #ifdef HAVE_MINIZIP 171 exportProfiles(true); 172 #endif 173 break; 174 case EditCurrentProfile: 175 item = pd_ui_->profileTreeView->currentIndex(); 176 if (item.isValid()) { 177 pd_ui_->profileTreeView->edit(item); 178 } 179 ret = exec(); 180 break; 181 case DeleteCurrentProfile: 182 if (delete_current_profile()) { 183 wsApp->setConfigurationProfile (Q_NULLPTR); 184 } 185 break; 186 } 187 return ret; 188 } 189 190 QModelIndexList ProfileDialog::selectedProfiles() 191 { 192 QModelIndexList profiles; 193 194 foreach (QModelIndex idx, pd_ui_->profileTreeView->selectionModel()->selectedIndexes()) 195 { 196 QModelIndex temp = sort_model_->mapToSource(idx); 197 if (! temp.isValid() || profiles.contains(temp) || temp.column() != ProfileModel::COL_NAME) 198 continue; 199 200 profiles << temp; 201 } 202 203 return profiles; 204 } 205 206 void ProfileDialog::selectionChanged() 207 { 208 if (selectedProfiles().count() == 0) 209 pd_ui_->profileTreeView->selectRow(0); 210 211 updateWidgets(); 212 } 213 214 void ProfileDialog::updateWidgets() 215 { 216 bool enable_del = true; 217 bool enable_ok = true; 218 bool multiple = false; 219 bool enable_import = true; 220 int user_profiles = 0; 221 222 QString msg; 223 QModelIndex index = sort_model_->mapToSource(pd_ui_->profileTreeView->currentIndex()); 224 QModelIndexList profiles = selectedProfiles(); 225 226 /* Ensure that the index is always the name column */ 227 if (index.column() != ProfileModel::COL_NAME) 228 index = index.sibling(index.row(), ProfileModel::COL_NAME); 229 230 /* check if more than one viable profile is selected, and inform the sorting model */ 231 if (profiles.count() > 1) 232 multiple = true; 233 234 /* Check if user profiles have been selected and allow export if it is so */ 235 for (int cnt = 0; cnt < profiles.count(); cnt++) 236 { 237 if (! profiles[cnt].data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! profiles[cnt].data(ProfileModel::DATA_IS_DEFAULT).toBool()) 238 user_profiles++; 239 } 240 if (model_->changesPending()) 241 { 242 enable_import = false; 243 msg = tr("An import of profiles is not allowed, while changes are pending"); 244 } 245 else if (model_->importPending()) 246 { 247 enable_import = false; 248 msg = tr("An import is pending to be saved. Additional imports are not allowed"); 249 } 250 import_button_->setToolTip(msg); 251 import_button_->setEnabled(enable_import); 252 253 #ifdef HAVE_MINIZIP 254 bool contains_user = false; 255 bool enable_export = false; 256 257 if (user_profiles > 0) 258 contains_user = true; 259 260 /* enable export if no changes are pending */ 261 if (! model_->changesPending()) 262 enable_export = true; 263 264 export_button_->setEnabled(enable_export); 265 if (! enable_export) 266 { 267 if (! contains_user) 268 export_button_->setToolTip(tr("An export of profiles is only allowed for personal profiles")); 269 else 270 export_button_->setToolTip(tr("An export of profiles is not allowed, while changes are pending")); 271 } 272 export_selected_entry_->setVisible(contains_user); 273 #endif 274 275 /* if the current profile is default with reset pending or a global one, deactivate delete */ 276 if (! multiple) 277 { 278 if (index.isValid()) 279 { 280 if (index.data(ProfileModel::DATA_IS_GLOBAL).toBool()) 281 enable_del = false; 282 else if (index.data(ProfileModel::DATA_IS_DEFAULT).toBool() && model_->resetDefault()) 283 enable_del = false; 284 } 285 else if (! index.isValid()) 286 enable_del = false; 287 } 288 289 QString hintUrl; 290 msg.clear(); 291 if (multiple) 292 { 293 /* multiple profiles are being selected, copy is no longer allowed */ 294 pd_ui_->copyToolButton->setEnabled(false); 295 296 msg = tr("%Ln selected personal profile(s)", "", user_profiles); 297 pd_ui_->hintLabel->setText(msg); 298 #ifdef HAVE_MINIZIP 299 export_selected_entry_->setText(msg); 300 #endif 301 } 302 else 303 { 304 /* if only one profile is selected, display it's path in the hint label and activate link (if allowed) */ 305 if (index.isValid()) 306 { 307 QString temp = index.data(ProfileModel::DATA_PATH).toString(); 308 if (index.data(ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION).toBool() && QFileInfo(temp).isDir()) 309 hintUrl = QUrl::fromLocalFile(temp).toString(); 310 pd_ui_->hintLabel->setText(temp); 311 pd_ui_->hintLabel->setToolTip(index.data(Qt::ToolTipRole).toString()); 312 313 if (! index.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! index.data(ProfileModel::DATA_IS_DEFAULT).toBool()) 314 msg = tr("%Ln selected personal profile(s)", "", 1); 315 } 316 317 pd_ui_->copyToolButton->setEnabled(true); 318 #ifdef HAVE_MINIZIP 319 export_selected_entry_->setText(msg); 320 #endif 321 } 322 323 /* Ensure, that the ok button is disabled, if an invalid name is used or if duplicate global profiles exist */ 324 if (model_ && model_->rowCount() > 0) 325 { 326 msg.clear(); 327 for (int row = 0; row < model_->rowCount() && enable_ok; row++) 328 { 329 QModelIndex idx = model_->index(row, ProfileModel::COL_NAME); 330 QString name = idx.data().toString(); 331 332 if (! ProfileModel::checkNameValidity(name, &msg)) 333 { 334 if (idx == index || selectedProfiles().contains(idx)) 335 { 336 hintUrl.clear(); 337 pd_ui_->hintLabel->setText(msg); 338 } 339 340 enable_ok = false; 341 continue; 342 } 343 344 if (model_->checkInvalid(idx) || (! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && model_->checkIfDeleted(idx)) ) 345 { 346 if (idx == index) 347 hintUrl.clear(); 348 enable_ok = false; 349 continue; 350 } 351 352 if (idx != index && idx.data().toString().compare(index.data().toString()) == 0) 353 { 354 if (idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() == index.data(ProfileModel::DATA_IS_GLOBAL).toBool()) 355 enable_ok = false; 356 } 357 358 QList<int> rows = model_->findAllByNameAndVisibility(name, idx.data(ProfileModel::DATA_IS_GLOBAL).toBool()); 359 if (rows.count() > 1) 360 enable_ok = false; 361 } 362 363 if (enable_ok && ! model_->checkIfDeleted(index) && index.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_CHANGED) 364 hintUrl.clear(); 365 } 366 367 pd_ui_->hintLabel->setUrl(hintUrl); 368 369 /* ensure the name column is resized to it's content */ 370 pd_ui_->profileTreeView->resizeColumnToContents(ProfileModel::COL_NAME); 371 372 pd_ui_->deleteToolButton->setEnabled(enable_del); 373 ok_button_->setEnabled(enable_ok); 374 } 375 376 void ProfileDialog::currentItemChanged(const QModelIndex &, const QModelIndex &) 377 { 378 updateWidgets(); 379 } 380 381 void ProfileDialog::on_newToolButton_clicked() 382 { 383 pd_ui_->lineProfileFilter->setText(""); 384 pd_ui_->cmbProfileTypes->setCurrentIndex(ProfileSortModel::AllProfiles); 385 sort_model_->setFilterString(); 386 387 QModelIndex ridx = sort_model_->mapFromSource(model_->addNewProfile(tr("New profile"))); 388 if (ridx.isValid()) 389 { 390 pd_ui_->profileTreeView->setCurrentIndex(ridx); 391 pd_ui_->profileTreeView->scrollTo(ridx); 392 pd_ui_->profileTreeView->edit(ridx); 393 currentItemChanged(); 394 } 395 else 396 updateWidgets(); 397 } 398 399 void ProfileDialog::on_deleteToolButton_clicked() 400 { 401 QModelIndexList profiles = selectedProfiles(); 402 if (profiles.count() <= 0) 403 return; 404 405 model_->deleteEntries(profiles); 406 407 bool isGlobal = model_->activeProfile().data(ProfileModel::DATA_IS_GLOBAL).toBool(); 408 int row = model_->findByName(model_->activeProfile().data().toString()); 409 /* If the active profile is deleted, the default is selected next */ 410 if (row < 0) 411 row = 0; 412 QModelIndex newIdx = sort_model_->mapFromSource(model_->index(row, 0)); 413 if (newIdx.data(ProfileModel::DATA_IS_GLOBAL).toBool() != isGlobal) 414 newIdx = sort_model_->mapFromSource(model_->index(0, 0)); 415 416 pd_ui_->profileTreeView->setCurrentIndex(newIdx); 417 418 updateWidgets(); 419 } 420 421 void ProfileDialog::on_copyToolButton_clicked() 422 { 423 QModelIndexList profiles = selectedProfiles(); 424 if (profiles.count() > 1) 425 return; 426 427 pd_ui_->lineProfileFilter->setText(""); 428 pd_ui_->cmbProfileTypes->setCurrentIndex(ProfileSortModel::AllProfiles); 429 sort_model_->setFilterString(); 430 431 QModelIndex current = pd_ui_->profileTreeView->currentIndex(); 432 if (current.column() != ProfileModel::COL_NAME) 433 current = current.sibling(current.row(), ProfileModel::COL_NAME); 434 435 QModelIndex source = sort_model_->mapToSource(current); 436 QModelIndex ridx = model_->duplicateEntry(source); 437 if (ridx.isValid()) 438 { 439 pd_ui_->profileTreeView->setCurrentIndex(sort_model_->mapFromSource(ridx)); 440 pd_ui_->profileTreeView->scrollTo(sort_model_->mapFromSource(ridx)); 441 pd_ui_->profileTreeView->edit(sort_model_->mapFromSource(ridx)); 442 currentItemChanged(); 443 } 444 else 445 updateWidgets(); 446 } 447 448 void ProfileDialog::on_buttonBox_accepted() 449 { 450 bool write_recent = true; 451 bool item_data_removed = false; 452 453 QModelIndex index = sort_model_->mapToSource(pd_ui_->profileTreeView->currentIndex()); 454 455 pd_ui_->buttonBox->setFocus(); 456 457 QModelIndexList profiles = selectedProfiles(); 458 if (profiles.count() <= 0) 459 index = QModelIndex(); 460 461 QModelIndex default_item = sort_model_->mapFromSource(model_->index(0, ProfileModel::COL_NAME)); 462 if (index.isValid() && index.column() != ProfileModel::COL_NAME) 463 index = index.sibling(index.row(), ProfileModel::COL_NAME); 464 465 if (default_item.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_DEFAULT && model_->resetDefault()) 466 { 467 // Reset Default profile. 468 GList *fl_entry = model_->at(0); 469 remove_from_profile_list(fl_entry); 470 471 // Don't write recent file if leaving the Default profile after this has been reset. 472 write_recent = !is_default_profile(); 473 474 // Don't fetch profile data if removed. 475 item_data_removed = (index.row() == 0); 476 } 477 478 if (write_recent) { 479 /* Get the current geometry, before writing it to disk */ 480 wsApp->emitAppSignal(WiresharkApplication::ProfileChanging); 481 482 /* Write recent file for current profile now because 483 * the profile may be renamed in apply_profile_changes() */ 484 write_profile_recent(); 485 } 486 487 gchar * err_msg = Q_NULLPTR; 488 if ((err_msg = apply_profile_changes()) != Q_NULLPTR) { 489 QMessageBox::critical(this, tr("Profile Error"), 490 err_msg, 491 QMessageBox::Ok); 492 g_free(err_msg); 493 494 model_->doResetModel(); 495 return; 496 } 497 498 model_->doResetModel(); 499 500 QString profileName; 501 502 if (! index.isValid() && model_->lastSetRow() >= 0) 503 { 504 QModelIndex original = model_->index(model_->lastSetRow(), ProfileModel::COL_NAME); 505 index = sort_model_->mapFromSource(original); 506 } 507 508 /* If multiple profiles are selected, do not change the selected profile */ 509 if (index.isValid() && ! item_data_removed && profiles.count() <= 1) 510 { 511 profileName = model_->data(index).toString(); 512 } 513 514 if (profileName.length() > 0 && model_->findByName(profileName) >= 0) { 515 // The new profile exists, change. 516 wsApp->setConfigurationProfile (profileName.toUtf8().constData(), FALSE); 517 } else if (!model_->activeProfile().isValid()) { 518 // The new profile does not exist, and the previous profile has 519 // been deleted. Change to the default profile. 520 wsApp->setConfigurationProfile (Q_NULLPTR, FALSE); 521 } 522 } 523 524 void ProfileDialog::on_buttonBox_rejected() 525 { 526 QString msg; 527 if (! model_->clearImported(&msg)) 528 QMessageBox::critical(this, tr("Error"), msg); 529 } 530 531 void ProfileDialog::on_buttonBox_helpRequested() 532 { 533 wsApp->helpTopicAction(HELP_CONFIG_PROFILES_DIALOG); 534 } 535 536 void ProfileDialog::dataChanged(const QModelIndex &) 537 { 538 pd_ui_->lineProfileFilter->setText(""); 539 pd_ui_->cmbProfileTypes->setCurrentIndex(ProfileSortModel::AllProfiles); 540 541 pd_ui_->profileTreeView->setFocus(); 542 if (model_->lastSetRow() >= 0) 543 { 544 QModelIndex original = model_->index(model_->lastSetRow(), ProfileModel::COL_NAME); 545 pd_ui_->profileTreeView->setCurrentIndex(sort_model_->mapFromSource(original)); 546 pd_ui_->profileTreeView->selectRow(sort_model_->mapFromSource(original).row()); 547 } 548 549 updateWidgets(); 550 } 551 552 void ProfileDialog::filterChanged(const QString &text) 553 { 554 if (qobject_cast<QComboBox *>(sender())) 555 { 556 QComboBox * cmb = qobject_cast<QComboBox *>(sender()); 557 sort_model_->setFilterType(static_cast<ProfileSortModel::FilterType>(cmb->currentIndex())); 558 } 559 else if (qobject_cast<QLineEdit *>(sender())) 560 sort_model_->setFilterString(text); 561 562 pd_ui_->profileTreeView->resizeColumnToContents(ProfileModel::COL_NAME); 563 564 QModelIndex active = sort_model_->mapFromSource(model_->activeProfile()); 565 if (active.isValid()) 566 pd_ui_->profileTreeView->setCurrentIndex(active); 567 } 568 569 #ifdef HAVE_MINIZIP 570 void ProfileDialog::exportProfiles(bool exportAllPersonalProfiles) 571 { 572 QAction * action = qobject_cast<QAction *>(sender()); 573 if (action && action->property(PROFILE_EXPORT_PROPERTY).isValid()) 574 exportAllPersonalProfiles = action->property(PROFILE_EXPORT_PROPERTY).toString().compare(PROFILE_EXPORT_ALL) == 0; 575 576 QModelIndexList items; 577 int skipped = 0; 578 579 if (! exportAllPersonalProfiles) 580 { 581 foreach (QModelIndex idx, selectedProfiles()) 582 { 583 if (! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool()) 584 items << idx; 585 else 586 skipped++; 587 } 588 } 589 else if (exportAllPersonalProfiles) 590 { 591 for (int cnt = 0; cnt < sort_model_->rowCount(); cnt++) 592 { 593 QModelIndex idx = sort_model_->index(cnt, ProfileModel::COL_NAME); 594 if (! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool()) 595 items << sort_model_->mapToSource(idx); 596 } 597 } 598 if (items.count() == 0) 599 { 600 QString msg = tr("No profiles found for export"); 601 if (skipped > 0) 602 msg.append(tr(", %Ln profile(s) skipped", "", skipped)); 603 QMessageBox::critical(this, tr("Exporting profiles"), msg); 604 return; 605 } 606 607 QString zipFile = QFileDialog::getSaveFileName(this, tr("Select zip file for export"), lastOpenDir(), tr("Zip File (*.zip)")); 608 609 if (zipFile.length() > 0) 610 { 611 QFileInfo fi(zipFile); 612 if (fi.suffix().length() == 0 || fi.suffix().toLower().compare("zip") != 0) 613 zipFile += ".zip"; 614 615 QString err; 616 if (model_->exportProfiles(zipFile, items, &err)) 617 { 618 QString msg = tr("%Ln profile(s) exported", "", items.count()); 619 if (skipped > 0) 620 msg.append(tr(", %Ln profile(s) skipped", "", skipped)); 621 QMessageBox::information(this, tr("Exporting profiles"), msg); 622 623 QFileInfo zip(zipFile); 624 storeLastDir(zip.absolutePath()); 625 } 626 else 627 { 628 QString msg = tr("An error has occurred while exporting profiles"); 629 if (err.length() > 0) 630 msg.append(QString("\n\n%1: %3").arg(tr("Error")).arg(err)); 631 QMessageBox::critical(this, tr("Exporting profiles"), msg); 632 } 633 } 634 } 635 636 void ProfileDialog::importFromZip() 637 { 638 QString zipFile = QFileDialog::getOpenFileName(this, tr("Select zip file for import"), lastOpenDir(), tr("Zip File (*.zip)")); 639 640 QFileInfo fi(zipFile); 641 if (! fi.exists()) 642 return; 643 644 int skipped = 0; 645 QStringList import; 646 int count = model_->importProfilesFromZip(zipFile, &skipped, &import); 647 648 finishImport(fi, count, skipped, import); 649 } 650 #endif 651 652 void ProfileDialog::importFromDirectory() 653 { 654 QString importDir = QFileDialog::getExistingDirectory(this, tr("Select directory for import"), lastOpenDir()); 655 656 QFileInfo fi(importDir); 657 if (! fi.isDir()) 658 return; 659 660 int skipped = 0; 661 QStringList import; 662 int count = model_->importProfilesFromDir(importDir, &skipped, false, &import); 663 664 finishImport(fi, count, skipped, import); 665 } 666 667 void ProfileDialog::finishImport(QFileInfo fi, int count, int skipped, QStringList import) 668 { 669 QString msg; 670 QMessageBox::Icon icon; 671 672 if (count == 0 && skipped == 0) 673 { 674 icon = QMessageBox::Warning; 675 msg = tr("No profiles found for import in %1").arg(fi.fileName()); 676 } 677 else 678 { 679 icon = QMessageBox::Information; 680 msg = tr("%Ln profile(s) imported", "", count); 681 if (skipped > 0) 682 msg.append(tr(", %Ln profile(s) skipped", "", skipped)); 683 } 684 685 storeLastDir(fi.absolutePath()); 686 687 if (count > 0) 688 { 689 import.sort(); 690 resetTreeView(); 691 model_->markAsImported(import); 692 int rowFirstImported = model_->findByName(import.at(0)); 693 QModelIndex idx = sort_model_->mapFromSource(model_->index(rowFirstImported, ProfileModel::COL_NAME)); 694 pd_ui_->profileTreeView->selectRow(idx.isValid() ? idx.row() : 0); 695 } 696 697 QMessageBox msgBox(icon, tr("Importing profiles"), msg, QMessageBox::Ok, this); 698 msgBox.exec(); 699 700 updateWidgets(); 701 } 702 703 QString ProfileDialog::lastOpenDir() 704 { 705 QString result; 706 707 switch (prefs.gui_fileopen_style) { 708 709 case FO_STYLE_LAST_OPENED: 710 /* The user has specified that we should start out in the last directory 711 we looked in. If we've already opened a file, use its containing 712 directory, if we could determine it, as the directory, otherwise 713 use the "last opened" directory saved in the preferences file if 714 there was one. */ 715 /* This is now the default behaviour in file_selection_new() */ 716 result = QString(get_last_open_dir()); 717 break; 718 719 case FO_STYLE_SPECIFIED: 720 /* The user has specified that we should always start out in a 721 specified directory; if they've specified that directory, 722 start out by showing the files in that dir. */ 723 if (prefs.gui_fileopen_dir[0] != '\0') 724 result = QString(prefs.gui_fileopen_dir); 725 break; 726 } 727 728 QDir ld(result); 729 if (ld.exists()) 730 return result; 731 732 return QString(); 733 } 734 735 void ProfileDialog::storeLastDir(QString dir) 736 { 737 if (wsApp && dir.length() > 0) 738 wsApp->setLastOpenDir(qUtf8Printable(dir)); 739 } 740 741 void ProfileDialog::resetTreeView() 742 { 743 if (model_) 744 { 745 pd_ui_->profileTreeView->setModel(Q_NULLPTR); 746 sort_model_->setSourceModel(Q_NULLPTR); 747 model_->disconnect(); 748 if (pd_ui_->profileTreeView->selectionModel()) 749 pd_ui_->profileTreeView->selectionModel()->disconnect(); 750 delete sort_model_; 751 delete model_; 752 } 753 754 model_ = new ProfileModel(pd_ui_->profileTreeView); 755 sort_model_ = new ProfileSortModel(pd_ui_->profileTreeView); 756 sort_model_->setSourceModel(model_); 757 pd_ui_->profileTreeView->setModel(sort_model_); 758 759 connect(model_, &ProfileModel::itemChanged, this, &ProfileDialog::dataChanged, Qt::QueuedConnection); 760 QItemSelectionModel *selModel = pd_ui_->profileTreeView->selectionModel(); 761 connect(selModel, &QItemSelectionModel::currentChanged, 762 this, &ProfileDialog::currentItemChanged, Qt::QueuedConnection); 763 connect(selModel, SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), 764 this, SLOT(selectionChanged())); 765 766 selectionChanged(); 767 768 if (sort_model_->columnCount() <= 1) 769 pd_ui_->profileTreeView->header()->hide(); 770 else 771 { 772 pd_ui_->profileTreeView->header()->setStretchLastSection(false); 773 pd_ui_->profileTreeView->header()->setSectionResizeMode(ProfileModel::COL_NAME, QHeaderView::Stretch); 774 } 775 } 776