Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

251
qt/CMakeLists.txt Normal file
View file

@ -0,0 +1,251 @@
project(desktop)
QT6_ADD_RESOURCES(RES_SOURCES res/resources.qrc)
set(SRC
about.cpp
about.hpp
bookmark_dialog.cpp
bookmark_dialog.hpp
build_style/build_common.cpp
build_style/build_common.h
build_style/build_drules.cpp
build_style/build_drules.h
build_style/build_phone_pack.cpp
build_style/build_phone_pack.h
build_style/build_skins.cpp
build_style/build_skins.h
build_style/build_statistics.cpp
build_style/build_statistics.h
build_style/build_style.cpp
build_style/build_style.h
build_style/run_tests.cpp
build_style/run_tests.h
create_feature_dialog.cpp
create_feature_dialog.hpp
draw_widget.cpp
draw_widget.hpp
editor_dialog.cpp
editor_dialog.hpp
info_dialog.cpp
info_dialog.hpp
main.cpp
mainwindow.cpp
mainwindow.hpp
mwms_borders_selection.cpp
mwms_borders_selection.hpp
osm_auth_dialog.cpp
osm_auth_dialog.hpp
place_page_dialog_common.cpp
place_page_dialog_common.hpp
place_page_dialog_developer.cpp
place_page_dialog_developer.hpp
place_page_dialog_user.cpp
place_page_dialog_user.hpp
preferences_dialog.cpp
preferences_dialog.hpp
popup_menu_holder.cpp
popup_menu_holder.hpp
routing_settings_dialog.cpp
routing_settings_dialog.hpp
routing_turns_visualizer.cpp
routing_turns_visualizer.hpp
ruler.cpp
ruler.hpp
screenshoter.cpp
screenshoter.hpp
search_panel.cpp
search_panel.hpp
selection.hpp
update_dialog.cpp
update_dialog.hpp
)
omim_add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${RES_SOURCES} ${SRC})
set_target_properties(${PROJECT_NAME} PROPERTIES AUTOMOC ON)
target_link_libraries(${PROJECT_NAME}
generator # For borders::LoadBorders
qt_common
map
gflags::gflags
location_service
)
if (BUILD_DESIGNER)
target_link_libraries(${PROJECT_NAME} generator)
set(BUNDLE_NAME "CoMaps.Designer")
set(BUNDLE_DISPLAY_NAME "CoMaps Designer")
set(BUNDLE_ICON designer.icns)
else()
set(BUNDLE_NAME "CoMaps")
set(BUNDLE_DISPLAY_NAME "CoMaps Desktop")
set(BUNDLE_ICON mac.icns)
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${BUNDLE_NAME})
set(BUNDLE_EXECUTABLE ${BUNDLE_NAME})
set(BUNDLE_FOLDER ${CMAKE_BINARY_DIR}/${BUNDLE_NAME}.app)
set(RESOURCES_FOLDER ${BUNDLE_FOLDER}/Contents/Resources)
set(DATA_DIR ${OMIM_ROOT}/data)
execute_process(
COMMAND mkdir -p ${RESOURCES_FOLDER}
)
function(copy_resources)
foreach(file ${ARGN})
execute_process(
COMMAND cp -r ${DATA_DIR}/${file} ${RESOURCES_FOLDER}
)
endforeach()
endfunction()
copy_resources(
countries-strings
fonts
symbols
welcome.html
categories.txt
categories_cuisines.txt
classificator.txt
colors.txt
copyright.html
countries.txt
drules_proto_default_light.bin
drules_proto_default_dark.bin
drules_proto_outdoors_light.bin
drules_proto_outdoors_dark.bin
drules_proto_vehicle_light.bin
drules_proto_vehicle_dark.bin
editor.config
packed_polygons.bin
patterns.txt
transit_colors.txt
types.txt
World.mwm
WorldCoasts.mwm
)
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin/)
install(DIRECTORY ${OMIM_ROOT}/data DESTINATION ${CMAKE_INSTALL_PREFIX}/share/comaps/)
if (PLATFORM_LINUX)
install(FILES ${OMIM_ROOT}/packaging/app.comaps.comaps.metainfo.xml DESTINATION ${CMAKE_INSTALL_PREFIX}/share/metainfo/)
install(FILES ${OMIM_ROOT}/qt/res/linux/app.comaps.comaps.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications/)
install(FILES ${OMIM_ROOT}/qt/res/logo.png DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/512x512/apps/ RENAME comaps.png)
endif()
if (NOT PLATFORM_LINUX)
# On Linux, ICU data is loaded from the shared library.
copy_resources(icudt75l.dat)
endif()
if (PLATFORM_MAC)
if (BUILD_DESIGNER)
execute_process(
COMMAND cp ${PROJECT_SOURCE_DIR}/res/mac/designer.icns ${RESOURCES_FOLDER}
)
else ()
execute_process(
COMMAND cp ${PROJECT_SOURCE_DIR}/res/mac/mac.icns ${RESOURCES_FOLDER}
)
endif ()
endif()
if (PLATFORM_MAC)
if (NOT APP_VERSION)
set(BUNDLE_VERSION "UNKNOWN")
else()
set(BUNDLE_VERSION ${APP_VERSION})
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/res/mac/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/generated/Info.plist)
set_target_properties(${PROJECT_NAME}
PROPERTIES
OUTPUT_NAME ${BUNDLE_NAME}
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/generated/Info.plist
)
endif()
if (PLATFORM_WIN)
target_sources(${PROJECT_NAME} PRIVATE res/windows/windows.rc)
endif()
if (BUILD_DESIGNER)
execute_process(
COMMAND cp -rf ${OMIM_ROOT}/data/symbols/mdpi/light/ ${OMIM_ROOT}/data/symbols/mdpi/design/
COMMAND cp -rf ${OMIM_ROOT}/data/symbols/hdpi/light/ ${OMIM_ROOT}/data/symbols/hdpi/design/
COMMAND cp -rf ${OMIM_ROOT}/data/symbols/xhdpi/light/ ${OMIM_ROOT}/data/symbols/xhdpi/design/
COMMAND cp -rf ${OMIM_ROOT}/data/symbols/xxhdpi/light/ ${OMIM_ROOT}/data/symbols/xxhdpi/design/
COMMAND cp -rf ${OMIM_ROOT}/data/symbols/6plus/light/ ${OMIM_ROOT}/data/symbols/6plus/design/
COMMAND cp -rf ${OMIM_ROOT}/data/symbols/xxxhdpi/light/ ${OMIM_ROOT}/data/symbols/xxxhdpi/design/
COMMAND cp -f ${OMIM_ROOT}/data/drules_proto_default_light.bin ${OMIM_ROOT}/data/drules_proto_default_design.bin
COMMAND cp -f ${OMIM_ROOT}/data/colors.txt ${OMIM_ROOT}/data/colors_design.txt
COMMAND cp -f ${OMIM_ROOT}/data/patterns.txt ${OMIM_ROOT}/data/patterns_design.txt
)
copy_resources(
colors_design.txt
drules_proto_default_design.bin
mapcss-dynamic.txt
mapcss-mapping.csv
patterns_design.txt
symbols/mdpi/design
symbols/hdpi/design
symbols/xhdpi/design
symbols/xxhdpi/design
symbols/xxxhdpi/design
symbols/6plus/design
)
execute_process(
COMMAND cp ${OMIM_ROOT}/tools/python/recalculate_geom_index.py ${RESOURCES_FOLDER}
COMMAND cp ${OMIM_ROOT}/tools/python/generate_styles_override.py ${RESOURCES_FOLDER}
COMMAND cp -rf ${OMIM_ROOT}/tools/kothic ${RESOURCES_FOLDER}/kothic/
COMMAND cp -f ${OMIM_ROOT}/tools/python/stylesheet/drules_info.py ${RESOURCES_FOLDER}/kothic/src/
COMMAND cp -rf ${OMIM_ROOT}/tools/python/stylesheet/ ${RESOURCES_FOLDER}/kothic/src/
COMMAND cp -f ${OMIM_ROOT}/3party/protobuf/protobuf-3.3.0-py2.7.egg ${RESOURCES_FOLDER}/kothic/
)
# Generate DMG
install(DIRECTORY ${DATA_DIR}/styles DESTINATION .)
set(BUNDLES
${CMAKE_BINARY_DIR}/generator_tool.app
${CMAKE_BINARY_DIR}/skin_generator_tool.app
${CMAKE_BINARY_DIR}/style_tests.app
)
install(CODE "
foreach(BUNDLE ${BUNDLE_FOLDER} ${BUNDLES})
execute_process(
COMMAND \"${QT_PATH}/bin/macdeployqt\" \"\${BUNDLE}\"
)
endforeach()
foreach(BUNDLE ${BUNDLES})
execute_process(
COMMAND cp -rf \"\${BUNDLE}\" \"${RESOURCES_FOLDER}\"
)
endforeach()
")
install(TARGETS ${PROJECT_NAME} DESTINATION .)
set(CPACK_GENERATOR DragNDrop)
set(CPACK_DMG_FORMAT UDZO)
set(CPACK_DMG_VOLUME_NAME ${BUNDLE_NAME})
set(CPACK_PACKAGE_ICON ${PROJECT_SOURCE_DIR}/res/mac/designer.icns)
include(CPack)
elseif (BUILD_STANDALONE)
add_custom_command(TARGET desktop POST_BUILD
COMMAND "${QT_PATH}/bin/macdeployqt" ${BUNDLE_FOLDER}
COMMAND ${Python3_EXECUTABLE} "${OMIM_ROOT}/tools/macdeployqtfix/macdeployqtfix.py" -q -nl ${BUNDLE_FOLDER}/Contents/MacOS/${BUNDLE_NAME} ${QT_PATH}
COMMAND echo "Fixing Qt dependencies finished."
VERBATIM
COMMENT "Fixing Qt dependencies for standalone desktop app"
)
endif()
add_subdirectory(qt_common)

70
qt/about.cpp Normal file
View file

@ -0,0 +1,70 @@
#include "qt/about.hpp"
#include "platform/platform.hpp"
#include "base/logging.hpp"
#include <string>
#include <QtCore/QFile>
#include <QtGui/QIcon>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QTextBrowser>
#include <QtWidgets/QVBoxLayout>
AboutDialog::AboutDialog(QWidget * parent)
: QDialog(parent,
Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint)
{
QIcon icon(":/ui/logo.png");
setWindowIcon(icon);
setWindowTitle(QMenuBar::tr("About"));
QLabel * labelIcon = new QLabel();
labelIcon->setPixmap(icon.pixmap(128));
Platform & platform = GetPlatform();
QVBoxLayout * versionBox = new QVBoxLayout();
versionBox->addWidget(new QLabel(QCoreApplication::applicationName()));
QLabel * versionLabel = new QLabel("Version: " + QString::fromStdString(platform.Version()));
versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
versionBox->addWidget(versionLabel);
// TODO: insert maps data version.
// versionBox->addWidget(new QLabel(QString("Data: ") + DESIGNER_DATA_VERSION));
QHBoxLayout * hBox = new QHBoxLayout();
hBox->addWidget(labelIcon);
hBox->addLayout(versionBox);
std::string aboutText;
try
{
ReaderPtr<Reader> reader = platform.GetReader("copyright.html");
reader.ReadAsString(aboutText);
}
catch (RootException const & ex)
{
LOG(LWARNING, ("About text error: ", ex.Msg()));
}
if (!aboutText.empty())
{
QTextBrowser * aboutTextBrowser = new QTextBrowser();
aboutTextBrowser->setReadOnly(true);
aboutTextBrowser->setOpenLinks(true);
aboutTextBrowser->setOpenExternalLinks(true);
aboutTextBrowser->setText(aboutText.c_str());
QVBoxLayout * vBox = new QVBoxLayout();
vBox->addLayout(hBox);
vBox->addWidget(aboutTextBrowser);
setLayout(vBox);
}
else
setLayout(hBox);
adjustSize();
}

12
qt/about.hpp Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <QtWidgets/QApplication>
#include <QtWidgets/QDialog>
class AboutDialog : public QDialog
{
Q_OBJECT
public:
explicit AboutDialog(QWidget * parent);
};

318
qt/bookmark_dialog.cpp Normal file
View file

@ -0,0 +1,318 @@
#include "qt/bookmark_dialog.hpp"
#include "map/bookmark_manager.hpp"
#include "map/framework.hpp"
#include "platform/measurement_utils.hpp"
#include <QtCore/QFile>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTreeWidget>
#include <QtWidgets/QVBoxLayout>
namespace qt
{
using namespace std::placeholders;
BookmarkDialog::BookmarkDialog(QWidget * parent, Framework & framework)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
, m_framework(framework)
{
setWindowModality(Qt::WindowModal);
QPushButton * closeButton = new QPushButton(tr("Close"), this);
closeButton->setDefault(true);
connect(closeButton, &QAbstractButton::clicked, this, &BookmarkDialog::OnCloseClick);
QPushButton * deleteButton = new QPushButton(tr("Delete"), this);
connect(deleteButton, &QAbstractButton::clicked, this, &BookmarkDialog::OnDeleteClick);
QPushButton * importButton = new QPushButton(tr("Import KML, KMZ, GPX"), this);
connect(importButton, &QAbstractButton::clicked, this, &BookmarkDialog::OnImportClick);
QPushButton * exportButton = new QPushButton(tr("Export KMZ"), this);
connect(exportButton, &QAbstractButton::clicked, this, &BookmarkDialog::OnExportClick);
m_tree = new QTreeWidget(this);
m_tree->setColumnCount(2);
QStringList columnLabels;
columnLabels << tr("Bookmarks and tracks") << "";
m_tree->setHeaderLabels(columnLabels);
m_tree->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectItems);
connect(m_tree, &QTreeWidget::itemClicked, this, &BookmarkDialog::OnItemClick);
QHBoxLayout * horizontalLayout = new QHBoxLayout();
horizontalLayout->addStretch();
horizontalLayout->addWidget(importButton);
horizontalLayout->addWidget(exportButton);
horizontalLayout->addWidget(deleteButton);
horizontalLayout->addWidget(closeButton);
QVBoxLayout * verticalLayout = new QVBoxLayout();
verticalLayout->addWidget(m_tree);
verticalLayout->addLayout(horizontalLayout);
setLayout(verticalLayout);
setWindowTitle(tr("Bookmarks and tracks"));
resize(700, 600);
BookmarkManager::AsyncLoadingCallbacks callbacks;
callbacks.m_onStarted = std::bind(&BookmarkDialog::OnAsyncLoadingStarted, this);
callbacks.m_onFinished = std::bind(&BookmarkDialog::OnAsyncLoadingFinished, this);
callbacks.m_onFileSuccess = std::bind(&BookmarkDialog::OnAsyncLoadingFileSuccess, this, _1, _2);
callbacks.m_onFileError = std::bind(&BookmarkDialog::OnAsyncLoadingFileError, this, _1, _2);
m_framework.GetBookmarkManager().SetAsyncLoadingCallbacks(std::move(callbacks));
}
void BookmarkDialog::OnAsyncLoadingStarted()
{
FillTree();
}
void BookmarkDialog::OnAsyncLoadingFinished()
{
FillTree();
}
void BookmarkDialog::OnAsyncLoadingFileSuccess(std::string const & fileName, bool isTemporaryFile)
{
LOG(LINFO, ("OnAsyncLoadingFileSuccess", fileName, isTemporaryFile));
}
void BookmarkDialog::OnAsyncLoadingFileError(std::string const & fileName, bool isTemporaryFile)
{
LOG(LERROR, ("OnAsyncLoadingFileError", fileName, isTemporaryFile));
}
void BookmarkDialog::OnItemClick(QTreeWidgetItem * item, int column)
{
if (column != 1)
return;
auto const categoryIt = m_categories.find(item);
if (categoryIt != m_categories.cend())
{
done(0);
m_framework.ShowBookmarkCategory(categoryIt->second);
return;
}
auto const bookmarkIt = m_bookmarks.find(item);
if (bookmarkIt != m_bookmarks.cend())
{
done(0);
m_framework.ShowBookmark(bookmarkIt->second);
return;
}
auto const trackIt = m_tracks.find(item);
if (trackIt != m_tracks.cend())
{
done(0);
m_framework.ShowTrack(trackIt->second);
return;
}
}
void BookmarkDialog::OnCloseClick()
{
done(0);
}
void BookmarkDialog::OnImportClick()
{
auto const files = QFileDialog::getOpenFileNames(this /* parent */, tr("Open KML, KMZ, GPX..."), QString() /* dir */,
"KML, KMZ, GPX files (*.kml *.KML *.kmz *.KMZ, *.gpx *.GPX)");
for (auto const & name : files)
{
auto const file = name.toStdString();
if (file.empty())
continue;
m_framework.GetBookmarkManager().LoadBookmark(file, false /* isTemporaryFile */);
}
}
void BookmarkDialog::OnExportClick()
{
auto const selected = m_tree->selectedItems();
if (selected.empty())
{
QMessageBox ask(this);
ask.setIcon(QMessageBox::Information);
ask.setText(tr("Select one of the bookmark categories to export."));
ask.addButton(tr("OK"), QMessageBox::NoRole);
ask.exec();
return;
}
auto const categoryIt = m_categories.find(selected.front());
if (categoryIt == m_categories.cend())
{
QMessageBox ask(this);
ask.setIcon(QMessageBox::Warning);
ask.setText(tr("Selected item is not a bookmark category."));
ask.addButton(tr("OK"), QMessageBox::NoRole);
ask.exec();
return;
}
auto const name =
QFileDialog::getSaveFileName(this /* parent */, tr("Export KMZ..."), QString() /* dir */, "KMZ files (*.kmz)");
if (name.isEmpty())
return;
m_framework.GetBookmarkManager().PrepareFileForSharing({categoryIt->second},
[this, name](BookmarkManager::SharingResult const & result)
{
if (result.m_code == BookmarkManager::SharingResult::Code::Success)
{
QFile::rename(QString(result.m_sharingPath.c_str()), name);
QMessageBox ask(this);
ask.setIcon(QMessageBox::Information);
ask.setText(tr("Bookmarks successfully exported."));
ask.addButton(tr("OK"), QMessageBox::NoRole);
ask.exec();
}
else
{
QMessageBox ask(this);
ask.setIcon(QMessageBox::Critical);
ask.setText(tr("Could not export bookmarks: ") + result.m_errorString.c_str());
ask.addButton(tr("OK"), QMessageBox::NoRole);
ask.exec();
}
}, KmlFileType::Text);
}
void BookmarkDialog::OnDeleteClick()
{
auto & bm = m_framework.GetBookmarkManager();
for (auto const item : m_tree->selectedItems())
{
auto const categoryIt = m_categories.find(item);
if (categoryIt != m_categories.cend())
{
if (m_categories.size() == 1)
{
QMessageBox ask(this);
ask.setIcon(QMessageBox::Information);
ask.setText(tr("Could not delete the last category."));
ask.addButton(tr("OK"), QMessageBox::NoRole);
ask.exec();
}
else
{
bm.GetEditSession().DeleteBmCategory(categoryIt->second, true);
FillTree();
}
return;
}
auto const bookmarkIt = m_bookmarks.find(item);
if (bookmarkIt != m_bookmarks.cend())
{
bm.GetEditSession().DeleteBookmark(bookmarkIt->second);
FillTree();
return;
}
auto const trackIt = m_tracks.find(item);
if (trackIt != m_tracks.cend())
{
bm.GetEditSession().DeleteTrack(trackIt->second);
FillTree();
return;
}
}
QMessageBox ask(this);
ask.setIcon(QMessageBox::Warning);
ask.setText(tr("Select category, bookmark or track to delete."));
ask.addButton(tr("OK"), QMessageBox::NoRole);
ask.exec();
}
QTreeWidgetItem * BookmarkDialog::CreateTreeItem(std::string const & title, QTreeWidgetItem * parent)
{
QStringList labels;
labels << QString::fromStdString(title) << tr(parent != nullptr ? "Show on the map" : "");
QTreeWidgetItem * item = new QTreeWidgetItem(labels);
if (parent)
parent->addChild(item);
return item;
}
void BookmarkDialog::FillTree()
{
m_tree->setSortingEnabled(false);
m_tree->clear();
m_categories.clear();
m_bookmarks.clear();
m_tracks.clear();
auto categoriesItem = CreateTreeItem("Categories", nullptr);
auto const & bm = m_framework.GetBookmarkManager();
if (!bm.IsAsyncLoadingInProgress())
{
for (auto catId : bm.GetUnsortedBmGroupsIdList())
{
auto categoryItem = CreateTreeItem(bm.GetCategoryName(catId), categoriesItem);
m_categories[categoryItem] = catId;
for (auto bookmarkId : bm.GetUserMarkIds(catId))
{
auto const bookmark = bm.GetBookmark(bookmarkId);
auto name = GetPreferredBookmarkStr(bookmark->GetName());
if (name.empty())
{
name = measurement_utils::FormatLatLon(mercator::YToLat(bookmark->GetPivot().y),
mercator::XToLon(bookmark->GetPivot().x), true /* withComma */);
}
auto bookmarkItem = CreateTreeItem(name + " (Bookmark)", categoryItem);
m_bookmarks[bookmarkItem] = bookmarkId;
}
for (auto trackId : bm.GetTrackIds(catId))
{
auto const track = bm.GetTrack(trackId);
auto name = track->GetName();
if (name.empty())
name = "No name";
auto trackItem = CreateTreeItem(name + " (Track)", categoryItem);
trackItem->setForeground(0, Qt::darkGreen);
m_tracks[trackItem] = trackId;
}
}
}
else
{
CreateTreeItem("Loading in progress...", categoriesItem);
}
m_tree->addTopLevelItem(categoriesItem);
m_tree->expandAll();
m_tree->setCurrentItem(categoriesItem);
m_tree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
m_tree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
}
void BookmarkDialog::ShowModal()
{
FillTree();
exec();
}
} // namespace qt

50
qt/bookmark_dialog.hpp Normal file
View file

@ -0,0 +1,50 @@
#pragma once
#include "kml/types.hpp"
#include <QtWidgets/QApplication>
#include <QtWidgets/QDialog>
#include <string>
#include <unordered_map>
class QTreeWidget;
class QTreeWidgetItem;
class QLabel;
class QPushButton;
class Framework;
namespace qt
{
class BookmarkDialog : public QDialog
{
Q_OBJECT
public:
BookmarkDialog(QWidget * parent, Framework & framework);
void ShowModal();
private slots:
void OnItemClick(QTreeWidgetItem * item, int column);
void OnCloseClick();
void OnImportClick();
void OnExportClick();
void OnDeleteClick();
private:
void FillTree();
QTreeWidgetItem * CreateTreeItem(std::string const & title, QTreeWidgetItem * parent);
void OnAsyncLoadingStarted();
void OnAsyncLoadingFinished();
void OnAsyncLoadingFileSuccess(std::string const & fileName, bool isTemporaryFile);
void OnAsyncLoadingFileError(std::string const & fileName, bool isTemporaryFile);
QTreeWidget * m_tree;
Framework & m_framework;
std::unordered_map<QTreeWidgetItem *, kml::MarkGroupId> m_categories;
std::unordered_map<QTreeWidgetItem *, kml::MarkId> m_bookmarks;
std::unordered_map<QTreeWidgetItem *, kml::TrackId> m_tracks;
};
} // namespace qt

View file

@ -0,0 +1,118 @@
#include "build_common.h"
#include "platform/platform.hpp"
#include "base/file_name_utils.hpp"
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QProcess>
#include <QtCore/QProcessEnvironment>
#include <exception>
#include <iomanip> // std::quoted
#include <regex>
#include <string>
QString ExecProcess(QString const & program, std::initializer_list<QString> args, QProcessEnvironment const * env)
{
// Quote all arguments.
QStringList qargs(args);
for (auto i = qargs.begin(); i != qargs.end(); ++i)
*i = "\"" + *i + "\"";
QProcess p;
if (nullptr != env)
p.setProcessEnvironment(*env);
p.start(program, qargs, QIODevice::ReadOnly);
p.waitForFinished(-1);
int const exitCode = p.exitCode();
QString output = p.readAllStandardOutput();
QString const error = p.readAllStandardError();
if (exitCode != 0)
{
QString msg = "Error: " + program + " " + qargs.join(" ") + "\nReturned " + QString::number(exitCode);
if (!output.isEmpty())
msg += "\n" + output;
if (!error.isEmpty())
msg += "\nSTDERR:\n" + error;
throw std::runtime_error(msg.toStdString());
}
if (!error.isEmpty())
{
QString const msg = "STDERR with a zero exit code:\n" + program + " " + qargs.join(" ");
throw std::runtime_error(msg.toStdString());
}
return output;
}
bool CopyFile(QString const & oldFile, QString const & newFile)
{
if (oldFile == newFile)
return true;
if (!QFile::exists(oldFile))
return false;
if (QFile::exists(newFile) && !QFile::remove(newFile))
return false;
return QFile::copy(oldFile, newFile);
}
void CopyFromResources(QString const & name, QString const & output)
{
QString const resourceDir = GetPlatform().ResourcesDir().c_str();
if (!CopyFile(JoinPathQt({resourceDir, name}), JoinPathQt({output, name})))
throw std::runtime_error(std::string("Cannot copy file ") + name.toStdString() + " to " + output.toStdString());
}
void CopyToResources(QString const & name, QString const & input, QString const & newName)
{
QString const resourceDir = GetPlatform().ResourcesDir().c_str();
if (!CopyFile(JoinPathQt({input, name}), JoinPathQt({resourceDir, newName.isEmpty() ? name : newName})))
throw std::runtime_error(std::string("Cannot copy file ") + name.toStdString() + " from " + input.toStdString());
}
QString JoinPathQt(std::initializer_list<QString> folders)
{
QString result;
bool firstInserted = false;
for (auto it = folders.begin(); it != folders.end(); ++it)
{
if (it->isEmpty() || *it == QDir::separator())
continue;
if (firstInserted)
result.append(QDir::separator());
result.append(*it);
firstInserted = true;
}
return QDir::cleanPath(result);
}
QString GetExternalPath(QString const & name, QString const & primaryPath, QString const & secondaryPath)
{
QString const resourceDir = GetPlatform().ResourcesDir().c_str();
QString path = JoinPathQt({resourceDir, primaryPath, name});
if (!QFileInfo::exists(path))
path = JoinPathQt({resourceDir, secondaryPath, name});
// Special case for looking for in application folder.
if (!QFileInfo::exists(path) && secondaryPath.isEmpty())
{
std::string const appPath = QCoreApplication::applicationDirPath().toStdString();
std::regex re("(/[^/]*\\.app)");
std::smatch m;
if (std::regex_search(appPath, m, re) && m.size() > 0)
path.fromStdString(base::JoinPath(m[0], name.toStdString()));
}
return path;
}
QString GetProtobufEggPath()
{
return GetExternalPath("protobuf-3.3.0-py2.7.egg", "kothic", "../3party/protobuf");
}

View file

@ -0,0 +1,24 @@
#pragma once
#include <QtCore/QString>
#include <initializer_list>
#include <string>
#include <utility>
class QProcessEnvironment;
// Returns stdout output of the program, throws std::runtime_error in case of non-zero exit code.
// Quotes all arguments to avoid issues with space-containing paths.
QString ExecProcess(QString const & program, std::initializer_list<QString> args, QProcessEnvironment const * env = nullptr);
bool CopyFile(QString const & oldFile, QString const & newFile);
void CopyFromResources(QString const & name, QString const & output);
void CopyToResources(QString const & name, QString const & input, QString const & newName = "");
QString JoinPathQt(std::initializer_list<QString> folders);
QString GetExternalPath(QString const & name, QString const & primaryPath,
QString const & secondaryPath);
QString GetProtobufEggPath();

View file

@ -0,0 +1,64 @@
#include "build_drules.h"
#include "build_common.h"
#include "platform/platform.hpp"
#include <exception>
#include <fstream>
#include <streambuf>
#include <string>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QProcessEnvironment>
namespace build_style
{
void BuildDrawingRulesImpl(QString const & mapcssFile, QString const & outputDir)
{
QString const outputTemplate = JoinPathQt({outputDir, "drules_proto_design"});
QString const outputFile = outputTemplate + ".bin";
// Caller ensures that output directory is clear
if (QFile(outputFile).exists())
throw std::runtime_error("Output directory is not clear");
// Add path to the protobuf EGG in the PROTOBUF_EGG_PATH environment variable
QProcessEnvironment env{QProcessEnvironment::systemEnvironment()};
env.insert("PROTOBUF_EGG_PATH", GetProtobufEggPath());
// Run the script
(void)ExecProcess("python",
{
GetExternalPath("libkomwm.py", "kothic/src", "../tools/kothic/src"),
"-s",
mapcssFile,
"-o",
outputTemplate,
"-x",
"True",
},
&env);
// Ensure that generated file is not empty.
if (QFile(outputFile).size() == 0)
throw std::runtime_error("Drawing rules file has zero size");
}
void BuildDrawingRules(QString const & mapcssFile, QString const & outputDir)
{
CopyFromResources("mapcss-mapping.csv", outputDir);
CopyFromResources("mapcss-dynamic.txt", outputDir);
BuildDrawingRulesImpl(mapcssFile, outputDir);
}
void ApplyDrawingRules(QString const & outputDir)
{
CopyToResources("drules_proto_design.bin", outputDir);
CopyToResources("classificator.txt", outputDir);
CopyToResources("types.txt", outputDir);
CopyToResources("patterns.txt", outputDir, "patterns_design.txt");
CopyToResources("colors.txt", outputDir, "colors_design.txt");
}
} // namespace build_style

View file

@ -0,0 +1,9 @@
#pragma once
#include <QtCore/QString>
namespace build_style
{
void BuildDrawingRules(QString const & mapcssFile, QString const & outputDir);
void ApplyDrawingRules(QString const & outputDir);
} // build_style

View file

@ -0,0 +1,27 @@
#include "build_statistics.h"
#include "build_common.h"
#include "platform/platform.hpp"
#include <QtCore/QDir>
#include <exception>
#include <string>
namespace build_style
{
QString RunBuildingPhonePack(QString const & stylesDir, QString const & targetDir)
{
using std::to_string, std::runtime_error;
if (!QDir(stylesDir).exists())
throw runtime_error("Styles directory does not exist " + stylesDir.toStdString());
if (!QDir(targetDir).exists())
throw runtime_error("target directory does not exist" + targetDir.toStdString());
return ExecProcess("python",
{GetExternalPath("generate_styles_override.py", "", "../tools/python"), stylesDir, targetDir});
}
} // namespace build_style

View file

@ -0,0 +1,8 @@
#pragma once
#include <QString>
namespace build_style
{
QString RunBuildingPhonePack(QString const & stylesFolder, QString const & targetFolder);
} // namespace build_style

View file

@ -0,0 +1,216 @@
#include "qt/build_style/build_skins.h"
#include "qt/build_style/build_common.h"
#include "platform/platform.hpp"
#include <algorithm>
#include <array>
#include <exception>
#include <fstream>
#include <functional>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <QtCore/QDir>
namespace
{
enum SkinType
{
SkinMDPI,
SkinHDPI,
SkinXHDPI,
Skin6Plus,
SkinXXHDPI,
SkinXXXHDPI,
// SkinCount MUST BE last
SkinCount
};
using SkinInfo = std::tuple<char const *, int, bool>;
SkinInfo const g_skinInfo[SkinCount] = {
std::make_tuple("mdpi", 18, false), std::make_tuple("hdpi", 27, false), std::make_tuple("xhdpi", 36, false),
std::make_tuple("6plus", 43, false), std::make_tuple("xxhdpi", 54, false), std::make_tuple("xxxhdpi", 64, false),
};
std::array<SkinType, SkinCount> const g_skinTypes = {{
SkinMDPI,
SkinHDPI,
SkinXHDPI,
Skin6Plus,
SkinXXHDPI,
SkinXXXHDPI,
}};
inline char const * SkinSuffix(SkinType s)
{
return std::get<0>(g_skinInfo[s]);
}
inline int SkinSize(SkinType s)
{
return std::get<1>(g_skinInfo[s]);
}
inline bool SkinCoorrectColor(SkinType s)
{
return std::get<2>(g_skinInfo[s]);
}
QString GetSkinGeneratorPath()
{
QString const path = GetExternalPath("skin_generator_tool", "skin_generator_tool.app/Contents/MacOS", "");
if (path.isEmpty())
throw std::runtime_error("Can't find skin_generator_tool");
ASSERT(QFileInfo::exists(path), (path.toStdString()));
return path;
}
class RAII
{
public:
RAII(std::function<void()> && f) : m_f(std::move(f)) {}
~RAII() { m_f(); }
private:
std::function<void()> const m_f;
};
std::string trim(std::string && s)
{
s.erase(std::remove_if(s.begin(), s.end(), &isspace), s.end());
return std::move(s);
}
} // namespace
namespace build_style
{
std::unordered_map<std::string, int> GetSkinSizes(QString const & file)
{
std::unordered_map<std::string, int> skinSizes;
for (SkinType s : g_skinTypes)
skinSizes.insert(std::make_pair(SkinSuffix(s), SkinSize(s)));
try
{
std::ifstream ifs(file.toStdString());
std::string line;
while (std::getline(ifs, line))
{
size_t const pos = line.find('=');
if (pos == std::string::npos)
continue;
std::string name(line.begin(), line.begin() + pos);
std::string valueTxt(line.begin() + pos + 1, line.end());
name = trim(std::move(name));
int value = std::stoi(trim(std::move(valueTxt)));
if (value <= 0)
continue;
skinSizes[name] = value;
}
}
catch (std::exception const & e)
{
// reset
for (SkinType s : g_skinTypes)
skinSizes[SkinSuffix(s)] = SkinSize(s);
}
return skinSizes;
}
void BuildSkinImpl(QString const & styleDir, QString const & suffix, int size, bool colorCorrection,
QString const & outputDir)
{
QString const symbolsDir = JoinPathQt({styleDir, "symbols"});
// Check symbols directory exists
if (!QDir(symbolsDir).exists())
throw std::runtime_error("Symbols directory does not exist");
// Caller ensures that output directory is clear
if (QDir(outputDir).exists())
throw std::runtime_error("Output directory is not clear");
// Create output skin directory
if (!QDir().mkdir(outputDir))
throw std::runtime_error("Cannot create output skin directory");
// Create symbolic link for symbols/png
QString const pngOriginDir = styleDir + suffix;
QString const pngDir = JoinPathQt({styleDir, "symbols", "png"});
QFile::remove(pngDir);
if (!QFile::link(pngOriginDir, pngDir))
throw std::runtime_error("Unable to create symbols/png link");
RAII const cleaner([=]() { QFile::remove(pngDir); });
QString const strSize = QString::number(size);
// Run the script.
(void)ExecProcess(GetSkinGeneratorPath(), {
"--symbolWidth",
strSize,
"--symbolHeight",
strSize,
"--symbolsDir",
symbolsDir,
"--skinName",
JoinPathQt({outputDir, "basic"}),
"--skinSuffix=",
"--colorCorrection",
(colorCorrection ? "true" : "false"),
});
// Check if files were created.
if (QFile(JoinPathQt({outputDir, "symbols.png"})).size() == 0 ||
QFile(JoinPathQt({outputDir, "symbols.sdf"})).size() == 0)
{
throw std::runtime_error("Skin files have not been created");
}
}
void BuildSkins(QString const & styleDir, QString const & outputDir)
{
QString const resolutionFilePath = JoinPathQt({styleDir, "resolutions.txt"});
auto const resolution2size = GetSkinSizes(resolutionFilePath);
for (SkinType s : g_skinTypes)
{
QString const suffix = SkinSuffix(s);
QString const outputSkinDir = JoinPathQt({outputDir, "symbols", suffix, "design"});
int const size = resolution2size.at(suffix.toStdString()); // SkinSize(s);
bool const colorCorrection = SkinCoorrectColor(s);
BuildSkinImpl(styleDir, suffix, size, colorCorrection, outputSkinDir);
}
}
void ApplySkins(QString const & outputDir)
{
QString const resourceDir = GetPlatform().ResourcesDir().c_str();
for (SkinType s : g_skinTypes)
{
QString const suffix = SkinSuffix(s);
QString const outputSkinDir = JoinPathQt({outputDir, "symbols", suffix, "design"});
QString const resourceSkinDir = JoinPathQt({resourceDir, "symbols", suffix, "design"});
if (!QFileInfo::exists(resourceSkinDir) && !QDir().mkdir(resourceSkinDir))
throw std::runtime_error("Cannot create resource skin directory: " + resourceSkinDir.toStdString());
if (!CopyFile(JoinPathQt({outputSkinDir, "symbols.png"}), JoinPathQt({resourceSkinDir, "symbols.png"})) ||
!CopyFile(JoinPathQt({outputSkinDir, "symbols.sdf"}), JoinPathQt({resourceSkinDir, "symbols.sdf"})))
{
throw std::runtime_error("Cannot copy skins files");
}
}
}
} // namespace build_style

View file

@ -0,0 +1,9 @@
#pragma once
#include <QtCore/QString>
namespace build_style
{
void BuildSkins(QString const & styleDir, QString const & outputDir);
void ApplySkins(QString const & outputDir);
} // namespace build_style

View file

@ -0,0 +1,46 @@
#include "build_statistics.h"
#include "build_common.h"
#include "platform/platform.hpp"
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QProcessEnvironment>
#include <exception>
#include <string>
namespace build_style
{
QString GetStyleStatistics(QString const & mapcssMappingFile, QString const & drulesFile)
{
if (!QFile(mapcssMappingFile).exists())
throw std::runtime_error("mapcss-mapping file does not exist at " + mapcssMappingFile.toStdString());
if (!QFile(drulesFile).exists())
throw std::runtime_error("drawing-rules file does not exist at " + drulesFile.toStdString());
// Add path to the protobuf EGG in the PROTOBUF_EGG_PATH environment variable.
QProcessEnvironment env{QProcessEnvironment::systemEnvironment()};
env.insert("PROTOBUF_EGG_PATH", GetProtobufEggPath());
// Run the script.
return ExecProcess("python",
{
GetExternalPath("drules_info.py", "kothic/src", "../tools/python/stylesheet"),
mapcssMappingFile,
drulesFile,
},
&env);
}
QString GetCurrentStyleStatistics()
{
QString const resourceDir = GetPlatform().ResourcesDir().c_str();
QString const mappingPath = JoinPathQt({resourceDir, "mapcss-mapping.csv"});
QString const drulesPath = JoinPathQt({resourceDir, "drules_proto_design.bin"});
return GetStyleStatistics(mappingPath, drulesPath);
}
} // namespace build_style

View file

@ -0,0 +1,9 @@
#pragma once
#include <QtCore/QString>
namespace build_style
{
QString GetStyleStatistics(QString const & mapcssMappingFile, QString const & drulesFile);
QString GetCurrentStyleStatistics();
} // namespace build_style

View file

@ -0,0 +1,121 @@
#include "build_style.h"
#include "build_common.h"
#include "build_drules.h"
#include "build_skins.h"
#include "platform/platform.hpp"
#include <exception>
#include <future>
#include <string>
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFile>
namespace
{
QString GetRecalculateGeometryScriptPath()
{
return GetExternalPath("recalculate_geom_index.py", "", "../tools/python");
}
QString GetGeometryToolPath()
{
return GetExternalPath("generator_tool", "generator_tool.app/Contents/MacOS", "");
}
QString GetGeometryToolResourceDir()
{
return GetExternalPath("", "generator_tool.app/Contents/Resources", "");
}
} // namespace
namespace build_style
{
void BuildAndApply(QString const & mapcssFile)
{
// Ensure mapcss exists
if (!QFile(mapcssFile).exists())
throw std::runtime_error("mapcss files does not exist");
QDir const projectDir = QFileInfo(mapcssFile).absoluteDir();
QString const styleDir = projectDir.absolutePath() + QDir::separator();
QString const outputDir = styleDir + "out" + QDir::separator();
// Ensure output directory is clear
if (QDir(outputDir).exists() && !QDir(outputDir).removeRecursively())
throw std::runtime_error("Unable to remove the output directory");
if (!QDir().mkdir(outputDir))
throw std::runtime_error("Unable to make the output directory");
bool const hasSymbols = QDir(styleDir + "symbols/").exists();
if (hasSymbols)
{
auto future = std::async(std::launch::async, BuildSkins, styleDir, outputDir);
BuildDrawingRules(mapcssFile, outputDir);
future.get(); // may rethrow exception from the BuildSkin
ApplyDrawingRules(outputDir);
ApplySkins(outputDir);
}
else
{
BuildDrawingRules(mapcssFile, outputDir);
ApplyDrawingRules(outputDir);
}
}
void BuildIfNecessaryAndApply(QString const & mapcssFile)
{
if (!QFile(mapcssFile).exists())
throw std::runtime_error("mapcss files does not exist");
QDir const projectDir = QFileInfo(mapcssFile).absoluteDir();
QString const styleDir = projectDir.absolutePath() + QDir::separator();
QString const outputDir = styleDir + "out" + QDir::separator();
if (QDir(outputDir).exists())
{
try
{
ApplyDrawingRules(outputDir);
ApplySkins(outputDir);
}
catch (std::exception const & ex)
{
BuildAndApply(mapcssFile);
}
}
else
{
BuildAndApply(mapcssFile);
}
}
void RunRecalculationGeometryScript(QString const & mapcssFile)
{
QString const resourceDir = GetPlatform().ResourcesDir().c_str();
QString const writableDir = GetPlatform().WritableDir().c_str();
QString const generatorToolPath = GetGeometryToolPath();
QString const appPath = QCoreApplication::applicationFilePath();
QString const geometryToolResourceDir = GetGeometryToolResourceDir();
CopyFromResources("drules_proto_design.bin", geometryToolResourceDir);
CopyFromResources("classificator.txt", geometryToolResourceDir);
CopyFromResources("types.txt", geometryToolResourceDir);
(void)ExecProcess("python", {
GetRecalculateGeometryScriptPath(),
resourceDir,
writableDir,
generatorToolPath,
appPath,
mapcssFile,
});
}
bool NeedRecalculate = false;
} // namespace build_style

View file

@ -0,0 +1,12 @@
#pragma once
#include <QtCore/QString>
namespace build_style
{
void BuildAndApply(QString const & mapcssFile);
void BuildIfNecessaryAndApply(QString const & mapcssFile);
void RunRecalculationGeometryScript(QString const & mapcssFile);
extern bool NeedRecalculate;
} // namespace build_style

View file

@ -0,0 +1,22 @@
#include "run_tests.h"
#include "platform/platform.hpp"
#include "build_common.h"
namespace build_style
{
std::pair<bool, QString> RunCurrentStyleTests()
{
QString const program = GetExternalPath("style_tests", "style_tests.app/Contents/MacOS", "");
QString const resourcesDir = QString::fromStdString(GetPlatform().ResourcesDir());
QString const output = ExecProcess(program, {
"--user_resource_path=" + resourcesDir,
"--data_path=" + resourcesDir,
});
// Unfortunately test process returns 0 even if some test failed,
// therefore phrase 'All tests passed.' is looked to be sure that everything is OK.
return std::make_pair(output.contains("All tests passed."), output);
}
} // namespace build_style

View file

@ -0,0 +1,10 @@
#pragma once
#include <QtCore/QString>
#include <utility>
namespace build_style
{
std::pair<bool, QString> RunCurrentStyleTests();
} // namespace build_style

View file

@ -0,0 +1,45 @@
#include "qt/create_feature_dialog.hpp"
#include "editor/new_feature_categories.hpp"
#include "indexer/classificator.hpp"
#include "platform/preferred_languages.hpp"
#include <QtWidgets/QAbstractButton>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QListWidget>
#include <QtWidgets/QVBoxLayout>
CreateFeatureDialog::CreateFeatureDialog(QWidget * parent, osm::NewFeatureCategories & cats) : QDialog(parent)
{
cats.AddLanguage("en");
cats.AddLanguage(languages::GetCurrentNorm());
QListWidget * allSortedList = new QListWidget();
for (auto const & name : cats.GetAllCreatableTypeNames())
new QListWidgetItem(QString::fromStdString(name), allSortedList);
connect(allSortedList, &QAbstractItemView::clicked, this, &CreateFeatureDialog::OnListItemSelected);
QDialogButtonBox * dbb = new QDialogButtonBox();
dbb->addButton(QDialogButtonBox::Close);
connect(dbb, &QDialogButtonBox::clicked, this, &QDialog::reject);
QVBoxLayout * vBox = new QVBoxLayout();
vBox->addWidget(allSortedList);
vBox->addWidget(dbb);
setLayout(vBox);
setWindowTitle("OSM Editor");
}
void CreateFeatureDialog::OnListItemSelected(QModelIndex const & i)
{
auto const clType = i.data(Qt::DisplayRole).toString().toStdString();
m_selectedType = classif().GetTypeByReadableObjectName(clType);
ASSERT(m_selectedType != ftype::GetEmptyValue(), ());
accept();
}

View file

@ -0,0 +1,25 @@
#pragma once
#include <QtWidgets/QDialog>
class QModelIndex;
namespace osm
{
class NewFeatureCategories;
} // namespace osm
class CreateFeatureDialog : public QDialog
{
Q_OBJECT
public:
CreateFeatureDialog(QWidget * parent, osm::NewFeatureCategories & cats);
/// Valid only if dialog has finished with Accepted code.
uint32_t GetSelectedType() const { return m_selectedType; }
private slots:
void OnListItemSelected(QModelIndex const & i);
private:
uint32_t m_selectedType = 0;
};

773
qt/draw_widget.cpp Normal file
View file

@ -0,0 +1,773 @@
#include "qt/draw_widget.hpp"
#include "qt/create_feature_dialog.hpp"
#include "qt/editor_dialog.hpp"
#include "qt/place_page_dialog_common.hpp"
#include "qt/place_page_dialog_developer.hpp"
#include "qt/place_page_dialog_user.hpp"
#include "qt/qt_common/helpers.hpp"
#include "qt/routing_settings_dialog.hpp"
#include "qt/screenshoter.hpp"
#include "generator/borders.hpp"
#include "map/framework.hpp"
#include "search/result.hpp"
#include "search/reverse_geocoder.hpp"
#include "routing/following_info.hpp"
#include "routing/routing_callbacks.hpp"
#include "storage/country_decl.hpp"
#include "storage/storage_defines.hpp"
#include "indexer/editable_map_object.hpp"
#include "platform/platform.hpp"
#include "coding/reader.hpp"
#include "base/assert.hpp"
#include "base/file_name_utils.hpp"
#include "defines.hpp"
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <QtGui/QGuiApplication>
#include <QtGui/QMouseEvent>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QMenu>
#include <string>
#include <vector>
namespace qt
{
using namespace qt::common;
namespace
{
std::vector<dp::Color> colorList = {
dp::Color(255, 0, 0, 255), dp::Color(0, 255, 0, 255), dp::Color(0, 0, 255, 255), dp::Color(255, 255, 0, 255),
dp::Color(0, 255, 255, 255), dp::Color(255, 0, 255, 255), dp::Color(100, 0, 0, 255), dp::Color(0, 100, 0, 255),
dp::Color(0, 0, 100, 255), dp::Color(100, 100, 0, 255), dp::Color(0, 100, 100, 255), dp::Color(100, 0, 100, 255)};
void DrawMwmBorder(df::DrapeApi & drapeApi, std::string const & mwmName, std::vector<m2::RegionD> const & regions,
bool withVertices)
{
for (size_t i = 0; i < regions.size(); ++i)
{
auto const & region = regions[i];
auto const & points = region.Data();
if (points.empty())
return;
static uint32_t kColorCounter = 0;
auto lineData = df::DrapeApiLineData(points, colorList[kColorCounter]).Width(4.0f).ShowId();
if (withVertices)
lineData.ShowPoints(true /* markPoints */);
auto const & name = i == 0 ? mwmName : mwmName + "_" + std::to_string(i);
drapeApi.AddLine(name, lineData);
kColorCounter = (kColorCounter + 1) % colorList.size();
}
}
#if defined(OMIM_OS_LINUX)
df::TouchEvent::ETouchType qtTouchEventTypeToDfTouchEventType(QEvent::Type qEventType)
{
switch (qEventType)
{
case QEvent::TouchBegin: return df::TouchEvent::TOUCH_DOWN;
case QEvent::TouchEnd: return df::TouchEvent::TOUCH_UP;
case QEvent::TouchUpdate: return df::TouchEvent::TOUCH_MOVE;
case QEvent::TouchCancel: return df::TouchEvent::TOUCH_CANCEL;
default: return df::TouchEvent::TOUCH_NONE;
}
}
#endif
} // namespace
DrawWidget::DrawWidget(Framework & framework, std::unique_ptr<ScreenshotParams> && screenshotParams, QWidget * parent)
: TBase(framework, screenshotParams != nullptr, parent)
, m_rubberBand(nullptr)
, m_emulatingLocation(false)
{
setFocusPolicy(Qt::StrongFocus);
m_framework.SetPlacePageListeners([this]() { ShowPlacePage(); }, {} /* onClose */, {} /* onUpdate */,
{} /*onSwitchFullScreen */);
auto & routingManager = m_framework.GetRoutingManager();
routingManager.SetRouteBuildingListener(
[&routingManager, this](routing::RouterResultCode, storage::CountriesSet const &)
{
auto & drapeApi = m_framework.GetDrapeApi();
m_turnsVisualizer.ClearTurns(drapeApi);
if (RoutingSettings::TurnsEnabled())
m_turnsVisualizer.Visualize(routingManager, drapeApi);
auto const routerType = routingManager.GetLastUsedRouter();
if (routerType == routing::RouterType::Pedestrian || routerType == routing::RouterType::Bicycle)
{
RoutingManager::DistanceAltitude da;
if (!routingManager.GetRouteAltitudesAndDistancesM(da))
return;
for (int iter = 0; iter < 2; ++iter)
{
LOG(LINFO, ("Altitudes", iter == 0 ? "before" : "after", "simplify:"));
LOG_SHORT(LDEBUG, (da));
uint32_t totalAscent, totalDescent;
da.CalculateAscentDescent(totalAscent, totalDescent);
LOG_SHORT(LINFO, ("Ascent:", totalAscent, "Descent:", totalDescent));
da.Simplify();
}
}
});
routingManager.SetRouteRecommendationListener([this](RoutingManager::Recommendation r) { OnRouteRecommendation(r); });
if (screenshotParams != nullptr)
{
m_ratio = screenshotParams->m_dpiScale;
m_screenshoter = std::make_unique<Screenshoter>(*screenshotParams, m_framework, this);
}
}
DrawWidget::~DrawWidget()
{
delete m_rubberBand;
}
void DrawWidget::PrepareShutdown()
{
auto & routingManager = m_framework.GetRoutingManager();
if (routingManager.IsRoutingActive() && routingManager.IsRoutingFollowing())
{
routingManager.SaveRoutePoints();
auto style = m_framework.GetMapStyle();
m_framework.MarkMapStyle(MapStyleIsDark(style) ? MapStyle::MapStyleDefaultDark : MapStyle::MapStyleDefaultLight);
}
}
void DrawWidget::UpdateAfterSettingsChanged()
{
m_framework.EnterForeground();
}
void DrawWidget::ShowAll()
{
m_framework.ShowAll();
}
void DrawWidget::ChoosePositionModeEnable()
{
m_framework.BlockTapEvents(true /* block */);
m_framework.EnableChoosePositionMode(true /* enable */, false /* enableBounds */, nullptr /* optionalPosition */);
}
void DrawWidget::ChoosePositionModeDisable()
{
m_framework.EnableChoosePositionMode(false /* enable */, false /* enableBounds */, nullptr /* optionalPosition */);
m_framework.BlockTapEvents(false /* block */);
}
void DrawWidget::initializeGL()
{
if (m_screenshotMode)
m_framework.GetBookmarkManager().EnableTestMode(true);
else
m_framework.LoadBookmarks();
MapWidget::initializeGL();
m_framework.GetRoutingManager().LoadRoutePoints([this](bool success)
{
if (success)
m_framework.GetRoutingManager().BuildRoute();
});
if (m_screenshotMode)
m_screenshoter->Start();
}
bool DrawWidget::event(QEvent * event)
{
#if !defined(OMIM_OS_LINUX)
return QOpenGLWidget::event(event);
#else
// TouchScreen
if (auto dfTouchEventType = qtTouchEventTypeToDfTouchEventType(event->type());
dfTouchEventType != df::TouchEvent::TOUCH_NONE)
{
event->accept();
QTouchEvent const * qtTouchEvent = dynamic_cast<QTouchEvent const *>(event);
df::TouchEvent dfTouchEvent;
// The SetTouchType hast to be set even if `qtTouchEvent->points()` is empty
// which theoretically can happen in case of `QEvent::TouchCancel`
dfTouchEvent.SetTouchType(dfTouchEventType);
int64_t i = 0;
for (auto it = qtTouchEvent->points().cbegin();
it != qtTouchEvent->points().cend() && i < 2; /* For now drape_frontend can only handle max 2 touches */
++it, ++i)
{
df::Touch touch;
touch.m_id = i;
touch.m_location = m2::PointD(L2D(it->position().x()), L2D(it->position().y()));
if (i == 0)
dfTouchEvent.SetFirstTouch(touch);
else
dfTouchEvent.SetSecondTouch(touch);
}
m_framework.TouchEvent(dfTouchEvent);
return true;
}
// TouchPad
else if (event->type() == QEvent::NativeGesture)
{
event->accept();
auto qNativeGestureEvent = dynamic_cast<QNativeGestureEvent *>(event);
if (qNativeGestureEvent->gestureType() == Qt::ZoomNativeGesture)
{
QPointF const pos = qNativeGestureEvent->position();
double const factor = qNativeGestureEvent->value();
m_framework.Scale(exp(factor), m2::PointD(L2D(pos.x()), L2D(pos.y())), false);
return true;
}
}
// Everything else
return QOpenGLWidget::event(event);
#endif
}
void DrawWidget::mousePressEvent(QMouseEvent * e)
{
if (m_screenshotMode)
return;
QOpenGLWidget::mousePressEvent(e);
m2::PointD const pt = GetDevicePoint(e);
if (IsLeftButton(e))
{
if (IsShiftModifier(e))
SubmitRoutingPoint(pt, false);
else if (m_ruler.IsActive() && IsAltModifier(e))
SubmitRulerPoint(pt);
else if (IsAltModifier(e))
SubmitFakeLocationPoint(pt);
else
m_framework.TouchEvent(GetDfTouchEventFromQMouseEvent(e, df::TouchEvent::TOUCH_DOWN));
}
else if (IsRightButton(e))
{
if (IsAltModifier(e))
{
SubmitBookmark(pt);
}
else if (!m_selectionMode || IsCommandModifier(e))
{
ShowInfoPopup(e, pt);
}
else
{
m_rubberBandOrigin = e->pos();
if (m_rubberBand == nullptr)
m_rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
m_rubberBand->setGeometry(QRect(m_rubberBandOrigin, QSize()));
m_rubberBand->show();
}
}
}
void DrawWidget::mouseMoveEvent(QMouseEvent * e)
{
if (m_screenshotMode)
return;
QOpenGLWidget::mouseMoveEvent(e);
if (IsLeftButton(e) && !IsAltModifier(e))
{
m_framework.TouchEvent(GetDfTouchEventFromQMouseEvent(e, df::TouchEvent::TOUCH_MOVE));
e->accept();
}
if (m_selectionMode && m_rubberBand != nullptr && m_rubberBand->isVisible())
m_rubberBand->setGeometry(QRect(m_rubberBandOrigin, e->pos()).normalized());
}
void DrawWidget::VisualizeMwmsBordersInRect(m2::RectD const & rect, bool withVertices, bool fromPackedPolygon,
bool boundingBox)
{
auto const getRegions = [&](std::string const & mwmName)
{
if (fromPackedPolygon)
{
std::vector<storage::CountryDef> countries;
FilesContainerR reader(base::JoinPath(GetPlatform().ResourcesDir(), PACKED_POLYGONS_FILE));
ReaderSource<ModelReaderPtr> src(reader.GetReader(PACKED_POLYGONS_INFO_TAG));
rw::Read(src, countries);
for (size_t id = 0; id < countries.size(); ++id)
{
if (countries[id].m_countryId != mwmName)
continue;
src = reader.GetReader(std::to_string(id));
return borders::ReadPolygonsOfOneBorder(src);
}
UNREACHABLE();
}
else
{
std::string const bordersDir = base::JoinPath(GetPlatform().WritableDir(), BORDERS_DIR);
std::string const path = base::JoinPath(bordersDir, mwmName + BORDERS_EXTENSION);
std::vector<m2::RegionD> polygons;
borders::LoadBorders(path, polygons);
return polygons;
}
};
auto mwmNames = m_framework.GetRegionsCountryIdByRect(rect, false /* rough */);
for (auto & mwmName : mwmNames)
{
auto regions = getRegions(mwmName);
mwmName += fromPackedPolygon ? ".bin" : ".poly";
if (boundingBox)
{
std::vector<m2::RegionD> boxes;
for (auto const & region : regions)
{
auto const r = region.GetRect();
boxes.emplace_back(
std::vector<m2::PointD>({r.LeftBottom(), r.LeftTop(), r.RightTop(), r.RightBottom(), r.LeftBottom()}));
}
regions = std::move(boxes);
mwmName += ".box";
}
DrawMwmBorder(m_framework.GetDrapeApi(), mwmName, regions, withVertices);
}
}
void DrawWidget::mouseReleaseEvent(QMouseEvent * e)
{
if (m_screenshotMode)
return;
QOpenGLWidget::mouseReleaseEvent(e);
if (IsLeftButton(e) && !IsAltModifier(e))
m_framework.TouchEvent(GetDfTouchEventFromQMouseEvent(e, df::TouchEvent::TOUCH_UP));
else if (m_selectionMode && IsRightButton(e) && m_rubberBand != nullptr && m_rubberBand->isVisible())
ProcessSelectionMode();
}
void DrawWidget::ProcessSelectionMode()
{
QPoint const lt = m_rubberBand->geometry().topLeft();
QPoint const rb = m_rubberBand->geometry().bottomRight();
m2::RectD rect;
rect.Add(m_framework.PtoG(m2::PointD(L2D(lt.x()), L2D(lt.y()))));
rect.Add(m_framework.PtoG(m2::PointD(L2D(rb.x()), L2D(rb.y()))));
switch (*m_selectionMode)
{
case SelectionMode::Features: m_framework.VisualizeRoadsInRect(rect); break;
case SelectionMode::CityBoundaries: m_framework.VisualizeCityBoundariesInRect(rect); break;
case SelectionMode::CityRoads: m_framework.VisualizeCityRoadsInRect(rect); break;
case SelectionMode::CrossMwmSegments: m_framework.VisualizeCrossMwmTransitionsInRect(rect); break;
case SelectionMode::MwmsBordersByPolyFiles:
VisualizeMwmsBordersInRect(rect, false /* withVertices */, false /* fromPackedPolygon */, false /* boundingBox */);
break;
case SelectionMode::MwmsBordersWithVerticesByPolyFiles:
VisualizeMwmsBordersInRect(rect, true /* withVertices */, false /* fromPackedPolygon */, false /* boundingBox */);
break;
case SelectionMode::MwmsBordersByPackedPolygon:
VisualizeMwmsBordersInRect(rect, false /* withVertices */, true /* fromPackedPolygon */, false /* boundingBox */);
break;
case SelectionMode::MwmsBordersWithVerticesByPackedPolygon:
VisualizeMwmsBordersInRect(rect, true /* withVertices */, true /* fromPackedPolygon */, false /* boundingBox */);
break;
case SelectionMode::BoundingBoxByPolyFiles:
VisualizeMwmsBordersInRect(rect, true /* withVertices */, false /* fromPackedPolygon */, true /* boundingBox */);
break;
case SelectionMode::BoundingBoxByPackedPolygon:
VisualizeMwmsBordersInRect(rect, true /* withVertices */, true /* fromPackedPolygon */, true /* boundingBox */);
break;
default: UNREACHABLE();
}
m_rubberBand->hide();
}
void DrawWidget::keyPressEvent(QKeyEvent * e)
{
if (m_screenshotMode)
return;
if (IsLeftButton(QGuiApplication::mouseButtons()) && e->key() == Qt::Key_Control)
{
df::TouchEvent event;
event.SetTouchType(df::TouchEvent::TOUCH_DOWN);
df::Touch touch;
touch.m_id = 0;
touch.m_location = m2::PointD(L2D(QCursor::pos().x()), L2D(QCursor::pos().y()));
event.SetFirstTouch(touch);
event.SetSecondTouch(GetSymmetrical(touch));
m_framework.TouchEvent(event);
}
}
void DrawWidget::keyReleaseEvent(QKeyEvent * e)
{
if (m_screenshotMode)
return;
if (IsLeftButton(QGuiApplication::mouseButtons()) && e->key() == Qt::Key_Control)
{
df::TouchEvent event;
event.SetTouchType(df::TouchEvent::TOUCH_UP);
df::Touch touch;
touch.m_id = 0;
touch.m_location = m2::PointD(L2D(QCursor::pos().x()), L2D(QCursor::pos().y()));
event.SetFirstTouch(touch);
event.SetSecondTouch(GetSymmetrical(touch));
m_framework.TouchEvent(event);
}
else if (e->key() == Qt::Key_Alt)
m_emulatingLocation = false;
}
std::string DrawWidget::GetDistance(search::Result const & res) const
{
platform::Distance dist;
if (auto const position = m_framework.GetCurrentPosition())
{
auto const ll = mercator::ToLatLon(*position);
double dummy;
(void)m_framework.GetDistanceAndAzimut(res.GetFeatureCenter(), ll.m_lat, ll.m_lon, -1.0, dist, dummy);
}
return dist.ToString();
}
void DrawWidget::CreateFeature()
{
auto cats = m_framework.GetEditorCategories();
CreateFeatureDialog dlg(this, cats);
if (dlg.exec() == QDialog::Accepted)
{
osm::EditableMapObject emo;
if (m_framework.CreateMapObject(m_framework.GetViewportCenter(), dlg.GetSelectedType(), emo))
{
EditorDialog dlg(this, emo);
int const result = dlg.exec();
if (result == QDialog::Accepted)
m_framework.SaveEditedMapObject(emo);
}
else
{
LOG(LWARNING, ("Error creating new map object."));
}
}
}
void DrawWidget::OnLocationUpdate(location::GpsInfo const & info)
{
if (!m_emulatingLocation)
m_framework.OnLocationUpdate(info);
}
void DrawWidget::SetMapStyle(MapStyle mapStyle)
{
m_framework.SetMapStyle(mapStyle);
}
void DrawWidget::SubmitFakeLocationPoint(m2::PointD const & pt)
{
m_emulatingLocation = true;
m2::PointD const point = GetCoordsFromSettingsIfExists(true /* start */, pt, false /* pointIsMercator */);
m_framework.OnLocationUpdate(qt::common::MakeGpsInfo(point));
auto & routingManager = m_framework.GetRoutingManager();
if (routingManager.IsRoutingActive())
{
/// Immediate update of the position in Route to get updated FollowingInfo state for visual debugging.
/// m_framework.OnLocationUpdate calls RoutingSession::OnLocationPositionChanged
/// with delay several times according to interpolation.
/// @todo Write log when the final point will be reached and
/// RoutingSession::OnLocationPositionChanged will be called the last time.
routingManager.RoutingSession().OnLocationPositionChanged(qt::common::MakeGpsInfo(point));
routing::FollowingInfo loc;
routingManager.GetRouteFollowingInfo(loc);
if (routingManager.GetCurrentRouterType() == routing::RouterType::Pedestrian)
{
LOG(LDEBUG, ("Distance:", loc.m_distToTarget, "Time:", loc.m_time, DebugPrint(loc.m_pedestrianTurn), "in",
loc.m_distToTurn.ToString(), loc.m_nextStreetName.empty() ? "" : "to " + loc.m_nextStreetName));
}
else
{
std::string speed;
if (loc.m_speedLimitMps > 0)
speed = "SpeedLimit: " +
measurement_utils::FormatSpeedNumeric(loc.m_speedLimitMps, measurement_utils::Units::Metric);
LOG(LDEBUG, ("Distance:", loc.m_distToTarget, "Time:", loc.m_time, speed, GetTurnString(loc.m_turn),
(loc.m_exitNum != 0 ? ":" + std::to_string(loc.m_exitNum) : ""), "in", loc.m_distToTurn.ToString(),
loc.m_nextStreetName.empty() ? "" : "to " + loc.m_nextStreetName));
}
}
}
void DrawWidget::SubmitRulerPoint(m2::PointD const & pt)
{
m_ruler.AddPoint(P2G(pt));
m_ruler.DrawLine(m_framework.GetDrapeApi());
}
void DrawWidget::SubmitRoutingPoint(m2::PointD const & pt, bool pointIsMercator)
{
auto & routingManager = m_framework.GetRoutingManager();
// Check if limit of intermediate points is reached.
bool const isIntermediate = m_routePointAddMode == RouteMarkType::Intermediate;
if (isIntermediate && !routingManager.CouldAddIntermediatePoint())
routingManager.RemoveRoutePoint(RouteMarkType::Intermediate, 0);
// Insert implicit start point.
if (m_routePointAddMode == RouteMarkType::Finish && routingManager.GetRoutePoints().empty())
{
RouteMarkData startPoint;
startPoint.m_pointType = RouteMarkType::Start;
startPoint.m_isMyPosition = true;
routingManager.AddRoutePoint(std::move(startPoint));
}
RouteMarkData point;
point.m_pointType = m_routePointAddMode;
point.m_isMyPosition = false;
if (!isIntermediate)
point.m_position = GetCoordsFromSettingsIfExists(false /* start */, pt, pointIsMercator);
else
point.m_position = pointIsMercator ? pt : P2G(pt);
routingManager.AddRoutePoint(std::move(point));
if (routingManager.GetRoutePoints().size() >= 2)
{
if (RoutingSettings::UseDebugGuideTrack())
{
// Like in guides_tests.cpp, GetTestGuides().
routing::GuidesTracks guides;
guides[10] = {{{mercator::FromLatLon(48.13999, 11.56873), 10},
{mercator::FromLatLon(48.14096, 11.57246), 10},
{mercator::FromLatLon(48.14487, 11.57259), 10}}};
routingManager.RoutingSession().SetGuidesForTests(std::move(guides));
}
else
routingManager.RoutingSession().SetGuidesForTests({});
routingManager.BuildRoute();
}
}
void DrawWidget::SubmitBookmark(m2::PointD const & pt)
{
auto & manager = m_framework.GetBookmarkManager();
kml::BookmarkData data;
data.m_color.m_predefinedColor = kml::PredefinedColor::Red;
data.m_point = m_framework.P3dtoG(pt);
manager.GetEditSession().CreateBookmark(std::move(data), manager.LastEditedBMCategory());
}
void DrawWidget::FollowRoute()
{
auto & routingManager = m_framework.GetRoutingManager();
auto const points = routingManager.GetRoutePoints();
if (points.size() < 2)
return;
if (!points.front().m_isMyPosition && !points.back().m_isMyPosition)
return;
if (routingManager.IsRoutingActive() && !routingManager.IsRoutingFollowing())
{
routingManager.FollowRoute();
SetMapStyleToVehicle();
}
}
void DrawWidget::ClearRoute()
{
auto & routingManager = m_framework.GetRoutingManager();
bool const wasActive = routingManager.IsRoutingActive() && routingManager.IsRoutingFollowing();
routingManager.CloseRouting(true /* remove route points */);
if (wasActive)
SetMapStyleToDefault();
m_turnsVisualizer.ClearTurns(m_framework.GetDrapeApi());
}
void DrawWidget::OnRouteRecommendation(RoutingManager::Recommendation recommendation)
{
if (recommendation == RoutingManager::Recommendation::RebuildAfterPointsLoading)
{
auto & routingManager = m_framework.GetRoutingManager();
RouteMarkData startPoint;
startPoint.m_pointType = RouteMarkType::Start;
startPoint.m_isMyPosition = true;
routingManager.AddRoutePoint(std::move(startPoint));
if (routingManager.GetRoutePoints().size() >= 2)
routingManager.BuildRoute();
}
}
void DrawWidget::ShowPlacePage()
{
place_page::Info const & info = m_framework.GetCurrentPlacePageInfo();
search::ReverseGeocoder::Address address;
if (info.IsFeature())
{
search::ReverseGeocoder const coder(m_framework.GetDataSource());
coder.GetExactAddress(info.GetID(), address);
}
else
{
address = m_framework.GetAddressAtPoint(info.GetMercator());
}
std::unique_ptr<QDialog> placePageDialog = nullptr;
bool developerMode;
if (settings::Get(settings::kDeveloperMode, developerMode) && developerMode)
placePageDialog = std::make_unique<PlacePageDialogDeveloper>(this, info, address);
else
placePageDialog = std::make_unique<PlacePageDialogUser>(this, info, address);
switch (placePageDialog->exec())
{
case place_page_dialog::EditPlace:
{
osm::EditableMapObject emo;
if (m_framework.GetEditableMapObject(info.GetID(), emo))
{
EditorDialog dlg(this, emo);
int const result = dlg.exec();
if (result == QDialog::Accepted)
{
m_framework.SaveEditedMapObject(emo);
m_framework.UpdatePlacePageInfoForCurrentSelection();
}
else if (result == QDialogButtonBox::DestructiveRole)
{
m_framework.DeleteFeature(info.GetID());
}
}
else
{
LOG(LERROR, ("Error while trying to edit feature."));
}
}
break;
case place_page_dialog::RouteFrom:
{
SetRoutePointAddMode(RouteMarkType::Start);
SubmitRoutingPoint(info.GetMercator(), true);
}
break;
case place_page_dialog::AddStop:
{
SetRoutePointAddMode(RouteMarkType::Intermediate);
SubmitRoutingPoint(info.GetMercator(), true);
}
break;
case place_page_dialog::RouteTo:
{
SetRoutePointAddMode(RouteMarkType::Finish);
SubmitRoutingPoint(info.GetMercator(), true);
}
break;
default: break;
}
m_framework.DeactivateMapSelection();
}
void DrawWidget::SetRuler(bool enabled)
{
if (!enabled)
m_ruler.EraseLine(m_framework.GetDrapeApi());
m_ruler.SetActive(enabled);
}
// static
void DrawWidget::RefreshDrawingRules()
{
SetMapStyle(MapStyleDefaultLight);
}
void DrawWidget::SetMapStyleToDefault()
{
auto const style = m_framework.GetMapStyle();
SetMapStyle(MapStyleIsDark(style) ? MapStyle::MapStyleDefaultDark : MapStyle::MapStyleDefaultLight);
}
void DrawWidget::SetMapStyleToVehicle()
{
auto const style = m_framework.GetMapStyle();
SetMapStyle(MapStyleIsDark(style) ? MapStyle::MapStyleVehicleDark : MapStyle::MapStyleVehicleLight);
}
void DrawWidget::SetMapStyleToOutdoors()
{
auto const style = m_framework.GetMapStyle();
SetMapStyle(MapStyleIsDark(style) ? MapStyle::MapStyleOutdoorsDark : MapStyle::MapStyleOutdoorsLight);
}
m2::PointD DrawWidget::P2G(m2::PointD const & pt) const
{
return m_framework.P3dtoG(pt);
}
m2::PointD DrawWidget::GetCoordsFromSettingsIfExists(bool start, m2::PointD const & pt, bool pointIsMercator) const
{
if (auto optional = RoutingSettings::GetCoords(start))
return mercator::FromLatLon(*optional);
return pointIsMercator ? pt : P2G(pt);
}
} // namespace qt

129
qt/draw_widget.hpp Normal file
View file

@ -0,0 +1,129 @@
#pragma once
#include "qt/qt_common/map_widget.hpp"
#include "qt/routing_turns_visualizer.hpp"
#include "qt/ruler.hpp"
#include "qt/selection.hpp"
#include "map/routing_manager.hpp"
#include "search/result.hpp"
#include "indexer/map_style.hpp"
#include <QtWidgets/QRubberBand>
#include <memory>
#include <optional>
#include <string>
class Framework;
namespace qt
{
namespace common
{
class ScaleSlider;
}
class Screenshoter;
struct ScreenshotParams;
class DrawWidget : public qt::common::MapWidget
{
using TBase = MapWidget;
Q_OBJECT
public Q_SLOTS:
void ShowAll();
void ChoosePositionModeEnable();
void ChoosePositionModeDisable();
public:
DrawWidget(Framework & framework, std::unique_ptr<ScreenshotParams> && screenshotParams, QWidget * parent);
~DrawWidget() override;
std::string GetDistance(search::Result const & res) const;
void CreateFeature();
void OnLocationUpdate(location::GpsInfo const & info);
void UpdateAfterSettingsChanged();
void PrepareShutdown();
Framework & GetFramework() { return m_framework; }
void SetMapStyle(MapStyle mapStyle);
void SetRuler(bool enabled);
RouteMarkType GetRoutePointAddMode() const { return m_routePointAddMode; }
void SetRoutePointAddMode(RouteMarkType mode) { m_routePointAddMode = mode; }
void FollowRoute();
void ClearRoute();
void OnRouteRecommendation(RoutingManager::Recommendation recommendation);
void RefreshDrawingRules();
void SetMapStyleToDefault();
void SetMapStyleToVehicle();
void SetMapStyleToOutdoors();
protected:
/// @name Overriden from MapWidget.
//@{
void initializeGL() override;
// Touch events
bool event(QEvent * event) override;
// Non-touch events
void mousePressEvent(QMouseEvent * e) override;
void mouseMoveEvent(QMouseEvent * e) override;
void mouseReleaseEvent(QMouseEvent * e) override;
//@}
void keyPressEvent(QKeyEvent * e) override;
void keyReleaseEvent(QKeyEvent * e) override;
private:
void SubmitFakeLocationPoint(m2::PointD const & pt);
void SubmitRulerPoint(m2::PointD const & pt);
void SubmitRoutingPoint(m2::PointD const & pt, bool pointIsMercator);
void SubmitBookmark(m2::PointD const & pt);
void ShowPlacePage();
void VisualizeMwmsBordersInRect(m2::RectD const & rect, bool withVertices, bool fromPackedPolygon, bool boundingBox);
m2::PointD P2G(m2::PointD const & pt) const;
m2::PointD GetCoordsFromSettingsIfExists(bool start, m2::PointD const & pt, bool pointIsMercator) const;
QRubberBand * m_rubberBand;
QPoint m_rubberBandOrigin;
bool m_emulatingLocation;
public:
/// Pass empty \a mode to drop selection.
void SetSelectionMode(std::optional<SelectionMode> mode) { m_selectionMode = mode; }
void DropSelectionIfMWMBordersMode()
{
static_assert(SelectionMode::MWMBorders < SelectionMode::Cancelled, "");
if (m_selectionMode && *m_selectionMode > SelectionMode::MWMBorders && *m_selectionMode < SelectionMode::Cancelled)
m_selectionMode = {};
}
private:
void ProcessSelectionMode();
std::optional<SelectionMode> m_selectionMode;
RouteMarkType m_routePointAddMode = RouteMarkType::Finish;
std::unique_ptr<Screenshoter> m_screenshoter;
Ruler m_ruler;
RoutingTurnsVisualizer m_turnsVisualizer;
};
} // namespace qt

233
qt/editor_dialog.cpp Normal file
View file

@ -0,0 +1,233 @@
#include "qt/editor_dialog.hpp"
#include "indexer/editable_map_object.hpp"
#include "indexer/feature_utils.hpp"
#include "base/string_utils.hpp"
#include <string>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include <QtCore/QSignalMapper>
constexpr char const * kStreetObjectName = "addr:street";
constexpr char const * kHouseNumberObjectName = "addr:housenumber";
constexpr char const * kPostcodeObjectName = "addr:postcode";
constexpr char const * kInternetObjectName = "internet_access";
EditorDialog::EditorDialog(QWidget * parent, osm::EditableMapObject & emo) : QDialog(parent), m_feature(emo)
{
QGridLayout * grid = new QGridLayout();
int row = 0;
// Coordinates.
{
ms::LatLon const ll = emo.GetLatLon();
grid->addWidget(new QLabel("Latitude/Longitude:"), row, 0);
QHBoxLayout * coords = new QHBoxLayout();
coords->addWidget(new QLabel(
QString::fromStdString(strings::to_string_dac(ll.m_lat, 7) + " " + strings::to_string_dac(ll.m_lon, 7))));
grid->addLayout(coords, row++, 1);
}
// Feature types.
{
grid->addWidget(new QLabel("Type:"), row, 0);
std::string const raw = DebugPrint(m_feature.GetTypes());
QLabel * label = new QLabel(QString::fromStdString(raw));
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
grid->addWidget(label, row++, 1);
}
// Names.
if (emo.IsNameEditable())
{
grid->addWidget(new QLabel(QString("Name:")), row, 0);
QGridLayout * namesGrid = new QGridLayout();
int namesRow = 0;
for (auto const & ln : emo.GetNamesDataSource().names)
{
namesGrid->addWidget(new QLabel(QString::fromUtf8(ln.m_lang.data(), ln.m_lang.size())), namesRow, 0);
QLineEdit * lineEditName = new QLineEdit(QString::fromStdString(ln.m_name));
lineEditName->setReadOnly(!emo.IsNameEditable());
std::string_view const code = StringUtf8Multilang::GetLangByCode(ln.m_code);
lineEditName->setObjectName(QString::fromUtf8(code.data(), code.size()));
namesGrid->addWidget(lineEditName, namesRow++, 1);
}
grid->addLayout(namesGrid, row++, 1);
}
using PropID = osm::MapObject::MetadataID;
// Address rows.
if (emo.IsAddressEditable())
{
auto nearbyStreets = emo.GetNearbyStreets();
grid->addWidget(new QLabel(kStreetObjectName), row, 0);
QComboBox * cmb = new QComboBox();
cmb->setEditable(true);
if (emo.GetStreet().m_defaultName.empty())
cmb->addItem("");
for (size_t i = 0; i < nearbyStreets.size(); ++i)
{
std::string street = nearbyStreets[i].m_defaultName;
if (!nearbyStreets[i].m_localizedName.empty())
street += " / " + nearbyStreets[i].m_localizedName;
cmb->addItem(street.c_str());
if (emo.GetStreet() == nearbyStreets[i])
cmb->setCurrentIndex(static_cast<int>(i));
}
cmb->setObjectName(kStreetObjectName);
grid->addWidget(cmb, row++, 1);
grid->addWidget(new QLabel(kHouseNumberObjectName), row, 0);
QLineEdit * houseLineEdit = new QLineEdit(emo.GetHouseNumber().c_str());
houseLineEdit->setObjectName(kHouseNumberObjectName);
grid->addWidget(houseLineEdit, row++, 1);
grid->addWidget(new QLabel(kPostcodeObjectName), row, 0);
QLineEdit * postcodeEdit = new QLineEdit(QString::fromStdString(std::string(emo.GetPostcode())));
postcodeEdit->setObjectName(kPostcodeObjectName);
grid->addWidget(postcodeEdit, row++, 1);
}
// Editable metadata rows.
for (auto const prop : emo.GetEditableProperties())
{
std::string v;
switch (prop)
{
case PropID::FMD_INTERNET:
{
grid->addWidget(new QLabel(kInternetObjectName), row, 0);
QComboBox * cmb = new QComboBox();
std::string const values[] = {DebugPrint(feature::Internet::Unknown), DebugPrint(feature::Internet::Wlan),
DebugPrint(feature::Internet::Wired), DebugPrint(feature::Internet::Terminal),
DebugPrint(feature::Internet::Yes), DebugPrint(feature::Internet::No)};
for (auto const & v : values)
cmb->addItem(v.c_str());
cmb->setCurrentText(DebugPrint(emo.GetInternet()).c_str());
cmb->setObjectName(kInternetObjectName);
grid->addWidget(cmb, row++, 1);
}
continue;
case PropID::FMD_CUISINE: v = strings::JoinStrings(emo.GetLocalizedCuisines(), ", "); break;
case PropID::FMD_POSTCODE: // already set above
continue;
default: v = emo.GetMetadata(prop); break;
}
QString const fieldName = QString::fromStdString(DebugPrint(prop));
grid->addWidget(new QLabel(fieldName), row, 0);
QLineEdit * lineEdit = new QLineEdit(QString::fromStdString(v));
lineEdit->setObjectName(fieldName);
grid->addWidget(lineEdit, row++, 1);
}
// Dialog buttons.
{
QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
connect(buttonBox, &QDialogButtonBox::accepted, this, &EditorDialog::OnSave);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
// Delete button should send custom int return value from dialog.
QPushButton * deletePOIButton = new QPushButton("Delete POI");
QSignalMapper * signalMapper = new QSignalMapper();
connect(deletePOIButton, SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(deletePOIButton, QDialogButtonBox::DestructiveRole);
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(done(int)));
buttonBox->addButton(deletePOIButton, QDialogButtonBox::DestructiveRole);
grid->addWidget(buttonBox, row++, 1);
}
setLayout(grid);
setWindowTitle("OSM Editor");
}
void EditorDialog::OnSave()
{
// Store names.
if (m_feature.IsNameEditable())
{
StringUtf8Multilang names;
for (int8_t langCode = StringUtf8Multilang::kDefaultCode; langCode < StringUtf8Multilang::kMaxSupportedLanguages;
++langCode)
{
std::string_view const lang = StringUtf8Multilang::GetLangByCode(langCode);
QLineEdit * le = findChild<QLineEdit *>(QString::fromUtf8(lang.data(), lang.size()));
if (!le)
continue;
std::string const name = le->text().toStdString();
if (!name.empty())
names.AddString(langCode, name);
}
m_feature.SetName(names);
}
using PropID = osm::MapObject::MetadataID;
// Store address.
if (m_feature.IsAddressEditable())
{
m_feature.SetHouseNumber(findChild<QLineEdit *>(kHouseNumberObjectName)->text().toStdString());
QString const editedStreet = findChild<QComboBox *>(kStreetObjectName)->currentText();
QStringList const names = editedStreet.split(" / ", Qt::SkipEmptyParts);
QString const localized = names.size() > 1 ? names.at(1) : QString();
if (!names.empty())
m_feature.SetStreet({names.at(0).toStdString(), localized.toStdString()});
else
m_feature.SetStreet({});
QLineEdit * editor = findChild<QLineEdit *>(kPostcodeObjectName);
std::string v = editor->text().toStdString();
if (osm::EditableMapObject::ValidatePostCode(v))
m_feature.SetPostcode(v);
}
// Store other props.
for (auto const prop : m_feature.GetEditableProperties())
{
if (prop == PropID::FMD_INTERNET)
{
QComboBox * cmb = findChild<QComboBox *>(kInternetObjectName);
m_feature.SetInternet(feature::InternetFromString(cmb->currentText().toStdString()));
continue;
}
if (prop == PropID::FMD_POSTCODE) // already set above
continue;
QLineEdit * editor = findChild<QLineEdit *>(QString::fromStdString(DebugPrint(prop)));
if (!editor)
continue;
std::string v = editor->text().toStdString();
switch (prop)
{
case PropID::FMD_CUISINE: m_feature.SetCuisines(strings::Tokenize(v, ";")); break;
default:
if (osm::EditableMapObject::IsValidMetadata(prop, v))
m_feature.SetMetadata(prop, std::move(v));
else
{
/// @todo Show error popup?
editor->setFocus();
return;
}
}
}
accept();
}

21
qt/editor_dialog.hpp Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include <QtWidgets/QDialog>
namespace osm
{
class EditableMapObject;
} // namespace osm
class EditorDialog : public QDialog
{
Q_OBJECT
public:
EditorDialog(QWidget * parent, osm::EditableMapObject & emo);
private slots:
void OnSave();
private:
osm::EditableMapObject & m_feature;
};

63
qt/info_dialog.cpp Normal file
View file

@ -0,0 +1,63 @@
#include "qt/info_dialog.hpp"
#include "base/assert.hpp"
#include <QtGui/QIcon>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTextBrowser>
#include <QtWidgets/QVBoxLayout>
namespace qt
{
InfoDialog::InfoDialog(QString const & title, QString const & text, QWidget * parent, QStringList const & buttons)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
{
QIcon icon(":/ui/logo.png");
setWindowIcon(icon);
setWindowTitle(title);
setFocusPolicy(Qt::StrongFocus);
setWindowModality(Qt::WindowModal);
QVBoxLayout * vBox = new QVBoxLayout();
QTextBrowser * browser = new QTextBrowser();
browser->setReadOnly(true);
browser->setOpenLinks(true);
browser->setOpenExternalLinks(true);
browser->setText(text);
vBox->addWidget(browser);
// this horizontal layout is for buttons
QHBoxLayout * hBox = new QHBoxLayout();
hBox->addSpacing(static_cast<int>(browser->width() / 4 * (3.5 - buttons.size())));
for (int i = 0; i < buttons.size(); ++i)
{
QPushButton * button = new QPushButton(buttons[i], this);
switch (i)
{
case 0: connect(button, &QAbstractButton::clicked, this, &InfoDialog::OnButtonClick1); break;
case 1: connect(button, &QAbstractButton::clicked, this, &InfoDialog::OnButtonClick2); break;
case 2: connect(button, &QAbstractButton::clicked, this, &InfoDialog::OnButtonClick3); break;
default: ASSERT(false, ("Only 3 buttons are currently supported in info dialog"));
}
hBox->addWidget(button);
}
vBox->addLayout(hBox);
setLayout(vBox);
}
void InfoDialog::OnButtonClick1()
{
done(1);
}
void InfoDialog::OnButtonClick2()
{
done(2);
}
void InfoDialog::OnButtonClick3()
{
done(3);
}
} // namespace qt

22
qt/info_dialog.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <QtWidgets/QApplication>
#include <QtWidgets/QDialog>
namespace qt
{
/// Simple information dialog with scrollable html content
/// @note exec() returns 0 if dialog was closed or [1..buttons_count] for any button pressed
class InfoDialog : public QDialog
{
Q_OBJECT
public:
explicit InfoDialog(QString const & title, QString const & text, QWidget * parent,
QStringList const & buttons = QStringList());
public Q_SLOTS:
void OnButtonClick1();
void OnButtonClick2();
void OnButtonClick3();
};
} // namespace qt

270
qt/main.cpp Normal file
View file

@ -0,0 +1,270 @@
#include "qt/info_dialog.hpp"
#include "qt/mainwindow.hpp"
#include "qt/screenshoter.hpp"
#include "qt/qt_common/helpers.hpp"
#include "map/framework.hpp"
#include "platform/platform.hpp"
#include "platform/settings.hpp"
#include "coding/reader.hpp"
#include "base/logging.hpp"
#include "base/macros.hpp"
#include "build_style/build_style.h"
#include <QtGlobal>
#include <QtWidgets/QApplication>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include <sstream>
#include <gflags/gflags.h>
DEFINE_string(data_path, "", "Path to data directory.");
DEFINE_string(log_abort_level, base::ToString(base::GetDefaultLogAbortLevel()),
"Log messages severity that causes termination.");
DEFINE_string(resources_path, "", "Path to resources directory.");
DEFINE_string(kml_path, "",
"Activates screenshot mode. Path to a kml file or a directory with kml files to take screenshots.");
DEFINE_string(points, "",
"Activates screenshot mode. Points on the map and zoom level "
"[1..18] in format \"lat,lon,zoom[;lat,lon,zoom]\" or path to a file with points in "
"the same format. Each point and zoom define a place on the map to take screenshot.");
DEFINE_string(rects, "",
"Activates screenshot mode. Rects on the map in format"
"\"lat_leftBottom,lon_leftBottom,lat_rightTop,lon_rightTop"
"[;lat_leftBottom,lon_leftBottom,lat_rightTop,lon_rightTop]\" or path to a file with "
"rects in the same format. Each rect defines a place on the map to take screenshot.");
DEFINE_string(dst_path, "", "Path to a directory to save screenshots.");
DEFINE_string(lang, "", "Device language.");
DEFINE_int32(width, 0, "Screenshot width.");
DEFINE_int32(height, 0, "Screenshot height.");
DEFINE_double(
dpi_scale, 0.0,
"Screenshot dpi scale (mdpi = 1.0, hdpi = 1.5, xhdpiScale = 2.0, 6plus = 2.4, xxhdpi = 3.0, xxxhdpi = 3.5).");
namespace
{
bool ValidateLogAbortLevel(char const * flagname, std::string const & value)
{
if (auto level = base::FromString(value); !level)
{
std::cerr << "Invalid value for --" << flagname << ": " << value << ", must be one of: ";
auto const & names = base::GetLogLevelNames();
for (size_t i = 0; i < names.size(); ++i)
{
if (i != 0)
std::cerr << ", ";
std::cerr << names[i];
}
std::cerr << '\n';
return false;
}
return true;
}
bool const g_logAbortLevelDummy = gflags::RegisterFlagValidator(&FLAGS_log_abort_level, &ValidateLogAbortLevel);
class FinalizeBase
{
public:
~FinalizeBase()
{
// optional - clean allocated data in protobuf library
// useful when using memory and resource leak utilites
// google::protobuf::ShutdownProtobufLibrary();
}
};
#if defined(OMIM_OS_WINDOWS) //&& defined(PROFILER_COMMON)
class InitializeFinalize : public FinalizeBase
{
FILE * m_errFile;
base::ScopedLogLevelChanger const m_debugLog;
public:
InitializeFinalize() : m_debugLog(LDEBUG)
{
// App runs without error console under win32.
m_errFile = ::freopen(".\\mapsme.log", "w", stderr);
//_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_DELAY_FREE_MEM_DF);
//_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
}
~InitializeFinalize() { ::fclose(m_errFile); }
};
#else
typedef FinalizeBase InitializeFinalize;
#endif
} // namespace
int main(int argc, char * argv[])
{
// Our double parsing code (base/string_utils.hpp) needs dots as a floating point delimiters, not commas.
// TODO: Refactor our doubles parsing code to use locale-independent delimiters.
// For example, https://github.com/google/double-conversion can be used.
// See http://dbaron.org/log/20121222-locale for more details.
(void)::setenv("LC_NUMERIC", "C", 1);
Platform & platform = GetPlatform();
LOG(LINFO, ("CoMaps", platform.Version(), "built with QT:", QT_VERSION_STR, "runtime QT:", qVersion(),
"detected CPU cores:", platform.CpuCores()));
gflags::SetUsageMessage("Desktop application.");
gflags::SetVersionString(platform.Version());
gflags::ParseCommandLineFlags(&argc, &argv, true);
if (!FLAGS_resources_path.empty())
platform.SetResourceDir(FLAGS_resources_path);
if (!FLAGS_data_path.empty())
platform.SetWritableDirForTests(FLAGS_data_path);
if (auto const logLevel = base::FromString(FLAGS_log_abort_level); logLevel)
base::g_LogAbortLevel = *logLevel;
else
LOG(LCRITICAL, ("Invalid log level:", FLAGS_log_abort_level));
Q_INIT_RESOURCE(resources_common);
InitializeFinalize mainGuard;
UNUSED_VALUE(mainGuard);
QApplication app(argc, argv);
app.setDesktopFileName("app.comaps.comaps");
platform.SetupMeasurementSystem();
#ifdef BUILD_DESIGNER
QApplication::setApplicationName("CoMaps Designer");
#else
QApplication::setApplicationName("CoMaps");
#endif
#ifdef DEBUG
static bool constexpr developerMode = true;
#else
static bool constexpr developerMode = false;
#endif
bool outvalue;
if (!settings::Get(settings::kDeveloperMode, outvalue))
settings::Set(settings::kDeveloperMode, developerMode);
// Display EULA if needed.
char const * settingsEULA = "EulaAccepted";
bool eulaAccepted = false;
if (!settings::Get(settingsEULA, eulaAccepted) || !eulaAccepted)
{
std::string buffer;
{
ReaderPtr<Reader> reader = platform.GetReader("copyright.html");
reader.ReadAsString(buffer);
}
qt::InfoDialog eulaDialog(QCoreApplication::applicationName(), buffer.c_str(), nullptr, {"Accept", "Decline"});
eulaAccepted = (eulaDialog.exec() == 1);
settings::Set(settingsEULA, eulaAccepted);
}
int returnCode = -1;
if (eulaAccepted) // User has accepted EULA
{
std::unique_ptr<qt::ScreenshotParams> screenshotParams;
if (!FLAGS_lang.empty())
(void)::setenv("LANGUAGE", FLAGS_lang.c_str(), 1);
if (!FLAGS_kml_path.empty() || !FLAGS_points.empty() || !FLAGS_rects.empty())
{
screenshotParams = std::make_unique<qt::ScreenshotParams>();
if (!FLAGS_kml_path.empty())
{
screenshotParams->m_kmlPath = FLAGS_kml_path;
screenshotParams->m_mode = qt::ScreenshotParams::Mode::KmlFiles;
}
else if (!FLAGS_points.empty())
{
screenshotParams->m_points = FLAGS_points;
screenshotParams->m_mode = qt::ScreenshotParams::Mode::Points;
}
else if (!FLAGS_rects.empty())
{
screenshotParams->m_rects = FLAGS_rects;
screenshotParams->m_mode = qt::ScreenshotParams::Mode::Rects;
}
if (!FLAGS_dst_path.empty())
screenshotParams->m_dstPath = FLAGS_dst_path;
if (FLAGS_width > 0)
screenshotParams->m_width = FLAGS_width;
if (FLAGS_height > 0)
screenshotParams->m_height = FLAGS_height;
if (FLAGS_dpi_scale >= df::VisualParams::kMdpiScale && FLAGS_dpi_scale <= df::VisualParams::kXxxhdpiScale)
screenshotParams->m_dpiScale = FLAGS_dpi_scale;
}
qt::common::SetDefaultSurfaceFormat(QApplication::platformName());
FrameworkParams frameworkParams;
#ifdef BUILD_DESIGNER
QString mapcssFilePath;
if (argc >= 2 && platform.IsFileExistsByFullPath(argv[1]))
mapcssFilePath = argv[1];
if (0 == mapcssFilePath.length())
mapcssFilePath = QFileDialog::getOpenFileName(nullptr, "Open style.mapcss file", "~/", "MapCSS Files (*.mapcss)");
if (mapcssFilePath.isEmpty())
return returnCode;
try
{
build_style::BuildIfNecessaryAndApply(mapcssFilePath);
}
catch (std::exception const & e)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Error");
msgBox.setText(e.what());
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
return returnCode;
}
#endif // BUILD_DESIGNER
Framework framework(frameworkParams);
qt::MainWindow w(framework, std::move(screenshotParams), QApplication::primaryScreen()->geometry()
#ifdef BUILD_DESIGNER
,
mapcssFilePath
#endif // BUILD_DESIGNER
);
w.show();
returnCode = QApplication::exec();
}
#ifdef BUILD_DESIGNER
if (build_style::NeedRecalculate && !mapcssFilePath.isEmpty())
{
try
{
build_style::RunRecalculationGeometryScript(mapcssFilePath);
}
catch (std::exception & e)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Error");
msgBox.setText(e.what());
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
}
#endif // BUILD_DESIGNER
LOG_SHORT(LINFO, ("Finished with code", returnCode));
return returnCode;
}

926
qt/mainwindow.cpp Normal file
View file

@ -0,0 +1,926 @@
#include "qt/mainwindow.hpp"
#include "qt/about.hpp"
#include "qt/bookmark_dialog.hpp"
#include "qt/draw_widget.hpp"
#include "qt/mwms_borders_selection.hpp"
#include "qt/osm_auth_dialog.hpp"
#include "qt/popup_menu_holder.hpp"
#include "qt/preferences_dialog.hpp"
#include "qt/qt_common/helpers.hpp"
#include "qt/qt_common/scale_slider.hpp"
#include "qt/routing_settings_dialog.hpp"
#include "qt/screenshoter.hpp"
#include "qt/search_panel.hpp"
#include "platform/platform.hpp"
#include "platform/settings.hpp"
#include "base/assert.hpp"
#include "defines.hpp"
#include <functional>
#include <sstream>
#include "std/target_os.hpp"
#ifdef BUILD_DESIGNER
#include "build_style/build_common.h"
#include "build_style/build_phone_pack.h"
#include "build_style/build_statistics.h"
#include "build_style/build_style.h"
#include "build_style/run_tests.h"
#include "drape_frontend/debug_rect_renderer.hpp"
#endif // BUILD_DESIGNER
#include <QtGui/QCloseEvent>
#include <QtWidgets/QDockWidget>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QToolBar>
#ifdef OMIM_OS_WINDOWS
#include "std/windows.hpp"
#define IDM_ABOUT_DIALOG 1001
#define IDM_PREFERENCES_DIALOG 1002
#endif
#ifndef NO_DOWNLOADER
#include "qt/info_dialog.hpp"
#include "qt/update_dialog.hpp"
#endif // NO_DOWNLOADER
namespace qt
{
namespace
{
void FormatMapSize(uint64_t sizeInBytes, std::string & units, size_t & sizeToDownload)
{
int const mbInBytes = 1024 * 1024;
int const kbInBytes = 1024;
if (sizeInBytes > mbInBytes)
{
sizeToDownload = (sizeInBytes + mbInBytes - 1) / mbInBytes;
units = "MB";
}
else if (sizeInBytes > kbInBytes)
{
sizeToDownload = (sizeInBytes + kbInBytes - 1) / kbInBytes;
units = "KB";
}
else
{
sizeToDownload = sizeInBytes;
units = "B";
}
}
template <class T>
T * CreateBlackControl(QString const & name)
{
T * p = new T(name);
p->setStyleSheet("color: black;");
return p;
}
} // namespace
// Defined in osm_auth_dialog.cpp.
extern char const * kOauthTokenSetting;
MainWindow::MainWindow(Framework & framework, std::unique_ptr<ScreenshotParams> && screenshotParams,
QRect const & screenGeometry
#ifdef BUILD_DESIGNER
,
QString const & mapcssFilePath
#endif
)
: m_locationService(CreateDesktopLocationService(*this))
, m_screenshotMode(screenshotParams != nullptr)
#ifdef BUILD_DESIGNER
, m_mapcssFilePath(mapcssFilePath)
#endif
{
setGeometry(screenGeometry);
if (m_screenshotMode)
{
screenshotParams->m_statusChangedFn = [this](std::string const & state, bool finished)
{
statusBar()->showMessage(QString::fromStdString(state));
if (finished)
QCoreApplication::quit();
};
}
int const width = m_screenshotMode ? static_cast<int>(screenshotParams->m_width) : 0;
int const height = m_screenshotMode ? static_cast<int>(screenshotParams->m_height) : 0;
m_pDrawWidget = new DrawWidget(framework, std::move(screenshotParams), this);
setCentralWidget(m_pDrawWidget);
if (m_screenshotMode)
{
m_pDrawWidget->setFixedSize(width, height);
setFixedSize(width, height + statusBar()->height());
}
connect(m_pDrawWidget, SIGNAL(BeforeEngineCreation()), this, SLOT(OnBeforeEngineCreation()));
CreateCountryStatusControls();
CreateNavigationBar();
CreateSearchBarAndPanel();
QString caption = QCoreApplication::applicationName();
#ifdef BUILD_DESIGNER
if (!m_mapcssFilePath.isEmpty())
caption += QString(" - ") + m_mapcssFilePath;
#endif
setWindowTitle(caption);
setWindowIcon(QIcon(":/ui/logo.png"));
#ifndef OMIM_OS_WINDOWS
QMenu * helpMenu = new QMenu(tr("Help"), this);
menuBar()->addMenu(helpMenu);
helpMenu->addAction(tr("OpenStreetMap Login"), QKeySequence(Qt::CTRL | Qt::Key_O), this, SLOT(OnLoginMenuItem()));
helpMenu->addAction(tr("Upload Edits"), QKeySequence(Qt::CTRL | Qt::Key_U), this, SLOT(OnUploadEditsMenuItem()));
helpMenu->addAction(tr("Preferences"), QKeySequence(Qt::CTRL | Qt::Key_P), this, SLOT(OnPreferences()));
helpMenu->addAction(tr("About"), QKeySequence(Qt::Key_F1), this, SLOT(OnAbout()));
helpMenu->addAction(tr("Exit"), QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close()));
#else
{
// create items in the system menu
HMENU menu = ::GetSystemMenu((HWND)winId(), FALSE);
MENUITEMINFOA item;
item.cbSize = sizeof(MENUITEMINFOA);
item.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STRING;
item.fType = MFT_STRING;
item.wID = IDM_PREFERENCES_DIALOG;
QByteArray const prefsStr = tr("Preferences...").toLocal8Bit();
item.dwTypeData = const_cast<char *>(prefsStr.data());
item.cch = prefsStr.size();
::InsertMenuItemA(menu, ::GetMenuItemCount(menu) - 1, TRUE, &item);
item.wID = IDM_ABOUT_DIALOG;
QByteArray const aboutStr = tr("About...").toLocal8Bit();
item.dwTypeData = const_cast<char *>(aboutStr.data());
item.cch = aboutStr.size();
::InsertMenuItemA(menu, ::GetMenuItemCount(menu) - 1, TRUE, &item);
item.fType = MFT_SEPARATOR;
::InsertMenuItemA(menu, ::GetMenuItemCount(menu) - 1, TRUE, &item);
}
#endif
// Always show on full screen.
showMaximized();
#ifndef NO_DOWNLOADER
// Show intro dialog if necessary
bool bShow = true;
std::string const showWelcome = "ShowWelcome";
settings::TryGet(showWelcome, bShow);
if (bShow)
{
bool bShowUpdateDialog = true;
std::string text;
try
{
ReaderPtr<Reader> reader = GetPlatform().GetReader("welcome.html");
reader.ReadAsString(text);
}
catch (...)
{}
if (!text.empty())
{
InfoDialog welcomeDlg(QString("Welcome to ") + caption, text.c_str(), this, QStringList(tr("Download Maps")));
if (welcomeDlg.exec() == QDialog::Rejected)
bShowUpdateDialog = false;
}
settings::Set("ShowWelcome", false);
if (bShowUpdateDialog)
ShowUpdateDialog();
}
#endif // NO_DOWNLOADER
m_pDrawWidget->UpdateAfterSettingsChanged();
RoutingSettings::LoadSession(m_pDrawWidget->GetFramework());
}
#if defined(OMIM_OS_WINDOWS)
bool MainWindow::nativeEvent(QByteArray const & eventType, void * message, qintptr * result)
{
MSG * msg = static_cast<MSG *>(message);
if (msg->message == WM_SYSCOMMAND)
{
switch (msg->wParam)
{
case IDM_PREFERENCES_DIALOG:
OnPreferences();
*result = 0;
return true;
case IDM_ABOUT_DIALOG:
OnAbout();
*result = 0;
return true;
}
}
return QMainWindow::nativeEvent(eventType, message, result);
}
#endif
void MainWindow::LocationStateModeChanged(location::EMyPositionMode mode)
{
if (mode == location::PendingPosition)
{
m_locationService->Start();
m_pMyPositionAction->setIcon(QIcon(":/navig64/location-search.png"));
m_pMyPositionAction->setToolTip(tr("Looking for position..."));
return;
}
m_pMyPositionAction->setIcon(QIcon(":/navig64/location.png"));
m_pMyPositionAction->setToolTip(tr("My Position"));
}
void MainWindow::CreateNavigationBar()
{
QToolBar * pToolBar = new QToolBar(tr("Navigation Bar"), this);
pToolBar->setOrientation(Qt::Vertical);
pToolBar->setIconSize(QSize(32, 32));
{
m_pDrawWidget->BindHotkeys(*this);
// Add navigation hot keys.
qt::common::Hotkey const hotkeys[] = {{Qt::Key_A, SLOT(ShowAll())},
// Use CMD+n (New Item hotkey) to activate Create Feature mode.
{Qt::Key_Escape, SLOT(ChoosePositionModeDisable())}};
for (auto const & hotkey : hotkeys)
{
QAction * pAct = new QAction(this);
pAct->setShortcut(QKeySequence(hotkey.m_key));
connect(pAct, SIGNAL(triggered()), m_pDrawWidget, hotkey.m_slot);
addAction(pAct);
}
}
{
using namespace std::placeholders;
m_layers = new PopupMenuHolder(this);
/// @todo Uncomment when we will integrate a traffic provider.
// m_layers->addAction(QIcon(":/navig64/traffic.png"), tr("Traffic"),
// std::bind(&MainWindow::OnLayerEnabled, this, LayerType::TRAFFIC), true);
// m_layers->setChecked(LayerType::TRAFFIC, m_pDrawWidget->GetFramework().LoadTrafficEnabled());
m_layers->addAction(QIcon(":/navig64/subway.png"), tr("Public transport"),
std::bind(&MainWindow::OnLayerEnabled, this, LayerType::TRANSIT), true);
m_layers->setChecked(LayerType::TRANSIT, m_pDrawWidget->GetFramework().LoadTransitSchemeEnabled());
m_layers->addAction(QIcon(":/navig64/isolines.png"), tr("Isolines"),
std::bind(&MainWindow::OnLayerEnabled, this, LayerType::ISOLINES), true);
m_layers->setChecked(LayerType::ISOLINES, m_pDrawWidget->GetFramework().LoadIsolinesEnabled());
m_layers->addAction(QIcon(":/navig64/isolines.png"), tr("Outdoors"),
std::bind(&MainWindow::OnLayerEnabled, this, LayerType::OUTDOORS), true);
m_layers->setChecked(LayerType::OUTDOORS, m_pDrawWidget->GetFramework().LoadOutdoorsEnabled());
pToolBar->addWidget(m_layers->create());
m_layers->setMainIcon(QIcon(":/navig64/layers.png"));
pToolBar->addSeparator();
pToolBar->addAction(QIcon(":/navig64/bookmark.png"),
tr("Show bookmarks and tracks; use ALT + RMB to add a bookmark"), this,
SLOT(OnBookmarksAction()));
pToolBar->addSeparator();
#ifndef BUILD_DESIGNER
m_routing = new PopupMenuHolder(this);
// The order should be the same as in "enum class RouteMarkType".
m_routing->addAction(QIcon(":/navig64/point-start.png"), tr("Start point"),
std::bind(&MainWindow::OnRoutePointSelected, this, RouteMarkType::Start), false);
m_routing->addAction(QIcon(":/navig64/point-intermediate.png"), tr("Intermediate point"),
std::bind(&MainWindow::OnRoutePointSelected, this, RouteMarkType::Intermediate), false);
m_routing->addAction(QIcon(":/navig64/point-finish.png"), tr("Finish point"),
std::bind(&MainWindow::OnRoutePointSelected, this, RouteMarkType::Finish), false);
QToolButton * toolBtn = m_routing->create();
toolBtn->setToolTip(tr("Select mode and use SHIFT + LMB to set point"));
pToolBar->addWidget(toolBtn);
m_routing->setCurrent(m_pDrawWidget->GetRoutePointAddMode());
QAction * act =
pToolBar->addAction(QIcon(":/navig64/routing.png"), tr("Follow route"), this, SLOT(OnFollowRoute()));
act->setToolTip(tr("Build route and use ALT + LMB to emulate current position"));
pToolBar->addAction(QIcon(":/navig64/clear-route.png"), tr("Clear route"), this, SLOT(OnClearRoute()));
pToolBar->addAction(QIcon(":/navig64/settings-routing.png"), tr("Routing settings"), this,
SLOT(OnRoutingSettings()));
pToolBar->addSeparator();
m_pCreateFeatureAction =
pToolBar->addAction(QIcon(":/navig64/select.png"), tr("Create Feature"), this, SLOT(OnCreateFeatureClicked()));
m_pCreateFeatureAction->setCheckable(true);
m_pCreateFeatureAction->setToolTip(tr("Push to select position, next push to create Feature"));
m_pCreateFeatureAction->setShortcut(QKeySequence::New);
pToolBar->addSeparator();
m_selection = new PopupMenuHolder(this);
// The order should be the same as in "enum class SelectionMode".
m_selection->addAction(QIcon(":/navig64/selectmode.png"), tr("Roads selection mode"),
std::bind(&MainWindow::OnSwitchSelectionMode, this, SelectionMode::Features), true);
m_selection->addAction(QIcon(":/navig64/city_boundaries.png"), tr("City boundaries selection mode"),
std::bind(&MainWindow::OnSwitchSelectionMode, this, SelectionMode::CityBoundaries), true);
m_selection->addAction(QIcon(":/navig64/city_roads.png"), tr("City roads selection mode"),
std::bind(&MainWindow::OnSwitchSelectionMode, this, SelectionMode::CityRoads), true);
m_selection->addAction(QIcon(":/navig64/test.png"), tr("Cross MWM segments selection mode"),
std::bind(&MainWindow::OnSwitchSelectionMode, this, SelectionMode::CrossMwmSegments), true);
m_selection->addAction(QIcon(":/navig64/borders_selection.png"), tr("MWMs borders selection mode"), this,
SLOT(OnSwitchMwmsBordersSelectionMode()), true);
toolBtn = m_selection->create();
toolBtn->setToolTip(tr("Select mode and use RMB to define selection box"));
pToolBar->addWidget(toolBtn);
pToolBar->addAction(QIcon(":/navig64/clear.png"), tr("Clear selection"), this, SLOT(OnClearSelection()));
pToolBar->addSeparator();
#endif // NOT BUILD_DESIGNER
// Add search button with "checked" behavior.
m_pSearchAction =
pToolBar->addAction(QIcon(":/navig64/search.png"), tr("Offline Search"), this, SLOT(OnSearchButtonClicked()));
m_pSearchAction->setCheckable(true);
m_pSearchAction->setShortcut(QKeySequence::Find);
m_rulerAction = pToolBar->addAction(QIcon(":/navig64/ruler.png"), tr("Ruler"), this, SLOT(OnRulerEnabled()));
m_rulerAction->setToolTip(tr("Check this button and use ALT + LMB to set points"));
m_rulerAction->setCheckable(true);
m_rulerAction->setChecked(false);
pToolBar->addSeparator();
// add my position button with "checked" behavior
m_pMyPositionAction =
pToolBar->addAction(QIcon(":/navig64/location.png"), tr("My Position"), this, SLOT(OnMyPosition()));
m_pMyPositionAction->setCheckable(true);
#ifdef BUILD_DESIGNER
// Add "Build style" button
if (!m_mapcssFilePath.isEmpty())
{
m_pBuildStyleAction =
pToolBar->addAction(QIcon(":/navig64/run.png"), tr("Build style"), this, SLOT(OnBuildStyle()));
m_pBuildStyleAction->setCheckable(false);
m_pBuildStyleAction->setToolTip(tr("Build style"));
m_pRecalculateGeomIndex = pToolBar->addAction(QIcon(":/navig64/geom.png"), tr("Recalculate geometry index"), this,
SLOT(OnRecalculateGeomIndex()));
m_pRecalculateGeomIndex->setCheckable(false);
m_pRecalculateGeomIndex->setToolTip(tr("Recalculate geometry index"));
}
// Add "Debug style" button
m_pDrawDebugRectAction =
pToolBar->addAction(QIcon(":/navig64/bug.png"), tr("Debug style"), this, SLOT(OnDebugStyle()));
m_pDrawDebugRectAction->setCheckable(true);
m_pDrawDebugRectAction->setChecked(false);
m_pDrawDebugRectAction->setToolTip(tr("Debug style"));
m_pDrawWidget->GetFramework().EnableDebugRectRendering(false);
// Add "Get statistics" button
m_pGetStatisticsAction =
pToolBar->addAction(QIcon(":/navig64/chart.png"), tr("Get statistics"), this, SLOT(OnGetStatistics()));
m_pGetStatisticsAction->setCheckable(false);
m_pGetStatisticsAction->setToolTip(tr("Get statistics"));
// Add "Run tests" button
m_pRunTestsAction = pToolBar->addAction(QIcon(":/navig64/test.png"), tr("Run tests"), this, SLOT(OnRunTests()));
m_pRunTestsAction->setCheckable(false);
m_pRunTestsAction->setToolTip(tr("Run tests"));
// Add "Build phone package" button
m_pBuildPhonePackAction = pToolBar->addAction(QIcon(":/navig64/phonepack.png"), tr("Build phone package"), this,
SLOT(OnBuildPhonePackage()));
m_pBuildPhonePackAction->setCheckable(false);
m_pBuildPhonePackAction->setToolTip(tr("Build phone package"));
#endif // BUILD_DESIGNER
}
pToolBar->addSeparator();
qt::common::ScaleSlider::Embed(Qt::Vertical, *pToolBar, *m_pDrawWidget);
#ifndef NO_DOWNLOADER
pToolBar->addSeparator();
pToolBar->addAction(QIcon(":/navig64/download.png"), tr("Download Maps"), this, SLOT(ShowUpdateDialog()));
#endif // NO_DOWNLOADER
if (m_screenshotMode)
pToolBar->setVisible(false);
addToolBar(Qt::RightToolBarArea, pToolBar);
}
Framework & MainWindow::GetFramework() const
{
return m_pDrawWidget->GetFramework();
}
void MainWindow::CreateCountryStatusControls()
{
QHBoxLayout * mainLayout = new QHBoxLayout();
m_downloadButton = CreateBlackControl<QPushButton>("Download");
mainLayout->addWidget(m_downloadButton, 0, Qt::AlignHCenter);
m_downloadButton->setVisible(false);
connect(m_downloadButton, &QAbstractButton::released, this, &MainWindow::OnDownloadClicked);
m_retryButton = CreateBlackControl<QPushButton>("Retry downloading");
mainLayout->addWidget(m_retryButton, 0, Qt::AlignHCenter);
m_retryButton->setVisible(false);
connect(m_retryButton, &QAbstractButton::released, this, &MainWindow::OnRetryDownloadClicked);
m_downloadingStatusLabel = CreateBlackControl<QLabel>("Downloading");
mainLayout->addWidget(m_downloadingStatusLabel, 0, Qt::AlignHCenter);
m_downloadingStatusLabel->setVisible(false);
m_pDrawWidget->setLayout(mainLayout);
auto const OnCountryChanged = [this](storage::CountryId const & countryId)
{
m_downloadButton->setVisible(false);
m_retryButton->setVisible(false);
m_downloadingStatusLabel->setVisible(false);
m_lastCountry = countryId;
// Called by Framework in World zoom level.
if (countryId.empty())
return;
auto const & storage = GetFramework().GetStorage();
auto status = storage.CountryStatusEx(countryId);
auto const & countryName = countryId;
if (status == storage::Status::NotDownloaded)
{
m_downloadButton->setVisible(true);
std::string units;
size_t sizeToDownload = 0;
FormatMapSize(storage.CountrySizeInBytes(countryId).second, units, sizeToDownload);
std::stringstream str;
str << "Download (" << countryName << ") " << sizeToDownload << units;
m_downloadButton->setText(str.str().c_str());
}
else if (status == storage::Status::Downloading)
{
m_downloadingStatusLabel->setVisible(true);
}
else if (status == storage::Status::InQueue)
{
m_downloadingStatusLabel->setVisible(true);
std::stringstream str;
str << countryName << " is waiting for downloading";
m_downloadingStatusLabel->setText(str.str().c_str());
}
else if (status != storage::Status::OnDisk && status != storage::Status::OnDiskOutOfDate)
{
m_retryButton->setVisible(true);
std::stringstream str;
str << "Retry to download " << countryName;
m_retryButton->setText(str.str().c_str());
}
};
GetFramework().SetCurrentCountryChangedListener(OnCountryChanged);
GetFramework().GetStorage().Subscribe(
[this, onChanged = std::move(OnCountryChanged)](storage::CountryId const & countryId)
{
// Storage also calls notifications for parents, but we are interested in leafs only.
if (GetFramework().GetStorage().IsLeaf(countryId))
onChanged(countryId);
}, [this](storage::CountryId const & countryId, downloader::Progress const & progress)
{
std::stringstream str;
str << "Downloading (" << countryId << ") " << progress.m_bytesDownloaded * 100 / progress.m_bytesTotal << "%";
m_downloadingStatusLabel->setText(str.str().c_str());
});
}
void MainWindow::OnAbout()
{
AboutDialog dlg(this);
dlg.exec();
}
void MainWindow::OnLocationError(location::TLocationError errorCode)
{
switch (errorCode)
{
case location::EDenied: [[fallthrough]];
case location::ETimeout: [[fallthrough]];
case location::EUnknown:
{
if (m_pDrawWidget && m_pMyPositionAction)
m_pMyPositionAction->setEnabled(false);
break;
}
default: ASSERT(false, ("Not handled location notification:", errorCode)); break;
}
if (m_pDrawWidget != nullptr)
m_pDrawWidget->GetFramework().OnLocationError(errorCode);
}
void MainWindow::OnLocationUpdated(location::GpsInfo const & info)
{
m_pDrawWidget->GetFramework().OnLocationUpdate(info);
}
void MainWindow::OnMyPosition()
{
if (m_pMyPositionAction->isEnabled())
m_pDrawWidget->GetFramework().SwitchMyPositionNextMode();
}
void MainWindow::OnCreateFeatureClicked()
{
if (m_pCreateFeatureAction->isChecked())
{
m_pDrawWidget->ChoosePositionModeEnable();
}
else
{
m_pDrawWidget->ChoosePositionModeDisable();
m_pDrawWidget->CreateFeature();
}
}
void MainWindow::OnSwitchSelectionMode(SelectionMode mode)
{
if (m_selection->isChecked(mode))
{
m_selection->setCurrent(mode);
m_pDrawWidget->SetSelectionMode(mode);
}
else
OnClearSelection();
}
void MainWindow::OnSwitchMwmsBordersSelectionMode()
{
MwmsBordersSelection dlg(this);
auto const response = dlg.ShowModal();
if (response == SelectionMode::Cancelled)
{
m_pDrawWidget->DropSelectionIfMWMBordersMode();
return;
}
m_selection->setCurrent(SelectionMode::MWMBorders);
m_pDrawWidget->SetSelectionMode(response);
}
void MainWindow::OnClearSelection()
{
m_pDrawWidget->GetFramework().GetDrapeApi().Clear();
m_pDrawWidget->SetSelectionMode({});
m_selection->setMainIcon({});
}
void MainWindow::OnSearchButtonClicked()
{
if (m_pSearchAction->isChecked())
m_Docks[0]->show();
else
m_Docks[0]->hide();
}
void MainWindow::OnLoginMenuItem()
{
OsmAuthDialog dlg(this);
dlg.exec();
}
void MainWindow::OnUploadEditsMenuItem()
{
std::string token;
if (!settings::Get(kOauthTokenSetting, token) || token.empty())
{
OnLoginMenuItem();
}
else
{
auto & editor = osm::Editor::Instance();
if (editor.HaveMapEditsOrNotesToUpload())
editor.UploadChanges(token, {{"created_by", "CoMaps " OMIM_OS_NAME}});
}
}
void MainWindow::OnBeforeEngineCreation()
{
m_pDrawWidget->GetFramework().SetMyPositionModeListener([this](location::EMyPositionMode mode, bool /*routingActive*/)
{ LocationStateModeChanged(mode); });
}
void MainWindow::OnPreferences()
{
Framework & framework = m_pDrawWidget->GetFramework();
PreferencesDialog dlg(this, framework);
dlg.exec();
framework.EnterForeground();
}
#ifdef BUILD_DESIGNER
void MainWindow::OnBuildStyle()
{
try
{
build_style::BuildAndApply(m_mapcssFilePath);
m_pDrawWidget->RefreshDrawingRules();
bool enabled;
if (!settings::Get(kEnabledAutoRegenGeomIndex, enabled))
enabled = false;
if (enabled)
{
build_style::NeedRecalculate = true;
QMainWindow::close();
}
}
catch (std::exception & e)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Error");
msgBox.setText(e.what());
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
}
void MainWindow::OnRecalculateGeomIndex()
{
try
{
QMessageBox msgBox;
msgBox.setWindowTitle("Warning");
msgBox.setText("Geometry index will be regenerated. It can take a while.\nApplication may be closed and reopened!");
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::Yes);
if (msgBox.exec() == QMessageBox::Yes)
{
build_style::NeedRecalculate = true;
QMainWindow::close();
}
}
catch (std::exception & e)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Error");
msgBox.setText(e.what());
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
}
void MainWindow::OnDebugStyle()
{
bool const checked = m_pDrawDebugRectAction->isChecked();
m_pDrawWidget->GetFramework().EnableDebugRectRendering(checked);
m_pDrawWidget->RefreshDrawingRules();
}
void MainWindow::OnGetStatistics()
{
try
{
QString text = build_style::GetCurrentStyleStatistics();
InfoDialog dlg(QString("Style statistics"), text, NULL);
dlg.exec();
}
catch (std::exception & e)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Error");
msgBox.setText(e.what());
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
}
void MainWindow::OnRunTests()
{
try
{
std::pair<bool, QString> res = build_style::RunCurrentStyleTests();
InfoDialog dlg(QString("Style tests: ") + (res.first ? "OK" : "FAILED"), res.second, NULL);
dlg.exec();
}
catch (std::exception & e)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Error");
msgBox.setText(e.what());
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
}
void MainWindow::OnBuildPhonePackage()
{
try
{
char const * const kStylesFolder = "styles";
char const * const kClearStyleFolder = "clear";
QString const targetDir = QFileDialog::getExistingDirectory(nullptr, "Choose output directory");
if (targetDir.isEmpty())
return;
auto outDir = QDir(JoinPathQt({targetDir, kStylesFolder}));
if (outDir.exists())
{
QMessageBox msgBox;
msgBox.setWindowTitle("Warning");
msgBox.setText(QString("Folder ") + outDir.absolutePath() + " will be deleted?");
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
auto result = msgBox.exec();
if (result == QMessageBox::No)
throw std::runtime_error(std::string("Target directory exists: ") + outDir.absolutePath().toStdString());
}
QString const stylesDir = JoinPathQt({m_mapcssFilePath, "..", "..", ".."});
if (!QDir(JoinPathQt({stylesDir, kClearStyleFolder})).exists())
throw std::runtime_error(std::string("Styles folder is not found in ") + stylesDir.toStdString());
QString text = build_style::RunBuildingPhonePack(stylesDir, targetDir);
text.append("\nMobile device style package is in the directory: ");
text.append(JoinPathQt({targetDir, kStylesFolder}));
text.append(". Copy it to your mobile device.\n");
InfoDialog dlg(QString("Building phone pack"), text, nullptr);
dlg.exec();
}
catch (std::exception & e)
{
QMessageBox msgBox;
msgBox.setWindowTitle("Error");
msgBox.setText(e.what());
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
msgBox.exec();
}
}
#endif // BUILD_DESIGNER
#ifndef NO_DOWNLOADER
void MainWindow::ShowUpdateDialog()
{
UpdateDialog dlg(this, m_pDrawWidget->GetFramework());
dlg.ShowModal();
m_pDrawWidget->update();
}
#endif // NO_DOWNLOADER
void MainWindow::CreateSearchBarAndPanel()
{
CreatePanelImpl(0, Qt::RightDockWidgetArea, tr("Search"), QKeySequence(), 0);
SearchPanel * panel = new SearchPanel(m_pDrawWidget, m_Docks[0]);
m_Docks[0]->setWidget(panel);
}
void MainWindow::CreatePanelImpl(size_t i, Qt::DockWidgetArea area, QString const & name, QKeySequence const & hotkey,
char const * slot)
{
ASSERT_LESS(i, m_Docks.size(), ());
m_Docks[i] = new QDockWidget(name, this);
addDockWidget(area, m_Docks[i]);
// hide by default
m_Docks[i]->hide();
// register a hotkey to show panel
if (slot && !hotkey.isEmpty())
{
QAction * pAct = new QAction(this);
pAct->setShortcut(hotkey);
connect(pAct, SIGNAL(triggered()), this, slot);
addAction(pAct);
}
}
void MainWindow::closeEvent(QCloseEvent * e)
{
m_pDrawWidget->PrepareShutdown();
e->accept();
}
void MainWindow::OnDownloadClicked()
{
GetFramework().GetStorage().DownloadNode(m_lastCountry);
}
void MainWindow::OnRetryDownloadClicked()
{
GetFramework().GetStorage().RetryDownloadNode(m_lastCountry);
}
void MainWindow::SetLayerEnabled(LayerType type, bool enable)
{
auto & frm = m_pDrawWidget->GetFramework();
switch (type)
{
// @todo Uncomment when we will integrate a traffic provider.
// case LayerType::TRAFFIC:
// frm.GetTrafficManager().SetEnabled(enable);
// frm.SaveTrafficEnabled(enable);
// break;
case LayerType::TRANSIT:
frm.GetTransitManager().EnableTransitSchemeMode(enable);
frm.SaveTransitSchemeEnabled(enable);
break;
case LayerType::ISOLINES:
frm.GetIsolinesManager().SetEnabled(enable);
frm.SaveIsolinesEnabled(enable);
break;
case LayerType::OUTDOORS:
frm.SaveOutdoorsEnabled(enable);
if (enable)
m_pDrawWidget->SetMapStyleToOutdoors();
else
m_pDrawWidget->SetMapStyleToDefault();
break;
}
}
void MainWindow::OnLayerEnabled(LayerType layer)
{
SetLayerEnabled(layer, m_layers->isChecked(layer));
}
void MainWindow::OnRulerEnabled()
{
m_pDrawWidget->SetRuler(m_rulerAction->isChecked());
}
void MainWindow::OnRoutePointSelected(RouteMarkType type)
{
m_routing->setCurrent(type);
m_pDrawWidget->SetRoutePointAddMode(type);
}
void MainWindow::OnFollowRoute()
{
m_pDrawWidget->FollowRoute();
}
void MainWindow::OnClearRoute()
{
m_pDrawWidget->ClearRoute();
}
void MainWindow::OnRoutingSettings()
{
RoutingSettings dlg(this, m_pDrawWidget->GetFramework());
dlg.ShowModal();
}
void MainWindow::OnBookmarksAction()
{
BookmarkDialog dlg(this, m_pDrawWidget->GetFramework());
dlg.ShowModal();
m_pDrawWidget->update();
}
} // namespace qt

152
qt/mainwindow.hpp Normal file
View file

@ -0,0 +1,152 @@
#pragma once
#include "qt/selection.hpp"
#include "map/routing_mark.hpp"
#include "storage/storage_defines.hpp"
#include "platform/location.hpp"
#include "platform/location_service/location_service.hpp"
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <array>
#include <memory>
#include <string>
class Framework;
class QDockWidget;
class QLabel;
class QPushButton;
namespace search
{
class Result;
}
namespace qt
{
class DrawWidget;
class PopupMenuHolder;
struct ScreenshotParams;
class MainWindow
: public QMainWindow
, location::LocationObserver
{
DrawWidget * m_pDrawWidget = nullptr;
// TODO(mgsergio): Make indexing more informative.
std::array<QDockWidget *, 1> m_Docks;
QPushButton * m_downloadButton = nullptr;
QPushButton * m_retryButton = nullptr;
QLabel * m_downloadingStatusLabel = nullptr;
storage::CountryId m_lastCountry;
std::unique_ptr<location::LocationService> const m_locationService;
bool const m_screenshotMode;
QAction * m_pMyPositionAction = nullptr;
QAction * m_pCreateFeatureAction = nullptr;
QAction * m_pSearchAction = nullptr;
QAction * m_rulerAction = nullptr;
enum LayerType : uint8_t
{
/// @todo Uncomment when we will integrate a traffic provider.
// TRAFFIC = 0,
TRANSIT = 0, // Metro scheme
ISOLINES,
OUTDOORS,
};
PopupMenuHolder * m_layers = nullptr;
PopupMenuHolder * m_routing = nullptr;
PopupMenuHolder * m_selection = nullptr;
#ifdef BUILD_DESIGNER
QString const m_mapcssFilePath = nullptr;
QAction * m_pBuildStyleAction = nullptr;
QAction * m_pRecalculateGeomIndex = nullptr;
QAction * m_pDrawDebugRectAction = nullptr;
QAction * m_pGetStatisticsAction = nullptr;
QAction * m_pRunTestsAction = nullptr;
QAction * m_pBuildPhonePackAction = nullptr;
#endif // BUILD_DESIGNER
Q_OBJECT
public:
MainWindow(Framework & framework, std::unique_ptr<ScreenshotParams> && screenshotParams, QRect const & screenGeometry
#ifdef BUILD_DESIGNER
,
QString const & mapcssFilePath = QString()
#endif
);
protected:
Framework & GetFramework() const;
void OnLocationError(location::TLocationError errorCode) override;
void OnLocationUpdated(location::GpsInfo const & info) override;
void LocationStateModeChanged(location::EMyPositionMode mode);
void CreatePanelImpl(size_t i, Qt::DockWidgetArea area, QString const & name, QKeySequence const & hotkey,
char const * slot);
void CreateNavigationBar();
void CreateSearchBarAndPanel();
void CreateCountryStatusControls();
void SetLayerEnabled(LayerType type, bool enable);
#if defined(OMIM_OS_WINDOWS)
/// to handle menu messages
bool nativeEvent(QByteArray const & eventType, void * message, qintptr * result) override;
#endif
void closeEvent(QCloseEvent * e) override;
protected Q_SLOTS:
#ifndef NO_DOWNLOADER
void ShowUpdateDialog();
#endif // NO_DOWNLOADER
void OnPreferences();
void OnAbout();
void OnMyPosition();
void OnCreateFeatureClicked();
void OnSearchButtonClicked();
void OnLoginMenuItem();
void OnUploadEditsMenuItem();
void OnBeforeEngineCreation();
void OnDownloadClicked();
void OnRetryDownloadClicked();
void OnSwitchSelectionMode(SelectionMode mode);
void OnSwitchMwmsBordersSelectionMode();
void OnClearSelection();
void OnLayerEnabled(LayerType layer);
void OnRulerEnabled();
void OnRoutePointSelected(RouteMarkType type);
void OnFollowRoute();
void OnClearRoute();
void OnRoutingSettings();
void OnBookmarksAction();
#ifdef BUILD_DESIGNER
void OnBuildStyle();
void OnRecalculateGeomIndex();
void OnDebugStyle();
void OnGetStatistics();
void OnRunTests();
void OnBuildPhonePackage();
#endif // BUILD_DESIGNER
};
} // namespace qt

View file

@ -0,0 +1,115 @@
#include "qt/mwms_borders_selection.hpp"
#include "base/assert.hpp"
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QRadioButton>
#include <QtWidgets/QSplitter>
#include <QtWidgets/QVBoxLayout>
namespace qt
{
MwmsBordersSelection::MwmsBordersSelection(QWidget * parent) : QDialog(parent)
{
setWindowTitle("Mwms borders selection settings");
auto * grid = new QGridLayout;
grid->addWidget(CreateSourceChoosingGroup(), 0, 0);
grid->addWidget(CreateViewTypeGroup(), 1, 0);
grid->addWidget(CreateButtonBoxGroup(), 2, 0);
setLayout(grid);
}
QGroupBox * MwmsBordersSelection::CreateButtonBoxGroup()
{
auto * groupBox = new QGroupBox();
auto * buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
auto * vbox = new QVBoxLayout;
vbox->addWidget(buttonBox);
groupBox->setLayout(vbox);
groupBox->setFlat(true);
QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
return groupBox;
}
SelectionMode MwmsBordersSelection::ShowModal()
{
if (exec() != QDialog::Accepted)
return SelectionMode::Cancelled;
if (m_radioBordersFromData->isChecked())
{
if (m_radioJustBorders->isChecked())
return SelectionMode::MwmsBordersByPolyFiles;
if (m_radioWithPoints->isChecked())
return SelectionMode::MwmsBordersWithVerticesByPolyFiles;
if (m_radioBoundingBox->isChecked())
return SelectionMode::BoundingBoxByPolyFiles;
UNREACHABLE();
}
if (m_radioBordersFromPackedPolygon->isChecked())
{
if (m_radioJustBorders->isChecked())
return SelectionMode::MwmsBordersByPackedPolygon;
if (m_radioWithPoints->isChecked())
return SelectionMode::MwmsBordersWithVerticesByPackedPolygon;
if (m_radioBoundingBox->isChecked())
return SelectionMode::BoundingBoxByPackedPolygon;
UNREACHABLE();
}
UNREACHABLE();
}
QGroupBox * MwmsBordersSelection::CreateSourceChoosingGroup()
{
auto * groupBox = new QGroupBox();
m_radioBordersFromPackedPolygon = new QRadioButton(tr("Get borders from packed_polygon.bin"));
m_radioBordersFromData = new QRadioButton(tr("Get borders from *.poly files"));
m_radioBordersFromPackedPolygon->setChecked(true);
auto * vbox = new QVBoxLayout;
vbox->addWidget(m_radioBordersFromPackedPolygon);
vbox->addWidget(m_radioBordersFromData);
groupBox->setLayout(vbox);
return groupBox;
}
QGroupBox * MwmsBordersSelection::CreateViewTypeGroup()
{
auto * groupBox = new QGroupBox();
m_radioWithPoints = new QRadioButton(tr("Show borders with vertices"));
m_radioJustBorders = new QRadioButton(tr("Show just borders"));
m_radioBoundingBox = new QRadioButton(tr("Show bounding box"));
m_radioWithPoints->setChecked(true);
auto * vbox = new QVBoxLayout;
vbox->addWidget(m_radioWithPoints);
vbox->addWidget(m_radioJustBorders);
vbox->addWidget(m_radioBoundingBox);
groupBox->setLayout(vbox);
return groupBox;
}
} // namespace qt

View file

@ -0,0 +1,36 @@
#pragma once
#include "qt/selection.hpp"
#include <string>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDialog>
#include <QtWidgets/QFormLayout>
#include <QtWidgets/QGroupBox>
#include <QtWidgets/QRadioButton>
class Framework;
namespace qt
{
class MwmsBordersSelection : public QDialog
{
public:
MwmsBordersSelection(QWidget * parent);
SelectionMode ShowModal();
private:
QGroupBox * CreateSourceChoosingGroup();
QGroupBox * CreateViewTypeGroup();
QGroupBox * CreateButtonBoxGroup();
QRadioButton * m_radioBordersFromPackedPolygon;
QRadioButton * m_radioBordersFromData;
QRadioButton * m_radioWithPoints;
QRadioButton * m_radioJustBorders;
QRadioButton * m_radioBoundingBox;
};
} // namespace qt

131
qt/osm_auth_dialog.cpp Normal file
View file

@ -0,0 +1,131 @@
#include "qt/osm_auth_dialog.hpp"
#include "editor/osm_auth.hpp"
#include "platform/settings.hpp"
#include <string>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QVBoxLayout>
using namespace osm;
namespace qt
{
char const * kTokenKeySetting = "OsmTokenKey";
char const * kTokenSecretSetting = "OsmTokenSecret";
char const * kLoginDialogTitle = "OpenStreetMap Login";
char const * kOauthTokenSetting = "OsmOauthToken";
OsmAuthDialog::OsmAuthDialog(QWidget * parent)
{
std::string token;
bool const isLoginDialog = !settings::Get(kOauthTokenSetting, token) || token.empty();
QVBoxLayout * vLayout = new QVBoxLayout(parent);
QHBoxLayout * loginRow = new QHBoxLayout();
loginRow->addWidget(new QLabel(tr("Username/email:")));
QLineEdit * loginLineEdit = new QLineEdit();
loginLineEdit->setObjectName("login");
if (!isLoginDialog)
loginLineEdit->setEnabled(false);
loginRow->addWidget(loginLineEdit);
vLayout->addLayout(loginRow);
QHBoxLayout * passwordRow = new QHBoxLayout();
passwordRow->addWidget(new QLabel(tr("Password:")));
QLineEdit * passwordLineEdit = new QLineEdit();
passwordLineEdit->setEchoMode(QLineEdit::Password);
passwordLineEdit->setObjectName("password");
if (!isLoginDialog)
passwordLineEdit->setEnabled(false);
passwordRow->addWidget(passwordLineEdit);
vLayout->addLayout(passwordRow);
QPushButton * actionButton = new QPushButton(isLoginDialog ? tr("Login") : tr("Logout"));
actionButton->setObjectName("button");
connect(actionButton, &QAbstractButton::clicked, this, &OsmAuthDialog::OnAction);
QPushButton * closeButton = new QPushButton(tr("Close"));
connect(closeButton, &QAbstractButton::clicked, this, &QWidget::close);
QHBoxLayout * buttonsLayout = new QHBoxLayout();
buttonsLayout->addWidget(closeButton);
buttonsLayout->addWidget(actionButton);
vLayout->addLayout(buttonsLayout);
setLayout(vLayout);
setWindowTitle(tr(kLoginDialogTitle));
}
void SwitchToLogin(QDialog * dlg)
{
dlg->findChild<QLineEdit *>("login")->setEnabled(true);
dlg->findChild<QLineEdit *>("password")->setEnabled(true);
dlg->findChild<QPushButton *>("button")->setText(dlg->tr("Login"));
}
void SwitchToLogout(QDialog * dlg)
{
dlg->findChild<QLineEdit *>("login")->setEnabled(false);
dlg->findChild<QLineEdit *>("password")->setEnabled(false);
dlg->findChild<QPushButton *>("button")->setText(dlg->tr("Logout"));
dlg->setWindowTitle(dlg->tr(kLoginDialogTitle));
}
void OsmAuthDialog::OnAction()
{
bool const isLoginDialog = findChild<QPushButton *>("button")->text() == tr("Login");
if (isLoginDialog)
{
std::string const login = findChild<QLineEdit *>("login")->text().toStdString();
std::string const password = findChild<QLineEdit *>("password")->text().toStdString();
if (login.empty())
{
setWindowTitle("Please enter Login");
return;
}
if (password.empty())
{
setWindowTitle("Please enter Password");
return;
}
OsmOAuth auth = osm::OsmOAuth::ServerAuth();
try
{
if (auth.AuthorizePassword(login, password))
{
auto const token = auth.GetAuthToken();
settings::Set(kOauthTokenSetting, token);
SwitchToLogout(this);
}
else
{
setWindowTitle("Auth failed: invalid login or password");
return;
}
}
catch (std::exception const & ex)
{
setWindowTitle((std::string("Auth failed: ") + ex.what()).c_str());
return;
}
}
else
{
settings::Set(kOauthTokenSetting, std::string());
SwitchToLogin(this);
}
}
} // namespace qt

19
qt/osm_auth_dialog.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include <QtWidgets/QDialog>
namespace qt
{
class OsmAuthDialog : public QDialog
{
Q_OBJECT
public:
explicit OsmAuthDialog(QWidget * parent);
private slots:
void OnAction();
};
} // namespace qt

View file

@ -0,0 +1,42 @@
#include "qt/place_page_dialog_common.hpp"
#include <QtWidgets/QPushButton>
namespace place_page_dialog
{
void addCommonButtons(QDialog * this_, QDialogButtonBox * dbb, bool shouldShowEditPlace)
{
dbb->setCenterButtons(true);
QPushButton * fromButton = new QPushButton("Route From");
fromButton->setIcon(QIcon(":/navig64/point-start.png"));
fromButton->setAutoDefault(false);
this_->connect(fromButton, &QAbstractButton::clicked, this_, [this_] { this_->done(RouteFrom); });
dbb->addButton(fromButton, QDialogButtonBox::ActionRole);
QPushButton * addStopButton = new QPushButton("Add Stop");
addStopButton->setIcon(QIcon(":/navig64/point-intermediate.png"));
addStopButton->setAutoDefault(false);
this_->connect(addStopButton, &QAbstractButton::clicked, this_, [this_] { this_->done(AddStop); });
dbb->addButton(addStopButton, QDialogButtonBox::ActionRole);
QPushButton * routeToButton = new QPushButton("Route To");
routeToButton->setIcon(QIcon(":/navig64/point-finish.png"));
routeToButton->setAutoDefault(false);
this_->connect(routeToButton, &QAbstractButton::clicked, this_, [this_] { this_->done(RouteTo); });
dbb->addButton(routeToButton, QDialogButtonBox::ActionRole);
QPushButton * closeButton = new QPushButton("Close");
closeButton->setDefault(true);
this_->connect(closeButton, &QAbstractButton::clicked, this_, [this_] { this_->done(place_page_dialog::Close); });
dbb->addButton(closeButton, QDialogButtonBox::RejectRole);
if (shouldShowEditPlace)
{
QPushButton * editButton = new QPushButton("Edit Place");
this_->connect(editButton, &QAbstractButton::clicked, this_,
[this_] { this_->done(place_page_dialog::EditPlace); });
dbb->addButton(editButton, QDialogButtonBox::AcceptRole);
}
}
} // namespace place_page_dialog

View file

@ -0,0 +1,18 @@
#pragma once
#include <QtWidgets/QDialog>
#include <QtWidgets/QDialogButtonBox>
namespace place_page_dialog
{
enum PressedButton : int
{
Close = QDialog::Rejected,
RouteFrom,
AddStop,
RouteTo,
EditPlace
};
void addCommonButtons(QDialog * this_, QDialogButtonBox * dbb, bool shouldShowEditPlace);
} // namespace place_page_dialog

View file

@ -0,0 +1,132 @@
#include "qt/place_page_dialog_developer.hpp"
#include "qt/place_page_dialog_common.hpp"
#include "qt/qt_common/text_dialog.hpp"
#include "map/place_page_info.hpp"
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QVBoxLayout>
#include <string>
PlacePageDialogDeveloper::PlacePageDialogDeveloper(QWidget * parent, place_page::Info const & info,
search::ReverseGeocoder::Address const & address)
: QDialog(parent)
{
QVBoxLayout * layout = new QVBoxLayout();
QGridLayout * grid = new QGridLayout();
int row = 0;
auto const addEntry = [grid, &row](std::string const & key, std::string const & value, bool isLink = false)
{
grid->addWidget(new QLabel(QString::fromStdString(key)), row, 0);
QLabel * label = new QLabel(QString::fromStdString(value));
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
if (isLink)
{
label->setOpenExternalLinks(true);
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
label->setText(QString::fromStdString("<a href=\"" + value + "\">" + value + "</a>"));
}
grid->addWidget(label, row++, 1);
return label;
};
{
ms::LatLon const ll = info.GetLatLon();
addEntry("lat, lon", strings::to_string_dac(ll.m_lat, 7) + ", " + strings::to_string_dac(ll.m_lon, 7));
}
addEntry("CountryId", info.GetCountryId());
auto const & title = info.GetTitle();
if (!title.empty())
addEntry("Title", title);
if (auto const & subTitle = info.GetSubtitle(); !subTitle.empty())
addEntry("Subtitle", subTitle);
addEntry("Address", address.FormatAddress());
if (info.IsBookmark())
{
grid->addWidget(new QLabel("Bookmark"), row, 0);
grid->addWidget(new QLabel("Yes"), row++, 1);
}
if (info.IsMyPosition())
{
grid->addWidget(new QLabel("MyPosition"), row, 0);
grid->addWidget(new QLabel("Yes"), row++, 1);
}
if (info.HasApiUrl())
{
grid->addWidget(new QLabel("Api URL"), row, 0);
grid->addWidget(new QLabel(QString::fromStdString(info.GetApiUrl())), row++, 1);
}
if (info.IsFeature())
{
addEntry("Feature ID", DebugPrint(info.GetID()));
addEntry("Raw Types", DebugPrint(info.GetTypes()));
}
auto const layer = info.GetLayer();
if (layer != feature::LAYER_EMPTY)
addEntry("Layer", std::to_string(layer));
using PropID = osm::MapObject::MetadataID;
if (auto cuisines = info.FormatCuisines(); !cuisines.empty())
addEntry(DebugPrint(PropID::FMD_CUISINE), cuisines);
layout->addLayout(grid);
QDialogButtonBox * dbb = new QDialogButtonBox();
place_page_dialog::addCommonButtons(this, dbb, info.ShouldShowEditPlace());
if (auto const & descr = info.GetWikiDescription(); !descr.empty())
{
QPushButton * wikiButton = new QPushButton("Wiki Description");
connect(wikiButton, &QAbstractButton::clicked, this, [this, descr, title]()
{
auto textDialog = TextDialog(this, QString::fromStdString(descr), QString::fromStdString("Wikipedia: " + title));
textDialog.exec();
});
dbb->addButton(wikiButton, QDialogButtonBox::ActionRole);
}
info.ForEachMetadataReadable([&addEntry](PropID id, std::string const & value)
{
bool isLink = false;
switch (id)
{
case PropID::FMD_EMAIL:
case PropID::FMD_WEBSITE:
case PropID::FMD_CONTACT_FACEBOOK:
case PropID::FMD_CONTACT_INSTAGRAM:
case PropID::FMD_CONTACT_TWITTER:
case PropID::FMD_CONTACT_VK:
case PropID::FMD_CONTACT_LINE:
case PropID::FMD_CONTACT_FEDIVERSE:
case PropID::FMD_CONTACT_BLUESKY:
case PropID::FMD_WIKIPEDIA:
case PropID::FMD_WIKIMEDIA_COMMONS:
case PropID::FMD_PANORAMAX: isLink = true; break;
default: break;
}
addEntry(DebugPrint(id), value, isLink);
});
layout->addWidget(dbb);
setLayout(layout);
auto const ppTitle = std::string("Place Page") + (info.IsBookmark() ? " (bookmarked)" : "");
setWindowTitle(ppTitle.c_str());
}

View file

@ -0,0 +1,19 @@
#pragma once
#include "search/reverse_geocoder.hpp"
#include <QtWidgets/QDialog>
namespace place_page
{
class Info;
}
class PlacePageDialogDeveloper : public QDialog
{
Q_OBJECT
public:
PlacePageDialogDeveloper(QWidget * parent, place_page::Info const & info,
search::ReverseGeocoder::Address const & address);
};

View file

@ -0,0 +1,312 @@
#include "qt/place_page_dialog_user.hpp"
#include "qt/place_page_dialog_common.hpp"
#include "qt/qt_common/text_dialog.hpp"
#include "indexer/validate_and_format_contacts.hpp"
#include "map/place_page_info.hpp"
#include "platform/settings.hpp"
#include <QtWidgets/QDialog>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QGridLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QVBoxLayout>
#include <sstream>
#include <string>
namespace
{
static int constexpr kMaxLengthOfPlacePageDescription = 500;
static int constexpr kMinWidthOfShortDescription = 390;
std::string getShortDescription(std::string const & description)
{
std::string_view view(description);
auto const paragraphStart = view.find("<p>");
auto const paragraphEnd = view.find("</p>");
if (paragraphStart == 0 && paragraphEnd != std::string::npos)
view = view.substr(3, paragraphEnd - 3);
if (view.length() > kMaxLengthOfPlacePageDescription)
return std::string(view.substr(0, kMaxLengthOfPlacePageDescription - 3)) + "...";
return std::string(view);
}
std::string_view stripSchemeFromURI(std::string_view uri)
{
for (std::string_view prefix : {"https://", "http://"})
if (uri.starts_with(prefix))
return uri.substr(prefix.size());
return uri;
}
} // namespace
class QHLine : public QFrame
{
public:
QHLine(QWidget * parent = nullptr) : QFrame(parent)
{
setFrameShape(QFrame::HLine);
setFrameShadow(QFrame::Sunken);
}
};
PlacePageDialogUser::PlacePageDialogUser(QWidget * parent, place_page::Info const & info,
search::ReverseGeocoder::Address const & address)
: QDialog(parent)
{
auto const & title = info.GetTitle();
QVBoxLayout * layout = new QVBoxLayout();
{
QVBoxLayout * header = new QVBoxLayout();
if (!title.empty())
{
QLabel * titleLabel = new QLabel(QString::fromStdString("<h1>" + title + "</h1>"));
titleLabel->setWordWrap(true);
header->addWidget(titleLabel);
}
if (auto const subTitle = info.GetSubtitle(); !subTitle.empty())
{
QLabel * subtitleLabel = new QLabel(QString::fromStdString(subTitle));
subtitleLabel->setWordWrap(true);
header->addWidget(subtitleLabel);
}
if (auto const addressFormatted = address.FormatAddress(); !addressFormatted.empty())
{
QLabel * addressLabel = new QLabel(QString::fromStdString(addressFormatted));
addressLabel->setWordWrap(true);
header->addWidget(addressLabel);
}
layout->addLayout(header);
}
{
QHLine * line = new QHLine();
layout->addWidget(line);
}
{
QGridLayout * data = new QGridLayout();
int row = 0;
auto const addEntry = [data, &row](std::string const & key, std::string const & value, bool isLink = false)
{
data->addWidget(new QLabel(QString::fromStdString(key)), row, 0);
QLabel * label = new QLabel(QString::fromStdString(value));
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
label->setWordWrap(true);
if (isLink)
{
label->setOpenExternalLinks(true);
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
label->setText(QString::fromStdString("<a href=\"" + value + "\">" + value + "</a>"));
}
data->addWidget(label, row++, 1);
return label;
};
if (info.IsBookmark())
addEntry("Bookmark", "Yes");
// Wikipedia fragment
if (auto const & wikipedia = info.GetMetadata(feature::Metadata::EType::FMD_WIKIPEDIA); !wikipedia.empty())
{
QLabel * name = new QLabel("Wikipedia");
name->setOpenExternalLinks(true);
name->setTextInteractionFlags(Qt::TextBrowserInteraction);
name->setText(QString::fromStdString("<a href=\"" + feature::Metadata::ToWikiURL(std::string(wikipedia)) +
"\">Wikipedia</a>"));
data->addWidget(name, row++, 0);
}
// Description
if (auto const & description = info.GetWikiDescription(); !description.empty())
{
auto descriptionShort = getShortDescription(description);
QLabel * value = new QLabel(QString::fromStdString(descriptionShort));
value->setWordWrap(true);
data->addWidget(value, row++, 0, 1, 2);
QPushButton * wikiButton = new QPushButton("More...", value);
wikiButton->setAutoDefault(false);
connect(wikiButton, &QAbstractButton::clicked, this, [this, description, title]()
{
auto textDialog =
TextDialog(this, QString::fromStdString(description), QString::fromStdString("Wikipedia: " + title));
textDialog.exec();
});
data->addWidget(wikiButton, row++, 0, 1, 2, Qt::AlignLeft);
}
// Opening hours fragment
if (auto openingHours = info.GetOpeningHours(); !openingHours.empty())
addEntry("Opening hours", std::string(openingHours));
// Cuisine fragment
if (auto cuisines = info.FormatCuisines(); !cuisines.empty())
addEntry("Cuisine", cuisines);
// Capacity fragment
if (auto capacity = info.GetCapacity(); !capacity.empty())
addEntry("Capacity", capacity);
// Sockets fragment
if (auto sockets = info.GetChargeSockets(); !sockets.empty())
{
std::ostringstream oss;
for (auto s : sockets)
{
oss << s.type;
if (s.power > 0)
oss << " (" << s.power << "kW)";
if (s.count > 0)
oss << " × " << s.count;
oss << "\n";
}
addEntry("Charging sockets", oss.str());
}
// Entrance fragment
// TODO
// Phone fragment
if (auto phoneNumber = info.GetMetadata(feature::Metadata::EType::FMD_PHONE_NUMBER); !phoneNumber.empty())
{
data->addWidget(new QLabel("Phone"), row, 0);
QLabel * value = new QLabel(QString::fromStdString("<a href='tel:" + std::string(phoneNumber) + "'>" +
std::string(phoneNumber) + "</a>"));
value->setOpenExternalLinks(true);
data->addWidget(value, row++, 1);
}
// Operator fragment
if (auto operatorName = info.GetMetadata(feature::Metadata::EType::FMD_OPERATOR); !operatorName.empty())
addEntry("Operator", std::string(operatorName));
// Wifi fragment
if (info.HasWifi())
addEntry("Wi-Fi", "Yes");
// Links fragment
if (auto website = info.GetMetadata(feature::Metadata::EType::FMD_WEBSITE); !website.empty())
addEntry("Website", std::string(stripSchemeFromURI(website)), true);
if (auto email = info.GetMetadata(feature::Metadata::EType::FMD_EMAIL); !email.empty())
{
data->addWidget(new QLabel("Email"), row, 0);
QLabel * value = new QLabel(
QString::fromStdString("<a href='mailto:" + std::string(email) + "'>" + std::string(email) + "</a>"));
value->setOpenExternalLinks(true);
data->addWidget(value, row++, 1);
}
// Social networks
{
auto addSocialNetworkWidget = [data, &info, &row](std::string const label, feature::Metadata::EType const eType)
{
if (auto item = info.GetMetadata(eType); !item.empty())
{
data->addWidget(new QLabel(QString::fromStdString(label)), row, 0);
QLabel * value = new QLabel(QString::fromStdString(
"<a href='" + osm::socialContactToURL(eType, std::string(item)) + "'>" + std::string(item) + "</a>"));
value->setOpenExternalLinks(true);
value->setTextInteractionFlags(Qt::TextBrowserInteraction);
data->addWidget(value, row++, 1);
}
};
addSocialNetworkWidget("Facebook", feature::Metadata::EType::FMD_CONTACT_FACEBOOK);
addSocialNetworkWidget("Instagram", feature::Metadata::EType::FMD_CONTACT_INSTAGRAM);
addSocialNetworkWidget("Twitter", feature::Metadata::EType::FMD_CONTACT_TWITTER);
addSocialNetworkWidget("VK", feature::Metadata::EType::FMD_CONTACT_VK);
addSocialNetworkWidget("Line", feature::Metadata::EType::FMD_CONTACT_LINE);
addSocialNetworkWidget("Mastodon", feature::Metadata::EType::FMD_CONTACT_FEDIVERSE);
addSocialNetworkWidget("Bluesky", feature::Metadata::EType::FMD_CONTACT_BLUESKY);
}
if (auto wikimedia_commons = info.GetMetadata(feature::Metadata::EType::FMD_WIKIMEDIA_COMMONS);
!wikimedia_commons.empty())
{
data->addWidget(new QLabel("Wikimedia Commons"), row, 0);
QLabel * value = new QLabel(QString::fromStdString(
"<a href='" + feature::Metadata::ToWikimediaCommonsURL(std::string(wikimedia_commons)) +
"'>Wikimedia Commons</a>"));
value->setOpenExternalLinks(true);
value->setTextInteractionFlags(Qt::TextBrowserInteraction);
data->addWidget(value, row++, 1);
}
if (auto panoramax = info.GetMetadata(feature::Metadata::EType::FMD_PANORAMAX); !panoramax.empty())
{
data->addWidget(new QLabel("Panoramax Picture"), row, 0);
QLabel * value = new QLabel(QString::fromStdString(
"<a href='https://api.panoramax.xyz/?pic=" + std::string(panoramax) + "'>Panoramax Image</a>"));
value->setOpenExternalLinks(true);
value->setTextInteractionFlags(Qt::TextBrowserInteraction);
data->addWidget(value, row++, 1);
}
// Level fragment
if (auto level = info.GetMetadata(feature::Metadata::EType::FMD_LEVEL); !level.empty())
addEntry("Level", std::string(level));
// ATM fragment
if (info.HasAtm())
addEntry("ATM", "Yes");
// Latlon fragment
{
ms::LatLon const ll = info.GetLatLon();
addEntry("Coordinates", strings::to_string_dac(ll.m_lat, 7) + ", " + strings::to_string_dac(ll.m_lon, 7));
}
data->setColumnStretch(0, 0);
data->setColumnStretch(1, 1);
layout->addLayout(data);
}
layout->addStretch();
{
QHLine * line = new QHLine();
layout->addWidget(line);
}
{
QDialogButtonBox * dbb = new QDialogButtonBox();
place_page_dialog::addCommonButtons(this, dbb, info.ShouldShowEditPlace());
layout->addWidget(dbb, Qt::AlignCenter);
}
setLayout(layout);
auto const ppTitle = std::string("Place Page") + (info.IsBookmark() ? " (bookmarked)" : "");
setWindowTitle(ppTitle.c_str());
}

View file

@ -0,0 +1,19 @@
#pragma once
#include "search/reverse_geocoder.hpp"
#include <QtWidgets/QDialog>
namespace place_page
{
class Info;
}
class PlacePageDialogUser : public QDialog
{
Q_OBJECT
public:
PlacePageDialogUser(QWidget * parent, place_page::Info const & info,
search::ReverseGeocoder::Address const & address);
};

57
qt/popup_menu_holder.cpp Normal file
View file

@ -0,0 +1,57 @@
#include "popup_menu_holder.hpp"
#include "base/assert.hpp"
#include <QtWidgets/QMenu>
namespace qt
{
PopupMenuHolder::PopupMenuHolder(QObject * parent) : QObject(parent) {}
QAction * PopupMenuHolder::addActionImpl(QIcon const & icon, QString const & text, bool checkable)
{
QAction * p = new QAction(icon, text, this);
p->setCheckable(checkable);
m_actions.push_back(p);
return p;
}
QToolButton * PopupMenuHolder::create()
{
QMenu * menu = new QMenu();
for (auto * p : m_actions)
menu->addAction(p);
m_toolButton = new QToolButton();
m_toolButton->setPopupMode(QToolButton::MenuButtonPopup);
m_toolButton->setMenu(menu);
return m_toolButton;
}
void PopupMenuHolder::setMainIcon(QIcon const & icon)
{
m_toolButton->setIcon(icon);
}
void PopupMenuHolder::setCurrent(size_t idx)
{
CHECK_LESS(idx, m_actions.size(), ());
m_toolButton->setIcon(m_actions[idx]->icon());
}
void PopupMenuHolder::setChecked(size_t idx, bool checked)
{
CHECK_LESS(idx, m_actions.size(), ());
ASSERT(m_actions[idx]->isCheckable(), ());
m_actions[idx]->setChecked(checked);
}
bool PopupMenuHolder::isChecked(size_t idx)
{
CHECK_LESS(idx, m_actions.size(), ());
ASSERT(m_actions[idx]->isCheckable(), ());
return m_actions[idx]->isChecked();
}
} // namespace qt

60
qt/popup_menu_holder.hpp Normal file
View file

@ -0,0 +1,60 @@
#pragma once
#include <QtGui/QAction>
#include <QtWidgets/QToolButton>
#include <type_traits>
#include <vector>
namespace qt
{
/// Inherit from QObject to assign parents and don't worry about deleting.
class PopupMenuHolder : public QObject
{
Q_OBJECT
QToolButton * m_toolButton;
std::vector<QAction *> m_actions;
QAction * addActionImpl(QIcon const & icon, QString const & text, bool checkable);
public:
explicit PopupMenuHolder(QObject * parent = nullptr);
QAction * addAction(QIcon const & icon, QString const & text, QObject const * receiver, char const * member,
bool checkable)
{
QAction * p = addActionImpl(icon, text, checkable);
connect(p, SIGNAL(triggered()), receiver, member);
return p;
}
template <class SlotT>
QAction * addAction(QIcon const & icon, QString const & text, SlotT slot, bool checkable)
{
QAction * p = addActionImpl(icon, text, checkable);
connect(p, &QAction::triggered, std::move(slot));
return p;
}
QToolButton * create();
void setMainIcon(QIcon const & icon);
void setCurrent(size_t idx);
template <class T>
typename std::enable_if<std::is_enum<T>::value, void>::type setCurrent(T idx)
{
setCurrent(static_cast<size_t>(idx));
}
void setChecked(size_t idx, bool checked);
bool isChecked(size_t idx);
template <class T>
typename std::enable_if<std::is_enum<T>::value, bool>::type isChecked(T idx)
{
return isChecked(static_cast<size_t>(idx));
}
};
} // namespace qt

216
qt/preferences_dialog.cpp Normal file
View file

@ -0,0 +1,216 @@
#include "qt/preferences_dialog.hpp"
#include "coding/string_utf8_multilang.hpp"
#include "indexer/map_style.hpp"
#include "map/framework.hpp"
#include "platform/measurement_utils.hpp"
#include "platform/preferred_languages.hpp"
#include "platform/settings.hpp"
#include "platform/style_utils.hpp"
#include <QLocale>
#include <QtGui/QIcon>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QGroupBox>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QRadioButton>
#include <QtWidgets/QVBoxLayout>
using namespace measurement_utils;
#ifdef BUILD_DESIGNER
std::string const kEnabledAutoRegenGeomIndex = "EnabledAutoRegenGeomIndex";
#endif
namespace qt
{
PreferencesDialog::PreferencesDialog(QWidget * parent, Framework & framework)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
{
QIcon icon(":/ui/logo.png");
setWindowIcon(icon);
setWindowTitle(tr("Preferences"));
QButtonGroup * unitsGroup = new QButtonGroup(this);
QGroupBox * unitsRadioBox = new QGroupBox("System of measurement");
{
QHBoxLayout * layout = new QHBoxLayout();
QRadioButton * radioButton = new QRadioButton("Metric");
layout->addWidget(radioButton);
unitsGroup->addButton(radioButton, static_cast<int>(Units::Metric));
radioButton = new QRadioButton("Imperial (foot)");
layout->addWidget(radioButton);
unitsGroup->addButton(radioButton, static_cast<int>(Units::Imperial));
unitsRadioBox->setLayout(layout);
Units u;
if (!settings::Get(settings::kMeasurementUnits, u))
{
// Set default measurement from system locale
if (QLocale::system().measurementSystem() == QLocale::MetricSystem)
u = Units::Metric;
else
u = Units::Imperial;
}
unitsGroup->button(static_cast<int>(u))->setChecked(true);
// Temporary to pass the address of overloaded function.
void (QButtonGroup::*buttonClicked)(int) = &QButtonGroup::idClicked;
connect(unitsGroup, buttonClicked, [&framework](int i)
{
Units u = Units::Metric;
switch (i)
{
case 0: u = Units::Metric; break;
case 1: u = Units::Imperial; break;
}
settings::Set(settings::kMeasurementUnits, u);
framework.SetupMeasurementSystem();
});
}
QCheckBox * largeFontCheckBox = new QCheckBox("Use larger font on the map");
{
largeFontCheckBox->setChecked(framework.LoadLargeFontsSize());
connect(largeFontCheckBox, &QCheckBox::stateChanged,
[&framework](int i) { framework.SetLargeFontsSize(static_cast<bool>(i)); });
}
QCheckBox * transliterationCheckBox = new QCheckBox("Transliterate to Latin");
{
transliterationCheckBox->setChecked(framework.LoadTransliteration());
connect(transliterationCheckBox, &QCheckBox::stateChanged, [&framework](int i)
{
bool const enable = i > 0;
framework.SaveTransliteration(enable);
framework.AllowTransliteration(enable);
});
}
QCheckBox * developerModeCheckBox = new QCheckBox("Developer Mode");
{
bool developerMode;
if (settings::Get(settings::kDeveloperMode, developerMode) && developerMode)
developerModeCheckBox->setChecked(developerMode);
connect(developerModeCheckBox, &QCheckBox::stateChanged,
[](int i) { settings::Set(settings::kDeveloperMode, static_cast<bool>(i)); });
}
QLabel * mapLanguageLabel = new QLabel("Map Language");
QComboBox * mapLanguageComboBox = new QComboBox();
{
// The property maxVisibleItems is ignored for non-editable comboboxes in styles that
// return true for `QStyle::SH_ComboBox_Popup such as the Mac style or the Gtk+ Style.
// So we ensure that it returns false here.
mapLanguageComboBox->setStyleSheet("QComboBox { combobox-popup: 0; }");
mapLanguageComboBox->setMaxVisibleItems(10);
StringUtf8Multilang::Languages const & supportedLanguages =
StringUtf8Multilang::GetSupportedLanguages(/* includeServiceLangs */ false);
// Create a vector of pairs (name, index) and sort by name
std::vector<std::pair<std::string, size_t>> languageNameIndexPairs;
for (size_t i = 0; i < supportedLanguages.size(); ++i)
{
languageNameIndexPairs.emplace_back(std::string(supportedLanguages[i].m_name), i);
}
std::sort(languageNameIndexPairs.begin(), languageNameIndexPairs.end(),
[](auto const & a, auto const & b) { return a.first < b.first; });
QStringList languagesList = QStringList();
std::vector<size_t> sortedIndices;
for (auto const & pair : languageNameIndexPairs)
{
languagesList << QString::fromStdString(pair.first);
sortedIndices.push_back(pair.second);
}
mapLanguageComboBox->addItems(languagesList);
std::string const & mapLanguageCode = framework.GetMapLanguageCode();
int8_t languageIndex = StringUtf8Multilang::GetLangIndex(mapLanguageCode);
if (languageIndex == StringUtf8Multilang::kUnsupportedLanguageCode)
languageIndex = StringUtf8Multilang::kDefaultCode;
mapLanguageComboBox->setCurrentText(
QString::fromStdString(std::string(StringUtf8Multilang::GetLangNameByCode(languageIndex))));
connect(mapLanguageComboBox, &QComboBox::activated, [&framework, &supportedLanguages, sortedIndices](int index)
{
auto const & mapLanguageCode = std::string(supportedLanguages[sortedIndices[index]].m_code);
framework.SetMapLanguageCode(mapLanguageCode);
});
}
QButtonGroup * nightModeGroup = new QButtonGroup(this);
QGroupBox * nightModeRadioBox = new QGroupBox("Night Mode");
{
using namespace style_utils;
QHBoxLayout * layout = new QHBoxLayout();
QRadioButton * radioButton = new QRadioButton("Off");
layout->addWidget(radioButton);
nightModeGroup->addButton(radioButton, static_cast<int>(NightMode::Off));
radioButton = new QRadioButton("On");
layout->addWidget(radioButton);
nightModeGroup->addButton(radioButton, static_cast<int>(NightMode::On));
nightModeRadioBox->setLayout(layout);
int const btn = MapStyleIsDark(framework.GetMapStyle()) ? 1 : 0;
nightModeGroup->button(btn)->setChecked(true);
void (QButtonGroup::*buttonClicked)(int) = &QButtonGroup::idClicked;
connect(nightModeGroup, buttonClicked, [&framework](int i)
{
auto const currStyle = framework.GetMapStyle();
framework.SetMapStyle((i == 0) ? GetLightMapStyleVariant(currStyle) : GetDarkMapStyleVariant(currStyle));
});
}
#ifdef BUILD_DESIGNER
QCheckBox * indexRegenCheckBox = new QCheckBox("Enable auto regeneration of geometry index");
{
bool enabled = false;
if (!settings::Get(kEnabledAutoRegenGeomIndex, enabled))
settings::Set(kEnabledAutoRegenGeomIndex, false);
indexRegenCheckBox->setChecked(enabled);
connect(indexRegenCheckBox, &QCheckBox::stateChanged,
[](int i) { settings::Set(kEnabledAutoRegenGeomIndex, static_cast<bool>(i)) });
}
#endif
QHBoxLayout * bottomLayout = new QHBoxLayout();
{
QPushButton * closeButton = new QPushButton(tr("Close"));
closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
closeButton->setDefault(true);
connect(closeButton, &QAbstractButton::clicked, [this]() { done(0); });
bottomLayout->addStretch(1);
bottomLayout->setSpacing(0);
bottomLayout->addWidget(closeButton);
}
QVBoxLayout * finalLayout = new QVBoxLayout();
finalLayout->addWidget(unitsRadioBox);
finalLayout->addWidget(largeFontCheckBox);
finalLayout->addWidget(transliterationCheckBox);
finalLayout->addWidget(developerModeCheckBox);
finalLayout->addWidget(mapLanguageLabel);
finalLayout->addWidget(mapLanguageComboBox);
finalLayout->addWidget(nightModeRadioBox);
#ifdef BUILD_DESIGNER
finalLayout->addWidget(indexRegenCheckBox);
#endif
finalLayout->addLayout(bottomLayout);
setLayout(finalLayout);
}
} // namespace qt

24
qt/preferences_dialog.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <QtWidgets/QDialog>
class Framework;
namespace qt
{
class PreferencesDialog : public QDialog
{
typedef QDialog base_t;
Q_OBJECT
public:
PreferencesDialog(QWidget * parent, Framework & framework);
};
} // namespace qt
#ifdef BUILD_DESIGNER
extern std::string const kEnabledAutoRegenGeomIndex;
#endif

View file

@ -0,0 +1,36 @@
project(qt_common)
QT6_ADD_RESOURCES(RESOURCES res/resources_common.qrc)
set_property(SOURCE qrc_resources_common.cpp PROPERTY SKIP_AUTOGEN ON)
set(SRC
helpers.cpp
helpers.hpp
map_widget.cpp
map_widget.hpp
proxy_style.cpp
proxy_style.hpp
qtoglcontext.cpp
qtoglcontext.hpp
qtoglcontextfactory.cpp
qtoglcontextfactory.hpp
scale_slider.cpp
scale_slider.hpp
spinner.cpp
spinner.hpp
text_dialog.cpp
text_dialog.hpp
)
omim_add_library(${PROJECT_NAME} ${SRC} ${RESOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES AUTOMOC ON)
target_link_libraries(${PROJECT_NAME}
map
Qt6::Gui
Qt6::Widgets
Qt6::OpenGL
Qt6::OpenGLWidgets
)

96
qt/qt_common/helpers.cpp Normal file
View file

@ -0,0 +1,96 @@
#include "qt/qt_common/helpers.hpp"
#include "geometry/mercator.hpp"
#include "base/logging.hpp"
#include <QtCore/QDateTime>
#include <QtGui/QSurfaceFormat>
namespace qt::common
{
bool IsLeftButton(Qt::MouseButtons buttons)
{
return buttons & Qt::LeftButton;
}
bool IsLeftButton(QMouseEvent const * const e)
{
return IsLeftButton(e->button()) || IsLeftButton(e->buttons());
}
bool IsRightButton(Qt::MouseButtons buttons)
{
return buttons & Qt::RightButton;
}
bool IsRightButton(QMouseEvent const * const e)
{
return IsRightButton(e->button()) || IsRightButton(e->buttons());
}
bool IsCommandModifier(QMouseEvent const * const e)
{
return e->modifiers() & Qt::ControlModifier;
}
bool IsShiftModifier(QMouseEvent const * const e)
{
return e->modifiers() & Qt::ShiftModifier;
}
bool IsAltModifier(QMouseEvent const * const e)
{
return e->modifiers() & Qt::AltModifier;
}
location::GpsInfo MakeGpsInfo(m2::PointD const & point)
{
location::GpsInfo info;
info.m_source = location::EUser;
info.m_latitude = mercator::YToLat(point.y);
info.m_longitude = mercator::XToLon(point.x);
info.m_horizontalAccuracy = 10;
info.m_timestamp = QDateTime::currentMSecsSinceEpoch() / 1000.0;
return info;
}
void SetDefaultSurfaceFormat(QString const & platformName)
{
QSurfaceFormat fmt;
fmt.setAlphaBufferSize(8);
fmt.setBlueBufferSize(8);
fmt.setGreenBufferSize(8);
fmt.setRedBufferSize(8);
fmt.setStencilBufferSize(0);
fmt.setSamples(0);
fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
fmt.setSwapInterval(1);
fmt.setDepthBufferSize(16);
#if defined(OMIM_OS_LINUX)
fmt.setRenderableType(QSurfaceFormat::OpenGLES);
fmt.setProfile(QSurfaceFormat::CompatibilityProfile);
#endif
// Set proper OGL version now (needed for "cocoa" or "xcb"), but have troubles with "wayland" devices.
// It will be resolved later in MapWidget::initializeGL when OGL context is available.
if (platformName != QString("wayland"))
{
#if defined(OMIM_OS_LINUX)
LOG(LINFO, ("Set default OpenGL version to ES 3.0"));
fmt.setVersion(3, 0);
#else
LOG(LINFO, ("Set default OGL version to 3.2"));
fmt.setProfile(QSurfaceFormat::CoreProfile);
fmt.setVersion(3, 2);
#endif
}
#ifdef ENABLE_OPENGL_DIAGNOSTICS
fmt.setOption(QSurfaceFormat::DebugContext);
#endif
QSurfaceFormat::setDefaultFormat(fmt);
}
} // namespace qt::common

31
qt/qt_common/helpers.hpp Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include "platform/location.hpp"
#include <QtGui/QMouseEvent>
namespace qt::common
{
bool IsLeftButton(Qt::MouseButtons buttons);
bool IsLeftButton(QMouseEvent const * const e);
bool IsRightButton(Qt::MouseButtons buttons);
bool IsRightButton(QMouseEvent const * const e);
bool IsCommandModifier(QMouseEvent const * const e);
bool IsShiftModifier(QMouseEvent const * const e);
bool IsAltModifier(QMouseEvent const * const e);
struct Hotkey
{
Hotkey() = default;
Hotkey(QKeySequence const & key, char const * slot) : m_key(key), m_slot(slot) {}
QKeySequence m_key = 0;
char const * m_slot = nullptr;
};
location::GpsInfo MakeGpsInfo(m2::PointD const & point);
void SetDefaultSurfaceFormat(QString const & platformName);
} // namespace qt::common

522
qt/qt_common/map_widget.cpp Normal file
View file

@ -0,0 +1,522 @@
#include "qt/qt_common/map_widget.hpp"
#include "qt/qt_common/helpers.hpp"
#include "qt/qt_common/scale_slider.hpp"
#include "map/framework.hpp"
#include "routing/maxspeeds.hpp"
#include "geometry/point2d.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <functional>
#include <string>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QTouchEvent>
#include <QtGui/QAction>
#include <QtGui/QMouseEvent>
#include <QtGui/QOpenGLFunctions>
#include <QtWidgets/QMenu>
// Fraction of the viewport for a move event
static constexpr float kViewportFractionRoughMove = 0.2;
// Fraction of the viewport for a small move event
static constexpr float kViewportFractionSmoothMove = 0.1;
namespace qt::common
{
// #define ENABLE_AA_SWITCH
MapWidget::MapWidget(Framework & framework, bool isScreenshotMode, QWidget * parent)
: QOpenGLWidget(parent)
, m_framework(framework)
, m_screenshotMode(isScreenshotMode)
, m_slider(nullptr)
, m_sliderState(SliderState::Released)
, m_ratio(1.0f)
, m_contextFactory(nullptr)
{
setMouseTracking(true);
// Update widget's content frequency is 60 fps.
m_updateTimer = std::make_unique<QTimer>(this);
VERIFY(connect(m_updateTimer.get(), SIGNAL(timeout()), this, SLOT(update())), ());
m_updateTimer->setSingleShot(false);
m_updateTimer->start(1000 / 60);
setAttribute(Qt::WA_AcceptTouchEvents);
}
MapWidget::~MapWidget()
{
m_framework.EnterBackground();
m_framework.SetRenderingDisabled(true);
m_contextFactory->PrepareToShutdown();
m_framework.DestroyDrapeEngine();
m_contextFactory.reset();
}
void MapWidget::BindHotkeys(QWidget & parent)
{
Hotkey const hotkeys[] = {
{Qt::Key_Equal, SLOT(ScalePlus())},
{Qt::Key_Plus, SLOT(ScalePlus())},
{Qt::Key_Minus, SLOT(ScaleMinus())},
{Qt::Key_Right, SLOT(MoveRight())},
{Qt::Key_Left, SLOT(MoveLeft())},
{Qt::Key_Up, SLOT(MoveUp())},
{Qt::Key_Down, SLOT(MoveDown())},
{Qt::ALT | Qt::Key_Equal, SLOT(ScalePlusLight())},
{Qt::ALT | Qt::Key_Plus, SLOT(ScalePlusLight())},
{Qt::ALT | Qt::Key_Minus, SLOT(ScaleMinusLight())},
{Qt::ALT | Qt::Key_Right, SLOT(MoveRightSmooth())},
{Qt::ALT | Qt::Key_Left, SLOT(MoveLeftSmooth())},
{Qt::ALT | Qt::Key_Up, SLOT(MoveUpSmooth())},
{Qt::ALT | Qt::Key_Down, SLOT(MoveDownSmooth())},
#ifdef ENABLE_AA_SWITCH
{Qt::ALT | Qt::Key_A, SLOT(AntialiasingOn())},
{Qt::ALT | Qt::Key_S, SLOT(AntialiasingOff())},
#endif
};
for (auto const & hotkey : hotkeys)
{
auto action = std::make_unique<QAction>(&parent);
action->setShortcut(QKeySequence(hotkey.m_key));
connect(action.get(), SIGNAL(triggered()), this, hotkey.m_slot);
parent.addAction(action.release());
}
}
void MapWidget::BindSlider(ScaleSlider & slider)
{
m_slider = &slider;
connect(m_slider, &QAbstractSlider::actionTriggered, this, &MapWidget::ScaleChanged);
connect(m_slider, &QAbstractSlider::sliderPressed, this, &MapWidget::SliderPressed);
connect(m_slider, &QAbstractSlider::sliderReleased, this, &MapWidget::SliderReleased);
}
void MapWidget::CreateEngine()
{
Framework::DrapeCreationParams p;
p.m_apiVersion = dp::ApiVersion::OpenGLES3;
p.m_surfaceWidth = m_screenshotMode ? width() : static_cast<int>(m_ratio * width());
p.m_surfaceHeight = m_screenshotMode ? height() : static_cast<int>(m_ratio * height());
p.m_visualScale = static_cast<float>(m_ratio);
p.m_hints.m_screenshotMode = m_screenshotMode;
m_skin.reset(new gui::Skin(gui::ResolveGuiSkinFile("default"), m_ratio));
m_skin->Resize(p.m_surfaceWidth, p.m_surfaceHeight);
m_skin->ForEach([&p](gui::EWidget widget, gui::Position const & pos) { p.m_widgetsInitInfo[widget] = pos; });
p.m_widgetsInitInfo[gui::WIDGET_SCALE_FPS_LABEL] = gui::Position(dp::LeftTop);
m_framework.CreateDrapeEngine(make_ref(m_contextFactory), std::move(p));
m_framework.SetViewportListener(std::bind(&MapWidget::OnViewportChanged, this, std::placeholders::_1));
}
void MapWidget::ScalePlus()
{
m_framework.Scale(Framework::SCALE_MAG, true);
}
void MapWidget::ScaleMinus()
{
m_framework.Scale(Framework::SCALE_MIN, true);
}
void MapWidget::ScalePlusLight()
{
m_framework.Scale(Framework::SCALE_MAG_LIGHT, true);
}
void MapWidget::ScaleMinusLight()
{
m_framework.Scale(Framework::SCALE_MIN_LIGHT, true);
}
void MapWidget::MoveRight()
{
m_framework.Move(-kViewportFractionRoughMove, 0, true);
}
void MapWidget::MoveRightSmooth()
{
m_framework.Move(-kViewportFractionSmoothMove, 0, true);
}
void MapWidget::MoveLeft()
{
m_framework.Move(kViewportFractionRoughMove, 0, true);
}
void MapWidget::MoveLeftSmooth()
{
m_framework.Move(kViewportFractionSmoothMove, 0, true);
}
void MapWidget::MoveUp()
{
m_framework.Move(0, -kViewportFractionRoughMove, true);
}
void MapWidget::MoveUpSmooth()
{
m_framework.Move(0, -kViewportFractionSmoothMove, true);
}
void MapWidget::MoveDown()
{
m_framework.Move(0, kViewportFractionRoughMove, true);
}
void MapWidget::MoveDownSmooth()
{
m_framework.Move(0, kViewportFractionSmoothMove, true);
}
void MapWidget::AntialiasingOn()
{
auto engine = m_framework.GetDrapeEngine();
if (engine != nullptr)
engine->SetPosteffectEnabled(df::PostprocessRenderer::Antialiasing, true);
}
void MapWidget::AntialiasingOff()
{
auto engine = m_framework.GetDrapeEngine();
if (engine != nullptr)
engine->SetPosteffectEnabled(df::PostprocessRenderer::Antialiasing, false);
}
void MapWidget::ScaleChanged(int action)
{
if (!m_slider)
return;
if (action == QAbstractSlider::SliderNoAction)
return;
double const factor = m_slider->GetScaleFactor();
if (factor != 1.0)
m_framework.Scale(factor, false);
}
void MapWidget::SliderPressed()
{
m_sliderState = SliderState::Pressed;
}
void MapWidget::SliderReleased()
{
m_sliderState = SliderState::Released;
}
m2::PointD MapWidget::GetDevicePoint(QMouseEvent * e) const
{
return m2::PointD(L2D(e->position().x()), L2D(e->position().y()));
}
df::Touch MapWidget::GetDfTouchFromQMouseEvent(QMouseEvent * e) const
{
df::Touch touch;
touch.m_id = 0;
touch.m_location = GetDevicePoint(e);
return touch;
}
df::TouchEvent MapWidget::GetDfTouchEventFromQMouseEvent(QMouseEvent * e, df::TouchEvent::ETouchType type) const
{
df::TouchEvent event;
event.SetTouchType(type);
event.SetFirstTouch(GetDfTouchFromQMouseEvent(e));
if (IsCommandModifier(e))
event.SetSecondTouch(GetSymmetrical(event.GetFirstTouch()));
return event;
}
df::Touch MapWidget::GetSymmetrical(df::Touch const & touch) const
{
m2::PointD const pixelCenter = m_framework.GetVisiblePixelCenter();
m2::PointD const symmetricalLocation = pixelCenter + pixelCenter - m2::PointD(touch.m_location);
df::Touch result;
result.m_id = touch.m_id + 1;
result.m_location = symmetricalLocation;
return result;
}
void MapWidget::OnViewportChanged(ScreenBase const & screen)
{
UpdateScaleControl();
}
void MapWidget::UpdateScaleControl()
{
if (!m_slider || m_sliderState == SliderState::Pressed)
return;
m_slider->SetPosWithBlockedSignals(m_framework.GetDrawScale());
}
void MapWidget::Build()
{
std::string_view vertexSrc;
std::string_view fragmentSrc;
#if defined(OMIM_OS_LINUX)
vertexSrc = ":common/shaders/gles_300.vsh.glsl";
fragmentSrc = ":common/shaders/gles_300.fsh.glsl";
#else
vertexSrc = ":common/shaders/gl_150.vsh.glsl";
fragmentSrc = ":common/shaders/gl_150.fsh.glsl";
#endif
m_program = std::make_unique<QOpenGLShaderProgram>(this);
m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, vertexSrc.data());
m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, fragmentSrc.data());
m_program->link();
m_vao = std::make_unique<QOpenGLVertexArrayObject>(this);
m_vao->create();
m_vao->bind();
m_vbo = std::make_unique<QOpenGLBuffer>(QOpenGLBuffer::VertexBuffer);
m_vbo->setUsagePattern(QOpenGLBuffer::StaticDraw);
m_vbo->create();
m_vbo->bind();
QVector4D vertices[4] = {QVector4D(-1.0, 1.0, 0.0, 1.0), QVector4D(1.0, 1.0, 1.0, 1.0),
QVector4D(-1.0, -1.0, 0.0, 0.0), QVector4D(1.0, -1.0, 1.0, 0.0)};
m_vbo->allocate(static_cast<void *>(vertices), sizeof(vertices));
QOpenGLFunctions * f = QOpenGLContext::currentContext()->functions();
// 0-index of the buffer is linked to "a_position" attribute in vertex shader.
// Introduced in https://github.com/organicmaps/organicmaps/pull/9814
f->glEnableVertexAttribArray(0);
f->glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(QVector4D), nullptr);
m_program->release();
m_vao->release();
}
namespace
{
search::ReverseGeocoder::Address GetFeatureAddressInfo(Framework const & framework, FeatureType & ft)
{
search::ReverseGeocoder const coder(framework.GetDataSource());
search::ReverseGeocoder::Address address;
coder.GetExactAddress(ft, address);
return address;
}
} // namespace
void MapWidget::ShowInfoPopup(QMouseEvent * e, m2::PointD const & pt)
{
// show feature types
QMenu menu;
auto const addStringFn = [&menu](std::string const & s)
{
if (!s.empty())
menu.addAction(QString::fromUtf8(s.c_str()));
};
m_framework.ForEachFeatureAtPoint([&](FeatureType & ft)
{
// ID
addStringFn(DebugPrint(ft.GetID()));
// Types
std::string concat;
auto types = feature::TypesHolder(ft);
types.SortBySpec();
for (auto const & type : types.ToObjectNames())
concat += type + " ";
addStringFn(concat);
// Name
addStringFn(std::string(ft.GetReadableName()));
// Address
auto const info = GetFeatureAddressInfo(m_framework, ft);
addStringFn(info.FormatAddress());
if (ft.GetGeomType() == feature::GeomType::Line)
{
// Maxspeed
auto const & dataSource = m_framework.GetDataSource();
auto const handle = dataSource.GetMwmHandleById(ft.GetID().m_mwmId);
auto const speeds = routing::LoadMaxspeeds(handle);
if (speeds)
{
auto const speed = speeds->GetMaxspeed(ft.GetID().m_index);
if (speed.IsValid())
addStringFn(DebugPrint(speed));
}
}
int const layer = ft.GetLayer();
if (layer != feature::LAYER_EMPTY)
addStringFn("Layer = " + std::to_string(layer));
menu.addSeparator();
}, m_framework.PtoG(pt));
menu.exec(e->pos());
}
void MapWidget::initializeGL()
{
ASSERT(m_contextFactory == nullptr, ());
if (!m_screenshotMode)
m_ratio = devicePixelRatio();
#if defined(OMIM_OS_LINUX)
{
QOpenGLFunctions * funcs = context()->functions();
LOG(LINFO, ("Vendor:", funcs->glGetString(GL_VENDOR), "\nRenderer:", funcs->glGetString(GL_RENDERER),
"\nVersion:", funcs->glGetString(GL_VERSION), "\nShading language version:\n",
funcs->glGetString(GL_SHADING_LANGUAGE_VERSION), "\nExtensions:", funcs->glGetString(GL_EXTENSIONS)));
if (!context()->isOpenGLES())
LOG(LCRITICAL, ("Context is not LibGLES! This shouldn't have happened."));
auto fmt = context()->format();
if (context()->format().version() < qMakePair(3, 0))
{
CHECK(false, ("OpenGL ES2 is not supported"));
}
else
{
LOG(LINFO, ("OpenGL version is at least 3.0, enabling GLSL '#version 300 es'"));
fmt.setVersion(3, 0);
}
QSurfaceFormat::setDefaultFormat(fmt);
}
#endif
m_contextFactory.reset(new QtOGLContextFactory(context()));
emit BeforeEngineCreation();
CreateEngine();
m_framework.EnterForeground();
}
void MapWidget::paintGL()
{
if (m_program == nullptr)
Build();
if (m_contextFactory->AcquireFrame())
{
m_vao->bind();
m_program->bind();
QOpenGLFunctions * funcs = context()->functions();
funcs->glActiveTexture(GL_TEXTURE0);
GLuint const image = m_contextFactory->GetTextureHandle();
funcs->glBindTexture(GL_TEXTURE_2D, image);
int const samplerLocation = m_program->uniformLocation("u_sampler");
m_program->setUniformValue(samplerLocation, 0);
QRectF const & texRect = m_contextFactory->GetTexRect();
QVector2D const samplerSize(texRect.width(), texRect.height());
int const samplerSizeLocation = m_program->uniformLocation("u_samplerSize");
m_program->setUniformValue(samplerSizeLocation, samplerSize);
funcs->glClearColor(0.0, 0.0, 0.0, 1.0);
funcs->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
funcs->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_program->release();
m_vao->release();
}
}
void MapWidget::resizeGL(int width, int height)
{
int const w = m_screenshotMode ? width : m_ratio * width;
int const h = m_screenshotMode ? height : m_ratio * height;
m_framework.OnSize(w, h);
if (m_skin)
{
m_skin->Resize(w, h);
gui::TWidgetsLayoutInfo layout;
m_skin->ForEach([&layout](gui::EWidget w, gui::Position const & pos) { layout[w] = pos.m_pixelPivot; });
m_framework.SetWidgetLayout(std::move(layout));
}
}
void MapWidget::mouseDoubleClickEvent(QMouseEvent * e)
{
if (m_screenshotMode)
return;
QOpenGLWidget::mouseDoubleClickEvent(e);
if (IsLeftButton(e))
m_framework.Scale(Framework::SCALE_MAG_LIGHT, GetDevicePoint(e), true);
}
void MapWidget::mousePressEvent(QMouseEvent * e)
{
if (m_screenshotMode)
return;
QOpenGLWidget::mousePressEvent(e);
if (IsLeftButton(e))
m_framework.TouchEvent(GetDfTouchEventFromQMouseEvent(e, df::TouchEvent::TOUCH_DOWN));
}
void MapWidget::mouseMoveEvent(QMouseEvent * e)
{
if (m_screenshotMode)
return;
QOpenGLWidget::mouseMoveEvent(e);
if (IsLeftButton(e))
m_framework.TouchEvent(GetDfTouchEventFromQMouseEvent(e, df::TouchEvent::TOUCH_MOVE));
}
void MapWidget::mouseReleaseEvent(QMouseEvent * e)
{
if (m_screenshotMode)
return;
if (e->button() == Qt::RightButton)
emit OnContextMenuRequested(e->globalPosition().toPoint());
QOpenGLWidget::mouseReleaseEvent(e);
if (IsLeftButton(e))
m_framework.TouchEvent(GetDfTouchEventFromQMouseEvent(e, df::TouchEvent::TOUCH_UP));
}
void MapWidget::wheelEvent(QWheelEvent * e)
{
if (m_screenshotMode)
return;
QOpenGLWidget::wheelEvent(e);
QPointF const pos = e->position();
double const factor = e->angleDelta().y() / 3.0 / 360.0;
// https://doc-snapshots.qt.io/qt6-dev/qwheelevent.html#angleDelta, angleDelta() returns in eighths of a degree.
/// @todo Here you can tune the speed of zooming.
m_framework.Scale(exp(factor), m2::PointD(L2D(pos.x()), L2D(pos.y())), false);
}
} // namespace qt::common

113
qt/qt_common/map_widget.hpp Normal file
View file

@ -0,0 +1,113 @@
#pragma once
#include "drape/pointers.hpp"
#include "drape_frontend/gui/skin.hpp"
#include "drape_frontend/user_event_stream.hpp"
#include "qt/qt_common/qtoglcontextfactory.hpp"
#include <QOpenGLWidget>
#include <QtCore/QTimer>
#include <memory>
class Framework;
class QMouseEvent;
class QWidget;
class ScreenBase;
class QOpenGLShaderProgram;
class QOpenGLVertexArrayObject;
class QOpenGLBuffer;
namespace qt::common
{
class ScaleSlider;
class MapWidget : public QOpenGLWidget
{
Q_OBJECT
public:
MapWidget(Framework & framework, bool isScreenshotMode, QWidget * parent);
~MapWidget() override;
void BindHotkeys(QWidget & parent);
void BindSlider(ScaleSlider & slider);
void CreateEngine();
void grabGestures(QList<Qt::GestureType> const & gestures);
signals:
void OnContextMenuRequested(QPoint const & p);
public slots:
void ScalePlus();
void ScaleMinus();
void ScalePlusLight();
void ScaleMinusLight();
void MoveRight();
void MoveRightSmooth();
void MoveLeft();
void MoveLeftSmooth();
void MoveUp();
void MoveUpSmooth();
void MoveDown();
void MoveDownSmooth();
void ScaleChanged(int action);
void SliderPressed();
void SliderReleased();
void AntialiasingOn();
void AntialiasingOff();
public:
Q_SIGNAL void BeforeEngineCreation();
protected:
enum class SliderState
{
Pressed,
Released
};
int L2D(int px) const { return px * m_ratio; }
m2::PointD GetDevicePoint(QMouseEvent * e) const;
df::Touch GetDfTouchFromQMouseEvent(QMouseEvent * e) const;
df::TouchEvent GetDfTouchEventFromQMouseEvent(QMouseEvent * e, df::TouchEvent::ETouchType type) const;
df::Touch GetSymmetrical(df::Touch const & touch) const;
void UpdateScaleControl();
void Build();
void ShowInfoPopup(QMouseEvent * e, m2::PointD const & pt);
void OnViewportChanged(ScreenBase const & screen);
// QOpenGLWidget overrides:
void initializeGL() override;
void paintGL() override;
void resizeGL(int width, int height) override;
void mouseDoubleClickEvent(QMouseEvent * e) override;
void mousePressEvent(QMouseEvent * e) override;
void mouseMoveEvent(QMouseEvent * e) override;
void mouseReleaseEvent(QMouseEvent * e) override;
void wheelEvent(QWheelEvent * e) override;
Framework & m_framework;
bool m_screenshotMode;
ScaleSlider * m_slider;
SliderState m_sliderState;
float m_ratio;
drape_ptr<QtOGLContextFactory> m_contextFactory;
std::unique_ptr<gui::Skin> m_skin;
std::unique_ptr<QTimer> m_updateTimer;
std::unique_ptr<QOpenGLShaderProgram> m_program;
std::unique_ptr<QOpenGLVertexArrayObject> m_vao;
std::unique_ptr<QOpenGLBuffer> m_vbo;
};
} // namespace qt::common

View file

@ -0,0 +1,135 @@
#include "qt/qt_common/proxy_style.hpp"
namespace qt
{
namespace common
{
ProxyStyle::ProxyStyle(QStyle * p) : QStyle(), style(p) {}
void ProxyStyle::drawComplexControl(ComplexControl control, QStyleOptionComplex const * option, QPainter * painter,
QWidget const * widget) const
{
style->drawComplexControl(control, option, painter, widget);
}
void ProxyStyle::drawControl(ControlElement element, QStyleOption const * option, QPainter * painter,
QWidget const * widget) const
{
style->drawControl(element, option, painter, widget);
}
void ProxyStyle::drawItemPixmap(QPainter * painter, QRect const & rect, int alignment, QPixmap const & pixmap) const
{
style->drawItemPixmap(painter, rect, alignment, pixmap);
}
void ProxyStyle::drawItemText(QPainter * painter, QRect const & rect, int alignment, QPalette const & pal, bool enabled,
QString const & text, QPalette::ColorRole textRole) const
{
style->drawItemText(painter, rect, alignment, pal, enabled, text, textRole);
}
void ProxyStyle::drawPrimitive(PrimitiveElement elem, QStyleOption const * option, QPainter * painter,
QWidget const * widget) const
{
style->drawPrimitive(elem, option, painter, widget);
}
QPixmap ProxyStyle::generatedIconPixmap(QIcon::Mode iconMode, QPixmap const & pixmap, QStyleOption const * option) const
{
return style->generatedIconPixmap(iconMode, pixmap, option);
}
QStyle::SubControl ProxyStyle::hitTestComplexControl(ComplexControl control, QStyleOptionComplex const * option,
QPoint const & pos, QWidget const * widget) const
{
return style->hitTestComplexControl(control, option, pos, widget);
}
QRect ProxyStyle::itemPixmapRect(QRect const & rect, int alignment, QPixmap const & pixmap) const
{
return style->itemPixmapRect(rect, alignment, pixmap);
}
QRect ProxyStyle::itemTextRect(QFontMetrics const & metrics, QRect const & rect, int alignment, bool enabled,
QString const & text) const
{
return style->itemTextRect(metrics, rect, alignment, enabled, text);
}
int ProxyStyle::pixelMetric(PixelMetric metric, QStyleOption const * option, QWidget const * widget) const
{
return style->pixelMetric(metric, option, widget);
}
void ProxyStyle::polish(QWidget * widget)
{
style->polish(widget);
}
void ProxyStyle::polish(QApplication * app)
{
style->polish(app);
}
void ProxyStyle::polish(QPalette & pal)
{
style->polish(pal);
}
QSize ProxyStyle::sizeFromContents(ContentsType type, QStyleOption const * option, QSize const & contentsSize,
QWidget const * widget) const
{
return style->sizeFromContents(type, option, contentsSize, widget);
}
QIcon ProxyStyle::standardIcon(StandardPixmap standardIcon, QStyleOption const * option, QWidget const * widget) const
{
return style->standardIcon(standardIcon, option, widget);
}
QPalette ProxyStyle::standardPalette() const
{
return style->standardPalette();
}
QPixmap ProxyStyle::standardPixmap(StandardPixmap standardPixmap, QStyleOption const * option,
QWidget const * widget) const
{
return style->standardPixmap(standardPixmap, option, widget);
}
int ProxyStyle::styleHint(StyleHint hint, QStyleOption const * option, QWidget const * widget,
QStyleHintReturn * returnData) const
{
return style->styleHint(hint, option, widget, returnData);
}
QRect ProxyStyle::subControlRect(ComplexControl control, QStyleOptionComplex const * option, SubControl subControl,
QWidget const * widget) const
{
return style->subControlRect(control, option, subControl, widget);
}
QRect ProxyStyle::subElementRect(SubElement element, QStyleOption const * option, QWidget const * widget) const
{
return style->subElementRect(element, option, widget);
}
void ProxyStyle::unpolish(QWidget * widget)
{
style->unpolish(widget);
}
void ProxyStyle::unpolish(QApplication * app)
{
style->unpolish(app);
}
int ProxyStyle::layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2,
Qt::Orientation orientation, QStyleOption const * option, QWidget const * widget) const
{
return style->layoutSpacing(control1, control2, orientation, option, widget);
}
} // namespace common
} // namespace qt

View file

@ -0,0 +1,55 @@
#pragma once
#include <QtWidgets/QStyle>
namespace qt
{
namespace common
{
class ProxyStyle : public QStyle
{
public:
explicit ProxyStyle(QStyle * p);
// QStyle overrides:
void drawComplexControl(ComplexControl control, QStyleOptionComplex const * option, QPainter * painter,
QWidget const * widget = 0) const override;
void drawControl(ControlElement element, QStyleOption const * option, QPainter * painter,
QWidget const * widget = 0) const override;
void drawItemPixmap(QPainter * painter, QRect const & rect, int alignment, QPixmap const & pixmap) const override;
void drawItemText(QPainter * painter, QRect const & rect, int alignment, QPalette const & pal, bool enabled,
QString const & text, QPalette::ColorRole textRole = QPalette::NoRole) const override;
void drawPrimitive(PrimitiveElement elem, QStyleOption const * option, QPainter * painter,
QWidget const * widget = 0) const override;
QPixmap generatedIconPixmap(QIcon::Mode iconMode, QPixmap const & pixmap, QStyleOption const * option) const override;
SubControl hitTestComplexControl(ComplexControl control, QStyleOptionComplex const * option, QPoint const & pos,
QWidget const * widget = 0) const override;
QRect itemPixmapRect(QRect const & rect, int alignment, QPixmap const & pixmap) const override;
QRect itemTextRect(QFontMetrics const & metrics, QRect const & rect, int alignment, bool enabled,
QString const & text) const override;
int pixelMetric(PixelMetric metric, QStyleOption const * option = 0, QWidget const * widget = 0) const override;
void polish(QWidget * widget) override;
void polish(QApplication * app) override;
void polish(QPalette & pal) override;
QSize sizeFromContents(ContentsType type, QStyleOption const * option, QSize const & contentsSize,
QWidget const * widget = 0) const override;
QIcon standardIcon(StandardPixmap standardIcon, QStyleOption const * option = 0,
QWidget const * widget = 0) const override;
QPalette standardPalette() const override;
QPixmap standardPixmap(StandardPixmap standardPixmap, QStyleOption const * option = 0,
QWidget const * widget = 0) const override;
int styleHint(StyleHint hint, QStyleOption const * option = 0, QWidget const * widget = 0,
QStyleHintReturn * returnData = 0) const override;
QRect subControlRect(ComplexControl control, QStyleOptionComplex const * option, SubControl subControl,
QWidget const * widget = 0) const override;
QRect subElementRect(SubElement element, QStyleOption const * option, QWidget const * widget = 0) const override;
void unpolish(QWidget * widget) override;
void unpolish(QApplication * app) override;
int layoutSpacing(QSizePolicy::ControlType control1, QSizePolicy::ControlType control2, Qt::Orientation orientation,
QStyleOption const * option = 0, QWidget const * widget = 0) const override;
private:
QStyle * style;
};
} // namespace common
} // namespace qt

View file

@ -0,0 +1,144 @@
#include "qt/qt_common/qtoglcontext.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/macros.hpp"
#include "base/math.hpp"
#include "drape/gl_functions.hpp"
#include <memory>
namespace qt
{
namespace common
{
QtRenderOGLContext::QtRenderOGLContext(QOpenGLContext * rootContext, QOffscreenSurface * surface)
: m_surface(surface)
, m_ctx(std::make_unique<QOpenGLContext>())
, m_isContextAvailable(false)
{
m_ctx->setFormat(rootContext->format());
m_ctx->setShareContext(rootContext);
m_ctx->create();
CHECK(m_ctx->isValid(), ());
}
void QtRenderOGLContext::Present()
{
GLFunctions::glFinish();
std::lock_guard<std::mutex> lock(m_frameMutex);
std::swap(m_frontFrame, m_backFrame);
m_frameUpdated = true;
}
void QtRenderOGLContext::MakeCurrent()
{
CHECK(m_ctx->makeCurrent(m_surface), ());
m_isContextAvailable = true;
}
void QtRenderOGLContext::DoneCurrent()
{
m_isContextAvailable = false;
m_ctx->doneCurrent();
}
void QtRenderOGLContext::SetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer)
{
if (framebuffer)
framebuffer->Bind();
else if (m_backFrame != nullptr)
m_backFrame->bind();
}
void QtRenderOGLContext::Resize(uint32_t w, uint32_t h)
{
CHECK(m_isContextAvailable, ());
// This function can't be called inside BeginRendering - EndRendering.
std::lock_guard lock(m_frameMutex);
auto const nw = static_cast<int>(math::NextPowOf2(w));
auto const nh = static_cast<int>(math::NextPowOf2(h));
if (nw <= m_width && nh <= m_height && m_backFrame != nullptr)
{
m_frameRect = QRectF(0.0, 0.0, w / static_cast<float>(m_width), h / static_cast<float>(m_height));
return;
}
m_width = nw;
m_height = nh;
m_frameRect = QRectF(0.0, 0.0, w / static_cast<float>(m_width), h / static_cast<float>(m_height));
m_backFrame = std::make_unique<QOpenGLFramebufferObject>(QSize(m_width, m_height), QOpenGLFramebufferObject::Depth);
m_frontFrame = std::make_unique<QOpenGLFramebufferObject>(QSize(m_width, m_height), QOpenGLFramebufferObject::Depth);
m_acquiredFrame =
std::make_unique<QOpenGLFramebufferObject>(QSize(m_width, m_height), QOpenGLFramebufferObject::Depth);
}
bool QtRenderOGLContext::AcquireFrame()
{
if (!m_isContextAvailable)
return false;
std::lock_guard lock(m_frameMutex);
// Render current acquired frame.
if (!m_frameUpdated)
return true;
// Update acquired frame.
m_acquiredFrameRect = m_frameRect;
std::swap(m_acquiredFrame, m_frontFrame);
m_frameUpdated = false;
return true;
}
QRectF const & QtRenderOGLContext::GetTexRect() const
{
return m_acquiredFrameRect;
}
GLuint QtRenderOGLContext::GetTextureHandle() const
{
if (!m_acquiredFrame)
return 0;
CHECK(!m_acquiredFrame->isBound(), ());
return m_acquiredFrame->texture();
}
QtUploadOGLContext::QtUploadOGLContext(QOpenGLContext * rootContext, QOffscreenSurface * surface)
: m_surface(surface)
, m_ctx(std::make_unique<QOpenGLContext>())
{
m_ctx->setFormat(rootContext->format());
m_ctx->setShareContext(rootContext);
m_ctx->create();
CHECK(m_ctx->isValid(), ());
}
void QtUploadOGLContext::MakeCurrent()
{
m_ctx->makeCurrent(m_surface);
}
void QtUploadOGLContext::DoneCurrent()
{
m_ctx->doneCurrent();
}
void QtUploadOGLContext::Present()
{
CHECK(false, ());
}
void QtUploadOGLContext::SetFramebuffer(ref_ptr<dp::BaseFramebuffer>)
{
CHECK(false, ());
}
} // namespace common
} // namespace qt

View file

@ -0,0 +1,66 @@
#pragma once
#include "drape/oglcontext.hpp"
#include <QOpenGLFramebufferObject>
#include <QtGui/QOffscreenSurface>
#include <QtGui/QOpenGLContext>
#include <atomic>
#include <memory>
#include <mutex>
namespace qt
{
namespace common
{
class QtRenderOGLContext : public dp::OGLContext
{
public:
QtRenderOGLContext(QOpenGLContext * rootContext, QOffscreenSurface * surface);
void Present() override;
void MakeCurrent() override;
void DoneCurrent() override;
void SetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer) override;
void Resize(uint32_t w, uint32_t h) override;
bool AcquireFrame();
GLuint GetTextureHandle() const;
QRectF const & GetTexRect() const;
private:
QOffscreenSurface * m_surface = nullptr;
std::unique_ptr<QOpenGLContext> m_ctx;
std::unique_ptr<QOpenGLFramebufferObject> m_frontFrame;
std::unique_ptr<QOpenGLFramebufferObject> m_backFrame;
std::unique_ptr<QOpenGLFramebufferObject> m_acquiredFrame;
QRectF m_acquiredFrameRect = QRectF(0.0, 0.0, 0.0, 0.0);
QRectF m_frameRect = QRectF(0.0, 0.0, 0.0, 0.0);
bool m_frameUpdated = false;
std::atomic<bool> m_isContextAvailable;
int m_width = 0;
int m_height = 0;
std::mutex m_frameMutex;
};
class QtUploadOGLContext : public dp::OGLContext
{
public:
QtUploadOGLContext(QOpenGLContext * rootContext, QOffscreenSurface * surface);
void Present() override;
void MakeCurrent() override;
void DoneCurrent() override;
void SetFramebuffer(ref_ptr<dp::BaseFramebuffer> framebuffer) override;
private:
QOffscreenSurface * m_surface = nullptr; // non-owning ptr
std::unique_ptr<QOpenGLContext> m_ctx;
};
} // namespace common
} // namespace qt

View file

@ -0,0 +1,78 @@
#include "qt/qt_common/qtoglcontextfactory.hpp"
#include "base/assert.hpp"
#include <memory>
namespace qt
{
namespace common
{
QtOGLContextFactory::QtOGLContextFactory(QOpenGLContext * rootContext) : m_rootContext(rootContext)
{
m_uploadSurface = CreateSurface();
m_drawSurface = CreateSurface();
}
QtOGLContextFactory::~QtOGLContextFactory()
{
m_drawContext.reset();
m_uploadContext.reset();
m_drawSurface->destroy();
m_uploadSurface->destroy();
}
void QtOGLContextFactory::PrepareToShutdown()
{
m_preparedToShutdown = true;
}
bool QtOGLContextFactory::AcquireFrame()
{
if (m_preparedToShutdown || !m_drawContext)
return false;
return m_drawContext->AcquireFrame();
}
QRectF const & QtOGLContextFactory::GetTexRect() const
{
ASSERT(m_drawContext != nullptr, ());
return m_drawContext->GetTexRect();
}
GLuint QtOGLContextFactory::GetTextureHandle() const
{
ASSERT(m_drawContext != nullptr, ());
return m_drawContext->GetTextureHandle();
}
dp::GraphicsContext * QtOGLContextFactory::GetDrawContext()
{
if (!m_drawContext)
m_drawContext = std::make_unique<QtRenderOGLContext>(m_rootContext, m_drawSurface.get());
return m_drawContext.get();
}
dp::GraphicsContext * QtOGLContextFactory::GetResourcesUploadContext()
{
if (!m_uploadContext)
m_uploadContext = std::make_unique<QtUploadOGLContext>(m_rootContext, m_uploadSurface.get());
return m_uploadContext.get();
}
std::unique_ptr<QOffscreenSurface> QtOGLContextFactory::CreateSurface()
{
QSurfaceFormat format = m_rootContext->format();
auto result = std::make_unique<QOffscreenSurface>(m_rootContext->screen());
result->setFormat(format);
result->create();
ASSERT(result->isValid(), ());
return result;
}
} // namespace common
} // namespace qt

View file

@ -0,0 +1,43 @@
#pragma once
#include "drape/graphics_context_factory.hpp"
#include "qt/qt_common/qtoglcontext.hpp"
#include <QtGui/QOpenGLContext>
#include <memory>
namespace qt
{
namespace common
{
class QtOGLContextFactory : public dp::GraphicsContextFactory
{
public:
QtOGLContextFactory(QOpenGLContext * rootContext);
~QtOGLContextFactory() override;
void PrepareToShutdown();
bool AcquireFrame();
GLuint GetTextureHandle() const;
QRectF const & GetTexRect() const;
// dp::GraphicsContextFactory overrides:
dp::GraphicsContext * GetDrawContext() override;
dp::GraphicsContext * GetResourcesUploadContext() override;
bool IsDrawContextCreated() const override { return m_drawContext != nullptr; }
bool IsUploadContextCreated() const override { return m_uploadContext != nullptr; }
private:
std::unique_ptr<QOffscreenSurface> CreateSurface();
QOpenGLContext * m_rootContext;
std::unique_ptr<QtRenderOGLContext> m_drawContext;
std::unique_ptr<QOffscreenSurface> m_drawSurface;
std::unique_ptr<QtUploadOGLContext> m_uploadContext;
std::unique_ptr<QOffscreenSurface> m_uploadSurface;
bool m_preparedToShutdown = false;
};
} // namespace common
} // namespace qt

BIN
qt/qt_common/res/minus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
qt/qt_common/res/plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,22 @@
<RCC>
<qresource prefix="/common">
<file>minus.png</file>
<file>plus.png</file>
<file>spinner1.png</file>
<file>spinner2.png</file>
<file>spinner3.png</file>
<file>spinner4.png</file>
<file>spinner5.png</file>
<file>spinner6.png</file>
<file>spinner7.png</file>
<file>spinner8.png</file>
<file>spinner9.png</file>
<file>spinner10.png</file>
<file>spinner11.png</file>
<file>spinner12.png</file>
<file>shaders/gl_150.fsh.glsl</file>
<file>shaders/gl_150.vsh.glsl</file>
<file>shaders/gles_300.fsh.glsl</file>
<file>shaders/gles_300.vsh.glsl</file>
</qresource>
</RCC>

View file

@ -0,0 +1,10 @@
#version 150 core
uniform sampler2D u_sampler;
in vec2 v_texCoord;
out vec4 v_FragColor;
void main()
{
v_FragColor = vec4(texture(u_sampler, v_texCoord).rgb, 1.0);
}

View file

@ -0,0 +1,11 @@
#version 150 core
in vec4 a_position;
uniform vec2 u_samplerSize;
out vec2 v_texCoord;
void main()
{
v_texCoord = vec2(a_position.z * u_samplerSize.x, a_position.w * u_samplerSize.y);
gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);
}

View file

@ -0,0 +1,16 @@
#version 300 es
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
uniform sampler2D u_sampler;
in vec2 v_texCoord;
out vec4 v_FragColor;
void main()
{
v_FragColor = vec4(texture(u_sampler, v_texCoord).rgb, 1.0);
}

View file

@ -0,0 +1,17 @@
#version 300 es
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
in vec4 a_position;
uniform vec2 u_samplerSize;
out vec2 v_texCoord;
void main()
{
v_texCoord = vec2(a_position.z * u_samplerSize.x, a_position.w * u_samplerSize.y);
gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

View file

@ -0,0 +1,79 @@
#include "qt/qt_common/scale_slider.hpp"
#include "qt/qt_common/map_widget.hpp"
#include "qt/qt_common/proxy_style.hpp"
#include "indexer/scales.hpp"
#include "base/math.hpp"
#include <QtWidgets/QToolBar>
#include <cmath>
#include <memory>
namespace qt
{
namespace common
{
namespace
{
class MyProxyStyle : public ProxyStyle
{
public:
explicit MyProxyStyle(QStyle * parent) : ProxyStyle(parent) {}
int styleHint(StyleHint hint, QStyleOption const * option, QWidget const * widget,
QStyleHintReturn * returnData) const override
{
if (hint == SH_Slider_AbsoluteSetButtons)
return Qt::LeftButton;
return ProxyStyle::styleHint(hint, option, widget, returnData);
}
};
} // namespace
ScaleSlider::ScaleSlider(Qt::Orientation orient, QWidget * parent) : QSlider(orient, parent), m_factor(20)
{
setStyle(new MyProxyStyle(style()));
SetRange(2, scales::GetUpperScale());
setTickPosition(QSlider::TicksRight);
}
// static
void ScaleSlider::Embed(Qt::Orientation orient, QToolBar & toolBar, MapWidget & mapWidget)
{
toolBar.addAction(QIcon(":/common/plus.png"), tr("Scale +"), &mapWidget, SLOT(ScalePlus()));
{
auto slider = std::make_unique<ScaleSlider>(orient, &toolBar);
mapWidget.BindSlider(*slider);
toolBar.addWidget(slider.release());
}
toolBar.addAction(QIcon(":/common/minus.png"), tr("Scale -"), &mapWidget, SLOT(ScaleMinus()));
}
double ScaleSlider::GetScaleFactor() const
{
double const oldValue = value();
double const newValue = sliderPosition();
if (oldValue == newValue)
return 1.0;
double const f = pow(2, fabs(oldValue - newValue) / m_factor);
return (newValue > oldValue ? f : 1.0 / f);
}
void ScaleSlider::SetPosWithBlockedSignals(double pos)
{
bool const blocked = signalsBlocked();
blockSignals(true);
setSliderPosition(std::lround(pos * m_factor));
blockSignals(blocked);
}
void ScaleSlider::SetRange(int low, int up)
{
setRange(low * m_factor, up * m_factor);
}
} // namespace common
} // namespace qt

View file

@ -0,0 +1,30 @@
#pragma once
#include <QtCore/Qt>
#include <QtWidgets/QSlider>
class QToolBar;
namespace qt
{
namespace common
{
class MapWidget;
class ScaleSlider : public QSlider
{
public:
ScaleSlider(Qt::Orientation orient, QWidget * parent);
static void Embed(Qt::Orientation orient, QToolBar & toolBar, MapWidget & mapWidget);
double GetScaleFactor() const;
void SetPosWithBlockedSignals(double pos);
private:
void SetRange(int low, int up);
int m_factor;
};
} // namespace common
} // namespace qt

52
qt/qt_common/spinner.cpp Normal file
View file

@ -0,0 +1,52 @@
#include "qt/qt_common/spinner.hpp"
#include <QtCore/QString>
#include <QtCore/QTimer>
#include <QtCore/Qt>
namespace
{
int constexpr kMinSpinnerPixmap = 1;
int constexpr kMaxSpinnerPixmap = 12;
int constexpr kTimeoutMs = 100;
} // namespace
Spinner::Spinner()
{
for (int i = kMinSpinnerPixmap; i <= kMaxSpinnerPixmap; ++i)
{
auto const path = ":common/spinner" + QString::number(i) + ".png";
m_pixmaps.emplace_back(path);
}
setEnabled(false);
setAlignment(Qt::AlignCenter);
QSizePolicy policy(QSizePolicy::Fixed, QSizePolicy::Fixed);
policy.setRetainSizeWhenHidden(true);
setSizePolicy(policy);
setMinimumSize(m_pixmaps.front().size());
setPixmap(m_pixmaps.front());
m_timer = new QTimer(this /* parent */);
connect(m_timer, &QTimer::timeout, [this]()
{
m_progress = (m_progress + 1) % m_pixmaps.size();
setPixmap(m_pixmaps[m_progress]);
});
}
void Spinner::Show()
{
m_progress = 0;
setPixmap(m_pixmaps[m_progress]);
m_timer->start(kTimeoutMs);
show();
}
void Spinner::Hide()
{
m_timer->stop();
hide();
}

23
qt/qt_common/spinner.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <cstddef>
#include <vector>
#include <QtGui/QPixmap>
#include <QtWidgets/QLabel>
class Spinner : private QLabel
{
public:
Spinner();
void Show();
void Hide();
QLabel & AsWidget() { return static_cast<QLabel &>(*this); }
private:
std::vector<QPixmap> m_pixmaps;
QTimer * m_timer = nullptr;
size_t m_progress = 0;
};

View file

@ -0,0 +1,37 @@
#include "qt/qt_common/text_dialog.hpp"
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTextEdit>
#include <QtWidgets/QVBoxLayout>
TextDialog::TextDialog(QWidget * parent, QString const & htmlOrText, QString const & title) : QDialog(parent)
{
auto * textEdit = new QTextEdit(this);
textEdit->setReadOnly(true);
textEdit->setHtml(htmlOrText);
auto * closeButton = new QPushButton("Close");
closeButton->setDefault(true);
connect(closeButton, &QAbstractButton::clicked, this, &TextDialog::OnClose);
auto * dbb = new QDialogButtonBox();
dbb->addButton(closeButton, QDialogButtonBox::RejectRole);
auto * vBoxLayout = new QVBoxLayout(this);
vBoxLayout->addWidget(textEdit);
vBoxLayout->addWidget(dbb);
setLayout(vBoxLayout);
setWindowTitle(title);
if (htmlOrText.size() > 10000)
setWindowState(Qt::WindowMaximized);
else
resize(parent->size());
}
void TextDialog::OnClose()
{
reject();
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <QtWidgets/QDialog>
/// A reusable dialog that shows text or HTML.
class TextDialog : public QDialog
{
Q_OBJECT
public:
TextDialog(QWidget * parent, QString const & htmlOrText, QString const & title = "");
private slots:
void OnClose();
};

BIN
qt/res/bookmark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 960 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
qt/res/bug.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
qt/res/busy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
qt/res/chart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
qt/res/city_boundaries.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

BIN
qt/res/city_roads.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
qt/res/clear-route.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
qt/res/clear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
qt/res/down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
qt/res/download.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
qt/res/geom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
qt/res/guides.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

BIN
qt/res/isolines.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 B

BIN
qt/res/layers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

BIN
qt/res/left.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

View file

@ -0,0 +1,15 @@
[Desktop Entry]
Type=Application
Version=1.0
Name=CoMaps
Comment=Detailed Offline Maps of the World
Comment[ast]=Mapes detallaos del mundu ensin conexón
Comment[ca]=Mapes detallats del món sense connexió
Comment[es]=Mapas detallados del mundo sin conexión
Comment[ru]=Подробная оффлайновая карта мира
Icon=comaps
TryExec=CoMaps
Exec=CoMaps
Categories=Maps;Education;Geography;Geoscience;Qt;
Keywords=Map;Maps;Offline Maps;CoMaps;OSM;OpenStreetMap;
X-KDE-FormFactor=desktop;tablet;handset;

BIN
qt/res/load.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
qt/res/location-search.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

BIN
qt/res/location.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Some files were not shown because too many files have changed in this diff Show more