Merge branch 'main' into feature/png-metadata

This commit is contained in:
Kai Richardson 2021-09-29 10:43:53 -07:00
commit 74a89085d8
33 changed files with 455 additions and 96 deletions

View File

@ -56,8 +56,13 @@ jobs:
if [[ "${{ runner.os }}" == "Linux" ]] ; then
export XVFB=xvfb-run
fi
if [[ "${{ github.base_ref }}" == "beta" ]] || [[ "${{ github.ref }}" == "refs/heads/beta" ]] ; then
export TESTS_BRANCH=beta
else
export TESTS_BRANCH=main
fi
cd build
export ASEPRITE=$PWD/bin/aseprite
git clone --recursive https://github.com/aseprite/tests.git
git clone --branch $TESTS_BRANCH --recursive https://github.com/aseprite/tests.git
cd tests
$XVFB bash run-tests.sh

View File

@ -79,8 +79,14 @@ option(ENABLE_UI "Compile UI (turn off to compile CLI-only version)" o
option(FULLSCREEN_PLATFORM "Enable fullscreen by default" off)
option(ENABLE_CLANG_TIDY "Enable static analysis" off)
option(ENABLE_CCACHE "Use CCache to improve recompilation speed (optional)" on)
option(ENABLE_SENTRY "Use Sentry SDK to report crashes" off)
set(CUSTOM_WEBSITE_URL "" CACHE STRING "Enable custom local webserver to check updates")
if(ENABLE_SENTRY)
set(SENTRY_DIR "" CACHE STRING "Sentry native location")
set(SENTRY_DNS "" CACHE STRING "Sentry DNS URL")
endif()
if(ENABLE_NEWS OR ENABLE_UPDATER)
set(REQUIRE_CURL ON)
else()

View File

@ -723,6 +723,9 @@
<style id="workspace_tabs">
<background color="workspace" />
</style>
<style id="workspace_check_box" extends="check_box" padding="4">
<text color="workspace_text" align="left middle" x="14" />
</style>
<style id="tab">
<background part="tab_normal" align="middle" />
<background part="tab_active" align="middle" state="focus" />

View File

@ -731,11 +731,16 @@ Recover files from crashed sessions or
closed sprite that were not saved in
previous sessions.
END
share_crashdb = Share crash data with Aseprite developers
share_crashdb_tooltip = <<<END
Check to share crash data with Aseprite developers automatically.
This will help to find new bugs and improve the general stability
of Aseprite for all users.
END
recent_files = Recent files:
recent_folders = Recent folders:
news = News:
checking_updates = Checking Updates...
is_up_to_date = {0} is up to date
new_version_available = New {0} v{1} available!
[import_sprite_sheet]

View File

@ -1,5 +1,5 @@
<!-- Aseprite -->
<!-- Copyright (C) 2019 Igara Studio S.A. -->
<!-- Copyright (C) 2019-2021 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2017 David Capello -->
<gui>
<vbox noborders="true" id="home_view" border="4" childspacing="2" expansive="true">
@ -14,6 +14,8 @@
</vbox>
<boxfiller />
<vbox border="4">
<check id="share_crashdb" text="@.share_crashdb"
tooltip="@.share_crashdb_tooltip" style="workspace_check_box" />
<link id="check_update" text="" style="workspace_link" />
</vbox>
</hbox>

View File

@ -68,6 +68,9 @@
text="@.color_bar_entries_separator"
tooltip="@.color_bar_entries_separator"
pref="color_bar.entries_separator" />
<check id="share_crashdb"
text="@home_view.share_crashdb"
tooltip="@home_view.share_crashdb_tooltip" />
<separator horizontal="true" />
<link id="locate_file" text="@.locate_file" />

View File

@ -951,6 +951,31 @@ possible. They may also add themselves to the list below.
*/
```
# [Sentry](https://sentry.io)
```
Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors.
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
# [skia](https://skia.org)
```

2
laf

@ -1 +1 @@
Subproject commit 0d1ba15a6af387f4fb57889e59ffd3e8fab29eda
Subproject commit f58ece8248e2ebf6124b28ec49044be913f57292

View File

@ -221,7 +221,6 @@ if(ENABLE_UI)
commands/cmd_duplicate_view.cpp
commands/cmd_exit.cpp
commands/cmd_eyedropper.cpp
commands/cmd_fill_and_stroke.cpp
commands/cmd_fit_screen.cpp
commands/cmd_frame_properties.cpp
commands/cmd_frame_tag_properties.cpp
@ -402,6 +401,13 @@ if(ENABLE_UI)
endif()
endif()
set(send_crash_files)
if(ENABLE_SENTRY)
set(send_crash_files sentry_wrapper.cpp)
else()
set(send_crash_files send_crash.cpp)
endif()
add_library(app-lib
active_site_handler.cpp
app.cpp
@ -501,6 +507,7 @@ add_library(app-lib
commands/cmd_color_quantization.cpp
commands/cmd_crop.cpp
commands/cmd_export_sprite_sheet.cpp
commands/cmd_fill_and_stroke.cpp
commands/cmd_flatten_layers.cpp
commands/cmd_flip.cpp
commands/cmd_import_sprite_sheet.cpp
@ -575,7 +582,6 @@ add_library(app-lib
res/resources_loader.cpp
resource_finder.cpp
restore_visible_layers.cpp
send_crash.cpp
shade.cpp
site.cpp
snap_to_grid.cpp
@ -617,6 +623,7 @@ add_library(app-lib
util/wrap_point.cpp
xml_document.cpp
xml_exception.cpp
${send_crash_files}
${ui_app_files}
${app_platform_files}
${data_recovery_files}
@ -677,3 +684,12 @@ if(ENABLE_STEAM)
add_definitions(-DENABLE_STEAM)
target_link_libraries(app-lib steam-lib)
endif()
if(ENABLE_SENTRY)
target_compile_definitions(app-lib PUBLIC
-DENABLE_SENTRY
-DSENTRY_BUILD_STATIC=1
-DSENTRY_DNS="${SENTRY_DNS}")
add_subdirectory(${SENTRY_DIR} sentry)
target_link_libraries(app-lib sentry)
endif()

View File

@ -418,8 +418,10 @@ void App::run()
checkUpdate.launch();
#endif
#if !ENABLE_SENTRY
app::SendCrash sendCrash;
sendCrash.search();
#endif
// Keep the console alive the whole program execute (just in case
// we've to print errors).

View File

@ -379,13 +379,13 @@ void AppBrushes::load(const std::string& filename)
// Pixel-perfect
if (TiXmlElement* pixelPerfectElem = brushElem->FirstChildElement("pixelperfect")) {
pixelPerfect = bool_attr_is_true(pixelPerfectElem, "value");
pixelPerfect = bool_attr(pixelPerfectElem, "value", false);
flags |= int(BrushSlot::Flags::PixelPerfect);
}
// Image color (enabled by default for backward compatibility)
if (!brushElem->Attribute("imagecolor") ||
bool_attr_is_true(brushElem, "imagecolor"))
bool_attr(brushElem, "imagecolor", false))
flags |= int(BrushSlot::Flags::ImageColor);
if (flags != 0)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2020-2021 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -21,6 +21,10 @@
#include "base/version.h"
#include "ver/info.h"
#if ENABLE_SENTRY
#include "app/sentry_wrapper.h"
#endif
#include <ctime>
#include <sstream>
@ -113,6 +117,14 @@ CheckUpdateThreadLauncher::~CheckUpdateThreadLauncher()
void CheckUpdateThreadLauncher::launch()
{
if (m_uuid.empty())
m_uuid = m_preferences.updater.uuid();
#if ENABLE_SENTRY
if (!m_uuid.empty())
Sentry::setUserID(m_uuid);
#endif
// In this case we are in the "wait days" period, so we don't check
// for updates.
if (!m_doCheck) {
@ -120,9 +132,6 @@ void CheckUpdateThreadLauncher::launch()
return;
}
if (m_uuid.empty())
m_uuid = m_preferences.updater.uuid();
m_delegate->onCheckingUpdates();
m_bgJob.reset(new CheckUpdateBackgroundJob);
@ -168,6 +177,11 @@ void CheckUpdateThreadLauncher::onMonitoringTick()
if (!m_response.getUuid().empty()) {
m_uuid = m_response.getUuid();
m_preferences.updater.uuid(m_uuid);
#if ENABLE_SENTRY
if (!m_uuid.empty())
Sentry::setUserID(m_uuid);
#endif
}
// Set the date of the last "check for updates" and the "WaitDays" parameter.

View File

@ -364,9 +364,14 @@ void CanvasSizeCommand::onExecute(Context* context)
Preferences::instance().canvasSize.trimOutside(params.trimOutside());
bounds.enlarge(
gfx::Border(params.left(), params.top(),
params.right(), params.bottom()));
if (params.bounds.isSet()) {
bounds = params.bounds();
}
else {
bounds.enlarge(
gfx::Border(params.left(), params.top(),
params.right(), params.bottom()));
}
}
#endif

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
@ -51,10 +52,12 @@ bool FillCommand::onEnabled(Context* ctx)
ContextFlags::ActiveLayerIsImage)) {
return true;
}
#if ENABLE_UI
else if (current_editor &&
current_editor->isMovingPixels()) {
return true;
}
#endif
else
return false;
}

View File

@ -27,6 +27,7 @@
#include "app/resource_finder.h"
#include "app/tx.h"
#include "app/ui/color_button.h"
#include "app/ui/main_window.h"
#include "app/ui/pref_widget.h"
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
@ -42,6 +43,10 @@
#include "render/render.h"
#include "ui/ui.h"
#if ENABLE_SENTRY
#include "app/sentry_wrapper.h"
#endif
#include "options.xml.h"
namespace app {
@ -490,6 +495,13 @@ public:
else
locateCrashFolder()->setVisible(false);
// Share crashdb
#if ENABLE_SENTRY
shareCrashdb()->setSelected(Sentry::consentGiven());
#else
shareCrashdb()->setVisible(false);
#endif
// Undo preferences
limitUndo()->Click.connect([this]{ onLimitUndoCheck(); });
limitUndo()->setSelected(m_pref.undo.sizeLimit() != 0);
@ -541,6 +553,15 @@ public:
sendMessage(msg);
}
// Share crashdb
#if ENABLE_SENTRY
if (shareCrashdb()->isSelected())
Sentry::giveConsent();
else
Sentry::revokeConsent();
App::instance()->mainWindow()->updateConsentCheckbox();
#endif
// Update language
Strings::instance()->setCurrentLanguage(
language()->getItemText(language()->getSelectedItemIndex()));

View File

@ -19,6 +19,7 @@ FOR_EACH_COMMAND(CopyColors)
FOR_EACH_COMMAND(CropSprite)
FOR_EACH_COMMAND(Despeckle)
FOR_EACH_COMMAND(ExportSpriteSheet)
FOR_EACH_COMMAND(Fill)
FOR_EACH_COMMAND(FlattenLayers)
FOR_EACH_COMMAND(Flip)
FOR_EACH_COMMAND(HueSaturation)
@ -41,6 +42,7 @@ FOR_EACH_COMMAND(SaveFile)
FOR_EACH_COMMAND(SaveFileAs)
FOR_EACH_COMMAND(SaveFileCopyAs)
FOR_EACH_COMMAND(SpriteSize)
FOR_EACH_COMMAND(Stroke)
FOR_EACH_COMMAND(Undo)
#ifdef ENABLE_UI
@ -67,7 +69,6 @@ FOR_EACH_COMMAND(DuplicateSprite)
FOR_EACH_COMMAND(DuplicateView)
FOR_EACH_COMMAND(Exit)
FOR_EACH_COMMAND(Eyedropper)
FOR_EACH_COMMAND(Fill)
FOR_EACH_COMMAND(FitScreen)
FOR_EACH_COMMAND(FrameProperties)
FOR_EACH_COMMAND(FrameTagProperties)
@ -151,7 +152,6 @@ FOR_EACH_COMMAND(ShowSlices)
FOR_EACH_COMMAND(SliceProperties)
FOR_EACH_COMMAND(SnapToGrid)
FOR_EACH_COMMAND(SpriteProperties)
FOR_EACH_COMMAND(Stroke)
FOR_EACH_COMMAND(SwapCheckerboardColors)
FOR_EACH_COMMAND(SwitchColors)
FOR_EACH_COMMAND(SymmetryMode)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2020-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -16,6 +16,8 @@
namespace app {
#if !ENABLE_SENTRY
class SendCrash
#ifdef ENABLE_UI
: public INotificationDelegate
@ -43,6 +45,8 @@ namespace app {
std::string m_dumpFilename;
};
#endif // !ENABLE_SENTRY
} // namespace app
#endif // APP_SEND_CRASH_H_INCLUDED

113
src/app/sentry_wrapper.cpp Normal file
View File

@ -0,0 +1,113 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/sentry_wrapper.h"
#include "app/resource_finder.h"
#include "base/fs.h"
#include "base/string.h"
#include "ver/info.h"
#include "sentry.h"
namespace app {
// Directory where Sentry database is saved.
std::string Sentry::m_dbdir;
void Sentry::init()
{
sentry_options_t* options = sentry_options_new();
sentry_options_set_dsn(options, SENTRY_DNS);
std::string release = "aseprite@";
release += get_app_version();
sentry_options_set_release(options, release.c_str());
#if _DEBUG
sentry_options_set_debug(options, 1);
#endif
setupDirs(options);
// We require the user consent to upload files.
sentry_options_set_require_user_consent(options, 1);
if (sentry_init(options) == 0)
m_init = true;
}
Sentry::~Sentry()
{
if (m_init)
sentry_close();
}
// static
void Sentry::setUserID(const std::string& uuid)
{
sentry_value_t user = sentry_value_new_object();
sentry_value_set_by_key(user, "id", sentry_value_new_string(uuid.c_str()));
sentry_set_user(user);
}
// static
bool Sentry::requireConsent()
{
return (sentry_user_consent_get() != SENTRY_USER_CONSENT_GIVEN);
}
// static
bool Sentry::consentGiven()
{
return (sentry_user_consent_get() == SENTRY_USER_CONSENT_GIVEN);
}
// static
void Sentry::giveConsent()
{
sentry_user_consent_give();
}
// static
void Sentry::revokeConsent()
{
sentry_user_consent_revoke();
}
// static
bool Sentry::areThereCrashesToReport()
{
if (m_dbdir.empty())
return false;
for (auto f : base::list_files(base::join_path(m_dbdir, "completed"))) {
if (base::get_file_extension(f) == "dmp")
return true; // At least one .dmp file in the completed/ directory
}
return false;
}
void Sentry::setupDirs(sentry_options_t* options)
{
ResourceFinder rf;
rf.includeUserDir("crashdb");
const std::string dir = rf.getFirstOrCreateDefault();
#if SENTRY_PLATFORM_WINDOWS
sentry_options_set_database_pathw(options, base::from_utf8(dir).c_str());
#else
sentry_options_set_database_path(options, dir.c_str());
#endif
m_dbdir = dir;
}
} // namespace app

45
src/app/sentry_wrapper.h Normal file
View File

@ -0,0 +1,45 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_SENTRY_WRAPPER_H
#define APP_SENTRY_WRAPPER_H
#if !ENABLE_SENTRY
#error ENABLE_SENTRY must be defined
#endif
#include "sentry.h"
#include <string>
namespace app {
class Sentry {
public:
void init();
~Sentry();
static void setUserID(const std::string& uuid);
static bool requireConsent();
static bool consentGiven();
static void giveConsent();
static void revokeConsent();
// Returns true if there are some crash to report. Used to display
// the "give consent" check box for first time.
static bool areThereCrashesToReport();
private:
void setupDirs(sentry_options_t* options);
bool m_init = false;
static std::string m_dbdir;
};
} // namespace app
#endif // APP_SENTRY_WRAPPER_H

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -38,6 +38,10 @@
#include "app/ui/news_listbox.h"
#endif
#if ENABLE_SENTRY
#include "app/sentry_wrapper.h"
#endif
namespace app {
using namespace ui;
@ -66,6 +70,23 @@ HomeView::HomeView()
#endif
checkUpdate()->setVisible(false);
shareCrashdb()->setVisible(false);
#if ENABLE_SENTRY
// Show this option in home tab only when we require consent for the
// first time and there is crash data available to report
if (Sentry::requireConsent() &&
Sentry::areThereCrashesToReport()) {
shareCrashdb()->setVisible(true);
shareCrashdb()->Click.connect(
[this]{
if (shareCrashdb()->isSelected())
Sentry::giveConsent();
else
Sentry::revokeConsent();
});
}
#endif
InitTheme.connect(
[this]{
@ -101,6 +122,21 @@ void HomeView::dataRecoverySessionsAreReady()
#endif
}
#if ENABLE_SENTRY
void HomeView::updateConsentCheckbox()
{
if (Sentry::requireConsent()) {
shareCrashdb()->setVisible(true);
shareCrashdb()->setSelected(false);
}
else if (Sentry::consentGiven()) {
shareCrashdb()->setVisible(false);
shareCrashdb()->setSelected(true);
}
layout();
}
#endif
std::string HomeView::getTabText()
{
return Strings::home_view_title();
@ -176,9 +212,7 @@ void HomeView::onCheckingUpdates()
void HomeView::onUpToDate()
{
checkUpdate()->setText(
fmt::format(Strings::home_view_is_up_to_date(), get_app_name()));
checkUpdate()->setVisible(true);
checkUpdate()->setVisible(false);
layout();
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -46,6 +46,10 @@ namespace app {
// function is called.
void dataRecoverySessionsAreReady();
#if ENABLE_SENTRY
void updateConsentCheckbox();
#endif
// TabView implementation
std::string getTabText() override;
TabIcon getTabIcon() override;

View File

@ -418,7 +418,7 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
while (xmlKey) {
const char* command_name = xmlKey->Attribute("command");
const char* command_key = get_shortcut(xmlKey);
bool removed = bool_attr_is_true(xmlKey, "removed");
bool removed = bool_attr(xmlKey, "removed", false);
if (command_name) {
Command* command = Commands::instance()->byId(command_name);
@ -481,7 +481,7 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
while (xmlKey) {
const char* tool_id = xmlKey->Attribute("tool");
const char* tool_key = get_shortcut(xmlKey);
bool removed = bool_attr_is_true(xmlKey, "removed");
bool removed = bool_attr(xmlKey, "removed", false);
if (tool_id) {
tools::Tool* tool = App::instance()->toolBox()->getToolById(tool_id);
@ -509,7 +509,7 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
while (xmlKey) {
const char* tool_id = xmlKey->Attribute("tool");
const char* tool_key = get_shortcut(xmlKey);
bool removed = bool_attr_is_true(xmlKey, "removed");
bool removed = bool_attr(xmlKey, "removed", false);
if (tool_id) {
tools::Tool* tool = App::instance()->toolBox()->getToolById(tool_id);
@ -537,7 +537,7 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
while (xmlKey) {
const char* action_id = xmlKey->Attribute("action");
const char* action_key = get_shortcut(xmlKey);
bool removed = bool_attr_is_true(xmlKey, "removed");
bool removed = bool_attr(xmlKey, "removed", false);
if (action_id) {
KeyAction action = base::convert_to<KeyAction, std::string>(action_id);
@ -565,7 +565,7 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
while (xmlKey) {
const char* action_id = xmlKey->Attribute("action");
const char* action_key = get_shortcut(xmlKey);
bool removed = bool_attr_is_true(xmlKey, "removed");
bool removed = bool_attr(xmlKey, "removed", false);
if (action_id) {
WheelAction action = base::convert_to<WheelAction, std::string>(action_id);

View File

@ -223,6 +223,13 @@ CheckUpdateDelegate* MainWindow::getCheckUpdateDelegate()
}
#endif
#if ENABLE_SENTRY
void MainWindow::updateConsentCheckbox()
{
getHomeView()->updateConsentCheckbox();
}
#endif
void MainWindow::showNotification(INotificationDelegate* del)
{
m_notifications->addLink(del);

View File

@ -66,6 +66,9 @@ namespace app {
#ifdef ENABLE_UPDATER
CheckUpdateDelegate* getCheckUpdateDelegate();
#endif
#if ENABLE_SENTRY
void updateConsentCheckbox();
#endif
void start();
void showNotification(INotificationDelegate* del);

View File

@ -168,7 +168,7 @@ static FontData* load_font(std::map<std::string, FontData*>& fonts,
const char* fileStr = xmlFont->Attribute("file");
bool antialias = true;
if (xmlFont->Attribute("antialias"))
antialias = bool_attr_is_true(xmlFont, "antialias");
antialias = bool_attr(xmlFont, "antialias", false);
std::string fontFilename;
if (platformFileStr)

View File

@ -135,8 +135,8 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
widget = new Panel();
}
else if (elem_name == "box") {
bool horizontal = bool_attr_is_true(elem, "horizontal");
bool vertical = bool_attr_is_true(elem, "vertical");
bool horizontal = bool_attr(elem, "horizontal", false);
bool vertical = bool_attr(elem, "vertical", false);
int align = (horizontal ? HORIZONTAL: vertical ? VERTICAL: 0);
if (!widget)
@ -173,11 +173,11 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
}
}
bool left = bool_attr_is_true(elem, "left");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool closewindow = bool_attr_is_true(elem, "closewindow");
bool left = bool_attr(elem, "left", false);
bool right = bool_attr(elem, "right", false);
bool top = bool_attr(elem, "top", false);
bool bottom = bool_attr(elem, "bottom", false);
bool closewindow = bool_attr(elem, "closewindow", false);
widget->setAlign((left ? LEFT: (right ? RIGHT: CENTER)) |
(top ? TOP: (bottom ? BOTTOM: MIDDLE)));
@ -214,10 +214,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
}
}
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool center = bool_attr(elem, "center", false);
bool right = bool_attr(elem, "right", false);
bool top = bool_attr(elem, "top", false);
bool bottom = bool_attr(elem, "bottom", false);
widget->setAlign((center ? CENTER:
(right ? RIGHT: LEFT)) |
@ -228,7 +228,7 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
if (!widget)
widget = new ComboBox();
bool editable = bool_attr_is_true(elem, "editable");
bool editable = bool_attr(elem, "editable", false);
if (editable)
((ComboBox*)widget)->setEditable(true);
}
@ -240,7 +240,7 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
const char* suffix = elem->Attribute("suffix");
const char* decimals = elem->Attribute("decimals");
const bool readonly = bool_attr_is_true(elem, "readonly");
const bool readonly = bool_attr(elem, "readonly", false);
widget = (elem_name == "expr" ?
new ExprEntry:
@ -257,7 +257,7 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
}
else if (elem_name == "grid") {
const char *columns = elem->Attribute("columns");
bool same_width_columns = bool_attr_is_true(elem, "same_width_columns");
bool same_width_columns = bool_attr(elem, "same_width_columns", false);
if (columns != NULL) {
widget = new Grid(strtol(columns, NULL, 10),
@ -268,10 +268,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
if (!widget)
widget = new Label("");
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool center = bool_attr(elem, "center", false);
bool right = bool_attr(elem, "right", false);
bool top = bool_attr(elem, "top", false);
bool bottom = bool_attr(elem, "bottom", false);
widget->setAlign((center ? CENTER:
(right ? RIGHT: LEFT)) |
@ -290,10 +290,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
link->setUrl(url);
}
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool center = bool_attr(elem, "center", false);
bool right = bool_attr(elem, "right", false);
bool top = bool_attr(elem, "top", false);
bool bottom = bool_attr(elem, "bottom", false);
widget->setAlign(
(center ? CENTER: (right ? RIGHT: LEFT)) |
@ -303,7 +303,7 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
if (!widget)
widget = new ListBox();
bool multiselect = bool_attr_is_true(elem, "multiselect");
bool multiselect = bool_attr(elem, "multiselect", false);
if (multiselect)
static_cast<ListBox*>(widget)->setMultiselect(multiselect);
}
@ -323,8 +323,8 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
listitem->setValue(value);
}
else if (elem_name == "splitter") {
bool horizontal = bool_attr_is_true(elem, "horizontal");
bool vertical = bool_attr_is_true(elem, "vertical");
bool horizontal = bool_attr(elem, "horizontal", false);
bool vertical = bool_attr(elem, "vertical", false);
const char* by = elem->Attribute("by");
const char* position = elem->Attribute("position");
Splitter::Type type = (by && strcmp(by, "pixel") == 0 ?
@ -361,10 +361,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
radio->setRadioGroup(radio_group);
}
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool top = bool_attr_is_true(elem, "top");
bool bottom = bool_attr_is_true(elem, "bottom");
bool center = bool_attr(elem, "center", false);
bool right = bool_attr(elem, "right", false);
bool top = bool_attr(elem, "top", false);
bool bottom = bool_attr(elem, "bottom", false);
widget->setAlign(
(center ? CENTER:
@ -373,12 +373,12 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
(bottom ? BOTTOM: MIDDLE)));
}
else if (elem_name == "separator") {
bool center = bool_attr_is_true(elem, "center");
bool right = bool_attr_is_true(elem, "right");
bool middle = bool_attr_is_true(elem, "middle");
bool bottom = bool_attr_is_true(elem, "bottom");
bool horizontal = bool_attr_is_true(elem, "horizontal");
bool vertical = bool_attr_is_true(elem, "vertical");
bool center = bool_attr(elem, "center", false);
bool right = bool_attr(elem, "right", false);
bool middle = bool_attr(elem, "middle", false);
bool bottom = bool_attr(elem, "bottom", false);
bool horizontal = bool_attr(elem, "horizontal", false);
bool vertical = bool_attr(elem, "vertical", false);
int align =
(horizontal ? HORIZONTAL: 0) |
(vertical ? VERTICAL: 0) |
@ -400,7 +400,7 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
widget = new Slider(min_value, max_value, min_value);
}
else if (elem_name == "textbox") {
bool wordwrap = bool_attr_is_true(elem, "wordwrap");
bool wordwrap = bool_attr(elem, "wordwrap", false);
if (!widget)
widget = new TextBox(elem->GetText(), 0);
@ -416,7 +416,7 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
}
else if (elem_name == "window") {
if (!widget) {
bool desktop = bool_attr_is_true(elem, "desktop");
bool desktop = bool_attr(elem, "desktop", false);
if (desktop)
widget = new Window(Window::DesktopWindow);
@ -427,8 +427,8 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
}
}
else if (elem_name == "colorpicker") {
const bool rgba = bool_attr_is_true(elem, "rgba");
const bool simple = bool_attr_is_true(elem, "simple");
const bool rgba = bool_attr(elem, "rgba", false);
const bool simple = bool_attr(elem, "simple", false);
if (!widget) {
ColorButtonOptions options;
@ -453,9 +453,9 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
widget = new ButtonSet(strtol(columns, NULL, 10));
if (ButtonSet* buttonset = dynamic_cast<ButtonSet*>(widget)) {
if (bool_attr_is_true(elem, "multiple"))
if (bool_attr(elem, "multiple", false))
buttonset->setMultiMode(ButtonSet::MultiMode::Set);
if (bool_attr_is_true(elem, "oneormore"))
if (bool_attr(elem, "oneormore", false))
buttonset->setMultiMode(ButtonSet::MultiMode::OneOrMore);
}
}
@ -487,21 +487,28 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
else if (elem_name == "image") {
if (!widget) {
const char* file = elem->Attribute("file");
const char* icon = elem->Attribute("icon");
// Load image
std::string icon(file);
if (file) {
ResourceFinder rf;
rf.includeDataDir(file);
if (!rf.findFirst())
throw base::Exception("File %s not found", file);
ResourceFinder rf;
rf.includeDataDir(file);
if (!rf.findFirst())
throw base::Exception("File %s not found", file);
try {
os::SurfaceRef sur = os::instance()->loadRgbaSurface(rf.filename().c_str());
widget = new ImageView(sur, 0);
try {
os::SurfaceRef sur = os::instance()->loadRgbaSurface(rf.filename().c_str());
widget = new ImageView(sur, 0);
}
catch (...) {
throw base::Exception("Error loading %s file", file);
}
}
catch (...) {
throw base::Exception("Error loading %s file", file);
if (icon) {
SkinPartPtr part = SkinTheme::instance()->getPartById(std::string(icon));
if (part) {
widget = new ImageView(part->bitmapRef(0), 0);
}
}
}
}
@ -522,12 +529,13 @@ void WidgetLoader::fillWidgetWithXmlElementAttributes(const TiXmlElement* elem,
{
const char* id = elem->Attribute("id");
const char* tooltip_dir = elem->Attribute("tooltip_dir");
bool selected = bool_attr_is_true(elem, "selected");
bool disabled = bool_attr_is_true(elem, "disabled");
bool expansive = bool_attr_is_true(elem, "expansive");
bool homogeneous = bool_attr_is_true(elem, "homogeneous");
bool magnet = bool_attr_is_true(elem, "magnet");
bool noborders = bool_attr_is_true(elem, "noborders");
bool selected = bool_attr(elem, "selected", false);
bool disabled = bool_attr(elem, "disabled", false);
bool expansive = bool_attr(elem, "expansive", false);
bool homogeneous = bool_attr(elem, "homogeneous", false);
bool magnet = bool_attr(elem, "magnet", false);
bool noborders = bool_attr(elem, "noborders", false);
bool visible = bool_attr(elem, "visible", true);
const char* width = elem->Attribute("width");
const char* height = elem->Attribute("height");
const char* minwidth = elem->Attribute("minwidth");
@ -585,6 +593,9 @@ void WidgetLoader::fillWidgetWithXmlElementAttributes(const TiXmlElement* elem,
if (expansive)
widget->setExpansive(true);
if (!visible)
widget->setVisible(false);
if (homogeneous)
widget->setAlign(widget->align() | HOMOGENEOUS);
@ -699,7 +710,7 @@ void WidgetLoader::fillWidgetWithXmlElementAttributesWithChildren(const TiXmlEle
}
if (widget->type() == kViewWidget) {
bool maxsize = bool_attr_is_true(elem, "maxsize");
bool maxsize = bool_attr(elem, "maxsize", false);
if (maxsize)
static_cast<View*>(widget)->makeVisibleAllScrollableArea();
}

View File

@ -45,11 +45,11 @@ void save_xml(XmlDocumentRef doc, const std::string& filename)
throw XmlException(doc.get());
}
bool bool_attr_is_true(const TiXmlElement* elem, const char* attrName)
bool bool_attr(const TiXmlElement* elem, const char* attrName, bool defaultVal)
{
const char* value = elem->Attribute(attrName);
return (value != NULL) && (strcmp(value, "true") == 0);
return value == NULL ? defaultVal : strcmp(value, "true") == 0;
}
} // namespace app

View File

@ -23,7 +23,7 @@ namespace app {
XmlDocumentRef open_xml(const std::string& filename);
void save_xml(XmlDocumentRef doc, const std::string& filename);
bool bool_attr_is_true(const TiXmlElement* elem, const char* attrName);
bool bool_attr(const TiXmlElement* elem, const char* attrName, bool defaultVal);
} // namespace app

View File

@ -116,6 +116,9 @@ static Item convert_to_item(TiXmlElement* elem)
if (name == "hbox")
return item.typeIncl("ui::HBox",
"ui/box.h");
if (name == "image")
return item.typeIncl("ui::ImageView",
"ui/image_view.h");
if (name == "item" &&
parent == "buttonset")
return item.typeIncl("app::ButtonSet::Item",

View File

@ -16,10 +16,16 @@
#include "app/send_crash.h"
#include "base/exception.h"
#include "base/memory.h"
#include "base/memory_dump.h"
#include "base/system_console.h"
#include "os/error.h"
#include "os/system.h"
#include "ver/info.h"
#if ENABLE_SENTRY
#include "app/sentry_wrapper.h"
#else
#include "base/memory_dump.h"
#endif
#include <clocale>
#include <cstdlib>
@ -78,13 +84,20 @@ int app_main(int argc, char* argv[])
#endif
try {
#if ENABLE_SENTRY
app::Sentry sentry;
#else
base::MemoryDump memoryDump;
#endif
MemLeak memleak;
base::SystemConsole systemConsole;
app::AppOptions options(argc, const_cast<const char**>(argv));
os::SystemRef system(os::make_system());
app::App app;
#if ENABLE_SENTRY
sentry.init();
#else
// Change the memory dump filename to save on disk (.dmp
// file). Note: Only useful on Windows.
{
@ -92,6 +105,7 @@ int app_main(int argc, char* argv[])
if (!fn.empty())
memoryDump.setFileName(fn);
}
#endif
const int code = app.initialize(options);

View File

@ -16,7 +16,13 @@ namespace ui {
{
public:
CloseEvent(Component* source)
: Event(source) { }
: Event(source)
, m_canceled(false) { }
void cancel() { m_canceled = true; }
bool canceled() const { return m_canceled; }
private:
bool m_canceled;
};
} // namespace ui

View File

@ -325,12 +325,16 @@ void Window::openWindowInForeground()
void Window::closeWindow(Widget* closer)
{
// Close event
CloseEvent ev(closer);
onBeforeClose(ev);
if (ev.canceled())
return;
m_closer = closer;
manager()->_closeWindow(this, true);
// Close event
CloseEvent ev(closer);
onClose(ev);
}

View File

@ -65,6 +65,7 @@ namespace ui {
virtual void onSetText() override;
// New events
virtual void onBeforeClose(CloseEvent& ev) {}
virtual void onClose(CloseEvent& ev);
virtual void onHitTest(HitTestEvent& ev);
virtual void onWindowResize();