// Copyright 2008, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "chrome/browser/wizard/wizard.h" #include #include "chrome/browser/wizard/wizard_step.h" #include "chrome/browser/standard_layout.h" #include "chrome/common/l10n_util.h" #include "chrome/common/resource_bundle.h" #include "chrome/common/stl_util-inl.h" #include "chrome/views/accelerator.h" #include "chrome/views/label.h" #include "chrome/views/native_button.h" #include "generated_resources.h" #include "SkBitmap.h" static const int kMinButtonWidth = 100; static const int kWizardWidth = 400; static const int kWizardHeight = 300; //////////////////////////////////////////////////////////////////////////////// // // WizardView is the Wizard top level view // //////////////////////////////////////////////////////////////////////////////// class WizardView : public ChromeViews::View, public ChromeViews::NativeButton::Listener { public: WizardView(Wizard* owner) : owner_(owner), contents_(NULL), custom_navigation_descriptor_(NULL) { title_ = new ChromeViews::Label(L""); title_->SetHorizontalAlignment(ChromeViews::Label::ALIGN_LEFT); title_->SetFont( ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::LargeFont)); AddChildView(title_); cancel_ = new ChromeViews::NativeButton(l10n_util::GetString(IDS_WIZARD_CANCEL)); AddChildView(cancel_); cancel_->SetListener(this); ChromeViews::Accelerator accelerator(VK_ESCAPE, false, false, false); cancel_->AddAccelerator(accelerator); previous_ = new ChromeViews::NativeButton( l10n_util::GetString(IDS_WIZARD_PREVIOUS)); AddChildView(previous_); previous_->SetListener(this); next_ = new ChromeViews::NativeButton(l10n_util::GetString(IDS_WIZARD_NEXT)); AddChildView(next_); next_->SetListener(this); } virtual ~WizardView() { // Views provided by WizardStep instances are owned by the step if (contents_) { RemoveChildView(contents_); contents_ = NULL; } // Sub views are deleted by the view system } void ComputeButtonSize(int* width, int *height) { *width = kMinButtonWidth; CSize s; cancel_->GetPreferredSize(&s); *height = s.cy; *width = std::max(*width, static_cast(s.cx)); previous_->GetPreferredSize(&s); *width = std::max(*width, static_cast(s.cx)); *height = std::max(*height, static_cast(s.cy)); next_->GetPreferredSize(&s); *width = std::max(*width, static_cast(s.cx)); *height = std::max(*height, static_cast(s.cy)); } virtual void Layout() { View* parent = GetParent(); DCHECK(parent); if (!parent) return; if (title_->GetText().empty()) { title_->SetBounds(0, 0, 0, 0); title_->SetVisible(false); } else { CSize s; title_->SetVisible(true); title_->GetPreferredSize(&s); title_->SetBounds(kPanelHorizMargin, kPanelVertMargin, GetWidth() - (2 * kPanelVertMargin), s.cy); } int bw, bh; ComputeButtonSize(&bw, &bh); int button_y = GetHeight() - kPanelVertMargin - bh; cancel_->SetBounds(kPanelHorizMargin, button_y, bw, bh); next_->SetBounds(GetWidth() - kPanelHorizMargin - bw, button_y, bw, bh); previous_->SetBounds(next_->GetX() - kPanelHorizMargin - bw, button_y, bw, bh); if (contents_) { int y = title_->GetY() + title_->GetHeight() + kPanelVertMargin; contents_->SetBounds(kPanelHorizMargin, y, GetWidth() - (2 * kPanelHorizMargin), cancel_->GetY() - kPanelVertMargin - y); contents_->Layout(); } } virtual void GetPreferredSize(CSize* out) { CSize s; int w = 0, h = 0; if (contents_) { int extra_margin = (2 * kPanelVertMargin); contents_->GetPreferredSize(&s); w = s.cx; h = s.cy + extra_margin; } if (!title_->GetText().empty()) { title_->GetPreferredSize(&s); w = std::max(w, static_cast(s.cx)); h += s.cy; } int bw, bh; ComputeButtonSize(&bw, &bh); h += bh; w += (2 * kPanelHorizMargin); h += (2 * kPanelVertMargin); out->cx = std::max(kWizardWidth, w); out->cy = std::max(kWizardHeight, h); } virtual void ButtonPressed(ChromeViews::NativeButton *sender) { if (sender == cancel_) owner_->Cancel(); else if (sender == next_) owner_->SelectNextStep(); else if (sender == previous_) owner_->SelectPreviousStep(); } typedef enum { FIRST_STEP_STYLE, NORMAL_STEP_STYLE, LAST_STEP_STYLE, CUSTOM_STYLE } ContentsStyle; void SetContents(ChromeViews::View* v, ContentsStyle s) { if (contents_) RemoveChildView(contents_); custom_navigation_descriptor_ = NULL; contents_ = v; // Note: we change the navigation button only if a view is provided. // if |v| is NULL, this wizard is closing and updating anything may // cause a flash. if (contents_) { switch (s) { case FIRST_STEP_STYLE: previous_->SetVisible(false); next_->SetVisible(true); next_->SetLabel(l10n_util::GetString(IDS_WIZARD_NEXT)); break; case NORMAL_STEP_STYLE: previous_->SetVisible(true); next_->SetVisible(true); next_->SetLabel(l10n_util::GetString(IDS_WIZARD_NEXT)); break; case LAST_STEP_STYLE: previous_->SetVisible(true); next_->SetVisible(true); next_->SetLabel(l10n_util::GetString(IDS_WIZARD_DONE)); break; } AddChildView(contents_); cancel_->SetVisible(true); // Reset the button labels to the default values. previous_->SetLabel(l10n_util::GetString(IDS_WIZARD_PREVIOUS)); next_->SetLabel(l10n_util::GetString(IDS_WIZARD_NEXT)); } } void SetTitle(const std::wstring& title) { title_->SetText(title); } void NavigationDescriptorChanged() { if (custom_navigation_descriptor_) { SetCustomNavigationDescriptor(custom_navigation_descriptor_); } } void SetCustomNavigationDescriptor(WizardNavigationDescriptor* wnd) { custom_navigation_descriptor_ = wnd; if (wnd->custom_default_button_) { if (wnd->default_label_.empty()) { next_->SetVisible(false); } else { next_->SetVisible(true); next_->SetLabel(wnd->default_label_); } } if (wnd->custom_alternate_button_) { if (wnd->alternate_label_.empty()) { previous_->SetVisible(false); } else { previous_->SetVisible(true); previous_->SetLabel(wnd->alternate_label_); } } cancel_->SetVisible(wnd->can_cancel_); } WizardNavigationDescriptor* GetCustomNavigationDescriptor() { return custom_navigation_descriptor_; } void EnableNextButton(bool f) { if (next_) next_->SetEnabled(f); } bool IsNextButtonEnabled() { if (next_) return next_->IsEnabled(); else return false; } void EnablePreviousButton(bool f) { if (previous_) previous_->SetEnabled(f); } bool IsPreviousButtonEnabled() { if (previous_) return previous_->IsEnabled(); else return false; } private: ChromeViews::Label* title_; ChromeViews::NativeButton* next_; ChromeViews::NativeButton* previous_; ChromeViews::NativeButton* cancel_; ChromeViews::View* contents_; WizardNavigationDescriptor* custom_navigation_descriptor_; Wizard* owner_; DISALLOW_EVIL_CONSTRUCTORS(WizardView); }; //////////////////////////////////////////////////////////////////////////////// // // Wizard implementation // //////////////////////////////////////////////////////////////////////////////// Wizard::Wizard(WizardDelegate* d) : delegate_(d), view_(NULL), state_(NULL), selected_step_(-1), is_running_(false) { } Wizard::~Wizard() { Abort(); delete view_; RemoveAllSteps(); delete state_; STLDeleteContainerPairSecondPointers(images_.begin(), images_.end()); } void Wizard::SetState(DictionaryValue* state) { delete state_; state_ = state; } DictionaryValue* Wizard::GetState() { return state_; } void Wizard::AddStep(WizardStep* s) { steps_.push_back(s); } int Wizard::GetStepCount() const { return static_cast(steps_.size()); } WizardStep* Wizard::GetStepAt(int index) { DCHECK(index >= 0 && index < static_cast(steps_.size())); return steps_[index]; } void Wizard::RemoveAllSteps() { DCHECK(!is_running_); int c = static_cast(steps_.size()); while (--c >= 0) steps_[c]->Dispose(); steps_.clear(); } ChromeViews::View* Wizard::GetTopLevelView() { if (!view_) { view_ = new WizardView(this); // We own the view and it should never be deleted by the parent // view view_->SetParentOwned(false); } return view_; } void Wizard::Start() { DCHECK(view_ && view_->GetParent()); DCHECK(steps_.size() > 0); DCHECK(!is_running_); is_running_ = true; SelectStepAt(0); view_->Layout(); } void Wizard::Reset() { is_running_ = false; if (selected_step_ != -1) steps_[selected_step_]->WillBecomeInvisible(this, WizardStep::CANCEL_ACTION); selected_step_ = -1; view_->EnableNextButton(true); view_->EnablePreviousButton(true); view_->SetContents(NULL, WizardView::NORMAL_STEP_STYLE); if (view_->GetParent()) view_->GetParent()->RemoveChildView(view_); } void Wizard::WizardDone(bool commit) { if (is_running_) { Reset(); is_running_ = false; if (delegate_) delegate_->WizardClosed(commit); } } void Wizard::Abort() { WizardDone(false); } // Note: this private method doesn't call WillBecomeInvisible because // it needs to be called before deciding what step to select next. void Wizard::SelectStepAt(int index) { DCHECK(index >= 0 && index < static_cast(steps_.size())); if (index == selected_step_) return; selected_step_ = index; WizardView::ContentsStyle style = WizardView::NORMAL_STEP_STYLE; if (selected_step_ == 0) style = WizardView::FIRST_STEP_STYLE; else if (selected_step_ == (static_cast(steps_.size()) - 1)) style = WizardView::LAST_STEP_STYLE; view_->SetContents(steps_[selected_step_]->GetView(this), style); steps_[selected_step_]->DidBecomeVisible(this); WizardNavigationDescriptor* wnd = steps_[selected_step_]->GetNavigationDescriptor(); if (wnd) view_->SetCustomNavigationDescriptor(wnd); view_->SetTitle(steps_[selected_step_]->GetTitle(this)); CSize s; view_->GetPreferredSize(&s); if (view_->GetWidth() != s.cx || view_->GetHeight() != s.cy) delegate_->ResizeTopLevelView(s.cx, s.cy); view_->Layout(); view_->SchedulePaint(); } void Wizard::NavigationDescriptorChanged() { view_->NavigationDescriptorChanged(); } void Wizard::Cancel() { Abort(); } void Wizard::SelectStep(bool is_forward) { int delta = is_forward ? 1 : -1; WizardNavigationDescriptor* wnd = view_->GetCustomNavigationDescriptor(); if (wnd) { if (is_forward && wnd->custom_default_button_) delta = wnd->default_offset_; else if (!is_forward && wnd->custom_alternate_button_) delta = wnd->alternate_offset_; } if (selected_step_ != -1) { steps_[selected_step_]->WillBecomeInvisible( this, is_forward ? WizardStep::DEFAULT_ACTION : WizardStep::ALTERNATE_ACTION); } bool step_selected = false; int i; for (i = selected_step_ + delta; i >= 0 && i < static_cast(steps_.size()); i += delta) { if (steps_[i]->IsEnabledFor(this)) { SelectStepAt(i); step_selected = true; break; } } if (!step_selected && is_forward) { WizardDone(true); } } void Wizard::SelectNextStep() { SelectStep(true); } void Wizard::SelectPreviousStep() { SelectStep(false); } void Wizard::SetImage(const std::wstring& image_name, SkBitmap* image) { ImageMap::iterator i = images_.find(image_name); if (i != images_.end()) delete i->second; images_[image_name] = image; } SkBitmap* Wizard::GetImage(const std::wstring& image_name, bool remove_image) { ImageMap::iterator i = images_.find(image_name); SkBitmap* bitmap = NULL; if (i != images_.end()) { bitmap = i->second; if (remove_image) { images_.erase(i); } } return bitmap; } void Wizard::EnableNextStep(bool flag) { view_->EnableNextButton(flag); } bool Wizard::IsNextStepEnabled() const { return view_->IsNextButtonEnabled(); } void Wizard::EnablePreviousStep(bool flag) { view_->EnablePreviousButton(flag); } bool Wizard::IsPreviousStepEnabled() const { return view_->IsPreviousButtonEnabled(); }