#!/usr/bin/env python # -*- mode: python; coding: utf-8; -*- # ---------------------------------------------------------------------------# # # Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer # Copyright (C) 2003 Mt. Hood Playing Card Co. # Copyright (C) 2005-2009 Skomoroh # Copyright (C) 2017 LB # # This program 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. # # This program 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 this program. If not, see . # # ---------------------------------------------------------------------------# # Note: # Many classes or some methods of classes are dead code resulting from the tk # implementation. If executed it would throw exceptions. # # Kivy Implementation used: MfxScrolledCanvas, MfxDialog (partly) from __future__ import division import logging from kivy.clock import Clock from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.boxlayout import BoxLayout from kivy.uix.label import Label from pysollib.kivy.LApp import LBoxLayout from pysollib.kivy.LApp import LScrollView from pysollib.kivy.LApp import LTopLevel from pysollib.kivy.LApp import LTopLevel0 from pysollib.kivy.LApp import get_menu_size_hint from pysollib.kivy.LImage import LImage from pysollib.kivy.tkcanvas import MfxCanvas from pysollib.kivy.tkutil import bind, unbind_destroy from pysollib.mfxutil import KwStruct, kwdefault from pysollib.mygettext import _ from pysollib.settings import WIN_SYSTEM # ************************************************************************ # * abstract base class for the dialogs in this module # ************************************************************************ class MfxDialog: # ex. _ToplevelDialog img = {} button_img = {} def __init__(self, parent, title="", resizable=False, default=-1): self.parent = parent self.status = 0 self.button = default self.timer = None self.buttons = [] self.accel_keys = {} self.window = LTopLevel0(parent, title=title) self.top = self.window.content def setSizeRule(obj, val): self.window.size_hint = get_menu_size_hint() self.parent.bind(size=setSizeRule) setSizeRule(0, 0) def wmDeleteWindow(self, *event): self.status = 1 raise SystemExit # return EVENT_HANDLED def mCancel(self, *event): self.status = 1 raise SystemExit def mTimeout(self, *event): self.status = 2 raise SystemExit def mDone(self, button): self.button = button raise SystemExit def altKeyEvent(self, event): key = event.char # key = unicode(key, 'utf-8') key = key.lower() button = self.accel_keys.get(key) if button is not None: self.mDone(button) def initKw(self, kw): kw = KwStruct(kw, timeout=0, resizable=False, text="", justify="center", strings=(_("&OK"), ), default=0, width=0, padx=20, pady=20, bitmap=None, bitmap_side="left", bitmap_padx=10, bitmap_pady=20, image=None, image_side="left", image_padx=10, image_pady=20, ) # default to separator if more than one button sep = len(kw.strings) > 1 kwdefault(kw.__dict__, separator=sep) return kw def createFrames(self, kw): a = LBoxLayout(orientation="vertical") b = LBoxLayout(orientation="vertical") return a, b # ************************************************************************ # Needed Labels. class MfxSimpleEntry: pass class MfxToolTip: pass # ************************************************************************ # * replacement for the tk_dialog script # ************************************************************************ # Kivy implementation helpers. class FLabel(Label): def __init__(self, **kw): super(FLabel, self).__init__(**kw) self.bind(size=self.onUpdate) self.bind(pos=self.onUpdate) self.bind(text=self.onUpdate) def onUpdate(self, instance, size): self.size_hint_y = None self.text_size = self.width, None self.texture_update() self.height = self.texture_size[1] class FText(LScrollView): def __init__(self, **kw): super(FText, self).__init__(**kw) self.label = FLabel(**kw) self.add_widget(self.label) class MfxMessageDialog(MfxDialog): def __init__(self, parent, title, **kw): kw = self.initKw(kw) MfxDialog.__init__(self, parent, title, kw.resizable, kw.default) if (kw.image): image = LImage(texture=kw.image.texture, size_hint=(1, 1)) self.top.add_widget(image) label = FText(text=kw.text, halign='center') self.top.add_widget(label) # LB # nicht automatisch ein neues spiel laden. if (title == _("Game won")): self.status = 1 # self.button = 0 if (title == _("Game finished")): self.status = 1 # self.button = # ************************************************************************ # * # ************************************************************************ class MfxExceptionDialog(MfxMessageDialog): def __init__(self, parent, ex, title=_("Error"), **kw): kw = KwStruct(kw, bitmap="error") text = kw.get("text", "") if not text.endswith("\n"): text = text + "\n" text = text + "\n" if isinstance(ex, EnvironmentError) and ex.filename is not None: t = "[Errno %s] %s:\n%s" % ( ex.errno, ex.strerror, repr(ex.filename)) else: t = str(ex) kw.text = text + t MfxMessageDialog.__init__(self, parent, title, **kw.getKw()) # ************************************************************************ # * # ************************************************************************ class PysolAboutDialog(object): # Die einzige Instanz. AboutDialog = None def onClick(self, event): print('LTopLevel: onClick') PysolAboutDialog.AboutDialog.parent.popWork('AboutDialog') PysolAboutDialog.AboutDialog.running = False def __init__(self, app, parent, title, **kw): logging.info('PysolAboutDialog:') super(PysolAboutDialog, self).__init__() self._url = kw['url'] logging.info('PysolAboutDialog: txt=%s' % title) text = kw['text'] text = text + '\n' + self._url logging.info('PysolAboutDialog: txt=%s' % text) text = text + '\n\n' + 'Adaptation to Kivy/Android\n' + \ ' Copyright (C) (2016-24) LB' self.parent = parent self.app = app self.window = None self.running = False self.status = 1 # -> von help.py so benötigt self.button = 0 # -> von help.py so benötigt # bestehenden Dialog rezyklieren. logging.info('PysolAboutDialog: 1') onlyone = PysolAboutDialog.AboutDialog if (onlyone and onlyone.running): return if (onlyone): onlyone.parent.pushWork('AboutDialog', onlyone.window) onlyone.running = True return # neuen Dialog aufbauen. window = LTopLevel(parent, title, size_hint=(1.0, 1.0)) window.titleline.bind(on_press=self.onClick) self.parent.pushWork('AboutDialog', window) self.window = window self.running = True PysolAboutDialog.AboutDialog = self if kw['image']: image = LImage(texture=kw['image'].texture) image.size_hint = (1, 0.8) al = AnchorLayout() al.add_widget(image) al.size_hint = (1, 0.3) window.content.add_widget(al) label = FText(text=text, halign='center', size_hint=(1, 1)) window.content.add_widget(label) # ************************************************************************ # * a simple tooltip # ************************************************************************ # ToolTip - not used in Kivy - would not run without adaptations. ''' class MfxTooltip: last_leave_time = 0 def __init__(self, widget): # private vars self.widget = widget self.text = None self.timer = None self.cancel_timer = None self.tooltip = None self.label = None self.bindings = [] self.bindings.append(self.widget.bind("", self._enter)) self.bindings.append(self.widget.bind("", self._leave)) self.bindings.append(self.widget.bind("", self._leave)) # user overrideable settings self.timeout = 800 # milliseconds self.cancel_timeout = 5000 self.leave_timeout = 400 self.relief = 'solid' self.justify = 'left' self.fg = "#000000" self.bg = "#ffffe0" self.xoffset = 0 self.yoffset = 4 def setText(self, text): self.text = text def _unbind(self): if self.bindings and self.widget: self.widget.unbind("", self.bindings[0]) self.widget.unbind("", self.bindings[1]) self.widget.unbind("", self.bindings[2]) self.bindings = [] def destroy(self): self._unbind() self._leave() def _enter(self, *event): after_cancel(self.timer) after_cancel(self.cancel_timer) self.cancel_timer = None timediff = time.time() - MfxTooltip.last_leave_time if timediff < self.leave_timeout / 1000.: self._showTip() else: self.timer = after(self.widget, self.timeout, self._showTip) def _leave(self, *event): after_cancel(self.timer) after_cancel(self.cancel_timer) self.timer = self.cancel_timer = None if self.tooltip: self.label.destroy() destruct(self.label) self.label = None self.tooltip.destroy() destruct(self.tooltip) self.tooltip = None MfxTooltip.last_leave_time = time.time() def _showTip(self): self.timer = None if self.tooltip or not self.text: return # if isinstance(self.widget, (Tkinter.Button, Tkinter.Checkbutton)): # if self.widget["state"] == 'disabled': # return # x = self.widget.winfo_rootx() x = self.widget.winfo_pointerx() y = self.widget.winfo_rooty() + self.widget.winfo_height() x += self.xoffset y += self.yoffset self.tooltip = Tkinter.Toplevel() self.tooltip.wm_iconify() self.tooltip.wm_overrideredirect(1) self.tooltip.wm_protocol("WM_DELETE_WINDOW", self.destroy) self.label = Tkinter.Label(self.tooltip, text=self.text, relief=self.relief, justify=self.justify, fg=self.fg, bg=self.bg, bd=1, takefocus=0) self.label.pack(ipadx=1, ipady=1) self.tooltip.wm_geometry("%+d%+d" % (x, y)) self.tooltip.wm_deiconify() self.cancel_timer = after( self.widget, self.cancel_timeout, self._leave) # self.tooltip.tkraise() ''' # ************************************************************************ # * A canvas widget with scrollbars and some useful bindings. # ************************************************************************ # Kivy implementation of MfxScrolledCanvas. from kivy.uix.scatterlayout import Scatter # noqa from kivy.uix.stencilview import StencilView # noqa from kivy.graphics.transformation import Matrix # noqa class LScatterFrame(Scatter): def __init__(self, inner, **kw): super(LScatterFrame, self).__init__(**kw) self.inner = inner self.add_widget(inner) self.bind(pos=self._updatepos) self.bind(size=self._updatesize) self.do_rotation = False self.scale_min = 1.0 self.scale_max = 2.2 self.lock_pos = None self.lock_chk = None self.offset = None self.tkopt = None def set_scale(self,zoom): scale = zoom[0] self.transform = Matrix().scale(scale,scale,1) xoff = zoom[1] yoff = zoom[2] self.offset = (xoff,yoff) def _change_command(self,inst,val): if self.lock_pos is None: self.set_scale(val) def _update(self): # initialisation if self.tkopt is None: app = self.inner.wmain.app tkopt = None if app is not None: tkopt = app.menubar.tkopt if tkopt is not None: self.tkopt = tkopt self.set_scale(tkopt.table_zoom.value) print("table_zoom",tkopt.table_zoom.value) tkopt.table_zoom.bind(value=self._change_command) # update if self.lock_pos is None: self.lock_pos = "locked" if self.offset is not None: dx = round(self.offset[0] * (self.bbox[1][0] - self.size[0])) dy = round(self.offset[1] * (self.bbox[1][1] - self.size[1])) self.pos = (self.parent.pos[0]-dx,self.parent.pos[1]-dy) if self.lock_chk is None: Clock.schedule_once(lambda dt: self.chk_bnd()) # noqa self.lock_pos = None # print("_update",self.pos,self.size) def _updatesize(self,instance,value): self.inner.size = self.size self._update() def _updatepos(self,instance,value): self._update() def collide_point(self,x,y): px,py = self.parent.pos sx,sy = self.parent.size if (px<=x and x<(px+sx) and py<=y and y<(py+sy)): return True return False def on_touch_down(self, touch): ret = False x,y = touch.pos if self.collide_point(x,y): if touch.is_double_tap: # Do not use the event handling of scatter because scatter # does not allow to propagate an unhandled double tap back # to parent (it grabs the touch unseen if not # handled by a child!). touch.push() touch.apply_transform_2d(self.to_local) ret = self.inner.on_touch_down(touch) touch.pop() else: ret = super(LScatterFrame, self).on_touch_down(touch) return ret def on_touch_up(self, touch): if touch.grab_current == self: return super(LScatterFrame, self).on_touch_up(touch) x,y = touch.pos if self.collide_point(x,y): return super(LScatterFrame, self).on_touch_up(touch) return False def on_touch_move(self, touch): ret = False self.lock_pos = "locked" ret = super(LScatterFrame, self).on_touch_move(touch) self.lock_pos = None return ret def on_transform_with_touch(self,touch): self.chk_bnd() def chk_bnd(self): # Keep the game on the screen. # check and set lock if self.lock_chk is not None: return self.lock_chk = "locked" # limiting parameters: pos,size = self.bbox w,h = size x,y = pos px,py = self.parent.pos sx,sy = self.parent.size # calculate correction matrix and apply tm = Matrix() if (x>px): tm = tm.multiply(Matrix().translate(px-x,0,0)) if (y>py): tm = tm.multiply(Matrix().translate(0,py-y,0)) if ((x+w) <= (px+sx)): tm = tm.multiply(Matrix().translate(px+sx-x-w,0,0)) if ((y+h) <= (py+sy)): tm = tm.multiply(Matrix().translate(0,py+sy-y-h,0)) self.apply_transform(tm) # save current offset. self.offset = None offx = self.parent.pos[0] - self.pos[0] offy = self.parent.pos[1] - self.pos[1] offmx = float(self.bbox[1][0] - self.size[0]) offmy = float(self.bbox[1][1] - self.size[1]) if (offmx>0 and offmy>0): self.offset = (offx/offmx,offy/offmy) # update persistent zoom parameters zoom = self.bbox[1][0]/float(self.size[0]) if self.offset is not None: zoominfo = [zoom, self.offset[0], self.offset[1]] else: zoominfo = [zoom, 0.0, 0.0] self.tkopt.table_zoom.value = zoominfo # remove lock self.lock_chk = None class LScrollFrame(BoxLayout,StencilView): def __init__(self, **kw): super(LScrollFrame, self).__init__(orientation="vertical", **kw) class MfxScrolledCanvas(object): def __init__(self, parent, hbar=True, vbar=True, propagate=False, **kw): kwdefault(kw, highlightthickness=0, bd=1, relief='sunken') self.parent = parent # workarea = parent.getWork() print('MfxScrolledCanvas: parent=%s' % (parent)) super(MfxScrolledCanvas, self).__init__() self.createFrame(kw) self.canvas = None # do_scroll_x = None # do_scroll_y = None # self.hbar = None # self.vbar = None self.hbar_show = False self.vbar_show = False self.createCanvas(kw) # self.frame.grid_rowconfigure(0, weight=1) # self.frame.grid_columnconfigure(0, weight=1) # self.frame.grid_propagate(propagate) if hbar: self.createHbar() self.bindHbar() if vbar: self.createVbar() self.bindVbar() # self.canvas.focus_set() def destroy(self): logging.info('MfxRoot: destroy') self.unbind_all() self.canvas.destroy() self.frame.destroy() def pack(self, **kw): pass # self.frame.pack(**kw) def grid(self, **kw): pass # self.frame.grid(**kw) # # # def setTile(self, app, i, scale_method, force=False): logging.info('MfxRoot: setTitle app=%s' % app) tile = app.tabletile_manager.get(i) print('setTile: (tile) %s, index=%s' % (tile, i)) if tile is None or tile.error: return False # print i, tile if i == 0: assert tile.color assert tile.filename is None else: assert tile.color is None assert tile.filename assert tile.basename if not force: if (i == app.tabletile_index and tile.color == app.opt.colors['table']): return False # print('setTile2: %s, %s' % (tile.filename, tile.color)) if not self.canvas.setTile( tile.filename, tile.stretch, tile.save_aspect): tile.error = True return False print( 'MfxScrolledCanvas: tile.color, app.top_bg %s, %s' % (tile.color, app.top_bg)) if i == 0: if force: tile.color = app.opt.colors['table'] self.canvas.config(bg=tile.color) else: if type(app.top_bg) is str: self.canvas.config(bg=app.top_bg) self.canvas.setTextColor(app.opt.colors['text']) return True # # # def deleteAllItems(self): logging.info('MfxRoot: deleteAllItems') # self.parent.getWork() # self.parent.popWork() # self.frame.clear_widgets() self.canvas.clear_widgets() def update_idletasks(self): logging.info('MfxRoot: update_idletasks') Clock.schedule_once(lambda x: self.canvas.canvas.ask_update) def unbind_all(self): unbind_destroy(self.hbar) unbind_destroy(self.vbar) unbind_destroy(self.canvas) unbind_destroy(self.frame) def createFrame(self, kw): logging.info('MfxRoot: createFrame') # width = kw.get("width") # height = kw.get("height") print('createFrame: kw=%s' % kw) # self.frame = Tkinter.Frame(self.parent, width=width, height=height) self.frame = LScrollFrame(size_hint=(1, 1)) print("createFrame: self.parent %s" % str(self.frame)) def createCanvas(self, kw): logging.info('MfxRoot: createCanvas') kw['bd'] = 0 del kw['relief'] self.canvas = MfxCanvas(self.parent, **kw) scatter = LScatterFrame(self.canvas) self.frame.add_widget(scatter) self.parent.pushWork('playground', self.frame) def createHbar(self): pass ''' self.hbar = Tkinter.Scrollbar(self.frame, takefocus=0, orient="horizontal") self.canvas["xscrollcommand"] = self._setHbar self.hbar["command"] = self.canvas.xview self.hbar.grid(row=1, column=0, sticky="we") self.hbar.grid_remove() ''' def createVbar(self): pass ''' self.vbar = Tkinter.Scrollbar(self.frame, takefocus=0) self.canvas["yscrollcommand"] = self._setVbar self.vbar["command"] = self.canvas.yview self.vbar.grid(row=0, column=1, sticky="ns") self.vbar.grid_remove() ''' def bindHbar(self, w=None): if w is None: w = self.canvas bind(w, "", self.unit_left) bind(w, "", self.unit_right) def bindVbar(self, w=None): if w is None: w = self.canvas bind(w, "", self.page_up) bind(w, "", self.page_down) bind(w, "", self.unit_up) bind(w, "", self.unit_down) bind(w, "", self.scroll_top) bind(w, "", self.scroll_top) bind(w, "", self.scroll_bottom) # mousewheel support if WIN_SYSTEM == 'x11': bind(w, '<4>', self.mouse_wheel_up) bind(w, '<5>', self.mouse_wheel_down) # don't work on Linux # bind(w, '', self.mouse_wheel) def mouse_wheel(self, *args): pass def _setHbar(self, first, last): if self.canvas.busy: return sb = self.hbar if float(first) <= 0 and float(last) >= 1: sb.grid_remove() self.hbar_show = False else: if self.canvas.winfo_ismapped(): sb.grid() self.hbar_show = True sb.set(first, last) def _setVbar(self, first, last): if self.canvas.busy: return sb = self.vbar if float(first) <= 0 and float(last) >= 1: sb.grid_remove() self.vbar_show = False else: if self.canvas.winfo_ismapped(): sb.grid() self.vbar_show = True sb.set(first, last) def _xview(self, *args): if self.hbar_show: self.canvas.xview(*args) return 'break' def _yview(self, *args): if self.vbar_show: self.canvas.yview(*args) return 'break' def page_up(self, *event): return self._yview('scroll', -1, 'page') def page_down(self, *event): return self._yview('scroll', 1, 'page') def unit_up(self, *event): return self._yview('scroll', -1, 'unit') def unit_down(self, *event): return self._yview('scroll', 1, 'unit') def mouse_wheel_up(self, *event): return self._yview('scroll', -5, 'unit') def mouse_wheel_down(self, *event): return self._yview('scroll', 5, 'unit') def page_left(self, *event): return self._xview('scroll', -1, 'page') def page_right(self, *event): return self._xview('scroll', 1, 'page') def unit_left(self, *event): return self._xview('scroll', -1, 'unit') def unit_right(self, *event): return self._xview('scroll', 1, 'unit') def scroll_top(self, *event): return self._yview('moveto', 0) def scroll_bottom(self, *event): return self._yview('moveto', 1) # ************************************************************************ # * # ************************************************************************ # not used witch kivy. would not nun as it refers TkInter.