Add support to split workspace views

+ Added WorkspacePart class
+ Added WorkspaceView::cloneWorkspaceView()/onClonedFrom() methods.
This commit is contained in:
David Capello 2013-03-27 21:19:35 -03:00
parent 163af12313
commit 1d56133e75
16 changed files with 370 additions and 93 deletions

View File

@ -407,7 +407,8 @@ add_library(aseprite-library
widgets/status_bar.cpp
widgets/tabs.cpp
widgets/toolbar.cpp
widgets/workspace.cpp)
widgets/workspace.cpp
widgets/workspace_part.cpp)
######################################################################
# ASEPRITE application

View File

@ -159,8 +159,9 @@ int App::run()
// Create the main window and show it.
m_mainWindow.reset(new MainWindow);
// Create the list of tabs
// Default status of the main window.
app_rebuild_documents_tabs();
app_default_statusbar_message();
m_mainWindow->openWindow();

View File

@ -82,7 +82,9 @@ protected:
}
// Close the active view.
workspace->closeView(workspace->getActiveView());
WorkspaceView* view = workspace->getActiveView();
workspace->removeView(view);
delete view;
}
private:

View File

@ -47,31 +47,34 @@ UIContext::UIContext()
{
ASSERT(m_instance == NULL);
m_instance = this;
m_activeView = NULL;
}
UIContext::~UIContext()
{
// No views at this point.
ASSERT(m_allViews.empty());
ASSERT(m_activeView == NULL);
ASSERT(m_instance == this);
m_instance = NULL;
}
widgets::DocumentView* UIContext::getActiveView()
widgets::DocumentView* UIContext::getActiveView() const
{
return m_activeView;
Workspace* workspace = App::instance()->getMainWindow()->getWorkspace();
WorkspaceView* view = workspace->getActiveView();
if (DocumentView* docView = dynamic_cast<DocumentView*>(view))
return docView;
else
return NULL;
}
void UIContext::setActiveView(widgets::DocumentView* docView)
{
m_activeView = docView;
current_editor = (docView ? docView->getEditor(): NULL);
if (docView != NULL) {
App::instance()->getMainWindow()->getTabsBar()->selectTab(docView);
App::instance()->getMainWindow()->getTabsBar()->selectTab(docView);
App::instance()->getMainWindow()->getWorkspace()->setActiveView(docView);
if (App::instance()->getMainWindow()->getWorkspace()->getActiveView() != docView)
App::instance()->getMainWindow()->getWorkspace()->setActiveView(docView);
}
current_editor = (docView ? docView->getEditor(): NULL);
if (current_editor)
current_editor->requestFocus();
@ -87,9 +90,9 @@ void UIContext::setActiveView(widgets::DocumentView* docView)
// Change the main frame title.
base::string defaultTitle = PACKAGE " v" VERSION;
base::string title;
if (m_activeView) {
if (docView) {
// Prepend the document's filename.
title += base::get_file_name(m_activeView->getDocument()->getFilename());
title += base::get_file_name(docView->getDocument()->getFilename());
title += " - ";
}
title += defaultTitle;
@ -98,20 +101,26 @@ void UIContext::setActiveView(widgets::DocumentView* docView)
size_t UIContext::countViewsOf(Document* document) const
{
Workspace* workspace = App::instance()->getMainWindow()->getWorkspace();
size_t counter = 0;
for (DocumentViews::const_iterator
it=m_allViews.begin(), end=m_allViews.end(); it != end; ++it) {
DocumentView* view = *it;
if (view->getDocument() == document)
++counter;
for (Workspace::iterator it=workspace->begin(); it != workspace->end(); ++it) {
WorkspaceView* view = *it;
if (DocumentView* docView = dynamic_cast<DocumentView*>(view)) {
if (docView->getDocument() == document) {
++counter;
}
}
}
return counter;
}
Editor* UIContext::getActiveEditor()
{
if (m_activeView)
return m_activeView->getEditor();
DocumentView* activeView = getActiveView();
if (activeView)
return activeView->getEditor();
else
return NULL;
}
@ -123,62 +132,41 @@ void UIContext::onAddDocument(Document* document)
// Add a new view for this document
DocumentView* view = new DocumentView(document, DocumentView::Normal);
m_allViews.push_back(view);
// Add a tab with the new view for the document
App::instance()->getMainWindow()->getTabsBar()->addTab(view);
App::instance()->getMainWindow()->getWorkspace()->addView(view);
setActiveView(view);
view->getEditor()->setDefaultScroll();
// Rebuild the list of tabs
app_rebuild_documents_tabs();
}
void UIContext::onRemoveDocument(Document* document)
{
Context::onRemoveDocument(document);
DocumentView* newActiveView = NULL;
bool activeViewChanged = false;
Workspace* workspace = App::instance()->getMainWindow()->getWorkspace();
DocumentViews docViews;
// Remove all views of this document
for (DocumentViews::iterator it=m_allViews.begin(); it != m_allViews.end(); ) {
DocumentView* view = *it;
if (view->getDocument() == document) {
App::instance()->getMainWindow()->getTabsBar()->removeTab(view);
App::instance()->getMainWindow()->getWorkspace()->removeView(view);
// We cannot point as "active view" this view that we're
// destroying. In this case we go to the next view (or the
// previous one if we are at the end).
if (view == m_activeView ||
view == newActiveView) {
m_activeView = NULL;
newActiveView = ((it+1) != m_allViews.end() ? *(it+1):
(it != m_allViews.begin() ? *(it-1): NULL));
activeViewChanged = true;
// Collect all views related to the document.
for (Workspace::iterator it=workspace->begin(); it != workspace->end(); ++it) {
WorkspaceView* view = *it;
if (DocumentView* docView = dynamic_cast<DocumentView*>(view)) {
if (docView->getDocument() == document) {
docViews.push_back(docView);
}
delete view;
it = m_allViews.erase(it);
}
else
++it;
}
// Select the next view as current view.
if (activeViewChanged)
setActiveView(newActiveView);
// Rebuild the tabs
app_rebuild_documents_tabs();
for (DocumentViews::iterator it=docViews.begin(); it != docViews.end(); ++it) {
DocumentView* docView = *it;
workspace->removeView(docView);
delete docView;
}
}
void UIContext::onGetActiveLocation(DocumentLocation* location) const
{
if (m_activeView)
m_activeView->getDocumentLocation(location);
widgets::DocumentView* activeView = getActiveView();
if (activeView)
activeView->getDocumentLocation(location);
}

View File

@ -37,7 +37,7 @@ public:
virtual bool isUiAvailable() const { return true; }
widgets::DocumentView* getActiveView();
widgets::DocumentView* getActiveView() const;
void setActiveView(widgets::DocumentView* documentView);
// Returns the number of views that the given document has.
@ -56,9 +56,6 @@ protected:
virtual void onGetActiveLocation(DocumentLocation* location) const OVERRIDE;
private:
DocumentViews m_allViews;
widgets::DocumentView* m_activeView;
static UIContext* m_instance;
};

View File

@ -158,6 +158,24 @@ std::string DocumentView::getTabText()
return str;
}
WorkspaceView* DocumentView::cloneWorkspaceView()
{
return new DocumentView(m_document, Normal);
}
void DocumentView::onClonedFrom(WorkspaceView* from)
{
Editor* newEditor = getEditor();
Editor* srcEditor = static_cast<DocumentView*>(from)->getEditor();
newEditor->setLayer(srcEditor->getLayer());
newEditor->setFrame(srcEditor->getFrame());
newEditor->setZoom(srcEditor->getZoom());
View::getView(newEditor)
->setViewScroll(View::getView(srcEditor)->getViewScroll());
}
bool DocumentView::onProcessMessage(Message* msg)
{
switch (msg->type) {

View File

@ -56,6 +56,8 @@ namespace widgets {
// WorkspaceView implementation
ui::Widget* getContentWidget() OVERRIDE { return this; }
WorkspaceView* cloneWorkspaceView() OVERRIDE;
void onClonedFrom(WorkspaceView* from) OVERRIDE;
// DocumentObserver implementation
void onGeneralUpdate(DocumentEvent& ev) OVERRIDE;

View File

@ -69,6 +69,7 @@ MainWindow::MainWindow()
m_toolBar = new ToolBar();
m_tabsBar = new Tabs(this);
m_workspace = new Workspace();
m_workspace->ActiveViewChanged.connect(&MainWindow::onActiveViewChange, this);
m_miniEditor = new MiniEditorWindow();
m_colorBarSplitter = findChildT<Splitter>("colorbarsplitter");
@ -83,9 +84,6 @@ MainWindow::MainWindow()
// Setup the menus
m_menuBar->setMenu(AppMenus::instance()->getRootMenu());
// Start text of status bar
app_default_statusbar_message();
// Add the widgets in the boxes
if (box_menubar) box_menubar->addChild(m_menuBar);
if (box_colorbar) box_colorbar->addChild(m_colorBar);
@ -155,6 +153,15 @@ void MainWindow::onSaveLayout(SaveLayoutEvent& ev)
m_colorBarSplitter->setPosition(m_lastSplitterPos);
}
// 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 = dynamic_cast<DocumentView*>(m_workspace->getActiveView()))
UIContext::instance()->setActiveView(docView);
}
void MainWindow::clickTab(Tabs* tabs, TabView* tabView, int button)
{
if (!tabView)

View File

@ -56,6 +56,7 @@ public:
protected:
void onSaveLayout(ui::SaveLayoutEvent& ev) OVERRIDE;
void onActiveViewChange();
private:
MainMenuBar* m_menuBar; // the menu bar widget

View File

@ -185,6 +185,8 @@ void Tabs::updateTabsText()
void Tabs::selectTab(TabView* tabView)
{
ASSERT(tabView != NULL);
Tab *tab = getTabByView(tabView);
if (tab != NULL)
selectTabInternal(tab);

View File

@ -20,7 +20,12 @@
#include "widgets/workspace.h"
#include "app.h"
#include "skin/skin_theme.h"
#include "ui/splitter.h"
#include "widgets/main_window.h"
#include "widgets/tabs.h"
#include "widgets/workspace_part.h"
#include "widgets/workspace_view.h"
#include <algorithm>
@ -30,23 +35,30 @@ using namespace widgets;
Workspace::Workspace()
: Box(JI_VERTICAL)
, m_activeView(NULL)
, m_mainPart(new WorkspacePart)
, m_activePart(m_mainPart)
{
SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
setBgColor(theme->getColor(ThemeColor::Workspace));
addChild(m_mainPart);
}
Workspace::~Workspace()
{
// No views at this point.
ASSERT(m_views.empty());
}
void Workspace::addView(WorkspaceView* view)
{
m_views.push_back(view);
addChild(view->getContentWidget());
m_activePart->addView(view);
setActiveView(view);
App::instance()->getMainWindow()->getTabsBar()->addTab(dynamic_cast<TabView*>(view));
ActiveViewChanged(); // Fire ActiveViewChanged event
}
void Workspace::removeView(WorkspaceView* view)
@ -55,39 +67,104 @@ void Workspace::removeView(WorkspaceView* view)
ASSERT(it != m_views.end());
m_views.erase(it);
removeChild(view->getContentWidget());
m_activePart->removeView(view);
if (m_activePart->getViewCount() == 0 &&
m_activePart->getParent() != this) {
m_activePart = destroyPart(m_activePart);
}
setActiveView(NULL);
App::instance()->getMainWindow()->getTabsBar()->removeTab(dynamic_cast<TabView*>(view));
ActiveViewChanged(); // Fire ActiveViewChanged event
}
WorkspaceView* Workspace::getActiveView()
{
ASSERT(m_activePart != NULL);
return m_activePart->getActiveView();
}
void Workspace::setActiveView(WorkspaceView* view)
{
m_activeView = view;
ASSERT(view != NULL);
Widget* newContent = (view ? view->getContentWidget(): NULL);
WidgetsList children = getChildren();
UI_FOREACH_WIDGET(children, it) {
if ((*it) != newContent)
(*it)->setVisible(false);
}
WorkspacePart* viewPart =
static_cast<WorkspacePart*>(view->getContentWidget()->getParent());
if (newContent) {
newContent->setExpansive(true);
newContent->setVisible(true);
newContent->requestFocus();
}
viewPart->setActiveView(view);
layout();
m_activePart = viewPart;
ActiveViewChanged(); // Fire ActiveViewChanged event
}
void Workspace::splitView(WorkspaceView* view, int orientation)
{
// TODO
WorkspacePart* viewPart =
static_cast<WorkspacePart*>(view->getContentWidget()->getParent());
// Create a new splitter to add new WorkspacePart on it: the given
// "viewPart" and a new part named "newPart".
Splitter* splitter = new Splitter(Splitter::ByPercentage, orientation);
splitter->setExpansive(true);
// Create the new part to contain the cloned view (see below, "newView").
WorkspacePart* newPart = new WorkspacePart();
// Replace the "viewPart" with the "splitter".
Widget* parent = viewPart->getParent();
parent->replaceChild(viewPart, splitter);
splitter->addChild(viewPart);
splitter->addChild(newPart);
// The new part is the active one.
m_activePart = newPart;
// Clone the workspace view, and add it to the active part (newPart)
// using Workspace::addView().
WorkspaceView* newView = view->cloneWorkspaceView();
addView(newView);
setActiveView(newView);
layout();
newView->onClonedFrom(view);
ActiveViewChanged(); // Fire ActiveViewChanged event
}
void Workspace::closeView(WorkspaceView* view)
WorkspacePart* Workspace::destroyPart(WorkspacePart* part)
{
// TODO
ASSERT(part != NULL);
ASSERT(part->getViewCount() == 0);
Widget* splitter = part->getParent();
ASSERT(splitter != this);
ASSERT(splitter->getChildren().size() == 2);
splitter->removeChild(part);
delete part;
ASSERT(splitter->getChildren().size() == 1);
Widget* otherWidget = splitter->getChildren().front();
WorkspacePart* otherPart = dynamic_cast<WorkspacePart*>(otherWidget);
if (otherPart == NULL) {
Widget* widget = otherWidget;
for (;;) {
otherPart = widget->findFirstChildByType<WorkspacePart>();
if (otherPart != NULL)
break;
widget = widget->getChildren().front();
}
}
ASSERT(otherPart != NULL);
splitter->removeChild(otherWidget);
splitter->getParent()->replaceChild(splitter, otherWidget);
delete splitter;
layout();
return otherPart;
}
void Workspace::makeUnique(WorkspaceView* view)

View File

@ -19,33 +19,44 @@
#ifndef WIDGETS_WORKSPACE_H_INCLUDED
#define WIDGETS_WORKSPACE_H_INCLUDED
#include "base/signal.h"
#include "ui/box.h"
#include "widgets/workspace_views.h"
#include <vector>
namespace widgets {
class WorkspaceView;
typedef std::vector<WorkspaceView*> WorkspaceViews;
class WorkspacePart;
class Workspace : public ui::Box
{
public:
typedef WorkspaceViews::iterator iterator;
Workspace();
~Workspace();
iterator begin() { return m_views.begin(); }
iterator end() { return m_views.end(); }
void addView(WorkspaceView* view);
void removeView(WorkspaceView* view);
WorkspaceView* getActiveView() { return m_activeView; }
WorkspaceView* getActiveView();
void setActiveView(WorkspaceView* view);
void splitView(WorkspaceView* view, int orientation);
void closeView(WorkspaceView* view);
void makeUnique(WorkspaceView* view);
Signal0<void> ActiveViewChanged;
private:
WorkspaceView* m_activeView;
WorkspacePart* destroyPart(WorkspacePart* part);
// All views of all parts.
WorkspaceViews m_views;
WorkspacePart* m_mainPart;
WorkspacePart* m_activePart;
};
} // namespace widgets

View File

@ -0,0 +1,84 @@
/* ASEPRITE
* Copyright (C) 2001-2013 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include "widgets/workspace_part.h"
#include "skin/skin_theme.h"
#include "widgets/workspace_view.h"
#include <algorithm>
using namespace ui;
using namespace widgets;
WorkspacePart::WorkspacePart()
: Box(JI_VERTICAL)
, m_activeView(NULL)
{
SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
setBgColor(theme->getColor(ThemeColor::Workspace));
setExpansive(true);
}
WorkspacePart::~WorkspacePart()
{
}
void WorkspacePart::addView(WorkspaceView* view)
{
m_views.push_back(view);
addChild(view->getContentWidget());
setActiveView(view);
}
void WorkspacePart::removeView(WorkspaceView* view)
{
WorkspaceViews::iterator it = std::find(m_views.begin(), m_views.end(), view);
ASSERT(it != m_views.end());
it = m_views.erase(it);
removeChild(view->getContentWidget());
setActiveView((it != m_views.end() ?
*it : (!m_views.empty() ? m_views.front(): NULL)));
}
void WorkspacePart::setActiveView(WorkspaceView* view)
{
m_activeView = view;
Widget* newContent = (view ? view->getContentWidget(): NULL);
WidgetsList children = getChildren();
UI_FOREACH_WIDGET(children, it) {
if ((*it) != newContent)
(*it)->setVisible(false);
}
if (newContent) {
newContent->setExpansive(true);
newContent->setVisible(true);
newContent->requestFocus();
}
layout();
}

View File

@ -0,0 +1,49 @@
/* ASEPRITE
* Copyright (C) 2001-2013 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef WIDGETS_WORKSPACE_PART_H_INCLUDED
#define WIDGETS_WORKSPACE_PART_H_INCLUDED
#include "ui/box.h"
#include "widgets/workspace_views.h"
#include <vector>
namespace widgets {
class WorkspacePart : public ui::Box
{
public:
WorkspacePart();
~WorkspacePart();
size_t getViewCount() { return m_views.size(); }
void addView(WorkspaceView* view);
void removeView(WorkspaceView* view);
WorkspaceView* getActiveView() { return m_activeView; }
void setActiveView(WorkspaceView* view);
private:
WorkspaceView* m_activeView;
WorkspaceViews m_views;
};
} // namespace widgets
#endif

View File

@ -29,6 +29,12 @@ namespace widgets {
virtual ~WorkspaceView() { }
virtual ui::Widget* getContentWidget() = 0;
virtual WorkspaceView* cloneWorkspaceView() = 0;
// Called after the view is added in the correct position inside
// the workspace. It can be used to copy/clone scroll position
// from the original view.
virtual void onClonedFrom(WorkspaceView* from) = 0;
};
} // namespace widgets

View File

@ -0,0 +1,31 @@
/* ASEPRITE
* Copyright (C) 2001-2013 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef WIDGETS_WORKSPACE_VIEWS_H_INCLUDED
#define WIDGETS_WORKSPACE_VIEWS_H_INCLUDED
#include <vector>
namespace widgets {
class WorkspaceView;
typedef std::vector<WorkspaceView*> WorkspaceViews;
} // namespace widgets
#endif