mirror of
https://github.com/jlengrand/Ivolution.git
synced 2026-03-10 08:21:18 +00:00
Removes code for gtk. Not supported any more
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
from gi.repository import Gtk
|
||||
|
||||
from .. import get_data
|
||||
|
||||
|
||||
class AboutDialog():
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Special static method that's automatically called by Python when
|
||||
constructing a new instance of this class.
|
||||
|
||||
Returns a fully instantiated AboutDialog object.
|
||||
"""
|
||||
self.builder = Gtk.Builder()
|
||||
self.builder.add_from_file(get_data("/ui/AboutIvolutionDialog.glade"))
|
||||
self.window = self.builder.get_object("about_ivolution_dialog")
|
||||
|
||||
self.window.run()
|
||||
self.window.destroy()
|
||||
150
ivolution/gui/IvolutionTemplate.py
Normal file
150
ivolution/gui/IvolutionTemplate.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
###########################################################################
|
||||
## Python code generated with wxFormBuilder (version Jun 30 2011)
|
||||
## http://www.wxformbuilder.org/
|
||||
##
|
||||
## PLEASE DO "NOT" EDIT THIS FILE!
|
||||
###########################################################################
|
||||
|
||||
import wx
|
||||
import wx.xrc
|
||||
import wx.aui
|
||||
|
||||
inputid = 1000
|
||||
settingsid = 1001
|
||||
startid = 1002
|
||||
stopid = 1003
|
||||
helpid = 1004
|
||||
|
||||
###########################################################################
|
||||
## Class IvolutionTemplate
|
||||
###########################################################################
|
||||
|
||||
class IvolutionTemplate ( wx.Frame ):
|
||||
|
||||
def __init__( self, parent ):
|
||||
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 416,471 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
|
||||
|
||||
self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
|
||||
|
||||
self.statusbar = self.CreateStatusBar( 2, wx.ST_SIZEGRIP, wx.ID_ANY )
|
||||
self.menubar = wx.MenuBar( 0 )
|
||||
self.filemenu = wx.Menu()
|
||||
self.exititem = wx.MenuItem( self.filemenu, wx.ID_ANY, u"Exit"+ u"\t" + u"CTRL + q", wx.EmptyString, wx.ITEM_NORMAL )
|
||||
self.filemenu.AppendItem( self.exititem )
|
||||
|
||||
self.menubar.Append( self.filemenu, u"File" )
|
||||
|
||||
self.aboutmenu = wx.Menu()
|
||||
self.helpitem = wx.MenuItem( self.aboutmenu, wx.ID_ANY, u"Help"+ u"\t" + u"CTRL + h", wx.EmptyString, wx.ITEM_NORMAL )
|
||||
self.aboutmenu.AppendItem( self.helpitem )
|
||||
|
||||
self.aboutmenu.AppendSeparator()
|
||||
|
||||
self.aboutitem = wx.MenuItem( self.aboutmenu, wx.ID_ANY, u"About"+ u"\t" + u"CTRL + F12", wx.EmptyString, wx.ITEM_NORMAL )
|
||||
self.aboutmenu.AppendItem( self.aboutitem )
|
||||
|
||||
self.menubar.Append( self.aboutmenu, u"About" )
|
||||
|
||||
self.SetMenuBar( self.menubar )
|
||||
|
||||
mainsizer = wx.FlexGridSizer( 4, 1, 0, 0 )
|
||||
mainsizer.AddGrowableCol( 0 )
|
||||
mainsizer.AddGrowableRow( 2 )
|
||||
mainsizer.SetFlexibleDirection( wx.BOTH )
|
||||
mainsizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_ALL )
|
||||
|
||||
self.toolbar = wx.aui.AuiToolBar( self, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,25 ), wx.aui.AUI_TB_HORZ_LAYOUT )
|
||||
self.toolbar.SetToolBitmapSize( wx.Size( 25,25 ) )
|
||||
self.toolbar.SetMinSize( wx.Size( -1,25 ) )
|
||||
self.toolbar.SetMaxSize( wx.Size( -1,25 ) )
|
||||
|
||||
self.toolbar.AddTool( inputid, u"Input", wx.Bitmap( u"../Ivolution/ivolution/data/media/icons/folder_add_48.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None )
|
||||
|
||||
|
||||
self.toolbar.AddTool( settingsid, u"Settings", wx.Bitmap( u"../Ivolution/ivolution/data/media/icons/spanner_48.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None )
|
||||
|
||||
|
||||
self.toolbar.AddSeparator()
|
||||
|
||||
self.toolbar.AddTool( startid, u"Go!", wx.Bitmap( u"../Ivolution/ivolution/data/media/icons/accepted_48.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None )
|
||||
|
||||
|
||||
self.toolbar.AddTool( stopid, u"Stop!", wx.Bitmap( u"../Ivolution/ivolution/data/media/icons/cancel_48.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None )
|
||||
|
||||
|
||||
self.toolbar.AddSeparator()
|
||||
|
||||
self.helptool = wx.HyperlinkCtrl( self.toolbar, helpid, u"Ivolution online", u"http://jlengrand.github.com/FaceMovie/", wx.DefaultPosition, wx.DefaultSize, wx.HL_ALIGN_RIGHT )
|
||||
self.toolbar.AddControl( self.helptool )
|
||||
self.toolbar.Realize()
|
||||
|
||||
mainsizer.Add( self.toolbar, 0, wx.EXPAND|wx.TOP, 0 )
|
||||
|
||||
inputfoldergrid = wx.FlexGridSizer( 1, 2, 0, 0 )
|
||||
inputfoldergrid.AddGrowableCol( 1 )
|
||||
inputfoldergrid.SetFlexibleDirection( wx.BOTH )
|
||||
inputfoldergrid.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.inputtextfixed = wx.StaticText( self, wx.ID_ANY, u"Chosen Folder : ", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.inputtextfixed.Wrap( -1 )
|
||||
self.inputtextfixed.SetFont( wx.Font( 8, 74, 90, 92, False, "Tahoma" ) )
|
||||
|
||||
inputfoldergrid.Add( self.inputtextfixed, 0, wx.ALL, 5 )
|
||||
|
||||
self.inputtextbox = wx.StaticText( self, wx.ID_ANY, u"~/Documents", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.inputtextbox.Wrap( -1 )
|
||||
inputfoldergrid.Add( self.inputtextbox, 0, wx.ALL, 5 )
|
||||
|
||||
mainsizer.Add( inputfoldergrid, 1, wx.EXPAND, 5 )
|
||||
|
||||
self.filelist = wx.ListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_LIST )
|
||||
mainsizer.Add( self.filelist, 0, wx.EXPAND, 5 )
|
||||
|
||||
self.progressgauge = wx.Gauge( self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.GA_HORIZONTAL )
|
||||
mainsizer.Add( self.progressgauge, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.SetSizer( mainsizer )
|
||||
self.Layout()
|
||||
|
||||
self.Centre( wx.BOTH )
|
||||
|
||||
# Connect Events
|
||||
self.Bind( wx.EVT_MENU, self.on_exit, id = self.exititem.GetId() )
|
||||
self.Bind( wx.EVT_MENU, self.on_help, id = self.helpitem.GetId() )
|
||||
self.Bind( wx.EVT_MENU, self.on_about, id = self.aboutitem.GetId() )
|
||||
self.Bind( wx.EVT_TOOL, self.on_input, id = inputid )
|
||||
self.Bind( wx.EVT_TOOL, self.on_settings, id = settingsid )
|
||||
self.Bind( wx.EVT_TOOL, self.on_start, id = startid )
|
||||
self.Bind( wx.EVT_TOOL, self.on_stop, id = stopid )
|
||||
self.helptool.Bind( wx.EVT_HYPERLINK, self.on_help )
|
||||
|
||||
def __del__( self ):
|
||||
pass
|
||||
|
||||
|
||||
# Virtual event handlers, overide them in your derived class
|
||||
def on_exit( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def on_help( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def on_about( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def on_input( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def on_settings( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def on_start( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def on_stop( self, event ):
|
||||
event.Skip()
|
||||
|
||||
|
||||
|
||||
329
ivolution/gui/IvolutionWindow.py
Executable file → Normal file
329
ivolution/gui/IvolutionWindow.py
Executable file → Normal file
@@ -1,108 +1,97 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
.. module:: IvolutionWindow
|
||||
:platform: Unix, Windows, Mac
|
||||
:synopsis: Main Window of the Ivolution GUI designed to be supported by all platforms.
|
||||
|
||||
.. moduleauthor:: Julien Lengrand-Lambert <jlengrand@gmail.com>
|
||||
|
||||
"""
|
||||
|
||||
import wx
|
||||
import wx.lib.newevent
|
||||
|
||||
import sys
|
||||
import os
|
||||
import webbrowser
|
||||
import logging
|
||||
import webbrowser
|
||||
|
||||
from gi.repository import Gtk, GLib
|
||||
from AboutDialog import AboutDialog
|
||||
|
||||
from .. import get_data
|
||||
|
||||
# import os
|
||||
# parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
# os.sys.path.insert(0,parentdir) # import parent folder
|
||||
from .. import get_data # used to load images and files
|
||||
|
||||
from .. import FaceParams
|
||||
from .. import FacemovieThread
|
||||
|
||||
from ..util.Notifier import Observer
|
||||
from ..util.Notifier import Observable
|
||||
|
||||
from IvolutionTemplate import IvolutionTemplate
|
||||
from SettingsWindow import SettingsWindow
|
||||
|
||||
class IvolutionWindow(Observer, Observable):
|
||||
def __init__(self, name):
|
||||
Observer.__init__(self, name)
|
||||
|
||||
class IvolutionWindow(IvolutionTemplate, Observer, Observable):
|
||||
"""
|
||||
Main Window of the Ivolution application
|
||||
"""
|
||||
def __init__(self, parent, title):
|
||||
"""
|
||||
Overrides init frame IvolutionTemplate
|
||||
"""
|
||||
IvolutionTemplate.__init__(self, parent)
|
||||
Observer.__init__(self, "Interface")
|
||||
Observable.__init__(self)
|
||||
|
||||
# Sets up logging capability
|
||||
self.my_logger = None
|
||||
self.console_logger = None
|
||||
self.setup_logger()
|
||||
|
||||
self.builder = Gtk.Builder()
|
||||
self.builder.add_from_file(get_data('ui/IvolutionWindow.glade'))
|
||||
#self.builder.add_from_file("ivolution/data/ui/IvolutionWindow.glade")
|
||||
#self.builder.connect_signals({ "on_ivolutionwindow_destroy" : Gtk.main_quit })
|
||||
self.window = self.builder.get_object("ivolution_window")
|
||||
self.window.show()
|
||||
self.builder.connect_signals(self)
|
||||
# Defines all our parameters neededfor the facemovie
|
||||
self.get_default_parameters()
|
||||
|
||||
self.process_running = False
|
||||
self.facemovie = None
|
||||
|
||||
self.inputtextbox.SetLabel(self.in_fo) # sets label to default input folder
|
||||
self.SetIcon(wx.Icon('ivolution/data/media/vitruve.ico', wx.BITMAP_TYPE_ICO)) # Sets icon
|
||||
|
||||
self.Show(True) # Finally show the frame
|
||||
|
||||
def get_default_parameters(self):
|
||||
"""
|
||||
"""
|
||||
# FIXME: Put really general stuff here !
|
||||
self.videospeedlistChoices = [u"slow", u"medium", u"fast"]
|
||||
self.gaugerange = 100
|
||||
|
||||
## Defines parameters needed to run the FaceMovie
|
||||
self.root_fo = ""
|
||||
self.in_fo = "" # Input folder, where images are located
|
||||
self.out_fo = "" # Input folder, where the video will be saved
|
||||
self.mode = "crop" # type of video to be created
|
||||
self.sort = "name" # how image files will be chronologically sorted
|
||||
self.speed = 1 # Speed of the movie
|
||||
self.param = "frontal_face" # type of face profile to be searched for
|
||||
|
||||
self.in_fo = "" # Input folder, where images are located
|
||||
|
||||
self.process_running = False
|
||||
|
||||
self.facemovie = None
|
||||
|
||||
self.AboutDialog = None # class
|
||||
|
||||
self.setup()
|
||||
self.setup_logger()
|
||||
|
||||
def setup(self):
|
||||
"""
|
||||
Sets up all the default paramters and retrieve the element of the GUI we want to follow
|
||||
"""
|
||||
self.AboutDialog = AboutDialog # FIXME : Still not working
|
||||
|
||||
self.startbutton = self.builder.get_object("startbutton")
|
||||
|
||||
self.filechooserinput = self.builder.get_object("filechooserinput")
|
||||
self.filechooseroutput = self.builder.get_object("filechooseroutput")
|
||||
|
||||
self.typecombobox = self.builder.get_object("typecombobox")
|
||||
self.typecombobox.set_active(0)
|
||||
|
||||
self.speedcombobox = self.builder.get_object("speedcombobox")
|
||||
self.speedcombobox.set_active(0)
|
||||
|
||||
self.cropradiobutton = self.builder.get_object("cropradiobutton")
|
||||
self.namesortradiobutton = self.builder.get_object("namesortradiobutton")
|
||||
|
||||
self.progressbar = self.builder.get_object("progressbar")
|
||||
self.statuslabel = self.builder.get_object("statuslabel")
|
||||
|
||||
# Signal handling related stuff
|
||||
def on_cropradiobutton_toggled(self, widget):
|
||||
"""
|
||||
We need to take care only of this one as both are grouped
|
||||
"""
|
||||
if widget.get_active(): # means crop is activated
|
||||
self.mode = "crop"
|
||||
if "win" in sys.platform:
|
||||
self.out_fo = "C:/Users/jll/Videos/" # Default folder for Windows
|
||||
self.in_fo = "C:\Users\jll\Pictures/"
|
||||
else:
|
||||
self.mode = "conservative"
|
||||
self.out_fo = "/home/jll/Videos/" # Default folder for Linux
|
||||
self.in_fo = "/home/jll/Pictures/"
|
||||
|
||||
def on_namesortradiobutton_toggled(self, widget):
|
||||
"""
|
||||
We need to take care only of this one as both are grouped
|
||||
"""
|
||||
if widget.get_active(): # means name is activated
|
||||
self.sort = "name"
|
||||
else:
|
||||
self.sort = "exif"
|
||||
# Overriding event handling methods
|
||||
def on_settings(self, event):
|
||||
settings = SettingsWindow(self)
|
||||
settings.Show(True) # Finally show the frame
|
||||
|
||||
def on_startbutton_pressed(self, widget):
|
||||
def on_start(self, event):
|
||||
"""
|
||||
User asks for starting the algorithm
|
||||
Sets all parameters and start processing
|
||||
"""
|
||||
self.my_logger.debug("start pressed")
|
||||
|
||||
if not self.process_running: # start only if not already running
|
||||
# Empty list on screen
|
||||
self.filelist.DeleteAllItems()
|
||||
|
||||
self.set_parameters()
|
||||
self.print_parameters()
|
||||
# Instantiating the facemovie
|
||||
@@ -117,59 +106,150 @@ class IvolutionWindow(Observer, Observable):
|
||||
self.console_logger.error("Cannot start, process already running !")
|
||||
self.my_logger.error("Cannot start, process already running !")
|
||||
|
||||
def on_stopbutton_pressed(self, widget):
|
||||
def on_stop(self, event):
|
||||
"""
|
||||
Asks the Facemovie thread to terminate
|
||||
User asks for stopping the algorithm
|
||||
Asks the FacemovieThread to terminate
|
||||
"""
|
||||
self.my_logger.debug("Stop pressed")
|
||||
self.console_logger.debug("Stop pressed")
|
||||
self.notify(["STOP"]) # Asking the Facemovie to stop
|
||||
self.notify(["Application", ["STOP"]]) # Asking the Facemovie to stop
|
||||
self.process_running = False
|
||||
|
||||
def on_destroy(self, widget, data=None):
|
||||
"""Called when the IvolutionWindow is closed."""
|
||||
# Clean up code for saving application state should be added here.
|
||||
self.notify(["STOP"]) # Asking the Facemovie to stop
|
||||
self.process_running = False
|
||||
#self.on_exit(event) # Finally shuts down the interface
|
||||
|
||||
Gtk.main_quit()
|
||||
print "Gtk Exited"
|
||||
|
||||
def on_menu_about_activate(self, widget, data=None):
|
||||
def on_input(self, event):
|
||||
"""
|
||||
Displays the about box for Ivolution
|
||||
# FIXME : Can start several about Dialogs at the same time
|
||||
Activated when a user clicks to choose its input location
|
||||
"""
|
||||
if self.AboutDialog is not None:
|
||||
about = self.AboutDialog()
|
||||
self.inputdialog = wx.DirDialog(self, "Please choose your input directory", style=1, defaultPath=self.in_fo)
|
||||
|
||||
def on_menu_help_activate(self, widget, data=None):
|
||||
if self.inputdialog.ShowModal() == wx.ID_OK:
|
||||
self.in_fo = self.inputdialog.GetPath()
|
||||
self.inputtextbox.SetLabel(self.in_fo)
|
||||
self.inputdialog.Destroy()
|
||||
|
||||
def on_output(self, event):
|
||||
"""
|
||||
Activated when a user clicks to choose its output location
|
||||
"""
|
||||
default_dir = "~/Videos"
|
||||
self.outputdialog = wx.DirDialog(self, "Please choose your output directory", style=1, defaultPath=default_dir)
|
||||
|
||||
if self.outputdialog.ShowModal() == wx.ID_OK:
|
||||
self.outputchoosertext.SetLabel(self.outputdialog.GetPath())
|
||||
self.outputdialog.Destroy()
|
||||
|
||||
def on_help(self, event):
|
||||
"""
|
||||
Opens a browser and points to online help.
|
||||
"""
|
||||
url = "http://jlengrand.github.com/FaceMovie/"
|
||||
webbrowser.open(url, new=2) # in new tab if possible
|
||||
#print "Should open help"
|
||||
|
||||
#Methods processing data
|
||||
def on_about(self, event):
|
||||
"""
|
||||
Displays the about box for Ivolution
|
||||
"""
|
||||
description =""" Ivolution is a project aiming at helping you
|
||||
create videos of yourself over time.
|
||||
Simply take pictures of yourself, Ivolution does
|
||||
everything else for you.
|
||||
|
||||
Ivolution may be used for faces, but also profiles
|
||||
(to show women along pregnancy for example)
|
||||
or full body (for people workouting).
|
||||
|
||||
The only limitation comes from you !
|
||||
"""
|
||||
|
||||
licence = """Copyright (c) <2012>, <Julien Lengrand-Lambert>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. 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.
|
||||
|
||||
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.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project."""
|
||||
|
||||
info = wx.AboutDialogInfo()
|
||||
|
||||
info.SetIcon(wx.Icon('ivolution/data/media/vitruve.png', wx.BITMAP_TYPE_PNG))
|
||||
info.SetName('Ivolution')
|
||||
info.SetVersion('0.8')
|
||||
info.SetDescription(description)
|
||||
info.SetCopyright('(C) 2012 Julien Lengrand-Lambert')
|
||||
info.SetWebSite('http://www.lengrand.fr')
|
||||
info.SetLicence(licence)
|
||||
info.AddDeveloper('Julien Lengrand-Lambert')
|
||||
info.AddDocWriter('Julien Lengrand-Lambert')
|
||||
info.AddArtist('Luc Viatour')
|
||||
info.AddTranslator('Julien Lengrand-Lambert')
|
||||
|
||||
wx.AboutBox(info)
|
||||
|
||||
def on_exit(self, event):
|
||||
"""
|
||||
Called when the IvolutionWindow is closed, or File/Exit is called.
|
||||
"""
|
||||
# Clean up code for saving application state should be added here.
|
||||
self.notify(["Application", ["STOP"]]) # Asking the Facemovie to stop
|
||||
self.process_running = False
|
||||
self.Close(True) # Close the frame.
|
||||
|
||||
# system methods
|
||||
def get_current_mode(self):
|
||||
"""
|
||||
"""
|
||||
if self.cropmode.GetValue():
|
||||
mode = "crop"
|
||||
else:
|
||||
mode = "conservative"
|
||||
|
||||
return mode
|
||||
|
||||
def get_current_sort(self):
|
||||
"""
|
||||
"""
|
||||
if self.namemode.GetValue():
|
||||
sort = "name"
|
||||
else:
|
||||
sort = "exif"
|
||||
|
||||
return sort
|
||||
|
||||
def set_parameters(self):
|
||||
"""
|
||||
Sets all needed parameters for create the movie.
|
||||
Retrieves all parameters needed for the algorithm to run
|
||||
"""
|
||||
self.in_fo = self.filechooserinput.get_current_folder() + "/" # TODO : Find correct fix
|
||||
self.out_fo = self.filechooseroutput.get_current_folder() + "/" # TODO : Find correct fix
|
||||
self.param = self.typecombobox.get_active_text()
|
||||
self.speed = self.speedcombobox.get_active() # We need and integer between 0 and 2
|
||||
|
||||
# Instantiating the face_params object that will be needed by the facemovie
|
||||
self.out_fo += "/" #FIXME: enhance that
|
||||
par_fo = os.path.join(self.root_fo, get_data("haarcascades"))
|
||||
self.face_params = FaceParams.FaceParams(par_fo,
|
||||
self.in_fo,
|
||||
self.out_fo,
|
||||
self.param,
|
||||
self.sort,
|
||||
self.mode,
|
||||
self.speed)
|
||||
self.in_fo,
|
||||
self.out_fo,
|
||||
self.param,
|
||||
self.sort,
|
||||
self.mode,
|
||||
self.speed)
|
||||
|
||||
def print_parameters(self):
|
||||
print "#########"
|
||||
@@ -234,10 +314,10 @@ class IvolutionWindow(Observer, Observable):
|
||||
|
||||
if message[0] == "PROGRESS": # progress bar
|
||||
# big steps performed
|
||||
|
||||
# Uses GLib to run Thread safe operations on GUI
|
||||
GLib.idle_add(self.progressbar.set_fraction, float(message[2]))
|
||||
GLib.idle_add(self.progressbar.set_text, message[1])
|
||||
wx.MutexGuiEnter() # to avoid thread problems
|
||||
self.progressgauge.SetValue(self.gaugerange * float(message[2]))
|
||||
self.statusbar.SetStatusText(message[1], 0)
|
||||
wx.MutexGuiLeave()
|
||||
|
||||
if float(message[2]) >= 1.0: # 100% of process
|
||||
self.my_logger.debug("Reached end of facemovie process")
|
||||
@@ -245,16 +325,35 @@ class IvolutionWindow(Observer, Observable):
|
||||
self.process_running = False
|
||||
|
||||
elif message[0] == "STATUS": # status label
|
||||
# intermediate results
|
||||
GLib.idle_add(self.statuslabel.set_text, message[1])
|
||||
#pass
|
||||
if message[1] == "Error":
|
||||
wx.MutexGuiEnter() # to avoid thread problems
|
||||
self.statusbar.SetStatusText("Error detected", 0)
|
||||
self.progressgauge.SetValue(0)
|
||||
wx.MutexGuiLeave()
|
||||
self.process_running = False
|
||||
|
||||
wx.MutexGuiEnter() # to avoid thread problems
|
||||
self.statusbar.SetStatusText(message[1], 1)
|
||||
wx.MutexGuiLeave()
|
||||
elif message[0] == "FILEADD":
|
||||
item = wx.ListItem()
|
||||
item.SetText(message[1])
|
||||
wx.MutexGuiEnter() # to avoid thread problems
|
||||
self.filelist.InsertItem(item)
|
||||
wx.MutexGuiLeave()
|
||||
elif message[0] == "FILEDONE":
|
||||
for i in range(self.filelist.GetItemCount()):
|
||||
if message[1] == self.filelist.GetItemText(i):
|
||||
if message[2] == 1:
|
||||
color = "green"
|
||||
else:
|
||||
color = "red"
|
||||
wx.MutexGuiEnter() # to avoid thread problems
|
||||
self.filelist.SetItemTextColour(i, color)
|
||||
wx.MutexGuiLeave()
|
||||
|
||||
elif len(message) > 1: # system commands shall be ignored
|
||||
self.console_logger.debug("Unrecognized command")
|
||||
self.my_logger.debug("Unrecognized command")
|
||||
self.console_logger.debug(message)
|
||||
self.my_logger.debug(message)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = IvolutionWindow()
|
||||
Gtk.main()
|
||||
|
||||
220
ivolution/gui/SettingsTemplate.py
Normal file
220
ivolution/gui/SettingsTemplate.py
Normal file
@@ -0,0 +1,220 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
###########################################################################
|
||||
## Python code generated with wxFormBuilder (version Jun 30 2011)
|
||||
## http://www.wxformbuilder.org/
|
||||
##
|
||||
## PLEASE DO "NOT" EDIT THIS FILE!
|
||||
###########################################################################
|
||||
|
||||
import wx
|
||||
import wx.xrc
|
||||
|
||||
###########################################################################
|
||||
## Class SettingsTemplate
|
||||
###########################################################################
|
||||
|
||||
class SettingsTemplate ( wx.Frame ):
|
||||
|
||||
def __init__( self, parent ):
|
||||
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"Settings", pos = wx.DefaultPosition, size = wx.Size( 342,451 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
|
||||
|
||||
self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
|
||||
|
||||
fgSizer4 = wx.FlexGridSizer( 2, 1, 0, 0 )
|
||||
fgSizer4.AddGrowableCol( 0 )
|
||||
fgSizer4.AddGrowableRow( 0 )
|
||||
fgSizer4.SetFlexibleDirection( wx.BOTH )
|
||||
fgSizer4.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.m_notebook4 = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.basicPage = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
|
||||
fgSizer5 = wx.FlexGridSizer( 2, 1, 0, 0 )
|
||||
fgSizer5.SetFlexibleDirection( wx.BOTH )
|
||||
fgSizer5.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
fgSizer9 = wx.FlexGridSizer( 2, 1, 0, 0 )
|
||||
fgSizer9.SetFlexibleDirection( wx.BOTH )
|
||||
fgSizer9.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.outputLocationTitle = wx.StaticText( self.basicPage, wx.ID_ANY, u"Choose location where the video will be saved: ", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.outputLocationTitle.Wrap( -1 )
|
||||
self.outputLocationTitle.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString ) )
|
||||
|
||||
fgSizer9.Add( self.outputLocationTitle, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
fgSizer7 = wx.FlexGridSizer( 0, 2, 0, 0 )
|
||||
fgSizer7.AddGrowableCol( 0 )
|
||||
fgSizer7.SetFlexibleDirection( wx.BOTH )
|
||||
fgSizer7.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.outputLocationLabel = wx.StaticText( self.basicPage, wx.ID_ANY, u"C:/Toussa", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.outputLocationLabel.Wrap( -1 )
|
||||
fgSizer7.Add( self.outputLocationLabel, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
|
||||
|
||||
self.outputButton = wx.Button( self.basicPage, wx.ID_ANY, u"...", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
fgSizer7.Add( self.outputButton, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
|
||||
|
||||
fgSizer9.Add( fgSizer7, 1, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.m_staticline1 = wx.StaticLine( self.basicPage, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
fgSizer9.Add( self.m_staticline1, 0, wx.EXPAND |wx.ALL, 5 )
|
||||
|
||||
fgSizer5.Add( fgSizer9, 1, wx.EXPAND, 5 )
|
||||
|
||||
fgSizer10 = wx.FlexGridSizer( 2, 1, 0, 0 )
|
||||
fgSizer10.SetFlexibleDirection( wx.BOTH )
|
||||
fgSizer10.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.OutputName = wx.StaticText( self.basicPage, wx.ID_ANY, u"Choose the name of the generated video:", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.OutputName.Wrap( -1 )
|
||||
self.OutputName.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString ) )
|
||||
|
||||
fgSizer10.Add( self.OutputName, 0, wx.ALL, 5 )
|
||||
|
||||
fgSizer8 = wx.FlexGridSizer( 1, 2, 0, 0 )
|
||||
fgSizer8.AddGrowableCol( 0 )
|
||||
fgSizer8.SetFlexibleDirection( wx.BOTH )
|
||||
fgSizer8.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.outputText = wx.TextCtrl( self.basicPage, wx.ID_ANY, u"Ivolution", wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY )
|
||||
fgSizer8.Add( self.outputText, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
|
||||
|
||||
self.extentLabel = wx.StaticText( self.basicPage, wx.ID_ANY, u".avi", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.extentLabel.Wrap( -1 )
|
||||
fgSizer8.Add( self.extentLabel, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 10 )
|
||||
|
||||
fgSizer10.Add( fgSizer8, 1, wx.EXPAND, 5 )
|
||||
|
||||
fgSizer5.Add( fgSizer10, 1, wx.EXPAND, 5 )
|
||||
|
||||
self.basicPage.SetSizer( fgSizer5 )
|
||||
self.basicPage.Layout()
|
||||
fgSizer5.Fit( self.basicPage )
|
||||
self.m_notebook4.AddPage( self.basicPage, u"Basic", False )
|
||||
self.advancedPage = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
|
||||
fgSizer11 = wx.FlexGridSizer( 4, 1, 0, 0 )
|
||||
fgSizer11.SetFlexibleDirection( wx.BOTH )
|
||||
fgSizer11.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
speedSizer = wx.FlexGridSizer( 2, 1, 0, 0 )
|
||||
speedSizer.SetFlexibleDirection( wx.BOTH )
|
||||
speedSizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.speedLabel = wx.StaticText( self.advancedPage, wx.ID_ANY, u"Choose the speed of the video :", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.speedLabel.Wrap( -1 )
|
||||
self.speedLabel.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString ) )
|
||||
|
||||
speedSizer.Add( self.speedLabel, 0, wx.ALL, 5 )
|
||||
|
||||
speedComboChoices = [ u"Slow", u"Medium", u"Fast" ]
|
||||
self.speedCombo = wx.ComboBox( self.advancedPage, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, speedComboChoices, wx.CB_READONLY )
|
||||
speedSizer.Add( self.speedCombo, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
fgSizer11.Add( speedSizer, 1, wx.EXPAND, 10 )
|
||||
|
||||
self.m_staticline2 = wx.StaticLine( self.advancedPage, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
fgSizer11.Add( self.m_staticline2, 0, wx.EXPAND |wx.ALL, 5 )
|
||||
|
||||
modeSizer = wx.FlexGridSizer( 2, 1, 0, 0 )
|
||||
modeSizer.SetFlexibleDirection( wx.BOTH )
|
||||
modeSizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.modeLabel = wx.StaticText( self.advancedPage, wx.ID_ANY, u"Choose the mode for processing pictures:", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.modeLabel.Wrap( -1 )
|
||||
self.modeLabel.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString ) )
|
||||
|
||||
modeSizer.Add( self.modeLabel, 0, wx.ALL, 5 )
|
||||
|
||||
gSizer7 = wx.GridSizer( 1, 2, 0, 0 )
|
||||
|
||||
modeRadioBoxChoices = [ u"Conservative", u"Crop" ]
|
||||
self.modeRadioBox = wx.RadioBox( self.advancedPage, wx.ID_ANY, u"Available modes", wx.DefaultPosition, wx.DefaultSize, modeRadioBoxChoices, 1, wx.RA_SPECIFY_ROWS )
|
||||
self.modeRadioBox.SetSelection( 0 )
|
||||
gSizer7.Add( self.modeRadioBox, 0, wx.ALL, 5 )
|
||||
|
||||
modeSizer.Add( gSizer7, 1, wx.EXPAND, 5 )
|
||||
|
||||
fgSizer11.Add( modeSizer, 1, wx.EXPAND, 5 )
|
||||
|
||||
self.m_staticline3 = wx.StaticLine( self.advancedPage, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
fgSizer11.Add( self.m_staticline3, 0, wx.EXPAND |wx.ALL, 5 )
|
||||
|
||||
typeSizer = wx.FlexGridSizer( 2, 1, 0, 0 )
|
||||
typeSizer.SetFlexibleDirection( wx.BOTH )
|
||||
typeSizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.typeLabel = wx.StaticText( self.advancedPage, wx.ID_ANY, u"Choose the type of faces for the video :", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.typeLabel.Wrap( -1 )
|
||||
self.typeLabel.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString ) )
|
||||
|
||||
typeSizer.Add( self.typeLabel, 0, wx.ALL, 5 )
|
||||
|
||||
typeComboChoices = [ u"frontal_face", u"profile_face" ]
|
||||
self.typeCombo = wx.ComboBox( self.advancedPage, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, typeComboChoices, wx.CB_READONLY )
|
||||
typeSizer.Add( self.typeCombo, 0, wx.ALL, 5 )
|
||||
|
||||
fgSizer11.Add( typeSizer, 1, wx.EXPAND, 5 )
|
||||
|
||||
self.m_staticline4 = wx.StaticLine( self.advancedPage, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
fgSizer11.Add( self.m_staticline4, 0, wx.EXPAND |wx.ALL, 5 )
|
||||
|
||||
sortSizer = wx.FlexGridSizer( 2, 1, 0, 0 )
|
||||
sortSizer.SetFlexibleDirection( wx.BOTH )
|
||||
sortSizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.sortLabel = wx.StaticText( self.advancedPage, wx.ID_ANY, u"Choose the method used to sort images:", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.sortLabel.Wrap( -1 )
|
||||
self.sortLabel.SetFont( wx.Font( wx.NORMAL_FONT.GetPointSize(), 70, 90, 92, False, wx.EmptyString ) )
|
||||
|
||||
sortSizer.Add( self.sortLabel, 0, wx.ALL, 5 )
|
||||
|
||||
sortRadioBoxChoices = [ u"FileName", u"EXIF Metadata" ]
|
||||
self.sortRadioBox = wx.RadioBox( self.advancedPage, wx.ID_ANY, u"Available methods", wx.DefaultPosition, wx.DefaultSize, sortRadioBoxChoices, 1, wx.RA_SPECIFY_ROWS )
|
||||
self.sortRadioBox.SetSelection( 0 )
|
||||
sortSizer.Add( self.sortRadioBox, 0, wx.ALL, 5 )
|
||||
|
||||
fgSizer11.Add( sortSizer, 1, wx.EXPAND, 5 )
|
||||
|
||||
self.advancedPage.SetSizer( fgSizer11 )
|
||||
self.advancedPage.Layout()
|
||||
fgSizer11.Fit( self.advancedPage )
|
||||
self.m_notebook4.AddPage( self.advancedPage, u"Advanced", True )
|
||||
|
||||
fgSizer4.Add( self.m_notebook4, 1, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
gSizer4 = wx.GridSizer( 0, 2, 0, 0 )
|
||||
|
||||
self.cancelButton = wx.Button( self, wx.ID_ANY, u"Cancel", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
gSizer4.Add( self.cancelButton, 0, wx.ALIGN_BOTTOM|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5 )
|
||||
|
||||
self.saveButton = wx.Button( self, wx.ID_ANY, u"Save", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
gSizer4.Add( self.saveButton, 0, wx.ALIGN_BOTTOM|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5 )
|
||||
|
||||
fgSizer4.Add( gSizer4, 1, wx.ALIGN_BOTTOM|wx.EXPAND, 10 )
|
||||
|
||||
self.SetSizer( fgSizer4 )
|
||||
self.Layout()
|
||||
|
||||
self.Centre( wx.BOTH )
|
||||
|
||||
# Connect Events
|
||||
self.outputButton.Bind( wx.EVT_BUTTON, self.on_output )
|
||||
self.cancelButton.Bind( wx.EVT_BUTTON, self.on_cancel )
|
||||
self.saveButton.Bind( wx.EVT_BUTTON, self.on_save )
|
||||
|
||||
def __del__( self ):
|
||||
pass
|
||||
|
||||
|
||||
# Virtual event handlers, overide them in your derived class
|
||||
def on_output( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def on_cancel( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def on_save( self, event ):
|
||||
event.Skip()
|
||||
|
||||
|
||||
99
ivolution/gui/SettingsWindow.py
Normal file
99
ivolution/gui/SettingsWindow.py
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
.. module:: SettingsWindow
|
||||
:platform: Unix, Windows, Mac
|
||||
:synopsis: Settings Window of the Ivolution GUI.
|
||||
|
||||
.. moduleauthor:: Julien Lengrand-Lambert <jlengrand@gmail.com>
|
||||
|
||||
"""
|
||||
|
||||
import wx
|
||||
import wx.lib.newevent
|
||||
|
||||
from .. import get_data # used to load images and files
|
||||
|
||||
from SettingsTemplate import SettingsTemplate
|
||||
|
||||
|
||||
class SettingsWindow(SettingsTemplate):
|
||||
"""
|
||||
Settings Window of the Ivolution application
|
||||
"""
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Overrides init frame SettingsTemplate
|
||||
"""
|
||||
SettingsTemplate.__init__(self, parent)
|
||||
# TODO : Set icon
|
||||
# Sets icon
|
||||
#self.SetIcon(wx.Icon('ivolution/data/media/icons/spanner_48.ico',
|
||||
# wx.BITMAP_TYPE_ICO))
|
||||
|
||||
self.parent = parent
|
||||
|
||||
# Defining settings value
|
||||
self.output_folder = parent.out_fo
|
||||
self.video_name = "Ivolution"
|
||||
self.type = ""
|
||||
self.mode = ""
|
||||
self.speed = ""
|
||||
self.sort = ""
|
||||
|
||||
# setting default value from main window
|
||||
self.outputLocationLabel.SetLabel(self.output_folder)
|
||||
self.typeCombo.SetSelection(0)
|
||||
self.speedCombo.SetSelection(1)
|
||||
self.m_notebook4.SetSelection(0) # I want to see basic first
|
||||
|
||||
# Virtual event handlers, overide them in your derived class
|
||||
def on_output(self, event):
|
||||
self.outputdialog = wx.DirDialog(self,
|
||||
"Please choose your output directory",
|
||||
style=1,
|
||||
defaultPath=self.output_folder)
|
||||
|
||||
if self.outputdialog.ShowModal() == wx.ID_OK:
|
||||
self.output_folder = self.outputdialog.GetPath()
|
||||
self.outputLocationLabel.SetLabel(self.output_folder)
|
||||
self.outputdialog.Destroy()
|
||||
|
||||
def on_cancel(self, event):
|
||||
self.Close(True) # Close the frame.
|
||||
|
||||
def on_save(self, event):
|
||||
# output_folder is already set
|
||||
self.video_name = self.outputText.GetValue()
|
||||
self.type = self.typeCombo.GetSelection()
|
||||
self.mode = self.modeRadioBox.GetSelection()
|
||||
self.speed = self.speedCombo.GetSelection()
|
||||
self.sort = self.sortRadioBox.GetSelection()
|
||||
|
||||
#self.print_parameters()
|
||||
self.setParentParams()
|
||||
self.Close(True) # Close the frame.
|
||||
|
||||
def setParentParams(self):
|
||||
modeChoices = [u"conservative", u"crop"]
|
||||
paramChoices = [u"frontal_face", u"profile_face"]
|
||||
sortChoices = [u"name", u"exif"]
|
||||
|
||||
#self.video_name
|
||||
self.parent.out_fo = self.output_folder
|
||||
self.parent.param = paramChoices[self.type]
|
||||
self.parent.mode = modeChoices[self.mode]
|
||||
self.parent.speed = self.speed
|
||||
self.parent.sort = sortChoices[self.sort]
|
||||
|
||||
def print_parameters(self):
|
||||
print "#########"
|
||||
print "Settings:"
|
||||
print "file name : %s" % (self.video_name)
|
||||
print "output folder : %s" % (self.output_folder)
|
||||
|
||||
print "Face Type : %s" % (self.type)
|
||||
print "Speed chosen : %s" % (self.speed)
|
||||
print "Mode chosen : %s" % (self.mode)
|
||||
print "Sort method : %s" % (self.sort)
|
||||
|
||||
print "#########"
|
||||
0
ivolution/gui/__init__.py
Executable file → Normal file
0
ivolution/gui/__init__.py
Executable file → Normal file
Reference in New Issue
Block a user