From 73fefcfb67b2de42a0675eb88aaa5cb2751402e6 Mon Sep 17 00:00:00 2001 From: kh Date: Mon, 27 Apr 2009 17:30:20 +0200 Subject: fixes undefined behavior will closing all tabs Task-number: 249001, 236473 Reviewed-by: kh --- tools/assistant/tools/assistant/centralwidget.cpp | 241 ++++++++++++---------- tools/assistant/tools/assistant/centralwidget.h | 14 +- tools/assistant/tools/assistant/mainwindow.cpp | 10 +- tools/assistant/tools/assistant/mainwindow.h | 1 + tools/assistant/tools/assistant/searchwidget.cpp | 11 + tools/assistant/tools/assistant/searchwidget.h | 4 + 6 files changed, 165 insertions(+), 116 deletions(-) diff --git a/tools/assistant/tools/assistant/centralwidget.cpp b/tools/assistant/tools/assistant/centralwidget.cpp index 0a94a3d..f953ab7 100644 --- a/tools/assistant/tools/assistant/centralwidget.cpp +++ b/tools/assistant/tools/assistant/centralwidget.cpp @@ -161,7 +161,7 @@ void FindWidget::updateButtons() QToolButton* FindWidget::setupToolButton(const QString &text, const QString &icon) { - QToolButton* toolButton = new QToolButton(this); + QToolButton *toolButton = new QToolButton(this); toolButton->setText(text); toolButton->setAutoRaise(true); @@ -262,13 +262,16 @@ CentralWidget::~CentralWidget() QString zoomCount; QString currentPages; QLatin1Char separator('|'); - for (int i = 1; i < tabWidget->count(); ++i) { + int i = m_searchWidget->isAttached() ? 1 : 0; + + for (; i < tabWidget->count(); ++i) { HelpViewer *viewer = qobject_cast(tabWidget->widget(i)); if (viewer && viewer->source().isValid()) { currentPages += viewer->source().toString() + separator; zoomCount += QString::number(viewer->zoom()) + separator; } } + engine.setCustomValue(QLatin1String("LastTabPage"), lastTabPage); engine.setCustomValue(QLatin1String("LastShownPages"), currentPages); #if !defined(QT_NO_WEBKIT) @@ -285,7 +288,7 @@ CentralWidget *CentralWidget::instance() void CentralWidget::newTab() { - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); #if !defined(QT_NO_WEBKIT) if (viewer && viewer->hasLoadFinished()) #else @@ -296,7 +299,7 @@ void CentralWidget::newTab() void CentralWidget::zoomIn() { - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (viewer) viewer->zoomIn(); @@ -306,7 +309,7 @@ void CentralWidget::zoomIn() void CentralWidget::zoomOut() { - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (viewer) viewer->zoomOut(); @@ -329,7 +332,7 @@ void CentralWidget::nextPage() void CentralWidget::resetZoom() { - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (viewer) viewer->resetZoom(); @@ -353,7 +356,7 @@ void CentralWidget::findPrevious() void CentralWidget::closeTab() { - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (!viewer|| tabWidget->count() == 1) return; @@ -363,8 +366,8 @@ void CentralWidget::closeTab() void CentralWidget::setSource(const QUrl &url) { - HelpViewer* viewer = currentHelpViewer(); - HelpViewer* lastViewer = qobject_cast(tabWidget->widget(lastTabPage)); + HelpViewer *viewer = currentHelpViewer(); + HelpViewer *lastViewer = qobject_cast(tabWidget->widget(lastTabPage)); if (!viewer && !lastViewer) { viewer = new HelpViewer(helpEngine, this); @@ -418,13 +421,13 @@ void CentralWidget::setLastShownPages() bool CentralWidget::hasSelection() const { - const HelpViewer* viewer = currentHelpViewer(); + const HelpViewer *viewer = currentHelpViewer(); return viewer ? viewer->hasSelection() : false; } QUrl CentralWidget::currentSource() const { - const HelpViewer* viewer = currentHelpViewer(); + const HelpViewer *viewer = currentHelpViewer(); if (viewer) return viewer->source(); @@ -433,7 +436,7 @@ QUrl CentralWidget::currentSource() const QString CentralWidget::currentTitle() const { - const HelpViewer* viewer = currentHelpViewer(); + const HelpViewer *viewer = currentHelpViewer(); if (viewer) return viewer->documentTitle(); @@ -442,7 +445,7 @@ QString CentralWidget::currentTitle() const void CentralWidget::copySelection() { - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (viewer) viewer->copy(); } @@ -465,7 +468,7 @@ void CentralWidget::initPrinter() void CentralWidget::print() { #ifndef QT_NO_PRINTER - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (!viewer) return; @@ -522,14 +525,14 @@ bool CentralWidget::isHomeAvailable() const void CentralWidget::home() { - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (viewer) viewer->home(); } bool CentralWidget::isForwardAvailable() const { - const HelpViewer* viewer = currentHelpViewer(); + const HelpViewer *viewer = currentHelpViewer(); if (viewer) return viewer->isForwardAvailable(); @@ -538,14 +541,14 @@ bool CentralWidget::isForwardAvailable() const void CentralWidget::forward() { - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (viewer) viewer->forward(); } bool CentralWidget::isBackwardAvailable() const { - const HelpViewer* viewer = currentHelpViewer(); + const HelpViewer *viewer = currentHelpViewer(); if (viewer) return viewer->isBackwardAvailable(); @@ -554,7 +557,7 @@ bool CentralWidget::isBackwardAvailable() const void CentralWidget::backward() { - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (viewer) viewer->backward(); } @@ -572,7 +575,7 @@ void CentralWidget::setGlobalActions(const QList &actions) void CentralWidget::setSourceInNewTab(const QUrl &url, qreal zoom) { - HelpViewer* viewer; + HelpViewer *viewer; #if defined(QT_NO_WEBKIT) viewer = currentHelpViewer(); @@ -595,20 +598,20 @@ void CentralWidget::setSourceInNewTab(const QUrl &url, qreal zoom) } #if !defined(QT_NO_WEBKIT) - QWebSettings* settings = QWebSettings::globalSettings(); + QWebSettings *settings = QWebSettings::globalSettings(); if (!userFont) { int fontSize = settings->fontSize(QWebSettings::DefaultFontSize); QString fontFamily = settings->fontFamily(QWebSettings::StandardFont); font = QFont(fontFamily, fontSize); } - QWebView* view = qobject_cast (viewer); + QWebView *view = qobject_cast (viewer); if (view) { settings = view->settings(); settings->setFontFamily(QWebSettings::StandardFont, font.family()); settings->setFontSize(QWebSettings::DefaultFontSize, font.pointSize()); } else if (viewer) { - viewer->setFont(font); + viewer->setFont(font); } viewer->setTextSizeMultiplier(zoom == 0.0 ? 1.0 : zoom); #else @@ -622,7 +625,7 @@ void CentralWidget::setSourceInNewTab(const QUrl &url, qreal zoom) HelpViewer *CentralWidget::newEmptyTab() { - HelpViewer* viewer = new HelpViewer(helpEngine, this); + HelpViewer *viewer = new HelpViewer(helpEngine, this); viewer->installEventFilter(this); viewer->setFocus(Qt::OtherFocusReason); #if defined(QT_NO_WEBKIT) @@ -641,7 +644,7 @@ void CentralWidget::findCurrentText(const QString &text) void CentralWidget::connectSignals() { - const HelpViewer* viewer = currentHelpViewer(); + const HelpViewer *viewer = currentHelpViewer(); if (viewer) { connect(viewer, SIGNAL(copyAvailable(bool)), this, SIGNAL(copyAvailable(bool))); @@ -676,18 +679,18 @@ void CentralWidget::activateTab(bool onlyHelpViewer) } } -void CentralWidget::setTabTitle(const QUrl& url) +void CentralWidget::setTabTitle(const QUrl &url) { Q_UNUSED(url) #if !defined(QT_NO_WEBKIT) QTabBar *tabBar = qFindChild(tabWidget); for (int tab = 0; tab < tabBar->count(); ++tab) { - HelpViewer* viewer = qobject_cast(tabWidget->widget(tab)); + HelpViewer *viewer = qobject_cast(tabWidget->widget(tab)); if (viewer) tabWidget->setTabText(tab, viewer->documentTitle().trimmed()); } #else - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); if (viewer) { tabWidget->setTabText(lastTabPage, quoteTabTitle(viewer->documentTitle().trimmed())); @@ -698,61 +701,50 @@ void CentralWidget::setTabTitle(const QUrl& url) void CentralWidget::currentPageChanged(int index) { const HelpViewer *viewer = currentHelpViewer(); - - if (viewer || tabWidget->count() == 1) + if (viewer) lastTabPage = index; - bool enabled = false; - if (viewer) { - enabled = true; - if (!m_searchWidget) - enabled = tabWidget->count() > 1; - } + QWidget *widget = tabWidget->cornerWidget(Qt::TopRightCorner); + widget->setEnabled(viewer && enableTabCloseAction()); - QWidget *widget = tabWidget->cornerWidget(Qt::TopLeftCorner); - widget->setEnabled(m_searchWidget ? enabled : true); + widget = tabWidget->cornerWidget(Qt::TopLeftCorner); + widget->setEnabled(viewer ? true : false); - widget = tabWidget->cornerWidget(Qt::TopRightCorner); - widget->setEnabled(enabled); - - emit currentViewerChanged(); + emit currentViewerChanged(); } void CentralWidget::showTabBarContextMenu(const QPoint &point) { - HelpViewer* viewer = helpViewerFromTabPosition(tabWidget, point); + HelpViewer *viewer = helpViewerFromTabPosition(tabWidget, point); if (!viewer) return; QTabBar *tabBar = qFindChild(tabWidget); QMenu menu(QLatin1String(""), tabBar); - QAction *new_page = menu.addAction(tr("Add New Page")); - QAction *close_page = menu.addAction(tr("Close This Page")); - QAction *close_pages = menu.addAction(tr("Close Other Pages")); - menu.addSeparator(); - QAction *newBookmark = menu.addAction(tr("Add Bookmark for this Page...")); + QAction *newPage = menu.addAction(tr("Add New Page")); - if (tabBar->count() == 1) { - close_page->setEnabled(false); - close_pages->setEnabled(false); - } else if (m_searchWidget && tabBar->count() == 2) { - close_pages->setEnabled(false); - } + bool enableAction = enableTabCloseAction(); + QAction *closePage = menu.addAction(tr("Close This Page")); + closePage->setEnabled(enableAction); - QAction *picked_action = menu.exec(tabBar->mapToGlobal(point)); - if (!picked_action) - return; + QAction *closePages = menu.addAction(tr("Close Other Pages")); + closePages->setEnabled(enableAction); - if (picked_action == new_page) + menu.addSeparator(); + + QAction *newBookmark = menu.addAction(tr("Add Bookmark for this Page...")); + + QAction *pickedAction = menu.exec(tabBar->mapToGlobal(point)); + if (pickedAction == newPage) setSourceInNewTab(viewer->source()); - if (picked_action == close_page) { + if (pickedAction == closePage) { tabWidget->removeTab(tabWidget->indexOf(viewer)); QTimer::singleShot(0, viewer, SLOT(deleteLater())); } - if (picked_action == close_pages) { + if (pickedAction == closePages) { int currentPage = tabWidget->indexOf(viewer); for (int i = tabBar->count() -1; i >= 0; --i) { viewer = qobject_cast(tabWidget->widget(i)); @@ -766,58 +758,68 @@ void CentralWidget::showTabBarContextMenu(const QPoint &point) } } - if (picked_action == newBookmark) + if (pickedAction == newBookmark) emit addNewBookmark(viewer->documentTitle(), viewer->source().toString()); } bool CentralWidget::eventFilter(QObject *object, QEvent *e) { - if (currentHelpViewer() == object && e->type() == QEvent::KeyPress){ + if (e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); - if (ke->key() == Qt::Key_Backspace) { - HelpViewer *viewer = currentHelpViewer(); + switch (ke->key()) { + default: { + return QWidget::eventFilter(object, e); + } break; + + case Qt::Key_Escape: { + if (findWidget->editFind == object) { + findBar->hide(); + if (HelpViewer *viewer = currentHelpViewer()) + viewer->setFocus(); + } + } break; + + case Qt::Key_Backspace: { + HelpViewer *viewer = currentHelpViewer(); + if (viewer == object) { #if defined(QT_NO_WEBKIT) - if (viewer && viewer->isBackwardAvailable()) { + if (viewer->isBackwardAvailable()) { #else - if (viewer && viewer->isBackwardAvailable() && !viewer->hasFocus()) { + if (viewer->isBackwardAvailable() && !viewer->hasFocus()) { #endif - viewer->backward(); - return true; - } + viewer->backward(); + return true; + } + } + } break; } } - QTabBar *tabBar = qobject_cast(object); - bool mousRel = e->type() == QEvent::MouseButtonRelease; - bool dblClick = e->type() == QEvent::MouseButtonDblClick; - - if (tabBar && (mousRel || dblClick)) { - QMouseEvent *mouseEvent = static_cast(e); - HelpViewer *viewer = helpViewerFromTabPosition(tabWidget, mouseEvent->pos()); - if (!m_searchWidget && tabWidget->count() <= 1) - return QWidget::eventFilter(object, e); - - if (viewer && (mouseEvent->button() == Qt::MidButton || dblClick)) { - tabWidget->removeTab(tabWidget->indexOf(viewer)); - QTimer::singleShot(0, viewer, SLOT(deleteLater())); - currentPageChanged(tabWidget->currentIndex()); - return true; - } - } else if (object == findWidget->editFind && e->type() == QEvent::KeyPress) { - QKeyEvent *ke = static_cast(e); - if (ke->key() == Qt::Key_Escape) { - findBar->hide(); - HelpViewer *hv = currentHelpViewer(); - if (hv) - hv->setFocus(); + if (QTabBar *tabBar = qobject_cast(object)) { + const bool dblClick = e->type() == QEvent::MouseButtonDblClick; + if ((e->type() == QEvent::MouseButtonRelease) || dblClick) { + QMouseEvent *mouseEvent = static_cast(e); + HelpViewer *viewer = helpViewerFromTabPosition(tabWidget, + mouseEvent->pos()); + if (viewer) { + if ((mouseEvent->button() == Qt::MidButton) || dblClick) { + if (availableHelpViewer() > 1) { + tabWidget->removeTab(tabWidget->indexOf(viewer)); + QTimer::singleShot(0, viewer, SLOT(deleteLater())); + currentPageChanged(tabWidget->currentIndex()); + return true; + } + } + } } } + return QWidget::eventFilter(object, e); } void CentralWidget::keyPressEvent(QKeyEvent *e) { - QString text = e->text(); + const QString &text = e->text(); if (text.startsWith(QLatin1Char('/'))) { if (!findBar->isVisible()) { findBar->show(); @@ -837,7 +839,7 @@ void CentralWidget::find(QString ttf, bool forward, bool backward) QTextDocument *doc = 0; QTextBrowser *browser = 0; - HelpViewer* viewer = currentHelpViewer(); + HelpViewer *viewer = currentHelpViewer(); QPalette p = findWidget->editFind->palette(); p.setColor(QPalette::Active, QPalette::Base, Qt::white); @@ -876,7 +878,7 @@ void CentralWidget::find(QString ttf, bool forward, bool backward) } if (tabWidget->currentWidget() == m_searchWidget) { - QTextBrowser* browser = qFindChild(m_searchWidget); + QTextBrowser *browser = qFindChild(m_searchWidget); if (browser) { doc = browser->document(); cursor = browser->textCursor(); @@ -933,15 +935,6 @@ void CentralWidget::find(QString ttf, bool forward, bool backward) findWidget->editFind->setPalette(p); } -void CentralWidget::activateSearch() -{ - if (tabWidget->widget(0) != m_searchWidget) - createSearchWidget(helpEngine->searchEngine()); - - tabWidget->setCurrentWidget(m_searchWidget); - m_searchWidget->setFocus(); -} - void CentralWidget::updateBrowserFont() { QFont font = qApp->font(); @@ -952,7 +945,7 @@ void CentralWidget::updateBrowserFont() } #if !defined(QT_NO_WEBKIT) - QWebSettings* settings = QWebSettings::globalSettings(); + QWebSettings *settings = QWebSettings::globalSettings(); if (!userFont) { int fontSize = settings->fontSize(QWebSettings::DefaultFontSize); QString fontFamily = settings->fontFamily(QWebSettings::StandardFont); @@ -960,11 +953,11 @@ void CentralWidget::updateBrowserFont() } #endif - QWidget* widget = 0; + QWidget *widget = 0; for (int i = 0; i < tabWidget->count(); ++i) { widget = tabWidget->widget(i); #if !defined(QT_NO_WEBKIT) - QWebView* view = qobject_cast (widget); + QWebView *view = qobject_cast (widget); if (view) { settings = view->settings(); settings->setFontFamily(QWebSettings::StandardFont, font.family()); @@ -991,11 +984,41 @@ void CentralWidget::createSearchWidget(QHelpSearchEngine *searchEngine) SLOT(setSourceFromSearchInNewTab(QUrl))); } tabWidget->insertTab(0, m_searchWidget, tr("Search")); + m_searchWidget->setAttached(true); +} + +void CentralWidget::activateSearchWidget() +{ + if (!m_searchWidget->isAttached()) + createSearchWidget(helpEngine->searchEngine()); + + tabWidget->setCurrentWidget(m_searchWidget); + m_searchWidget->setFocus(); } void CentralWidget::removeSearchWidget() { - tabWidget->removeTab(0); + if (m_searchWidget && m_searchWidget->isAttached()) { + tabWidget->removeTab(0); + m_searchWidget->setAttached(false); + } +} + +int CentralWidget::availableHelpViewer() const +{ + int count = tabWidget->count(); + if (m_searchWidget && m_searchWidget->isAttached()) + count--; + return count; +} + +bool CentralWidget::enableTabCloseAction() const +{ + int minTabCount = 1; + if (m_searchWidget && m_searchWidget->isAttached()) + minTabCount = 2; + + return (tabWidget->count() > minTabCount); } QString CentralWidget::quoteTabTitle(const QString &title) const @@ -1026,7 +1049,7 @@ CentralWidget::highlightSearchTerms() if (!viewer) return; - QHelpSearchEngine* searchEngine = helpEngine->searchEngine(); + QHelpSearchEngine *searchEngine = helpEngine->searchEngine(); QList queryList = searchEngine->query(); QStringList terms; diff --git a/tools/assistant/tools/assistant/centralwidget.h b/tools/assistant/tools/assistant/centralwidget.h index a34ba65..2c28091 100644 --- a/tools/assistant/tools/assistant/centralwidget.h +++ b/tools/assistant/tools/assistant/centralwidget.h @@ -116,10 +116,14 @@ public: void setGlobalActions(const QList &actions); HelpViewer *currentHelpViewer() const; void activateTab(bool onlyHelpViewer = false); - void activateSearch(); + void createSearchWidget(QHelpSearchEngine *searchEngine); + void activateSearchWidget(); void removeSearchWidget(); + int availableHelpViewer() const; + bool enableTabCloseAction() const; + void closeTabAt(int index); QMap currentSourceFileList() const; @@ -170,7 +174,7 @@ private slots: void setSourceFromSearchInNewTab(const QUrl &url); private: - void connectSignals(); + void connectSignals(); bool eventFilter(QObject *object, QEvent *e); void find(QString ttf, bool forward, bool backward); void initPrinter(); @@ -183,13 +187,13 @@ private: QList globalActionList; QWidget *findBar; - QTabWidget* tabWidget; + QTabWidget *tabWidget; FindWidget *findWidget; QHelpEngine *helpEngine; QPrinter *printer; bool usesDefaultCollection; - - SearchWidget* m_searchWidget; + + SearchWidget *m_searchWidget; }; QT_END_NAMESPACE diff --git a/tools/assistant/tools/assistant/mainwindow.cpp b/tools/assistant/tools/assistant/mainwindow.cpp index 6e22413..426a828 100644 --- a/tools/assistant/tools/assistant/mainwindow.cpp +++ b/tools/assistant/tools/assistant/mainwindow.cpp @@ -535,6 +535,8 @@ void MainWindow::setupActions() SLOT(copyAvailable(bool))); connect(m_centralWidget, SIGNAL(currentViewerChanged()), this, SLOT(updateNavigationItems())); + connect(m_centralWidget, SIGNAL(currentViewerChanged()), this, + SLOT(updateTabCloseAction())); connect(m_centralWidget, SIGNAL(forwardAvailable(bool)), this, SLOT(updateNavigationItems())); connect(m_centralWidget, SIGNAL(backwardAvailable(bool)), this, @@ -697,10 +699,14 @@ void MainWindow::updateNavigationItems() m_printAction->setEnabled(hasCurrentViewer); m_nextAction->setEnabled(m_centralWidget->isForwardAvailable()); m_backAction->setEnabled(m_centralWidget->isBackwardAvailable()); - m_closeTabAction->setEnabled(hasCurrentViewer); m_newTabAction->setEnabled(hasCurrentViewer); } +void MainWindow::updateTabCloseAction() +{ + m_closeTabAction->setEnabled(m_centralWidget->enableTabCloseAction()); +} + void MainWindow::showTopicChooser(const QMap &links, const QString &keyword) { @@ -871,7 +877,7 @@ void MainWindow::activateCurrentCentralWidgetTab() void MainWindow::showSearch() { - m_centralWidget->activateSearch(); + m_centralWidget->activateSearchWidget(); } void MainWindow::hideSearch() diff --git a/tools/assistant/tools/assistant/mainwindow.h b/tools/assistant/tools/assistant/mainwindow.h index 1bd8005..7d08a74 100644 --- a/tools/assistant/tools/assistant/mainwindow.h +++ b/tools/assistant/tools/assistant/mainwindow.h @@ -104,6 +104,7 @@ private slots: void showAboutDialog(); void copyAvailable(bool yes); void updateNavigationItems(); + void updateTabCloseAction(); void showNewAddress(const QUrl &url); void addNewBookmark(const QString &title, const QString &url); void showTopicChooser(const QMap &links, const QString &keyword); diff --git a/tools/assistant/tools/assistant/searchwidget.cpp b/tools/assistant/tools/assistant/searchwidget.cpp index 677e18e..c40a9c4 100644 --- a/tools/assistant/tools/assistant/searchwidget.cpp +++ b/tools/assistant/tools/assistant/searchwidget.cpp @@ -62,6 +62,7 @@ QT_BEGIN_NAMESPACE SearchWidget::SearchWidget(QHelpSearchEngine *engine, QWidget *parent) : QWidget(parent) , zoomCount(0) + , attached(false) , searchEngine(engine) { QVBoxLayout *vLayout = new QVBoxLayout(this); @@ -122,6 +123,16 @@ void SearchWidget::resetZoom() } } +bool SearchWidget::isAttached() const +{ + return attached; +} + +void SearchWidget::setAttached(bool state) +{ + attached = state; +} + void SearchWidget::search() const { QList query = searchEngine->queryWidget()->query(); diff --git a/tools/assistant/tools/assistant/searchwidget.h b/tools/assistant/tools/assistant/searchwidget.h index 22fe80d..34eb86f 100644 --- a/tools/assistant/tools/assistant/searchwidget.h +++ b/tools/assistant/tools/assistant/searchwidget.h @@ -65,6 +65,9 @@ public: void zoomOut(); void resetZoom(); + bool isAttached() const; + void setAttached(bool state); + signals: void requestShowLink(const QUrl &url); void requestShowLinkInNewTab(const QUrl &url); @@ -81,6 +84,7 @@ private: private: int zoomCount; + bool attached; QHelpSearchEngine *searchEngine; QHelpSearchResultWidget *resultWidget; }; -- cgit v0.12