mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 01:20:17 +00:00
519 lines
14 KiB
C++
519 lines
14 KiB
C++
// Aseprite
|
|
// Copyright (C) 2001-2016 David Capello
|
|
//
|
|
// 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/ui/main_window.h"
|
|
|
|
#include "app/app.h"
|
|
#include "app/app_menus.h"
|
|
#include "app/commands/commands.h"
|
|
#include "app/ini_file.h"
|
|
#include "app/modules/editors.h"
|
|
#include "app/notification_delegate.h"
|
|
#include "app/pref/preferences.h"
|
|
#include "app/ui/color_bar.h"
|
|
#include "app/ui/context_bar.h"
|
|
#include "app/ui/devconsole_view.h"
|
|
#include "app/ui/document_view.h"
|
|
#include "app/ui/editor/editor.h"
|
|
#include "app/ui/editor/editor_view.h"
|
|
#include "app/ui/home_view.h"
|
|
#include "app/ui/main_menu_bar.h"
|
|
#include "app/ui/notifications.h"
|
|
#include "app/ui/preview_editor.h"
|
|
#include "app/ui/skin/skin_property.h"
|
|
#include "app/ui/skin/skin_theme.h"
|
|
#include "app/ui/status_bar.h"
|
|
#include "app/ui/timeline.h"
|
|
#include "app/ui/toolbar.h"
|
|
#include "app/ui/workspace.h"
|
|
#include "app/ui/workspace_tabs.h"
|
|
#include "app/ui_context.h"
|
|
#include "base/bind.h"
|
|
#include "base/fs.h"
|
|
#include "she/display.h"
|
|
#include "ui/message.h"
|
|
#include "ui/splitter.h"
|
|
#include "ui/system.h"
|
|
#include "ui/view.h"
|
|
|
|
namespace app {
|
|
|
|
using namespace ui;
|
|
|
|
class ScreenScalePanic : public INotificationDelegate {
|
|
public:
|
|
std::string notificationText() override {
|
|
return "Reset Scale!";
|
|
}
|
|
|
|
void notificationClick() override {
|
|
auto& pref = Preferences::instance();
|
|
|
|
const int newScreenScale = 2;
|
|
const int newUIScale = 1;
|
|
bool needsRestart = false;
|
|
|
|
if (pref.general.screenScale() != newScreenScale)
|
|
pref.general.screenScale(newScreenScale);
|
|
|
|
if (pref.general.uiScale() != newUIScale) {
|
|
pref.general.uiScale(newUIScale);
|
|
needsRestart = true;
|
|
}
|
|
|
|
pref.save();
|
|
|
|
// If the UI scale is greater than 100%, we would like to avoid
|
|
// setting the Screen Scale to 200% right now to avoid a worse
|
|
// effect to the user. E.g. If the UI Scale is 400% and Screen
|
|
// Scale is 100%, and the user choose to reset the scale, we
|
|
// cannot change the Screen Scale to 200% (we're going to
|
|
// increase the problem), so we change the Screen Scale to 100%
|
|
// just to show the "restart" message.
|
|
Manager* manager = Manager::getDefault();
|
|
she::Display* display = manager->getDisplay();
|
|
display->setScale(ui::guiscale() > newUIScale ? 1: newScreenScale);
|
|
manager->setDisplay(display);
|
|
|
|
if (needsRestart)
|
|
Alert::show("Aseprite<<Restart the program.||&OK");
|
|
}
|
|
};
|
|
|
|
MainWindow::MainWindow()
|
|
: m_mode(NormalMode)
|
|
, m_homeView(nullptr)
|
|
, m_scalePanic(nullptr)
|
|
#ifdef ENABLE_SCRIPTING
|
|
, m_devConsoleView(nullptr)
|
|
#endif
|
|
{
|
|
// Load all menus by first time.
|
|
AppMenus::instance()->reload();
|
|
|
|
m_menuBar = new MainMenuBar();
|
|
m_notifications = new Notifications();
|
|
m_contextBar = new ContextBar();
|
|
m_statusBar = new StatusBar();
|
|
m_colorBar = new ColorBar(colorBarPlaceholder()->align());
|
|
m_toolBar = new ToolBar();
|
|
m_tabsBar = new WorkspaceTabs(this);
|
|
m_workspace = new Workspace();
|
|
m_previewEditor = new PreviewEditorWindow();
|
|
m_timeline = new Timeline();
|
|
|
|
m_workspace->setTabsBar(m_tabsBar);
|
|
m_workspace->ActiveViewChanged.connect(&MainWindow::onActiveViewChange, this);
|
|
|
|
// configure all widgets to expansives
|
|
m_menuBar->setExpansive(true);
|
|
m_contextBar->setExpansive(true);
|
|
m_contextBar->setVisible(false);
|
|
m_statusBar->setExpansive(true);
|
|
m_colorBar->setExpansive(true);
|
|
m_toolBar->setExpansive(true);
|
|
m_tabsBar->setExpansive(true);
|
|
m_timeline->setExpansive(true);
|
|
m_workspace->setExpansive(true);
|
|
m_notifications->setVisible(false);
|
|
|
|
// Setup the menus
|
|
m_menuBar->setMenu(AppMenus::instance()->getRootMenu());
|
|
|
|
// Add the widgets in the boxes
|
|
menuBarPlaceholder()->addChild(m_menuBar);
|
|
menuBarPlaceholder()->addChild(m_notifications);
|
|
contextBarPlaceholder()->addChild(m_contextBar);
|
|
colorBarPlaceholder()->addChild(m_colorBar);
|
|
toolBarPlaceholder()->addChild(m_toolBar);
|
|
statusBarPlaceholder()->addChild(m_statusBar);
|
|
tabsPlaceholder()->addChild(m_tabsBar);
|
|
workspacePlaceholder()->addChild(m_workspace);
|
|
timelinePlaceholder()->addChild(m_timeline);
|
|
|
|
// Default splitter positions
|
|
colorBarSplitter()->setPosition(m_colorBar->sizeHint().w);
|
|
timelineSplitter()->setPosition(75);
|
|
|
|
// Reconfigure workspace when the timeline position is changed.
|
|
Preferences::instance().general.timelinePosition
|
|
.AfterChange.connect(base::Bind<void>(&MainWindow::configureWorkspaceLayout, this));
|
|
|
|
// Prepare the window
|
|
remapWindow();
|
|
|
|
AppMenus::instance()->rebuildRecentList();
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
delete m_scalePanic;
|
|
|
|
#ifdef ENABLE_SCRIPTING
|
|
if (m_devConsoleView) {
|
|
if (m_devConsoleView->parent())
|
|
m_workspace->removeView(m_devConsoleView);
|
|
delete m_devConsoleView;
|
|
}
|
|
#endif
|
|
|
|
if (m_homeView) {
|
|
if (m_homeView->parent())
|
|
m_workspace->removeView(m_homeView);
|
|
delete m_homeView;
|
|
}
|
|
delete m_contextBar;
|
|
delete m_previewEditor;
|
|
|
|
// Destroy the workspace first so ~Editor can dettach slots from
|
|
// ColorBar. TODO this is a terrible hack for slot/signal stuff,
|
|
// connections should be handle in a better/safer way.
|
|
delete m_workspace;
|
|
|
|
// Remove the root-menu from the menu-bar (because the rootmenu
|
|
// module should destroy it).
|
|
m_menuBar->setMenu(NULL);
|
|
}
|
|
|
|
DocumentView* MainWindow::getDocView()
|
|
{
|
|
return dynamic_cast<DocumentView*>(m_workspace->activeView());
|
|
}
|
|
|
|
HomeView* MainWindow::getHomeView()
|
|
{
|
|
if (!m_homeView)
|
|
m_homeView = new HomeView;
|
|
return m_homeView;
|
|
}
|
|
|
|
#ifdef ENABLE_UPDATER
|
|
CheckUpdateDelegate* MainWindow::getCheckUpdateDelegate()
|
|
{
|
|
return getHomeView();
|
|
}
|
|
#endif
|
|
|
|
void MainWindow::reloadMenus()
|
|
{
|
|
m_menuBar->reload();
|
|
|
|
layout();
|
|
invalidate();
|
|
}
|
|
|
|
void MainWindow::showNotification(INotificationDelegate* del)
|
|
{
|
|
m_notifications->addLink(del);
|
|
m_notifications->setVisible(true);
|
|
m_notifications->parent()->layout();
|
|
}
|
|
|
|
void MainWindow::showHomeOnOpen()
|
|
{
|
|
if (!getHomeView()->parent()) {
|
|
TabView* selectedTab = m_tabsBar->getSelectedTab();
|
|
|
|
// Show "Home" tab in the first position, and select it only if
|
|
// there is no other view selected.
|
|
m_workspace->addView(m_homeView, 0);
|
|
if (selectedTab)
|
|
m_tabsBar->selectTab(selectedTab);
|
|
else
|
|
m_tabsBar->selectTab(m_homeView);
|
|
}
|
|
}
|
|
|
|
void MainWindow::showHome()
|
|
{
|
|
if (!getHomeView()->parent()) {
|
|
m_workspace->addView(m_homeView, 0);
|
|
}
|
|
m_tabsBar->selectTab(m_homeView);
|
|
}
|
|
|
|
bool MainWindow::isHomeSelected()
|
|
{
|
|
return (m_tabsBar->getSelectedTab() == m_homeView && m_homeView);
|
|
}
|
|
|
|
void MainWindow::showDevConsole()
|
|
{
|
|
#ifdef ENABLE_SCRIPTING
|
|
if (!m_devConsoleView)
|
|
m_devConsoleView = new DevConsoleView;
|
|
|
|
if (!m_devConsoleView->parent()) {
|
|
m_workspace->addView(m_devConsoleView);
|
|
m_tabsBar->selectTab(m_devConsoleView);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void MainWindow::setMode(Mode mode)
|
|
{
|
|
// Check if we already are in the given mode.
|
|
if (m_mode == mode)
|
|
return;
|
|
|
|
m_mode = mode;
|
|
configureWorkspaceLayout();
|
|
}
|
|
|
|
bool MainWindow::getTimelineVisibility() const
|
|
{
|
|
return Preferences::instance().general.visibleTimeline();
|
|
}
|
|
|
|
void MainWindow::setTimelineVisibility(bool visible)
|
|
{
|
|
Preferences::instance().general.visibleTimeline(visible);
|
|
|
|
configureWorkspaceLayout();
|
|
}
|
|
|
|
void MainWindow::popTimeline()
|
|
{
|
|
Preferences& preferences = Preferences::instance();
|
|
|
|
if (!preferences.general.autoshowTimeline())
|
|
return;
|
|
|
|
if (!getTimelineVisibility())
|
|
setTimelineVisibility(true);
|
|
}
|
|
|
|
void MainWindow::showDataRecovery(crash::DataRecovery* dataRecovery)
|
|
{
|
|
getHomeView()->showDataRecovery(dataRecovery);
|
|
}
|
|
|
|
bool MainWindow::onProcessMessage(ui::Message* msg)
|
|
{
|
|
if (msg->type() == kOpenMessage)
|
|
showHomeOnOpen();
|
|
|
|
return Window::onProcessMessage(msg);
|
|
}
|
|
|
|
void MainWindow::onSaveLayout(SaveLayoutEvent& ev)
|
|
{
|
|
// Invert the timeline splitter position before we save the setting.
|
|
if (Preferences::instance().general.timelinePosition() ==
|
|
gen::TimelinePosition::LEFT) {
|
|
timelineSplitter()->setPosition(100 - timelineSplitter()->getPosition());
|
|
}
|
|
|
|
Window::onSaveLayout(ev);
|
|
}
|
|
|
|
void MainWindow::onResize(ui::ResizeEvent& ev)
|
|
{
|
|
app::gen::MainWindow::onResize(ev);
|
|
|
|
she::Display* display = manager()->getDisplay();
|
|
if ((display) &&
|
|
(display->scale()*ui::guiscale() > 2) &&
|
|
(!m_scalePanic) &&
|
|
(ui::display_w()/ui::guiscale() < 320 ||
|
|
ui::display_h()/ui::guiscale() < 260)) {
|
|
showNotification(m_scalePanic = new ScreenScalePanic);
|
|
}
|
|
}
|
|
|
|
// When the active view is changed from methods like
|
|
// Workspace::splitView(), this function is called, and we have to
|
|
// inform to the UIContext that the current view has changed.
|
|
void MainWindow::onActiveViewChange()
|
|
{
|
|
if (DocumentView* docView = getDocView())
|
|
UIContext::instance()->setActiveView(docView);
|
|
else
|
|
UIContext::instance()->setActiveView(nullptr);
|
|
|
|
configureWorkspaceLayout();
|
|
}
|
|
|
|
bool MainWindow::isTabModified(Tabs* tabs, TabView* tabView)
|
|
{
|
|
if (DocumentView* docView = dynamic_cast<DocumentView*>(tabView)) {
|
|
Document* document = docView->document();
|
|
return document->isModified();
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool MainWindow::canCloneTab(Tabs* tabs, TabView* tabView)
|
|
{
|
|
ASSERT(tabView)
|
|
|
|
WorkspaceView* view = dynamic_cast<WorkspaceView*>(tabView);
|
|
return view->canCloneWorkspaceView();
|
|
}
|
|
|
|
void MainWindow::onSelectTab(Tabs* tabs, TabView* tabView)
|
|
{
|
|
if (!tabView)
|
|
return;
|
|
|
|
WorkspaceView* view = dynamic_cast<WorkspaceView*>(tabView);
|
|
if (m_workspace->activeView() != view)
|
|
m_workspace->setActiveView(view);
|
|
}
|
|
|
|
void MainWindow::onCloseTab(Tabs* tabs, TabView* tabView)
|
|
{
|
|
WorkspaceView* view = dynamic_cast<WorkspaceView*>(tabView);
|
|
ASSERT(view);
|
|
if (view)
|
|
m_workspace->closeView(view, false);
|
|
}
|
|
|
|
void MainWindow::onCloneTab(Tabs* tabs, TabView* tabView, int pos)
|
|
{
|
|
EditorView::SetScrollUpdateMethod(EditorView::KeepOrigin);
|
|
|
|
WorkspaceView* view = dynamic_cast<WorkspaceView*>(tabView);
|
|
WorkspaceView* clone = view->cloneWorkspaceView();
|
|
ASSERT(clone);
|
|
|
|
m_workspace->addViewToPanel(
|
|
static_cast<WorkspaceTabs*>(tabs)->panel(), clone, true, pos);
|
|
|
|
clone->onClonedFrom(view);
|
|
}
|
|
|
|
void MainWindow::onContextMenuTab(Tabs* tabs, TabView* tabView)
|
|
{
|
|
WorkspaceView* view = dynamic_cast<WorkspaceView*>(tabView);
|
|
ASSERT(view);
|
|
if (view)
|
|
view->onTabPopup(m_workspace);
|
|
}
|
|
|
|
void MainWindow::onMouseOverTab(Tabs* tabs, TabView* tabView)
|
|
{
|
|
// Note: tabView can be NULL
|
|
if (DocumentView* docView = dynamic_cast<DocumentView*>(tabView)) {
|
|
Document* document = docView->document();
|
|
|
|
std::string name;
|
|
if (Preferences::instance().general.showFullPath())
|
|
name = document->filename();
|
|
else
|
|
name = base::get_file_name(document->filename());
|
|
|
|
m_statusBar->setStatusText(250, "%s", name.c_str());
|
|
}
|
|
else {
|
|
m_statusBar->clearText();
|
|
}
|
|
}
|
|
|
|
DropViewPreviewResult MainWindow::onFloatingTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos)
|
|
{
|
|
return m_workspace->setDropViewPreview(pos,
|
|
dynamic_cast<WorkspaceView*>(tabView),
|
|
static_cast<WorkspaceTabs*>(tabs));
|
|
}
|
|
|
|
void MainWindow::onDockingTab(Tabs* tabs, TabView* tabView)
|
|
{
|
|
m_workspace->removeDropViewPreview();
|
|
}
|
|
|
|
DropTabResult MainWindow::onDropTab(Tabs* tabs, TabView* tabView, const gfx::Point& pos, bool clone)
|
|
{
|
|
m_workspace->removeDropViewPreview();
|
|
|
|
DropViewAtResult result =
|
|
m_workspace->dropViewAt(pos, dynamic_cast<WorkspaceView*>(tabView), clone);
|
|
|
|
if (result == DropViewAtResult::MOVED_TO_OTHER_PANEL)
|
|
return DropTabResult::REMOVE;
|
|
else if (result == DropViewAtResult::CLONED_VIEW)
|
|
return DropTabResult::DONT_REMOVE;
|
|
else
|
|
return DropTabResult::NOT_HANDLED;
|
|
}
|
|
|
|
void MainWindow::configureWorkspaceLayout()
|
|
{
|
|
const auto& pref = Preferences::instance();
|
|
bool normal = (m_mode == NormalMode);
|
|
bool isDoc = (getDocView() != nullptr);
|
|
|
|
m_menuBar->setVisible(normal);
|
|
m_tabsBar->setVisible(normal);
|
|
colorBarPlaceholder()->setVisible(normal && isDoc);
|
|
m_toolBar->setVisible(normal && isDoc);
|
|
m_statusBar->setVisible(normal);
|
|
m_contextBar->setVisible(
|
|
isDoc &&
|
|
(m_mode == NormalMode ||
|
|
m_mode == ContextBarAndTimelineMode));
|
|
|
|
// Configure timeline
|
|
{
|
|
auto timelinePosition = pref.general.timelinePosition();
|
|
bool invertWidgets = false;
|
|
int align = VERTICAL;
|
|
switch (timelinePosition) {
|
|
case gen::TimelinePosition::LEFT:
|
|
align = HORIZONTAL;
|
|
invertWidgets = true;
|
|
break;
|
|
case gen::TimelinePosition::RIGHT:
|
|
align = HORIZONTAL;
|
|
break;
|
|
case gen::TimelinePosition::BOTTOM:
|
|
break;
|
|
}
|
|
|
|
timelineSplitter()->setAlign(align);
|
|
timelinePlaceholder()->setVisible(
|
|
isDoc &&
|
|
(m_mode == NormalMode ||
|
|
m_mode == ContextBarAndTimelineMode) &&
|
|
pref.general.visibleTimeline());
|
|
|
|
bool invertSplitterPos = false;
|
|
if (invertWidgets) {
|
|
if (timelineSplitter()->firstChild() == workspacePlaceholder() &&
|
|
timelineSplitter()->lastChild() == timelinePlaceholder()) {
|
|
timelineSplitter()->removeChild(workspacePlaceholder());
|
|
timelineSplitter()->addChild(workspacePlaceholder());
|
|
invertSplitterPos = true;
|
|
}
|
|
}
|
|
else {
|
|
if (timelineSplitter()->firstChild() == timelinePlaceholder() &&
|
|
timelineSplitter()->lastChild() == workspacePlaceholder()) {
|
|
timelineSplitter()->removeChild(timelinePlaceholder());
|
|
timelineSplitter()->addChild(timelinePlaceholder());
|
|
invertSplitterPos = true;
|
|
}
|
|
}
|
|
if (invertSplitterPos)
|
|
timelineSplitter()->setPosition(100 - timelineSplitter()->getPosition());
|
|
}
|
|
|
|
if (m_contextBar->isVisible()) {
|
|
m_contextBar->updateForActiveTool();
|
|
}
|
|
|
|
layout();
|
|
invalidate();
|
|
}
|
|
|
|
} // namespace app
|