diff --git a/.gitignore b/.gitignore index 677cb209f..db7e1e15c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ #Patch files *.patch #Personal +/saveddata diff --git a/gui/builtinPreferenceViews/__init__.py b/gui/builtinPreferenceViews/__init__.py index 457724c9a..09ab03247 100644 --- a/gui/builtinPreferenceViews/__init__.py +++ b/gui/builtinPreferenceViews/__init__.py @@ -1 +1 @@ -__all__ = ["pyfaGlobalPreferences","pyfaHTMLExportPreferences","pyfaProxyPreferences"] +__all__ = ["pyfaGlobalPreferences","pyfaHTMLExportPreferences","pyfaUpdatePreferences","pyfaProxyPreferences"] \ No newline at end of file diff --git a/gui/builtinPreferenceViews/pyfaUpdatePreferences.py b/gui/builtinPreferenceViews/pyfaUpdatePreferences.py new file mode 100644 index 000000000..0655564fd --- /dev/null +++ b/gui/builtinPreferenceViews/pyfaUpdatePreferences.py @@ -0,0 +1,119 @@ +import wx +import service +import os + +from gui.preferenceView import PreferenceView +from gui import bitmapLoader + +import service +import gui.globalEvents as GE + + +class PFUpdatePref (PreferenceView): + title = "Pyfa Update Options" + desc = """ +Pyfa can automatically check and notify you of new releases. +These options will allow you to choose what kind of updates, if any, you wish +to receive notifications for. +""" + + def populatePanel( self, panel ): + self.UpdateSettings = service.settings.UpdateSettings.getInstance() + self.dirtySettings = False + + mainSizer = wx.BoxSizer( wx.VERTICAL ) + + self.stTitle = wx.StaticText( panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.stTitle.Wrap( -1 ) + self.stTitle.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) ) + mainSizer.Add( self.stTitle, 0, wx.ALL, 5 ) + + self.stDesc = wx.StaticText( panel, wx.ID_ANY, self.desc, wx.DefaultPosition, wx.DefaultSize, 0 ) + mainSizer.Add( self.stDesc, 0, wx.ALL, 5 ) + + self.suppressAll = wx.CheckBox( panel, wx.ID_ANY, u"Don't check for updates", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.suppressPrerelease = wx.CheckBox( panel, wx.ID_ANY, u"Allow pre-release notifications", wx.DefaultPosition, wx.DefaultSize, 0 ) + + mainSizer.Add( self.suppressAll, 0, wx.ALL|wx.EXPAND, 5 ) + mainSizer.Add( self.suppressPrerelease, 0, wx.ALL|wx.EXPAND, 5 ) + + self.suppressAll.Bind(wx.EVT_CHECKBOX, self.OnSuppressAllStateChange) + self.suppressPrerelease.Bind(wx.EVT_CHECKBOX, self.OnPrereleaseStateChange) + + self.suppressAll.SetValue(self.UpdateSettings.get('all')) + self.suppressPrerelease.SetValue(not self.UpdateSettings.get('prerelease')) + + if (self.UpdateSettings.get('version')): + self.versionSizer = wx.BoxSizer( wx.VERTICAL ) + + + self.versionTitle = wx.StaticText( panel, wx.ID_ANY, "Suppressing "+self.UpdateSettings.get('version')+" Notifications", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.versionTitle.Wrap( -1 ) + self.versionTitle.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) ) + + self.versionInfo = ''' +There is a release available which you have chosen to suppress. +You can choose to reset notification suppression for this release, +or download the new release from GitHub. +''' + + self.versionSizer.AddSpacer( ( 5, 5), 0, wx.EXPAND, 5 ) + + self.versionSizer.Add( wx.StaticLine( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ), 0, wx.EXPAND, 5 ) + self.versionSizer.AddSpacer( ( 5, 5), 0, wx.EXPAND, 5 ) + + self.versionSizer.Add( self.versionTitle, 0, wx.EXPAND, 5 ) + self.versionDesc = wx.StaticText( panel, wx.ID_ANY, self.versionInfo, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.versionSizer.Add( self.versionDesc, 0, wx.ALL, 5 ) + + actionSizer = wx.BoxSizer( wx.HORIZONTAL ) + resetSizer = wx.BoxSizer( wx.VERTICAL ) + + self.downloadButton = wx.Button( panel, wx.ID_ANY, "Download", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.downloadButton.Bind(wx.EVT_BUTTON, self.OnDownload) + resetSizer.Add( self.downloadButton, 0, wx.ALL, 5 ) + actionSizer.Add( resetSizer, 1, wx.EXPAND, 5 ) + + self.resetButton = wx.Button( panel, wx.ID_ANY, "Reset Suppression", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.resetButton.Bind(wx.EVT_BUTTON, self.ResetSuppression) + actionSizer.Add( self.resetButton, 0, wx.ALL, 5 ) + self.versionSizer.Add( actionSizer, 0, wx.EXPAND, 5 ) + mainSizer.Add( self.versionSizer, 0, wx.EXPAND, 5 ) + + self.ToggleSuppressAll(self.suppressAll.IsChecked()) + + panel.SetSizer( mainSizer ) + panel.Layout() + + def ToggleSuppressAll(self, bool): + ''' Toggles other inputs on/off depending on value of SuppressAll ''' + if bool: + self.suppressPrerelease.Disable() + else: + self.suppressPrerelease.Enable() + + def OnSuppressAllStateChange(self, event): + self.UpdateSettings.set('all', self.suppressAll.IsChecked()) + self.ToggleSuppressAll(self.suppressAll.IsChecked()) + + def OnPrereleaseStateChange(self, event): + self.UpdateSettings.set('prerelease', not self.suppressPrerelease.IsChecked()) + + def ResetSuppression(self, event): + self.UpdateSettings.set('version', None) + + # Todo: Find a way to hide the entire panel in one go + self.versionSizer.Hide(True) + self.versionTitle.Hide() + self.versionDesc.Hide() + self.downloadButton.Hide() + self.resetButton.Hide() + self.resetButton.Hide() + + def OnDownload(self, event): + wx.LaunchDefaultBrowser('https://github.com/DarkFenX/Pyfa/releases/tag/'+self.UpdateSettings.get('version')) + + def getImage(self): + return bitmapLoader.getBitmap("pyfa64", "icons") + +PFUpdatePref.register() \ No newline at end of file diff --git a/gui/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py index f079adbfa..21bd543a6 100644 --- a/gui/builtinViews/fittingView.py +++ b/gui/builtinViews/fittingView.py @@ -230,7 +230,7 @@ class FittingView(d.Display): self.removeModule(self.mods[row]) self.Select(row,0) row = self.GetNextSelected(row) - + event.Skip() def fitRemoved(self, event): @@ -288,9 +288,8 @@ class FittingView(d.Display): wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) else: populate = cFit.appendModule(fitID, itemID) - if populate: - self.slotsChanged() if populate is not None: + self.slotsChanged() wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) event.Skip() @@ -304,14 +303,14 @@ class FittingView(d.Display): else: if "wxMSW" in wx.PlatformInfo: self.click(event) - + def removeModule(self, module): cFit = service.Fit.getInstance() fit = cFit.getFit(self.activeFitID) populate = cFit.removeModule(self.activeFitID, fit.modules.index(module)) - + if populate is not None: - if populate: self.slotsChanged() + self.slotsChanged() wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.activeFitID)) def swapItems(self, x, y, itemID): @@ -373,16 +372,16 @@ class FittingView(d.Display): if self.activeFitID is not None and self.activeFitID == event.fitID: self.generateMods() self.refresh(self.mods) - + exportHtml.getInstance().refreshFittingHTMl() - + self.Show(self.activeFitID is not None and self.activeFitID == event.fitID) except wx._core.PyDeadObjectError: pass finally: event.Skip() - + def scheduleMenu(self, event): event.Skip() @@ -451,7 +450,7 @@ class FittingView(d.Display): 5: ''} def slotColour(self, slot): return self.slotColourMap[slot] or self.GetBackgroundColour() - + def refresh(self, stuff): d.Display.refresh(self, stuff) sFit = service.Fit.getInstance() @@ -460,7 +459,7 @@ class FittingView(d.Display): for slotType in Slot.getTypes(): slot = Slot.getValue(slotType) slotMap[slot] = fit.getSlotsFree(slot) < 0 - + for i, mod in enumerate(self.mods): if slotMap[mod.slot]: self.SetItemBackgroundColour(i, wx.Colour(204, 51, 51)) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 234537c91..1d1d8d4e0 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -49,6 +49,7 @@ from gui.graphFrame import GraphFrame from gui.copySelectDialog import CopySelectDialog from gui.utils.clipboard import toClipboard, fromClipboard from gui.fleetBrowser import FleetBrowser +from gui.updateDialog import UpdateDialog from gui.builtinViews import * #dummy panel(no paint no erasebk) @@ -161,6 +162,15 @@ class MainFrame(wx.Frame): #Show ourselves self.Show() + #Check for updates + self.sUpdate = service.Update.getInstance() + self.sUpdate.CheckUpdate(self.ShowUpdateBox) + + def ShowUpdateBox(self, release): + dlg = UpdateDialog(self, release) + dlg.ShowModal() + dlg.Destroy() + def LoadMainFrameAttribs(self): mainFrameDefaultAttribs = {"wnd_width":1000, "wnd_height": 700, "wnd_maximized": False} diff --git a/gui/updateDialog.py b/gui/updateDialog.py new file mode 100644 index 000000000..ec389186b --- /dev/null +++ b/gui/updateDialog.py @@ -0,0 +1,120 @@ +#=============================================================================== +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa 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 3 of the License, or +# (at your option) any later version. +# +# pyfa 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 pyfa. If not, see . +#=============================================================================== + +import wx +import bitmapLoader +import config +import service +import dateutil.parser + +class UpdateDialog(wx.Dialog): + + def __init__(self, parent, release): + wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = "Pyfa Update", pos = wx.DefaultPosition, size = wx.Size( 400,300 ), style = wx.DEFAULT_DIALOG_STYLE ) + + self.UpdateSettings = service.settings.UpdateSettings.getInstance() + self.releaseInfo = release + self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize ) + + mainSizer = wx.BoxSizer( wx.VERTICAL ) + + headSizer = wx.BoxSizer( wx.HORIZONTAL ) + + self.headingText = wx.StaticText( self, wx.ID_ANY, "Pyfa Update Available!", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + self.headingText.Wrap( -1 ) + self.headingText.SetFont( wx.Font( 14, 74, 90, 92, False) ) + + headSizer.Add( self.headingText, 1, wx.ALL, 5 ) + mainSizer.Add( headSizer, 0, wx.EXPAND, 5 ) + + mainSizer.Add( wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ), 0, wx.EXPAND |wx.ALL, 5 ) + + versionSizer = wx.BoxSizer( wx.HORIZONTAL ) + + if(self.releaseInfo['prerelease']): + self.releaseText = wx.StaticText( self, wx.ID_ANY, "Pre-release", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT ) + self.releaseText.SetFont( wx.Font( 12, 74, 90, 92, False) ) + self.releaseText.SetForegroundColour( wx.Colour( 230, 0, 0 ) ) + else: + self.releaseText = wx.StaticText( self, wx.ID_ANY, "Stable", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT ) + self.releaseText.SetFont( wx.Font( 12, 74, 90, 90, False) ) + + self.releaseText.Wrap( -1 ) + + versionSizer.Add( self.releaseText, 1, wx.ALL, 5 ) + + self.versionText = wx.StaticText( self, wx.ID_ANY, self.releaseInfo['tag_name'], wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_LEFT ) + self.versionText.Wrap( -1 ) + self.versionText.SetFont( wx.Font( 12, 74, 90, 90, False) ) + + versionSizer.Add( self.versionText, 1, wx.ALL, 5 ) + versionSizer.AddSpacer( ( 15, 5), 0, wx.EXPAND, 5 ) + + mainSizer.Add( versionSizer, 0, wx.EXPAND, 5 ) + mainSizer.AddSpacer( ( 0, 5), 0, wx.EXPAND, 5 ) + + releaseDate = dateutil.parser.parse(self.releaseInfo['created_at']) + notesSizer = wx.BoxSizer( wx.HORIZONTAL ) + self.notesTextCtrl = wx.TextCtrl( self, wx.ID_ANY, str(releaseDate.date())+":\n\n"+self.releaseInfo['body'], wx.DefaultPosition, wx.DefaultSize, wx.TE_AUTO_URL|wx.TE_MULTILINE|wx.TE_READONLY|wx.DOUBLE_BORDER|wx.TRANSPARENT_WINDOW ) + + notesSizer.Add( self.notesTextCtrl, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 5 ) + mainSizer.Add( notesSizer, 1, wx.EXPAND, 5 ) + + self.supressCheckbox = wx.CheckBox( self, wx.ID_ANY, "Don't remind me again for this release", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.supressCheckbox.Bind(wx.EVT_CHECKBOX, self.SuppressChange) + + mainSizer.Add( self.supressCheckbox, 0, wx.ALL, 5 ) + mainSizer.Add( wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ), 0, wx.EXPAND |wx.ALL, 5 ) + + actionSizer = wx.BoxSizer( wx.HORIZONTAL ) + + goSizer = wx.BoxSizer( wx.VERTICAL ) + self.downloadButton = wx.Button( self, wx.ID_ANY, "Download", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.downloadButton.Bind(wx.EVT_BUTTON, self.OnDownload) + goSizer.Add( self.downloadButton, 0, wx.ALL, 5 ) + actionSizer.Add( goSizer, 1, wx.EXPAND, 5 ) + + self.closeButton = wx.Button(self, wx.ID_CLOSE) + self.closeButton.Bind(wx.EVT_BUTTON, self.OnClose) + actionSizer.Add( self.closeButton, 0, wx.ALL, 5 ) + mainSizer.Add( actionSizer, 0, wx.EXPAND, 5 ) + + self.SetSizer( mainSizer ) + self.Layout() + + # Handle use-case of suppressing a release, then a new version becoming available. + # If that new version is not suppressed, the old version will remain in the preferences and + # may cause confusion. If this dialog box is popping up for any reason, that mean we can + # safely reset this setting + self.UpdateSettings.set('version', None) + + self.Centre( wx.BOTH ) + + def OnClose(self, e): + self.Destroy() + + def SuppressChange(self, e): + if (self.supressCheckbox.IsChecked()): + self.UpdateSettings.set('version', self.releaseInfo['tag_name']) + else: + self.UpdateSettings.set('version', None) + + def OnDownload(self, e): + wx.LaunchDefaultBrowser('https://github.com/DarkFenX/Pyfa/releases/tag/'+self.releaseInfo['tag_name']) + self.OnClose(e) \ No newline at end of file diff --git a/service/__init__.py b/service/__init__.py index 7ade0eb7e..e7581c675 100644 --- a/service/__init__.py +++ b/service/__init__.py @@ -5,3 +5,4 @@ from service.character import Character from service.damagePattern import DamagePattern from service.settings import SettingsProvider from service.fleet import Fleet +from service.update import Update diff --git a/service/market.py b/service/market.py index 96e6b6097..6a8313051 100644 --- a/service/market.py +++ b/service/market.py @@ -630,10 +630,13 @@ class Market(): def searchShips(self, name): """Find ships according to given text pattern""" - results = eos.db.searchItems(name) + filter = eos.types.Category.name.in_(["Ship"]) + results = eos.db.searchItems(name, where=filter, + join=(eos.types.Item.group, eos.types.Group.category), + eager=("icon", "group.category", "metaGroup", "metaGroup.parent")) ships = set() for item in results: - if self.getCategoryByItem(item).name == "Ship" and self.getPublicityByItem(item): + if self.getPublicityByItem(item): ships.add(item) return ships diff --git a/service/settings.py b/service/settings.py index eb5e66a77..f77a946b0 100644 --- a/service/settings.py +++ b/service/settings.py @@ -38,6 +38,7 @@ class SettingsProvider(): os.mkdir(self.BASE_PATH); def getSettings(self, area, defaults=None): + s = self.settings.get(area) if s is None: p = os.path.join(self.BASE_PATH, area) @@ -215,4 +216,31 @@ class HTMLExportSettings(): return self.serviceHTMLExportSettings["path"] def setPath(self, path): - self.serviceHTMLExportSettings["path"] = path \ No newline at end of file + self.serviceHTMLExportSettings["path"] = path + +""" +Settings used by update notification +""" +class UpdateSettings(): + _instance = None + + @classmethod + def getInstance(cls): + if cls._instance == None: + cls._instance = UpdateSettings() + + return cls._instance + + def __init__(self): + # Settings + # all - If True, suppress all update notifications + # prerelease - If True, suppress only prerelease notifications + # version - Set to release tag that user does not want notifications for + serviceUpdateDefaultSettings = { "all": False, "prerelease": True, 'version': None } + self.serviceUpdateSettings = SettingsProvider.getInstance().getSettings("pyfaServiceUpdateSettings", serviceUpdateDefaultSettings) + + def get(self, type): + return self.serviceUpdateSettings[type] + + def set(self, type, value): + self.serviceUpdateSettings[type] = value \ No newline at end of file diff --git a/service/update.py b/service/update.py new file mode 100644 index 000000000..5957ec765 --- /dev/null +++ b/service/update.py @@ -0,0 +1,86 @@ +#=============================================================================== +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa 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 3 of the License, or +# (at your option) any later version. +# +# pyfa 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 pyfa. If not, see . +#=============================================================================== + +import threading +import wx +import urllib2 +import json +import config +import service + +class CheckUpdateThread(threading.Thread): + def __init__(self, callback): + threading.Thread.__init__(self) + self.callback = callback + self.settings = service.settings.UpdateSettings.getInstance() + + def run(self): + # Suppress all + if (self.settings.get('all')): + return + + try: + # @todo: use proxy settings? + response = urllib2.urlopen('https://api.github.com/repos/DarkFenX/Pyfa/releases') + jsonResponse = json.loads(response.read()); + i = 0 + while (True): + release = jsonResponse[i] + + # Suppress pre releases + if (release['prerelease'] and self.settings.get('prerelease')): + i += 1 + continue + + # Handle use-case of updating to suppressed version + if self.settings.get('version') == 'v'+config.version: + self.settings.set('version', None) + + # Suppress version + if (release['tag_name'] == self.settings.get('version')): + return + + version = release['tag_name'].replace('v', '', 1) + + if self.versiontuple(version) > self.versiontuple(config.version): + wx.CallAfter(self.callback, jsonResponse[i]) + break; + + except: # for when there is no internet connection + pass + + def versiontuple(self, v): + return tuple(map(int, (v.split(".")))) + +class Update(): + instance = None + def __init__(self): + pass + + def CheckUpdate(self, callback): + thread = CheckUpdateThread(callback) + thread.start() + + @classmethod + def getInstance(cls): + if cls.instance == None: + cls.instance = Update() + return cls.instance + +