From: <geo...@us...> - 2014-04-12 09:42:20
|
Revision: 7046 http://sourceforge.net/p/freeorion/code/7046 Author: geoffthemedio Date: 2014-04-12 09:42:16 +0000 (Sat, 12 Apr 2014) Log Message: ----------- -Slighted groomed patch by Mitten.O redesigning the save file dialog and adding some extra info to save files to support it. -Added Vegavis and Mitten.O to credits. Modified Paths: -------------- trunk/FreeOrion/GG/GG/ListBox.h trunk/FreeOrion/GG/src/ListBox.cpp trunk/FreeOrion/UI/InGameMenu.cpp trunk/FreeOrion/UI/InGameMenu.h trunk/FreeOrion/client/human/CMakeLists.txt trunk/FreeOrion/client/human/HumanClientApp.cpp trunk/FreeOrion/default/credits.xml trunk/FreeOrion/default/stringtables/en.txt trunk/FreeOrion/msvc2010/FreeOrion/FreeOrion.vcxproj trunk/FreeOrion/msvc2010/FreeOrion/FreeOrion.vcxproj.filters trunk/FreeOrion/server/SaveLoad.cpp trunk/FreeOrion/util/Directories.cpp trunk/FreeOrion/util/Directories.h trunk/FreeOrion/util/MultiplayerCommon.cpp trunk/FreeOrion/util/MultiplayerCommon.h trunk/FreeOrion/util/SerializeMultiplayerCommon.cpp Added Paths: ----------- trunk/FreeOrion/UI/SaveFileDialog.cpp trunk/FreeOrion/UI/SaveFileDialog.h Modified: trunk/FreeOrion/GG/GG/ListBox.h =================================================================== --- trunk/FreeOrion/GG/GG/ListBox.h 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/GG/GG/ListBox.h 2014-04-12 09:42:16 UTC (rev 7046) @@ -512,6 +512,7 @@ void RecreateScrolls(); ///< recreates the vertical and horizontal scrolls as needed. void ResetAutoScrollVars(); ///< resets all variables related to auto-scroll to their initial values void Resort(); ///< performs a full resort of all rows, using the current sort functor. + Row& ColHeaders(); ///< returns the row containing the headings for the columns, if any. If undefined, the returned heading Row will have size() 0. non-const for derivers //@} private: Modified: trunk/FreeOrion/GG/src/ListBox.cpp =================================================================== --- trunk/FreeOrion/GG/src/ListBox.cpp 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/GG/src/ListBox.cpp 2014-04-12 09:42:16 UTC (rev 7046) @@ -1827,6 +1827,9 @@ m_first_row_shown = m_rows.empty() ? m_rows.end() : m_rows.begin(); } +ListBox::Row& ListBox::ColHeaders() +{ return *m_header_row; } + void ListBox::ConnectSignals() { if (m_vscroll) Modified: trunk/FreeOrion/UI/InGameMenu.cpp =================================================================== --- trunk/FreeOrion/UI/InGameMenu.cpp 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/UI/InGameMenu.cpp 2014-04-12 09:42:16 UTC (rev 7046) @@ -3,6 +3,7 @@ #include "ClientUI.h" #include "CUIControls.h" #include "OptionsWnd.h" +#include "SaveFileDialog.h" #include "../client/human/HumanClientApp.h" #include "../network/Networking.h" #include "../util/i18n.h" @@ -152,20 +153,16 @@ save_file_types.push_back(std::make_pair(UserString("GAME_MENU_SAVE_FILES"), "*" + SAVE_GAME_EXTENSION)); try { - Logger().debugStream() << "... getting save path string"; - std::string path_string = PathString(GetSaveDir()); - Logger().debugStream() << "... got save path string: " << path_string; - - Logger().debugStream() << "... running file dialog"; - FileDlg dlg(path_string, "", true, false, save_file_types); + Logger().debugStream() << "... running save file dialog"; + SaveFileDialog dlg(SAVE_GAME_EXTENSION); dlg.Run(); if (!dlg.Result().empty()) { if (!app->CanSaveNow()) { Logger().errorStream() << "InGameMenu::Save aborting; Client app can't save now"; throw std::runtime_error(UserString("UNABLE_TO_SAVE_NOW_TRY_AGAIN")); } - Logger().debugStream() << "... initiating save"; - app->SaveGame(*dlg.Result().begin()); + Logger().debugStream() << "... initiating save to " << dlg.Result() ; + app->SaveGame(dlg.Result()); CloseClicked(); Logger().debugStream() << "... save done"; } Modified: trunk/FreeOrion/UI/InGameMenu.h =================================================================== --- trunk/FreeOrion/UI/InGameMenu.h 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/UI/InGameMenu.h 2014-04-12 09:42:16 UTC (rev 7046) @@ -33,11 +33,11 @@ void Exit(); //!< when m_quit_btn button is pressed void Done(); //!< when m_done_btn is pressed - CUIButton* m_save_btn; //!< Save game button - CUIButton* m_load_btn; //!< Load game button - CUIButton* m_options_btn;//!< Options button - CUIButton* m_done_btn; //!< Done button - CUIButton* m_exit_btn; //!< Quit game button + CUIButton* m_save_btn; //!< Save game button + CUIButton* m_load_btn; //!< Load game button + CUIButton* m_options_btn; //!< Options button + CUIButton* m_done_btn; //!< Done button + CUIButton* m_exit_btn; //!< Quit game button }; #endif // _InGameMenu_h_ Added: trunk/FreeOrion/UI/SaveFileDialog.cpp =================================================================== --- trunk/FreeOrion/UI/SaveFileDialog.cpp (rev 0) +++ trunk/FreeOrion/UI/SaveFileDialog.cpp 2014-04-12 09:42:16 UTC (rev 7046) @@ -0,0 +1,522 @@ +#include "SaveFileDialog.h" + +#include "ClientUI.h" +#include "CUIControls.h" +#include "OptionsWnd.h" +#include "../client/human/HumanClientApp.h" +#include "../network/Networking.h" +#include "../util/i18n.h" +#include "../util/MultiplayerCommon.h" +#include "../util/OptionsDB.h" +#include "../util/Directories.h" +#include "../util/Logger.h" +#include "../util/Serialize.h" + +#include <GG/Button.h> +#include <GG/Clr.h> +#include <GG/DrawUtil.h> +#include <GG/utf8/checked.h> +#include <GG/dialogs/FileDlg.h> + +#include <boost/filesystem/operations.hpp> +#include <boost/filesystem/fstream.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/cast.hpp> +#include <fstream> + +namespace fs = boost::filesystem; + +namespace { + const GG::X SAVE_FILE_DIALOG_WIDTH ( 600 ); + const GG::Y SAVE_FILE_DIALOG_HEIGHT ( 400 ); + const GG::X SAVE_FILE_DIALOG_MIN_WIDTH ( 160 ); + const GG::Y SAVE_FILE_DIALOG_MIN_HEIGHT ( 100 ); + + const GG::X WIDE_COLUMN_WIDTH ( 198 ); + + const GG::X SAVE_FILE_BUTTON_MARGIN ( 10 ); + const unsigned int SAVE_FILE_CELL_MARGIN = 2; + const unsigned int ROW_MARGIN = 2; + const std::string UNABLE_TO_OPEN_FILE ( "Unable to open file" ); + + /// Splits time and date on separate lines for an ISO datetime string + std::string split_time ( const std::string& time ) { + std::string result = time; + std::string::size_type pos = result.find ( "T" ); + if ( pos != std::string::npos ) { + result.replace ( pos, 1, "\n" ); + } + return result; + } + + /// Populates a SaveGamePreviewData from a given file + /// returns true on success, false if preview data could not be found + bool LoadSaveGamePreviewData ( const fs::path& path, SaveGamePreviewData& preview ) { + if ( !fs::exists ( path ) ) { + Logger().debugStream() << "LoadSaveGamePreviewData: Save file note found: " << path.string(); + return false; + } + + fs::ifstream ifs ( path, std::ios_base::binary ); + + if ( !ifs ) + throw std::runtime_error ( UNABLE_TO_OPEN_FILE ); + try { + freeorion_iarchive ia ( ifs ); + Logger().debugStream() << "LoadSaveGamePreviewData: Loading preview from:" << path.string(); + ia >> BOOST_SERIALIZATION_NVP ( preview ); + } catch ( const std::exception& e ) { + Logger().errorStream() << "LoadSaveGamePreviewData: Failed to read preview of " << path.string() << " because: " << e.what(); + return false; + } + if ( preview.Valid() ) { + Logger().debugStream() << "LoadSaveGamePreviewData: Successfully loaded preview from:" << path.string(); + return true; + } else { + Logger().debugStream() << "LoadSaveGamePreviewData: Passing save file with no preview: " << path.string(); + return false; + } + } + + /// Creates a text control that support resizing and word wrap. + GG::TextControl* CreateResizingText(const std::string& string, + const boost::shared_ptr<GG::Font>& font, + const GG::Clr& color) + { + // Calculate the extent manually to ensure the control stretches to full + // width when possible. Otherwise it would always word break. + GG::Pt extent = font->TextExtent ( string ); + GG::TextControl* text = new GG::TextControl ( GG::X0, GG::Y0, extent.x, extent.y, + string, font, color, + GG::FORMAT_WORDBREAK | GG::FORMAT_LEFT ); + text->ClipText ( true ); + text->SetChildClippingMode ( GG::Wnd::ClipToClient ); + return text; + } +} + +/** A Specialized row for the save dialog list box. */ +class SaveFileRow: public GG::ListBox::Row { +public: + /// What sort of a row + enum RowType { + HEADER, PREVIEW + }; + + /// Creates a header row + SaveFileRow() : + m_type ( HEADER ) + { + SetMargin ( ROW_MARGIN ); + CalculateColumnWidths(); + + boost::shared_ptr<GG::Font> font = ClientUI::GetFont(); + GG::Clr head_clr = ClientUI::TextColor(); + + push_back ( new GG::TextControl ( GG::X0, GG::Y0, UserString ( "SAVE_TIME_TITLE" ), font, head_clr ) ); + push_back ( new GG::TextControl ( GG::X0, GG::Y0, UserString ( "SAVE_TURN_TITLE" ), font, head_clr ) ); + push_back ( new GG::TextControl ( GG::X0, GG::Y0, UserString ( "SAVE_PLAYER_TITLE" ), font, head_clr ) ); + push_back ( new GG::TextControl ( GG::X0, GG::Y0, UserString ( "SAVE_EMPIRE_TITLE" ), font, head_clr ) ); + push_back ( new GG::TextControl ( GG::X0, GG::Y0, UserString ( "SAVE_FILE_TITLE" ), font, head_clr ) ); + } + + /// Creates a row for the given savefile + SaveFileRow ( const std::string& filename, const SaveGamePreviewData& preview ) : + m_type ( PREVIEW ) + { + SetMargin ( ROW_MARGIN ); + this->m_filename = filename; + CalculateColumnWidths(); + + boost::shared_ptr<GG::Font> font = ClientUI::GetFont(); + GG::Clr item_clr = ClientUI::TextColor(); + + std::string save_time = split_time ( preview.save_time ); + + + push_back ( new GG::TextControl ( GG::X0, GG::Y0, m_time_column_width, font->Height(), + save_time, font, item_clr, + GG::FORMAT_LEFT ) ); + push_back ( new GG::TextControl ( GG::X0, GG::Y0, m_turn_column_width, font->Height(), + std::string ( " " ) + boost::lexical_cast<std::string> ( preview.current_turn ), font, item_clr, + GG::FORMAT_LEFT ) ); + + push_back ( CreateResizingText(preview.main_player_name, font, item_clr) ); + push_back ( CreateResizingText(preview.main_player_empire_name, font, preview.main_player_empire_colour) ); + push_back ( CreateResizingText(filename, font, item_clr) ); + } + + const std::string& Filename() const { + return m_filename; + } + + const RowType Type() const { + return m_type; + } + + // Overrides + virtual void Render() { + if ( m_type != HEADER ) { + GG::FlatRectangle ( ClientUpperLeft(), ClientLowerRight() - GG::Pt ( GG::X ( SAVE_FILE_CELL_MARGIN ), GG::Y0 ), + GG::CLR_ZERO, ClientUI::WndOuterBorderColor(), 1u ); + } + } + + /** Excludes border from the client area. */ + virtual GG::Pt ClientUpperLeft() const { + return UpperLeft() + GG::Pt ( GG::X ( SAVE_FILE_CELL_MARGIN ), GG::Y ( SAVE_FILE_CELL_MARGIN ) ); + } + + /** Excludes border from the client area. */ + virtual GG::Pt ClientLowerRight() const { + return LowerRight() - GG::Pt ( GG::X ( SAVE_FILE_CELL_MARGIN*2 ), GG::Y ( SAVE_FILE_CELL_MARGIN ) ); + } + + virtual void SizeMove ( const GG::Pt& ul, const GG::Pt& lr ) { + if ( GetLayout() ) { + GG::Layout* layout = GetLayout(); + int cols = layout->Columns(); + if ( cols > 0 ) { + layout->SetColumnStretch ( 0, 0 ); + layout->SetMinimumColumnWidth ( 0, m_time_column_width ); + } + if ( cols > 1 ) { + layout->SetColumnStretch ( 1, 0 ); + layout->SetMinimumColumnWidth ( 1, m_turn_column_width ); + } + if ( cols > 2 ) { + layout->SetColumnStretch ( 2, 1.0 ); + } + if ( cols > 3 ) { + layout->SetColumnStretch ( 3, 1.0 ); + } + if ( cols > 4 ) { + layout->SetColumnStretch ( 4, 1.0 ); + } + } + GG::ListBox::Row::SizeMove ( ul, lr ); + } + + private: + void CalculateColumnWidths() { + boost::shared_ptr<GG::Font> font = ClientUI::GetFont(); + // We need to maintain the fixed sizes since the base list box messes them + m_time_column_width = font->TextExtent ( "YYYY-MM-DD\nHH:MM.ss" ).x + GG::X ( SAVE_FILE_CELL_MARGIN ); + m_turn_column_width = font->TextExtent ( "9999" ).x + GG::X ( SAVE_FILE_CELL_MARGIN ); + } + + std::string m_filename; + GG::X m_time_column_width; + GG::X m_turn_column_width; + RowType m_type; +}; + +class SaveFileListBox : public CUIListBox { +public: + SaveFileListBox() : + CUIListBox ( GG::X0, GG::Y0, GG::X1, GG::Y1 ) + { + SetNumCols ( 5 ); + SetColWidth ( 0, GG::X0 ); + SetColWidth ( 1, GG::X0 ); + SetColWidth ( 2, GG::X0 ); + SetColWidth ( 3, GG::X0 ); + SetColWidth ( 4, GG::X0 ); + LockColWidths(); + } + + virtual void SizeMove ( const GG::Pt& ul, const GG::Pt& lr ) { + const GG::Pt old_size = Size(); + CUIListBox::SizeMove ( ul, lr ); + if ( old_size != Size() ) { + RefreshRowSizes(); + } + } + + void RefreshRowSizes() { + const GG::Pt row_size = ListRowSize(); + ColHeaders().Resize ( row_size ); + for ( GG::ListBox::iterator it = begin(); it != end(); ++it ) { + ( *it )->Resize ( row_size ); + } + } + + GG::Pt ListRowSize() const { + return GG::Pt ( Width() - RightMargin(), ListRowHeight() ); + } + + static GG::Y ListRowHeight() { + return GG::Y ( ClientUI::Pts() * 2 ); + } + + /// Loads preview data on all save files in a directory specidifed by path. + /// @param [in] path The path of the directory + /// @param [out] previews The preview datas indexed by file names + void LoadSaveGamePreviews ( const fs::path& path, const std::string& extension ) { + SaveGamePreviewData data; + fs::directory_iterator end_it; + + if ( !fs::exists ( path ) ) { + Logger().errorStream() << "SaveFileListBox::LoadSaveGamePreviews: Save Game directory \"" << path << "\" not found"; + return; + } + if ( !fs::is_directory ( path ) ) { + Logger().errorStream() << "SaveFileListBox::LoadSaveGamePreviews: Save Game directory \"" << path << "\" was not a directory"; + return; + } + + for ( fs::directory_iterator it ( path ); it != end_it; ++it ) { + try { + std::string filename = PathString ( it->path().filename() ); + if ( it->path().filename().extension() == extension ) { + if ( LoadSaveGamePreviewData ( *it, data ) ) { + // Add preview entry to list + Insert ( new SaveFileRow ( filename, data ) ); + } + } + } catch ( const std::exception& e ) { + Logger().errorStream() << "SaveFileListBox::LoadSaveGamePreviews: Failed loading preview from " << it->path() << " because: " << e.what(); + } + } + } + + bool HasFile ( const std::string& filename ) { + for ( GG::ListBox::iterator it = begin(); it != end(); ++it ) { + SaveFileRow* row = dynamic_cast<SaveFileRow*> ( *it ); + if ( row ) { + if ( row->Filename() == filename ) { + return true; + } + } + } + return false; + } +}; + +SaveFileDialog::SaveFileDialog (const std::string& extension, bool load ) : +CUIWnd ( UserString ( "GAME_MENU_SAVE_FILES" ), + ( GG::GUI::GetGUI()->AppWidth() - SAVE_FILE_DIALOG_WIDTH ) / 2, + ( GG::GUI::GetGUI()->AppHeight() - SAVE_FILE_DIALOG_HEIGHT ) / 2, + SAVE_FILE_DIALOG_WIDTH, + SAVE_FILE_DIALOG_HEIGHT, + GG::INTERACTIVE | GG::DRAGABLE | GG::MODAL | GG::RESIZABLE ) { + m_extension = extension; + m_load_only = load; + boost::shared_ptr<GG::Font> font = ClientUI::GetFont(); + SetMinSize ( GG::Pt ( 2*SAVE_FILE_DIALOG_MIN_WIDTH, 2*SAVE_FILE_DIALOG_MIN_HEIGHT ) ); + + m_layout = new GG::Layout ( GG::X0, GG::Y0, + SAVE_FILE_DIALOG_WIDTH - LeftBorder() - RightBorder(), + SAVE_FILE_DIALOG_HEIGHT - TopBorder() - BottomBorder(), 3, 4 ); + m_layout->SetCellMargin ( SAVE_FILE_CELL_MARGIN ); + m_layout->SetBorderMargin ( SAVE_FILE_CELL_MARGIN*2 ); + + m_file_list = new SaveFileListBox(); + m_file_list->SetStyle ( GG::LIST_SINGLESEL | GG::LIST_SORTDESCENDING ); + m_file_list->SetColHeaders ( new SaveFileRow() ); + + + m_confirm_btn = new CUIButton ( UserString ( "OK" ) ); + CUIButton* cancel_btn = new CUIButton ( UserString ( "CANCEL" ) ); + + m_name_edit = new CUIEdit ( GG::X0, GG::Y0, GG::X1, "", font ); + GG::TextControl* filename_label = new GG::TextControl ( GG::X0, GG::Y0, UserString ( "SAVE_FILENAME" ), font, ClientUI::TextColor() ); + GG::TextControl* directory_label = new GG::TextControl ( GG::X0, GG::Y0, UserString ( "SAVE_DIRECTORY" ), font, ClientUI::TextColor() ); + m_current_dir_edit = new CUIEdit ( GG::X0, GG::Y0, GG::X1, PathString ( GetSaveDir() ), font ); + m_browse_dir_btn = new CUIButton ( "..." ); + + m_layout->Add ( m_file_list, 0, 0, 1, 4 ); + m_layout->Add ( filename_label, 1, 0, 1, 1 ); + m_layout->Add ( m_name_edit, 1, 1, 1, 2 ); + m_layout->Add ( m_confirm_btn, 1, 3 ); + m_layout->Add ( directory_label,2, 0 ); + m_layout->Add ( m_current_dir_edit, 2, 1 ); + m_layout->Add ( m_browse_dir_btn, 2, 2 ); + m_layout->Add ( cancel_btn, 2, 3 ); + + + m_layout->SetRowStretch ( 0, 1.0 ); + m_layout->SetMinimumRowHeight ( 1, font->TextExtent ( m_confirm_btn->Text() ).y ); + m_layout->SetMinimumRowHeight ( 2, font->TextExtent ( cancel_btn->Text() ).y ); + + m_layout->SetMinimumColumnWidth ( 0, std::max ( font->TextExtent ( filename_label->Text() ).x, + font->TextExtent ( directory_label->Text() ).x ) ); + m_layout->SetColumnStretch ( 1, 1.0 ); + m_layout->SetMinimumColumnWidth ( 2, m_browse_dir_btn->MinUsableSize().x + + SAVE_FILE_BUTTON_MARGIN*2 ); + m_layout->SetMinimumColumnWidth ( 3, std::max ( m_confirm_btn->MinUsableSize().x, + cancel_btn->MinUsableSize().x ) + + SAVE_FILE_BUTTON_MARGIN ); + + SetLayout ( m_layout ); + + GG::Connect ( m_confirm_btn->LeftClickedSignal, &SaveFileDialog::Confirm, this ); + GG::Connect ( cancel_btn->LeftClickedSignal, &SaveFileDialog::Cancel, this ); + GG::Connect ( m_file_list->SelChangedSignal, &SaveFileDialog::SelectionChanged, this ); + GG::Connect ( m_file_list->DoubleClickedSignal, &SaveFileDialog::DoubleClickRow, this ); + GG::Connect ( m_browse_dir_btn->LeftClickedSignal, &SaveFileDialog::BrowseDirectories, this ); + GG::Connect ( m_name_edit->EditedSignal, &SaveFileDialog::FileNameEdited, this ); + + UpdatePreviewList(); + + if(!m_load_only){ + m_name_edit->SetText(std::string("save-") + FilenameTimestamp()); + m_name_edit->SelectAll(); + } +} + +SaveFileDialog::~SaveFileDialog() +{} + +void SaveFileDialog::ModalInit() { + GG::Wnd::ModalInit(); + GG::GUI::GetGUI()->SetFocusWnd(m_name_edit); +} + +void SaveFileDialog::KeyPress ( GG::Key key, boost::uint32_t key_code_point, GG::Flags<GG::ModKey> mod_keys ) { + // Return without filename + if ( key == GG::GGK_ESCAPE || key == GG::GGK_F10 ) { + Cancel(); + return; + } + + // Update list on enter if directory changed by hand + if ( key == GG::GGK_RETURN ) { + if ( m_loaded_dir != m_current_dir_edit->Text() ) { + UpdatePreviewList(); + }else{ + if(GG::GUI::GetGUI()->FocusWnd() == m_name_edit){ + Confirm(); + } + } + } else { + // The keypress may have changed our choice + CheckChoiceValidity(); + } + + +} + +void SaveFileDialog::Confirm() { + Logger().debugStream() << "SaveFileDialog::Confirm: Confirming"; + + if(!CheckChoiceValidity()){ + Logger().debugStream() << "SaveFileDialog::Confirm: Invalid choice. abort."; + return; + } + + /// Check if we chose a directory + std::string choice = m_name_edit->Text(); + fs::path current_dir ( m_current_dir_edit->Text() ); + if ( !choice.empty() ) { + fs::path chosen = current_dir / choice; + if ( fs::is_directory ( chosen ) ) { + Logger().debugStream() << "SaveFileDialog::Confirm: " << chosen << " is a directory. Listing content."; + UpdateDirectory ( PathString ( chosen ) ); + return; + } else { + Logger().debugStream() << "SaveFileDialog::Confirm: File " << chosen << " chosen."; + } + } else { + Logger().debugStream() << "SaveFileDialog::Confirm: Returning no file."; + } + + CloseClicked(); +} + +void SaveFileDialog::DoubleClickRow ( GG::ListBox::iterator row ) { + m_file_list->SelectRow ( row ); + Confirm(); +} + +void SaveFileDialog::Cancel() { + Logger().debugStream() << "SaveFileDialog::Cancel: Dialog Canceled"; + + m_name_edit->SetText ( "" ); + + CloseClicked(); +} + +void SaveFileDialog::SelectionChanged ( const GG::ListBox::SelectionSet& selections ) { + if ( selections.size() == 1 ) { + GG::ListBox::Row* row = **selections.begin(); + SaveFileRow* save_row = boost::polymorphic_downcast<SaveFileRow*> ( row ); + m_name_edit -> SetText ( save_row->Filename() ); + } else { + Logger().debugStream() << "SaveFileDialog::SelectionChanged: Unexpected selection size: " << selections.size(); + } + CheckChoiceValidity(); +} + +void SaveFileDialog::BrowseDirectories() { + std::vector<std::pair<std::string, std::string> > dummy; + FileDlg dlg ( m_current_dir_edit->Text(), "", false, false, dummy ); + dlg.SelectDirectories ( true ); + dlg.Run(); + if ( !dlg.Result().empty() ) { + // Normalize the path by converting it into a path and back + fs::path choice ( *dlg.Result().begin() ); + UpdateDirectory ( PathString ( fs::canonical ( choice ) ) ); + } +} + +void SaveFileDialog::UpdateDirectory ( const std::string& newdir ) { + m_current_dir_edit->SetText ( newdir ); + UpdatePreviewList(); +} + +void SaveFileDialog::UpdatePreviewList() { + Logger().debugStream() << "SaveFileDialog::UpdatePreviewList"; + + m_file_list->Clear(); + // Needed because there is a bug in ListBox, where the headers + // never resize to less wide + m_file_list->RemoveColHeaders(); + m_file_list->SetColHeaders ( new SaveFileRow() ); + m_file_list->LoadSaveGamePreviews ( m_current_dir_edit->Text(), m_extension ); + + // Forces the width to recompute + m_file_list->RefreshRowSizes(); + // HACK: Sometimes the first row is not drawn without this + m_file_list->BringRowIntoView ( m_file_list->begin() ); + + // Remember which directory we are showing + m_loaded_dir = m_current_dir_edit->Text(); + + CheckChoiceValidity(); +} + +bool SaveFileDialog::CheckChoiceValidity() { + if ( m_load_only ) { + if ( !m_file_list->HasFile ( m_name_edit->Text() ) ) { + m_confirm_btn->Disable(); + return false; + } else { + m_confirm_btn->Disable ( false ); + return true; + } + }else{ + return true; + } +} + +void SaveFileDialog::FileNameEdited ( const std::string& filename ) { + CheckChoiceValidity(); +} + +std::string SaveFileDialog::Result() const { + std::string filename = m_name_edit->Text(); + if ( filename.empty() ) { + return ""; + } else { + // Ensure the file has an extension + std::string::size_type end = filename.length(); + std::size_t ext_length = m_extension.length(); + if ( filename.length() < ext_length || filename.substr ( end-ext_length, ext_length ) != m_extension ) { + filename.append ( m_extension ); + } + fs::path current_dir ( m_current_dir_edit->Text() ); + return PathString ( ( current_dir / filename ).string() ); + } +} Added: trunk/FreeOrion/UI/SaveFileDialog.h =================================================================== --- trunk/FreeOrion/UI/SaveFileDialog.h (rev 0) +++ trunk/FreeOrion/UI/SaveFileDialog.h 2014-04-12 09:42:16 UTC (rev 7046) @@ -0,0 +1,74 @@ +// -*- C++ -*- +//SaveFileDialog.h +#ifndef _SaveFileDialog_h_ +#define _SaveFileDialog_h_ + +#ifndef _CUIWnd_h_ +#include "CUIWnd.h" +#endif + +#include "CUIControls.h" + +#include <GG/ListBox.h> +#include <GG/Layout.h> + +#include <set> +#include <string> + +class CUIButton; +class SaveFileRow; +class SaveFileListBox; + +/** A dialog for choosing save files. + * Shows some additional infoormation in the listing and more + * in a preview section to the side. + */ +class SaveFileDialog : public CUIWnd { +public: + /** \name Structors */ //@{ + /// Constructor + /// @param extension The extension to enforce on the file name + /// @param load If set to true, only allow choosing existing files + SaveFileDialog (const std::string& extension, bool load = false ); + ~SaveFileDialog(); //!< dtor + //@} + + /** \name Mutators */ //@{ + virtual void ModalInit(); //< Called when dialog is shown. Overrides + virtual void KeyPress ( GG::Key key, boost::uint32_t key_code_point, + GG::Flags<GG::ModKey> mod_keys ); + //@} + + /// Get the chosen save files full path + std::string Result() const; + + private: + void Confirm(); //!< when m_save_btn button is pressed + void DoubleClickRow ( GG::ListBox::iterator row ); + void Cancel(); //!< when m_load_btn button is pressed + void SelectionChanged ( const GG::ListBox::SelectionSet& files ); //!< When file selection changes. + void BrowseDirectories(); + void UpdateDirectory ( const std::string& newdir ); //!< Change current directory + + void UpdatePreviewList(); + bool CheckChoiceValidity(); //!< Disables confirm if filename invalid. Returns false if not valid. + void FileNameEdited ( const std::string& filename ); //!< Called when the filename changes + + GG::Layout* m_layout; //!< The layout of the dialog + + SaveFileListBox* m_file_list; //!< The list of available saves + CUIEdit* m_name_edit; //!< The file name edit control + CUIEdit* m_current_dir_edit; //!< The editor for the save directory + CUIButton* m_browse_dir_btn; //!< Button to browse directories + CUIButton* m_confirm_btn; //!< Button to confirm choice + + std::string m_loaded_dir; //!< The directory whose contents are currently shown + std::string m_extension; //!< The save game file name extension + bool m_load_only; //!< Whether we are loading + + /// Remove copy ctor, assign + SaveFileDialog ( const SaveFileDialog& ); + SaveFileDialog& operator= ( const SaveFileDialog& ); +}; + +#endif // _SaveFileDialog_h_ Modified: trunk/FreeOrion/client/human/CMakeLists.txt =================================================================== --- trunk/FreeOrion/client/human/CMakeLists.txt 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/client/human/CMakeLists.txt 2014-04-12 09:42:16 UTC (rev 7046) @@ -90,6 +90,7 @@ ../../UI/ProductionWnd.h ../../UI/QueueListBox.h ../../UI/ResearchWnd.h + ../../UI/SaveFileDialog.h ../../UI/ServerConnectWnd.h ../../UI/ShaderProgram.h ../../UI/SidePanel.h @@ -144,6 +145,7 @@ ../../UI/ProductionWnd.cpp ../../UI/QueueListBox.cpp ../../UI/ResearchWnd.cpp + ../../UI/SaveFileDialog.cpp ../../UI/ServerConnectWnd.cpp ../../UI/ShaderProgram.cpp ../../UI/SidePanel.cpp Modified: trunk/FreeOrion/client/human/HumanClientApp.cpp =================================================================== --- trunk/FreeOrion/client/human/HumanClientApp.cpp 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/client/human/HumanClientApp.cpp 2014-04-12 09:42:16 UTC (rev 7046) @@ -14,6 +14,7 @@ #include "../../UI/IntroScreen.h" #include "../../UI/GalaxySetupWnd.h" #include "../../UI/MultiplayerLobbyWnd.h" +#include "../../UI/SaveFileDialog.h" #include "../../UI/ServerConnectWnd.h" #include "../../UI/Sound.h" #include "../../network/Message.h" @@ -42,7 +43,6 @@ #include <boost/filesystem/fstream.hpp> #include <boost/format.hpp> #include <boost/serialization/vector.hpp> -#include <boost/date_time/posix_time/posix_time.hpp> #include <sstream> @@ -552,13 +552,10 @@ } } else { try { - std::vector<std::pair<std::string, std::string> > save_file_types; - save_file_types.push_back(std::pair<std::string, std::string>(UserString("GAME_MENU_SAVE_FILES"), "*.sav")); - std::string path_string = PathString(GetSaveDir()); - FileDlg dlg(path_string, "", false, false, save_file_types); - dlg.Run(); - if (!dlg.Result().empty()) - filename = *dlg.Result().begin(); + SaveFileDialog sfd(SP_SAVE_FILE_EXTENSION, true); + sfd.Run(); + if (!sfd.Result().empty()) + filename = sfd.Result(); } catch (const std::exception& e) { ClientUI::MessageBox(e.what(), true); } @@ -1042,11 +1039,7 @@ extension = MP_SAVE_FILE_EXTENSION; // Add timestamp to autosave generated files - boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%Y%m%d_%H%M%S"); - std::stringstream date_stream; - date_stream.imbue(std::locale(date_stream.getloc(), facet)); - date_stream << boost::posix_time::microsec_clock::local_time(); - std::string datetime_str=date_stream.str(); + std::string datetime_str = FilenameTimestamp(); boost::filesystem::path autosave_dir_path(GetSaveDir() / "auto"); Modified: trunk/FreeOrion/default/credits.xml =================================================================== --- trunk/FreeOrion/default/credits.xml 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/default/credits.xml 2014-04-12 09:42:16 UTC (rev 7046) @@ -53,6 +53,8 @@ <PERSON name="Landon Manning" nick="yandonman" task="Programming"/> <PERSON name="Marcel Metz" nick="adrian_broher" task="Programming, German Translation"/> <PERSON name="Vincent Fourmond" nick="yanub" task="Programming"/> + <PERSON name="" nick="Vegavis" task="Programming"/> + <PERSON name="" nick="Mitten.O" task="Programming"/> </GROUP> <GROUP name="GAMEDESIGN and CONTENT"> <PERSON name="Krum Stanoev" nick="" task="Game Design"/> Modified: trunk/FreeOrion/default/stringtables/en.txt =================================================================== --- trunk/FreeOrion/default/stringtables/en.txt 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/default/stringtables/en.txt 2014-04-12 09:42:16 UTC (rev 7046) @@ -1583,6 +1583,31 @@ Save Game Files ########################### +# Save Game Dialog # +########################### + +SAVE_TIME_TITLE +<u>Time</u> + +SAVE_TURN_TITLE +<u>Turn</u> + +SAVE_PLAYER_TITLE +<u>Player</u> + +SAVE_EMPIRE_TITLE +<u>Empire</u> + +SAVE_FILE_TITLE +<u>File</u> + +SAVE_FILENAME +File: + +SAVE_DIRECTORY +Path: + +########################### # Game Options # ########################### Modified: trunk/FreeOrion/msvc2010/FreeOrion/FreeOrion.vcxproj =================================================================== --- trunk/FreeOrion/msvc2010/FreeOrion/FreeOrion.vcxproj 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/msvc2010/FreeOrion/FreeOrion.vcxproj 2014-04-12 09:42:16 UTC (rev 7046) @@ -263,6 +263,7 @@ <ClCompile Include="..\..\UI\ProductionWnd.cpp" /> <ClCompile Include="..\..\UI\QueueListBox.cpp" /> <ClCompile Include="..\..\UI\ResearchWnd.cpp" /> + <ClCompile Include="..\..\UI\SaveFileDialog.cpp" /> <ClCompile Include="..\..\UI\ServerConnectWnd.cpp" /> <ClCompile Include="..\..\UI\ShaderProgram.cpp" /> <ClCompile Include="..\..\UI\SidePanel.cpp" /> Modified: trunk/FreeOrion/msvc2010/FreeOrion/FreeOrion.vcxproj.filters =================================================================== --- trunk/FreeOrion/msvc2010/FreeOrion/FreeOrion.vcxproj.filters 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/msvc2010/FreeOrion/FreeOrion.vcxproj.filters 2014-04-12 09:42:16 UTC (rev 7046) @@ -553,6 +553,9 @@ <ClCompile Include="..\..\UI\Hotkeys.cpp"> <Filter>Source Files\UI</Filter> </ClCompile> + <ClCompile Include="..\..\UI\SaveFileDialog.cpp"> + <Filter>Source Files\UI</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ResourceCompile Include="FreeOrion.rc"> Modified: trunk/FreeOrion/server/SaveLoad.cpp =================================================================== --- trunk/FreeOrion/server/SaveLoad.cpp 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/server/SaveLoad.cpp 2014-04-12 09:42:16 UTC (rev 7046) @@ -27,6 +27,7 @@ #include <boost/serialization/map.hpp> #include <boost/serialization/set.hpp> #include <boost/serialization/vector.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> #include <fstream> @@ -41,6 +42,32 @@ retval[it->first] = SaveGameEmpireData(it->first, it->second->Name(), it->second->PlayerName(), it->second->Color()); return retval; } + + void CompileSaveGamePreviewData(const ServerSaveGameData& server_save_game_data, + const std::vector<PlayerSaveGameData>& player_save_game_data, + const std::map<int, SaveGameEmpireData>& empire_save_game_data, + SaveGamePreviewData& preview){ + + typedef std::vector<PlayerSaveGameData>::const_iterator player_iterator; + + // Find the human player + const PlayerSaveGameData* the_player; + for(player_iterator it = player_save_game_data.begin(); it != player_save_game_data.end(); ++it){ + if(it->m_client_type == Networking::CLIENT_TYPE_HUMAN_PLAYER){ + the_player = &(*it); + } + } + + // Find the empire of the player + std::map<int, SaveGameEmpireData>::const_iterator the_empire = empire_save_game_data.find(the_player->m_empire_id); + + preview.current_turn = server_save_game_data.m_current_turn; + preview.main_player_name = the_player->m_name; + preview.main_player_empire_name = the_empire->second.m_empire_name; + preview.main_player_empire_colour = the_empire->second.m_color; + preview.save_time = boost::posix_time::to_iso_extended_string(boost::posix_time::second_clock::local_time()); + } + const std::string UNABLE_TO_OPEN_FILE("Unable to open file"); } @@ -53,6 +80,8 @@ GetUniverse().EncodingEmpire() = ALL_EMPIRES; std::map<int, SaveGameEmpireData> empire_save_game_data = CompileSaveGameEmpireData(empire_manager); + SaveGamePreviewData save_preview_data; + CompileSaveGamePreviewData(server_save_game_data, player_save_game_data, empire_save_game_data, save_preview_data); try { #ifdef FREEORION_WIN32 @@ -69,6 +98,7 @@ throw std::runtime_error(UNABLE_TO_OPEN_FILE); freeorion_oarchive oa(ofs); + oa << BOOST_SERIALIZATION_NVP(save_preview_data); oa << BOOST_SERIALIZATION_NVP(galaxy_setup_data); oa << BOOST_SERIALIZATION_NVP(server_save_game_data); oa << BOOST_SERIALIZATION_NVP(player_save_game_data); @@ -98,6 +128,7 @@ GetUniverse().EncodingEmpire() = ALL_EMPIRES; std::map<int, SaveGameEmpireData> ignored_save_game_empire_data; + SaveGamePreviewData ignored_save_preview_data; empire_manager.Clear(); universe.Clear(); @@ -116,6 +147,8 @@ if (!ifs) throw std::runtime_error(UNABLE_TO_OPEN_FILE); freeorion_iarchive ia(ifs); + Logger().debugStream() << "LoadGame : Passing Preview Data"; + ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); Logger().debugStream() << "LoadGame : Reading Galaxy Setup Data"; ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); @@ -143,6 +176,8 @@ } void LoadGalaxySetupData(const std::string& filename, GalaxySetupData& galaxy_setup_data) { + + SaveGamePreviewData ignored_save_preview_data; try { #ifdef FREEORION_WIN32 @@ -158,7 +193,7 @@ if (!ifs) throw std::runtime_error(UNABLE_TO_OPEN_FILE); freeorion_iarchive ia(ifs); - + ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); ia >> BOOST_SERIALIZATION_NVP(galaxy_setup_data); // skipping additional deserialization which is not needed for this function } catch (const std::exception& e) { @@ -169,6 +204,7 @@ void LoadPlayerSaveGameData(const std::string& filename, std::vector<PlayerSaveGameData>& player_save_game_data) { + SaveGamePreviewData ignored_save_preview_data; ServerSaveGameData ignored_server_save_game_data; GalaxySetupData ignored_galaxy_setup_data; @@ -186,7 +222,7 @@ if (!ifs) throw std::runtime_error(UNABLE_TO_OPEN_FILE); freeorion_iarchive ia(ifs); - + ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); ia >> BOOST_SERIALIZATION_NVP(ignored_galaxy_setup_data); ia >> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data); ia >> BOOST_SERIALIZATION_NVP(player_save_game_data); @@ -198,6 +234,7 @@ } void LoadEmpireSaveGameData(const std::string& filename, std::map<int, SaveGameEmpireData>& empire_save_game_data) { + SaveGamePreviewData ignored_save_preview_data; ServerSaveGameData ignored_server_save_game_data; std::vector<PlayerSaveGameData> ignored_player_save_game_data; GalaxySetupData ignored_galaxy_setup_data; @@ -216,6 +253,7 @@ if (!ifs) throw std::runtime_error(UNABLE_TO_OPEN_FILE); freeorion_iarchive ia(ifs); + ia >> BOOST_SERIALIZATION_NVP(ignored_save_preview_data); ia >> BOOST_SERIALIZATION_NVP(ignored_galaxy_setup_data); ia >> BOOST_SERIALIZATION_NVP(ignored_server_save_game_data); ia >> BOOST_SERIALIZATION_NVP(ignored_player_save_game_data); Modified: trunk/FreeOrion/util/Directories.cpp =================================================================== --- trunk/FreeOrion/util/Directories.cpp 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/util/Directories.cpp 2014-04-12 09:42:16 UTC (rev 7046) @@ -6,6 +6,7 @@ #include <boost/filesystem/convenience.hpp> #include <boost/filesystem/operations.hpp> +#include <boost/date_time/posix_time/posix_time.hpp> #include <cstdlib> @@ -367,3 +368,11 @@ #endif } +std::string FilenameTimestamp() { + boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%Y%m%d_%H%M%S"); + std::stringstream date_stream; + date_stream.imbue(std::locale(date_stream.getloc(), facet)); + date_stream << boost::posix_time::microsec_clock::local_time(); + return date_stream.str(); +} + Modified: trunk/FreeOrion/util/Directories.h =================================================================== --- trunk/FreeOrion/util/Directories.h 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/util/Directories.h 2014-04-12 09:42:16 UTC (rev 7046) @@ -50,6 +50,9 @@ /** Returns a canonical utf-8 string from the given filesystem path. */ FO_COMMON_API std::string PathString(const boost::filesystem::path& path); +/** Returns current timestamp in a form that can be used in file names */ +FO_COMMON_API std::string FilenameTimestamp(); + #ifdef FREEORION_MACOSX /** This function returns the Python home directory from where it is embedded * within the Mac OS X application bundle */ Modified: trunk/FreeOrion/util/MultiplayerCommon.cpp =================================================================== --- trunk/FreeOrion/util/MultiplayerCommon.cpp 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/util/MultiplayerCommon.cpp 2014-04-12 09:42:16 UTC (rev 7046) @@ -554,3 +554,15 @@ } return retval; } + + +SaveGamePreviewData::SaveGamePreviewData(): +magic_number(PREVIEW_PRESENT_MARKER), +main_player_name(UserString("UNKNOWN_VALUE_SYMBOL_2")), +main_player_empire_name(UserString("UNKNOWN_VALUE_SYMBOL_2")), +current_turn(-1) {} + +bool SaveGamePreviewData::Valid() const { + return magic_number == SaveGamePreviewData::PREVIEW_PRESENT_MARKER && current_turn >= -1 ; +} + Modified: trunk/FreeOrion/util/MultiplayerCommon.h =================================================================== --- trunk/FreeOrion/util/MultiplayerCommon.h 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/util/MultiplayerCommon.h 2014-04-12 09:42:16 UTC (rev 7046) @@ -301,6 +301,35 @@ void serialize(Archive& ar, const unsigned int version); }; +/** Contains preview information about a savegame. + */ +struct FO_COMMON_API SaveGamePreviewData { + /// Initialize with unknown markers. + SaveGamePreviewData(); + + /// Checks that this is a valid preview + bool Valid() const; + + /// A marker for the presence of the header + static const short PREVIEW_PRESENT_MARKER = 0xDA; + /// This should always contain PREVIEW_PRESENT_MARKER + short magic_number; + + /// The name of the hosting player, or the single human player in single player games + std::string main_player_name; + /// The name of the empire of the main player + std::string main_player_empire_name; + /// The colour of the empire of the main player + GG::Clr main_player_empire_colour; + /// The turn the game as saved one + int current_turn; + /// The time the game was saved as ISO 8601 YYYY-MM-DD"T"HH:MM:SS±HH:MM or Z + std::string save_time; + + template <class Archive> + void serialize ( Archive& ar, unsigned int version ); +}; + // Note: *::serialize() implemented in SerializeMultiplayerCommon.cpp. #endif // _MultiplayerCommon_h_ Modified: trunk/FreeOrion/util/SerializeMultiplayerCommon.cpp =================================================================== --- trunk/FreeOrion/util/SerializeMultiplayerCommon.cpp 2014-04-12 09:00:21 UTC (rev 7045) +++ trunk/FreeOrion/util/SerializeMultiplayerCommon.cpp 2014-04-12 09:42:16 UTC (rev 7046) @@ -149,3 +149,17 @@ template void CombatSetupGroup::serialize<freeorion_oarchive>(freeorion_oarchive&, const unsigned int); template void CombatSetupGroup::serialize<freeorion_iarchive>(freeorion_iarchive&, const unsigned int); + +template<class Archive> +void SaveGamePreviewData::serialize(Archive& ar, unsigned int version) +{ + ar & BOOST_SERIALIZATION_NVP(magic_number) + & BOOST_SERIALIZATION_NVP(main_player_name) + & BOOST_SERIALIZATION_NVP(main_player_empire_name) + & BOOST_SERIALIZATION_NVP(main_player_empire_colour) + & BOOST_SERIALIZATION_NVP(save_time) + & BOOST_SERIALIZATION_NVP(current_turn); +} + +template void SaveGamePreviewData::serialize<freeorion_oarchive>(freeorion_oarchive&, unsigned int); +template void SaveGamePreviewData::serialize<freeorion_iarchive>(freeorion_iarchive&, unsigned int); |