1/*
2 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
3 * Copyright (C) 2010 University of Szeged
4 *
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "BrowserWindow.h"
30
31#include "UrlLoader.h"
32#include "qwkpreferences.h"
33
34static QWKPage* newPageFunction(QWKPage* page)
35{
36    BrowserWindow* window = new BrowserWindow(page->context());
37    return window->page();
38}
39
40QVector<qreal> BrowserWindow::m_zoomLevels;
41
42BrowserWindow::BrowserWindow(QWKContext* context, WindowOptions* options)
43    : m_isZoomTextOnly(false)
44    , m_currentZoom(1)
45    , m_urlLoader(0)
46    , m_context(context)
47{
48    if (options)
49        m_windowOptions = *options;
50    else {
51        WindowOptions tmpOptions;
52        m_windowOptions = tmpOptions;
53    }
54
55    if (m_windowOptions.useTiledBackingStore)
56        m_browser = new BrowserView(QGraphicsWKView::Tiled, context);
57    else
58        m_browser = new BrowserView(QGraphicsWKView::Simple, context);
59
60    setAttribute(Qt::WA_DeleteOnClose);
61
62    connect(m_browser->view(), SIGNAL(loadProgress(int)), SLOT(loadProgress(int)));
63    connect(m_browser->view(), SIGNAL(titleChanged(const QString&)), SLOT(setWindowTitle(const QString&)));
64    connect(m_browser->view(), SIGNAL(urlChanged(const QUrl&)), SLOT(urlChanged(const QUrl&)));
65
66    if (m_windowOptions.printLoadedUrls)
67        connect(page(), SIGNAL(urlChanged(QUrl)), this, SLOT(printURL(QUrl)));
68
69    this->setCentralWidget(m_browser);
70    m_browser->setFocus(Qt::OtherFocusReason);
71
72    QMenu* fileMenu = menuBar()->addMenu("&File");
73    fileMenu->addAction("New Window", this, SLOT(newWindow()), QKeySequence::New);
74    fileMenu->addAction("Open File", this, SLOT(openFile()), QKeySequence::Open);
75    fileMenu->addSeparator();
76    fileMenu->addAction("Quit", this, SLOT(close()));
77
78    QMenu* viewMenu = menuBar()->addMenu("&View");
79    viewMenu->addAction(page()->action(QWKPage::Stop));
80    viewMenu->addAction(page()->action(QWKPage::Reload));
81    viewMenu->addSeparator();
82    QAction* zoomIn = viewMenu->addAction("Zoom &In", this, SLOT(zoomIn()));
83    QAction* zoomOut = viewMenu->addAction("Zoom &Out", this, SLOT(zoomOut()));
84    QAction* resetZoom = viewMenu->addAction("Reset Zoom", this, SLOT(resetZoom()));
85    QAction* zoomText = viewMenu->addAction("Zoom Text Only", this, SLOT(toggleZoomTextOnly(bool)));
86    zoomText->setCheckable(true);
87    zoomText->setChecked(false);
88    viewMenu->addSeparator();
89    viewMenu->addAction("Take Screen Shot...", this, SLOT(screenshot()));
90
91    zoomIn->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Plus));
92    zoomOut->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Minus));
93    resetZoom->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0));
94
95    QMenu* windowMenu = menuBar()->addMenu("&Window");
96    QAction* toggleFullScreen = windowMenu->addAction("Toggle FullScreen", this, SIGNAL(enteredFullScreenMode(bool)));
97    toggleFullScreen->setShortcut(Qt::Key_F11);
98    toggleFullScreen->setCheckable(true);
99    toggleFullScreen->setChecked(false);
100    // When exit fullscreen mode by clicking on the exit area (bottom right corner) we must
101    // uncheck the Toggle FullScreen action.
102    toggleFullScreen->connect(this, SIGNAL(enteredFullScreenMode(bool)), SLOT(setChecked(bool)));
103    connect(this, SIGNAL(enteredFullScreenMode(bool)), this, SLOT(toggleFullScreenMode(bool)));
104
105    QMenu* toolsMenu = menuBar()->addMenu("&Develop");
106    QAction* toggleFrameFlattening = toolsMenu->addAction("Toggle Frame Flattening", this, SLOT(toggleFrameFlattening(bool)));
107    toggleFrameFlattening->setCheckable(true);
108    toggleFrameFlattening->setChecked(false);
109    toolsMenu->addSeparator();
110    toolsMenu->addAction("Change User Agent", this, SLOT(showUserAgentDialog()));
111    toolsMenu->addSeparator();
112    toolsMenu->addAction("Load URLs from file", this, SLOT(loadURLListFromFile()));
113
114    QMenu* settingsMenu = menuBar()->addMenu("&Settings");
115    QAction* toggleAutoLoadImages = settingsMenu->addAction("Disable Auto Load Images", this, SLOT(toggleAutoLoadImages(bool)));
116    toggleAutoLoadImages->setCheckable(true);
117    toggleAutoLoadImages->setChecked(false);
118    QAction* toggleDisableJavaScript = settingsMenu->addAction("Disable JavaScript", this, SLOT(toggleDisableJavaScript(bool)));
119    toggleDisableJavaScript->setCheckable(true);
120    toggleDisableJavaScript->setChecked(false);
121
122    m_addressBar = new QLineEdit();
123    connect(m_addressBar, SIGNAL(returnPressed()), SLOT(changeLocation()));
124
125    QToolBar* bar = addToolBar("Navigation");
126    bar->addAction(page()->action(QWKPage::Back));
127    bar->addAction(page()->action(QWKPage::Forward));
128    bar->addAction(page()->action(QWKPage::Reload));
129    bar->addAction(page()->action(QWKPage::Stop));
130    bar->addWidget(m_addressBar);
131
132    QShortcut* selectAddressBar = new QShortcut(Qt::CTRL | Qt::Key_L, this);
133    connect(selectAddressBar, SIGNAL(activated()), this, SLOT(openLocation()));
134
135    page()->setCreateNewPageFunction(newPageFunction);
136
137    // the zoom values are chosen to be like in Mozilla Firefox 3
138    if (!m_zoomLevels.count()) {
139        m_zoomLevels << 0.3 << 0.5 << 0.67 << 0.8 << 0.9;
140        m_zoomLevels << 1;
141        m_zoomLevels << 1.1 << 1.2 << 1.33 << 1.5 << 1.7 << 2 << 2.4 << 3;
142    }
143
144    if (m_windowOptions.startMaximized)
145        setWindowState(windowState() | Qt::WindowMaximized);
146    else
147        resize(800, 600);
148    show();
149}
150
151void BrowserWindow::load(const QString& url)
152{
153    m_addressBar->setText(url);
154    m_browser->load(url);
155}
156
157QWKPage* BrowserWindow::page()
158{
159    return m_browser->view()->page();
160}
161
162BrowserWindow* BrowserWindow::newWindow(const QString& url)
163{
164    BrowserWindow* window;
165    if (m_windowOptions.useSeparateWebProcessPerWindow) {
166        QWKContext* context = new QWKContext();
167        window = new BrowserWindow(context);
168        context->setParent(window);
169    } else
170        window = new BrowserWindow(m_context);
171
172    window->load(url);
173    return window;
174}
175
176void BrowserWindow::openLocation()
177{
178    m_addressBar->selectAll();
179    m_addressBar->setFocus();
180}
181
182void BrowserWindow::changeLocation()
183{
184    QString string = m_addressBar->text();
185    m_browser->load(string);
186}
187
188void BrowserWindow::loadProgress(int progress)
189{
190    QColor backgroundColor = QApplication::palette().color(QPalette::Base);
191    QColor progressColor = QColor(120, 180, 240);
192    QPalette pallete = m_addressBar->palette();
193
194    if (progress <= 0 || progress >= 100)
195        pallete.setBrush(QPalette::Base, backgroundColor);
196    else {
197        QLinearGradient gradient(0, 0, width(), 0);
198        gradient.setColorAt(0, progressColor);
199        gradient.setColorAt(((double) progress) / 100, progressColor);
200        if (progress != 100)
201            gradient.setColorAt((double) progress / 100 + 0.001, backgroundColor);
202        pallete.setBrush(QPalette::Base, gradient);
203    }
204    m_addressBar->setPalette(pallete);
205}
206
207void BrowserWindow::urlChanged(const QUrl& url)
208{
209    m_addressBar->setText(url.toString());
210    m_browser->setFocus();
211    m_browser->view()->setFocus();
212}
213
214void BrowserWindow::openFile()
215{
216#ifndef QT_NO_FILEDIALOG
217    static const QString filter("HTML Files (*.htm *.html *.xhtml);;Text Files (*.txt);;Image Files (*.gif *.jpg *.png);;SVG Files (*.svg);;All Files (*)");
218
219    QFileDialog fileDialog(this, tr("Open"), QString(), filter);
220    fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
221    fileDialog.setFileMode(QFileDialog::ExistingFile);
222    fileDialog.setOptions(QFileDialog::ReadOnly);
223
224    if (fileDialog.exec()) {
225        QString selectedFile = fileDialog.selectedFiles()[0];
226        if (!selectedFile.isEmpty())
227            load(selectedFile);
228    }
229#endif
230}
231
232void BrowserWindow::screenshot()
233{
234    QPixmap pixmap = QPixmap::grabWidget(m_browser);
235    QLabel* label = 0;
236#if !defined(Q_OS_SYMBIAN)
237    label = new QLabel;
238    label->setAttribute(Qt::WA_DeleteOnClose);
239    label->setWindowTitle("Screenshot - Preview");
240    label->setPixmap(pixmap);
241    label->show();
242#endif
243
244#ifndef QT_NO_FILEDIALOG
245    QString fileName = QFileDialog::getSaveFileName(label, "Screenshot", QString(), QString("PNG File (.png)"));
246    if (!fileName.isEmpty()) {
247        QRegExp rx("*.png");
248        rx.setCaseSensitivity(Qt::CaseInsensitive);
249        rx.setPatternSyntax(QRegExp::Wildcard);
250
251        if (!rx.exactMatch(fileName))
252            fileName += ".png";
253
254        pixmap.save(fileName, "png");
255        if (label)
256            label->setWindowTitle(QString("Screenshot - Saved at %1").arg(fileName));
257    }
258#endif
259}
260
261void BrowserWindow::zoomIn()
262{
263    if (m_isZoomTextOnly)
264        m_currentZoom = page()->textZoomFactor();
265    else
266        m_currentZoom = page()->pageZoomFactor();
267
268    int i = m_zoomLevels.indexOf(m_currentZoom);
269    Q_ASSERT(i >= 0);
270    if (i < m_zoomLevels.count() - 1)
271        m_currentZoom = m_zoomLevels[i + 1];
272
273    applyZoom();
274}
275
276void BrowserWindow::zoomOut()
277{
278    if (m_isZoomTextOnly)
279        m_currentZoom = page()->textZoomFactor();
280    else
281        m_currentZoom = page()->pageZoomFactor();
282
283    int i = m_zoomLevels.indexOf(m_currentZoom);
284    Q_ASSERT(i >= 0);
285    if (i > 0)
286        m_currentZoom = m_zoomLevels[i - 1];
287
288    applyZoom();
289}
290
291void BrowserWindow::resetZoom()
292{
293    m_currentZoom = 1;
294    applyZoom();
295}
296
297void BrowserWindow::toggleZoomTextOnly(bool b)
298{
299    m_isZoomTextOnly = b;
300}
301
302void BrowserWindow::toggleFullScreenMode(bool enable)
303{
304    bool alreadyEnabled = windowState() & Qt::WindowFullScreen;
305    if (enable ^ alreadyEnabled)
306        setWindowState(windowState() ^ Qt::WindowFullScreen);
307}
308
309void BrowserWindow::toggleFrameFlattening(bool toggle)
310{
311    page()->preferences()->setAttribute(QWKPreferences::FrameFlatteningEnabled, toggle);
312}
313
314
315void BrowserWindow::showUserAgentDialog()
316{
317    updateUserAgentList();
318
319    QDialog dialog(this);
320    dialog.setWindowTitle("Change User Agent");
321    dialog.resize(size().width() * 0.7, dialog.size().height());
322    QVBoxLayout* layout = new QVBoxLayout(&dialog);
323    dialog.setLayout(layout);
324
325    QComboBox* combo = new QComboBox(&dialog);
326    combo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
327    combo->setEditable(true);
328    combo->insertItems(0, m_userAgentList);
329    layout->addWidget(combo);
330
331    int index = combo->findText(page()->customUserAgent());
332    combo->setCurrentIndex(index);
333
334    QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel
335                                                      , Qt::Horizontal, &dialog);
336    connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
337    connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
338    layout->addWidget(buttonBox);
339
340    if (dialog.exec() && !combo->currentText().isEmpty())
341        page()->setCustomUserAgent(combo->currentText());
342}
343
344void BrowserWindow::loadURLListFromFile()
345{
346    QString selectedFile;
347#ifndef QT_NO_FILEDIALOG
348    selectedFile = QFileDialog::getOpenFileName(this, tr("Load URL list from file")
349                                                       , QString(), tr("Text Files (*.txt);;All Files (*)"));
350#endif
351    if (selectedFile.isEmpty())
352       return;
353
354    m_urlLoader = new UrlLoader(this, selectedFile, 0, 0);
355    m_urlLoader->loadNext();
356}
357
358void BrowserWindow::printURL(const QUrl& url)
359{
360    QTextStream output(stdout);
361    output << "Loaded: " << url.toString() << endl;
362}
363
364void BrowserWindow::toggleDisableJavaScript(bool enable)
365{
366    page()->preferences()->setAttribute(QWKPreferences::JavascriptEnabled, !enable);
367}
368
369void BrowserWindow::toggleAutoLoadImages(bool enable)
370{
371    page()->preferences()->setAttribute(QWKPreferences::AutoLoadImages, !enable);
372}
373
374void BrowserWindow::updateUserAgentList()
375{
376    QFile file(":/useragentlist.txt");
377
378    if (file.open(QIODevice::ReadOnly)) {
379        while (!file.atEnd()) {
380            QString agent = file.readLine().trimmed();
381            if (!m_userAgentList.contains(agent))
382                m_userAgentList << agent;
383        }
384        file.close();
385    }
386
387    Q_ASSERT(!m_userAgentList.isEmpty());
388    QWKPage* wkPage = page();
389    if (!(wkPage->customUserAgent().isEmpty() || m_userAgentList.contains(wkPage->customUserAgent())))
390        m_userAgentList << wkPage->customUserAgent();
391}
392
393void BrowserWindow::applyZoom()
394{
395    if (m_isZoomTextOnly)
396        page()->setTextZoomFactor(m_currentZoom);
397    else
398        page()->setPageZoomFactor(m_currentZoom);
399}
400
401BrowserWindow::~BrowserWindow()
402{
403    delete m_urlLoader;
404    delete m_addressBar;
405    delete m_browser;
406}
407