Example #1
0
def show_gui():
    pycam.Utils.set_application_key("pycam-gtk")
    deps_gtk = GuiCommon.requirements_details_gtk()
    report_gtk = GuiCommon.get_dependency_report(deps_gtk, prefix="\t")
    if GuiCommon.check_dependencies(deps_gtk):
        from pycam.Gui.Project import ProjectGui
        gui_class = ProjectGui
    else:
        full_report = []
        full_report.append("PyCAM dependency problem")
        full_report.append("Error: Failed to load the GTK interface.")
        full_report.append("Details:")
        full_report.append(report_gtk)
        full_report.append("")
        full_report.append("Detailed list of requirements: %s" %
                           GuiCommon.REQUIREMENTS_LINK)
        log.critical(os.linesep.join(full_report))
        return EXIT_CODES["requirements"]

    event_manager = get_event_handler()
    event_manager.set("history", DataHistory())

    with merge_history_and_block_events(event_manager):
        gui = gui_class(event_manager)
        # initialize plugins
        plugin_manager = pycam.Plugins.PluginManager(core=event_manager)
        plugin_manager.import_plugins()
        # some more initialization
        gui.reset_preferences()
        gui.load_preferences()
        gui.load_startup_workspace()

    event_manager.emit_event("notify-initialization-finished")

    # open the GUI
    get_mainloop(use_gtk=True).run()

    # optionally save workspace (based on configuration or dialog response)
    if event_manager.get("save_workspace_on_exit") == QuestionStatus.ASK.value:
        response = gui.get_question_response("Save Workspace?",
                                             True,
                                             allow_memorize=True)
        if response.should_memorize:
            event_manager.set("save_workspace_on_exit",
                              (QuestionStatus.YES if response.is_yes else
                               QuestionStatus.NO).value)
        should_store = response.is_yes
    elif event_manager.get(
            "save_workspace_on_exit") == QuestionStatus.YES.value:
        should_store = True
    else:
        should_store = False
    if should_store:
        gui.save_startup_workspace()

    gui.save_preferences()

    # no error -> return no error code
    return None
Example #2
0
 def initialize_multiprocessing(self, widget=None):
     complete_area = self.gui.get_object("MultiprocessingFrame")
     # prevent any further actions while the connection is established
     complete_area.set_sensitive(False)
     # wait for the above "set_sensitive" to finish
     get_mainloop().update()
     enable_parallel = self.enable_parallel_processes.get_active()
     enable_server_obj = self.gui.get_object("EnableServerMode")
     enable_server = enable_server_obj.get_active()
     remote_host = self.gui.get_object("RemoteServerHostname").get_text()
     if remote_host:
         remote_port = int(self.server_port_remote_obj.get_value())
         remote = "%s:%s" % (remote_host, remote_port)
     else:
         remote = None
     local_port = int(self.server_port_local_obj.get_value())
     auth_key = self.auth_key_obj.get_text()
     auth_key = None if auth_key is None else auth_key.encode("utf-8")
     if not auth_key and enable_parallel and enable_server:
         self.log.error(
             "You need to provide a password for this connection.")
         enable_server_obj.set_active(False)
     elif enable_parallel:
         if enable_server and \
                 (pycam.Utils.get_platform() == pycam.Utils.OSPlatform.WINDOWS):
             if self.number_of_processes.get_value() > 0:
                 self.log.warn(
                     "Mixed local and remote processes are currently not available "
                     "on the Windows platform. Setting the number of local processes "
                     "to zero.")
                 self.number_of_processes.set_value(0)
             self.number_of_processes.set_sensitive(False)
         else:
             self.number_of_processes.set_sensitive(True)
         num_of_processes = int(self.number_of_processes.get_value())
         error = pycam.Utils.threading.init_threading(
             number_of_processes=num_of_processes,
             enable_server=enable_server,
             remote=remote,
             server_credentials=auth_key,
             local_port=local_port)
         if error:
             self.log.error("Failed to start server: %s", error)
             pycam.Utils.threading.cleanup()
             enable_server_obj.set_active(False)
     else:
         pycam.Utils.threading.cleanup()
         self.log.info("Multiprocessing disabled")
     # set the label of the "connect" button
     if enable_server_obj.get_active():
         info = self._gtk.stock_lookup(self._gtk.STOCK_DISCONNECT)
     else:
         info = self._gtk.stock_lookup(self._gtk.STOCK_CONNECT)
     enable_server_obj.set_label(info.label)
     complete_area.set_sensitive(True)
     self.update_parallel_processes_settings()
Example #3
0
 def update(self, *args, **kwargs):
     mainloop = get_mainloop()
     if mainloop is None:
         return False
     mainloop.update()
     if self._progress:
         return self._progress.update(*args, **kwargs)
     else:
         return False
Example #4
0
    def __init__(self, event_manager):
        super().__init__(event_manager)
        self._event_handlers = []
        self.gui_is_active = False
        self.gui = Gtk.Builder()
        self._mainloop_is_running = False
        self.mainloop = get_mainloop(use_gtk=True)
        gtk_build_file = get_ui_file_location(GTKBUILD_FILE)
        if gtk_build_file is None:
            raise InitializationError(
                "Failed to load GTK layout specification file: {}".format(
                    gtk_build_file))
        self.gui.add_from_file(gtk_build_file)
        if pycam.Utils.get_platform() == pycam.Utils.OSPlatform.WINDOWS:
            gtkrc_file = get_ui_file_location(GTKRC_FILE_WINDOWS)
            if gtkrc_file:
                Gtk.rc_add_default_file(gtkrc_file)
                Gtk.rc_reparse_all_for_settings(Gtk.settings_get_default(),
                                                True)
        action_group = Gio.SimpleActionGroup()
        self.settings.set("gtk_action_group_prefix", "pycam")
        self.settings.set("gtk_action_group", action_group)
        self.window = self.gui.get_object("ProjectWindow")
        self.window.insert_action_group(
            self.settings.get("gtk_action_group_prefix"),
            self.settings.get("gtk_action_group"))
        self.settings.set("main_window", self.window)
        # show stock items on buttons
        # increase the initial width of the window (due to hidden elements)
        self.window.set_default_size(400, -1)
        # initialize the RecentManager (TODO: check for Windows)
        if False and pycam.Utils.get_platform(
        ) == pycam.Utils.OSPlatform.WINDOWS:
            # The pyinstaller binary for Windows fails mysteriously when trying
            # to display the stock item.
            # Error message: Gtk:ERROR:gtkrecentmanager.c:1942:get_icon_fallback:
            #    assertion failed: (retval != NULL)
            self.recent_manager = None
        else:
            try:
                self.recent_manager = Gtk.recent_manager_get_default()
            except AttributeError:
                # GTK 2.12.1 seems to have problems with "RecentManager" on
                # Windows. Sadly this is the version, that is shipped with the
                # "appunti" GTK packages for Windows (April 2010).
                # see http://www.daa.com.au/pipermail/pygtk/2009-May/017052.html
                self.recent_manager = None
        # file loading
        self.last_dirname = None
        self.last_model_uri = None
        # define callbacks and accelerator keys for the menu actions
        for objname, callback, data, accel_key in (
            ("OpenModel", self.load_model_file, None,
             "<Control>o"), ("Quit", self.destroy, None, "<Control>q"),
            ("GeneralSettings", self.toggle_preferences_window, None,
             "<Control>p"), ("UndoButton", self.restore_undo_state, None,
                             "<Control>z"),
            ("HelpIntroduction", self.show_help, "introduction", "F1"),
            ("HelpSupportedFormats", self.show_help, "supported-formats",
             None), ("HelpModelTransformations", self.show_help,
                     "model-transformations", None),
            ("HelpProcessSettings", self.show_help, "process-settings",
             None), ("HelpBoundsSettings", self.show_help, "bounding-box",
                     None), ("HelpTouchOff", self.show_help, "touch-off",
                             None), ("Help3DView", self.show_help, "3d-view",
                                     None), ("HelpServerMode", self.show_help,
                                             "server-mode", None),
            ("HelpCommandLine", self.show_help, "cli-examples",
             None), ("HelpHotkeys", self.show_help, "keyboard-shortcuts",
                     None), ("ProjectWebsite", self.show_help,
                             "http://pycam.sourceforge.net",
                             None), ("DevelopmentBlog", self.show_help,
                                     "http://fab.senselab.org/pycam", None),
            ("Forum", self.show_help,
             "http://sourceforge.net/projects/pycam/forums",
             None), ("BugTracker", self.show_help,
                     "https://github.com/SebKuzminsky/pycam/issues/",
                     None), ("FeatureRequest", self.show_help,
                             "https://github.com/SebKuzminsky/pycam/issues/",
                             None)):
            item = self.gui.get_object(objname)
            action = "activate"
            if data is None:
                item.connect(action, callback)
            else:
                item.connect(action, callback, data)
            if accel_key:
                key, mod = Gtk.accelerator_parse(accel_key)
                accel_path = "<pycam>/%s" % objname
                item.set_accel_path(accel_path)
                # Gtk.accel_map_change_entry(accel_path, key, mod, True) FIXME
        # LinkButton does not work on Windows: https://bugzilla.gnome.org/show_bug.cgi?id=617874
        if pycam.Utils.get_platform() == pycam.Utils.OSPlatform.WINDOWS:

            def open_url(widget, data=None):
                webbrowser.open(widget.get_uri())

            Gtk.link_button_set_uri_hook(open_url)
        # configure drag-n-drop for config files and models
        self.settings.set("configure-drag-drop-func",
                          self.configure_drag_and_drop)
        self.settings.get("configure-drag-drop-func")(self.window)
        # other events
        self.window.connect("destroy", self.destroy)
        self.window.connect("delete-event", self.destroy)
        # the settings window
        self.gui.get_object("CloseSettingsWindow").connect(
            "clicked", self.toggle_preferences_window, False)
        self.gui.get_object("ResetPreferencesButton").connect(
            "clicked", self.reset_preferences)
        self.preferences_window = self.gui.get_object("GeneralSettingsWindow")
        self.preferences_window.connect("delete-event",
                                        self.toggle_preferences_window, False)
        self.preferences_window.insert_action_group(
            self.settings.get("gtk_action_group_prefix"),
            self.settings.get("gtk_action_group"))
        self._preferences_window_position = None
        self._preferences_window_visible = False
        # "about" window
        self.about_window = self.gui.get_object("AboutWindow")
        self.about_window.set_version(VERSION)
        self.about_window.insert_action_group(
            self.settings.get("gtk_action_group_prefix"),
            self.settings.get("gtk_action_group"))
        self.gui.get_object("About").connect("activate",
                                             self.toggle_about_window, True)
        # we assume, that the last child of the window is the "close" button
        # TODO: fix this ugly hack!
        about_window_children = self.gui.get_object(
            "AboutWindowButtons").get_children()
        if about_window_children:
            # it seems to be possible that there are no children - weird :(
            # see https://github.com/SebKuzminsky/pycam/issues/59
            about_window_children[-1].connect("clicked",
                                              self.toggle_about_window, False)
        self.about_window.connect("delete-event", self.toggle_about_window,
                                  False)
        # menu bar
        uimanager = Gtk.UIManager()
        self.settings.set("gtk-uimanager", uimanager)
        self._accel_group = uimanager.get_accel_group()
        # send a "delete" event on "CTRL-w" for every window
        self._accel_group.connect(
            ord('w'), Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.LOCKED,
            lambda accel_group, window, *args: window.emit(
                "delete-event", Gdk.Event()))
        self._accel_group.connect(ord('q'), Gdk.ModifierType.CONTROL_MASK,
                                  Gtk.AccelFlags.LOCKED,
                                  lambda *args: self.destroy())
        self.settings.add_item("gtk-accel-group", lambda: self._accel_group)
        for obj in self.gui.get_objects():
            if isinstance(obj, Gtk.Window):
                obj.add_accel_group(self._accel_group)
        # preferences tab
        preferences_book = self.gui.get_object("PreferencesNotebook")

        def clear_preferences():
            for child in preferences_book.get_children():
                preferences_book.remove(child)

        def add_preferences_item(item, name):
            preferences_book.append_page(item, Gtk.Label(name))

        self.settings.register_ui_section("preferences", add_preferences_item,
                                          clear_preferences)
        for obj_name, label, priority in (("GeneralSettingsPrefTab", "General",
                                           -50), ("ProgramsPrefTab",
                                                  "Programs", 50)):
            obj = self.gui.get_object(obj_name)
            obj.unparent()
            self.settings.register_ui("preferences", label, obj, priority)
        # general preferences
        general_prefs = self.gui.get_object("GeneralPreferencesBox")

        def clear_general_prefs():
            for item in general_prefs.get_children():
                general_prefs.remove(item)

        def add_general_prefs_item(item, name):
            general_prefs.pack_start(item, expand=False, fill=False, padding=3)

        self.settings.register_ui_section("preferences_general",
                                          add_general_prefs_item,
                                          clear_general_prefs)
        # set defaults
        main_tab = self.gui.get_object("MainTabs")

        def clear_main_tab():
            while main_tab.get_n_pages() > 0:
                main_tab.remove_page(0)

        def add_main_tab_item(item, name):
            main_tab.append_page(item, Gtk.Label(name))

        # TODO: move these to plugins, as well
        self.settings.register_ui_section("main", add_main_tab_item,
                                          clear_main_tab)
        main_window = self.gui.get_object("WindowBox")

        def clear_main_window():
            main_window.foreach(main_window.remove)

        def add_main_window_item(item, name, **extra_args):
            # some widgets may want to override the defaults
            args = {"expand": False, "fill": False, "padding": 3}
            args.update(extra_args)
            main_window.pack_start(item, **args)

        main_tab.unparent()
        self.settings.register_ui_section("main_window", add_main_window_item,
                                          clear_main_window)
        self.settings.register_ui("main_window",
                                  "Tabs",
                                  main_tab,
                                  -20,
                                  args_dict={
                                      "expand": True,
                                      "fill": True
                                  })

        def disable_gui():
            self.menubar.set_sensitive(False)
            main_tab.set_sensitive(False)

        def enable_gui():
            self.menubar.set_sensitive(True)
            main_tab.set_sensitive(True)

        # configure locations of external programs
        for auto_control_name, location_control_name, browse_button, key in (
            ("ExternalProgramInkscapeAuto", "ExternalProgramInkscapeControl",
             "ExternalProgramInkscapeBrowse", "inkscape"),
            ("ExternalProgramPstoeditAuto", "ExternalProgramPstoeditControl",
             "ExternalProgramPstoeditBrowse", "pstoedit")):
            self.gui.get_object(auto_control_name).connect(
                "clicked", self._locate_external_program, key)
            location_control = self.gui.get_object(location_control_name)
            self.settings.add_item("external_program_%s" % key,
                                   location_control.get_text,
                                   location_control.set_text)
            self.gui.get_object(browse_button).connect(
                "clicked", self._browse_external_program_location, key)
        for objname, callback in (
            ("ResetWorkspace", lambda widget: self.reset_workspace()),
            ("LoadWorkspace", lambda widget: self.load_workspace_dialog()),
            ("SaveWorkspace",
             lambda widget: self.save_workspace_dialog(self.last_workspace_uri)
             ), ("SaveAsWorkspace",
                 lambda widget: self.save_workspace_dialog())):
            self.gui.get_object(objname).connect("activate", callback)
        # set the icons (in different sizes) for all windows
        # Gtk.window_set_default_icon_list(*get_icons_pixbuffers()) FIXME
        # load menu data
        gtk_menu_file = get_ui_file_location(GTKMENU_FILE)
        if gtk_menu_file is None:
            raise InitializationError(
                "Failed to load GTK menu specification file: {}".format(
                    gtk_menu_file))
        uimanager.add_ui_from_file(gtk_menu_file)
        # make the actions defined in the GTKBUILD file available in the menu
        actiongroup = Gtk.ActionGroup("menubar")
        for action in [
                aobj for aobj in self.gui.get_objects()
                if isinstance(aobj, Gtk.Action)
        ]:
            actiongroup.add_action(action)
        # the "pos" parameter is optional since 2.12 - we can remove it later
        uimanager.insert_action_group(actiongroup)
        # the "recent files" sub-menu
        if self.recent_manager is not None:
            recent_files_menu = Gtk.RecentChooserMenu(self.recent_manager)
            recent_files_menu.set_name("RecentFilesMenu")
            recent_menu_filter = Gtk.RecentFilter()
            case_converter = pycam.Utils.get_case_insensitive_file_pattern
            for filter_name, patterns in FILTER_MODEL:
                if not isinstance(patterns, (list, set, tuple)):
                    patterns = [patterns]
                # convert it into a mutable list (instead of set/tuple)
                patterns = list(patterns)
                for index in range(len(patterns)):
                    patterns[index] = case_converter(patterns[index])
                for pattern in patterns:
                    recent_menu_filter.add_pattern(pattern)
            recent_files_menu.add_filter(recent_menu_filter)
            recent_files_menu.set_show_numbers(True)
            # non-local files (without "file://") are not supported. yet
            recent_files_menu.set_local_only(False)
            # most recent files to the top
            recent_files_menu.set_sort_type(Gtk.RECENT_SORT_MRU)
            # show only ten files
            recent_files_menu.set_limit(10)
            uimanager.get_widget("/MenuBar/FileMenu/OpenRecentModelMenu"
                                 ).set_submenu(recent_files_menu)
            recent_files_menu.connect("item-activated",
                                      self.load_recent_model_file)
        else:
            self.gui.get_object("OpenRecentModel").set_visible(False)
        # load the menubar and connect functions to its items
        self.menubar = uimanager.get_widget("/MenuBar")
        # dict of all merge-ids
        menu_merges = {}

        def clear_menu(menu_key):
            for merge in menu_merges.get(menu_key, []):
                uimanager.remove_ui(merge)

        def append_menu_item(menu_key, base_path, widget, name):
            merge_id = uimanager.new_merge_id()
            if widget:
                action_group = widget.props.action_group
                if action_group not in uimanager.get_action_groups():
                    uimanager.insert_action_group(action_group, -1)
                widget_name = widget.get_name()
                item_type = Gtk.UIManagerItemType.MENU
            else:
                widget_name = name
                item_type = Gtk.UIManagerItemType.SEPARATOR
            uimanager.add_ui(merge_id, base_path, name, widget_name, item_type,
                             False)
            if menu_key not in menu_merges:
                menu_merges[menu_key] = []
            menu_merges[menu_key].append(merge_id)

        def get_menu_funcs(menu_key, base_path):
            return (lambda widget, name: append_menu_item(
                menu_key, base_path, widget, name),
                    lambda: clear_menu(menu_key))

        for ui_name, base_path in (("view_menu", "/MenuBar/ViewMenu"),
                                   ("file_menu", "/MenuBar/FileMenu"),
                                   ("edit_menu", "/MenuBar/EditMenu"),
                                   ("export_menu",
                                    "/MenuBar/FileMenu/ExportMenu")):
            append_func, clear_func = get_menu_funcs(ui_name, base_path)
            self.settings.register_ui_section(ui_name, append_func, clear_func)
        self.settings.register_ui("file_menu", "Quit",
                                  self.gui.get_object("Quit"), 100)
        self.settings.register_ui("file_menu", "QuitSeparator", None, 95)
        self.settings.register_ui("main_window", "Main", self.menubar, -100)
        self.settings.set("set_last_filename", self.add_to_recent_file_list)
        self._event_handlers.extend((
            ("history-changed", self._update_undo_button),
            ("model-change-after", "visual-item-updated"),
            ("gui-disable", disable_gui),
            ("gui-enable", enable_gui),
            ("notify-file-saved", self.add_to_recent_file_list),
            ("notify-file-opened", self.add_to_recent_file_list),
        ))
        for name, target in self._event_handlers:
            self.settings.register_event(name, target)
        # allow the task settings control to be updated
        self.mainloop.update()
        # register a logging handler for displaying error messages
        pycam.Utils.log.add_gtk_gui(self.window, logging.ERROR)
        self.window.show()
        self.mainloop.update()
Example #5
0
        # Otherwise it will be added later after writing the file.
        uri = pycam.Utils.URIHandler(filename)
        if uri.exists():
            # skip this, if the recent manager is not available (e.g. GTK 2.12.1 on Windows)
            if self.recent_manager:
                if self.recent_manager.has_item(uri.get_url()):
                    try:
                        self.recent_manager.remove_item(uri.get_url())
                    except GObject.GError:
                        pass
                self.recent_manager.add_item(uri.get_url())
            # store the directory of the last loaded file
            if uri.is_local():
                self.last_dirname = os.path.dirname(uri.get_local_path())

    def get_meta_data(self):
        filename = "Filename: %s" % str(self.last_model_uri)
        timestamp = "Timestamp: %s" % str(datetime.datetime.now())
        version = "Version: %s" % VERSION
        result = []
        for text in (filename, timestamp, version):
            result.append("%s %s" % (self.META_DATA_PREFIX, text))
        return os.linesep.join(result)


if __name__ == "__main__":
    GUI = ProjectGui()
    if len(sys.argv) > 1:
        GUI.load_model_file(sys.argv[1])
    get_mainloop().run()
Example #6
0
 def update(self, *args, **kwargs):
     get_mainloop().update()
     if self._progress:
         return self._progress.update(*args, **kwargs)
     else:
         return False
Example #7
0
 def update(self, text=None, percent=None):
     if text:
         self._last_text = text
     if percent:
         self._last_percent = percent
     if percent is not None:
         percent = min(max(percent, 0.0), 100.0)
         self._progress_bar.set_fraction(percent / 100.0)
     if (not percent) and (self._progress_bar.get_fraction() == 0):
         # use "pulse" mode until we reach 1% of the work to be done
         self._progress_bar.pulse()
     # update the GUI
     current_time = time.time()
     # Don't update the GUI more often than once per second.
     # Exception: text-only updates
     # This restriction improves performance and reduces the
     # "snappiness" of the GUI.
     if (self._last_gtk_events_time is None) \
             or text \
             or (self._last_gtk_events_time + 0.5 <= current_time):
         # "estimated time of arrival" text
         time_estimation_suffix = " remaining ..."
         if self._progress_bar.get_fraction() > 0:
             total_fraction = (
                 (self._progress_bar.get_fraction() + self._multi_counter) /
                 max(1, self._multi_maximum))
             total_fraction = max(0.0, min(total_fraction, 1.0))
             eta_full = (time.time() - self._start_time) / total_fraction
             if eta_full > 0:
                 eta_delta = eta_full - (time.time() - self._start_time)
                 eta_delta = int(round(eta_delta))
                 if hasattr(self, "_last_eta_delta"):
                     previous_eta_delta = self._last_eta_delta
                     if eta_delta == previous_eta_delta + 1:
                         # We are currently toggling between two numbers.
                         # We want to avoid screen flicker, thus we just live
                         # with the slight inaccuracy.
                         eta_delta = self._last_eta_delta
                 self._last_eta_delta = eta_delta
                 eta_delta_obj = datetime.timedelta(seconds=eta_delta)
                 eta_text = "%s%s" % (eta_delta_obj, time_estimation_suffix)
             else:
                 eta_text = None
         else:
             eta_text = None
         if text is not None:
             lines = [text]
         else:
             old_lines = self._progress_bar.get_text().split(os.linesep)
             # skip the time estimation line
             lines = [
                 line for line in old_lines
                 if not line.endswith(time_estimation_suffix)
             ]
         if eta_text:
             lines.append(eta_text)
         self._progress_bar.set_text(os.linesep.join(lines))
         # show the "show_tool_button" ("hide" is called in the progress decorator)
         # TODO: move "in_progress" somewhere else
         if self.core.get("toolpath_in_progress"):
             self._progress_button.show()
         get_mainloop().update()
         if not text or (self._start_time + 5 < current_time):
             # We don't store the timining if the text was changed.
             # This is especially nice for the snappines during font
             # initialization. This exception is only valid for the first
             # five seconds of the operation.
             self._last_gtk_events_time = current_time
     # return if the user requested a break
     return self._cancel_requested
Example #8
0
 def destroy(self, widget=None, data=None):
     get_mainloop().stop()