//=============================================================================
//
// File : OptionsDialog.cpp
// Created on Fri 10 Feb 2006 18:52:18 by Szymon Stefanek
// Based on dlg_options.cpp: Tue Jun 11 2000 02:39:12 by Szymon Stefanek
//
// This file is part of the KVIrc IRC Client distribution
// Copyright (C) 2000-2010 Szymon Stefanek <pragma at kvirc dot 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.
//
// This program is distributed in the HOPE that it will be USEFUL,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================
#include "OptionsDialog.h"
#include "KviApplication.h"
#include "KviOptions.h"
#include "KviLocale.h"
#include "KviIconManager.h"
#include "KviMainWindow.h"
#include "KviModule.h"
#include "KviPointerHashTable.h"
#include "KviTalVBox.h"
#include "KviTalToolTip.h"
#include "KviTalGroupBox.h"
#include <QLayout>
#include <QLabel>
#include <QSplitter>
#include <QPushButton>
#include <QPainter>
#include <QFont>
#include <QEvent>
#include <QCloseEvent>
#include <QShortcut>
#include <QHeaderView>
#include <QDesktopWidget>
#include <QStackedWidget>
#include <QMenu>
//FIXME: some headers must be hidden
//extern KviModule * g_pOptionsModule;
extern KviPointerHashTable<QString, OptionsDialog> * g_pOptionsDialogDict;
extern KVIRC_API KviApplication * g_pApp;
extern OptionsInstanceManager * g_pOptionsInstanceManager;
OptionsDialogFrontWidget::OptionsDialogFrontWidget(QWidget * parent, const QString & szText)
: KviOptionsWidget(parent)
{
setObjectName("general_options_front_widget");
createLayout();
QLabel * l = new QLabel(szText, this);
l->setWordWrap(true);
l->setAlignment(Qt::AlignTop);
layout()->addWidget(l, 0, 0);
}
OptionsDialogFrontWidget::~OptionsDialogFrontWidget()
= default;
OptionsDialogTreeWidgetItem::OptionsDialogTreeWidgetItem(QTreeWidget * parent, OptionsWidgetInstanceEntry * e)
: QTreeWidgetItem()
{
m_pInstanceEntry = e;
m_pOptionsWidget = nullptr;
setText(0, e->szName);
setIcon(0, *(g_pIconManager->getSmallIcon(e->eIcon)));
parent->insertTopLevelItem(0, this);
}
OptionsDialogTreeWidgetItem::OptionsDialogTreeWidgetItem(QTreeWidgetItem * parent, OptionsWidgetInstanceEntry * e)
: QTreeWidgetItem()
{
m_pInstanceEntry = e;
m_pOptionsWidget = nullptr;
setText(0, e->szName);
setIcon(0, *(g_pIconManager->getSmallIcon(e->eIcon)));
parent->insertChild(0, this);
}
OptionsDialogTreeWidgetItem::~OptionsDialogTreeWidgetItem()
= default;
OptionsDialog::OptionsDialog(QWidget * par, const QString & szGroup, bool bModal)
: QDialog(par)
{
setObjectName("general_options_dialog");
setWindowIcon(*(g_pIconManager->getSmallIcon(KviIconManager::KVIrc)));
m_szGroup = szGroup;
QString szDialogTitle;
if(szGroup.isEmpty() || KviQString::equalCI(szGroup, "general"))
{
szDialogTitle = __tr2qs_ctx("General Preferences", "options");
}
else if(KviQString::equalCI(szGroup, "theme"))
{
szDialogTitle = __tr2qs_ctx("Theme Preferences", "options");
}
else
{
szDialogTitle = __tr2qs_ctx("KVIrc Preferences", "options");
}
QString szDialog = __tr2qs_ctx("This dialog contains a set of KVIrc settings.<br> Use the icons "
"on the left to navigate through the option pages. The text box in the "
"bottom left corner is a small search engine. It will highlight the "
"pages that contain options related to the search term you have entered.",
"options");
QString szInfoTips;
szInfoTips = __tr2qs_ctx("Many settings have tooltips that can be shown by holding "
"the cursor over their label for a few seconds.",
"options");
QString szOkCancelButtons = __tr2qs_ctx("When you have finished, click \"<b>OK</b>\" to accept your changes "
"or \"<b>Cancel</b>\" to discard them. Clicking \"<b>Apply</b>\" will commit your "
"changes without closing the window.",
"options");
// clang-format off
QString szFrontText = QString(
"<table width=\"100%\" height=\"100%\" valign=\"top\" align=\"center\" cellpadding=\"4\">" \
"<tr>" \
"<td bgcolor=\"#303030\" valign=\"top\">" \
"<center><h1><font color=\"#FFFFFF\">%1</font></h1></center>" \
"</td>" \
"</tr>" \
"<tr>" \
"<td valign=\"bottom\">" \
"<p>" \
"%2" \
"</p>" \
"<br style=\"line-height:5px;\"/>" \
"<p>" \
"%3" \
"</p>" \
"<br style=\"line-height:5px;\"/>" \
"<p>" \
"%4" \
"</p>" \
"</td>" \
"</tr>" \
"</table>"
).arg(szDialogTitle, szDialog, szInfoTips, szOkCancelButtons);
// clang-format on
QString szCaption = szDialogTitle + " - KVIrc";
setWindowTitle(szCaption);
QGridLayout * g1 = new QGridLayout(this);
QSplitter * spl = new QSplitter(Qt::Horizontal, this);
spl->setChildrenCollapsible(false);
g1->addWidget(spl, 0, 0, 1, 5);
KviTalVBox * vbox = new KviTalVBox(spl);
vbox->setSpacing(2);
vbox->setMargin(3);
// Controlling list view
m_pTreeWidget = new QTreeWidget(vbox);
m_pTreeWidget->header()->hide();
m_pTreeWidget->setRootIsDecorated(true);
m_pTreeWidget->setSortingEnabled(false);
m_pTreeWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_pTreeWidget->setCurrentItem(nullptr);
connect(m_pTreeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(treeWidgetItemSelectionChanged(QTreeWidgetItem *, QTreeWidgetItem *)));
KviTalHBox * hbox = new KviTalHBox(vbox);
hbox->setSpacing(2);
hbox->setMargin(3);
m_pSearchLineEdit = new QLineEdit(hbox);
connect(m_pSearchLineEdit, SIGNAL(textEdited(const QString &)), this, SLOT(searchClicked()));
m_pSearchButton = new QToolButton(hbox);
m_pSearchButton->setIconSize(QSize(16, 16));
m_pSearchButton->setIcon(*(g_pIconManager->getSmallIcon(KviIconManager::Search)));
connect(m_pSearchButton, SIGNAL(clicked()), this, SLOT(searchClicked()));
connect(m_pSearchLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(searchLineEditTextChanged(const QString &)));
QString szTip = __tr2qs_ctx("<p>This is the search tool for this options dialog.</p>"
"<p>You can enter a search term either in your native "
"language or in English and press the button on the right. "
"The pages that contain some options related to the "
"search term will be highlighted and you will be able "
"to quickly find them.</p><p>Try \"nickname\" for example.</p>",
"options");
KviTalToolTip::add(m_pSearchLineEdit, szTip);
KviTalToolTip::add(m_pSearchButton, szTip);
vbox = new KviTalVBox(spl);
vbox->setSpacing(2);
vbox->setMargin(3);
m_pCategoryLabel = new QLabel("<b> </b>", vbox);
m_pCategoryLabel->setObjectName("labgeneraloptions");
QFrame * f = new QFrame(vbox);
f->setFrameStyle(QFrame::HLine | QFrame::Sunken);
// Widget stack
m_pWidgetStack = new QStackedWidget(vbox);
vbox->setStretchFactor(m_pWidgetStack, 1);
// First widget visible
m_pFrontWidget = new OptionsDialogFrontWidget(m_pWidgetStack, szFrontText);
m_pWidgetStack->addWidget(m_pFrontWidget);
int idx = m_pWidgetStack->indexOf(m_pFrontWidget);
m_pWidgetStack->widget(idx)->raise();
// Ok,Cancel,Help
QPushButton * b = new QPushButton(__tr2qs_ctx("&OK", "options"), this);
b->setObjectName("btnok");
KviTalToolTip::add(b, __tr2qs_ctx("Close this dialog, accepting all changes.", "options"));
connect(b, SIGNAL(clicked()), this, SLOT(okClicked()));
//b->setMinimumWidth(whatIsThisWidth);
b->setIcon(*(g_pIconManager->getSmallIcon(KviIconManager::Accept)));
g1->addWidget(b, 1, 2);
b = new QPushButton(__tr2qs_ctx("&Apply", "options"), this);
b->setObjectName("btnapply");
KviTalToolTip::add(b, __tr2qs_ctx("Commit all changes immediately.", "options"));
connect(b, SIGNAL(clicked()), this, SLOT(applyClicked()));
b->setIcon(*(g_pIconManager->getSmallIcon(KviIconManager::Accept)));
g1->addWidget(b, 1, 3);
b = new QPushButton(__tr2qs_ctx("Cancel", "options"), this);
b->setObjectName("btncancel");
KviTalToolTip::add(b, __tr2qs_ctx("Close this dialog, discarding all changes.", "options"));
b->setDefault(true);
//b->setMinimumWidth(whatIsThisWidth);
connect(b, SIGNAL(clicked()), this, SLOT(cancelClicked()));
b->setIcon(*(g_pIconManager->getSmallIcon(KviIconManager::Discard)));
g1->addWidget(b, 1, 4);
g1->setRowStretch(0, 1);
g1->setColumnStretch(1, 1);
fillTreeWidget(nullptr, g_pOptionsInstanceManager->instanceEntryTree(), szGroup);
if(!parent())
{
if(KVI_OPTION_RECT(KviOption_rectGeneralOptionsDialogGeometry).y() < 5)
KVI_OPTION_RECT(KviOption_rectGeneralOptionsDialogGeometry)
.setY(5);
//setGeometry(KVI_OPTION_RECT(KviOption_rectGeneralOptionsDialogGeometry));
resize(KVI_OPTION_RECT(KviOption_rectGeneralOptionsDialogGeometry).width(),
KVI_OPTION_RECT(KviOption_rectGeneralOptionsDialogGeometry).height());
move(KVI_OPTION_RECT(KviOption_rectGeneralOptionsDialogGeometry).x(),
KVI_OPTION_RECT(KviOption_rectGeneralOptionsDialogGeometry).y());
}
new QShortcut(Qt::Key_Escape, this, SLOT(close()));
if(bModal)
setWindowModality(par ? Qt::WindowModal : Qt::ApplicationModal);
}
OptionsDialog::~OptionsDialog()
{
if(!parent())
KVI_OPTION_RECT(KviOption_rectGeneralOptionsDialogGeometry) = QRect(pos().x(), pos().y(), size().width(), size().height());
if(g_pOptionsDialogDict)
g_pOptionsDialogDict->remove(m_szGroup);
}
void OptionsDialog::showEvent(QShowEvent * e)
{
// setup a minimum size and move to the screen the main kvirc window is
QRect r = g_pApp->desktop()->screenGeometry(g_pMainWindow);
int w = r.width();
int h = r.height();
int ww = width();
int wh = height();
if(w < 800)
{
// 640x480
if(ww < 630)
ww = 630;
}
else
{
if(ww < 770)
ww = 770;
}
setGeometry(r.x() + ((w - ww) / 2), r.y() + ((h - wh) / 2), ww, wh);
QWidget::showEvent(e);
}
void OptionsDialog::searchLineEditTextChanged(const QString &)
{
QString txt = m_pSearchLineEdit->text().trimmed();
m_pSearchButton->setEnabled(txt.length() > 0);
}
bool OptionsDialog::searchInSelectors(KviOptionsWidget * pOptionsWidget, const QStringList & lKeywords)
{
KviPointerList<KviSelectorInterface> * selectors = pOptionsWidget->selectors();
bool bCleaningUp = lKeywords.isEmpty();
bool bFoundSomethingHere = false;
if(selectors->count() > 0)
{
for(unsigned int i = 0; i < selectors->count(); i++)
{
QString szText = selectors->at(i)->textForSearch();
QWidget * pWidget = selectors->at(i)->widgetToHighlight();
if(!pWidget)
continue;
if(bCleaningUp)
{
QFont font = pWidget->font();
font.setBold(false);
font.setUnderline(false);
pWidget->setFont(font);
}
else
{
QString szTmp = pWidget->toolTip();
szTmp = szTmp.replace(QRegExp("<[^<>]+>"), "");
szText.append(szTmp);
if(!szText.isEmpty())
{
bool bOk = true;
for(int j = 0; j < lKeywords.count(); j++)
{
if(szText.indexOf(lKeywords.at(j), 0, Qt::CaseInsensitive) == -1)
{
bOk = false;
break;
}
}
if(bOk)
{
bFoundSomethingHere = true;
}
QFont font = pWidget->font();
font.setBold(bOk);
font.setUnderline(bOk);
pWidget->setFont(font);
}
}
}
}
return bFoundSomethingHere;
}
bool OptionsDialog::recursiveSearch(OptionsDialogTreeWidgetItem * pItem, const QStringList & lKeywords)
{
if(!pItem)
return false;
if(!pItem->m_pOptionsWidget)
{
pItem->m_pOptionsWidget = g_pOptionsInstanceManager->getInstance(pItem->m_pInstanceEntry, m_pWidgetStack);
m_pWidgetStack->addWidget(pItem->m_pOptionsWidget);
}
bool bCleaningUp = lKeywords.isEmpty();
bool bFoundSomethingHere = false;
KviOptionsWidget * pOptionsWidget = pItem->m_pOptionsWidget;
QTabWidget * pTab = pOptionsWidget->tabWidget();
if(pTab)
{
for(int i = 0; i < pTab->count(); i++)
{
QString szTxt = pTab->tabText(i);
if(bCleaningUp || KviQString::equalCIN(szTxt, ">>> ", 4))
{
szTxt.replace(">>> ", "");
szTxt.replace(" <<<", "");
}
if(searchInSelectors((KviOptionsWidget *)pTab->widget(i), lKeywords) && !bCleaningUp)
{
bFoundSomethingHere = true;
szTxt.insert(0, ">>> ");
szTxt += QString(" <<<");
}
pTab->setTabText(i, szTxt);
}
}
else
{
if(searchInSelectors(pOptionsWidget, lKeywords) && !bCleaningUp)
bFoundSomethingHere = true;
}
if (!bCleaningUp)
{
QStringList szInstanceKeywords = pItem->m_pInstanceEntry->szKeywords.split(QChar(','));
// debug all the "search keywords" for each entry in the options tree
// qDebug("OPT %s",pItem->m_pInstanceEntry->szKeywords.toUtf8().data());
for(int i = 0; i < szInstanceKeywords.count() && !bFoundSomethingHere; i++)
for(int j = 0; j < lKeywords.count() && !bFoundSomethingHere; j++)
if(szInstanceKeywords.at(i).contains(lKeywords.at(j), Qt::CaseInsensitive))
bFoundSomethingHere = true;
}
if(bFoundSomethingHere)
{
pItem->setForeground(0, Qt::yellow);
pItem->setBackground(0, Qt::red);
}
else
{
pItem->setForeground(0, Qt::black);
pItem->setBackground(0, Qt::transparent);
}
bool bFoundSomethingInside = false;
int ccount = pItem->childCount();
if(ccount > 0)
{
for(int j = 0; j < ccount; j++)
{
OptionsDialogTreeWidgetItem * pChild = (OptionsDialogTreeWidgetItem *)pItem->child(j);
bool bRet = recursiveSearch(pChild, lKeywords) && !bCleaningUp;
if(bRet)
bFoundSomethingInside = true;
}
}
pItem->setSelected(false);
pItem->setExpanded(bFoundSomethingInside);
return (bFoundSomethingInside || bFoundSomethingHere);
}
void OptionsDialog::search(const QStringList & lKeywords)
{
m_pTreeWidget->setUpdatesEnabled(false);
int count = m_pTreeWidget->topLevelItemCount();
for(int i = 0; i < count; i++)
{
OptionsDialogTreeWidgetItem * pChild = (OptionsDialogTreeWidgetItem *)m_pTreeWidget->topLevelItem(i);
recursiveSearch(pChild, lKeywords);
}
m_pTreeWidget->setUpdatesEnabled(true);
m_pTreeWidget->update();
}
void OptionsDialog::search(const QString & szKeywords)
{
QStringList lKeywords = szKeywords.split(" ", QString::SkipEmptyParts);
search(lKeywords);
}
void OptionsDialog::clearSearch()
{
m_pTreeWidget->setUpdatesEnabled(false);
QTreeWidgetItemIterator it(m_pTreeWidget);
while (*it) {
recursiveSearch(((OptionsDialogTreeWidgetItem *)* it), QStringList());
++it;
}
m_pTreeWidget->setUpdatesEnabled(true);
m_pTreeWidget->update();
}
void OptionsDialog::searchClicked()
{
QString szTxt = m_pSearchLineEdit->text().trimmed();
if(szTxt.length() > 1)
search(szTxt);
else
clearSearch();
}
void OptionsDialog::fillTreeWidget(QTreeWidgetItem * p, KviPointerList<OptionsWidgetInstanceEntry> * l, const QString & szGroup, bool bNotContainedOnly)
{
if(!l)
return;
OptionsDialogTreeWidgetItem * it;
OptionsWidgetInstanceEntry * e;
KviPointerList<OptionsWidgetInstanceEntry> tmp;
tmp.setAutoDelete(false);
for(e = l->first(); e; e = l->next())
{
// must be in the correct group
// if we want only containers then well.. must be one
e->bDoInsert = KviQString::equalCI(szGroup, e->szGroup) && ((!bNotContainedOnly) || e->bIsContainer || e->bIsNotContained);
OptionsWidgetInstanceEntry * ee = tmp.first();
int idx = 0;
while(ee)
{
if(ee->iPriority >= e->iPriority)
break;
idx++;
ee = tmp.next();
}
tmp.insert(idx, e);
}
for(e = tmp.first(); e; e = tmp.next())
{
if(e->bDoInsert)
{
if(p)
it = new OptionsDialogTreeWidgetItem(p, e);
else
it = new OptionsDialogTreeWidgetItem(m_pTreeWidget, e);
if(!it->m_pOptionsWidget)
{
it->m_pOptionsWidget = g_pOptionsInstanceManager->getInstance(it->m_pInstanceEntry, m_pWidgetStack);
if(it->m_pOptionsWidget)
m_pWidgetStack->addWidget(it->m_pOptionsWidget);
}
}
else
{
it = (OptionsDialogTreeWidgetItem *)p;
}
if(e->pChildList)
{
if(e->bIsContainer)
{
// it's a container: add only eventual not contained children (containers or explicitly marked as not contained)
fillTreeWidget(it, e->pChildList, szGroup, true);
}
else
{
// it's not a container, add any children
fillTreeWidget(it, e->pChildList, szGroup, false);
}
}
}
}
void OptionsDialog::treeWidgetItemSelectionChanged(QTreeWidgetItem * it, QTreeWidgetItem *)
{
if(it)
{
QString str = it->text(0);
QTreeWidgetItem * par = it->parent();
while(par)
{
str.prepend(" :: ");
str.prepend(par->text(0));
par = par->parent();
}
str.prepend("<b>");
str += "</b>";
OptionsDialogTreeWidgetItem * i = (OptionsDialogTreeWidgetItem *)it;
if(!i->m_pOptionsWidget)
{
i->m_pOptionsWidget = g_pOptionsInstanceManager->getInstance(i->m_pInstanceEntry, m_pWidgetStack);
m_pWidgetStack->addWidget(i->m_pOptionsWidget);
}
//int idx = m_pWidgetStack->indexOf(i->m_pOptionsWidget);
m_pWidgetStack->setCurrentWidget(i->m_pOptionsWidget);
m_pCategoryLabel->setText(str);
}
}
void OptionsDialog::pageWantsToSwitchToAdvancedPage(KviOptionsWidget *)
{
// unused
}
void OptionsDialog::applyClicked()
{
apply(false);
}
void OptionsDialog::apply(bool bDialogAboutToClose)
{
int count = m_pTreeWidget->topLevelItemCount();
int curTab = -1;
OptionsDialogTreeWidgetItem * it;
if(!bDialogAboutToClose)
{
// bring up the current widget again!
it = (OptionsDialogTreeWidgetItem *)m_pTreeWidget->currentItem();
//checking if the check of the check is checkable
if(it)
if(it->m_pOptionsWidget)
if(it->m_pOptionsWidget->tabWidget())
curTab = it->m_pOptionsWidget->tabWidget()->currentIndex();
}
for(int i = 0; i < count; i++)
{
it = (OptionsDialogTreeWidgetItem *)m_pTreeWidget->topLevelItem(i);
recursiveCommit(it);
}
if(!bDialogAboutToClose)
{
// bring up the current widget again!
it = (OptionsDialogTreeWidgetItem *)m_pTreeWidget->currentItem();
if(it)
{
treeWidgetItemSelectionChanged(it, nullptr);
if(curTab > 0 && it->m_pOptionsWidget)
if(it->m_pOptionsWidget->tabWidget())
it->m_pOptionsWidget->tabWidget()->setCurrentIndex(curTab);
}
}
g_pApp->saveConfiguration();
}
void OptionsDialog::okClicked()
{
apply(true);
deleteLater();
}
void OptionsDialog::cancelClicked()
{
deleteLater();
}
void OptionsDialog::closeEvent(QCloseEvent * e)
{
e->ignore();
deleteLater();
}
void OptionsDialog::recursiveCommit(OptionsDialogTreeWidgetItem * it)
{
// First commit the children
if(!it)
return;
int count = it->childCount();
for(int i = 0; i < count; i++)
{
OptionsDialogTreeWidgetItem * pChild = (OptionsDialogTreeWidgetItem *)it->child(i);
recursiveCommit(pChild);
}
// then the parents
if(it->m_pOptionsWidget)
{
it->m_pOptionsWidget->commit();
delete it->m_pOptionsWidget;
it->m_pOptionsWidget = nullptr;
}
//refreshTreeWidget(); // <-- this tends to jump into infinite recursion
}
void OptionsDialog::keyPressEvent(QKeyEvent * e)
{
if(e->key() == Qt::Key_Return)
e->accept();
else
e->ignore();
}