Switch to side-by-side view

--- a/dirbrowser/cdbrowser.cpp
+++ b/dirbrowser/cdbrowser.cpp
@@ -15,9 +15,10 @@
  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
 
+#include "cdbrowser.h"
+
 #include <iostream>
 #include <functional>
-#include <time.h>
 
 #include "libupnpp/config.h"
 
@@ -60,7 +61,6 @@
 #include "upqo/cdirectory_qo.h"
 #include "upadapt/upputils.h"
 #include "upadapt/md5.hxx"
-#include "cdbrowser.h"
 #include "dirbrowser.h"
 #include "rreaper.h"
 
@@ -72,80 +72,45 @@
 
 static const string minimFoldersViewPrefix("0$folders");
 
-static void msleep(int millis)
-{
-struct timespec spec;
-spec.tv_sec = millis / 1000;
-spec.tv_nsec = (millis % 1000) * 1000000;
-nanosleep(&spec, 0);
-}
-
 void CDWebPage::javaScriptConsoleMessage(
 #ifdef USING_WEBENGINE
-JavaScriptConsoleMessageLevel,
+    JavaScriptConsoleMessageLevel,
 #endif
     const QString& msg, int lineNum, const QString&)
 {
-Q_UNUSED(msg);
-Q_UNUSED(lineNum);
-LOGDEB("JAVASCRIPT: "<< qs2utf8s(msg) << " at line " << lineNum << endl);
-}
-
-static const QString html_top_orig = QString::fromUtf8(
-"<html><head>"
-"<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"
-);
-
-// The const part + the js we need for webengine
-static QString html_top_js;
-// The const part + the js + changeable style, computed as needed.
-static QString html_top;
+    Q_UNUSED(msg);
+    Q_UNUSED(lineNum);
+    LOGDEB("JAVASCRIPT: "<< qs2utf8s(msg) << " at line " << lineNum << endl);
+}
 
 CDBrowser::CDBrowser(QWidget* parent)
     : QWEBVIEW(parent), m_reader(0), m_reaper(0), m_progressD(0),
       m_browsers(0), m_lastbutton(Qt::LeftButton), m_sysUpdId(0)
 {
-setPage(new CDWebPage(this));
+    setPage(new CDWebPage(this));
     
-#ifdef USING_WEBENGINE
-if (html_top_js.isEmpty()) {
-QLoggingCategory("js").setEnabled(QtDebugMsg, true);
-#if !defined(Q_OS_MACOS) && !defined(Q_OS_MAC)
-QString jsfn = Helper::getSharePath() + "/cdbrowser/containerscript.js";
-#else
-QString jsfn = Helper::getSharePath() + "/Resources/containerscript.js";
+#ifndef USING_WEBENGINE
+    connect(this, SIGNAL(linkClicked(const QUrl &)), 
+            this, SLOT(onLinkClicked(const QUrl &)));
+    // Not available and not sure that this is needed with webengine ?
+    connect(page()->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)),
+            this, SLOT(onContentsSizeChanged(const QSize&)));
+    page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
 #endif
-QString js = "<script type=\"text/javascript\">\n";
-js += QString::fromUtf8(Helper::readFileToByteArray(jsfn));
-js += "</script>\n";
-html_top_js = html_top + js;
-}
-#else
-html_top_js = html_top;
-connect(this, SIGNAL(linkClicked(const QUrl &)), 
-            this, SLOT(onLinkClicked(const QUrl &)));
-// Not available and not sure that this is needed with webengine ?
-connect(page()->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)),
-            this, SLOT(onContentsSizeChanged(const QSize&)));
-page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
-#endif
-
-// This sets html_top
-setStyleSheet(CSettingsStorage::getInstance()->getPlayerStyle(), false);
-    
-settings()->setAttribute(QWEBSETTINGS::JavascriptEnabled, true);
-if (parent) {
-settings()->setFontSize(QWEBSETTINGS::DefaultFontSize, 
-                        parent->font().pointSize()+4);
-settings()->setFontFamily(QWEBSETTINGS::StandardFont, 
-                          parent->font().family());
-}
-setContextMenuPolicy(Qt::CustomContextMenu);
-connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
-        this, SLOT(createPopupMenu(const QPoint&)));
-m_timer.setSingleShot(1);
-connect(&m_timer, SIGNAL(timeout()), this, SLOT(initialPage()));
-m_timer.start(0);
+
+    settings()->setAttribute(QWEBSETTINGS::JavascriptEnabled, true);
+    if (parent) {
+        settings()->setFontSize(QWEBSETTINGS::DefaultFontSize, 
+                                parent->font().pointSize()+4);
+        settings()->setFontFamily(QWEBSETTINGS::StandardFont, 
+                                  parent->font().family());
+    }
+    setContextMenuPolicy(Qt::CustomContextMenu);
+    connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
+            this, SLOT(createPopupMenu(const QPoint&)));
+    m_timer.setSingleShot(1);
+    connect(&m_timer, SIGNAL(timeout()), this, SLOT(initialPage()));
+    m_timer.start(0);
 }
 
 CDBrowser::~CDBrowser()
@@ -163,7 +128,7 @@
     // setPage(new CDWebPage(this));
     // connect(page(), SIGNAL(loadFinished(bool)), &pause, SLOT(quit()));
     setHtml(html);
-    msleep(100);
+    Helper::msleep(100);
     //pause.exec();
 }
 
@@ -172,51 +137,6 @@
     //qDebug() << "CDBrowser::mouseReleaseEvent";
     m_lastbutton = event->button();
     QWEBVIEW::mouseReleaseEvent(event);
-}
-
-// Insert style from on-disk config data to our static constant
-// top-of-page. 
-void CDBrowser::setStyleSheet(bool dark, bool redisplay)
-{
-#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
-    QString cssfn = Helper::getSharePath() + "/Resources/cdbrowser.css";
-#else
-    QString cssfn = Helper::getSharePath() + "/cdbrowser/cdbrowser.css";
-#endif
-    QString cssdata = QString::fromUtf8(Helper::readFileToByteArray(cssfn));
-
-    float multiplier = QSettings().value("wholeuiscale").toFloat();
-    if (multiplier == 0)
-        multiplier = 1.0;
-    cssdata = u8s2qs(Style::scale_fonts(qs2utf8s(cssdata), multiplier));
-    
-    if (dark) {
-#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
-        cssfn = Helper::getSharePath() + "/Resources/dark.css";
-#else
-        cssfn = Helper::getSharePath() + "/cdbrowser/dark.css";
-#endif
-        cssdata +=  Helper::readFileToByteArray(cssfn);
-    } else {
-#if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
-        cssfn = Helper::getSharePath() + "/Resources/standard.css";
-#else
-        cssfn = Helper::getSharePath() + "/cdbrowser/standard.css";
-#endif
-    }
-    cssdata +=  Helper::readFileToByteArray(cssfn);
-
-    html_top = html_top_js;
-    html_top += "<style>\n";
-    html_top += cssdata;
-    html_top += "</style>\n";
-    if (redisplay) {
-        if (m_curpath.size()) {
-            curpathClicked(m_curpath.size() - 1);
-        } else {
-            initialPage(true);
-        }
-    }
 }
 
 void CDBrowser::onContentsSizeChanged(const QSize&)
@@ -244,8 +164,8 @@
            "] html "<< qs2utf8s(html) << endl);
 
 #ifdef USING_WEBENGINE
-    // With webengine, we can't access qt object from the page, so we
-    // do everything in js. The data is encoded as base64 to avoid
+    // With webengine, we can't access the qt object from the page, so
+    // we do everything in js. The data is encoded as base64 to avoid
     // quoting issues
     QString js;
     js = "var morehtml = '" + base64_encode(html) + "';\n";
@@ -306,8 +226,8 @@
 
     if (!QSettings().value("monitorupdateid").toBool()) {
         return;
-
-    }
+    }
+
     // 1st time is free
     if (!m_sysUpdId) {
         m_sysUpdId = id;
@@ -369,6 +289,17 @@
 
     switch (what) {
 
+    case 'a':
+    {
+        string initial = scurl.substr(1,1);
+        qDebug() << "INITIAL: " << initial.c_str();
+        auto it = m_alphamap.find(initial);
+        if (it != m_alphamap.end()) {
+            m_browsers->doSearch(u8s2qs(it->second), false);
+        }
+    }
+    break;
+
     case 'S':
     {
         // Servers page server link click: browse clicked server root
@@ -404,6 +335,7 @@
     case 'C':
     {
         // Directory listing container link clicked: browse subdir.
+        m_curinitial = 0;
 #ifdef USING_WEBENGINE
 #warning tobedone
 #else
@@ -508,61 +440,6 @@
     qDebug() << "CDBrowser::browsein: " << UDN << " not found";
 }
 
-static const QString init_container_pagemid = QString::fromUtf8(
-#ifdef USING_WEBENGINE
-    "</head><body onload=\"addEventListener('contextmenu', saveLoc)\">\n"
-#else
-    "</head><body>\n"
-#endif
-    );
-static const QString init_container_pagebot = QString::fromUtf8(
-    "<table id=\"entrylist\">"
-    "<colgroup>"
-    "<col class=\"coltracknumber\">"
-    "<col class=\"coltitle\">"
-    "<col class=\"colartist\">"
-    "<col class=\"colalbum\">"
-    "<col class=\"colduration\">"
-    "</colgroup>"
-    "</table>"
-    "</body></html>"
-    );
-
-void CDBrowser::initContainerHtml(const string& ss)
-{
-    LOGDEB1("CDBrowser::initContainerHtml\n");
-    QString htmlpath("<div id=\"browsepath\"><ul>");
-    bool current_is_search = false;
-    for (unsigned i = 0; i < m_curpath.size(); i++) {
-        QString title = QString::fromUtf8(m_curpath[i].title.c_str());
-        QString objid = QString::fromUtf8(m_curpath[i].objid.c_str());
-        QString sep("&gt;");
-        if (!m_curpath[i].searchStr.empty()) {
-            if (current_is_search) {
-                // Indicate that searches are not nested by changing
-                // the separator
-                sep = "&lt;&gt;";
-            }
-            current_is_search = true;
-        }
-        if (i == 0)
-            sep = "";
-        htmlpath += QString("<li class=\"container\" objid=\"%3\">"
-                            " %4 <a href=\"http://h/L%1\">%2</a></li>").
-            arg(i).arg(title).arg(objid).arg(sep);
-    }
-    htmlpath += QString("</ul></div><br clear=\"all\"/>");
-    if (!ss.empty()) {
-        htmlpath += QString("Search results for: ") + 
-            QString::fromUtf8(ss.c_str()) + "<br/>";
-    }
-    QString html = html_top + init_container_pagemid +
-        htmlpath + init_container_pagebot;
-    LOGDEB1("initContainerHtml: initial content: " << qs2utf8s(html) << endl);
-    mySetHtml(html);
-    LOGDEB1("CDBrowser::initContainerHtml done\n");
-}
-
 // Re-browse (because sort criteria changed probably)
 void CDBrowser::refresh()
 {
@@ -624,99 +501,6 @@
     m_reader->start();
 }
 
-
-static QString CTToHtml(unsigned int idx, const UPnPDirObject& e)
-{
-    QString out;
-    out += QString("<tr class=\"container\" objid=\"%1\" objidx=\"\">"
-                   "<td></td><td>").arg(e.m_id.c_str());
-    out += QString("<a class=\"ct_title\" href=\"http://h/C%1\">").arg(idx);
-    out += QString::fromUtf8(Helper::escapeHtml(e.m_title).c_str());
-    out += "</a></td>";
-    string val;
-    e.getprop("upnp:artist", val);
-    QSettings settings;
-    if (!val.empty() && settings.value("showartwithalb").toBool()) {
-        int maxlen = settings.value("artwithalblen").toInt();
-        if (maxlen && int(val.size()) > maxlen) {
-            int len = maxlen-3 >= 0 ? maxlen-3 : 0;
-            val = val.substr(0,len) + "...";
-        }
-        out += "<td class=\"ct_artist\">";
-        out += QString::fromUtf8(Helper::escapeHtml(val).c_str());
-        out += "</td>";
-    }
-    out += "</tr>";
-    return out;
-}
-
-/** @arg idx index in entries array
-    @arg e array entry
-*/
-static QString ItemToHtml(unsigned int idx, const UPnPDirObject& e,
-                          int maxartlen)
-{
-    QString out;
-    string val;
-
-    out = QString("<tr class=\"item\" objid=\"%1\" objidx=\"%2\">").
-        arg(e.m_id.c_str()).arg(idx);
-
-    e.getprop("upnp:originalTrackNumber", val);
-    out += QString("<td class=\"tk_tracknum\">");
-    if (val.size() == 0) {
-        out += "&nbsp;";
-    } else {
-        out += QString::fromUtf8(Helper::escapeHtml(val).c_str());
-    }
-    out += "</td>";
-
-    out += "<td class=\"tk_title\">";
-    if (e.m_title.size() == 0) {
-        out += "&nbsp;";
-    } else {
-        out += QString("<a href=\"http://h/I%1\">").arg(idx);
-        out += QString::fromUtf8(Helper::escapeHtml(e.m_title).c_str());
-        out += "</a>";
-    }
-    out += "</td>";
-
-    val.clear();
-    e.getprop("upnp:artist", val);
-    out += "<td class=\"tk_artist\">";
-    if (maxartlen > 0 && int(val.size()) > maxartlen) {
-        int len = maxartlen-3 >= 0 ? maxartlen-3 : 0;
-        val = val.substr(0,len) + "...";
-    }
-    if (val.size() == 0) {
-        out += "&nbsp;";
-    } else {
-        out += QString::fromUtf8(Helper::escapeHtml(val).c_str());
-    }
-    out += "</td>";
-    
-    val.clear();
-    e.getprop("upnp:album", val);
-    out += "<td class=\"tk_album\">";
-    out += QString::fromUtf8(Helper::escapeHtml(val).c_str());
-    out += "</td>";
-    
-    val.clear();
-    e.getrprop(0, "duration", val);
-    int seconds = upnpdurationtos(val);
-    // Format as mm:ss
-    int mins = seconds / 60;
-    int secs = seconds % 60;
-    char sdur[100];
-    sprintf(sdur, "%02d:%02d", mins, secs);
-    out += "<td class=\"tk_duration\">";
-    out += sdur;
-    out += "</td>";
-
-    out += "</tr>";
-
-    return out;
-}
 
 void CDBrowser::deleteReaders() 
 {
@@ -753,11 +537,17 @@
 
     m_entries.reserve(m_entries.size() + dc->m_containers.size() + 
                       dc->m_items.size());
-    for (std::vector<UPnPDirObject>::iterator it = dc->m_containers.begin();
-         it != dc->m_containers.end(); it++) {
+    for (auto& entry: dc->m_containers) {
         //qDebug() << "Container: " << it->dump().c_str();;
-        m_entries.push_back(*it);
-        html += CTToHtml(m_entries.size()-1, *it);
+        char ninit = ::toupper(entry.m_title[0]);
+        qDebug() << entry.m_title.c_str();
+        if (ninit != m_curinitial) {
+            qDebug() << "NEW INITIAL " << ninit;
+            m_alphamap[string(1, ninit)] = CTTitleStartMarker + entry.m_title;
+            m_curinitial = ninit;
+        }
+        m_entries.push_back(entry);
+        html += CTToHtml(m_entries.size()-1, entry);
     }
     QSettings settings;
     int maxartlen = 0;
@@ -868,8 +658,15 @@
         if (settings.value("truncateartistindir").toBool()) {
             maxartlen = settings.value("truncateartistlen").toInt();
         }
+        m_curinitial = 0;
         for (unsigned i = 0; i < m_entries.size(); i++) {
             if (m_entries[i].m_type == UPnPDirObject::container) {
+                char ninit = m_entries[i].m_title[0];
+                if (ninit != m_curinitial) {
+                    m_alphamap[string(1, ninit)] = CTTitleStartMarker +
+                        m_entries[i].m_title;
+                    m_curinitial = ninit;
+                }
                 html += CTToHtml(i, m_entries[i]);
             } else {
                 html += ItemToHtml(i, m_entries[i], maxartlen);
@@ -890,22 +687,6 @@
 }
 
 
-static const QString init_server_page_bot = QString::fromUtf8(
-    "</head><body>"
-    "<h2 id=\"cdstitle\">Content Directory Services</h2>"
-    "</body></html>"
-    );
-
-static QString DSToHtml(unsigned int idx, const UPnPDeviceDesc& dev)
-{
-    QString out;
-    out += QString("<p class=\"cdserver\" cdsid=\"%1\">").arg(idx);
-    out += QString("<a href=\"http://h/S%1\">").arg(idx);
-    out += QString::fromUtf8(dev.friendlyName.c_str());
-    out += QString("</a></p>");
-    return out;
-}
-
 void CDBrowser::initialPage(bool redisplay)
 {
     deleteReaders();
@@ -914,9 +695,7 @@
     vector<UPnPDeviceDesc> msdescs;
     int secs = UPnPDeviceDirectory::getTheDir()->getRemainingDelay();
     if (secs > 1) {
-        mySetHtml(html_top + init_server_page_bot);
-        appendHtml("", QString::fromUtf8("</head><body><p>Looking for "
-                                         "servers...</p></body></html>"));
+        mySetHtml(initialServersPage());
         qDebug() << "CDBrowser::initialPage: waiting " << secs;
         m_timer.start(secs * 1000);
         return;
@@ -953,7 +732,7 @@
         browseIn(s, m_curpath);
     } else {
         // Show servers list
-        QString html = html_top + init_server_page_bot;
+        QString html = emptyServersPage();
         mySetHtml(html);
         for (unsigned i = 0; i < msdescs.size(); i++) {
             appendHtml("", DSToHtml(i, msdescs[i]));